diff --git a/.claude/skills/reclamm-pair-onboarding/SKILL.md b/.claude/skills/reclamm-pair-onboarding/SKILL.md new file mode 100644 index 0000000..880930c --- /dev/null +++ b/.claude/skills/reclamm-pair-onboarding/SKILL.md @@ -0,0 +1,102 @@ +--- +name: reclamm-pair-onboarding +description: Onboard a new token pair for reCLAMM simulations — fetch price data, run the Optuna parameter sweep, and produce final-sim CSVs/plots with candidate pool params. Use when asked to simulate a reCLAMM pool for a pair, gather candidate price_ratio/margin/shift params, or add a pair to run_final_sims.py. +--- + +# reCLAMM pair onboarding + +Pipeline: **price data → Optuna sweep → register pair → `run_final_sims.py`**. +Worked examples on branch `btc-eth-pair-onboarding`: BTC/ETH (Binance data, +real pool coefficients) and BOLD/USDC (CoinGecko data, median-fallback noise). + +**Never retrain the noise model.** The checked-in artifacts +(`results/mm_noise/{model.npz,meta.json}`, `results/competitor_tvl/competitor_tvl.npz`) +are fit across 38 pools and reused for every pair; per-pair arrays are built +and cached automatically under `results/mm_noise/_sim_arrays/`. + +## Step 1 — price data (`quantammsim/data/_USD.parquet`) + +`BTC_USD.parquet` is required for **every** pair (market features), plus one +parquet per pool token (`TOKEN_MAP` in `quantammsim/calibration/market_features.py` +maps WBTC→BTC, WETH→ETH, USDT→USDC, …). + +- **Binance-listed token**: `python scripts/download_data.py `. +- **Not on Binance**: copy `scripts/prepare_bold_usdc_data.py` (CoinGecko): + daily close+volume → minute grid by forward-fill, daily volume spread /1440 + (so the daily resample recovers real volume), stables as flat $1.00 peg. + CoinGecko free tier = **last 365 days only** — this bounds the earliest + simulation start and usually forces a per-pair train window (Step 4). + +Check coverage: the parquet must span train start → test end. + +## Step 2 — pool id and the noise fallback + +Look the pair up in `results/mm_noise/meta.json` (`pool_ids` / `pool_tokens`). + +- **Pair present** (e.g. WBTC/WETH → `0xa6f548df93de92`): use that id; the + noise model gets real per-pool coefficients and competitor TVL. +- **Pair absent**: any placeholder id works — the builder falls back to + median coefficients + K=$10M (`noise_model_arrays.py`). Median alpha is + tiny, so organic volume (and fee revenue) will be near zero. If realistic + fees matter, re-anchor: after `build_mm_simulator_arrays`, shift + `noise_base += log(V_max_daily) - mean(noise_base)` and optionally set a + constant competitor K — see `scripts/run_rpl_eth_sweep.py` (~lines 124-134) + for the pattern and the pair's real daily volume for V_max. + +## Step 3 — Optuna sweep + +Demo scale (~50 trials, minutes; the team's production scale is +`scripts/run_full_sweep.sh`: 300 trials × 4 objectives × 3 penalties per TVL): + +``` +python scripts/tune_reclamm_calibrated_noise.py \ + --noise-model mm_observed --artifact-dir results/mm_noise \ + --tokens BTC ETH --pool-id 0xa6f548df93de92 \ + --gas-cost 1.0 --fees 0.0025 --initial-pool-value 5000000 \ + --objective returns_over_hodl --n-trials 50 --pr-max 5.0 \ + --start-date "2025-01-01 00:00:00" --end-date "2025-10-05 00:00:00" \ + --output results/full_sweep/returns_over_hodl__.json +``` + +- One sweep per (pair, TVL). Search space: price_ratio 1.01–200 (log), + margin 0.01–0.99, shift_exponent 1e-5–125 (log). Cap `--pr-max` sensibly: + ~5 for correlated majors, ~1.05 for stable/stable. +- Dates: default train window is 2025-01-01 → 2025-10-05 (pre flash-crash); + keep it unless data starts later. `--end-test-date` sets the OOS span used + for validation metrics. +- Side effect (the part `run_final_sims.py` actually reads): a trajectory + file `results/run_.json` written by `train_on_historic_data`. + +## Step 4 — register the pair and run final sims + +Add an entry to `PAIR_CONFIGS` in `scripts/run_final_sims.py` (tokens, +pool_id, gas_cost, fees, the swept TVLs; `--pair` choices follow the dict). +If the pair's data can't cover the default windows, add per-pair overrides: + +```python +"train": ("2025-07-15 00:00:00", "2025-10-05 00:00:00"), # optional +"test": ("2025-10-25 00:00:00", "2026-05-01 00:00:00"), # optional +``` + +The sweep's train window **must match** the pair's train window — trials are +filtered by it (`filter_trials_to_window`). + +``` +python scripts/run_final_sims.py --pair # or --all +``` + +Selection: best trial per TVL by OOS `returns_over_hodl` (override with +`--metric`). Outputs in `results/final_sims/`: +`run_{Value,Reserves,TokenValues}___{train,test}_.csv`, +themed plots `_{train,test}[_weights]_{light,dark}.png`, and +`_sim_results.pkl`. The printed summary gives train/test RoH and fees. + +## Gotchas + +- `--method` on `run_final_sims.py` is currently a no-op. +- First run per (pool, window) builds noise arrays (needs network-free local + parquets only); subsequent runs hit the `_sim_arrays` cache. +- Stable pairs: expect PR near the 1.01 floor and near-zero fees under the + median-fallback noise — re-anchor (Step 2) before trusting fee numbers. +- Sweeps and final sims are pure JAX forward passes — a laptop handles demo + scale; production sweeps want the parallel `run_full_sweep.sh` machinery. diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 2e4b5f6..8647a5e 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -13,8 +13,6 @@ jobs: steps: - uses: actions/checkout@v4 - with: - lfs: true - name: Set up Python 3.9 uses: actions/setup-python@v5 @@ -25,7 +23,7 @@ jobs: - name: Install dependencies run: | python -m pip install --upgrade pip - pip install -e ".[dev]" + pip install -e ".[dev,calibration]" - name: Run tests with coverage run: | diff --git a/RECLAMM_PLOTS.md b/RECLAMM_PLOTS.md new file mode 100644 index 0000000..1339fc4 --- /dev/null +++ b/RECLAMM_PLOTS.md @@ -0,0 +1,143 @@ +# Reproducing the AAVE/ETH and COW/ETH plots + +This branch (`noise-modelling-fixes-data`) carries the minimum data needed to +produce the final train/test panels and PR-sweep heatmaps for both pairs +without re-running the training sweep. + +## What's on the branch + +- `results/mm_noise/model.npz` + `meta.json` — the frozen MM noise model. +- `results/competitor_tvl/competitor_tvl.npz` — DeFi Llama competitor TVL. +- `results/full_sweep/<6 files>` — sweep summaries for the 6 winning configs. +- `results/run_<6 hashes>.json` — trial trajectories for the same 6 winners + (read by `scripts/run_final_sims.py`). + +What's **not** included and must be pulled locally: + +- Binance minute parquets for `BTC`, `ETH`, `AAVE`, `COW`. + +## Prerequisites + +Conda env per the README: + +``` +conda activate qsim +``` + +## 1. Pull Binance price data + +``` +python scripts/download_data.py BTC ETH AAVE COW +``` + +Writes `quantammsim/data/_USD.parquet` per token. `BTC` is required by +the MM noise model's market features; `AAVE`/`COW`/`ETH` are the pair tokens +the simulator reads prices from. + +## 2. Final-sims: train/test forward passes and selection + +``` +python scripts/run_final_sims.py --all +``` + +Loads the 6 committed `run_.json` files, picks the single candidate per +`(pair, TVL)` tier, runs train and test forward passes at the picked params, +and writes: + +- `results/final_sims/{aave,cow}_{train,test}.png` — share price, fee revenue, + cumulative volume per TVL tier. +- `results/final_sims/{aave,cow}_{train,test}_weights.png` — effective weight + trajectories. +- `results/final_sims/{aave,cow}_sim_results.pkl` — per-tier results cache. + +Per-tier picks for this branch's data (printed to stdout as `Params:` lines): + +| Tier | PR | margin | shift | +|-----------|---------|--------|--------| +| AAVE 1m | 1.068 | 0.0240 | 0.0225 | +| AAVE 5m | 1.296 | 0.0101 | 0.0675 | +| AAVE 20m | 1.348 | 0.0103 | 0.5411 | +| COW 500k | 99.665 | 0.8235 | 0.1418 | +| COW 2m | 54.549 | 0.8351 | 0.1422 | +| COW 20m | 172.463 | 0.9265 | 0.0521 | + +## 3. PR-sweep heatmaps + +One `run_pr_sweep.py` invocation per (pair, TVL), with that tier's +`margin`, `shift`, `initial-pool-value`, and the selected PR as a marker. + +### AAVE/ETH + +Override the pair-specific flags (`run_pr_sweep.py` defaults are COW). + +``` +mkdir -p results/final_sims/for_fabio/aave/price_ratio_sweep + +python scripts/run_pr_sweep.py --tokens AAVE ETH --pool-id 0x9d1fcf346ea1b0 \ + --gas-cost 1.0 --fees 0.0025 \ + --multi-period --period-months 3 --onchain-pr 2.02 \ + --output-dir results/final_sims/for_fabio/aave/price_ratio_sweep \ + --prs 1.01 1.1 1.2 1.4 1.6 1.8 2.0 2.5 3.0 4.0 5.0 7.5 10.0 \ + --margin 0.0240 --shift 0.0225 --initial-pool-value 1000000 --selected-pr 1.068 + +python scripts/run_pr_sweep.py --tokens AAVE ETH --pool-id 0x9d1fcf346ea1b0 \ + --gas-cost 1.0 --fees 0.0025 \ + --multi-period --period-months 3 --onchain-pr 2.02 \ + --output-dir results/final_sims/for_fabio/aave/price_ratio_sweep \ + --prs 1.01 1.1 1.2 1.4 1.6 1.8 2.0 2.5 3.0 4.0 5.0 7.5 10.0 \ + --margin 0.0101 --shift 0.0675 --initial-pool-value 5000000 --selected-pr 1.296 + +python scripts/run_pr_sweep.py --tokens AAVE ETH --pool-id 0x9d1fcf346ea1b0 \ + --gas-cost 1.0 --fees 0.0025 \ + --multi-period --period-months 3 --onchain-pr 2.02 \ + --output-dir results/final_sims/for_fabio/aave/price_ratio_sweep \ + --prs 1.01 1.1 1.2 1.4 1.6 1.8 2.0 2.5 3.0 4.0 5.0 7.5 10.0 \ + --margin 0.0103 --shift 0.5411 --initial-pool-value 20000000 --selected-pr 1.348 +``` + +### COW/ETH + +The COW selections are at PR ≈ 50–200, so the `--prs` list extends past 10. +The script's defaults already match the COW pair, so the pair-specific flags +can be omitted. + +``` +mkdir -p results/final_sims/for_fabio/cow/price_ratio_sweep + +python scripts/run_pr_sweep.py --multi-period --period-months 3 --onchain-pr 2.02 \ + --output-dir results/final_sims/for_fabio/cow/price_ratio_sweep \ + --prs 1.01 1.1 1.2 1.4 1.6 1.8 2.0 2.5 3.0 5.0 10.0 30.0 50.0 80.0 100.0 120.0 150.0 200.0 \ + --margin 0.8235 --shift 0.1418 --initial-pool-value 500000 --selected-pr 99.665 + +python scripts/run_pr_sweep.py --multi-period --period-months 3 --onchain-pr 2.02 \ + --output-dir results/final_sims/for_fabio/cow/price_ratio_sweep \ + --prs 1.01 1.1 1.2 1.4 1.6 1.8 2.0 2.5 3.0 5.0 10.0 30.0 50.0 80.0 100.0 120.0 150.0 200.0 \ + --margin 0.8351 --shift 0.1422 --initial-pool-value 2000000 --selected-pr 54.549 + +python scripts/run_pr_sweep.py --multi-period --period-months 3 --onchain-pr 2.02 \ + --output-dir results/final_sims/for_fabio/cow/price_ratio_sweep \ + --prs 1.01 1.1 1.2 1.4 1.6 1.8 2.0 2.5 3.0 5.0 10.0 30.0 50.0 80.0 100.0 120.0 150.0 200.0 \ + --margin 0.9265 --shift 0.0521 --initial-pool-value 20000000 --selected-pr 172.463 +``` + +### Outputs + +Per-period single-config PNGs and one heatmap per invocation, named +`pr_heatmap__3mo_m_s.png`, under +`results/final_sims/for_fabio/{aave,cow}/price_ratio_sweep/`. + +## Gotchas + +- **zsh and shell variables**. `python ... $PRS_ARGS` with + `PRS_ARGS="--prs 1.01 1.1 ..."` does not word-split in zsh by default; + argparse receives the whole string as one token and emits + `unrecognized arguments: --prs ...`. Either inline the list (as above) + or use `${=PRS_ARGS}`. +- **COW early-period failures**. The COW pool's data starts around 2024-12. + `run_pr_sweep.py --multi-period` tries each period from `2024-01-01` + onwards; the early periods raise inside `run_single_period`, the script + catches the exception and the final heatmap only contains the surviving + rows. Expected. +- **Re-running with the same `(start, end)` reuses the cached noise array** + in `results/mm_noise/_sim_arrays/___mm.npz` — + rebuilding only the first time each period is touched. diff --git a/RECLAMM_TRAINING.md b/RECLAMM_TRAINING.md new file mode 100644 index 0000000..658e925 --- /dev/null +++ b/RECLAMM_TRAINING.md @@ -0,0 +1,541 @@ +# reCLAMM training: end-to-end + +Soup-to-nuts guide for going from no data on disk to a set of trained reCLAMM +params and the heatmap / weight / fee-revenue plots used in reports. + +All commands assume the working directory is the repo root and the canonical +conda env from the README (`qsim`) is active: + +``` +conda activate qsim +``` + +The pipeline has five stages. Outputs of each stage feed the next, so order +matters. + +``` +[1] Data pull → quantammsim/data/*.parquet, local_data/..., + results/competitor_tvl/, results/pool_grids_v2/ +[2] Grid build → results/pool_grids_v2/*_daily.parquet +[3] MM noise model → results/mm_noise/model.npz + meta.json +[4] Training sweep → results/full_sweep/*.json + results/run_*.json +[5] Selection + plots → results/final_sims/{aave,cow}*.png + .pkl + results/final_sims/for_fabio//price_ratio_sweep/* +``` + +### What depends on what + +The pipeline has two distinct dependency surfaces, separated by the MM model +artifact. + +Training the MM noise model (stages 1+2+3) needs: + +- Balancer V3 API (`api-v3.balancer.fi`) — panel snapshots +- Binance minute parquets — token price series +- DeFi Llama — competitor TVL +- Pool grids (`results/pool_grids_v2/`) — built locally from the panel + +Training a reCLAMM (stages 4+5) only needs: + +- Binance minute parquets (`quantammsim/data/_USD.parquet`) +- The MM artifact (`results/mm_noise/model.npz` + `meta.json`) +- The competitor-TVL bundle (`results/competitor_tvl/competitor_tvl.npz`) + +`quantammsim/runners/jax_runners.py` does not import from +`quantammsim.noise_calibration.*`, and +`quantammsim/calibration/noise_model_arrays.build_mm_simulator_arrays` reads +only Binance data plus the two artifacts (standardisation stats are stored +inside `model.npz` as `x_mean` / `x_std`). Once the MM model exists you can +train reCLAMMs on a different machine by carrying `results/mm_noise/`, +`results/competitor_tvl/`, and the Binance parquets for the pair. + +--- + +## 1. Pull the data + +Three data sources, three scripts. + +### 1a. Token price history (Binance minute bars) + +``` +python scripts/download_data.py BTC ETH AAVE COW USDC USDT WBTC +``` + +Reads from `scripts/ticker_list.txt` if no tickers are passed. Output: +`quantammsim/data/_USD.parquet` (minute resolution) and +`_USD_daily.csv`. Used by all simulator runs and noise calibration. +`USDT` is fetched against `USD` when needed, since `USDT/USDT` is not a real +market. + +### 1b. Balancer pool snapshots (volume + TVL) + +``` +python -m quantammsim.noise_calibration --fetch --chain ethereum +python -m quantammsim.noise_calibration --fetch --chain base +python -m quantammsim.noise_calibration --fetch --chain gnosis +``` + +Fetches the V3 API (`api-v3.balancer.fi`) for WEIGHTED and RECLAMM pools, then +pulls daily snapshots and assembles a panel. Outputs the panel parquet under +`local_data/noise_calibration/panel.parquet`. + +#### How the pool list is determined + +`--fetch` does not take a pool or pair argument. It calls +`enumerate_balancer_pools(min_tvl=args.min_tvl)`, which asks the Balancer V3 +API for every WEIGHTED + RECLAMM pool currently above `--min-tvl` (default +$10k) on the specified chain. The threshold is checked against the pool's +*current* TVL at fetch time, so a pool that once held large TVL but is now +below the floor will be excluded — even if its earlier history would have +been useful. + +The calibration set the MM model is trained on is the survivor set after +downstream filters: pools with both tokens matched to Binance data and with +enough clean daily snapshots. The survivors are cached in +`results/token_factored_calibration/_cache/stage1.pkl` (the `matched_clean` +dict). +`scripts/fetch_competitor_tvl.py` and `scripts/run_mm_noise.py` both read +that file for their pool list and do not expose a per-pool selector. + +To get a specific pool included: + +1. Confirm its current TVL is above `--min-tvl`, or lower the threshold. +2. Make sure both tokens have Binance parquets in `quantammsim/data/` + (`scripts/download_data.py `). +3. Re-run `--fetch` → `fetch_competitor_tvl.py` → `run_mm_noise.py`. Any + pool that passes the threshold and Binance-match check ends up in the + trained MM artifact. + +#### How the pool address propagates downstream + +`scripts/tune_reclamm_calibrated_noise.py` takes `--pool-id`. It is used +only as a lookup key into the frozen MM artifact: +`_find_pool_index(pool_id, meta["pool_ids"])` returns the per-pool +`log_alpha`, `gamma`, `log_K`, and `log_cadence`. The address is not used +to fetch price data — prices come from Binance via `--tokens `. So +`--tokens AAVE ETH --pool-id 0xnotreal` will run reCLAMM training with +AAVE/ETH price feeds but with the median MM parameters and `K = $10M` +fallback documented in §6. + +`scripts/run_full_sweep.sh` hard-codes the 6 (token_a, token_b, pool_id, +gas, fees, tvl_label, initial_tvl) tuples it sweeps, which is where the +pool address is pinned per-job. + +### 1c. Competitor TVL (per-pair, per-chain) — DeFi Llama + +``` +python scripts/fetch_competitor_tvl.py +# optional: python scripts/fetch_competitor_tvl.py --cache-dir results/competitor_tvl +``` + +For each of the calibration pools, finds all other DEX pools trading the same +token pair and sums their daily TVL. Output: + +- `results/competitor_tvl/competitor_tvl.npz` — `(n_dates, n_pools)` array of + daily competitor TVL in USD, used as `K_i(t) = sum_{j ≠ i} TVL_j(t)` in the + MM noise model. +- `results/competitor_tvl/___history.pkl` — per-pair cache + for re-runs. + +Optional token-mcaps cache (used by some plotting / classification code): + +``` +python scripts/fetch_token_mcaps.py +``` + +writes `local_data/noise_calibration/token_mcaps.json`. + +--- + +## 2. Build the per-pool arb-volume grids + +``` +python scripts/build_pool_grids.py --workers 1 --train-days 90 +``` + +Increase the worker count based on the memory available on your machine. + +Sweeps `(cadence, gas)` per real Balancer pool to produce PCHIP grids of daily +arb volume. Output: `results/pool_grids_v2/_daily.parquet` per +pool, plus a summary CSV. The grids are what the joint calibration model +interpolates over to attribute total observed volume to arb vs noise. + +This step is slow; it forward-simulates each pool across `cadence × gas` for +the chosen training window. Use a non-default `--workers` to parallelise. + +--- + +## 3. Train the Michaelis-Menten noise model + +Fits the per-pool MM model that the simulator uses to predict noise volume at +run-time: + +``` +log(V_noise) = log_alpha_i + x_market @ gamma + log(TVL) − log(K_i + TVL) +V_total = V_arb(cadence_i) + exp(log_V_noise) +Loss = Huber(log(V_total) − log(V_obs)) +``` + +First generate the shared stage-1 cache used by the competitor-TVL and MM +scripts: + +``` +python scripts/run_token_factored_calibration.py --stage1-only +``` + +This writes `results/token_factored_calibration/_cache/stage1.pkl`. + +``` +python scripts/run_mm_noise.py \ + --per-pool-gamma --epochs 5000 --lr 1e-4 \ + --huber-delta 0.5 --observed-K --no-split +``` + +The hparams the existing artifact was fit with are stored in +`results/mm_noise/meta.json` under `hparams`; reproduce by matching them on +the command line. + +Loads the panel from `local_data/noise_calibration/panel.parquet`, matches +each row to the right per-day grid in `results/pool_grids_v2/`, and fits the +MM model jointly across all pools. Output: + +- `results/mm_noise/model.npz` — fitted parameters (per-pool `log_alpha`, + `log_K`, `log_cadence`, shared `gamma`). +- `results/mm_noise/meta.json` — `pool_ids`, `n_market_feat`, + `per_pool_gamma`, `hparams`. +- `results/mm_noise/trials/trial_NNNN/` — checkpoint per trial during a + hyperparameter sweep. + +`meta.json` has the canonical schema: + +```json +{ + "model": "michaelis_menten", + "pool_ids": ["0x9d1fcf346ea1b0", "0xd321300ef77067", ...], + "n_market_feat": 18, + "per_pool_gamma": true, + "hparams": {"epochs": 5000, "lr": 1e-4, "l2_alpha": 1e-3, + "huber_delta": 0.5, "init_log_K": 17.0} +} +``` + +Diagnostic plot: + +``` +python scripts/plot_mm_noise_fit.py +``` + +writes per-pool fit overlays to `results/mm_noise/plots/`. + +--- + +## 3b. Smoke test before sweeping + +Before kicking off a multi-hour sweep, run a tiny single-config job to verify +the data, the MM artifact, and the competitor-TVL bundle are all in place and +the simulator returns sensible numbers: + +``` +python scripts/tune_reclamm_calibrated_noise.py \ + --noise-model mm_observed --artifact-dir results/mm_noise \ + --n-trials 5 \ + --tokens AAVE ETH --pool-id 0x9d1fcf346ea1b0 \ + --gas-cost 1.0 --fees 0.0025 \ + --initial-pool-value 1000000 \ + --objective calmar \ + --start-date "2025-01-01 00:00:00" --end-date "2025-10-05 00:00:00" \ + --output /tmp/smoke.json +``` + +Completes in a couple of minutes. A non-empty `/tmp/smoke.json` and a new +`results/run_.json` with five trial entries means the pipeline is wired +correctly end-to-end. `scripts/demo_run_reclamm.py` is a complementary smoke +test that runs reCLAMM and Balancer-50/50 forward passes side-by-side without +training, useful for sanity-checking the simulator independently of any +optimiser. + +--- + +## 4. Training sweep + +`scripts/run_full_sweep.sh` is the orchestrator; it shells out to +`scripts/tune_reclamm_calibrated_noise.py` per job. Each (pair, TVL tier, +objective, penalty) combination is one job. With the default config there are +72 jobs per method: + +- 2 pairs (AAVE/ETH, COW/ETH) × 3 TVL tiers = 6 configs +- 4 objectives: `returns_over_hodl`, `fee_revenue_over_value`, `calmar`, + `daily_log_sharpe_excess` +- 3 penalty variants: no penalty, `--overfitting-penalty 1.0`, `5.0` + +``` +# Optuna (default, 300 trials per job) +MAX_WORKERS=6 bash scripts/run_full_sweep.sh + +# CMA-ES (500 generations per job) +MAX_WORKERS=6 bash scripts/run_full_sweep.sh --method cma_es + +# Single pair only +bash scripts/run_full_sweep.sh --pair aave +bash scripts/run_full_sweep.sh --pair cow +``` + +Each job writes two files: + +- `results/full_sweep/.json` — per-job summary with chosen best params +- `results/run_.json` — full trial trajectory used by the + selection step. Hash is computed from the run_fingerprint, so changes to + any fingerprint field (method, penalty, TVL, objective, token pair, …) + produce a fresh file. + +Per-job stdout / stderr go to `/tmp/tune_full_.log` — useful for spot +checks while a sweep runs. + +`MAX_WORKERS` defaults to 8 for optuna and 4 for CMA-ES (CMA-ES uses more RAM). +Override either with the env var. + +### What each method optimises + +| Method | Optimiser | Per-job time (300 trials / 500 gens) | Notes | +|---|---|---|---| +| optuna | TPE sampler, median pruner | ~20–40 min | Single-objective; multi-objective optional via `--multi-objective`. | +| cma_es | CMA-ES with box constraints from `parameter_config` | ~30–60 min | One restart per `n_parameter_sets`. | +| bfgs | `jax.scipy.optimize.minimize(method="BFGS")` | depends on `bfgs_maxiter` | Unconstrained; needs sp_/logit_ reparametrisation to stay in bounds. | + +### What each objective measures + +`--objective` picks which scalar the optimiser maximises: + +- `returns_over_hodl` — final pool value minus the HODL counterfactual, + divided by initial. Direct profitability against holding the constituent + tokens. +- `fee_revenue_over_value` — cumulative LP fee revenue as a fraction of + initial pool value. +- `calmar` — annualised return divided by max drawdown. +- `daily_log_sharpe_excess` — pool's daily log-Sharpe minus the HODL daily + log-Sharpe; risk-adjusted excess return vs. holding. + +### Overfitting penalty + +`--overfitting-penalty α` adds a generalisation penalty. Optuna and CMA-ES +both reserve `val_fraction` of the training window (default 0.2; see +`default_run_fingerprint.py`) as a held-out validation window. The +optimised objective becomes: + +``` +penalised = mean_train_obj − α · max(0, mean_train_obj − mean_val_obj) +``` + +When train looks better than val, the gap is subtracted. `α = 0` recovers +the raw training objective. `run_full_sweep.sh` sweeps three variants per +(pair, TVL, objective): `α = 0`, `α = 1.0`, `α = 5.0`. + +### Single-process alternative + +`scripts/tune_reclamm_calibrated_noise.py --all-objectives` runs the four +objectives sequentially in a single Python process, without the +penalty-variant axis. Useful for quick single-machine exploration without +spawning the orchestrator's parallel processes. + +--- + +## 5. Selection, final sims, and plots + +### 5a. Pick the best params per (pair, TVL tier) and run train+test forward sims + +``` +python scripts/run_final_sims.py --all +# or +python scripts/run_final_sims.py --pair aave +python scripts/run_final_sims.py --pair cow +``` + +`run_final_sims.py` loads every `results/run_*.json`, filters to its +hardcoded train period (see `TRAIN_START` / `TRAIN_END` at the top of the +script), ranks candidates by val-RoH pulled from +`continuous_test_metrics["returns_over_hodl"]`, and picks the best for each +`(token, TVL)` combination. Then runs two independent forward passes (one +each for the train and test windows) at the picked params, and writes: + +- `results/final_sims/_sim_results.pkl` — the full per-tier results (large; ~200 MB per pair). +- `results/final_sims/_{train,test}.png` — share-price / fee revenue / cumulative volume per TVL tier. +- `results/final_sims/_{train,test}_weights.png` — effective weight trajectories. + +The printed `Best:` / `Params:` lines are the per-tier selections — that's +where `(PR, margin, shift)` for the next step come from. + +### 5b. PR-sweep heatmaps (PR × period grid) + +Once a tier's `(margin, shift, initial_pool_value)` is chosen, sweep PR across +a multi-period window: + +``` +python scripts/run_pr_sweep.py --tokens COW ETH \ + --pool-id 0xd321300ef77067 --gas-cost 3.0 --fees 0.003 \ + --noise-model mm_observed \ + --multi-period --period-months 3 \ + --onchain-pr 2.02 \ + --prs 1.01 1.1 1.2 1.4 1.6 1.8 2.0 2.5 3.0 5.0 10.0 \ + 30.0 50.0 80.0 100.0 120.0 150.0 200.0 \ + --output-dir results/final_sims/for_fabio/cow/price_ratio_sweep \ + --margin 0.8235 --shift 0.1418 --initial-pool-value 500000 \ + --selected-pr 99.665 +``` + +The `--prs` list should bracket the selected PR for the tier. The +`run_final_sims.py` output tells you the selected PR; extend the upper bound +of `--prs` past it so the heatmap shows the surrounding landscape. Output: +`pr_heatmap___m_s.png` in the output dir. + +The script tries each period from `2024-01-01` onwards in 1-month steps; +periods that start before the pool's data is available raise inside +`run_single_period`, the script catches the exception, and the final heatmap +only contains the surviving rows. + +### 5c. Other diagnostic plots + +| Script | What it produces | +|---|---| +| `scripts/plot_reclamm_optuna_result.py` | per-config train/test panels from a single sweep result | +| `scripts/plot_mm_noise_fit.py` | per-pool MM-model fit overlay | +| `scripts/plot_calibrated_vs_real.py` | total volume: modelled vs observed | +| `scripts/compare_modelled_vs_real.py` | noise volume of model pool A vs real volume of pool B | +| `scripts/select_best_params.py` | ranked summary of sweep results, optional `--export` of best params | + +--- + +## 6. Extending to a new pair + +The frozen artifacts in `results/mm_noise/` and `results/competitor_tvl/` +cover the set of pools that survived calibration filtering at training time +(see §1b). The list lives in `results/mm_noise/meta.json` under `pool_ids`, +and the same set is the column index of `results/competitor_tvl/competitor_tvl.npz`. + +If the pair you want to train on is in that set, training picks up the +per-pool `log_alpha` / `gamma` / `log_K` automatically. + +If the pair is not in the set, `build_mm_simulator_arrays` falls back: + +- MM noise level → cross-pool median `log_alpha` and `gamma`. Logs + `MM model: pool not found, using median alpha=...`. +- Competitor TVL → constant `K = $10M`. Logs + `WARNING: pool not in competitor TVL data, using K=$10M`. + +The simulator still runs, with cross-pool averages replacing the pool-specific +saturation curve and intercept. For pairs close to the calibration set this is +a reasonable approximation; for very small / illiquid / very large pairs the +median is likely well off and the constant `K` flattens the TVL→noise response. + +To get proper per-pool parameters for a new pair: + +``` +# 1. Make sure the pool is in the panel +python -m quantammsim.noise_calibration --fetch --chain ethereum + +# 2. Refresh the competitor-TVL bundle (DeFi Llama) +python scripts/fetch_competitor_tvl.py + +# 3. Re-train the MM model so the new pool gets its own entry +python scripts/run_mm_noise.py \ + --per-pool-gamma --epochs 5000 --lr 1e-4 \ + --huber-delta 0.5 --observed-K --no-split +``` + +Step 1 requires the pool to have TVL above `--min-tvl` on the Balancer API. +Step 2 caches a fresh `___history.pkl` and rebuilds +`competitor_tvl.npz`. Step 3 produces fresh `model.npz` + `meta.json` whose +`pool_ids` list now includes the new pool. Existing reCLAMM training results +for other pairs are unaffected (they're keyed off the run_fingerprint hash; +re-running step 3 doesn't change those hashes). + +After this, any `run_full_sweep.sh` / `run_final_sims.py` / `run_pr_sweep.py` +call against the new pair reads its per-pool MM parameters and per-day +competitor TVL series instead of falling back to medians. + +### Adding the new pair to `run_full_sweep.sh` + +The sweep orchestrator iterates over a hard-coded `CONFIGS` array near the +top of `scripts/run_full_sweep.sh`. Each row is a whitespace-separated tuple: + +``` +"token_a token_b pool_id gas_cost fees tvl_label initial_tvl" +``` + +For example, the existing AAVE/ETH and COW/ETH rows: + +``` +"AAVE ETH 0x9d1fcf346ea1b0 1.0 0.0025 aave_1m 1000000" +"COW ETH 0xd321300ef77067 3.0 0.003 cow_500k 500000" +``` + +To sweep a new pair, append one row per TVL tier with the pool's address +prefix, an appropriate gas cost (chain dependent) and fees, a unique +`tvl_label`, and the starting pool value. The same change in +`scripts/run_final_sims.py` (`PAIR_CONFIGS`) makes the new pair available to +the final-sims selector. `--pair ` filters to a single pair when needed. + +--- + +## Caveats / things to watch + +- **The `results/run_*.json` cache is fingerprint-keyed**. The hash is + `SHA256(json.dumps(run_fingerprint, sort_keys=True))`, computed in + `quantammsim/core_simulator/result_exporter.py:get_run_location`. Every + field of `run_fingerprint` participates: token pair, pool_id, TVL, fees, + gas_cost, objective, method-specific settings (`n_trials`, `n_generations`, + `overfitting_penalty`, `val_fraction`), noise-model config, and simulator + config (`arb_frequency`, `ste_temperature`, `max_memory_days`, …). + Identical fingerprints reload from cache instead of re-training; delete + the matching `run_.json` to force a fresh run. To map a hash back + to its fingerprint, read the first entry of the JSON: + `json.loads(json.load(open(path)))[0]`. + +- **CMA-ES penalty** factors into the run_fingerprint. If you have older + `run_*.json` files from a version where the penalty was not in the + fingerprint, they share a hash across penalty variants and should be + deleted before a fresh sweep, otherwise the candidate pool mixes + configurations. + +- **Stale data ranges**. `run_pr_sweep.py --multi-period` starts at + `2024-01-01`; pools that didn't exist that early will fail the early + periods. The script catches the exception and produces a heatmap of only + the surviving rows. + +- **`MAX_WORKERS=N`** in `run_full_sweep.sh` controls shell parallelism only — + each job is a separate Python process. Memory is the binding constraint; + 4–6 workers is typical for a laptop. + +- **PR axis on heatmaps** is what you pass via `--prs`. The default upper + bound is 10; pass higher values when the selected PR for the pair exceeds + that. + +- **CPU vs GPU**. The simulator runs on whichever device JAX picks. + `scripts/build_pool_grids.py` forces `JAX_PLATFORMS=cpu` so the grid build + is deterministic. The update-rule estimator backend (scan vs conv/FFT) is + selected by `DEFAULT_BACKEND` in + `quantammsim/pools/G3M/quantamm/update_rule_estimators/estimators.py` and + defaults to scan on CPU; it is not exposed as a CLI flag. + +--- + +## Quick re-run shortcut + +If the data + MM model + grids are already on disk and you just want fresh +trained params and plots: + +``` +# (1) sweep +MAX_WORKERS=6 bash scripts/run_full_sweep.sh +MAX_WORKERS=6 bash scripts/run_full_sweep.sh --method cma_es + +# (2) select + final sims +python scripts/run_final_sims.py --all + +# (3) PR heatmaps per pair × tier — adjust margin/shift/TVL/selected-PR +# from the run_final_sims.py output +python scripts/run_pr_sweep.py --tokens AAVE ETH ... +python scripts/run_pr_sweep.py --tokens COW ETH ... +``` + +The whole chain from a clean cache is ~half a day on 6 workers for AAVE+COW +at the canonical 6-tier sweep. diff --git a/docs/joint_calibration_analysis.md b/docs/joint_calibration_analysis.md new file mode 100644 index 0000000..dee2760 --- /dev/null +++ b/docs/joint_calibration_analysis.md @@ -0,0 +1,230 @@ +# Joint Calibration Analysis: MLP Capacity vs Identification + +## Training results (2026-03-10) + +### R2 progression across model architectures + +| Attempt | Architecture | Noise params | Total params | Median R2 | Joint loss | +|---|---|---|---|---|---| +| Structural MoE (numpyro) | 3 archetypes x 8 coeffs | 24 | ~40 | -0.70 | - | +| Linear joint | SharedLinearNoiseHead | 63 | 63 | -0.15 | 9.62 | +| MLP noise joint | MLPNoiseHead(hidden=16) | 255 | 255 | 0.01 | 9.39 | +| Full MLP | MLPHead(cad,16) + MLPNoiseHead(16) | 255 | 377 | -0.02 | 8.59 | +| Option C (per-pool) | PerPoolNoiseHead | 37x8=296 | 37x9=333 | 0.61 | 1.25 (median) | + +The direction is clear: more capacity on the noise side helps substantially +(-0.70 -> -0.15 -> 0.01). Adding cadence capacity (MLP noise -> full MLP) +improved joint loss (9.39 -> 8.59) but not per-pool R2 (-0.02), and didn't +converge within 500 iterations. + +### Convergence concern + +The full MLP explicitly failed to converge (scipy `success=False`). The MLP +noise model converged but only reduced loss from 12.70 to 9.39 — a 26% +reduction vs the linear baseline's 99.5% reduction (2011.77 -> 9.62). This +suggests the MLPs are undertraining. + +Current optimizer settings: +- L-BFGS-B with maxiter=500 +- ftol=1e-10, gtol=1e-8 +- maxcor=10 (L-BFGS memory, scipy default) +- alpha=0.01 for all heads (L2 regularization on weights) +- hidden=16 for all MLPs +- He init for W1, W2=0, b2=pooled OLS / mean of Option C + +### Why the MLPs may not be converging + +1. **maxiter=500 is low for 255-377 params.** L-BFGS-B typically needs + O(1000-5000) iterations for MLP-scale problems. The linear model with + 63 params converges easily in 500; the MLP with 377 params does not. + +2. **maxcor=10 may be too small.** The default L-BFGS memory of 10 past + gradients may not provide a good enough Hessian approximation for 377 + parameters. Increasing to 20-50 can help. + +3. **Regularization alpha=0.01 may be wrong.** With 37 pools and 255 noise + params, the model is overparameterized (255/37 ≈ 7 params per pool). + alpha=0.01 might be too weak (overfitting some pools, underfitting + others) or too strong (preventing the MLP from expressing the necessary + nonlinearity). This is the most important hyperparameter to sweep. + +4. **W2=0 initialization creates a flat starting surface.** Since the MLP + starts as a constant function (output = b2 everywhere), L-BFGS-B must + first learn to differentiate between pools. The initial gradients + through W1 are informative (He init + backprop through ReLU), but the + first few iterations may be slow compared to the linear model which + starts from an OLS warm-start. + +5. **Dead ReLU units.** With He init and k_attr=6 features, some hidden + units may have all-negative pre-activations across the 37 pool + attribute vectors, making them permanently dead with zero gradient. + +6. **Per-pool loss weighting.** All observations contribute equally. + USDC/WETH (1757 obs) dominates RDNT/WETH (89 obs) by 20x. The + optimizer may be fitting a few high-obs pools at the expense of many + low-obs ones. + +## Diagnosis: identification vs convergence + +Two distinct problems: + +1. **Convergence problem** (addressable via hyperparameters): + The MLP isn't reaching its minimum. Fix: more iterations, better + hyperparameters, multiple restarts. + +2. **Identification problem** (addressable via architecture): + Even at the minimum, the shared mapping can't match per-pool R2. + 37 pools is tiny for a nonlinear model. Cadence is idiosyncratic. + Fix: DeltaHead (per-pool residuals with shrinkage), better features. + +These are **independent** problems that compound. We should fix convergence +first (hyperparameter sweep) to understand the true capacity of the current +architecture before adding structural complexity. + +## Hyperparameter sweep design + +### Parameters to sweep + +| Parameter | Current | Sweep values | Rationale | +|---|---|---|---| +| maxiter | 500 | 500, 2000, 5000 | Primary convergence bottleneck | +| alpha (noise) | 0.01 | 0.0001, 0.001, 0.01, 0.1 | Controls overfitting vs underfitting | +| alpha (cadence) | 0.01 | 0.001, 0.01, 0.1 | Separate from noise reg | +| hidden | 16 | 8, 16, 32 | Capacity vs overfitting | +| maxcor | 10 | 10, 30 | L-BFGS Hessian quality | +| loss_type | l2 | l2, huber | Outlier robustness | + +### Sweep strategy + +Full grid is 3 x 4 x 3 x 3 x 2 x 2 = 432 runs. Too many. + +**Phase 1: Fix convergence (1D sweeps)** +- Sweep maxiter = [500, 2000, 5000] with defaults. Cheapest diagnostic. +- If 5000 converges, use that going forward. + +**Phase 2: Regularization (most important)** +- alpha_noise x alpha_cad grid: 4 x 3 = 12 runs at converged maxiter. +- Evaluate both joint loss AND per-pool median R2. + +**Phase 3: Architecture** +- hidden = [8, 16, 32] at best alpha settings: 3 runs. +- loss_type = [l2, huber] at best settings: 2 runs. +- maxcor = [10, 30] at best settings: 2 runs. + +Total: ~22 runs, each ~2-5 min = ~1-2 hours. + +### Metrics to track per run + +- Joint loss (final) +- Joint loss (init) — sanity check +- Converged (bool) +- Number of L-BFGS iterations used +- Per-pool median R2 +- Per-pool mean R2 +- Per-pool R2 distribution (10th, 25th, 50th, 75th, 90th percentiles) +- Wall time + +### What success looks like + +- Converged = True for the full MLP +- Joint loss < 8.0 (below current 8.59) +- Per-pool median R2 > 0.3 (closing the gap toward Option C's 0.61) +- The R2 improvement should be spread across pools, not concentrated + +## Features / data that would help + +### Missing pool attributes (from docs) + +Current features (k_attr=6 after chain dummy removal): +log_fee, mean_log_tvl, log_mcap_product, has_stable, same_asset_type, +weight_imbalance. + +These describe what the pool IS but not the market around it. Cadence is +driven by arbitrage frequency, which depends on: + +| Missing feature | Why it matters | Source | Effort | +|---|---|---|---| +| Block time | Directly limits minimum cadence. Arb=0.25s vs Main=12s | Static per chain | Trivial | +| Mean pair volatility | Pool-level (not obs-level) vol predicts arb intensity | Binance minute data (loaded) | Small | +| CEX daily volume | More CEX vol = more arb opportunities | Binance API | Medium | +| Competing DEX pools | More pools for same pair = faster arb | Balancer subgraph | Medium | +| Pool routing share | Dominant pool gets arbitraged first | DEX aggregator data | Hard | +| Mean daily swap count | Direct proxy for pool activity | Panel data | Small | + +The pair-intrinsic formula bias (1.26-2.22x) documented in +noise_calibration_review.md is the largest unexplained variance source. +It varies with pair liquidity characteristics in ways that the current +token classification doesn't capture. CEX volume/depth would help. + +### Observation-level features (x_obs, K_OBS=8) + +Current: [1, log_tvl_lag1, log_sigma, tvl*sigma, tvl*fee, sigma*fee, +dow_sin, dow_cos] + +Missing: +- Rolling CEX volume (daily) — high volume days have more noise/organic flow +- Gas price that day (mainnet) — affects whether arbs execute +- Market regime (rolling momentum) — trending vs mean-reverting +- Number of swaps that day — direct activity measure + +### Time-varying dynamics + +Panel spans 2021-2026. MEV dynamics changed dramatically: +- Flashbots launched mid-2021 +- L2s matured 2023-2024 +- EIP-4844 (March 2024) dropped L2 gas costs +The current model assumes constant cadence per pool over this period. + +## Structural improvements (post-sweep) + +### DeltaHead (per-pool residuals with shrinkage) + +Most important structural change. For cadence: +``` +log_cadence_i = f(x_attr_i) + delta_i +regularization: alpha_shared * ||W||^2 + alpha_delta * sum(delta_i^2) +``` + +At alpha_delta=0: pure per-pool (Option C) +At alpha_delta=inf: pure shared (current joint) +Cross-validate alpha_delta. + +For new pools: predict f(x_attr_new) with delta=0. + +This is essentially a mixed-effects model fitted end-to-end through the +grid interpolation loss. + +### Per-pool loss weighting + +Weight each pool's contribution by 1/sqrt(n_obs_i) to equalize pool-level +influence. Currently USDC/WETH (1757 obs) has 20x the influence of any +Sonic pool (89 obs). + +### Hybrid: per-pool cadence + shared noise + +Cadence is idiosyncratic (LOO R2 = 0.24 at best). Noise structure is +more regular (hierarchical model R2 = 0.71 on total volume). Natural split: +- Cadence: per-pool (Option C) +- Noise: shared MLP (generalizable) +- Gas: fixed to chain values + +### Sensitivity analysis (the decision point) + +Before investing more in mapping improvement: does reCLAMM optimal +concentration change materially when cadence varies +/-50%? This is +recommendation #1 in calibration_results.md, noise_calibration_review.md, +and joint_calibration_design.md. Still not done. + +If the optimum is robust, the current pipeline (Option C + Ridge LOO) is +already sufficient and further mapping improvement is nice-to-have. + +## Priority order + +1. **Hyperparameter sweep** — fix convergence before changing architecture +2. **DeltaHead** — if R2 gap persists post-sweep, this is the minimal + structural change +3. **Per-pool loss weighting** — simple fix, helps all joint models +4. **Add block_time and mean_pair_volatility** — high-signal, low-effort + features +5. **Sensitivity analysis** — the real decision point for whether any of + this matters for the downstream task diff --git a/experiments/compare_exposures.py b/experiments/compare_exposures.py new file mode 100644 index 0000000..14fdd06 --- /dev/null +++ b/experiments/compare_exposures.py @@ -0,0 +1,141 @@ +"""Compare effective weight/exposure trajectories: reClAMM vs Balancer 50/50. + +Prints weight stats and saves a plot of weight[AAVE] over time for both pools. +""" + +import jax.numpy as jnp +import numpy as np +import matplotlib.pyplot as plt +from quantammsim.runners.jax_runners import do_run_on_historic_data + + +def to_daily_price_shift_base(exponent): + return 1.0 - exponent / 124649.0 + + +TOKENS = ["AAVE", "ETH"] +START = "2024-06-01 00:00:00" +END = "2025-06-01 00:00:00" + +CONFIGS = { + "reClAMM on-chain (pr=1.5)": { + "fingerprint": { + "tokens": TOKENS, "rule": "reclamm", + "startDateString": START, "endDateString": END, + "initial_pool_value": 1_000_000.0, "do_arb": True, + "fees": 0.0, "gas_cost": 0.0, "arb_fees": 0.0, + "chunk_period": 60, "weight_interpolation_period": 60, + }, + "params": { + "price_ratio": jnp.array(1.5), + "centeredness_margin": jnp.array(0.5), + "daily_price_shift_base": jnp.array(to_daily_price_shift_base(0.1)), + }, + }, + "reClAMM wide (pr=4)": { + "fingerprint": { + "tokens": TOKENS, "rule": "reclamm", + "startDateString": START, "endDateString": END, + "initial_pool_value": 1_000_000.0, "do_arb": True, + "fees": 0.0, "gas_cost": 0.0, "arb_fees": 0.0, + "chunk_period": 60, "weight_interpolation_period": 60, + }, + "params": { + "price_ratio": jnp.array(4.0), + "centeredness_margin": jnp.array(0.2), + "daily_price_shift_base": jnp.array(to_daily_price_shift_base(1.0)), + }, + }, + "reClAMM Phase 2 (pr=4, m=0.1)": { + "fingerprint": { + "tokens": TOKENS, "rule": "reclamm", + "startDateString": START, "endDateString": END, + "initial_pool_value": 1_000_000.0, "do_arb": True, + "fees": 0.0, "gas_cost": 0.0, "arb_fees": 0.0, + "chunk_period": 60, "weight_interpolation_period": 60, + }, + "params": { + "price_ratio": jnp.array(4.0), + "centeredness_margin": jnp.array(0.1), + "daily_price_shift_base": jnp.array(to_daily_price_shift_base(0.001)), + }, + }, + "Balancer 50/50": { + "fingerprint": { + "tokens": TOKENS, "rule": "balancer", + "startDateString": START, "endDateString": END, + "initial_pool_value": 1_000_000.0, "do_arb": True, + "fees": 0.0, "gas_cost": 0.0, "arb_fees": 0.0, + "chunk_period": 60, "weight_interpolation_period": 60, + }, + "params": { + "initial_weights_logits": jnp.zeros(2), + }, + }, +} + +results = {} +for name, cfg in CONFIGS.items(): + print(f"Running {name}...") + r = do_run_on_historic_data( + run_fingerprint=cfg["fingerprint"], params=cfg["params"] + ) + results[name] = r + +# Compute effective weights (value fraction in token 0 = AAVE) +print("\n" + "=" * 90) +print(f" {'Config':<35s} {'w_AAVE mean':>10s} {'w_AAVE std':>10s} " + f"{'w_AAVE min':>10s} {'w_AAVE max':>10s} {'vs HODL':>10s}") +print("-" * 90) + +daily = 1440 # subsample to daily for stats and plotting +fig, axes = plt.subplots(3, 1, figsize=(14, 10), sharex=True) + +for name, r in results.items(): + reserves = np.array(r["reserves"]) + prices = np.array(r["prices"]) + values = reserves * prices # (T, 2) + total = values.sum(axis=1, keepdims=True) + weights = values / np.clip(total, 1e-10, None) # (T, 2) + w_aave = weights[::daily, 0] + + hodl_value = float((reserves[0] * prices[-1]).sum()) + vs_hodl = r["final_value"] / hodl_value - 1.0 + + print(f" {name:<35s} {w_aave.mean():>10.4f} {w_aave.std():>10.4f} " + f"{w_aave.min():>10.4f} {w_aave.max():>10.4f} {vs_hodl * 100:>9.2f}%") + + days = np.arange(len(w_aave)) + axes[0].plot(days, w_aave, label=name, alpha=0.8) + + # Pool value over time + pool_val = np.array(r["value"])[::daily] + axes[1].plot(days[:len(pool_val)], pool_val / 1e6, label=name, alpha=0.8) + +print("=" * 90) + +# HODL line +r0 = results[list(results.keys())[0]] +prices_daily = np.array(r0["prices"])[::daily] +reserves_0 = np.array(r0["reserves"])[0] +hodl_val = (reserves_0 * prices_daily).sum(axis=1) / 1e6 +axes[1].plot(np.arange(len(hodl_val)), hodl_val, label="HODL", ls="--", color="gray", alpha=0.7) + +# Price ratio (AAVE/ETH) on third axis +price_ratio_series = prices_daily[:, 0] / prices_daily[:, 1] +axes[2].plot(np.arange(len(price_ratio_series)), price_ratio_series, color="black", alpha=0.7) +axes[2].set_ylabel("AAVE/ETH price") +axes[2].set_xlabel("Days") + +axes[0].set_ylabel("AAVE weight (value fraction)") +axes[0].axhline(0.5, ls="--", color="gray", alpha=0.5) +axes[0].legend(fontsize=8) +axes[0].set_title("Effective AAVE exposure over time") + +axes[1].set_ylabel("Pool value ($M)") +axes[1].legend(fontsize=8) +axes[1].set_title("Pool value over time") + +plt.tight_layout() +plt.savefig("reclamm_exposure_comparison.png", dpi=150) +print("\nSaved reclamm_exposure_comparison.png") diff --git a/experiments/diagnose_spikes.py b/experiments/diagnose_spikes.py new file mode 100644 index 0000000..b146c6b --- /dev/null +++ b/experiments/diagnose_spikes.py @@ -0,0 +1,216 @@ +"""Diagnose spikes in sim-vs-world deviation for LP-supply-normalized runs. + +Compares old (no LP supply) vs new (with LP supply + per-LP normalization) +to pinpoint what causes spikes in the deviation time series. +""" + +import os +import numpy as np +import matplotlib +matplotlib.use("Agg") +import matplotlib.pyplot as plt +from datetime import datetime, timezone + +from experiments.pool_registry import ( + POOL_REGISTRY, extract_on_chain_state, extract_initial_state, + get_data_end_date, load_world_history, load_bpt_supply_df, +) +from experiments.run_pool_battery import ( + run_sim, sample_at_timestamps, _start_str_from_pool, + _onchain_params_to_sim, PROTOCOL_FEE_SPLIT, +) +from quantammsim.runners.jax_runners import do_run_on_historic_data + + +POOL_LABEL = "WAVAX_USDC" # Change to "cbBTC_WETH" etc. + + +def main(): + pool = POOL_REGISTRY[POOL_LABEL] + extract_on_chain_state(pool) + initial_state = extract_initial_state(pool) + start_str = _start_str_from_pool(pool) + end_str = get_data_end_date(pool.tokens) + lp_supply_df = load_bpt_supply_df(pool, end_date=end_str) + + start_sec = datetime.strptime( + start_str, "%Y-%m-%d %H:%M:%S" + ).replace(tzinfo=timezone.utc).timestamp() + + print(f"Pool: {pool.label}, TVL: ${pool.initial_pool_value_usd:,.0f}") + print(f"Period: {start_str} to {end_str}") + print(f"BPT range: {lp_supply_df['lp_supply'].min():.4f} to {lp_supply_df['lp_supply'].max():.4f}") + + # ---- Run sims ---- + # 1. Old way: no lp_supply at all + result_old = run_sim( + pool, gas_cost=0.0, arb_frequency=1, + initial_state=initial_state, start=start_str, end=end_str, + lp_supply_df=None, + ) + + # 2. New way: lp_supply in scan + per-LP normalization + result_new = run_sim( + pool, gas_cost=0.0, arb_frequency=1, + initial_state=initial_state, start=start_str, end=end_str, + lp_supply_df=lp_supply_df, + ) + + # 3. Raw lp run (scan has lp_supply, but we DON'T divide by it) + params = _onchain_params_to_sim(pool) + fp = { + "tokens": pool.tokens, "rule": "reclamm", + "startDateString": start_str, "endDateString": end_str, + "initial_pool_value": pool.initial_pool_value_usd, + "fees": pool.swap_fee, "gas_cost": 0.0, "arb_fees": 0.0, + "do_arb": True, "arb_frequency": 1, "chunk_period": 1440, + "weight_interpolation_period": 1440, + "reclamm_use_shift_exponent": True, + "reclamm_interpolation_method": "geometric", + "reclamm_centeredness_scaling": False, + "protocol_fee_split": PROTOCOL_FEE_SPLIT, + "reclamm_initial_state": initial_state, + } + result_raw = do_run_on_historic_data( + run_fingerprint=fp, params=params, lp_supply_df=lp_supply_df, + ) + v_lp_raw = np.array(result_raw["value"]) + + v_old = np.array(result_old["value_usd"]) # no LP, no normalization + v_new = np.array(result_new["value_usd"]) # LP scan + divided by lp_supply + + # ---- World ---- + world = load_world_history(pool, end_date=end_str) + world_ts = world["timestamps"] + prices_min = result_old["prices"] + + prices_at_world = np.stack([ + sample_at_timestamps(prices_min[:, i], start_sec, world_ts) + for i in range(prices_min.shape[1]) + ], axis=1) + + # BPT-normalized world value (same in both old and new) + world_bpt_val = ( + world["bal_0"] * prices_at_world[:, 0] + + world["bal_1"] * prices_at_world[:, 1] + ) + world_growth = world_bpt_val / world_bpt_val[0] + + # Raw world value (absolute, un-normalized) + world_raw_val = ( + world["raw_bal_0"] * prices_at_world[:, 0] + + world["raw_bal_1"] * prices_at_world[:, 1] + ) + + # Sample sim at world timestamps + old_at_world = sample_at_timestamps(v_old, start_sec, world_ts) + new_at_world = sample_at_timestamps(v_new, start_sec, world_ts) + raw_at_world = sample_at_timestamps(v_lp_raw, start_sec, world_ts) + + old_growth = old_at_world / old_at_world[0] + new_growth = new_at_world / new_at_world[0] + raw_growth = raw_at_world / raw_at_world[0] + + # Deviations + dev_old = (old_growth / world_growth - 1) * 100 + dev_new = (new_growth / world_growth - 1) * 100 + + # Raw vs raw-world comparison (both absolute) + world_raw_growth = world_raw_val / world_raw_val[0] + dev_raw = (raw_growth / world_raw_growth - 1) * 100 + + days = (world_ts - world_ts[0]) / 86400 + + # LP supply at world timestamps + lp_unix = np.array(lp_supply_df["unix"]) + lp_vals = np.array(lp_supply_df["lp_supply"]) + lp_at_world = np.interp(world_ts, lp_unix / 1000, lp_vals) + + # ---- Print diagnostics ---- + print(f"\n--- Final deviations ---") + print(f"Old (no LP): {dev_old[-1]:+.4f}%") + print(f"New (LP + per-LP): {dev_new[-1]:+.4f}%") + print(f"Raw (LP, absolute): {dev_raw[-1]:+.4f}%") + + # Spike analysis + for label, dev in [("Old", dev_old), ("New", dev_new), ("Raw", dev_raw)]: + diffs = np.abs(np.diff(dev)) + n_spikes_01 = np.sum(diffs > 0.1) + n_spikes_05 = np.sum(diffs > 0.5) + n_spikes_10 = np.sum(diffs > 1.0) + print(f"\n{label} — step-to-step jumps in deviation:") + print(f" >0.1%: {n_spikes_01}, >0.5%: {n_spikes_05}, >1.0%: {n_spikes_10}") + if n_spikes_10 > 0: + spike_idx = np.where(diffs > 1.0)[0] + for si in spike_idx[:5]: + print(f" day {days[si]:.1f}: dev {dev[si]:+.2f}% -> {dev[si+1]:+.2f}% " + f"(Δ={dev[si+1]-dev[si]:+.2f}%, lp={lp_at_world[si]:.4f}->{lp_at_world[si+1]:.4f})") + + # World growth spikes + world_g_diffs = np.diff(world_growth) + n_world_spikes = np.sum(np.abs(world_g_diffs) > 0.01) + print(f"\nWorld BPT-normalized growth jumps > 1%: {n_world_spikes}") + if n_world_spikes > 0: + wsi = np.where(np.abs(world_g_diffs) > 0.01)[0] + for si in wsi[:5]: + print(f" day {days[si]:.1f}: growth {world_growth[si]:.4f} -> {world_growth[si+1]:.4f} " + f"(Δ={world_g_diffs[si]:+.4f}, lp={lp_at_world[si]:.4f}->{lp_at_world[si+1]:.4f})") + + # ---- Plot ---- + fig, axes = plt.subplots(4, 1, figsize=(14, 16), sharex=True) + + ax = axes[0] + ax.plot(days, dev_old, "b-", linewidth=1.5, label=f"Old (no LP) → {dev_old[-1]:+.2f}%") + ax.plot(days, dev_new, "r-", linewidth=1.5, label=f"New (LP + per-LP norm) → {dev_new[-1]:+.2f}%") + ax.axhline(0, color="gray", linestyle=":", alpha=0.5) + ax.set_ylabel("% deviation from world") + ax.set_title(f"{pool.label} — gas=0, arb=1min — old vs new deviation") + ax.legend(fontsize=9) + ax.grid(True, alpha=0.2) + + ax = axes[1] + ax.plot(days, dev_raw, "g-", linewidth=1.5, label=f"Raw absolute (LP scan, raw world) → {dev_raw[-1]:+.2f}%") + ax.axhline(0, color="gray", linestyle=":", alpha=0.5) + ax.set_ylabel("% deviation") + ax.set_title("Alternative: raw absolute sim vs raw absolute world") + ax.legend(fontsize=9) + ax.grid(True, alpha=0.2) + + ax = axes[2] + ax.plot(days, world_growth, "k-", linewidth=2, label="World (BPT-normalized)") + ax.plot(days, old_growth, "b-", linewidth=1, alpha=0.8, label="Old sim") + ax.plot(days, new_growth, "r-", linewidth=1, alpha=0.8, label="New sim (LP + per-LP)") + ax.set_ylabel("Growth factor") + ax.set_title("Growth factors") + ax.legend(fontsize=9) + ax.grid(True, alpha=0.2) + + ax = axes[3] + ax.plot(days, lp_at_world, "g-", linewidth=2, label="LP supply (BPT/BPT₀)") + # Mark large LP changes + lp_diffs = np.abs(np.diff(lp_at_world)) + big_lp = np.where(lp_diffs > 0.05)[0] + if len(big_lp): + ax.scatter(days[big_lp], lp_at_world[big_lp], c="red", s=40, zorder=5, + label=f"Large LP events ({len(big_lp)})") + ax.set_ylabel("BPT / BPT₀") + ax.set_xlabel("Days from start") + ax.set_title("On-chain BPT supply") + ax.legend(fontsize=9) + ax.grid(True, alpha=0.2) + + fig.suptitle( + f"{pool.label} ({pool.chain}) — spike diagnosis", + fontsize=13, fontweight="bold", + ) + plt.tight_layout() + + os.makedirs("results", exist_ok=True) + out = f"results/diagnose_spikes_{POOL_LABEL}.png" + plt.savefig(out, dpi=150, bbox_inches="tight") + plt.close() + print(f"\nSaved: {out}") + + +if __name__ == "__main__": + main() diff --git a/experiments/diagnostic_lp_supply.py b/experiments/diagnostic_lp_supply.py new file mode 100644 index 0000000..3f3c317 --- /dev/null +++ b/experiments/diagnostic_lp_supply.py @@ -0,0 +1,134 @@ +"""Diagnostic: sim vs world absolute pool value for a single (gas=0, arb_freq=1) run. + +Plots raw USD pool value over time for both sim and world, no per-LP normalization. +""" + +import os +import numpy as np +import matplotlib +matplotlib.use("Agg") +import matplotlib.pyplot as plt +from datetime import datetime, timezone + +from experiments.pool_registry import ( + POOL_REGISTRY, extract_on_chain_state, extract_initial_state, + get_data_end_date, load_world_history, load_bpt_supply_df, +) +from experiments.run_pool_battery import run_sim, sample_at_timestamps, _start_str_from_pool + + +def main(): + pool = POOL_REGISTRY["cbBTC_WETH"] + extract_on_chain_state(pool) + initial_state = extract_initial_state(pool) + + start_str = _start_str_from_pool(pool) + end_str = get_data_end_date(pool.tokens) + lp_supply_df = load_bpt_supply_df(pool, end_date=end_str) + + print(f"Pool: {pool.label}, TVL: ${pool.initial_pool_value_usd:,.0f}") + print(f"BPT: {lp_supply_df['lp_supply'].iloc[0]:.4f} -> {lp_supply_df['lp_supply'].iloc[-1]:.4f}") + print(f"Period: {start_str} to {end_str}") + + # Run sim WITH lp_supply (gas=0, arb_freq=1) + result_lp = run_sim(pool, gas_cost=0.0, arb_frequency=1, + initial_state=initial_state, + start=start_str, end=end_str, + lp_supply_df=lp_supply_df) + + # Run sim WITHOUT lp_supply (gas=0, arb_freq=1) + result_no_lp = run_sim(pool, gas_cost=0.0, arb_frequency=1, + initial_state=initial_state, + start=start_str, end=end_str, + lp_supply_df=None) + + # World + world = load_world_history(pool, end_date=end_str) + world_ts = world["timestamps"] + raw_bal_0 = world["raw_bal_0"] + raw_bal_1 = world["raw_bal_1"] + + start_sec = result_lp["start_unix_sec"] + prices_min = result_lp["prices"] + + # World value at world timestamps (raw balances × USD prices) + prices_at_world = np.stack([ + sample_at_timestamps(prices_min[:, i], start_sec, world_ts) + for i in range(prices_min.shape[1]) + ], axis=1) + world_value = raw_bal_0 * prices_at_world[:, 0] + raw_bal_1 * prices_at_world[:, 1] + + # Sim values (minute-resolution) + sim_value_lp = np.array(result_lp["value_usd"]) + sim_value_no_lp = np.array(result_no_lp["value_usd"]) + n_minutes = len(sim_value_lp) + sim_times_sec = start_sec + np.arange(n_minutes) * 60 + sim_days = (sim_times_sec - start_sec) / 86400 + world_days = (world_ts - start_sec) / 86400 + + # BPT supply at world timestamps (for annotation) + lp_at_world = np.interp( + world_ts, + np.array(lp_supply_df["unix"]) / 1000, + np.array(lp_supply_df["lp_supply"]), + ) + + # --- Plot --- + fig, axes = plt.subplots(3, 1, figsize=(14, 12), sharex=True) + + # Panel 1: absolute pool value + ax = axes[0] + ax.plot(world_days, world_value, "k-", linewidth=2, label="World (raw balances × prices)") + ax.plot(sim_days, sim_value_lp, "b-", linewidth=1, alpha=0.8, label="Sim (with lp_supply)") + ax.plot(sim_days, sim_value_no_lp, "r--", linewidth=1, alpha=0.8, label="Sim (no lp_supply)") + ax.set_ylabel("Pool value (USD)") + ax.set_title(f"{pool.label} — gas=0, arb_freq=1min — absolute pool value") + ax.legend(fontsize=9) + ax.grid(True, alpha=0.2) + + # Panel 2: growth factors + ax = axes[1] + world_growth = world_value / world_value[0] + sim_growth_lp = sim_value_lp / sim_value_lp[0] + sim_growth_no_lp = sim_value_no_lp / sim_value_no_lp[0] + ax.plot(world_days, world_growth, "k-", linewidth=2, label="World growth") + ax.plot(sim_days, sim_growth_lp, "b-", linewidth=1, alpha=0.8, label="Sim growth (with lp_supply)") + ax.plot(sim_days, sim_growth_no_lp, "r--", linewidth=1, alpha=0.8, label="Sim growth (no lp_supply)") + ax.axhline(1.0, color="gray", linestyle=":", alpha=0.5) + ax.set_ylabel("Growth factor") + ax.set_title("Growth factors (value / initial value)") + ax.legend(fontsize=9) + ax.grid(True, alpha=0.2) + + # Panel 3: BPT supply + ax = axes[2] + ax.plot(world_days, lp_at_world, "g-", linewidth=2, label="BPT supply (normalized)") + ax.set_ylabel("BPT / BPT₀") + ax.set_xlabel("Days from start") + ax.set_title("On-chain BPT supply") + ax.legend(fontsize=9) + ax.grid(True, alpha=0.2) + + fig.suptitle( + f"{pool.label} ({pool.chain}) — TVL=${pool.initial_pool_value_usd:,.0f} — " + f"PR={pool.on_chain_params['price_ratio']:.4f}", + fontsize=12, fontweight="bold", + ) + plt.tight_layout() + + os.makedirs("results", exist_ok=True) + out = "results/diagnostic_lp_supply_cbBTC_WETH.png" + plt.savefig(out, dpi=150, bbox_inches="tight") + plt.close() + print(f"\nSaved: {out}") + + # Print key numbers + print(f"\nWorld: {world_value[0]:.0f} -> {world_value[-1]:.0f} (growth={world_growth[-1]:.4f})") + print(f"Sim (lp): {sim_value_lp[0]:.0f} -> {sim_value_lp[-1]:.0f} (growth={sim_growth_lp[-1]:.4f})") + print(f"Sim (no lp): {sim_value_no_lp[0]:.0f} -> {sim_value_no_lp[-1]:.0f} (growth={sim_growth_no_lp[-1]:.4f})") + print(f"\nDeviation (lp): {(sim_growth_lp[-1]/world_growth[-1] - 1)*100:+.2f}%") + print(f"Deviation (no lp): {(sim_growth_no_lp[-1]/world_growth[-1] - 1)*100:+.2f}%") + + +if __name__ == "__main__": + main() diff --git a/experiments/pool_registry.py b/experiments/pool_registry.py new file mode 100644 index 0000000..cfdce2f --- /dev/null +++ b/experiments/pool_registry.py @@ -0,0 +1,512 @@ +"""Registry of on-chain reClAMM pools for sim-vs-world gas calibration. + +Extracts pool state from reclamm-simulations DB and computes TVL in USD +at each pool's plausible_start date. Maps chain → realistic gas costs. +Also provides initial on-chain state (Ra, Rb, Va, Vb) and world balance +history for comparison. + +Pools excluded: + - EUR_USDC_b, sUSDai_USDT0, WXPL_USDT0: stable/stable pairs + - wstETH_GNO: boosted (wstETH yield-bearing) +""" + +import math +import os +import sqlite3 +from dataclasses import dataclass, field +from datetime import datetime, timezone +from typing import Optional + +import numpy as np +import pandas as pd + + +# --------------------------------------------------------------------------- +# Database path (reclamm-simulations repo) +# --------------------------------------------------------------------------- +DEFAULT_DB_PATH = os.path.expanduser( + "~/Projects/reclamm-simulations/data/pools_history.db" +) + +# --------------------------------------------------------------------------- +# Chain → gas cost batteries (USD) +# --------------------------------------------------------------------------- +# Non-mainnet chains use flat gas costs. +# Ethereum uses time-varying gas from on-chain percentile CSVs. +CHAIN_GAS_COSTS = { + "base": [0.0, 0.01, 0.1, 0.5], + "gnosis": [0.0, 0.01, 0.1, 0.5], + "avalanche": [0.0, 0.01, 0.1, 0.5], +} + +# Ethereum mainnet: time-varying gas percentiles + flat zero baseline. +# CSVs live in gas_csvs/ with columns [unix, USD]. +GAS_CSV_DIR = os.path.join(os.path.dirname(__file__), "..", "gas_csvs") +ETHEREUM_GAS_PERCENTILES = ["50p", "75p", "90p", "95p"] + + +@dataclass +class PoolConfig: + """Static metadata for a simulatable on-chain reClAMM pool.""" + + label: str + tokens: list # quantammsim ticker names, e.g. ['BTC', 'ETH'] + chain: str + swap_fee: float + db_label: str # table name in pools_history.db + plausible_start: str # YYYY-MM-DD + reverse: bool # True if DB token order is reversed vs quantammsim + pool_address: str = "" # on-chain contract address (hex, no 0x prefix) + # Filled by extract_on_chain_state(): + on_chain_params: Optional[dict] = None # price_ratio, margin, shift_rate + initial_pool_value_usd: Optional[float] = None + + +# --------------------------------------------------------------------------- +# Pool definitions (non-stable, non-boosted pools with quantammsim tickers) +# --------------------------------------------------------------------------- +# --------------------------------------------------------------------------- +# Chain → Balancer V3 API chain identifier +# --------------------------------------------------------------------------- +BALANCER_API_CHAIN = { + "base": "BASE", + "ethereum": "MAINNET", + "gnosis": "GNOSIS", + "avalanche": "AVALANCHE", + "arbitrum": "ARBITRUM", + "polygon": "POLYGON", + "optimism": "OPTIMISM", + "sonic": "SONIC", +} + + +POOL_REGISTRY = { + "cbBTC_WETH": PoolConfig( + label="cbBTC_WETH", + tokens=["BTC", "ETH"], + chain="base", + swap_fee=0.0005, + db_label="cbBTC_WETH", + plausible_start="2025-08-01", + reverse=True, + pool_address="19aeb8168d921bb069c6771bbaff7c09116720d0", + ), + "cbBTC_WETH_post_oct": PoolConfig( + label="cbBTC_WETH_post_oct", + tokens=["BTC", "ETH"], + chain="base", + swap_fee=0.0005, + db_label="cbBTC_WETH", + plausible_start="2025-12-01", + reverse=True, + pool_address="19aeb8168d921bb069c6771bbaff7c09116720d0", + ), + "AAVE_WETH": PoolConfig( + label="AAVE_WETH", + tokens=["AAVE", "ETH"], + chain="ethereum", + swap_fee=0.0025, + db_label="AAVE_WETH", + plausible_start="2025-08-15", + reverse=False, + pool_address="9d1fcf346ea1b073de4d5834e25572cc6ad71f4d", + ), + "AAVE_WETH_post_gov": PoolConfig( + label="AAVE_WETH_post_gov", + tokens=["AAVE", "ETH"], + chain="ethereum", + swap_fee=0.0025, + db_label="AAVE_WETH", + plausible_start="2025-12-21", + reverse=False, + pool_address="9d1fcf346ea1b073de4d5834e25572cc6ad71f4d", + ), + "COW_WETH_b": PoolConfig( + label="COW_WETH_b", + tokens=["COW", "ETH"], + chain="base", + swap_fee=0.003, + db_label="COW_WETH_b", + plausible_start="2025-07-18", + reverse=True, + pool_address="ff028c1ec4559d3aa2b0859aa582925b5cc28069", + ), + "COW_WETH_e": PoolConfig( + label="COW_WETH_e", + tokens=["COW", "ETH"], + chain="ethereum", + swap_fee=0.003, + db_label="COW_WETH_e", + plausible_start="2025-09-21", + reverse=True, + pool_address="d321300ef77067d4a868f117d37706eb81368e98", + ), + "WAVAX_USDC": PoolConfig( + label="WAVAX_USDC", + tokens=["AVAX", "USDC"], + chain="avalanche", + swap_fee=0.001, + db_label="WAVAX_USDC", + plausible_start="2025-08-17", + reverse=False, + pool_address="8750ccffcddbff81b63790dbcb1ffd8c7dc4c16d", + ), + "GNO_USDC": PoolConfig( + label="GNO_USDC", + tokens=["GNO", "USDC"], + chain="gnosis", + swap_fee=0.003, + db_label="GNO_USDC", + plausible_start="2025-09-18", + reverse=True, + pool_address="70b3b56773ace43fe86ee1d80cbe03176cbe4c09", + ), +} + + +def _date_to_unix(date_str: str) -> int: + """Convert YYYY-MM-DD or YYYY-MM-DD HH:MM:SS to unix timestamp (seconds).""" + for fmt in ("%Y-%m-%d %H:%M:%S", "%Y-%m-%d"): + try: + dt = datetime.strptime(date_str, fmt).replace(tzinfo=timezone.utc) + return int(dt.timestamp()) + except ValueError: + continue + raise ValueError(f"Cannot parse date: {date_str}") + + +def _get_usd_price_at(ticker: str, unix_ms: int, data_root: str) -> float: + """Get the USD price of a ticker at a given unix timestamp (ms).""" + path = os.path.join(data_root, f"{ticker}_USD.parquet") + df = pd.read_parquet(path) + idx = (df["unix"] - unix_ms).abs().idxmin() + return float(df.iloc[idx]["close"]) + + +def extract_on_chain_state( + pool: PoolConfig, + db_path: str = DEFAULT_DB_PATH, + data_root: str = None, +) -> PoolConfig: + """Query the DB for on-chain state at plausible_start and compute USD TVL. + + Mutates and returns the pool config with on_chain_params and + initial_pool_value_usd filled in. + """ + if data_root is None: + data_root = os.path.join( + os.path.dirname(__file__), "..", "quantammsim", "data" + ) + + conn = sqlite3.connect(db_path) + cur = conn.cursor() + ts = _date_to_unix(pool.plausible_start) + + cur.execute( + f"""SELECT * FROM {pool.db_label} + WHERE timestamp <= ? + ORDER BY timestamp DESC LIMIT 1""", + (ts + 3600,), + ) + row = cur.fetchone() + conn.close() + + if row is None: + raise ValueError( + f"No DB data for {pool.db_label} at {pool.plausible_start}" + ) + + # DB columns: timestamp, block_number, bpt_supply, balance_0, balance_1, + # spot_price, virtual_0, virtual_1, time_last_interaction, + # price_ratio, margin, shift_rate, swap_fee + balance_0, balance_1 = row[3], row[4] + price_ratio = row[9] + margin = row[10] + shift_rate = row[11] + + pool.on_chain_params = { + "price_ratio": price_ratio, + "margin": margin, + "shift_rate": shift_rate, + "swap_fee": row[12], + } + + # Compute TVL in USD from per-token USD prices. + # DB stores balances in contract token order (bring_pool_data.py never + # applies reverse). The reverse flag tells us the mapping: + # reverse=False → balance_0=tokens[0], balance_1=tokens[1] + # reverse=True → balance_0=tokens[1], balance_1=tokens[0] + unix_ms = ts * 1000 + if pool.reverse: + tickers_in_db_order = [pool.tokens[1], pool.tokens[0]] + else: + tickers_in_db_order = [pool.tokens[0], pool.tokens[1]] + + usd_prices = [] + for ticker in tickers_in_db_order: + if ticker == "USDC": + usd_prices.append(1.0) + else: + usd_prices.append( + _get_usd_price_at(ticker, unix_ms, data_root) + ) + + pool.initial_pool_value_usd = ( + balance_0 * usd_prices[0] + balance_1 * usd_prices[1] + ) + return pool + + +def extract_initial_state( + pool: PoolConfig, + db_path: str = DEFAULT_DB_PATH, +) -> dict: + """Extract on-chain Ra, Rb, Va, Vb at plausible_start in quantammsim order. + + quantammsim sorts tokens alphabetically, so token[0] is the + alphabetically-first ticker. The reverse flag maps DB contract + order to this sorted order. + + Returns dict with keys Ra, Rb, Va, Vb (floats). + """ + conn = sqlite3.connect(db_path) + cur = conn.cursor() + ts = _date_to_unix(pool.plausible_start) + + cur.execute( + f"""SELECT balance_0, balance_1, virtual_0, virtual_1 + FROM {pool.db_label} + WHERE timestamp <= ? + ORDER BY timestamp DESC LIMIT 1""", + (ts + 3600,), + ) + row = cur.fetchone() + conn.close() + + if row is None: + raise ValueError( + f"No DB data for {pool.db_label} at {pool.plausible_start}" + ) + + b0, b1, v0, v1 = row + if pool.reverse: + # DB contract order is opposite to quantammsim sorted order + return {"Ra": b1, "Rb": b0, "Va": v1, "Vb": v0} + else: + return {"Ra": b0, "Rb": b1, "Va": v0, "Vb": v1} + + +def load_world_history( + pool: PoolConfig, + end_date: str = None, + db_path: str = DEFAULT_DB_PATH, +) -> dict: + """Load on-chain balance history from the DB. + + Returns dict with: + timestamps: array of unix timestamps (seconds) + bal_0: BPT-normalized balance of quantammsim token[0] + bal_1: BPT-normalized balance of quantammsim token[1] + raw_bal_0: raw (un-normalized) balance of quantammsim token[0] + raw_bal_1: raw (un-normalized) balance of quantammsim token[1] + governance_events: list of (timestamp, field, old_val, new_val) + """ + conn = sqlite3.connect(db_path) + cur = conn.cursor() + + ts_start = _date_to_unix(pool.plausible_start) - 1000 + if end_date: + ts_end = _date_to_unix(end_date) + else: + ts_end = 2_000_000_000 # far future + + cur.execute( + f"""SELECT timestamp, bpt_supply, balance_0, balance_1, + price_ratio, margin, shift_rate, swap_fee + FROM {pool.db_label} + WHERE timestamp BETWEEN ? AND ? + ORDER BY timestamp""", + (ts_start, ts_end), + ) + rows = cur.fetchall() + conn.close() + + if not rows: + raise ValueError(f"No world history for {pool.db_label}") + + initial_bpt = rows[0][1] + timestamps = [] + bal_db_0_norm = [] + bal_db_1_norm = [] + bal_db_0_raw = [] + bal_db_1_raw = [] + governance_events = [] + + for i, row in enumerate(rows): + ts, bpt, b0, b1, pr, margin, shift_rate, swap_fee = row + timestamps.append(ts) + norm = initial_bpt / bpt + bal_db_0_norm.append(b0 * norm) + bal_db_1_norm.append(b1 * norm) + bal_db_0_raw.append(b0) + bal_db_1_raw.append(b1) + + # Detect governance changes. + # price_ratio drifts continuously via the shift mechanism, so + # only flag large discrete jumps (>1% relative change) as governance. + # margin, shift_rate, and swap_fee are set by governance and don't drift. + if i > 0: + prev = rows[i - 1] + if not math.isclose(prev[4], pr, rel_tol=0.01): + governance_events.append((ts, "price_ratio", prev[4], pr)) + if not math.isclose(prev[5], margin, rel_tol=1e-6): + governance_events.append((ts, "margin", prev[5], margin)) + if not math.isclose(prev[6], shift_rate, rel_tol=1e-6): + governance_events.append((ts, "shift_rate", prev[6], shift_rate)) + + bal_db_0_norm = np.array(bal_db_0_norm) + bal_db_1_norm = np.array(bal_db_1_norm) + bal_db_0_raw = np.array(bal_db_0_raw) + bal_db_1_raw = np.array(bal_db_1_raw) + + # Apply reverse: swap to quantammsim sorted token order + if pool.reverse: + bal_sorted_0, bal_sorted_1 = bal_db_1_norm, bal_db_0_norm + raw_sorted_0, raw_sorted_1 = bal_db_1_raw, bal_db_0_raw + else: + bal_sorted_0, bal_sorted_1 = bal_db_0_norm, bal_db_1_norm + raw_sorted_0, raw_sorted_1 = bal_db_0_raw, bal_db_1_raw + + return { + "timestamps": np.array(timestamps), + "bal_0": bal_sorted_0, + "bal_1": bal_sorted_1, + "raw_bal_0": raw_sorted_0, + "raw_bal_1": raw_sorted_1, + "governance_events": governance_events, + } + + +def load_bpt_supply_df( + pool: PoolConfig, + end_date: str = None, + db_path: str = DEFAULT_DB_PATH, +) -> pd.DataFrame: + """Load BPT supply as a DataFrame suitable for do_run_on_historic_data. + + Returns DataFrame with columns: + unix: timestamps in milliseconds + lp_supply: BPT normalized to 1.0 at plausible_start + + The normalization matches the simulator convention: lp_supply=1.0 at the + start of the sim, scaling proportionally as the on-chain pool grows/shrinks. + """ + conn = sqlite3.connect(db_path) + cur = conn.cursor() + + ts_start = _date_to_unix(pool.plausible_start) - 1000 + if end_date: + ts_end = _date_to_unix(end_date) + else: + ts_end = 2_000_000_000 + + cur.execute( + f"""SELECT timestamp, bpt_supply + FROM {pool.db_label} + WHERE timestamp BETWEEN ? AND ? + ORDER BY timestamp""", + (ts_start, ts_end), + ) + rows = cur.fetchall() + conn.close() + + if not rows: + raise ValueError(f"No BPT data for {pool.db_label}") + + initial_bpt = rows[0][1] + return pd.DataFrame({ + # Round to nearest minute boundary so timestamps land on the minute grid + # used by raw_fee_like_amounts_to_fee_like_array. + "unix": [round(r[0] / 60) * 60 * 1000 for r in rows], + "lp_supply": [r[1] / initial_bpt for r in rows], + }) + + +def get_data_end_date(tokens: list, data_root: str = None) -> str: + """Find the latest common date across all token parquets. + + Returns a date string like '2026-02-18 00:00:00'. + """ + if data_root is None: + data_root = os.path.join( + os.path.dirname(__file__), "..", "quantammsim", "data" + ) + + min_end = float("inf") + for ticker in tokens: + path = os.path.join(data_root, f"{ticker}_USD.parquet") + df = pd.read_parquet(path, columns=["unix"]) + last = float(df["unix"].iloc[-1]) + if last < min_end: + min_end = last + + # Convert ms to datetime + dt = datetime.utcfromtimestamp(min_end / 1000) + return dt.strftime("%Y-%m-%d %H:%M:%S") + + +def load_gas_csv(percentile: str) -> pd.DataFrame: + """Load a gas percentile CSV as a DataFrame for do_run_on_historic_data. + + Returns DataFrame with columns [unix, trade_gas_cost_usd], timestamps + floored to minute boundaries. + """ + path = os.path.join(GAS_CSV_DIR, f"Gas_{percentile}.csv") + df = pd.read_csv(path) + df = df.rename(columns={"USD": "trade_gas_cost_usd"}) + df["unix"] = (df["unix"] // 60000) * 60000 # floor to minute boundary + return df + + +def get_gas_costs(pool: PoolConfig, custom: list = None) -> list: + """Return the gas cost battery for a pool's chain. + + For Ethereum, returns a list mixing flat 0.0 with gas percentile labels + (e.g. ["0.0", "50p", "75p", "90p", "95p"]). + For other chains, returns flat USD values. + """ + if custom is not None: + return custom + if pool.chain == "ethereum": + flat = [0.0, 0.1, 0.5, 1.0, 3.0, 5.0, 10.0] + return flat + ETHEREUM_GAS_PERCENTILES + return CHAIN_GAS_COSTS.get(pool.chain, [0.0, 0.1, 1.0]) + + +def print_pool_summary(pool: PoolConfig): + """Print a summary of the pool's on-chain state.""" + print(f"\n{'='*60}") + print(f"Pool: {pool.label}") + print(f" Chain: {pool.chain}") + print(f" Tokens: {pool.tokens[0]}/{pool.tokens[1]}") + print(f" Swap fee: {pool.swap_fee}") + print(f" Start: {pool.plausible_start}") + if pool.on_chain_params: + p = pool.on_chain_params + print(f" On-chain: PR={p['price_ratio']:.4f} " + f"margin={p['margin']} shift_rate={p['shift_rate']} " + f"fee={p['swap_fee']}") + if pool.initial_pool_value_usd: + print(f" TVL: ${pool.initial_pool_value_usd:,.0f} USD") + print(f" Gas battery: {get_gas_costs(pool)}") + print(f"{'='*60}") + + +if __name__ == "__main__": + # Print summary of all pools + for label, pool in POOL_REGISTRY.items(): + try: + extract_on_chain_state(pool) + print_pool_summary(pool) + except Exception as e: + print(f"\n{label}: FAILED — {e}") diff --git a/experiments/run_cross_pool_diagnostics.py b/experiments/run_cross_pool_diagnostics.py new file mode 100644 index 0000000..ae9e2ed --- /dev/null +++ b/experiments/run_cross_pool_diagnostics.py @@ -0,0 +1,423 @@ +"""Diagnostic experiments for cross-pool noise calibration. + +Runs cheap experiments to bound the value of learned cross-pool aggregation: +1. Lambda_token sweep — is the LOO failure due to overfitting token effects? +2. Leave-one-in — how much pool-specific data closes the gap? +3. Naive AR baseline — is the model barely beating lag-1? +4. Pool connectivity — which pools are predictable at all? + +Uses cached stage1 data from run_token_factored_calibration.py. +""" + +import os +import pickle +import sys + +import numpy as np +import pandas as pd + +CACHE_DIR = os.path.join( + os.path.dirname(os.path.dirname(__file__)), + "results", "token_factored_calibration", "_cache", +) +JOINT_MAXITER = 3000 # reduced for sweep speed + + +def load_stage1(): + """Load cached stage 1 (matched_clean + option_c_clean).""" + path = os.path.join(CACHE_DIR, "stage1.pkl") + if not os.path.exists(path): + print("ERROR: no stage1 cache. Run run_token_factored_calibration.py first.") + sys.exit(1) + with open(path, "rb") as f: + data = pickle.load(f) + print(f"Loaded {len(data['matched_clean'])} pools from cache") + return data["matched_clean"], data["option_c_clean"] + + +# ---- Diagnostic 1: Lambda_token sweep ---- + + +def run_lambda_token_sweep(matched_clean, option_c_clean): + """LOO with varying lambda_token to test whether overfitting is the problem.""" + from quantammsim.calibration.calibration_model import CalibrationModel + from quantammsim.calibration.heads import ( + FixedHead, PerPoolHead, TokenFactoredNoiseHead, + ) + from quantammsim.calibration.grid_interpolation import interpolate_pool_daily + from quantammsim.calibration.joint_fit import prepare_token_factored_data + from quantammsim.calibration.loss import CHAIN_GAS_USD + from quantammsim.calibration.pool_data import K_OBS_REDUCED, build_x_obs, _parse_tokens + import jax.numpy as jnp + + pool_ids = sorted(matched_clean.keys()) + lambda_tokens = [0.1, 0.5, 1.0, 5.0, 10.0] + + print("\n" + "=" * 70) + print("Diagnostic 1: Lambda_token sweep (LOO)") + print("=" * 70) + print(f" lambda_delta=1.0 fixed, sweeping lambda_token") + print(f" maxiter={JOINT_MAXITER}") + + all_results = {} + + for lt in lambda_tokens: + print(f"\n--- lambda_token={lt} ---") + loo_r2s = [] + + for hold_out_pid in pool_ids: + train_matched = {p: matched_clean[p] for p in pool_ids if p != hold_out_pid} + train_oc = {p: option_c_clean[p] for p in pool_ids if p != hold_out_pid} + + if len(train_matched) < 3: + continue + + jdata, enc = prepare_token_factored_data(train_matched) + + gas_values = [] + for pid in jdata.pool_ids: + chain = train_matched[pid]["chain"] + gas_values.append(np.log(max(CHAIN_GAS_USD.get(chain, 1.0), 1e-6))) + + noise_head = TokenFactoredNoiseHead( + k_obs=K_OBS_REDUCED, + lambda_delta=1.0, + lambda_token=lt, + **enc, + ) + model = CalibrationModel( + PerPoolHead("log_cadence", default=np.log(12.0)), + FixedHead("log_gas", np.array(gas_values)), + noise_head, + ) + result = model.fit(jdata, maxiter=JOINT_MAXITER, warm_start=train_oc) + + # Predict for held-out pool + n_train = len(jdata.pool_data) + k_attr = jdata.x_attr.shape[1] + (_, _), (_, _), (ns, ne) = model._head_slices(n_train, k_attr) + noise_params = result["params_flat"][ns:ne] + + ho_entry = matched_clean[hold_out_pid] + toks = _parse_tokens(ho_entry["tokens"]) + ho_pred = noise_head.predict_new_pool( + noise_params, toks[0], toks[1], + ho_entry["chain"], ho_entry["fee"], + n_pools=n_train, + ) + + # Evaluate + ho_panel = ho_entry["panel"] + x_obs_ho = build_x_obs(ho_panel, reduced=True) + y_obs_ho = ho_panel["log_volume"].values.astype(float) + + oc_ho = option_c_clean[hold_out_pid] + v_arb_all = np.array(interpolate_pool_daily( + ho_entry["coeffs"], + jnp.float64(oc_ho["log_cadence"]), + jnp.float64(np.exp(oc_ho["log_gas"])), + )) + v_arb = v_arb_all[ho_entry["day_indices"]] + v_noise = np.exp(x_obs_ho @ ho_pred["noise_coeffs"][:K_OBS_REDUCED]) + log_pred = np.log(np.maximum(v_arb + v_noise, 1e-6)) + ss_res = np.sum((log_pred - y_obs_ho) ** 2) + ss_tot = np.sum((y_obs_ho - y_obs_ho.mean()) ** 2) + r2 = 1 - ss_res / max(ss_tot, 1e-10) + loo_r2s.append(r2) + + tag = "OK" if r2 > 0 else "NEG" + print(f" {hold_out_pid[:16]} R²={r2:.3f} [{tag}]") + + median_r2 = np.median(loo_r2s) + wins = sum(1 for r2, pid in zip(loo_r2s, pool_ids) + if r2 > option_c_clean[pid].get("r2", 0)) + all_results[lt] = { + "median_r2": median_r2, + "mean_r2": np.mean(loo_r2s), + "r2s": loo_r2s, + "n_negative": sum(1 for r in loo_r2s if r < 0), + } + print(f" lambda_token={lt}: median R²={median_r2:.4f}, " + f"mean={np.mean(loo_r2s):.4f}, " + f"n_negative={sum(1 for r in loo_r2s if r < 0)}") + + # Summary table + print(f"\n{'='*60}") + print(f"{'lambda_token':>12} {'median_R²':>10} {'mean_R²':>10} {'n_neg':>6}") + print("-" * 42) + for lt in lambda_tokens: + r = all_results[lt] + print(f"{lt:>12.1f} {r['median_r2']:>10.4f} {r['mean_r2']:>10.4f} " + f"{r['n_negative']:>6}") + + return all_results + + +# ---- Diagnostic 2: Leave-one-in ---- + + +def run_leave_one_in(matched_clean, option_c_clean, n_days_in=30): + """LOO but give held-out pool n_days_in days of data for adaptation.""" + from quantammsim.calibration.calibration_model import CalibrationModel + from quantammsim.calibration.heads import ( + FixedHead, PerPoolHead, TokenFactoredNoiseHead, + ) + from quantammsim.calibration.grid_interpolation import interpolate_pool_daily + from quantammsim.calibration.joint_fit import prepare_token_factored_data + from quantammsim.calibration.loss import CHAIN_GAS_USD + from quantammsim.calibration.pool_data import K_OBS_REDUCED, build_x_obs, _parse_tokens + import jax.numpy as jnp + + pool_ids = sorted(matched_clean.keys()) + + print("\n" + "=" * 70) + print(f"Diagnostic 2: Leave-one-in ({n_days_in} days of held-out data)") + print("=" * 70) + + results = [] + + for hold_out_pid in pool_ids: + ho_entry = matched_clean[hold_out_pid] + ho_panel = ho_entry["panel"] + n_obs = len(ho_panel) + + if n_obs <= n_days_in + 10: + print(f" {hold_out_pid[:16]} — too few obs ({n_obs}), skipping") + continue + + # Split: first n_days_in for training, rest for evaluation + train_panel = ho_panel.iloc[:n_days_in].copy() + eval_panel = ho_panel.iloc[n_days_in:].copy() + train_day_indices = ho_entry["day_indices"][:n_days_in] + eval_day_indices = ho_entry["day_indices"][n_days_in:] + + # Build training matched: all other pools + truncated held-out pool + train_matched = {} + for p in pool_ids: + if p != hold_out_pid: + train_matched[p] = matched_clean[p] + + # Add truncated held-out pool + ho_train_entry = dict(ho_entry) + ho_train_entry["panel"] = train_panel.reset_index(drop=True) + ho_train_entry["day_indices"] = train_day_indices + train_matched[hold_out_pid] = ho_train_entry + + train_oc = dict(option_c_clean) # all pools including held-out + + # Fit with held-out pool included (gets its own delta from 30 days) + jdata, enc = prepare_token_factored_data(train_matched) + + gas_values = [] + for pid in jdata.pool_ids: + chain = train_matched[pid]["chain"] + gas_values.append(np.log(max(CHAIN_GAS_USD.get(chain, 1.0), 1e-6))) + + noise_head = TokenFactoredNoiseHead( + k_obs=K_OBS_REDUCED, + lambda_delta=1.0, + lambda_token=0.1, + **enc, + ) + model = CalibrationModel( + PerPoolHead("log_cadence", default=np.log(12.0)), + FixedHead("log_gas", np.array(gas_values)), + noise_head, + ) + result = model.fit(jdata, maxiter=JOINT_MAXITER, warm_start=train_oc) + + # Find held-out pool's index in training set and extract noise_coeffs + ho_idx = jdata.pool_ids.index(hold_out_pid) + noise_coeffs = result["noise_coeffs"][ho_idx] + + # Evaluate on held-out days + x_obs_eval = build_x_obs(eval_panel, reduced=True) + y_obs_eval = eval_panel["log_volume"].values.astype(float) + + oc_ho = option_c_clean[hold_out_pid] + v_arb_all = np.array(interpolate_pool_daily( + ho_entry["coeffs"], + jnp.float64(oc_ho["log_cadence"]), + jnp.float64(np.exp(oc_ho["log_gas"])), + )) + v_arb = v_arb_all[eval_day_indices] + v_noise = np.exp(x_obs_eval @ noise_coeffs[:K_OBS_REDUCED]) + log_pred = np.log(np.maximum(v_arb + v_noise, 1e-6)) + ss_res = np.sum((log_pred - y_obs_eval) ** 2) + ss_tot = np.sum((y_obs_eval - y_obs_eval.mean()) ** 2) + r2_in = 1 - ss_res / max(ss_tot, 1e-10) + + # Also compute Option C R² on eval days for comparison + v_noise_c = np.exp(x_obs_eval @ oc_ho["noise_coeffs"][:K_OBS_REDUCED]) + log_pred_c = np.log(np.maximum(v_arb + v_noise_c, 1e-6)) + ss_res_c = np.sum((log_pred_c - y_obs_eval) ** 2) + r2_c_eval = 1 - ss_res_c / max(ss_tot, 1e-10) + + results.append({ + "pool_id": hold_out_pid, + "r2_leave_one_in": r2_in, + "r2_option_c_eval": r2_c_eval, + "n_train_days": n_days_in, + "n_eval_days": len(eval_panel), + "tokens": ho_entry["tokens"], + }) + + print(f" {hold_out_pid[:16]} ({ho_entry['tokens']:<14}) " + f"R²_in={r2_in:.3f} R²_C_eval={r2_c_eval:.3f} " + f"n_eval={len(eval_panel)}") + + if results: + r2s_in = [r["r2_leave_one_in"] for r in results] + r2s_c = [r["r2_option_c_eval"] for r in results] + print(f"\n Leave-one-in ({n_days_in}d): median R²={np.median(r2s_in):.4f}") + print(f" Option C (eval days): median R²={np.median(r2s_c):.4f}") + print(f" Recall: zero-shot LOO: median R²=0.362") + + return results + + +# ---- Diagnostic 3: Naive AR baseline ---- + + +def run_naive_ar_baseline(matched_clean): + """Compute R² of vol_tomorrow = vol_today (no model, no cross-pool).""" + print("\n" + "=" * 70) + print("Diagnostic 3: Naive autoregressive baseline (lag-1 copy)") + print("=" * 70) + + pool_r2s = [] + for pid in sorted(matched_clean.keys()): + panel = matched_clean[pid]["panel"] + y = panel["log_volume"].values.astype(float) + + if len(y) < 3: + continue + + # Predict day t from day t-1 + y_true = y[1:] + y_pred = y[:-1] + + ss_res = np.sum((y_pred - y_true) ** 2) + ss_tot = np.sum((y_true - y_true.mean()) ** 2) + r2 = 1 - ss_res / max(ss_tot, 1e-10) + pool_r2s.append(r2) + + print(f" {pid[:16]} ({matched_clean[pid]['tokens']:<14}) " + f"R²_AR1={r2:.3f} n_obs={len(y)}") + + print(f"\n Naive AR1: median R²={np.median(pool_r2s):.4f}, " + f"mean={np.mean(pool_r2s):.4f}") + print(f" Recall: zero-shot LOO = 0.362, Option C in-sample = 0.589") + + return pool_r2s + + +# ---- Diagnostic 4: Pool connectivity analysis ---- + + +def run_connectivity_analysis(matched_clean, option_c_clean): + """Analyze token overlap and partition LOO R² by connectivity.""" + from quantammsim.calibration.pool_data import _parse_tokens, _canonicalize_token + + print("\n" + "=" * 70) + print("Diagnostic 4: Pool connectivity analysis") + print("=" * 70) + + pool_ids = sorted(matched_clean.keys()) + + # Build canonical token sets per pool + pool_tokens = {} + for pid in pool_ids: + toks = _parse_tokens(matched_clean[pid]["tokens"]) + canon = {_canonicalize_token(t) for t in toks[:2]} + pool_tokens[pid] = canon + + # Count: for each pool, how many other pools share at least 1 token? + # And how many share both tokens? + print(f"\n{'Pool':<18} {'Tokens':<16} {'1+ shared':>10} {'2 shared':>10} " + f"{'R²_C':>8}") + print("-" * 66) + + connectivity = [] + for pid in pool_ids: + my_toks = pool_tokens[pid] + n_one_shared = 0 + n_both_shared = 0 + for other in pool_ids: + if other == pid: + continue + overlap = len(my_toks & pool_tokens[other]) + if overlap >= 1: + n_one_shared += 1 + if overlap >= 2: + n_both_shared += 1 + + oc = option_c_clean[pid] + r2_c = 1 - oc["loss"] / max( + np.var(matched_clean[pid]["panel"]["log_volume"].values) * + len(matched_clean[pid]["panel"]) / + max(len(matched_clean[pid]["panel"]) - 1, 1), + 1e-10, + ) + + connectivity.append({ + "pool_id": pid, + "tokens": matched_clean[pid]["tokens"], + "n_one_shared": n_one_shared, + "n_both_shared": n_both_shared, + }) + + print(f" {pid[:16]} {matched_clean[pid]['tokens']:<16} " + f"{n_one_shared:>10} {n_both_shared:>10}") + + # Partition: well-connected (1+ shared ≥ 3) vs isolated + well_connected = [c for c in connectivity if c["n_one_shared"] >= 3] + isolated = [c for c in connectivity if c["n_one_shared"] < 3] + + print(f"\n Well-connected (≥3 pools share a token): {len(well_connected)}") + print(f" Isolated (<3 pools share a token): {len(isolated)}") + + if isolated: + print(f"\n Isolated pools:") + for c in isolated: + print(f" {c['pool_id'][:16]} {c['tokens']}") + + return connectivity + + +# ---- Main ---- + + +def main(): + os.environ.setdefault("JAX_PLATFORMS", "cpu") + + print("=" * 70) + print("Cross-Pool Calibration Diagnostics") + print("=" * 70) + + matched_clean, option_c_clean = load_stage1() + + # Run all diagnostics + ar_results = run_naive_ar_baseline(matched_clean) + connectivity = run_connectivity_analysis(matched_clean, option_c_clean) + leave_one_in = run_leave_one_in(matched_clean, option_c_clean, n_days_in=30) + lambda_sweep = run_lambda_token_sweep(matched_clean, option_c_clean) + + # Final summary + print("\n" + "=" * 70) + print("SUMMARY") + print("=" * 70) + print(f" Naive AR1 baseline: median R² = {np.median(ar_results):.4f}") + if leave_one_in: + r2s_in = [r["r2_leave_one_in"] for r in leave_one_in] + print(f" Leave-one-in (30 days): median R² = {np.median(r2s_in):.4f}") + print(f" Zero-shot LOO (current): median R² = 0.362") + print(f" Option C in-sample: median R² = 0.589") + print(f"\n Lambda_token sweep:") + for lt, r in sorted(lambda_sweep.items()): + print(f" lambda_token={lt:>5.1f}: median R² = {r['median_r2']:.4f} " + f"(n_neg={r['n_negative']})") + + +if __name__ == "__main__": + main() diff --git a/experiments/run_cross_pool_linear.py b/experiments/run_cross_pool_linear.py new file mode 100644 index 0000000..acd5c48 --- /dev/null +++ b/experiments/run_cross_pool_linear.py @@ -0,0 +1,457 @@ +"""Cross-pool linear volume prediction baselines. + +1. Ridge cross-pool regression: log_vol_i_t = W_i @ log_vol_{-i, t-1} + - In-sample: fit full 36x36 W, evaluate on training data + - LOO: hold out pool i, fit W on 35 pools, predict pool i using + token-overlap-weighted average of learned rows (transfer via similarity) + - LOO with burn-in: use 30 days of pool i to learn its row of W directly + +2. Zero-parameter peer-mean: predicted_vol_i_t = mean(log_vol_{j,t-1}) + for peers sharing a canonical token with pool i +""" + +import os +import pickle +import sys + +import numpy as np +from sklearn.linear_model import RidgeCV + +CACHE_DIR = os.path.join( + os.path.dirname(os.path.dirname(__file__)), + "results", "token_factored_calibration", "_cache", +) + + +def load_stage1(): + path = os.path.join(CACHE_DIR, "stage1.pkl") + if not os.path.exists(path): + print("ERROR: no stage1 cache. Run run_token_factored_calibration.py first.") + sys.exit(1) + with open(path, "rb") as f: + data = pickle.load(f) + print(f"Loaded {len(data['matched_clean'])} pools from cache") + return data["matched_clean"], data["option_c_clean"] + + +def build_volume_matrix(matched_clean): + """Build (n_dates, n_pools) aligned volume matrix. + + Returns vol_matrix, date_list, pool_ids. + Dates are the intersection of all pools' date ranges. + Missing values filled with NaN. + """ + pool_ids = sorted(matched_clean.keys()) + + # Collect all (pool, date) -> log_volume + pool_date_vol = {} + all_dates = set() + for pid in pool_ids: + panel = matched_clean[pid]["panel"] + dates = panel["date"].values + vols = panel["log_volume"].values.astype(float) + pool_date_vol[pid] = dict(zip(dates, vols)) + all_dates.update(dates) + + date_list = sorted(all_dates) + n_dates = len(date_list) + n_pools = len(pool_ids) + + vol_matrix = np.full((n_dates, n_pools), np.nan) + for j, pid in enumerate(pool_ids): + dv = pool_date_vol[pid] + for t, date in enumerate(date_list): + if date in dv: + vol_matrix[t, j] = dv[date] + + return vol_matrix, date_list, pool_ids + + +def build_token_overlap(matched_clean, pool_ids): + """Build (n_pools, n_pools) token overlap matrix (0, 1, or 2).""" + from quantammsim.calibration.pool_data import _parse_tokens, _canonicalize_token + + n = len(pool_ids) + overlap = np.zeros((n, n), dtype=np.int32) + pool_tokens = {} + for i, pid in enumerate(pool_ids): + toks = _parse_tokens(matched_clean[pid]["tokens"]) + pool_tokens[i] = {_canonicalize_token(t) for t in toks[:2]} + + for i in range(n): + for j in range(n): + overlap[i, j] = len(pool_tokens[i] & pool_tokens[j]) + + return overlap + + +def r2_score(y_true, y_pred): + ss_res = np.sum((y_true - y_pred) ** 2) + ss_tot = np.sum((y_true - y_true.mean()) ** 2) + return 1 - ss_res / max(ss_tot, 1e-10) + + +# ---- 1. Ridge cross-pool regression ---- + + +def run_ridge_cross_pool(matched_clean): + """Full cross-pool ridge: predict each pool from all others' lag-1.""" + vol_matrix, date_list, pool_ids = build_volume_matrix(matched_clean) + n_dates, n_pools = vol_matrix.shape + + # Check if fully-observed rows exist + X_lag = vol_matrix[:-1, :] + Y_cur = vol_matrix[1:, :] + valid = ~np.any(np.isnan(X_lag), axis=1) & ~np.any(np.isnan(Y_cur), axis=1) + n_valid = int(valid.sum()) + + # Peers only + print("\n" + "=" * 70) + print("1a. Ridge cross-pool regression (in-sample, peers only)") + print("=" * 70) + print(f" {n_pools} pools, {n_valid} fully-observed day pairs " + f"(of {n_dates-1} total)") + print(" Using per-pool valid rows with NaN imputation.") + r2_peers, _, _, _ = _run_ridge_per_pool_valid( + vol_matrix, pool_ids, matched_clean, include_own_lag=False) + + # Peers + own lag + print("\n" + "=" * 70) + print("1a+. Ridge cross-pool regression (in-sample, peers + own lag)") + print("=" * 70) + r2_both, _, _, _ = _run_ridge_per_pool_valid( + vol_matrix, pool_ids, matched_clean, include_own_lag=True) + + return r2_peers, r2_both, vol_matrix, date_list, pool_ids + + +def _run_ridge_per_pool_valid(vol_matrix, pool_ids, matched_clean, + include_own_lag=False): + """Fallback: per-pool ridge using only rows where pool i AND predictors have data.""" + n_dates, n_pools = vol_matrix.shape + tag = " + own_lag" if include_own_lag else "" + + pool_r2s = [] + for i, pid in enumerate(pool_ids): + X_lag = vol_matrix[:-1, :] + y_cur = vol_matrix[1:, i] + own_lag = X_lag[:, i] # pool i's own lag + + # Valid: pool i has data today AND own lag exists (if used) + valid_y = ~np.isnan(y_cur) + if include_own_lag: + valid_y = valid_y & ~np.isnan(own_lag) + + X_others = np.delete(X_lag, i, axis=1) + + # For each predictor, fill NaN with that predictor's mean (simple imputation) + X_filled = X_others.copy() + for j in range(X_filled.shape[1]): + col = X_filled[:, j] + col_mean = np.nanmean(col) + col[np.isnan(col)] = col_mean + X_filled[:, j] = col + + if include_own_lag: + X_full = np.column_stack([X_filled, own_lag[:, None]]) + else: + X_full = X_filled + + X_i = X_full[valid_y] + y_i = y_cur[valid_y] + + if len(y_i) < 10: + pool_r2s.append(np.nan) + continue + + model = RidgeCV(alphas=np.logspace(-2, 4, 50)) + model.fit(X_i, y_i) + y_pred = model.predict(X_i) + r2 = r2_score(y_i, y_pred) + pool_r2s.append(r2) + + print(f" {pid[:16]} ({matched_clean[pid]['tokens']:<14}) " + f"R²={r2:.3f} n_obs={len(y_i)} alpha={model.alpha_:.1f}") + + valid_r2s = [r for r in pool_r2s if not np.isnan(r)] + print(f"\n In-sample ridge{tag}: median R²={np.median(valid_r2s):.4f}, " + f"mean={np.mean(valid_r2s):.4f}") + + return pool_r2s, vol_matrix, None, pool_ids + + +def run_ridge_loo(matched_clean): + """LOO cross-pool ridge with token-overlap transfer.""" + print("\n" + "=" * 70) + print("1b. Ridge cross-pool LOO (transfer via token overlap)") + print("=" * 70) + + vol_matrix, date_list, pool_ids = build_volume_matrix(matched_clean) + n_dates, n_pools = vol_matrix.shape + overlap = build_token_overlap(matched_clean, pool_ids) + + pool_r2s = [] + for i, pid in enumerate(pool_ids): + # Training pools: all except i + train_idx = [j for j in range(n_pools) if j != i] + n_train = len(train_idx) + + # Build training data: for each training pool k, predict from others' lag + # Use per-pool valid rows with NaN imputation + X_lag_all = vol_matrix[:-1, :] + Y_cur_all = vol_matrix[1:, :] + + # Fit a ridge model for each training pool + train_models = {} + train_weights = {} # weight vectors (excluding self) + for k_pos, k in enumerate(train_idx): + # Predictors: all pools except k (including pool i's historical data!) + pred_idx = [j for j in range(n_pools) if j != k] + X_k = X_lag_all[:, pred_idx].copy() + y_k = Y_cur_all[:, k] + + valid = ~np.isnan(y_k) + for c in range(X_k.shape[1]): + col = X_k[:, c] + m = np.nanmean(col) + col[np.isnan(col)] = m if np.isfinite(m) else 0.0 + X_k[:, c] = col + + X_k = X_k[valid] + y_k = y_k[valid] + + if len(y_k) < 10: + continue + + model = RidgeCV(alphas=np.logspace(-2, 4, 50)) + model.fit(X_k, y_k) + train_models[k] = model + + # Store full weight vector (n_pools-1,) with mapping to pool indices + w = np.zeros(n_pools) + for widx, pidx in enumerate(pred_idx): + w[pidx] = model.coef_[widx] + w_intercept = model.intercept_ + train_weights[k] = (w, w_intercept) + + if not train_weights: + pool_r2s.append(np.nan) + continue + + # Transfer to held-out pool i: weighted average of training pools' weight vectors + # Weight by token overlap with pool i + w_transfer = np.zeros(n_pools) + intercept_transfer = 0.0 + total_sim = 0.0 + for k in train_weights: + sim = overlap[i, k] + if sim == 0: + sim = 0.1 # small weight for unrelated pools + w_k, b_k = train_weights[k] + w_transfer += sim * w_k + intercept_transfer += sim * b_k + total_sim += sim + + w_transfer /= total_sim + intercept_transfer /= total_sim + + # Zero out pool i's own weight (shouldn't predict from self) + w_transfer[i] = 0.0 + + # Predict pool i + X_lag_i = vol_matrix[:-1, :].copy() + y_true_i = vol_matrix[1:, i] + valid = ~np.isnan(y_true_i) + + # Impute NaN predictors + for c in range(X_lag_i.shape[1]): + col = X_lag_i[:, c] + m = np.nanmean(col) + col[np.isnan(col)] = m if np.isfinite(m) else 0.0 + X_lag_i[:, c] = col + + y_pred_i = X_lag_i[valid] @ w_transfer + intercept_transfer + y_true_i = y_true_i[valid] + + r2 = r2_score(y_true_i, y_pred_i) + pool_r2s.append(r2) + + print(f" {pid[:16]} ({matched_clean[pid]['tokens']:<14}) " + f"R²={r2:.3f} n_eval={len(y_true_i)}") + + valid_r2s = [r for r in pool_r2s if not np.isnan(r)] + print(f"\n LOO ridge (overlap transfer): median R²={np.median(valid_r2s):.4f}, " + f"mean={np.mean(valid_r2s):.4f}") + print(f" Recall: AR1={0.397:.3f}, zero-shot token-factored={0.362:.3f}") + + return pool_r2s + + +def run_ridge_loo_burnin(matched_clean, n_burnin=30): + """LOO with burn-in: learn pool i's weight row from n_burnin days.""" + print("\n" + "=" * 70) + print(f"1c. Ridge cross-pool LOO with {n_burnin}-day burn-in") + print("=" * 70) + + vol_matrix, date_list, pool_ids = build_volume_matrix(matched_clean) + n_dates, n_pools = vol_matrix.shape + + pool_r2s = [] + for i, pid in enumerate(pool_ids): + # Pool i's data + y_all = vol_matrix[:, i] + valid_days = ~np.isnan(y_all) + valid_indices = np.where(valid_days)[0] + + if len(valid_indices) < n_burnin + 10: + print(f" {pid[:16]} — too few obs, skipping") + pool_r2s.append(np.nan) + continue + + # Split: first n_burnin valid days for training, rest for eval + burn_indices = valid_indices[:n_burnin] + eval_indices = valid_indices[n_burnin:] + + # Training: predict pool i from all others' lag using burn-in days + # Need (day, day-1) pairs where day is in burn_indices and day >= 1 + burn_pairs = burn_indices[burn_indices >= 1] + + pred_idx = [j for j in range(n_pools) if j != i] + X_burn = vol_matrix[burn_pairs - 1][:, pred_idx].copy() + y_burn = vol_matrix[burn_pairs, i] + + # Impute NaN + for c in range(X_burn.shape[1]): + col = X_burn[:, c] + m = np.nanmean(col) + col[np.isnan(col)] = m if np.isfinite(m) else 0.0 + X_burn[:, c] = col + + model = RidgeCV(alphas=np.logspace(-2, 4, 50)) + model.fit(X_burn, y_burn) + + # Evaluate on remaining days + eval_pairs = eval_indices[eval_indices >= 1] + X_eval = vol_matrix[eval_pairs - 1][:, pred_idx].copy() + y_eval = vol_matrix[eval_pairs, i] + + for c in range(X_eval.shape[1]): + col = X_eval[:, c] + m = np.nanmean(col) + col[np.isnan(col)] = m if np.isfinite(m) else 0.0 + X_eval[:, c] = col + + y_pred = model.predict(X_eval) + r2 = r2_score(y_eval, y_pred) + pool_r2s.append(r2) + + print(f" {pid[:16]} ({matched_clean[pid]['tokens']:<14}) " + f"R²={r2:.3f} n_burn={len(burn_pairs)} n_eval={len(eval_pairs)} " + f"alpha={model.alpha_:.1f}") + + valid_r2s = [r for r in pool_r2s if not np.isnan(r)] + print(f"\n LOO ridge ({n_burnin}d burn-in): " + f"median R²={np.median(valid_r2s):.4f}, " + f"mean={np.mean(valid_r2s):.4f}") + + return pool_r2s + + +# ---- 2. Zero-parameter peer-mean ---- + + +def run_peer_mean_baseline(matched_clean): + """Predict pool i's volume as mean of token-peer lagged volumes.""" + from quantammsim.calibration.pool_data import _parse_tokens, _canonicalize_token + + print("\n" + "=" * 70) + print("2. Zero-parameter peer-mean baseline") + print("=" * 70) + + vol_matrix, date_list, pool_ids = build_volume_matrix(matched_clean) + n_dates, n_pools = vol_matrix.shape + overlap = build_token_overlap(matched_clean, pool_ids) + + pool_r2s = [] + for i, pid in enumerate(pool_ids): + # Peers: pools sharing at least 1 token + peers = [j for j in range(n_pools) if j != i and overlap[i, j] >= 1] + + if not peers: + pool_r2s.append(np.nan) + continue + + y_true = vol_matrix[1:, i] + valid = ~np.isnan(y_true) + + # Peer mean at t-1 + peer_lag = vol_matrix[:-1, :][:, peers] + peer_mean = np.nanmean(peer_lag, axis=1) + + y_pred = peer_mean[valid] + y_true = y_true[valid] + + # Remove any remaining NaN + both_valid = ~np.isnan(y_pred) + y_pred = y_pred[both_valid] + y_true = y_true[both_valid] + + r2 = r2_score(y_true, y_pred) + pool_r2s.append(r2) + + print(f" {pid[:16]} ({matched_clean[pid]['tokens']:<14}) " + f"R²={r2:.3f} n_peers={len(peers)} n_obs={len(y_true)}") + + valid_r2s = [r for r in pool_r2s if not np.isnan(r)] + print(f"\n Peer-mean baseline: median R²={np.median(valid_r2s):.4f}, " + f"mean={np.mean(valid_r2s):.4f}") + print(f" Recall: AR1={0.397:.3f}, zero-shot token-factored={0.362:.3f}") + + return pool_r2s + + +# ---- Main ---- + + +def main(): + os.environ.setdefault("JAX_PLATFORMS", "cpu") + + print("=" * 70) + print("Cross-Pool Linear Volume Prediction Baselines") + print("=" * 70) + + matched_clean, option_c_clean = load_stage1() + + # 1a. In-sample ridge (peers only + peers + own lag) + r2_peers, r2_both, vol_matrix, date_list, pool_ids = run_ridge_cross_pool(matched_clean) + + # 1b. LOO ridge with token-overlap transfer + loo_r2s = run_ridge_loo(matched_clean) + + # 1c. LOO ridge with 30-day burn-in + burnin_r2s = run_ridge_loo_burnin(matched_clean, n_burnin=30) + + # 2. Zero-parameter peer mean + peer_r2s = run_peer_mean_baseline(matched_clean) + + # Summary + def safe_median(xs): + v = [x for x in xs if not np.isnan(x)] + return np.median(v) if v else float("nan") + + print("\n" + "=" * 70) + print("SUMMARY") + print("=" * 70) + print(f" Ridge in-sample (peers): median R² = {safe_median(r2_peers):.4f}") + print(f" Ridge in-sample (+own): median R² = {safe_median(r2_both):.4f}") + print(f" Ridge LOO (overlap xfer): median R² = {safe_median(loo_r2s):.4f}") + print(f" Ridge LOO (30d burn-in): median R² = {safe_median(burnin_r2s):.4f}") + print(f" Peer-mean (0 params): median R² = {safe_median(peer_r2s):.4f}") + print(f" ---") + print(f" Naive AR1: median R² = 0.397") + print(f" Token-factored LOO: median R² = 0.362") + print(f" Option C in-sample: median R² = 0.589") + + +if __name__ == "__main__": + main() diff --git a/experiments/run_cross_pool_noise_linear.py b/experiments/run_cross_pool_noise_linear.py new file mode 100644 index 0000000..08ead10 --- /dev/null +++ b/experiments/run_cross_pool_noise_linear.py @@ -0,0 +1,392 @@ +"""Cross-pool linear prediction of NOISE residuals. + +Decomposes total volume into V_arb (from grid + Option C cadence/gas) +and noise residual, then tests whether peer pools' lagged noise +residuals predict this pool's noise residual. + +1. Ridge in-sample: noise_resid_i_t = W_i @ noise_resid_{-i, t-1} [+ own_lag] +2. Ridge LOO with overlap transfer +3. Ridge LOO with 30-day burn-in +""" + +import os +import pickle +import sys + +import numpy as np +from sklearn.linear_model import RidgeCV + +CACHE_DIR = os.path.join( + os.path.dirname(os.path.dirname(__file__)), + "results", "token_factored_calibration", "_cache", +) + + +def load_stage1(): + path = os.path.join(CACHE_DIR, "stage1.pkl") + if not os.path.exists(path): + print("ERROR: no stage1 cache.") + sys.exit(1) + with open(path, "rb") as f: + data = pickle.load(f) + print(f"Loaded {len(data['matched_clean'])} pools from cache") + return data["matched_clean"], data["option_c_clean"] + + +def build_noise_residual_matrix(matched_clean, option_c_clean): + """Build (n_dates, n_pools) noise residual matrix. + + noise_resid_i_t = log_volume_i_t - log(V_arb_i_t) + + V_arb computed from grid interpolation at Option C cadence/gas. + Returns residual matrix (NaN where missing), date list, pool ids. + """ + import jax.numpy as jnp + from quantammsim.calibration.grid_interpolation import interpolate_pool_daily + + pool_ids = sorted(matched_clean.keys()) + n_pools = len(pool_ids) + + # Collect all dates + all_dates = set() + for pid in pool_ids: + panel = matched_clean[pid]["panel"] + all_dates.update(panel["date"].values) + date_list = sorted(all_dates) + n_dates = len(date_list) + date_to_idx = {d: i for i, d in enumerate(date_list)} + + # Build matrices + vol_matrix = np.full((n_dates, n_pools), np.nan) + resid_matrix = np.full((n_dates, n_pools), np.nan) + + for j, pid in enumerate(pool_ids): + entry = matched_clean[pid] + oc = option_c_clean[pid] + panel = entry["panel"] + coeffs = entry["coeffs"] + day_indices = entry["day_indices"] + + # Compute V_arb from grid + v_arb_all = np.array(interpolate_pool_daily( + coeffs, + jnp.float64(oc["log_cadence"]), + jnp.float64(np.exp(oc["log_gas"])), + )) + v_arb = v_arb_all[day_indices] + log_v_arb = np.log(np.maximum(v_arb, 1e-6)) + + # Fill matrices + dates = panel["date"].values + log_vols = panel["log_volume"].values.astype(float) + + for k, date in enumerate(dates): + t = date_to_idx[date] + vol_matrix[t, j] = log_vols[k] + resid_matrix[t, j] = log_vols[k] - log_v_arb[k] + + print(f" Built noise residual matrix: {n_dates} dates x {n_pools} pools") + print(f" Residual stats: mean={np.nanmean(resid_matrix):.3f}, " + f"std={np.nanstd(resid_matrix):.3f}") + + return vol_matrix, resid_matrix, date_list, pool_ids + + +def build_token_overlap(matched_clean, pool_ids): + from quantammsim.calibration.pool_data import _parse_tokens, _canonicalize_token + n = len(pool_ids) + overlap = np.zeros((n, n), dtype=np.int32) + pool_tokens = {} + for i, pid in enumerate(pool_ids): + toks = _parse_tokens(matched_clean[pid]["tokens"]) + pool_tokens[i] = {_canonicalize_token(t) for t in toks[:2]} + for i in range(n): + for j in range(n): + overlap[i, j] = len(pool_tokens[i] & pool_tokens[j]) + return overlap + + +def r2_score(y_true, y_pred): + ss_res = np.sum((y_true - y_pred) ** 2) + ss_tot = np.sum((y_true - y_true.mean()) ** 2) + return 1 - ss_res / max(ss_tot, 1e-10) + + +# ---- In-sample ridge on noise residuals ---- + + +def run_ridge_insample(resid_matrix, pool_ids, matched_clean): + """Per-pool ridge: predict noise_resid from peers' lagged residuals.""" + n_dates, n_pools = resid_matrix.shape + + for include_own in [False, True]: + tag = "peers + own_lag" if include_own else "peers only" + print(f"\n{'='*70}") + print(f"Ridge in-sample on noise residuals ({tag})") + print(f"{'='*70}") + + pool_r2s = [] + for i, pid in enumerate(pool_ids): + X_lag = resid_matrix[:-1, :] + y_cur = resid_matrix[1:, i] + own_lag = X_lag[:, i] + + valid = ~np.isnan(y_cur) + if include_own: + valid = valid & ~np.isnan(own_lag) + + X_others = np.delete(X_lag, i, axis=1) + X_filled = X_others.copy() + for c in range(X_filled.shape[1]): + col = X_filled[:, c] + m = np.nanmean(col) + col[np.isnan(col)] = m if np.isfinite(m) else 0.0 + X_filled[:, c] = col + + if include_own: + X_full = np.column_stack([X_filled, own_lag[:, None]]) + else: + X_full = X_filled + + X_i = X_full[valid] + y_i = y_cur[valid] + + if len(y_i) < 10: + pool_r2s.append(np.nan) + continue + + model = RidgeCV(alphas=np.logspace(-2, 4, 50)) + model.fit(X_i, y_i) + y_pred = model.predict(X_i) + r2 = r2_score(y_i, y_pred) + pool_r2s.append(r2) + + print(f" {pid[:16]} ({matched_clean[pid]['tokens']:<14}) " + f"R²={r2:.3f} n={len(y_i)} alpha={model.alpha_:.1f}") + + valid_r2s = [r for r in pool_r2s if not np.isnan(r)] + print(f"\n In-sample ridge ({tag}): median R²={np.median(valid_r2s):.4f}, " + f"mean={np.mean(valid_r2s):.4f}") + + return pool_r2s + + +def run_ar1_noise_baseline(resid_matrix, pool_ids, matched_clean): + """Naive AR1 on noise residuals: resid_tomorrow = resid_today.""" + print(f"\n{'='*70}") + print("AR1 baseline on noise residuals") + print(f"{'='*70}") + + pool_r2s = [] + for i, pid in enumerate(pool_ids): + y = resid_matrix[:, i] + valid = ~np.isnan(y[:-1]) & ~np.isnan(y[1:]) + y_true = y[1:][valid] + y_pred = y[:-1][valid] + + if len(y_true) < 3: + pool_r2s.append(np.nan) + continue + + r2 = r2_score(y_true, y_pred) + pool_r2s.append(r2) + print(f" {pid[:16]} ({matched_clean[pid]['tokens']:<14}) " + f"R²={r2:.3f} n={len(y_true)}") + + valid_r2s = [r for r in pool_r2s if not np.isnan(r)] + print(f"\n AR1 noise residual: median R²={np.median(valid_r2s):.4f}, " + f"mean={np.mean(valid_r2s):.4f}") + return pool_r2s + + +# ---- LOO with overlap transfer ---- + + +def run_ridge_loo(resid_matrix, pool_ids, matched_clean): + """LOO on noise residuals with token-overlap weight transfer.""" + print(f"\n{'='*70}") + print("Ridge LOO on noise residuals (overlap transfer, peers + own_lag)") + print(f"{'='*70}") + + n_dates, n_pools = resid_matrix.shape + overlap = build_token_overlap(matched_clean, pool_ids) + + pool_r2s = [] + for i, pid in enumerate(pool_ids): + train_idx = [j for j in range(n_pools) if j != i] + + # Fit ridge for each training pool + train_weights = {} + for k in train_idx: + pred_idx = [j for j in range(n_pools) if j != k] + X_lag = resid_matrix[:-1, pred_idx].copy() + own_lag_k = resid_matrix[:-1, k] + y_k = resid_matrix[1:, k] + + valid = ~np.isnan(y_k) & ~np.isnan(own_lag_k) + for c in range(X_lag.shape[1]): + col = X_lag[:, c] + m = np.nanmean(col) + col[np.isnan(col)] = m if np.isfinite(m) else 0.0 + X_lag[:, c] = col + + X_k = np.column_stack([X_lag[valid], own_lag_k[valid, None]]) + y_k = y_k[valid] + + if len(y_k) < 10: + continue + + model = RidgeCV(alphas=np.logspace(-2, 4, 50)) + model.fit(X_k, y_k) + + # Store weights mapped to pool indices + w = np.zeros(n_pools + 1) # +1 for own_lag + for widx, pidx in enumerate(pred_idx): + w[pidx] = model.coef_[widx] + w[-1] = model.coef_[-1] # own_lag weight + train_weights[k] = (w, model.intercept_) + + if not train_weights: + pool_r2s.append(np.nan) + continue + + # Transfer: overlap-weighted average of training pool weight vectors + w_transfer = np.zeros(n_pools + 1) + b_transfer = 0.0 + total_sim = 0.0 + for k in train_weights: + sim = max(overlap[i, k], 0.1) + w_k, b_k = train_weights[k] + w_transfer += sim * w_k + b_transfer += sim * b_k + total_sim += sim + w_transfer /= total_sim + b_transfer /= total_sim + w_transfer[i] = 0.0 # no self-prediction from peers + + # Predict held-out pool + X_lag_all = resid_matrix[:-1, :].copy() + own_lag_i = resid_matrix[:-1, i] + y_true = resid_matrix[1:, i] + valid = ~np.isnan(y_true) & ~np.isnan(own_lag_i) + + for c in range(X_lag_all.shape[1]): + col = X_lag_all[:, c] + m = np.nanmean(col) + col[np.isnan(col)] = m if np.isfinite(m) else 0.0 + X_lag_all[:, c] = col + + X_i = np.column_stack([X_lag_all[valid], own_lag_i[valid, None]]) + y_pred = X_i @ w_transfer + b_transfer + y_true = y_true[valid] + + r2 = r2_score(y_true, y_pred) + pool_r2s.append(r2) + print(f" {pid[:16]} ({matched_clean[pid]['tokens']:<14}) " + f"R²={r2:.3f} n={len(y_true)}") + + valid_r2s = [r for r in pool_r2s if not np.isnan(r)] + print(f"\n LOO ridge (overlap transfer): median R²={np.median(valid_r2s):.4f}, " + f"mean={np.mean(valid_r2s):.4f}") + return pool_r2s + + +# ---- LOO with burn-in ---- + + +def run_ridge_burnin(resid_matrix, pool_ids, matched_clean, n_burnin=30): + """LOO with burn-in: learn pool i's weights from first n_burnin days.""" + print(f"\n{'='*70}") + print(f"Ridge LOO on noise residuals ({n_burnin}d burn-in, peers + own_lag)") + print(f"{'='*70}") + + n_dates, n_pools = resid_matrix.shape + + pool_r2s = [] + for i, pid in enumerate(pool_ids): + y_all = resid_matrix[:, i] + own_lag_all = np.full(n_dates, np.nan) + own_lag_all[1:] = y_all[:-1] + + valid_days = ~np.isnan(y_all) & ~np.isnan(own_lag_all) + valid_indices = np.where(valid_days)[0] + + if len(valid_indices) < n_burnin + 10: + pool_r2s.append(np.nan) + continue + + burn_idx = valid_indices[:n_burnin] + eval_idx = valid_indices[n_burnin:] + + pred_idx = [j for j in range(n_pools) if j != i] + + def build_X(indices): + X_peers = resid_matrix[indices - 1][:, pred_idx].copy() + for c in range(X_peers.shape[1]): + col = X_peers[:, c] + m = np.nanmean(col) + col[np.isnan(col)] = m if np.isfinite(m) else 0.0 + X_peers[:, c] = col + own = y_all[indices - 1] + return np.column_stack([X_peers, own[:, None]]) + + X_burn = build_X(burn_idx) + y_burn = y_all[burn_idx] + + model = RidgeCV(alphas=np.logspace(-2, 4, 50)) + model.fit(X_burn, y_burn) + + X_eval = build_X(eval_idx) + y_eval = y_all[eval_idx] + y_pred = model.predict(X_eval) + + r2 = r2_score(y_eval, y_pred) + pool_r2s.append(r2) + print(f" {pid[:16]} ({matched_clean[pid]['tokens']:<14}) " + f"R²={r2:.3f} n_eval={len(eval_idx)} alpha={model.alpha_:.1f}") + + valid_r2s = [r for r in pool_r2s if not np.isnan(r)] + print(f"\n LOO ridge ({n_burnin}d burn-in): " + f"median R²={np.median(valid_r2s):.4f}, mean={np.mean(valid_r2s):.4f}") + return pool_r2s + + +# ---- Main ---- + + +def main(): + os.environ.setdefault("JAX_PLATFORMS", "cpu") + + print("=" * 70) + print("Cross-Pool Linear Prediction of Noise Residuals") + print(" (total volume - grid arb volume)") + print("=" * 70) + + matched_clean, option_c_clean = load_stage1() + vol_matrix, resid_matrix, date_list, pool_ids = build_noise_residual_matrix( + matched_clean, option_c_clean) + + ar1_r2s = run_ar1_noise_baseline(resid_matrix, pool_ids, matched_clean) + insample_r2s = run_ridge_insample(resid_matrix, pool_ids, matched_clean) + loo_r2s = run_ridge_loo(resid_matrix, pool_ids, matched_clean) + burnin_r2s = run_ridge_burnin(resid_matrix, pool_ids, matched_clean, n_burnin=30) + + def safe_median(xs): + v = [x for x in xs if x is not None and not np.isnan(x)] + return np.median(v) if v else float("nan") + + print("\n" + "=" * 70) + print("SUMMARY (noise residuals)") + print("=" * 70) + print(f" AR1 noise residual: median R² = {safe_median(ar1_r2s):.4f}") + print(f" Ridge in-sample (+own): median R² = {safe_median(insample_r2s):.4f}") + print(f" Ridge LOO (overlap xfer): median R² = {safe_median(loo_r2s):.4f}") + print(f" Ridge LOO (30d burn-in): median R² = {safe_median(burnin_r2s):.4f}") + print(f" ---") + print(f" (total vol) AR1: median R² = 0.397") + print(f" (total vol) Ridge +own: median R² = 0.599") + print(f" Option C in-sample: median R² = 0.589") + + +if __name__ == "__main__": + main() diff --git a/experiments/run_deconfounder_noise.py b/experiments/run_deconfounder_noise.py new file mode 100644 index 0000000..cd761b9 --- /dev/null +++ b/experiments/run_deconfounder_noise.py @@ -0,0 +1,555 @@ +"""Causal noise volume estimation: TVL decomposition + deconfounder sensitivity. + +Two identification strategies for the causal effect of TVL on noise volume: + +**Primary: TVL decomposition (IV-style)** + Decomposes Δlog(TVL) into: + - Price-driven: Δlog(TVL) - Δlog(shares) — market price moves, more + exogenous to pool-specific trading activity (conditional on BTC/token + market features already in the model) + - Flow-driven: Δlog(shares) — LP deposits/withdrawals, endogenous + (LPs deposit when they expect fees → correlated with noise) + If b_tvl estimated from price-driven variation ≈ observational b_tvl, + the coefficient is likely causal. + +**Secondary: Deconfounder sensitivity analysis (Wang & Blei 2019)** + Fit a factor model (PPCA) on covariates only (no outcome), extract + latent factors Z_hat, include them in the outcome model. + This is a sensitivity analysis: if b_tvl shifts substantially when + conditioning on Z_hat, there's evidence of unobserved confounding. + If it's stable, confounding through the covariate structure is small. + + NB: The deconfounder has known theoretical limitations (D'Amour 2019). + Wang & Blei (2020, arXiv:2003.04948) respond that D'Amour's + counterexamples violate the required assumptions (pinpointability). + The theory holds under its assumptions, but the key assumption (no + unobserved single-cause confounders) is domain-specific and + uncheckable. Results should be interpreted as sensitivity bounds. + +**Diagnostics:** + - Variance decomposition of log_tvl: between-pool vs within-pool + - Within-pool simple regression: Δlog(V_obs) on Δlog(TVL) per pool + - These test whether the observational b_tvl reflects cross-sectional + or temporal variation + +Usage: + python experiments/run_deconfounder_noise.py + python experiments/run_deconfounder_noise.py --n-factors 1 2 3 5 +""" + +import argparse +import os +import time + +import jax.numpy as jnp +import numpy as np + + +# ---- Factor model ---- + + +def fit_ppca(X, n_components): + """Probabilistic PCA. Returns Z_hat and the model.""" + from sklearn.decomposition import PCA + pca = PCA(n_components=n_components) + Z_hat = pca.fit_transform(X) + print(f" PPCA({n_components}): explained var = " + f"{pca.explained_variance_ratio_.sum():.3f} " + f"per-component: {np.round(pca.explained_variance_ratio_, 3)}") + return Z_hat, pca + + +def build_augmented_data(data, Z_hat): + """Augment covariate matrix with standardized substitute confounders.""" + x_orig = data["x"] + n_z = Z_hat.shape[1] + + z_mean = Z_hat.mean(axis=0) + z_std = Z_hat.std(axis=0) + z_std[z_std < 1e-6] = 1.0 + Z_std = ((Z_hat - z_mean) / z_std).astype(np.float32) + + x_aug = np.concatenate([x_orig, Z_std], axis=1) + data_aug = dict(data) + data_aug["x"] = x_aug + data_aug["n_feat"] = x_aug.shape[1] + data_aug["feat_names"] = data["feat_names"] + [f"Z_{k}" for k in range(n_z)] + data_aug["x_mean"] = np.concatenate([ + data["x_mean"], z_mean.astype(np.float32)]) + data_aug["x_std"] = np.concatenate([ + data["x_std"], z_std.astype(np.float32)]) + return data_aug + + +def _tvl_col_index(feat_names): + """Find TVL column index from feature names (robust to reordering).""" + return feat_names.index("xobs_1") + + +def _intercept_col_index(feat_names): + """Find intercept column index.""" + return feat_names.index("xobs_0") + + +# ---- TVL decomposition ---- + + +def decompose_tvl(matched_clean, pool_ids, sample_pools, sample_days, + date_to_idx, n_dates, n_pools): + """Decompose Δlog(TVL) into price-driven and flow-driven components. + + Uses log_tvl (not log_tvl_lag1) for the decomposition to avoid + mixing lags. total_shares is assumed to be contemporaneous with TVL. + + flow = Δlog(shares) — LP deposits/withdrawals + price = Δlog(tvl) - Δlog(shares) — price changes + + Returns per-sample arrays and a validity mask. + """ + log_shares = np.full((n_dates, n_pools), np.nan) + log_tvl = np.full((n_dates, n_pools), np.nan) + + for j, pid in enumerate(pool_ids): + panel = matched_clean[pid]["panel"] + dates = panel["date"].values + + has_shares = ("total_shares" in panel.columns and + panel["total_shares"].notna().any()) + if not has_shares: + continue + + shares = panel["total_shares"].values.astype(float) + shares = np.maximum(shares, 1e-10) + + # Use log_tvl (not lag) for contemporaneous decomposition + if "log_tvl" in panel.columns: + tvl_vals = panel["log_tvl"].values.astype(float) + else: + tvl_vals = panel["log_tvl_lag1"].values.astype(float) + + for k, date in enumerate(dates): + t = date_to_idx.get(date) + if t is not None: + log_shares[t, j] = np.log(shares[k]) + log_tvl[t, j] = tvl_vals[k] + + n_samples = len(sample_pools) + tvl_flow = np.full(n_samples, np.nan, dtype=np.float32) + tvl_price = np.full(n_samples, np.nan, dtype=np.float32) + + for s in range(n_samples): + i = sample_pools[s] + t = sample_days[s] + if t >= 1: + d_log_shares = log_shares[t, i] - log_shares[t - 1, i] + d_log_tvl = log_tvl[t, i] - log_tvl[t - 1, i] + if np.isfinite(d_log_shares) and np.isfinite(d_log_tvl): + tvl_flow[s] = d_log_shares + tvl_price[s] = d_log_tvl - d_log_shares + + valid = np.isfinite(tvl_flow) & np.isfinite(tvl_price) + return tvl_flow, tvl_price, valid + + +def run_tvl_decomposition_analysis(data, matched_clean, tvl_flow, tvl_price, valid): + """Primary identification: compare b_tvl from price-driven vs all TVL.""" + from sklearn.linear_model import RidgeCV + + y = data["y_total"] + x = data["x"] + tvl_idx = _tvl_col_index(data["feat_names"]) + + print(f"\n Valid samples (have LP shares data): {valid.sum()}/{len(valid)}") + if valid.sum() < 100: + print(" Insufficient LP shares data for TVL decomposition.") + return None + + x_valid = x[valid] + y_valid = y[valid] + tvl_price_valid = tvl_price[valid] + tvl_flow_valid = tvl_flow[valid] + + print(f" Price component: mean={tvl_price_valid.mean():.4f}," + f" std={tvl_price_valid.std():.4f}") + print(f" Flow component: mean={tvl_flow_valid.mean():.4f}," + f" std={tvl_flow_valid.std():.4f}") + + # Observational b_tvl (Ridge on all features) + ridge_obs = RidgeCV(alphas=np.logspace(-2, 4, 50)) + ridge_obs.fit(x_valid, y_valid) + b_tvl_obs = ridge_obs.coef_[tvl_idx] + + # Replace TVL column with price-driven component only + x_price = x_valid.copy() + ps = tvl_price_valid.std() + x_price[:, tvl_idx] = (tvl_price_valid - tvl_price_valid.mean()) / max(ps, 1e-6) + ridge_price = RidgeCV(alphas=np.logspace(-2, 4, 50)) + ridge_price.fit(x_price, y_valid) + b_tvl_price = ridge_price.coef_[tvl_idx] + + # Replace TVL column with flow-driven component only + x_flow = x_valid.copy() + fs = tvl_flow_valid.std() + x_flow[:, tvl_idx] = (tvl_flow_valid - tvl_flow_valid.mean()) / max(fs, 1e-6) + ridge_flow = RidgeCV(alphas=np.logspace(-2, 4, 50)) + ridge_flow.fit(x_flow, y_valid) + b_tvl_flow = ridge_flow.coef_[tvl_idx] + + print(f"\n b_tvl estimates (Ridge, all 22 features):") + print(f" All TVL variation: {b_tvl_obs:+.4f}") + print(f" Price-driven only: {b_tvl_price:+.4f}" + f" (more exogenous)") + print(f" Flow-driven only: {b_tvl_flow:+.4f}" + f" (endogenous)") + + if abs(b_tvl_price - b_tvl_obs) < 0.3 * abs(b_tvl_obs): + print(f"\n → Price-driven ≈ observational: confounding small.") + else: + print(f"\n → Price-driven ≠ observational: potential confounding.") + + return {"obs": b_tvl_obs, "price": b_tvl_price, "flow": b_tvl_flow} + + +# ---- Variance decomposition ---- + + +def run_variance_decomposition(matched_clean, pool_ids): + """Decompose log_tvl variance into between-pool and within-pool.""" + all_tvls = [] + pool_labels = [] + + for j, pid in enumerate(pool_ids): + panel = matched_clean[pid]["panel"] + tvls = panel["log_tvl_lag1"].values.astype(float) + valid = np.isfinite(tvls) + all_tvls.extend(tvls[valid]) + pool_labels.extend([j] * valid.sum()) + + all_tvls = np.array(all_tvls) + pool_labels = np.array(pool_labels) + + total_var = np.var(all_tvls) + pool_means = np.array([all_tvls[pool_labels == j].mean() + for j in range(len(pool_ids))]) + between_var = np.var(pool_means) + within_vars = [np.var(all_tvls[pool_labels == j]) + for j in range(len(pool_ids))] + within_var = np.mean(within_vars) + + print(f" Total variance: {total_var:.4f}") + print(f" Between-pool: {between_var:.4f} ({between_var/total_var*100:.1f}%)") + print(f" Within-pool (avg): {within_var:.4f} ({within_var/total_var*100:.1f}%)") + print(f" Pool mean range: {pool_means.min():.1f} to {pool_means.max():.1f}") + + return {"total": total_var, "between": between_var, "within": within_var} + + +# ---- Within-pool simple regression ---- + + +def run_within_pool_regressions(matched_clean, pool_ids): + """Per-pool: Δlog(V_obs) on Δlog(TVL), no other covariates.""" + print(f"\n {'Pool':16s} {'Tokens':16s} {'b_tvl':>8s} {'R²':>6s}" + f" {'n':>5s} {'ΔTVL_std':>8s}") + print(f" {'-'*65}") + + b_tvls = [] + for pid in pool_ids: + panel = matched_clean[pid]["panel"] + log_vol = panel["log_volume"].values.astype(float) + log_tvl = panel["log_tvl_lag1"].values.astype(float) + + d_vol = np.diff(log_vol) + d_tvl = np.diff(log_tvl) + + valid = np.isfinite(d_vol) & np.isfinite(d_tvl) + if valid.sum() < 10: + continue + + dv = d_vol[valid] + dt = d_tvl[valid] + + # OLS: Δlog_vol = a + b * Δlog_tvl + X = np.column_stack([np.ones(len(dt)), dt]) + sol, _, _, _ = np.linalg.lstsq(X, dv, rcond=None) + b = sol[1] + pred = X @ sol + ss_res = np.sum((dv - pred) ** 2) + ss_tot = np.sum((dv - dv.mean()) ** 2) + r2 = 1 - ss_res / max(ss_tot, 1e-10) + + b_tvls.append(b) + tokens = matched_clean[pid]["tokens"] + print(f" {pid[:16]:16s} {tokens[:16]:16s} {b:+8.3f} {r2:6.3f}" + f" {valid.sum():5d} {dt.std():8.4f}") + + if b_tvls: + print(f"\n Median within-pool b_tvl: {np.median(b_tvls):+.4f}") + print(f" Mean: {np.mean(b_tvls):+.4f}") + print(f" Std across pools: {np.std(b_tvls):.4f}") + return b_tvls + + +# ---- Lagged-average TVL analysis ---- + + +def run_lagged_average_analysis(matched_clean, pool_ids): + """Test TVL→noise at different timescales. + + If the daily Δ elasticity is ~0 but the level elasticity is ~2.5, + the effect may operate on longer timescales. Test by regressing + noise on rolling-average TVL at windows of 7, 14, 30, 60, 90 days. + If b_tvl grows with window size, the relationship is real but slow. + """ + windows = [1, 7, 14, 30, 60, 90] + + print(f"\n Window Median b_tvl Mean b_tvl Pools w/ data") + print(f" {'-'*55}") + + for w in windows: + b_tvls = [] + n_pools_used = 0 + for pid in pool_ids: + panel = matched_clean[pid]["panel"] + log_vol = panel["log_volume"].values.astype(float) + log_tvl = panel["log_tvl_lag1"].values.astype(float) + + if len(log_vol) < w + 10: + continue + + # Rolling mean TVL over window w + if w == 1: + tvl_avg = log_tvl + else: + # Simple trailing average + tvl_avg = np.full_like(log_tvl, np.nan) + for t in range(w, len(log_tvl)): + vals = log_tvl[t - w:t] + if np.all(np.isfinite(vals)): + tvl_avg[t] = np.mean(vals) + + # Within-pool: demean both series + valid = np.isfinite(log_vol) & np.isfinite(tvl_avg) + if valid.sum() < 15: + continue + + vol = log_vol[valid] + tvl = tvl_avg[valid] + vol_dm = vol - vol.mean() + tvl_dm = tvl - tvl.mean() + + # OLS: demeaned_vol = b * demeaned_tvl + if np.var(tvl_dm) < 1e-10: + continue + b = np.sum(vol_dm * tvl_dm) / np.sum(tvl_dm ** 2) + b_tvls.append(b) + n_pools_used += 1 + + if b_tvls: + print(f" {w:5d}d {np.median(b_tvls):+11.4f} {np.mean(b_tvls):+10.4f}" + f" {n_pools_used:>13d}") + + return windows + + +# ---- Deconfounder sensitivity ---- + + +def run_deconfounder(data, n_factors_list, args): + """Secondary: deconfounder sensitivity analysis across n_factors.""" + from experiments.run_linear_market_noise import make_loss_fn, train + from sklearn.linear_model import RidgeCV + + X = data["x"] + tvl_idx = _tvl_col_index(data["feat_names"]) + intercept_idx = _intercept_col_index(data["feat_names"]) + results = {} + + for n_f in n_factors_list: + print(f"\n --- n_factors={n_f} ---") + Z_hat, _ = fit_ppca(X, n_f) + data_aug = build_augmented_data(data, Z_hat) + + n_feat = data_aug["n_feat"] + n_pools = data_aug["n_pools"] + + # Ridge warm-start + ridge = RidgeCV(alphas=np.logspace(-2, 4, 50)) + ridge.fit(data_aug["x"], data_aug["y_total"]) + sol = ridge.coef_.copy() + sol[intercept_idx] += ridge.intercept_ + + params = { + "log_cadence": jnp.array(data_aug["init_log_cadences"]), + "noise_coeffs": jnp.array(sol.astype(np.float32)), + } + + grad_fn = make_loss_fn(data_aug["pool_coeffs"], data_aug["pool_gas"], n_pools) + params = train(params, data_aug, grad_fn, args.epochs, args.lr, + args.l2_alpha, args.huber_delta, verbose=False) + + nc = np.array(params["noise_coeffs"]) + b_tvl = nc[tvl_idx] + n_orig = data["n_feat"] + z_coeffs = nc[n_orig:n_orig + n_f] + + print(f" b_tvl = {b_tvl:+.4f} " + f" Z coeffs: {np.round(z_coeffs, 3)}") + + results[n_f] = { + "b_tvl": float(b_tvl), + "z_coeffs": z_coeffs.tolist(), + "explained_var": float(Z_hat.var(axis=0).sum() / X.var(axis=0).sum()), + } + + return results + + +def main(): + parser = argparse.ArgumentParser(description=__doc__, + formatter_class=argparse.RawDescriptionHelpFormatter) + parser.add_argument("--n-factors", type=int, nargs="+", default=[1, 2, 3, 5], + help="Number of latent factors to sweep") + parser.add_argument("--epochs", type=int, default=2000) + parser.add_argument("--lr", type=float, default=3e-4) + parser.add_argument("--l2-alpha", type=float, default=1e-3) + parser.add_argument("--huber-delta", type=float, default=1.0) + parser.add_argument("--trend-windows", type=int, nargs="+", default=[7]) + args = parser.parse_args() + + os.environ.setdefault("JAX_PLATFORMS", "cpu") + + print("=" * 70) + print("Causal Noise Volume Estimation") + print(" 1. Variance decomposition (between vs within pool)") + print(" 2. Within-pool simple regressions") + print(" 3. TVL decomposition (price vs flow)") + print(" 4. Deconfounder sensitivity (PPCA factors)") + print(f" n_factors sweep: {args.n_factors}") + print("=" * 70) + + from experiments.run_linear_market_noise import load_stage1, build_data + + matched_clean, option_c_clean = load_stage1() + + print("\nBuilding data...") + t0 = time.time() + data = build_data( + matched_clean, option_c_clean, + trend_windows=tuple(args.trend_windows), + include_market=True, include_cross_pool=True, + ) + pool_ids = data["pool_ids"] + n_pools = data["n_pools"] + print(f" {len(data['pool_idx'])} samples, {n_pools} pools," + f" {data['n_feat']} features, {time.time() - t0:.1f}s") + + # ---- 1. Variance decomposition ---- + print(f"\n{'='*70}") + print("1. Variance Decomposition of log_tvl_lag1") + print(f"{'='*70}") + var_results = run_variance_decomposition(matched_clean, pool_ids) + + # ---- 2. Within-pool simple regressions ---- + print(f"\n{'='*70}") + print("2. Within-pool: Δlog(V_obs) ~ Δlog(TVL) (no other covariates)") + print(f"{'='*70}") + within_b_tvls = run_within_pool_regressions(matched_clean, pool_ids) + + # ---- 2b. Lagged-average TVL (timescale test) ---- + print(f"\n{'='*70}") + print("2b. Lagged-Average TVL: Does b_tvl grow with averaging window?") + print(f" (Tests whether the TVL→noise effect is slow-moving)") + print(f"{'='*70}") + run_lagged_average_analysis(matched_clean, pool_ids) + + # ---- 3. TVL decomposition ---- + print(f"\n{'='*70}") + print("3. TVL Decomposition: Price-driven vs Flow-driven") + print(f"{'='*70}") + + all_dates = set() + for pid in pool_ids: + all_dates.update(matched_clean[pid]["panel"]["date"].values) + date_list = sorted(all_dates) + date_to_idx = {d: i for i, d in enumerate(date_list)} + + tvl_flow, tvl_price, valid = decompose_tvl( + matched_clean, pool_ids, data["pool_idx"], data["day_idx"], + date_to_idx, len(date_list), n_pools, + ) + tvl_results = run_tvl_decomposition_analysis( + data, matched_clean, tvl_flow, tvl_price, valid) + + # ---- 4. Deconfounder sensitivity ---- + print(f"\n{'='*70}") + print("4. Deconfounder Sensitivity Analysis") + print(f" (Wang & Blei 2019; D'Amour 2019; Wang & Blei 2020)") + print(f"{'='*70}") + deconf_results = run_deconfounder(data, args.n_factors, args) + + # ---- Summary ---- + print(f"\n{'='*70}") + print("SUMMARY: b_tvl across identification strategies") + print(f"{'='*70}") + + # Observational baseline from artifact + obs_path = os.path.join( + os.path.dirname(os.path.dirname(__file__)), + "results", "linear_market_noise", "model.npz", + ) + if os.path.exists(obs_path): + obs_nc = np.load(obs_path)["noise_coeffs"] + tvl_idx = _tvl_col_index(data["feat_names"]) + if obs_nc.ndim == 2: + b_obs = float(np.median(obs_nc[:, tvl_idx])) + print(f" Observational (per-pool median): {b_obs:+.4f}") + else: + b_obs = float(obs_nc[tvl_idx]) + print(f" Observational (shared): {b_obs:+.4f}") + + between_pct = var_results["between"] / var_results["total"] * 100 + print(f"\n Variance decomposition: {between_pct:.0f}% between-pool," + f" {100-between_pct:.0f}% within-pool") + + if within_b_tvls: + print(f" Within-pool Δ regressions: " + f"median={np.median(within_b_tvls):+.4f}" + f" (mean={np.mean(within_b_tvls):+.4f})") + + if tvl_results: + print(f"\n TVL decomposition (Ridge, 22 features):") + print(f" All variation: {tvl_results['obs']:+.4f}") + print(f" Price-driven (exogenous): {tvl_results['price']:+.4f}") + print(f" Flow-driven (endogenous): {tvl_results['flow']:+.4f}") + + print(f"\n Deconfounder sensitivity (shared, learnable cadence):") + print(f" {'n_factors':>10s} {'b_tvl':>8s}") + for n_f, r in sorted(deconf_results.items()): + print(f" {n_f:>10d} {r['b_tvl']:+8.4f}") + + # Stability + b_tvls_d = [r["b_tvl"] for r in deconf_results.values()] + rng = max(b_tvls_d) - min(b_tvls_d) + mn = np.mean(b_tvls_d) + stable = rng < 0.3 * abs(mn) + print(f"\n Deconfounder: {'STABLE' if stable else 'VARIES'}" + f" (range {rng:.3f}, mean {mn:+.3f})") + + print(f"\n Interpretation:") + if tvl_results and abs(tvl_results['price']) < 0.5: + print(f" Daily b_tvl (Δ regression, price-driven) is near zero.") + print(f" This does NOT mean the long-run effect is zero:") + print(f" - Noise may respond slowly to TVL (routing updates,") + print(f" aggregator discovery, ecosystem integration)") + print(f" - The lagged-average analysis above tests this") + print(f" - The per-pool b_tvl of ~1.0 captures medium-frequency") + print(f" within-pool variation and is the best working estimate") + print(f" - Changing reClAMM concentration is a structural change") + print(f" (like being a different pool), not a daily TVL shock") + print(f" → Use per-pool b_tvl (~1.0) for counterfactuals, with") + print(f" sensitivity analysis across [0.5, 1.0, 2.0]") + + +if __name__ == "__main__": + main() diff --git a/experiments/run_deepsets_noise.py b/experiments/run_deepsets_noise.py new file mode 100644 index 0000000..a639256 --- /dev/null +++ b/experiments/run_deepsets_noise.py @@ -0,0 +1,595 @@ +"""DeepSets noise volume prediction with V_arb decomposition. + +Predicts V_noise via a shared encoder-decoder over peer pools. +V_arb is precomputed from grids at Option C cadence/gas. +Loss: mean((log(V_arb + V_noise_predicted) - log_volume)^2) + +Usage: + python experiments/run_deepsets_noise.py # default hparams + python experiments/run_deepsets_noise.py --tune 50 # Optuna, 50 trials +""" + +import argparse +import os +import pickle +import sys +import time + +import jax +import jax.numpy as jnp +import numpy as np +import pandas as pd + +CACHE_DIR = os.path.join( + os.path.dirname(os.path.dirname(__file__)), + "results", "token_factored_calibration", "_cache", +) + +# Default hyperparameters +DEFAULTS = dict( + hidden=16, + d_embed=8, + lr=3e-4, + l2_alpha=1e-3, + n_epochs=1000, + include_own_lag=True, +) + + +def load_stage1(): + path = os.path.join(CACHE_DIR, "stage1.pkl") + if not os.path.exists(path): + print("ERROR: no stage1 cache.") + sys.exit(1) + with open(path, "rb") as f: + data = pickle.load(f) + return data["matched_clean"], data["option_c_clean"] + + +# ---- Data construction ---- + + +def build_data(matched_clean, option_c_clean, exclude_pool_idx=None): + """Build training arrays with V_arb decomposition. + + For each (pool i, day t) sample: + - peer_vols: other pools' log_volume at t-1 + - v_arb: precomputed arb volume for pool i at day t + - local_features: [log_tvl_lag1, dow_sin, dow_cos] + - own_lag: pool i's log_volume at t-1 + - y: log_volume at day t + """ + from quantammsim.calibration.grid_interpolation import interpolate_pool_daily + from quantammsim.calibration.pool_data import ( + build_pool_attributes, _parse_tokens, _canonicalize_token, + ) + + pool_ids = sorted(matched_clean.keys()) + n_pools = len(pool_ids) + + # ---- Collect all dates, build volume + V_arb matrices ---- + all_dates = set() + for pid in pool_ids: + all_dates.update(matched_clean[pid]["panel"]["date"].values) + date_list = sorted(all_dates) + n_dates = len(date_list) + date_to_idx = {d: i for i, d in enumerate(date_list)} + + vol_matrix = np.full((n_dates, n_pools), np.nan) + v_arb_matrix = np.full((n_dates, n_pools), np.nan) + tvl_matrix = np.full((n_dates, n_pools), np.nan) + weekday_matrix = np.full(n_dates, np.nan) + + for j, pid in enumerate(pool_ids): + entry = matched_clean[pid] + oc = option_c_clean[pid] + panel = entry["panel"] + + # V_arb from grid + v_arb_all = np.array(interpolate_pool_daily( + entry["coeffs"], + jnp.float64(oc["log_cadence"]), + jnp.float64(np.exp(oc["log_gas"])), + )) + v_arb_day = v_arb_all[entry["day_indices"]] + + dates = panel["date"].values + log_vols = panel["log_volume"].values.astype(float) + tvl_vals = panel["log_tvl_lag1"].values.astype(float) + + for k, date in enumerate(dates): + t = date_to_idx[date] + vol_matrix[t, j] = log_vols[k] + v_arb_matrix[t, j] = v_arb_day[k] + tvl_matrix[t, j] = tvl_vals[k] + + # Weekdays + for t, date in enumerate(date_list): + dt = pd.Timestamp(date) + weekday_matrix[t] = dt.weekday() + + # ---- Pool attributes ---- + X_attr, attr_names, _ = build_pool_attributes(matched_clean) + attr_mean = np.mean(X_attr, axis=0) + attr_std = np.std(X_attr, axis=0) + attr_std[attr_std < 1e-6] = 1.0 + X_attr_norm = ((X_attr - attr_mean) / attr_std).astype(np.float32) + k_attr = X_attr_norm.shape[1] + + # ---- Token overlap ---- + pool_tokens = {} + for i, pid in enumerate(pool_ids): + toks = _parse_tokens(matched_clean[pid]["tokens"]) + pool_tokens[i] = {_canonicalize_token(t) for t in toks[:2]} + + n_peers = n_pools - 1 + peer_attrs = np.zeros((n_pools, n_peers, k_attr), dtype=np.float32) + peer_overlap = np.zeros((n_pools, n_peers), dtype=np.float32) + peer_col_idx = np.zeros((n_pools, n_peers), dtype=np.int32) + + for i in range(n_pools): + peers = [j for j in range(n_pools) if j != i] + for p, j in enumerate(peers): + peer_attrs[i, p] = X_attr_norm[j] + peer_overlap[i, p] = len(pool_tokens[i] & pool_tokens[j]) + peer_col_idx[i, p] = j + + target_attrs = X_attr_norm + + # ---- Standardize volumes for encoder input ---- + vol_mean = float(np.nanmean(vol_matrix)) + vol_std = float(np.nanstd(vol_matrix)) + + # ---- Build samples ---- + sample_pools, sample_days = [], [] + for i in range(n_pools): + if i == exclude_pool_idx: + continue + for t in range(1, n_dates): + if (np.isnan(vol_matrix[t, i]) or np.isnan(vol_matrix[t - 1, i]) + or np.isnan(v_arb_matrix[t, i]) or np.isnan(tvl_matrix[t, i])): + continue + sample_pools.append(i) + sample_days.append(t) + + sample_pools = np.array(sample_pools, dtype=np.int32) + sample_days = np.array(sample_days, dtype=np.int32) + n_samples = len(sample_pools) + + peer_vols_arr = np.zeros((n_samples, n_peers), dtype=np.float32) + peer_mask_arr = np.zeros((n_samples, n_peers), dtype=np.float32) + own_lag_arr = np.zeros(n_samples, dtype=np.float32) + v_arb_arr = np.zeros(n_samples, dtype=np.float32) + local_arr = np.zeros((n_samples, 3), dtype=np.float32) # tvl, dow_sin, dow_cos + y_arr = np.zeros(n_samples, dtype=np.float32) + + for s in range(n_samples): + i = sample_pools[s] + t = sample_days[s] + cols = peer_col_idx[i] + + pvols_raw = vol_matrix[t - 1, cols] + valid = ~np.isnan(pvols_raw) + pvols_norm = (pvols_raw - vol_mean) / vol_std + peer_vols_arr[s] = np.where(valid, pvols_norm, 0.0) + peer_mask_arr[s] = valid.astype(np.float32) + + own_lag_arr[s] = (vol_matrix[t - 1, i] - vol_mean) / vol_std + v_arb_arr[s] = v_arb_matrix[t, i] + y_arr[s] = vol_matrix[t, i] # raw log_volume (not standardized) + + wd = weekday_matrix[t] + local_arr[s, 0] = tvl_matrix[t, i] + local_arr[s, 1] = np.sin(2 * np.pi * wd / 7) + local_arr[s, 2] = np.cos(2 * np.pi * wd / 7) + + # Standardize local features + local_mean = np.mean(local_arr, axis=0) + local_std = np.std(local_arr, axis=0) + local_std[local_std < 1e-6] = 1.0 + local_arr = ((local_arr - local_mean) / local_std).astype(np.float32) + + return { + "peer_attrs": jnp.array(peer_attrs), + "target_attrs": jnp.array(target_attrs), + "peer_overlap": jnp.array(peer_overlap), + "peer_vols": jnp.array(peer_vols_arr), + "peer_mask": jnp.array(peer_mask_arr), + "own_lag": jnp.array(own_lag_arr), + "v_arb": jnp.array(v_arb_arr), + "local": jnp.array(local_arr), + "y": jnp.array(y_arr), + "pool_idx": jnp.array(sample_pools), + "day_idx": sample_days, + "n_pools": n_pools, + "n_peers": n_peers, + "k_attr": k_attr, + "k_local": 3, + "pool_ids": pool_ids, + } + + +# ---- Model ---- + + +def init_params(key, k_attr, k_local, hidden, d_embed, include_own_lag): + k1, k2, k3, k4 = jax.random.split(key, 4) + enc_in = 2 * k_attr + 2 # peer_attr + target_attr + peer_vol + overlap + dec_in = d_embed + k_attr + k_local + (1 if include_own_lag else 0) + + return { + "enc_W1": jax.random.normal(k1, (enc_in, hidden)) * np.sqrt(2.0 / enc_in), + "enc_b1": jnp.zeros(hidden), + "enc_W2": jax.random.normal(k2, (hidden, d_embed)) * np.sqrt(2.0 / hidden), + "enc_b2": jnp.zeros(d_embed), + "dec_W1": jax.random.normal(k3, (dec_in, hidden)) * np.sqrt(2.0 / dec_in), + "dec_b1": jnp.zeros(hidden), + "dec_W2": jax.random.normal(k4, (hidden, 1)) * 0.01, + "dec_b2": jnp.zeros(1), + } + + +def forward(params, peer_attrs_all, target_attrs_all, peer_overlap_all, + peer_vols, peer_mask, own_lag, local_feat, pool_idx, + include_own_lag=True): + """Returns log_v_noise per sample.""" + batch = peer_vols.shape[0] + + pa = peer_attrs_all[pool_idx] + ta = target_attrs_all[pool_idx] + ov = peer_overlap_all[pool_idx] + ta_broad = jnp.broadcast_to(ta[:, None, :], pa.shape) + + enc_in = jnp.concatenate([ + pa, ta_broad, + peer_vols[:, :, None], + ov[:, :, None], + ], axis=-1) + + flat = enc_in.reshape(-1, enc_in.shape[-1]) + h = jnp.maximum(flat @ params["enc_W1"] + params["enc_b1"], 0.0) + h = h @ params["enc_W2"] + params["enc_b2"] + h = h.reshape(batch, peer_vols.shape[1], -1) + + h_masked = h * peer_mask[:, :, None] + n_valid = jnp.maximum(jnp.sum(peer_mask, axis=1, keepdims=True), 1.0) + summary = jnp.sum(h_masked, axis=1) / n_valid + + dec_parts = [summary, ta, local_feat] + if include_own_lag: + dec_parts.append(own_lag[:, None]) + dec_in = jnp.concatenate(dec_parts, axis=-1) + + h_dec = jnp.maximum(dec_in @ params["dec_W1"] + params["dec_b1"], 0.0) + log_v_noise = (h_dec @ params["dec_W2"] + params["dec_b2"])[:, 0] + return log_v_noise + + +def loss_fn(params, static, peer_vols, peer_mask, own_lag, local_feat, + pool_idx, v_arb, y, l2_alpha, include_own_lag): + """Log-space V_arb + V_noise loss matching the calibration pipeline.""" + log_v_noise = forward( + params, static["peer_attrs"], static["target_attrs"], + static["peer_overlap"], peer_vols, peer_mask, own_lag, local_feat, + pool_idx, include_own_lag, + ) + v_noise = jnp.exp(log_v_noise) + log_v_pred = jnp.log(jnp.maximum(v_arb + v_noise, 1e-6)) + mse = jnp.mean((log_v_pred - y) ** 2) + reg = sum(jnp.sum(v ** 2) for k, v in params.items() if "W" in k) + return mse + alpha * reg if (alpha := l2_alpha) else mse + l2_alpha * reg + + +@jax.jit +def _loss_and_grad(params, static, peer_vols, peer_mask, own_lag, local_feat, + pool_idx, v_arb, y, l2_alpha, include_own_lag): + return jax.value_and_grad(loss_fn)( + params, static, peer_vols, peer_mask, own_lag, local_feat, + pool_idx, v_arb, y, l2_alpha, include_own_lag, + ) + + +# ---- Training ---- + + +def train(params, data, hparams, verbose=True): + """Full-batch Adam.""" + static = {k: data[k] for k in ["peer_attrs", "target_attrs", "peer_overlap"]} + include_own_lag = hparams["include_own_lag"] + lr = hparams["lr"] + l2_alpha = hparams["l2_alpha"] + n_epochs = hparams["n_epochs"] + + m = {k: jnp.zeros_like(v) for k, v in params.items()} + v = {k: jnp.zeros_like(v) for k, v in params.items()} + + for epoch in range(n_epochs): + loss_val, grads = _loss_and_grad( + params, static, data["peer_vols"], data["peer_mask"], + data["own_lag"], data["local"], data["pool_idx"], + data["v_arb"], data["y"], l2_alpha, include_own_lag, + ) + + for k in params: + m[k] = 0.9 * m[k] + 0.1 * grads[k] + v[k] = 0.999 * v[k] + 0.001 * grads[k] ** 2 + m_hat = m[k] / (1.0 - 0.9 ** (epoch + 1)) + v_hat = v[k] / (1.0 - 0.999 ** (epoch + 1)) + params[k] = params[k] - lr * m_hat / (jnp.sqrt(v_hat) + 1e-8) + + if verbose and (epoch % 200 == 0 or epoch == n_epochs - 1): + print(f" epoch {epoch:4d} loss={float(loss_val):.6f}") + + return params, float(loss_val) + + +# ---- Evaluation ---- + + +def per_pool_r2(params, data, hparams): + """Per-pool R² on the log(V_arb + V_noise) prediction.""" + static = {k: data[k] for k in ["peer_attrs", "target_attrs", "peer_overlap"]} + log_v_noise = np.array(forward( + params, static["peer_attrs"], static["target_attrs"], + static["peer_overlap"], data["peer_vols"], data["peer_mask"], + data["own_lag"], data["local"], data["pool_idx"], + hparams["include_own_lag"], + )) + v_noise = np.exp(log_v_noise) + v_arb = np.array(data["v_arb"]) + log_v_pred = np.log(np.maximum(v_arb + v_noise, 1e-6)) + y = np.array(data["y"]) + pool_idx = np.array(data["pool_idx"]) + + r2s = {} + for i in range(data["n_pools"]): + mask = pool_idx == i + if mask.sum() < 2: + continue + yi = y[mask] + pi = log_v_pred[mask] + ss_res = np.sum((yi - pi) ** 2) + ss_tot = np.sum((yi - yi.mean()) ** 2) + r2s[i] = 1 - ss_res / max(ss_tot, 1e-10) + return r2s + + +def subset_data(data, mask): + """Subset data arrays by boolean mask.""" + jmask = jnp.array(mask) + static_keys = ["peer_attrs", "target_attrs", "peer_overlap", + "n_pools", "n_peers", "k_attr", "k_local", "pool_ids"] + out = {k: data[k] for k in static_keys} + for k in ["peer_vols", "peer_mask", "own_lag", "v_arb", "local", "y", "pool_idx"]: + out[k] = data[k][jmask] + out["day_idx"] = np.array(data["day_idx"])[mask] + return out + + +# ---- Experiments ---- + + +def run_single(matched_clean, option_c_clean, hparams, split_frac=0.7): + """Train with temporal split, report in-sample and eval R².""" + data = build_data(matched_clean, option_c_clean) + n_params = sum(v.size for v in init_params( + jax.random.PRNGKey(0), data["k_attr"], data["k_local"], + hparams["hidden"], hparams["d_embed"], hparams["include_own_lag"], + ).values()) + + print(f" {data['peer_vols'].shape[0]} samples, {data['n_pools']} pools, " + f"{n_params} params") + + day_idx = np.array(data["day_idx"]) + split_day = int(day_idx.max() * split_frac) + train_mask = day_idx <= split_day + eval_mask = day_idx > split_day + train_data = subset_data(data, train_mask) + eval_data = subset_data(data, eval_mask) + + print(f" Train: {int(train_mask.sum())} samples, " + f"Eval: {int(eval_mask.sum())} samples") + + params = init_params( + jax.random.PRNGKey(42), data["k_attr"], data["k_local"], + hparams["hidden"], hparams["d_embed"], hparams["include_own_lag"], + ) + t0 = time.time() + params, final_loss = train(params, train_data, hparams) + print(f" Training: {time.time() - t0:.1f}s, final loss={final_loss:.6f}") + + r2_train = per_pool_r2(params, train_data, hparams) + r2_eval = per_pool_r2(params, eval_data, hparams) + + pool_ids = data["pool_ids"] + for i, pid in enumerate(pool_ids): + r_tr = r2_train.get(i, float("nan")) + r_ev = r2_eval.get(i, float("nan")) + print(f" {pid[:16]} ({matched_clean[pid]['tokens']:<14}) " + f"train={r_tr:.3f} eval={r_ev:.3f}") + + vals_train = [v for v in r2_train.values() if np.isfinite(v)] + vals_eval = [v for v in r2_eval.values() if np.isfinite(v)] + med_train = np.median(vals_train) if vals_train else float("nan") + med_eval = np.median(vals_eval) if vals_eval else float("nan") + + print(f"\n Train: median R²={med_train:.4f}") + print(f" Eval: median R²={med_eval:.4f}") + print(f" (Option C in-sample: 0.589)") + + return med_eval, params, data + + +def run_loo(matched_clean, option_c_clean, hparams): + """Full LOO.""" + pool_ids = sorted(matched_clean.keys()) + n_pools = len(pool_ids) + loo_r2s = [] + + print(f"\n{'='*70}") + print("LOO DeepSets Noise") + print(f"{'='*70}") + + # Use fewer epochs for LOO + loo_hparams = dict(hparams, n_epochs=min(hparams["n_epochs"], 500)) + + for hold_out_idx in range(n_pools): + hold_out_pid = pool_ids[hold_out_idx] + train_data = build_data(matched_clean, option_c_clean, + exclude_pool_idx=hold_out_idx) + + params = init_params( + jax.random.PRNGKey(42), train_data["k_attr"], train_data["k_local"], + loo_hparams["hidden"], loo_hparams["d_embed"], + loo_hparams["include_own_lag"], + ) + params, _ = train(params, train_data, loo_hparams, verbose=False) + + # Eval on held-out pool + full_data = build_data(matched_clean, option_c_clean) + ho_mask = np.array(full_data["pool_idx"]) == hold_out_idx + if ho_mask.sum() < 2: + loo_r2s.append(float("nan")) + continue + + eval_data = subset_data(full_data, ho_mask) + r2s = per_pool_r2(params, eval_data, loo_hparams) + r2 = r2s.get(hold_out_idx, float("nan")) + loo_r2s.append(r2) + + tag = "OK" if r2 > 0 else "NEG" + print(f" {hold_out_pid[:16]} ({matched_clean[hold_out_pid]['tokens']:<14}) " + f"R²={r2:.3f} [{tag}]") + + valid = [r for r in loo_r2s if np.isfinite(r)] + med = np.median(valid) if valid else float("nan") + print(f"\n LOO: median R²={med:.4f}, " + f"mean={np.mean(valid):.4f}, " + f"n_neg={sum(1 for r in valid if r < 0)}") + return loo_r2s + + +# ---- Optuna ---- + + +def run_optuna(matched_clean, option_c_clean, n_trials): + """Hyperparameter optimization with Optuna.""" + import optuna + + # Precompute data once (shared across trials) + data = build_data(matched_clean, option_c_clean) + day_idx = np.array(data["day_idx"]) + split_day = int(day_idx.max() * 0.7) + train_mask = day_idx <= split_day + eval_mask = day_idx > split_day + train_data = subset_data(data, train_mask) + eval_data = subset_data(data, eval_mask) + + def objective(trial): + hp = { + "hidden": trial.suggest_categorical("hidden", [8, 16, 32]), + "d_embed": trial.suggest_categorical("d_embed", [4, 8, 16]), + "lr": trial.suggest_float("lr", 1e-4, 1e-2, log=True), + "l2_alpha": trial.suggest_float("l2_alpha", 1e-5, 1e-1, log=True), + "n_epochs": trial.suggest_categorical("n_epochs", [500, 1000, 2000]), + "include_own_lag": trial.suggest_categorical("include_own_lag", [True, False]), + } + + params = init_params( + jax.random.PRNGKey(42), data["k_attr"], data["k_local"], + hp["hidden"], hp["d_embed"], hp["include_own_lag"], + ) + params, final_loss = train(params, train_data, hp, verbose=False) + + r2s = per_pool_r2(params, eval_data, hp) + vals = [v for v in r2s.values() if np.isfinite(v)] + med_r2 = float(np.median(vals)) if vals else -10.0 + + # Report train R² too for diagnostics + r2s_tr = per_pool_r2(params, train_data, hp) + vals_tr = [v for v in r2s_tr.values() if np.isfinite(v)] + med_tr = float(np.median(vals_tr)) if vals_tr else -10.0 + + trial.set_user_attr("train_median_r2", med_tr) + trial.set_user_attr("final_loss", final_loss) + + print(f" Trial {trial.number}: eval={med_r2:.4f} train={med_tr:.4f} " + f"h={hp['hidden']} d={hp['d_embed']} lr={hp['lr']:.1e} " + f"alpha={hp['l2_alpha']:.1e} epochs={hp['n_epochs']} " + f"own_lag={hp['include_own_lag']}") + + return med_r2 + + study = optuna.create_study(direction="maximize") + study.optimize(objective, n_trials=n_trials) + + print(f"\n{'='*70}") + print("Optuna Results") + print(f"{'='*70}") + print(f" Best trial: {study.best_trial.number}") + print(f" Best eval median R²: {study.best_value:.4f}") + print(f" Best params: {study.best_params}") + print(f" Train median R²: {study.best_trial.user_attrs['train_median_r2']:.4f}") + + # Show top 5 + print(f"\n Top 5 trials:") + trials = sorted(study.trials, key=lambda t: t.value if t.value else -999, + reverse=True) + for t in trials[:5]: + if t.value is not None: + print(f" #{t.number}: eval={t.value:.4f} " + f"train={t.user_attrs.get('train_median_r2', '?'):.4f} " + f"{t.params}") + + return study + + +# ---- Main ---- + + +def main(): + parser = argparse.ArgumentParser() + parser.add_argument("--tune", type=int, default=0, + help="Run Optuna with N trials") + parser.add_argument("--loo", action="store_true", + help="Run LOO evaluation") + parser.add_argument("--hidden", type=int, default=DEFAULTS["hidden"]) + parser.add_argument("--d-embed", type=int, default=DEFAULTS["d_embed"]) + parser.add_argument("--lr", type=float, default=DEFAULTS["lr"]) + parser.add_argument("--l2-alpha", type=float, default=DEFAULTS["l2_alpha"]) + parser.add_argument("--epochs", type=int, default=DEFAULTS["n_epochs"]) + parser.add_argument("--no-own-lag", action="store_true") + args = parser.parse_args() + + os.environ.setdefault("JAX_PLATFORMS", "cpu") + + hparams = { + "hidden": args.hidden, + "d_embed": args.d_embed, + "lr": args.lr, + "l2_alpha": args.l2_alpha, + "n_epochs": args.epochs, + "include_own_lag": not args.no_own_lag, + } + + print("=" * 70) + print("DeepSets Noise Volume Prediction (V_arb decomposition)") + print(f" {hparams}") + print("=" * 70) + + matched_clean, option_c_clean = load_stage1() + + if args.tune > 0: + run_optuna(matched_clean, option_c_clean, args.tune) + else: + print(f"\n{'='*70}") + print("Temporal split (70/30)") + print(f"{'='*70}") + med_eval, params, data = run_single(matched_clean, option_c_clean, hparams) + + if args.loo: + run_loo(matched_clean, option_c_clean, hparams) + + +if __name__ == "__main__": + main() diff --git a/experiments/run_deepsets_v2.py b/experiments/run_deepsets_v2.py new file mode 100644 index 0000000..bc3661b --- /dev/null +++ b/experiments/run_deepsets_v2.py @@ -0,0 +1,1656 @@ +"""DeepSets v2: full feature menu with Optuna feature selection. + +Trains on total log_volume, evaluates on both total volume and noise +residual (log_vol - log_V_arb). V_arb precomputed from Option C fits. + +Feature menu: + Peer (encoder) — always: peer_attr, target_attr, vol_lag1, overlap + optional: vol_lag2, vol_change, tvl, volatility + relational: same_chain, log_tvl_ratio, log_fee_ratio + Local (decoder) — always: target_attr, own_vol_lag1, dow_sin, dow_cos + optional: own_vol_lag2, own_vol_change, own_tvl, own_volatility + +Model variants: + encoder_type: "mlp" (2-layer ReLU) or "linear" (single affine) + no_peers: decoder-only ablation (zero peer summary) + huber_delta: Huber loss transition point (default 1.0) + Per-pool loss weighting (equal weight per pool regardless of sample count) + +Usage: + python experiments/run_deepsets_v2.py # defaults + python experiments/run_deepsets_v2.py --tune 50 # Optuna + python experiments/run_deepsets_v2.py --no-peers # decoder-only + python experiments/run_deepsets_v2.py --encoder-type linear + python experiments/run_deepsets_v2.py --loo # LOO eval +""" + +import argparse +import os +import pickle +import sys +import time + +import jax +import jax.numpy as jnp +import numpy as np +import pandas as pd + +CACHE_DIR = os.path.join( + os.path.dirname(os.path.dirname(__file__)), + "results", "token_factored_calibration", "_cache", +) + + +def load_stage1(): + path = os.path.join(CACHE_DIR, "stage1.pkl") + if not os.path.exists(path): + print("ERROR: no stage1 cache.") + sys.exit(1) + with open(path, "rb") as f: + data = pickle.load(f) + return data["matched_clean"], data["option_c_clean"] + + +# ---- Data construction ---- + + +def build_all_features(matched_clean, option_c_clean): + """Build all possible feature matrices. Called once.""" + from quantammsim.calibration.grid_interpolation import interpolate_pool_daily + from quantammsim.calibration.pool_data import ( + build_pool_attributes, _parse_tokens, _canonicalize_token, + build_x_obs, build_cross_pool_x_obs, + ) + + pool_ids = sorted(matched_clean.keys()) + n_pools = len(pool_ids) + + # Collect dates + all_dates = set() + for pid in pool_ids: + all_dates.update(matched_clean[pid]["panel"]["date"].values) + date_list = sorted(all_dates) + n_dates = len(date_list) + date_to_idx = {d: i for i, d in enumerate(date_list)} + + # Daily matrices: (n_dates, n_pools) + vol_matrix = np.full((n_dates, n_pools), np.nan) + tvl_matrix = np.full((n_dates, n_pools), np.nan) + volatility_matrix = np.full((n_dates, n_pools), np.nan) + v_arb_matrix = np.full((n_dates, n_pools), np.nan) + weekday_arr = np.zeros(n_dates) + + for j, pid in enumerate(pool_ids): + entry = matched_clean[pid] + oc = option_c_clean[pid] + panel = entry["panel"] + + v_arb_all = np.array(interpolate_pool_daily( + entry["coeffs"], + jnp.float64(oc["log_cadence"]), + jnp.float64(np.exp(oc["log_gas"])), + )) + v_arb = v_arb_all[entry["day_indices"]] + + dates = panel["date"].values + for k, date in enumerate(dates): + t = date_to_idx[date] + vol_matrix[t, j] = panel["log_volume"].values[k] + tvl_matrix[t, j] = panel["log_tvl_lag1"].values[k] + volatility_matrix[t, j] = panel["volatility"].values[k] + v_arb_matrix[t, j] = v_arb[k] + + # Per-pool coeffs, gas, and day mapping for learnable cadence + pool_coeffs = [] + pool_gas = [] + init_log_cadences = np.zeros(n_pools, dtype=np.float32) + common_to_grid = np.full((n_pools, n_dates), 0, dtype=np.int32) + + for j, pid in enumerate(pool_ids): + entry = matched_clean[pid] + oc = option_c_clean[pid] + pool_coeffs.append(entry["coeffs"]) + pool_gas.append(jnp.float64(np.exp(oc["log_gas"]))) + init_log_cadences[j] = oc["log_cadence"] + dates_j = entry["panel"]["date"].values + for k, date in enumerate(dates_j): + common_to_grid[j, date_to_idx[date]] = entry["day_indices"][k] + + for t, date in enumerate(date_list): + weekday_arr[t] = pd.Timestamp(date).weekday() + + # Pool attributes (static) + X_attr, attr_names, _ = build_pool_attributes(matched_clean) + attr_mean = np.mean(X_attr, axis=0) + attr_std = np.std(X_attr, axis=0) + attr_std[attr_std < 1e-6] = 1.0 + X_attr_norm = ((X_attr - attr_mean) / attr_std).astype(np.float32) + k_attr = X_attr_norm.shape[1] + + # Raw per-pool values for relational features + fee_idx = attr_names.index("log_fee") + tvl_idx = attr_names.index("mean_log_tvl") + raw_log_fee = X_attr[:, fee_idx] + raw_mean_log_tvl = X_attr[:, tvl_idx] + pool_chains = [matched_clean[pid]["chain"] for pid in pool_ids] + + # Token overlap + pool_tokens = {} + for i, pid in enumerate(pool_ids): + toks = _parse_tokens(matched_clean[pid]["tokens"]) + pool_tokens[i] = {_canonicalize_token(t) for t in toks[:2]} + + n_peers = n_pools - 1 + peer_attrs = np.zeros((n_pools, n_peers, k_attr), dtype=np.float32) + peer_overlap = np.zeros((n_pools, n_peers), dtype=np.float32) + peer_col_idx = np.zeros((n_pools, n_peers), dtype=np.int32) + rel_same_chain = np.zeros((n_pools, n_peers), dtype=np.float32) + rel_log_tvl_ratio = np.zeros((n_pools, n_peers), dtype=np.float32) + rel_log_fee_ratio = np.zeros((n_pools, n_peers), dtype=np.float32) + + for i in range(n_pools): + peers = [j for j in range(n_pools) if j != i] + for p, j in enumerate(peers): + peer_attrs[i, p] = X_attr_norm[j] + peer_overlap[i, p] = len(pool_tokens[i] & pool_tokens[j]) + peer_col_idx[i, p] = j + rel_same_chain[i, p] = float(pool_chains[i] == pool_chains[j]) + rel_log_tvl_ratio[i, p] = abs(raw_mean_log_tvl[i] - raw_mean_log_tvl[j]) + rel_log_fee_ratio[i, p] = abs(raw_log_fee[i] - raw_log_fee[j]) + + # Standardize ratio features (new arrays, no in-place mutation) + def _standardize(arr): + mu = np.mean(arr) + sigma = max(np.std(arr), 1e-6) + return ((arr - mu) / sigma).astype(np.float32) + + rel_log_tvl_ratio = _standardize(rel_log_tvl_ratio) + rel_log_fee_ratio = _standardize(rel_log_fee_ratio) + + # Cross-pool peer maps: which pools share tokens / chain + from collections import defaultdict + pool_tokens_ordered = {} + token_to_pools = defaultdict(set) + for i, pid in enumerate(pool_ids): + toks = _parse_tokens(matched_clean[pid]["tokens"]) + ordered = [_canonicalize_token(t) for t in toks[:2]] + pool_tokens_ordered[i] = ordered + for tok in ordered: + token_to_pools[tok].add(i) + + token_a_peers = {} + token_b_peers = {} + chain_peer_map = {} + for i in range(n_pools): + toks = pool_tokens_ordered[i] + token_a_peers[i] = sorted(token_to_pools[toks[0]] - {i}) + token_b_peers[i] = sorted(token_to_pools[toks[1]] - {i}) if len(toks) > 1 else [] + chain_peer_map[i] = [j for j in range(n_pools) if j != i and pool_chains[j] == pool_chains[i]] + + # Per-pool log_fee for interaction features + pool_log_fee = raw_log_fee.copy() + fee_mean = float(np.mean(pool_log_fee)) + fee_std = max(float(np.std(pool_log_fee)), 1e-6) + + # Standardization stats for volumes + vol_mean = float(np.nanmean(vol_matrix)) + vol_std = float(np.nanstd(vol_matrix)) + tvl_mean = float(np.nanmean(tvl_matrix)) + tvl_std = float(np.nanstd(tvl_matrix)) + vola_mean = float(np.nanmean(volatility_matrix)) + vola_std = float(np.nanstd(volatility_matrix)) + + # Build x_obs per pool, mapped to common date grid + # x_obs_reduced: (n_dates, n_pools, 4), x_obs_cross: (n_dates, n_pools, 7) + from quantammsim.calibration.pool_data import K_OBS_REDUCED, K_OBS_CROSS + x_obs_reduced_grid = np.full((n_dates, n_pools, K_OBS_REDUCED), np.nan) + x_obs_cross_grid = np.full((n_dates, n_pools, K_OBS_CROSS), np.nan) + + for j, pid in enumerate(pool_ids): + entry = matched_clean[pid] + panel = entry["panel"] + dates_j = panel["date"].values + + # Reduced x_obs (4 features) + xr = build_x_obs(panel, reduced=True) # (n_obs, 4) + for k, date in enumerate(dates_j): + x_obs_reduced_grid[date_to_idx[date], j] = xr[k] + + # Cross-pool x_obs (7 features) — drops first day + xc = build_cross_pool_x_obs(panel, matched_clean, pid) # (n_obs-1, 7) + for k, date in enumerate(dates_j[1:]): + x_obs_cross_grid[date_to_idx[date], j] = xc[k] + + # Build samples: require t >= 2 (for lag-2), valid vol at t, t-1, t-2 + sample_pools, sample_days = [], [] + for i in range(n_pools): + for t in range(2, n_dates): + if (np.isnan(vol_matrix[t, i]) or np.isnan(vol_matrix[t - 1, i]) + or np.isnan(vol_matrix[t - 2, i])): + continue + sample_pools.append(i) + sample_days.append(t) + + sample_pools = np.array(sample_pools, dtype=np.int32) + sample_days = np.array(sample_days, dtype=np.int32) + n_samples = len(sample_pools) + + def _norm_vol(x): + return (x - vol_mean) / vol_std + + def _norm_tvl(x): + return (x - tvl_mean) / tvl_std + + def _norm_vola(x): + return (x - vola_mean) / vola_std + + # Per-sample arrays + # Peer features (per peer) + pf_vol_lag1 = np.zeros((n_samples, n_peers), dtype=np.float32) + pf_vol_lag2 = np.zeros((n_samples, n_peers), dtype=np.float32) + pf_vol_change = np.zeros((n_samples, n_peers), dtype=np.float32) + pf_tvl = np.zeros((n_samples, n_peers), dtype=np.float32) + pf_volatility = np.zeros((n_samples, n_peers), dtype=np.float32) + peer_mask = np.zeros((n_samples, n_peers), dtype=np.float32) + + # Local features + lf_own_vol_lag1 = np.zeros(n_samples, dtype=np.float32) + lf_own_vol_lag2 = np.zeros(n_samples, dtype=np.float32) + lf_own_vol_change = np.zeros(n_samples, dtype=np.float32) + lf_own_tvl = np.zeros(n_samples, dtype=np.float32) + lf_own_volatility = np.zeros(n_samples, dtype=np.float32) + lf_dow_sin = np.zeros(n_samples, dtype=np.float32) + lf_dow_cos = np.zeros(n_samples, dtype=np.float32) + # Interaction features (from calibration pipeline's x_obs) + lf_tvl_x_vola = np.zeros(n_samples, dtype=np.float32) + lf_tvl_x_fee = np.zeros(n_samples, dtype=np.float32) + lf_vola_x_fee = np.zeros(n_samples, dtype=np.float32) + # Cross-pool volume aggregates + lf_cross_vol_tok_a = np.zeros(n_samples, dtype=np.float32) + lf_cross_vol_tok_b = np.zeros(n_samples, dtype=np.float32) + lf_cross_vol_chain = np.zeros(n_samples, dtype=np.float32) + lf_market_vol = np.zeros(n_samples, dtype=np.float32) + # Cross-pool momentum (peer volume changes) + lf_cross_mom_tok_a = np.zeros(n_samples, dtype=np.float32) + lf_cross_mom_tok_b = np.zeros(n_samples, dtype=np.float32) + lf_cross_mom_chain = np.zeros(n_samples, dtype=np.float32) + + # Targets + y_total = np.zeros(n_samples, dtype=np.float32) + v_arb_samples = np.zeros(n_samples, dtype=np.float32) + + for s in range(n_samples): + i = sample_pools[s] + t = sample_days[s] + cols = peer_col_idx[i] + + # Peer features at t-1 + pvols1 = vol_matrix[t - 1, cols] + pvols2 = vol_matrix[t - 2, cols] + valid = ~np.isnan(pvols1) + peer_mask[s] = valid.astype(np.float32) + + pf_vol_lag1[s] = np.where(valid, _norm_vol(pvols1), 0.0) + pf_vol_lag2[s] = np.where(valid & ~np.isnan(pvols2), _norm_vol(pvols2), 0.0) + pf_vol_change[s] = np.where( + valid & ~np.isnan(pvols2), + _norm_vol(pvols1) - _norm_vol(pvols2), 0.0) + + ptvl = tvl_matrix[t - 1, cols] + pf_tvl[s] = np.where(valid & ~np.isnan(ptvl), _norm_tvl(ptvl), 0.0) + + pvola = volatility_matrix[t - 1, cols] + pf_volatility[s] = np.where(valid & ~np.isnan(pvola), _norm_vola(pvola), 0.0) + + # Local features + lf_own_vol_lag1[s] = _norm_vol(vol_matrix[t - 1, i]) + lf_own_vol_lag2[s] = _norm_vol(vol_matrix[t - 2, i]) + lf_own_vol_change[s] = lf_own_vol_lag1[s] - lf_own_vol_lag2[s] + + tvl_val = tvl_matrix[t, i] + lf_own_tvl[s] = _norm_tvl(tvl_val) if np.isfinite(tvl_val) else 0.0 + + vola_val = volatility_matrix[t, i] + lf_own_volatility[s] = _norm_vola(vola_val) if np.isfinite(vola_val) else 0.0 + + wd = weekday_arr[t] + lf_dow_sin[s] = np.sin(2 * np.pi * wd / 7) + lf_dow_cos[s] = np.cos(2 * np.pi * wd / 7) + + # Interaction features (raw products, standardized after loop) + norm_fee_i = (raw_log_fee[i] - fee_mean) / fee_std + lf_tvl_x_vola[s] = lf_own_tvl[s] * lf_own_volatility[s] + lf_tvl_x_fee[s] = lf_own_tvl[s] * norm_fee_i + lf_vola_x_fee[s] = lf_own_volatility[s] * norm_fee_i + + # Cross-pool volume aggregates at t-1 + def _peer_vol_mean(peer_list, t_lag): + if not peer_list: + return vol_mean # global fallback + vals = vol_matrix[t_lag, peer_list] + valid = vals[~np.isnan(vals)] + return float(np.mean(valid)) if len(valid) > 0 else vol_mean + + def _peer_vol_change_mean(peer_list, t_lag): + if not peer_list: + return 0.0 + v1 = vol_matrix[t_lag, peer_list] + v2 = vol_matrix[t_lag - 1, peer_list] + valid = ~np.isnan(v1) & ~np.isnan(v2) + if valid.sum() == 0: + return 0.0 + return float(np.mean(v1[valid] - v2[valid])) + + lf_cross_vol_tok_a[s] = _norm_vol(_peer_vol_mean(token_a_peers[i], t - 1)) + lf_cross_vol_tok_b[s] = _norm_vol(_peer_vol_mean(token_b_peers[i], t - 1)) + lf_cross_vol_chain[s] = _norm_vol(_peer_vol_mean(chain_peer_map[i], t - 1)) + lf_market_vol[s] = _norm_vol(float(np.nanmean(vol_matrix[t - 1, :]))) + + # Cross-pool momentum: mean volume change of peers (t-1 vs t-2) + lf_cross_mom_tok_a[s] = _peer_vol_change_mean(token_a_peers[i], t - 1) + lf_cross_mom_tok_b[s] = _peer_vol_change_mean(token_b_peers[i], t - 1) + lf_cross_mom_chain[s] = _peer_vol_change_mean(chain_peer_map[i], t - 1) + + y_total[s] = vol_matrix[t, i] + v_arb_val = v_arb_matrix[t, i] + v_arb_samples[s] = v_arb_val if np.isfinite(v_arb_val) else 1e-6 + + # Per-sample grid day indices for learnable cadence + sample_grid_days = common_to_grid[sample_pools, sample_days] + + # Per-sample x_obs arrays + x_obs_reduced = np.zeros((n_samples, K_OBS_REDUCED), dtype=np.float32) + x_obs_cross = np.zeros((n_samples, K_OBS_CROSS), dtype=np.float32) + for s in range(n_samples): + xr = x_obs_reduced_grid[sample_days[s], sample_pools[s]] + if np.all(np.isfinite(xr)): + x_obs_reduced[s] = xr + xc = x_obs_cross_grid[sample_days[s], sample_pools[s]] + if np.all(np.isfinite(xc)): + x_obs_cross[s] = xc + + # Standardize momentum features (raw volume differences) + for arr in [lf_cross_mom_tok_a, lf_cross_mom_tok_b, lf_cross_mom_chain]: + mu = np.mean(arr) + sigma = max(np.std(arr), 1e-6) + arr[:] = ((arr - mu) / sigma).astype(np.float32) + + return { + # Static per-pool + "peer_attrs": peer_attrs, # (n_pools, n_peers, k_attr) + "target_attrs": X_attr_norm, # (n_pools, k_attr) + "peer_overlap": peer_overlap, # (n_pools, n_peers) + "rel_same_chain": rel_same_chain, # (n_pools, n_peers) + "rel_log_tvl_ratio": rel_log_tvl_ratio, # (n_pools, n_peers) + "rel_log_fee_ratio": rel_log_fee_ratio, # (n_pools, n_peers) + # Per-sample peer features + "pf_vol_lag1": pf_vol_lag1, + "pf_vol_lag2": pf_vol_lag2, + "pf_vol_change": pf_vol_change, + "pf_tvl": pf_tvl, + "pf_volatility": pf_volatility, + "peer_mask": peer_mask, + # Per-sample local features + "lf_own_vol_lag1": lf_own_vol_lag1, + "lf_own_vol_lag2": lf_own_vol_lag2, + "lf_own_vol_change": lf_own_vol_change, + "lf_own_tvl": lf_own_tvl, + "lf_own_volatility": lf_own_volatility, + "lf_dow_sin": lf_dow_sin, + "lf_dow_cos": lf_dow_cos, + # Interaction features + "lf_tvl_x_vola": lf_tvl_x_vola, + "lf_tvl_x_fee": lf_tvl_x_fee, + "lf_vola_x_fee": lf_vola_x_fee, + # Cross-pool volume aggregates + "lf_cross_vol_tok_a": lf_cross_vol_tok_a, + "lf_cross_vol_tok_b": lf_cross_vol_tok_b, + "lf_cross_vol_chain": lf_cross_vol_chain, + "lf_market_vol": lf_market_vol, + # Cross-pool momentum + "lf_cross_mom_tok_a": lf_cross_mom_tok_a, + "lf_cross_mom_tok_b": lf_cross_mom_tok_b, + "lf_cross_mom_chain": lf_cross_mom_chain, + # Targets + "y_total": y_total, + "y_residual": (y_total - np.log(np.maximum(v_arb_samples, 1e-6))).astype(np.float32), + "v_arb": v_arb_samples, + # Cadence learning (per-pool, not subject to _subset) + "pool_coeffs": pool_coeffs, # list of PoolCoeffsDaily + "pool_gas": pool_gas, # list of jnp scalars + "init_log_cadences": init_log_cadences, # (n_pools,) + "sample_grid_days": sample_grid_days, # (n_samples,) + "x_obs_reduced": x_obs_reduced, # (n_samples, 4) + "x_obs_cross": x_obs_cross, # (n_samples, 7) + # Indices + "pool_idx": sample_pools, + "day_idx": sample_days, + # Meta + "n_pools": n_pools, + "n_peers": n_peers, + "k_attr": k_attr, + "pool_ids": pool_ids, + "vol_mean": vol_mean, + "vol_std": vol_std, + "fee_attr_idx": fee_idx, + "tvl_attr_idx": tvl_idx, + } + + +def assemble_inputs(data, feat_cfg): + """Assemble encoder/decoder inputs based on feature config. + + Returns dict with JAX arrays ready for training. + """ + # ---- Peer encoder input: (n_samples, n_peers, n_feat) ---- + pool_idx = data["pool_idx"] + pa = data["peer_attrs"][pool_idx] # (n_samples, n_peers, k_attr) + ta = data["target_attrs"][pool_idx] # (n_samples, k_attr) + + if feat_cfg.get("minimal_encoder"): + # 7-feature encoder: peer_fee, peer_tvl, target_fee, target_tvl, + # vol_lag1, overlap, same_chain — prevents pool identification + fi = data["fee_attr_idx"] + ti = data["tvl_attr_idx"] + pa_min = np.stack([pa[:, :, fi], pa[:, :, ti]], axis=-1) + ta_min = np.stack([ta[:, fi], ta[:, ti]], axis=-1) + ta_min_broad = np.broadcast_to( + ta_min[:, None, :], (pa_min.shape[0], pa_min.shape[1], 2)) + peer_parts = [ + pa_min, ta_min_broad, + data["pf_vol_lag1"][:, :, None], + data["peer_overlap"][pool_idx][:, :, None], + data["rel_same_chain"][pool_idx][:, :, None], + ] + else: + ta_broad = np.broadcast_to(ta[:, None, :], pa.shape) + peer_parts = [ + pa, ta_broad, + data["pf_vol_lag1"][:, :, None], + data["peer_overlap"][pool_idx][:, :, None], + ] + # Relational features (optional via feat_cfg, default on) + if feat_cfg.get("rel_same_chain", True): + peer_parts.append(data["rel_same_chain"][pool_idx][:, :, None]) + + # Optional temporal peer features (both modes) + if feat_cfg.get("peer_vol_lag2"): + peer_parts.append(data["pf_vol_lag2"][:, :, None]) + if feat_cfg.get("peer_vol_change"): + peer_parts.append(data["pf_vol_change"][:, :, None]) + if feat_cfg.get("peer_tvl"): + peer_parts.append(data["pf_tvl"][:, :, None]) + if feat_cfg.get("peer_volatility"): + peer_parts.append(data["pf_volatility"][:, :, None]) + + # Relational ratio features (both modes) + if feat_cfg.get("rel_tvl_ratio", True): + peer_parts.append(data["rel_log_tvl_ratio"][pool_idx][:, :, None]) + if feat_cfg.get("rel_fee_ratio", True): + peer_parts.append(data["rel_log_fee_ratio"][pool_idx][:, :, None]) + + peer_input = np.concatenate(peer_parts, axis=-1).astype(np.float32) + + # ---- Local decoder input: (n_samples, n_feat) ---- + # Always: target_attr, own_vol_lag1, dow_sin, dow_cos + local_parts = [ + ta, + data["lf_own_vol_lag1"][:, None], + data["lf_dow_sin"][:, None], + data["lf_dow_cos"][:, None], + ] + + if feat_cfg.get("own_vol_lag2"): + local_parts.append(data["lf_own_vol_lag2"][:, None]) + if feat_cfg.get("own_vol_change"): + local_parts.append(data["lf_own_vol_change"][:, None]) + if feat_cfg.get("own_tvl"): + local_parts.append(data["lf_own_tvl"][:, None]) + if feat_cfg.get("own_volatility"): + local_parts.append(data["lf_own_volatility"][:, None]) + + # Interaction features (tvl×vola, tvl×fee, vola×fee) + if feat_cfg.get("interactions"): + local_parts.append(data["lf_tvl_x_vola"][:, None]) + local_parts.append(data["lf_tvl_x_fee"][:, None]) + local_parts.append(data["lf_vola_x_fee"][:, None]) + + # Cross-pool volume aggregates (token-peer, chain-peer, market) + if feat_cfg.get("cross_pool_vol"): + local_parts.append(data["lf_cross_vol_tok_a"][:, None]) + local_parts.append(data["lf_cross_vol_tok_b"][:, None]) + local_parts.append(data["lf_cross_vol_chain"][:, None]) + local_parts.append(data["lf_market_vol"][:, None]) + + # Cross-pool momentum (peer volume changes) + if feat_cfg.get("cross_pool_momentum"): + local_parts.append(data["lf_cross_mom_tok_a"][:, None]) + local_parts.append(data["lf_cross_mom_tok_b"][:, None]) + local_parts.append(data["lf_cross_mom_chain"][:, None]) + + # Option C x_obs covariates (none / reduced=4 / cross=7) + x_obs_mode = feat_cfg.get("x_obs_mode", "none") + if x_obs_mode == "reduced" and "x_obs_reduced" in data: + local_parts.append(data["x_obs_reduced"]) + elif x_obs_mode == "cross" and "x_obs_cross" in data: + local_parts.append(data["x_obs_cross"]) + + local_input = np.concatenate(local_parts, axis=-1).astype(np.float32) + + result = { + "peer_input": jnp.array(peer_input), + "local_input": jnp.array(local_input), + "peer_mask": jnp.array(data["peer_mask"]), + "y": jnp.array(data["y_residual"] if feat_cfg.get("target_residual") else data["y_total"]), + "y_total": jnp.array(data["y_total"]), + "v_arb": jnp.array(data["v_arb"]), + "pool_idx": jnp.array(pool_idx), + "n_pools": data["n_pools"], + "n_peer_feat": peer_input.shape[-1], + "n_local_feat": local_input.shape[-1], + } + # Cadence learning arrays + if "sample_grid_days" in data: + result["sample_grid_days"] = jnp.array(data["sample_grid_days"]) + return result + + +# ---- Model ---- + + +def init_params(key, n_peer_feat, n_local_feat, hidden, d_embed, + encoder_type="mlp"): + """Initialize model parameters. + + encoder_type: "mlp" (2-layer ReLU) or "linear" (single affine). + Presence of "enc_W2" in params dict distinguishes the two at forward time. + """ + k1, k2, k3, k4 = jax.random.split(key, 4) + dec_in = d_embed + n_local_feat + params = {} + + if encoder_type == "mlp": + params["enc_W1"] = jax.random.normal(k1, (n_peer_feat, hidden)) * np.sqrt(2.0 / n_peer_feat) + params["enc_b1"] = jnp.zeros(hidden) + params["enc_W2"] = jax.random.normal(k2, (hidden, d_embed)) * np.sqrt(2.0 / hidden) + params["enc_b2"] = jnp.zeros(d_embed) + else: # linear + params["enc_W1"] = jax.random.normal(k1, (n_peer_feat, d_embed)) * np.sqrt(2.0 / n_peer_feat) + params["enc_b1"] = jnp.zeros(d_embed) + + params["dec_W1"] = jax.random.normal(k3, (dec_in, hidden)) * np.sqrt(2.0 / dec_in) + params["dec_b1"] = jnp.zeros(hidden) + params["dec_W2"] = jax.random.normal(k4, (hidden, 1)) * 0.01 + params["dec_b2"] = jnp.zeros(1) + return params + + +def warm_start_decoder(params, inputs, d_embed): + """Set decoder output layer via OLS through hidden activations. + + Fits y ~ h(local_input) with zero peer summary, so the decoder + starts predicting in the right volume range (~10-17 log scale). + """ + local = np.array(inputs["local_input"]) + y = np.array(inputs["y"]) + n = local.shape[0] + + # Simulate decoder input with zero peer summary + dec_in = np.concatenate( + [np.zeros((n, d_embed), dtype=np.float32), local], axis=1) + + # Forward through first decoder layer with current (random) weights + h = np.maximum( + dec_in @ np.array(params["dec_W1"]) + np.array(params["dec_b1"]), 0.0) + + # OLS: y ≈ h @ W2 + b2 + h_bias = np.concatenate([h, np.ones((n, 1), dtype=np.float32)], axis=1) + sol, _, _, _ = np.linalg.lstsq(h_bias, y[:, None], rcond=None) + + params["dec_W2"] = jnp.array(sol[:-1].astype(np.float32)) + params["dec_b2"] = jnp.array(sol[-1:].astype(np.float32)) + return params + + +def forward(params, peer_input, peer_mask, local_input, no_peers=False): + """Returns predicted log_volume (total) per sample.""" + batch, n_peers, _ = peer_input.shape + + if no_peers: + d_embed = params["dec_W1"].shape[0] - local_input.shape[-1] + summary = jnp.zeros((batch, d_embed)) + else: + flat = peer_input.reshape(-1, peer_input.shape[-1]) + if "enc_W2" in params: + # MLP encoder: 2-layer with ReLU + h = jnp.maximum(flat @ params["enc_W1"] + params["enc_b1"], 0.0) + h = h @ params["enc_W2"] + params["enc_b2"] + else: + # Linear encoder: single affine + h = flat @ params["enc_W1"] + params["enc_b1"] + h = h.reshape(batch, n_peers, -1) + + h_masked = h * peer_mask[:, :, None] + n_valid = jnp.maximum(jnp.sum(peer_mask, axis=1, keepdims=True), 1.0) + summary = jnp.sum(h_masked, axis=1) / n_valid + + dec_in = jnp.concatenate([summary, local_input], axis=-1) + h_dec = jnp.maximum(dec_in @ params["dec_W1"] + params["dec_b1"], 0.0) + return (h_dec @ params["dec_W2"] + params["dec_b2"])[:, 0] + + +def loss_fn(params, peer_input, peer_mask, local_input, y, l2_alpha, + pool_idx, n_pools, huber_delta, no_peers): + """Huber loss with per-pool weighting + L2 reg.""" + pred = forward(params, peer_input, peer_mask, local_input, no_peers) + residuals = pred - y + abs_r = jnp.abs(residuals) + huber_vals = jnp.where(abs_r <= huber_delta, 0.5 * residuals ** 2, + huber_delta * (abs_r - 0.5 * huber_delta)) + + # Per-pool mean loss, then average across active pools (handles LOO gaps) + pool_counts = jnp.zeros(n_pools).at[pool_idx].add(jnp.ones_like(pool_idx, dtype=jnp.float32)) + active = (pool_counts > 0).astype(jnp.float32) + n_active = jnp.maximum(jnp.sum(active), 1.0) + pool_counts = jnp.maximum(pool_counts, 1.0) + pool_sums = jnp.zeros(n_pools).at[pool_idx].add(huber_vals) + data_loss = jnp.sum((pool_sums / pool_counts) * active) / n_active + + reg = sum(jnp.sum(v ** 2) for k, v in params.items() if "W" in k) + return data_loss + l2_alpha * reg + + +# n_pools (arg 7): static for jnp.zeros shape; no_peers (arg 9): static for if/else in forward +_grad_fn = jax.jit(jax.value_and_grad(loss_fn), static_argnums=(7, 9)) + + +# ---- Learnable cadence ---- + + +def make_cadence_loss_fn(pool_coeffs, pool_gas, n_pools, no_peers): + """Build a loss function with per-pool PCHIP coefficients closed over. + + The returned function is JIT-compiled. The Python loop over pools is + unrolled at trace time, so each pool's coefficients are constants. + + The neural net predicts log(V_noise). V_arb comes from PCHIP at the + current learnable log_cadence. Loss is Huber on log(V_arb + V_noise) + vs log(V_obs). + """ + from quantammsim.calibration.grid_interpolation import interpolate_pool_daily + + def loss_fn_cadence(params, peer_input, peer_mask, local_input, y_total, + sample_grid_days, pool_idx, l2_alpha, huber_delta): + # Neural net predicts log(V_noise) + log_v_noise = forward(params, peer_input, peer_mask, local_input, no_peers) + + # Compute V_arb per sample via PCHIP (loop unrolled at trace time) + log_cadence = params["log_cadence"] + n_samples = y_total.shape[0] + v_arb = jnp.zeros(n_samples) + + for i in range(n_pools): + v_arb_all = interpolate_pool_daily( + pool_coeffs[i], log_cadence[i], pool_gas[i]) + # Index into this pool's daily V_arb; clip for safety on other pools' samples + safe_days = jnp.clip(sample_grid_days, 0, v_arb_all.shape[0] - 1) + v_arb = jnp.where(pool_idx == i, v_arb_all[safe_days], v_arb) + + # Combine: log(V_arb + V_noise) via numerically stable logaddexp + log_v_arb = jnp.log(jnp.maximum(v_arb, 1e-10)) + log_v_total = jnp.logaddexp(log_v_arb, log_v_noise) + + # Huber loss with per-pool weighting + residuals = log_v_total - y_total + abs_r = jnp.abs(residuals) + huber_vals = jnp.where(abs_r <= huber_delta, 0.5 * residuals ** 2, + huber_delta * (abs_r - 0.5 * huber_delta)) + + pool_counts = jnp.zeros(n_pools).at[pool_idx].add( + jnp.ones_like(pool_idx, dtype=jnp.float32)) + active = (pool_counts > 0).astype(jnp.float32) + n_active = jnp.maximum(jnp.sum(active), 1.0) + pool_counts = jnp.maximum(pool_counts, 1.0) + pool_sums = jnp.zeros(n_pools).at[pool_idx].add(huber_vals) + data_loss = jnp.sum((pool_sums / pool_counts) * active) / n_active + + reg = sum(jnp.sum(v ** 2) for k, v in params.items() if "W" in k) + return data_loss + l2_alpha * reg + + grad_fn = jax.jit(jax.value_and_grad(loss_fn_cadence)) + return grad_fn + + +# ---- Training ---- + + +def train(params, inputs, n_epochs, lr, l2_alpha, huber_delta=1.0, + no_peers=False, verbose=True, grad_fn_override=None): + m = {k: jnp.zeros_like(v) for k, v in params.items()} + v = {k: jnp.zeros_like(v) for k, v in params.items()} + final_loss = float("inf") + + n_pools = int(inputs["n_pools"]) + pool_idx = inputs["pool_idx"] + use_cadence = grad_fn_override is not None + + for epoch in range(n_epochs): + if use_cadence: + loss_val, grads = grad_fn_override( + params, inputs["peer_input"], inputs["peer_mask"], + inputs["local_input"], inputs["y_total"], + inputs["sample_grid_days"], pool_idx, l2_alpha, huber_delta, + ) + else: + loss_val, grads = _grad_fn( + params, inputs["peer_input"], inputs["peer_mask"], + inputs["local_input"], inputs["y"], l2_alpha, + pool_idx, n_pools, huber_delta, no_peers, + ) + final_loss = float(loss_val) + + for k in params: + m[k] = 0.9 * m[k] + 0.1 * grads[k] + v[k] = 0.999 * v[k] + 0.001 * grads[k] ** 2 + m_hat = m[k] / (1.0 - 0.9 ** (epoch + 1)) + v_hat = v[k] / (1.0 - 0.999 ** (epoch + 1)) + params[k] = params[k] - lr * m_hat / (jnp.sqrt(v_hat) + 1e-8) + + if verbose and (epoch % 200 == 0 or epoch == n_epochs - 1): + if use_cadence: + cads = np.exp(np.array(params["log_cadence"])) + # Quick decomposition check: forward pass + V_arb at current cadences + _lvn = np.array(forward( + params, inputs["peer_input"], inputs["peer_mask"], + inputs["local_input"], no_peers)) + _vn = np.exp(_lvn) + _vo = np.exp(np.array(inputs["y_total"])) + # Approximate arb fraction (use V_obs - V_noise as proxy to avoid PCHIP call) + _arb_proxy = np.clip(1.0 - _vn / _vo, 0, None) + _n_pathological = np.sum(_arb_proxy < -0.5) # noise > 1.5x observed + _n_bound = np.sum((cads <= 1.01) | (cads >= 59.9)) + print(f" epoch {epoch:4d} loss={final_loss:.6f}" + f" cad=[{cads.min():.1f}-{np.median(cads):.1f}-{cads.max():.1f}]" + f" |logVn|={np.mean(np.abs(_lvn)):.1f}" + f" bound={_n_bound}") + else: + print(f" epoch {epoch:4d} loss={final_loss:.6f}") + + return params, final_loss + + +# ---- Evaluation ---- + + +def evaluate(params, inputs, data, label="", no_peers=False, + target_residual=False): + """Per-pool R² on total volume and noise residual.""" + pred = np.array(forward( + params, inputs["peer_input"], inputs["peer_mask"], + inputs["local_input"], no_peers=no_peers, + )) + y = np.array(inputs["y"]) + v_arb = np.array(inputs["v_arb"]) + pool_idx = np.array(inputs["pool_idx"]) + + log_v_arb = np.log(np.maximum(v_arb, 1e-6)) + + if target_residual: + # Model predicts noise residual directly + resid_true = y + resid_pred = pred + y_total = y + log_v_arb # reconstruct total for total R² + pred_total = pred + log_v_arb + else: + # Model predicts total log_volume + y_total = y + pred_total = pred + resid_true = y - log_v_arb + resid_pred = pred - log_v_arb + + r2_total = {} + r2_resid = {} + pool_ids = data.get("pool_ids", []) + + for i in range(data["n_pools"]): + mask = pool_idx == i + if mask.sum() < 2: + continue + yt = y_total[mask] + pt = pred_total[mask] + ss_res_t = np.sum((yt - pt) ** 2) + ss_tot_t = np.sum((yt - yt.mean()) ** 2) + r2_total[i] = 1 - ss_res_t / max(ss_tot_t, 1e-10) + + rt = resid_true[mask] + rp = resid_pred[mask] + ss_res_r = np.sum((rt - rp) ** 2) + ss_tot_r = np.sum((rt - rt.mean()) ** 2) + r2_resid[i] = 1 - ss_res_r / max(ss_tot_r, 1e-10) + + def _med(d): + v = [x for x in d.values() if np.isfinite(x)] + return np.median(v) if v else float("nan") + + if label: + print(f"\n {label}:") + for i in range(data["n_pools"]): + if i in r2_total and i < len(pool_ids): + pid = pool_ids[i] + print(f" {pid[:16]} total={r2_total[i]:.3f} resid={r2_resid[i]:.3f}") + + med_total = _med(r2_total) + med_resid = _med(r2_resid) + print(f" Median R² total={med_total:.4f} resid={med_resid:.4f}") + return med_total, med_resid, r2_total, r2_resid + + +def _compute_cadence_decomposition(params, inputs, data, no_peers=False): + """Compute V_arb, V_noise, and predictions for cadence mode. Returns numpy arrays.""" + from quantammsim.calibration.grid_interpolation import interpolate_pool_daily + + log_v_noise = np.array(forward( + params, inputs["peer_input"], inputs["peer_mask"], + inputs["local_input"], no_peers=no_peers, + )) + y_total = np.array(inputs["y_total"]) + pool_idx = np.array(inputs["pool_idx"]) + sample_grid_days = np.array(inputs["sample_grid_days"]) + + pool_coeffs = data["pool_coeffs"] + pool_gas = data["pool_gas"] + log_cadence = np.array(params["log_cadence"]) + n_pools = data["n_pools"] + + v_arb = np.zeros(len(y_total)) + for i in range(n_pools): + mask = pool_idx == i + if not mask.any(): + continue + v_arb_all = np.array(interpolate_pool_daily( + pool_coeffs[i], jnp.float64(log_cadence[i]), pool_gas[i])) + v_arb[mask] = v_arb_all[sample_grid_days[mask]] + + v_obs = np.exp(y_total) + v_noise = np.exp(log_v_noise) + log_v_arb = np.log(np.maximum(v_arb, 1e-10)) + pred_total = np.logaddexp(log_v_arb, log_v_noise) + + return { + "v_arb": v_arb, "v_noise": v_noise, "v_obs": v_obs, + "log_v_noise": log_v_noise, "log_v_arb": log_v_arb, + "pred_total": pred_total, "y_total": y_total, + "pool_idx": pool_idx, "log_cadence": log_cadence, + } + + +def evaluate_cadence(params, inputs, data, label="", no_peers=False): + """Evaluate with learned cadence: per-pool R², decomposition diagnostics.""" + dec = _compute_cadence_decomposition(params, inputs, data, no_peers) + pool_ids = data.get("pool_ids", []) + init_cads = data["init_log_cadences"] + n_pools = data["n_pools"] + + if label: + print(f"\n {label}:") + print(f" {'Pool'[:16]:16s} {'R²':>6s} {'Cad init':>8s} {'→learn':>7s}" + f" {'Arb%':>6s} {'Noise%':>7s} {'logVn μ':>7s} {'logVn σ':>7s} {'Flag':>5s}") + print(f" {'-'*80}") + + r2_total = {} + pool_diag = [] + for i in range(n_pools): + mask = dec["pool_idx"] == i + if mask.sum() < 2: + continue + yt = dec["y_total"][mask] + pt = dec["pred_total"][mask] + ss_res = np.sum((yt - pt) ** 2) + ss_tot = np.sum((yt - yt.mean()) ** 2) + r2_total[i] = 1 - ss_res / max(ss_tot, 1e-10) + + pid = pool_ids[i] if i < len(pool_ids) else f"pool_{i}" + cad_init = np.exp(init_cads[i]) + cad_learned = np.exp(dec["log_cadence"][i]) + + va = dec["v_arb"][mask] + vo = dec["v_obs"][mask] + vn = dec["v_noise"][mask] + lvn = dec["log_v_noise"][mask] + + arb_pct = np.median(va / vo) * 100 + noise_pct = np.median(vn / vo) * 100 + lvn_mu = np.mean(lvn) + lvn_std = np.std(lvn) + + # Flags + flags = [] + if arb_pct > 150: + flags.append("A") # arb dominates + if cad_learned <= 1.01 or cad_learned >= 59.9: + flags.append("B") # cadence at bound + if r2_total[i] < 0: + flags.append("X") # negative R² + + flag_str = "".join(flags) if flags else "" + pool_diag.append({ + "idx": i, "pid": pid, "r2": r2_total[i], + "cad_init": cad_init, "cad_learned": cad_learned, + "arb_pct": arb_pct, "noise_pct": noise_pct, + "lvn_mu": lvn_mu, "lvn_std": lvn_std, "flags": flag_str, + }) + + print(f" {pid[:16]:16s} {r2_total[i]:6.3f} {cad_init:7.1f}m {cad_learned:6.1f}m" + f" {arb_pct:6.0f}% {noise_pct:6.0f}% {lvn_mu:7.1f} {lvn_std:7.2f}" + f" {flag_str:>5s}") + + # ── Summary statistics ── + vals = [x for x in r2_total.values() if np.isfinite(x)] + med_r2 = np.median(vals) if vals else float("nan") + cads = np.exp(dec["log_cadence"]) + + n_pathological = sum(1 for d in pool_diag if d["arb_pct"] > 150) + n_at_bound = sum(1 for d in pool_diag + if d["cad_learned"] <= 1.01 or d["cad_learned"] >= 59.9) + n_negative_r2 = sum(1 for d in pool_diag if d["r2"] < 0) + healthy = [d for d in pool_diag if d["arb_pct"] <= 150 and d["r2"] > 0] + med_r2_healthy = (np.median([d["r2"] for d in healthy]) + if healthy else float("nan")) + + print(f"\n ── Summary ──") + print(f" Median R² total: {med_r2:.4f} (healthy only: {med_r2_healthy:.4f})") + print(f" Cadence range: {cads.min():.1f} - {np.median(cads):.1f}" + f" - {cads.max():.1f} min") + print(f" Decomposition: {len(pool_diag) - n_pathological}/{len(pool_diag)}" + f" healthy (arb≤150%), {n_pathological} pathological") + print(f" Cadence at bounds: {n_at_bound}/{len(pool_diag)}" + f" (≤1min or ≥60min)") + print(f" Negative R²: {n_negative_r2}/{len(pool_diag)}") + print(f" Flags: A=arb>150%, B=cadence at bound, X=negative R²") + + return med_r2, r2_total, pool_diag + + +def print_cadence_comparison(train_diag, eval_diag): + """Print train vs eval diagnostic comparison.""" + train_map = {d["pid"]: d for d in train_diag} + eval_map = {d["pid"]: d for d in eval_diag} + all_pids = sorted(set(train_map) | set(eval_map)) + + print(f"\n ── Train vs Eval Gap ──") + print(f" {'Pool'[:16]:16s} {'R² trn':>7s} {'R² eval':>7s} {'Gap':>6s}" + f" {'ArbTrn%':>7s} {'ArbEval%':>8s}") + print(f" {'-'*55}") + + gaps = [] + for pid in all_pids: + td = train_map.get(pid) + ed = eval_map.get(pid) + if td is None or ed is None: + continue + gap = td["r2"] - ed["r2"] + gaps.append(gap) + flag = " ***" if abs(gap) > 0.5 else "" + print(f" {pid[:16]:16s} {td['r2']:7.3f} {ed['r2']:7.3f} {gap:+6.3f}" + f" {td['arb_pct']:6.0f}% {ed['arb_pct']:7.0f}%{flag}") + + if gaps: + print(f" Median gap: {np.median(gaps):+.3f} " + f"Mean gap: {np.mean(gaps):+.3f} " + f"Max gap: {max(gaps):+.3f}") + + +# Keys indexed by sample (shape[0] == n_samples) +_SAMPLE_KEYS = { + "pf_vol_lag1", "pf_vol_lag2", "pf_vol_change", "pf_tvl", "pf_volatility", + "peer_mask", "lf_own_vol_lag1", "lf_own_vol_lag2", "lf_own_vol_change", + "lf_own_tvl", "lf_own_volatility", "lf_dow_sin", "lf_dow_cos", + "lf_tvl_x_vola", "lf_tvl_x_fee", "lf_vola_x_fee", + "lf_cross_vol_tok_a", "lf_cross_vol_tok_b", "lf_cross_vol_chain", + "lf_market_vol", "lf_cross_mom_tok_a", "lf_cross_mom_tok_b", + "lf_cross_mom_chain", + "y_total", "y_residual", "v_arb", "sample_grid_days", + "x_obs_reduced", "x_obs_cross", + "pool_idx", "day_idx", +} + + +def _subset(d, mask): + """Subset sample-indexed arrays by boolean mask.""" + out = {} + for k, v in d.items(): + if k in _SAMPLE_KEYS and isinstance(v, np.ndarray): + out[k] = v[mask] + else: + out[k] = v + return out + + +# ---- Temporal split ---- + + +def run_temporal(data, feat_cfg, hparams, split_frac=0.7): + """Train on first split_frac of days, eval on rest.""" + day_idx = data["day_idx"] + split_day = int(day_idx.max() * split_frac) + train_mask = day_idx <= split_day + eval_mask = day_idx > split_day + + train_data = _subset(data, train_mask) + eval_data = _subset(data, eval_mask) + + train_inputs = assemble_inputs(train_data, feat_cfg) + eval_inputs = assemble_inputs(eval_data, feat_cfg) + + encoder_type = hparams.get("encoder_type", "mlp") + no_peers = hparams.get("no_peers", False) + huber_delta = hparams.get("huber_delta", 1.0) + target_residual = feat_cfg.get("target_residual", False) + learn_cadence = hparams.get("learn_cadence", False) + + n_pf = train_inputs["n_peer_feat"] + n_lf = train_inputs["n_local_feat"] + n_params = sum(v.size for v in init_params( + jax.random.PRNGKey(0), n_pf, n_lf, hparams["hidden"], hparams["d_embed"], + encoder_type=encoder_type, + ).values()) + + print(f" Train: {int(train_mask.sum())}, Eval: {int(eval_mask.sum())}, " + f"peer_feat={n_pf}, local_feat={n_lf}, params={n_params}") + if learn_cadence: + print(f" learn_cadence=True (joint cadence+noise optimization)") + if target_residual: + print(f" target=residual (log_vol - log_V_arb)") + if encoder_type != "mlp": + print(f" encoder_type={encoder_type}") + if no_peers: + print(f" no_peers=True (decoder-only ablation)") + if huber_delta != 1.0: + print(f" huber_delta={huber_delta}") + + params = init_params( + jax.random.PRNGKey(42), n_pf, n_lf, hparams["hidden"], hparams["d_embed"], + encoder_type=encoder_type, + ) + + if learn_cadence: + # Add learnable cadence, initialized from Option C + params["log_cadence"] = jnp.array(data["init_log_cadences"]) + init_cads = np.exp(data["init_log_cadences"]) + print(f" Init cadence: {init_cads.min():.1f}-{np.median(init_cads):.1f}" + f"-{init_cads.max():.1f} min") + + # Warm-start decoder to predict noise residual (log_vol - log_V_arb) + # using the Option C V_arb as the initial target + ws_inputs = dict(train_inputs) + ws_inputs["y"] = train_inputs["y_total"] - jnp.log( + jnp.maximum(train_inputs["v_arb"], 1e-6)) + params = warm_start_decoder(params, ws_inputs, hparams["d_embed"]) + + grad_fn = make_cadence_loss_fn( + data["pool_coeffs"], data["pool_gas"], + data["n_pools"], no_peers) + + print(" Compiling cadence loss (may take a moment)...") + t0 = time.time() + params, _ = train( + params, train_inputs, hparams["n_epochs"], hparams["lr"], + hparams["l2_alpha"], huber_delta=huber_delta, no_peers=no_peers, + grad_fn_override=grad_fn, + ) + print(f" Training: {time.time() - t0:.1f}s") + + print("\n --- Train ---") + _, _, train_diag = evaluate_cadence( + params, train_inputs, data, no_peers=no_peers) + print("\n --- Eval ---") + _, _, eval_diag = evaluate_cadence( + params, eval_inputs, data, no_peers=no_peers) + print_cadence_comparison(train_diag, eval_diag) + else: + params = warm_start_decoder(params, train_inputs, hparams["d_embed"]) + t0 = time.time() + params, _ = train( + params, train_inputs, hparams["n_epochs"], hparams["lr"], + hparams["l2_alpha"], huber_delta=huber_delta, no_peers=no_peers, + ) + print(f" Training: {time.time() - t0:.1f}s") + + eval_kw = dict(no_peers=no_peers, target_residual=target_residual) + print("\n --- Train ---") + evaluate(params, train_inputs, data, **eval_kw) + print("\n --- Eval ---") + _, med_resid_eval, _, _ = evaluate(params, eval_inputs, data, **eval_kw) + + return params + + +# ---- LOO cross-validation ---- + + +def run_loo(data, feat_cfg, hparams): + """Leave-one-pool-out: train on N-1 pools, evaluate on held-out pool. + + Tests cross-pool generalization — can the shared encoder+decoder predict + volume for a pool it has never optimized on? The held-out pool's volume + is still observable as peer features for training pools. + + Note: normalization stats are computed on the full dataset. With N=36 + the leakage from including one held-out pool is ~3% on mean/std. + """ + n_pools = data["n_pools"] + pool_idx = data["pool_idx"] + pool_ids = data.get("pool_ids", []) + + encoder_type = hparams.get("encoder_type", "mlp") + no_peers = hparams.get("no_peers", False) + huber_delta = hparams.get("huber_delta", 1.0) + target_residual = feat_cfg.get("target_residual", False) + d_embed = hparams["d_embed"] + + r2_total_all = {} + r2_resid_all = {} + + for held_out in range(n_pools): + pid = pool_ids[held_out] if held_out < len(pool_ids) else f"pool_{held_out}" + + train_mask = pool_idx != held_out + eval_mask = pool_idx == held_out + n_eval = int(eval_mask.sum()) + + if n_eval < 2: + print(f" [{held_out:2d}] {pid[:16]}: skipped ({n_eval} samples)") + continue + + train_data = _subset(data, train_mask) + eval_data = _subset(data, eval_mask) + + train_inputs = assemble_inputs(train_data, feat_cfg) + eval_inputs = assemble_inputs(eval_data, feat_cfg) + + params = init_params( + jax.random.PRNGKey(42), + train_inputs["n_peer_feat"], train_inputs["n_local_feat"], + hparams["hidden"], d_embed, + encoder_type=encoder_type, + ) + params = warm_start_decoder(params, train_inputs, d_embed) + + params, _ = train( + params, train_inputs, hparams["n_epochs"], + hparams["lr"], hparams["l2_alpha"], + huber_delta=huber_delta, no_peers=no_peers, + verbose=False, + ) + + # Evaluate on held-out pool + pred = np.array(forward( + params, eval_inputs["peer_input"], eval_inputs["peer_mask"], + eval_inputs["local_input"], no_peers=no_peers, + )) + y = np.array(eval_inputs["y"]) + v_arb = np.array(eval_inputs["v_arb"]) + log_v_arb = np.log(np.maximum(v_arb, 1e-6)) + + if target_residual: + resid_true, resid_pred = y, pred + y_total = y + log_v_arb + pred_total = pred + log_v_arb + else: + y_total, pred_total = y, pred + resid_true = y - log_v_arb + resid_pred = pred - log_v_arb + + ss_res = np.sum((y_total - pred_total) ** 2) + ss_tot = np.sum((y_total - y_total.mean()) ** 2) + r2_t = 1 - ss_res / max(ss_tot, 1e-10) + + ss_res_r = np.sum((resid_true - resid_pred) ** 2) + ss_tot_r = np.sum((resid_true - resid_true.mean()) ** 2) + r2_r = 1 - ss_res_r / max(ss_tot_r, 1e-10) + + r2_total_all[held_out] = r2_t + r2_resid_all[held_out] = r2_r + + print(f" [{held_out:2d}] {pid[:16]}: total={r2_t:.3f} resid={r2_r:.3f} (n={n_eval})") + + def _med(d): + v = [x for x in d.values() if np.isfinite(x)] + return np.median(v) if v else float("nan") + + med_total = _med(r2_total_all) + med_resid = _med(r2_resid_all) + print(f"\n LOO Median R² total={med_total:.4f} resid={med_resid:.4f}") + print(f" ({len(r2_total_all)} pools evaluated)") + + return med_total, med_resid + + +# ---- Optuna ---- + + +_FEAT_KEYS = [ + "peer_vol_lag2", "peer_vol_change", "peer_tvl", "peer_volatility", + "own_vol_lag2", "own_vol_change", "own_tvl", "own_volatility", + "rel_same_chain", "rel_tvl_ratio", "rel_fee_ratio", "minimal_encoder", + "interactions", "cross_pool_vol", "cross_pool_momentum", +] + + +def run_optuna(data, n_trials, target_residual=False): + import optuna + + day_idx = data["day_idx"] + split_day = int(day_idx.max() * 0.7) + train_mask = day_idx <= split_day + eval_mask = day_idx > split_day + + train_data = _subset(data, train_mask) + eval_data = _subset(data, eval_mask) + + def objective(trial): + feat_cfg = { + "peer_vol_lag2": trial.suggest_categorical("peer_vol_lag2", [True, False]), + "peer_vol_change": trial.suggest_categorical("peer_vol_change", [True, False]), + "peer_tvl": trial.suggest_categorical("peer_tvl", [True, False]), + "peer_volatility": trial.suggest_categorical("peer_volatility", [True, False]), + "own_vol_lag2": trial.suggest_categorical("own_vol_lag2", [True, False]), + "own_vol_change": trial.suggest_categorical("own_vol_change", [True, False]), + "own_tvl": trial.suggest_categorical("own_tvl", [True, False]), + "own_volatility": trial.suggest_categorical("own_volatility", [True, False]), + "rel_same_chain": trial.suggest_categorical("rel_same_chain", [True, False]), + "rel_tvl_ratio": trial.suggest_categorical("rel_tvl_ratio", [True, False]), + "rel_fee_ratio": trial.suggest_categorical("rel_fee_ratio", [True, False]), + "minimal_encoder": trial.suggest_categorical("minimal_encoder", [True, False]), + "interactions": trial.suggest_categorical("interactions", [True, False]), + "cross_pool_vol": trial.suggest_categorical("cross_pool_vol", [True, False]), + "cross_pool_momentum": trial.suggest_categorical("cross_pool_momentum", [True, False]), + "target_residual": target_residual, + } + hparams = { + "hidden": trial.suggest_categorical("hidden", [16, 32, 64, 128]), + "d_embed": trial.suggest_categorical("d_embed", [4, 8, 16, 32]), + "lr": trial.suggest_float("lr", 1e-4, 1e-2, log=True), + "l2_alpha": trial.suggest_float("l2_alpha", 1e-5, 1e-1, log=True), + "n_epochs": trial.suggest_categorical("n_epochs", [500, 1000, 2000]), + "encoder_type": trial.suggest_categorical("encoder_type", ["mlp", "linear"]), + "huber_delta": trial.suggest_categorical("huber_delta", [0.5, 1.0, 1.5, 2.0]), + "no_peers": trial.suggest_categorical("no_peers", [True, False]), + } + + train_inputs = assemble_inputs(train_data, feat_cfg) + eval_inputs = assemble_inputs(eval_data, feat_cfg) + + params = init_params( + jax.random.PRNGKey(42), + train_inputs["n_peer_feat"], train_inputs["n_local_feat"], + hparams["hidden"], hparams["d_embed"], + encoder_type=hparams["encoder_type"], + ) + params = warm_start_decoder(params, train_inputs, hparams["d_embed"]) + params, _ = train( + params, train_inputs, hparams["n_epochs"], + hparams["lr"], hparams["l2_alpha"], + huber_delta=hparams["huber_delta"], + no_peers=hparams["no_peers"], + verbose=False, + ) + + # Eval R² on noise residual + _tgt_resid = feat_cfg.get("target_residual", False) + pred = np.array(forward( + params, eval_inputs["peer_input"], eval_inputs["peer_mask"], + eval_inputs["local_input"], no_peers=hparams["no_peers"], + )) + y = np.array(eval_inputs["y"]) + v_arb = np.array(eval_inputs["v_arb"]) + log_v_arb = np.log(np.maximum(v_arb, 1e-6)) + pool_idx = np.array(eval_data["pool_idx"]) + + r2_resids = [] + r2_totals = [] + for i in range(data["n_pools"]): + mask = pool_idx == i + if mask.sum() < 2: + continue + yi = y[mask] + pi = pred[mask] + lva = log_v_arb[mask] + + if _tgt_resid: + resid_true, resid_pred = yi, pi + yt, pt = yi + lva, pi + lva + else: + yt, pt = yi, pi + resid_true, resid_pred = yi - lva, pi - lva + + ss_res = np.sum((yt - pt) ** 2) + ss_tot = np.sum((yt - yt.mean()) ** 2) + r2_totals.append(1 - ss_res / max(ss_tot, 1e-10)) + + ss_res_r = np.sum((resid_true - resid_pred) ** 2) + ss_tot_r = np.sum((resid_true - resid_true.mean()) ** 2) + r2_resids.append(1 - ss_res_r / max(ss_tot_r, 1e-10)) + + med_resid = float(np.median(r2_resids)) if r2_resids else -10.0 + med_total = float(np.median(r2_totals)) if r2_totals else -10.0 + + trial.set_user_attr("med_total_r2", med_total) + n_feat = sum(1 for k in _FEAT_KEYS if feat_cfg.get(k)) + print(f" Trial {trial.number}: resid={med_resid:.4f} total={med_total:.4f} " + f"enc={hparams['encoder_type']} h={hparams['hidden']} d={hparams['d_embed']} " + f"hub={hparams['huber_delta']} " + f"{'no_peers ' if hparams['no_peers'] else ''}" + f"lr={hparams['lr']:.1e} a={hparams['l2_alpha']:.1e} " + f"ep={hparams['n_epochs']} feat={n_feat}/15" + f"{' minimal' if feat_cfg.get('minimal_encoder') else ''}") + + return med_resid + + study = optuna.create_study(direction="maximize") + study.optimize(objective, n_trials=n_trials) + + print(f"\n{'='*70}") + print("Optuna Results") + print(f"{'='*70}") + print(f" Best eval noise resid R²: {study.best_value:.4f}") + print(f" Best total R²: {study.best_trial.user_attrs['med_total_r2']:.4f}") + print(f" Best params:") + for k, v in sorted(study.best_params.items()): + print(f" {k}: {v}") + + print(f"\n Top 10:") + trials = sorted(study.trials, key=lambda t: t.value if t.value else -999, + reverse=True) + for t in trials[:10]: + if t.value is not None: + feats = sum(1 for k in _FEAT_KEYS if t.params.get(k)) + print(f" #{t.number}: resid={t.value:.4f} " + f"total={t.user_attrs.get('med_total_r2', '?'):.4f} " + f"enc={t.params['encoder_type']} " + f"h={t.params['hidden']} d={t.params['d_embed']} " + f"feat={feats}/15") + + return study + + +def run_optuna_cadence(data, n_trials): + """Optuna sweep with learnable cadence. Optimizes median eval total R².""" + import optuna + from quantammsim.calibration.grid_interpolation import interpolate_pool_daily + + day_idx = data["day_idx"] + split_day = int(day_idx.max() * 0.7) + train_mask = day_idx <= split_day + eval_mask = day_idx > split_day + + train_data = _subset(data, train_mask) + eval_data = _subset(data, eval_mask) + + pool_coeffs = data["pool_coeffs"] + pool_gas = data["pool_gas"] + n_pools = data["n_pools"] + + # Pre-build grad_fn closures for (no_peers=True, no_peers=False) + # to avoid recompiling on every trial with the same no_peers setting + _grad_fn_cache = {} + + def _get_grad_fn(no_peers): + if no_peers not in _grad_fn_cache: + _grad_fn_cache[no_peers] = make_cadence_loss_fn( + pool_coeffs, pool_gas, n_pools, no_peers) + return _grad_fn_cache[no_peers] + + def objective(trial): + feat_cfg = { + "peer_vol_lag2": trial.suggest_categorical("peer_vol_lag2", [True, False]), + "peer_vol_change": trial.suggest_categorical("peer_vol_change", [True, False]), + "peer_tvl": trial.suggest_categorical("peer_tvl", [True, False]), + "peer_volatility": trial.suggest_categorical("peer_volatility", [True, False]), + "own_vol_lag2": trial.suggest_categorical("own_vol_lag2", [True, False]), + "own_vol_change": trial.suggest_categorical("own_vol_change", [True, False]), + "own_tvl": trial.suggest_categorical("own_tvl", [True, False]), + "own_volatility": trial.suggest_categorical("own_volatility", [True, False]), + "rel_same_chain": trial.suggest_categorical("rel_same_chain", [True, False]), + "rel_tvl_ratio": trial.suggest_categorical("rel_tvl_ratio", [True, False]), + "rel_fee_ratio": trial.suggest_categorical("rel_fee_ratio", [True, False]), + "minimal_encoder": trial.suggest_categorical("minimal_encoder", [True, False]), + "interactions": trial.suggest_categorical("interactions", [True, False]), + "cross_pool_vol": trial.suggest_categorical("cross_pool_vol", [True, False]), + "cross_pool_momentum": trial.suggest_categorical("cross_pool_momentum", [True, False]), + "x_obs_mode": trial.suggest_categorical("x_obs_mode", ["none", "reduced", "cross"]), + } + hparams = { + "hidden": trial.suggest_categorical("hidden", [16, 32, 64]), + "d_embed": trial.suggest_categorical("d_embed", [4, 8, 16]), + "lr": trial.suggest_float("lr", 3e-4, 3e-3, log=True), + "l2_alpha": trial.suggest_float("l2_alpha", 1e-5, 1e-2, log=True), + "n_epochs": trial.suggest_categorical("n_epochs", [500, 1000, 2000]), + "encoder_type": trial.suggest_categorical("encoder_type", ["mlp", "linear"]), + "huber_delta": trial.suggest_categorical("huber_delta", [0.5, 1.0, 1.5]), + "no_peers": trial.suggest_categorical("no_peers", [True, False]), + } + + no_peers = hparams["no_peers"] + train_inputs = assemble_inputs(train_data, feat_cfg) + eval_inputs = assemble_inputs(eval_data, feat_cfg) + + params = init_params( + jax.random.PRNGKey(42), + train_inputs["n_peer_feat"], train_inputs["n_local_feat"], + hparams["hidden"], hparams["d_embed"], + encoder_type=hparams["encoder_type"], + ) + # Learnable cadence from Option C init + params["log_cadence"] = jnp.array(data["init_log_cadences"]) + + # Warm-start decoder on noise residual + ws_inputs = dict(train_inputs) + ws_inputs["y"] = train_inputs["y_total"] - jnp.log( + jnp.maximum(train_inputs["v_arb"], 1e-6)) + params = warm_start_decoder(params, ws_inputs, hparams["d_embed"]) + + grad_fn = _get_grad_fn(no_peers) + params, _ = train( + params, train_inputs, hparams["n_epochs"], + hparams["lr"], hparams["l2_alpha"], + huber_delta=hparams["huber_delta"], no_peers=no_peers, + verbose=False, grad_fn_override=grad_fn, + ) + + # Eval: compute V_arb at learned cadences, combine with net + log_v_noise = np.array(forward( + params, eval_inputs["peer_input"], eval_inputs["peer_mask"], + eval_inputs["local_input"], no_peers=no_peers, + )) + y_total = np.array(eval_inputs["y_total"]) + pool_idx = np.array(eval_data["pool_idx"]) + sample_grid_days = np.array(eval_inputs["sample_grid_days"]) + log_cadence = np.array(params["log_cadence"]) + + v_arb = np.zeros(len(y_total)) + for i in range(n_pools): + mask = pool_idx == i + if not mask.any(): + continue + v_arb_all = np.array(interpolate_pool_daily( + pool_coeffs[i], jnp.float64(log_cadence[i]), pool_gas[i])) + v_arb[mask] = v_arb_all[sample_grid_days[mask]] + + log_v_arb = np.log(np.maximum(v_arb, 1e-10)) + pred_total = np.logaddexp(log_v_arb, log_v_noise) + + r2_totals = [] + for i in range(n_pools): + mask = pool_idx == i + if mask.sum() < 2: + continue + yt = y_total[mask] + pt = pred_total[mask] + ss_res = np.sum((yt - pt) ** 2) + ss_tot = np.sum((yt - yt.mean()) ** 2) + r2_totals.append(1 - ss_res / max(ss_tot, 1e-10)) + + med_total = float(np.median(r2_totals)) if r2_totals else -10.0 + + cads = np.exp(log_cadence) + trial.set_user_attr("med_total_r2", med_total) + trial.set_user_attr("cad_median", float(np.median(cads))) + n_feat = sum(1 for k in _FEAT_KEYS if feat_cfg.get(k)) + print(f" Trial {trial.number}: total={med_total:.4f} " + f"enc={hparams['encoder_type']} h={hparams['hidden']} d={hparams['d_embed']} " + f"hub={hparams['huber_delta']} " + f"{'no_peers ' if no_peers else ''}" + f"lr={hparams['lr']:.1e} a={hparams['l2_alpha']:.1e} " + f"ep={hparams['n_epochs']} feat={n_feat}/15" + f" cad=[{cads.min():.0f}-{np.median(cads):.0f}-{cads.max():.0f}]") + + return med_total + + study = optuna.create_study(direction="maximize") + study.optimize(objective, n_trials=n_trials) + + print(f"\n{'='*70}") + print("Optuna Results (learn_cadence)") + print(f"{'='*70}") + print(f" Best eval total R²: {study.best_value:.4f}") + print(f" Best params:") + for k, v in sorted(study.best_params.items()): + print(f" {k}: {v}") + + print(f"\n Top 10:") + trials = sorted(study.trials, key=lambda t: t.value if t.value else -999, + reverse=True) + for t in trials[:10]: + if t.value is not None: + feats = sum(1 for k in _FEAT_KEYS if t.params.get(k)) + cad_med = t.user_attrs.get("cad_median", "?") + print(f" #{t.number}: total={t.value:.4f} " + f"enc={t.params['encoder_type']} " + f"h={t.params['hidden']} d={t.params['d_embed']} " + f"feat={feats}/15 cad_med={cad_med:.0f}") + + return study + + +# ---- Main ---- + + +def main(): + parser = argparse.ArgumentParser() + parser.add_argument("--tune", type=int, default=0) + parser.add_argument("--loo", action="store_true") + # Architecture + parser.add_argument("--hidden", type=int, default=16) + parser.add_argument("--d-embed", type=int, default=8) + parser.add_argument("--lr", type=float, default=3e-4) + parser.add_argument("--l2-alpha", type=float, default=1e-3) + parser.add_argument("--epochs", type=int, default=1000) + parser.add_argument("--encoder-type", choices=["mlp", "linear"], default="mlp") + parser.add_argument("--huber-delta", type=float, default=1.0) + parser.add_argument("--no-peers", action="store_true", + help="Decoder-only ablation (zero peer summary)") + parser.add_argument("--learn-cadence", action="store_true", + help="Jointly optimize per-pool arb cadence via PCHIP") + parser.add_argument("--x-obs", choices=["none", "reduced", "cross"], + default="none", + help="Append Option C x_obs covariates to decoder: " + "none, reduced (4: intercept,tvl,dow), " + "cross (7: +peer volumes)") + parser.add_argument("--minimal-encoder", action="store_true", + help="7-feature encoder (fee, tvl, overlap, same_chain) " + "instead of full attributes") + parser.add_argument("--target-residual", action="store_true", + help="Train on noise residual (log_vol - log_V_arb) " + "instead of total log_volume") + # Feature flags + parser.add_argument("--peer-vol-lag2", action="store_true") + parser.add_argument("--peer-vol-change", action="store_true") + parser.add_argument("--peer-tvl", action="store_true") + parser.add_argument("--peer-volatility", action="store_true") + parser.add_argument("--own-vol-lag2", action="store_true") + parser.add_argument("--own-vol-change", action="store_true") + parser.add_argument("--own-tvl", action="store_true") + parser.add_argument("--own-volatility", action="store_true") + parser.add_argument("--interactions", action="store_true", + help="tvl×vola, tvl×fee, vola×fee interaction terms") + parser.add_argument("--cross-pool-vol", action="store_true", + help="Token-peer, chain-peer, market volume aggregates") + parser.add_argument("--cross-pool-momentum", action="store_true", + help="Peer volume change momentum features") + parser.add_argument("--all-features", action="store_true", + help="Enable all optional features") + args = parser.parse_args() + + os.environ.setdefault("JAX_PLATFORMS", "cpu") + + feat_cfg = { + "peer_vol_lag2": args.peer_vol_lag2 or args.all_features, + "peer_vol_change": args.peer_vol_change or args.all_features, + "peer_tvl": args.peer_tvl or args.all_features, + "peer_volatility": args.peer_volatility or args.all_features, + "own_vol_lag2": args.own_vol_lag2 or args.all_features, + "own_vol_change": args.own_vol_change or args.all_features, + "own_tvl": args.own_tvl or args.all_features, + "own_volatility": args.own_volatility or args.all_features, + # Relational features: always on for CLI, searchable in Optuna + "rel_same_chain": True, + "rel_tvl_ratio": True, + "rel_fee_ratio": True, + "interactions": args.interactions or args.all_features, + "cross_pool_vol": args.cross_pool_vol or args.all_features, + "cross_pool_momentum": args.cross_pool_momentum or args.all_features, + "minimal_encoder": args.minimal_encoder, + "target_residual": args.target_residual, + "x_obs_mode": args.x_obs, + } + hparams = { + "hidden": args.hidden, + "d_embed": args.d_embed, + "lr": args.lr, + "l2_alpha": args.l2_alpha, + "n_epochs": args.epochs, + "encoder_type": args.encoder_type, + "huber_delta": args.huber_delta, + "no_peers": args.no_peers, + "learn_cadence": args.learn_cadence, + } + + print("=" * 70) + print("DeepSets v2: Total Volume Target + Noise Residual Eval") + feat_on = [k for k, v in feat_cfg.items() if v] + print(f" Optional features: {feat_on or 'none'}") + print(f" Architecture: {hparams}") + print("=" * 70) + + matched_clean, option_c_clean = load_stage1() + + print("\nBuilding features...") + t0 = time.time() + data = build_all_features(matched_clean, option_c_clean) + print(f" {len(data['pool_idx'])} samples, {data['n_pools']} pools, " + f"{time.time() - t0:.1f}s") + + if args.loo: + print(f"\n{'='*70}") + print("Leave-One-Pool-Out Cross-Validation") + print(f"{'='*70}") + run_loo(data, feat_cfg, hparams) + elif args.tune > 0 and args.learn_cadence: + run_optuna_cadence(data, args.tune) + elif args.tune > 0: + run_optuna(data, args.tune, target_residual=args.target_residual) + else: + print(f"\n{'='*70}") + print("Temporal split (70/30)") + print(f"{'='*70}") + run_temporal(data, feat_cfg, hparams) + + print(f"\n Baselines for comparison:") + print(f" Option C on residual: median R² = 0.060") + print(f" Ridge+own on residual: median R² = 0.098") + print(f" Constant zero: median R² = -0.083") + + +if __name__ == "__main__": + main() diff --git a/experiments/run_deepsets_volume.py b/experiments/run_deepsets_volume.py new file mode 100644 index 0000000..dc174d2 --- /dev/null +++ b/experiments/run_deepsets_volume.py @@ -0,0 +1,467 @@ +"""DeepSets cross-pool volume prediction. + +Architecture: + For pool i at day t: + For each peer j != i with valid data at t-1: + h_j = Encoder(attr_j, attr_i, vol_j_{t-1}, overlap_ij) + peer_summary = masked_mean(h_j) + pred_i_t = Decoder(peer_summary, attr_i, own_vol_{t-1}) + +Evaluation: + 1. In-sample R² (all data) + 2. Temporal split (70/30) + 3. LOO (hold out one pool, retrain, evaluate) +""" + +import os +import pickle +import sys +import time + +import jax +import jax.numpy as jnp +import numpy as np + +CACHE_DIR = os.path.join( + os.path.dirname(os.path.dirname(__file__)), + "results", "token_factored_calibration", "_cache", +) +HIDDEN = 8 +D_EMBED = 4 +LR = 1e-3 +N_EPOCHS = 500 +N_EPOCHS_LOO = 200 +L2_ALPHA = 0.001 + + +def load_stage1(): + path = os.path.join(CACHE_DIR, "stage1.pkl") + if not os.path.exists(path): + print("ERROR: no stage1 cache.") + sys.exit(1) + with open(path, "rb") as f: + data = pickle.load(f) + return data["matched_clean"], data["option_c_clean"] + + +def build_volume_matrix(matched_clean): + """Build (n_dates, n_pools) volume matrix. NaN where missing.""" + pool_ids = sorted(matched_clean.keys()) + pool_date_vol = {} + all_dates = set() + for pid in pool_ids: + panel = matched_clean[pid]["panel"] + dates = panel["date"].values + vols = panel["log_volume"].values.astype(float) + pool_date_vol[pid] = dict(zip(dates, vols)) + all_dates.update(dates) + + date_list = sorted(all_dates) + n_dates = len(date_list) + n_pools = len(pool_ids) + vol_matrix = np.full((n_dates, n_pools), np.nan) + for j, pid in enumerate(pool_ids): + dv = pool_date_vol[pid] + for t, date in enumerate(date_list): + if date in dv: + vol_matrix[t, j] = dv[date] + return vol_matrix, date_list, pool_ids + + +def build_data(matched_clean, exclude_pool_idx=None): + """Build all arrays for DeepSets training. + + If exclude_pool_idx is set, that pool is excluded from training + samples but kept as a peer (its volume data is still available). + """ + from quantammsim.calibration.pool_data import ( + build_pool_attributes, _parse_tokens, _canonicalize_token, + ) + + pool_ids = sorted(matched_clean.keys()) + n_pools = len(pool_ids) + + vol_matrix, date_list, _ = build_volume_matrix(matched_clean) + X_attr, attr_names, _ = build_pool_attributes(matched_clean) + + # Standardize attributes + attr_mean = np.mean(X_attr, axis=0) + attr_std = np.std(X_attr, axis=0) + attr_std[attr_std < 1e-6] = 1.0 + X_attr_norm = ((X_attr - attr_mean) / attr_std).astype(np.float32) + + # Standardize volumes + vol_mean = float(np.nanmean(vol_matrix)) + vol_std = float(np.nanstd(vol_matrix)) + vol_norm = ((vol_matrix - vol_mean) / vol_std).astype(np.float32) + + # Token overlap + k_attr = X_attr_norm.shape[1] + pool_tokens = {} + for i, pid in enumerate(pool_ids): + toks = _parse_tokens(matched_clean[pid]["tokens"]) + pool_tokens[i] = {_canonicalize_token(t) for t in toks[:2]} + + # Per-pool peer structures + n_peers = n_pools - 1 + peer_attrs = np.zeros((n_pools, n_peers, k_attr), dtype=np.float32) + peer_overlap = np.zeros((n_pools, n_peers), dtype=np.float32) + peer_col_idx = np.zeros((n_pools, n_peers), dtype=np.int32) + + for i in range(n_pools): + peers = [j for j in range(n_pools) if j != i] + for p, j in enumerate(peers): + peer_attrs[i, p] = X_attr_norm[j] + peer_overlap[i, p] = len(pool_tokens[i] & pool_tokens[j]) + peer_col_idx[i, p] = j + + target_attrs = X_attr_norm + + # Build samples + sample_pools, sample_days = [], [] + for i in range(n_pools): + if i == exclude_pool_idx: + continue + for t in range(1, len(date_list)): + if np.isnan(vol_matrix[t, i]) or np.isnan(vol_matrix[t - 1, i]): + continue + sample_pools.append(i) + sample_days.append(t) + + sample_pools = np.array(sample_pools, dtype=np.int32) + sample_days = np.array(sample_days, dtype=np.int32) + n_samples = len(sample_pools) + + # Vectorized: gather peer volumes and masks + peer_vols = np.zeros((n_samples, n_peers), dtype=np.float32) + peer_mask = np.zeros((n_samples, n_peers), dtype=np.float32) + own_lag = np.zeros(n_samples, dtype=np.float32) + y = np.zeros(n_samples, dtype=np.float32) + + for s in range(n_samples): + i = sample_pools[s] + t = sample_days[s] + cols = peer_col_idx[i] + pvols = vol_norm[t - 1, cols] + valid = ~np.isnan(pvols) + peer_vols[s] = np.where(valid, pvols, 0.0) + peer_mask[s] = valid.astype(np.float32) + own_lag[s] = vol_norm[t - 1, i] + y[s] = vol_norm[t, i] + + return { + "peer_attrs": jnp.array(peer_attrs), + "target_attrs": jnp.array(target_attrs), + "peer_overlap": jnp.array(peer_overlap), + "peer_vols": jnp.array(peer_vols), + "peer_mask": jnp.array(peer_mask), + "own_lag": jnp.array(own_lag), + "y": jnp.array(y), + "pool_idx": jnp.array(sample_pools), + "day_idx": sample_days, + "n_pools": n_pools, + "n_peers": n_peers, + "k_attr": k_attr, + "pool_ids": pool_ids, + "vol_mean": vol_mean, + "vol_std": vol_std, + } + + +# ---- Model ---- + + +def init_params(key, k_attr, hidden=HIDDEN, d=D_EMBED): + k1, k2, k3, k4 = jax.random.split(key, 4) + enc_in = 2 * k_attr + 2 # peer_attr + target_attr + peer_vol + overlap + dec_in = d + k_attr + 1 # summary + target_attr + own_lag + return { + "enc_W1": jax.random.normal(k1, (enc_in, hidden)) * np.sqrt(2.0 / enc_in), + "enc_b1": jnp.zeros(hidden), + "enc_W2": jax.random.normal(k2, (hidden, d)) * np.sqrt(2.0 / hidden), + "enc_b2": jnp.zeros(d), + "dec_W1": jax.random.normal(k3, (dec_in, hidden)) * np.sqrt(2.0 / dec_in), + "dec_b1": jnp.zeros(hidden), + "dec_W2": jax.random.normal(k4, (hidden, 1)) * 0.01, + "dec_b2": jnp.zeros(1), + } + + +def forward(params, peer_attrs_all, target_attrs_all, peer_overlap_all, + peer_vols, peer_mask, own_lag, pool_idx): + """Batched DeepSets forward pass.""" + batch = peer_vols.shape[0] + n_peers = peer_vols.shape[1] + + pa = peer_attrs_all[pool_idx] # (batch, n_peers, k_attr) + ta = target_attrs_all[pool_idx] # (batch, k_attr) + ov = peer_overlap_all[pool_idx] # (batch, n_peers) + + ta_broad = jnp.broadcast_to(ta[:, None, :], pa.shape) + + enc_in = jnp.concatenate([ + pa, ta_broad, + peer_vols[:, :, None], + ov[:, :, None], + ], axis=-1) + + # Encoder MLP + flat = enc_in.reshape(-1, enc_in.shape[-1]) + h = jnp.maximum(flat @ params["enc_W1"] + params["enc_b1"], 0.0) + h = h @ params["enc_W2"] + params["enc_b2"] + h = h.reshape(batch, n_peers, -1) + + # Masked mean + h_masked = h * peer_mask[:, :, None] + n_valid = jnp.maximum(jnp.sum(peer_mask, axis=1, keepdims=True), 1.0) + summary = jnp.sum(h_masked, axis=1) / n_valid + + # Decoder MLP + dec_in = jnp.concatenate([summary, ta, own_lag[:, None]], axis=-1) + h_dec = jnp.maximum(dec_in @ params["dec_W1"] + params["dec_b1"], 0.0) + return (h_dec @ params["dec_W2"] + params["dec_b2"])[:, 0] + + +def loss_fn(params, static, peer_vols, peer_mask, own_lag, pool_idx, y, alpha): + pred = forward(params, static["peer_attrs"], static["target_attrs"], + static["peer_overlap"], peer_vols, peer_mask, own_lag, pool_idx) + mse = jnp.mean((pred - y) ** 2) + reg = sum(jnp.sum(v ** 2) for k, v in params.items() if "W" in k) + return mse + alpha * reg + + +grad_fn = jax.jit(jax.value_and_grad(loss_fn)) + + +# ---- Training ---- + + +def train(params, data, n_epochs=N_EPOCHS, lr=LR, alpha=L2_ALPHA, verbose=True): + """Full-batch Adam training.""" + static = { + "peer_attrs": data["peer_attrs"], + "target_attrs": data["target_attrs"], + "peer_overlap": data["peer_overlap"], + } + + # Adam state + m = {k: jnp.zeros_like(v) for k, v in params.items()} + v = {k: jnp.zeros_like(v) for k, v in params.items()} + + for epoch in range(n_epochs): + loss_val, grads = grad_fn( + params, static, data["peer_vols"], data["peer_mask"], + data["own_lag"], data["pool_idx"], data["y"], alpha, + ) + + # Adam update + for k in params: + m[k] = 0.9 * m[k] + 0.1 * grads[k] + v[k] = 0.999 * v[k] + 0.001 * grads[k] ** 2 + m_hat = m[k] / (1.0 - 0.9 ** (epoch + 1)) + v_hat = v[k] / (1.0 - 0.999 ** (epoch + 1)) + params[k] = params[k] - lr * m_hat / (jnp.sqrt(v_hat) + 1e-8) + + if verbose and (epoch % 100 == 0 or epoch == n_epochs - 1): + print(f" epoch {epoch:4d} loss={float(loss_val):.6f}") + + return params + + +# ---- Evaluation ---- + + +def per_pool_r2(params, data): + """Compute per-pool R² from trained model.""" + static = { + "peer_attrs": data["peer_attrs"], + "target_attrs": data["target_attrs"], + "peer_overlap": data["peer_overlap"], + } + pred = np.array(forward( + params, static["peer_attrs"], static["target_attrs"], + static["peer_overlap"], data["peer_vols"], data["peer_mask"], + data["own_lag"], data["pool_idx"], + )) + y = np.array(data["y"]) + pool_idx = np.array(data["pool_idx"]) + + r2s = {} + for i in range(data["n_pools"]): + mask = pool_idx == i + if mask.sum() < 2: + continue + yi = y[mask] + pi = pred[mask] + ss_res = np.sum((yi - pi) ** 2) + ss_tot = np.sum((yi - yi.mean()) ** 2) + r2s[i] = 1 - ss_res / max(ss_tot, 1e-10) + return r2s + + +# ---- Main experiments ---- + + +def run_insample(matched_clean): + print("\n" + "=" * 70) + print("1. In-sample DeepSets") + print("=" * 70) + + data = build_data(matched_clean) + n_params = sum(v.size for v in init_params(jax.random.PRNGKey(0), data["k_attr"]).values()) + print(f" {data['peer_vols'].shape[0]} samples, {data['n_pools']} pools, " + f"{data['k_attr']} attrs, {n_params} params") + + params = init_params(jax.random.PRNGKey(42), data["k_attr"]) + t0 = time.time() + params = train(params, data) + print(f" Training: {time.time() - t0:.1f}s") + + r2s = per_pool_r2(params, data) + pool_ids = data["pool_ids"] + for i, pid in enumerate(pool_ids): + if i in r2s: + print(f" {pid[:16]} ({matched_clean[pid]['tokens']:<14}) R²={r2s[i]:.3f}") + + vals = list(r2s.values()) + print(f"\n In-sample: median R²={np.median(vals):.4f}, mean={np.mean(vals):.4f}") + return params, data, r2s + + +def run_temporal_split(matched_clean, split_frac=0.7): + print("\n" + "=" * 70) + print(f"2. Temporal split ({int(split_frac*100)}/{int((1-split_frac)*100)})") + print("=" * 70) + + data_all = build_data(matched_clean) + day_idx = np.array(data_all["day_idx"]) + max_day = day_idx.max() + split_day = int(max_day * split_frac) + + train_mask = day_idx <= split_day + eval_mask = day_idx > split_day + + def subset(data, mask): + jmask = jnp.array(mask) + return { + **{k: data[k] for k in ["peer_attrs", "target_attrs", "peer_overlap", + "n_pools", "n_peers", "k_attr", "pool_ids", + "vol_mean", "vol_std"]}, + "peer_vols": data["peer_vols"][jmask], + "peer_mask": data["peer_mask"][jmask], + "own_lag": data["own_lag"][jmask], + "y": data["y"][jmask], + "pool_idx": data["pool_idx"][jmask], + "day_idx": data_all["day_idx"][mask], + } + + train_data = subset(data_all, train_mask) + eval_data = subset(data_all, eval_mask) + + print(f" Train: {int(train_mask.sum())} samples, Eval: {int(eval_mask.sum())} samples") + + params = init_params(jax.random.PRNGKey(42), data_all["k_attr"]) + params = train(params, train_data) + + r2s_train = per_pool_r2(params, train_data) + r2s_eval = per_pool_r2(params, eval_data) + + pool_ids = data_all["pool_ids"] + for i, pid in enumerate(pool_ids): + r_tr = r2s_train.get(i, float("nan")) + r_ev = r2s_eval.get(i, float("nan")) + print(f" {pid[:16]} ({matched_clean[pid]['tokens']:<14}) " + f"train={r_tr:.3f} eval={r_ev:.3f}") + + vals_eval = [v for v in r2s_eval.values() if np.isfinite(v)] + print(f"\n Temporal eval: median R²={np.median(vals_eval):.4f}, " + f"mean={np.mean(vals_eval):.4f}") + return r2s_eval + + +def run_loo(matched_clean): + print("\n" + "=" * 70) + print("3. LOO DeepSets") + print("=" * 70) + + pool_ids = sorted(matched_clean.keys()) + n_pools = len(pool_ids) + loo_r2s = [] + + for hold_out_idx in range(n_pools): + hold_out_pid = pool_ids[hold_out_idx] + + # Build training data excluding held-out pool's samples + # (but keeping its volume data for peers) + train_data = build_data(matched_clean, exclude_pool_idx=hold_out_idx) + + params = init_params(jax.random.PRNGKey(42), train_data["k_attr"]) + params = train(params, train_data, n_epochs=N_EPOCHS_LOO, verbose=False) + + # Build eval data: only held-out pool's samples + eval_data = build_data(matched_clean) + ho_mask = np.array(eval_data["pool_idx"]) == hold_out_idx + if ho_mask.sum() < 2: + loo_r2s.append(float("nan")) + continue + + jmask = jnp.array(ho_mask) + eval_sub = { + **{k: eval_data[k] for k in ["peer_attrs", "target_attrs", "peer_overlap", + "n_pools", "n_peers", "k_attr", "pool_ids", + "vol_mean", "vol_std"]}, + "peer_vols": eval_data["peer_vols"][jmask], + "peer_mask": eval_data["peer_mask"][jmask], + "own_lag": eval_data["own_lag"][jmask], + "y": eval_data["y"][jmask], + "pool_idx": eval_data["pool_idx"][jmask], + "day_idx": np.array(eval_data["day_idx"])[ho_mask], + } + + r2s = per_pool_r2(params, eval_sub) + r2 = r2s.get(hold_out_idx, float("nan")) + loo_r2s.append(r2) + + tag = "OK" if r2 > 0 else "NEG" + print(f" {hold_out_pid[:16]} ({matched_clean[hold_out_pid]['tokens']:<14}) " + f"R²={r2:.3f} [{tag}]") + + valid = [r for r in loo_r2s if np.isfinite(r)] + print(f"\n LOO DeepSets: median R²={np.median(valid):.4f}, " + f"mean={np.mean(valid):.4f}, " + f"n_neg={sum(1 for r in valid if r < 0)}") + return loo_r2s + + +def main(): + os.environ.setdefault("JAX_PLATFORMS", "cpu") + + print("=" * 70) + print("DeepSets Cross-Pool Volume Prediction") + print(f" hidden={HIDDEN}, d={D_EMBED}, lr={LR}, " + f"alpha={L2_ALPHA}, epochs={N_EPOCHS}") + print("=" * 70) + + matched_clean, _ = load_stage1() + + params, data, r2_insample = run_insample(matched_clean) + r2_temporal = run_temporal_split(matched_clean) + r2_loo = run_loo(matched_clean) + + print("\n" + "=" * 70) + print("SUMMARY") + print("=" * 70) + vals_in = list(r2_insample.values()) + vals_temp = [v for v in r2_temporal.values() if np.isfinite(v)] + vals_loo = [r for r in r2_loo if np.isfinite(r)] + print(f" DeepSets in-sample: median R² = {np.median(vals_in):.4f}") + print(f" DeepSets temporal (30%): median R² = {np.median(vals_temp):.4f}") + print(f" DeepSets LOO: median R² = {np.median(vals_loo):.4f}") + print(f" ---") + print(f" Ridge in-sample: median R² = 0.441") + print(f" Naive AR1: median R² = 0.397") + print(f" Token-factored LOO: median R² = 0.362") + + +if __name__ == "__main__": + main() diff --git a/experiments/run_hybrid_noise.py b/experiments/run_hybrid_noise.py new file mode 100644 index 0000000..d815a5c --- /dev/null +++ b/experiments/run_hybrid_noise.py @@ -0,0 +1,814 @@ +"""Hybrid noise model: DeepSets peer encoder + linear noise model. + +Architecture: + peer_effect = DeepSets_encoder(peer_data, current_pool_attrs) → scalar + log(V_noise) = [x_obs, market, peer_effect, peer_effect×tvl, ...] @ coeffs + V_total = V_arb(cadence) + exp(log_v_noise) + +The encoder learns how to aggregate peer information. The linear model +learns how that aggregate (plus market/pool features) drives noise volume. +Cadence is learnable per-pool via PCHIP. + +Usage: + python experiments/run_hybrid_noise.py + python experiments/run_hybrid_noise.py --encoder-hidden 16 --epochs 2000 + python experiments/run_hybrid_noise.py --n-peer-outputs 3 # multi-dim peer effect +""" + +import argparse +import os +import pickle +import sys +import time + +import jax +import jax.numpy as jnp +import numpy as np + +CACHE_DIR = os.path.join( + os.path.dirname(os.path.dirname(__file__)), + "results", "token_factored_calibration", "_cache", +) + + +def load_stage1(): + path = os.path.join(CACHE_DIR, "stage1.pkl") + if not os.path.exists(path): + print("ERROR: no stage1 cache.") + sys.exit(1) + with open(path, "rb") as f: + data = pickle.load(f) + return data["matched_clean"], data["option_c_clean"] + + +def build_data(matched_clean, option_c_clean, trend_windows=(7, 14, 30)): + """Build all features: x_obs, market, peer encoder inputs.""" + from quantammsim.calibration.grid_interpolation import interpolate_pool_daily + from quantammsim.calibration.pool_data import ( + build_x_obs, build_cross_pool_x_obs, build_pool_attributes, + _parse_tokens, _canonicalize_token, K_OBS_CROSS, + ) + from quantammsim.calibration.market_features import ( + build_pool_market_features, pool_market_features_to_matrix, + ) + + pool_ids = sorted(matched_clean.keys()) + n_pools = len(pool_ids) + + # Common date grid + all_dates = set() + for pid in pool_ids: + all_dates.update(matched_clean[pid]["panel"]["date"].values) + date_list = sorted(all_dates) + n_dates = len(date_list) + date_to_idx = {d: i for i, d in enumerate(date_list)} + + # Volume matrix and per-pool metadata + vol_matrix = np.full((n_dates, n_pools), np.nan) + pool_coeffs = [] + pool_gas = [] + init_log_cadences = np.zeros(n_pools, dtype=np.float32) + common_to_grid = np.full((n_pools, n_dates), 0, dtype=np.int32) + + for j, pid in enumerate(pool_ids): + entry = matched_clean[pid] + oc = option_c_clean[pid] + pool_coeffs.append(entry["coeffs"]) + pool_gas.append(jnp.float64(np.exp(oc["log_gas"]))) + init_log_cadences[j] = oc["log_cadence"] + dates = entry["panel"]["date"].values + log_vols = entry["panel"]["log_volume"].values.astype(float) + for k, date in enumerate(dates): + t = date_to_idx[date] + vol_matrix[t, j] = log_vols[k] + common_to_grid[j, t] = entry["day_indices"][k] + + # Pool attributes (static, normalized) + X_attr, attr_names, _ = build_pool_attributes(matched_clean) + attr_mean = np.mean(X_attr, axis=0) + attr_std = np.std(X_attr, axis=0) + attr_std[attr_std < 1e-6] = 1.0 + X_attr_norm = ((X_attr - attr_mean) / attr_std).astype(np.float32) + k_attr = X_attr_norm.shape[1] + + # Token overlap matrix + pool_tokens = {} + for i, pid in enumerate(pool_ids): + toks = _parse_tokens(matched_clean[pid]["tokens"]) + pool_tokens[i] = {_canonicalize_token(t) for t in toks[:2]} + + overlap = np.zeros((n_pools, n_pools), dtype=np.float32) + for i in range(n_pools): + for j in range(n_pools): + if i != j: + overlap[i, j] = len(pool_tokens[i] & pool_tokens[j]) + + # Peer index mapping: for pool i, peers are all j != i + n_peers = n_pools - 1 + peer_idx = np.zeros((n_pools, n_peers), dtype=np.int32) + peer_overlap = np.zeros((n_pools, n_peers), dtype=np.float32) + for i in range(n_pools): + peers = [j for j in range(n_pools) if j != i] + peer_idx[i] = peers + peer_overlap[i] = overlap[i, peers] + + # Build samples + sample_pools, sample_days = [], [] + for i in range(n_pools): + for t in range(1, n_dates): + if np.isnan(vol_matrix[t, i]) or np.isnan(vol_matrix[t - 1, i]): + continue + sample_pools.append(i) + sample_days.append(t) + sample_pools = np.array(sample_pools, dtype=np.int32) + sample_days = np.array(sample_days, dtype=np.int32) + n_samples = len(sample_pools) + + # ---- x_obs (cross-pool, 7 features) ---- + x_obs_grid = np.full((n_dates, n_pools, K_OBS_CROSS), np.nan) + for j, pid in enumerate(pool_ids): + panel = matched_clean[pid]["panel"] + xc = build_cross_pool_x_obs(panel, matched_clean, pid) + dates_j = panel["date"].values + for k, date in enumerate(dates_j[1:]): + x_obs_grid[date_to_idx[date], j] = xc[k] + + x_obs = np.zeros((n_samples, K_OBS_CROSS), dtype=np.float32) + for s in range(n_samples): + xval = x_obs_grid[sample_days[s], sample_pools[s]] + if np.all(np.isfinite(xval)): + x_obs[s] = xval + + # ---- Market features ---- + print(" Building market features...") + pool_feat = build_pool_market_features( + matched_clean, trend_windows=list(trend_windows)) + x_market, market_names = pool_market_features_to_matrix( + pool_feat, matched_clean, date_to_idx, pool_ids, + sample_pools, sample_days) + print(f" Market features: {len(market_names)} columns") + + # ---- Peer encoder inputs: (n_samples, n_peers, n_peer_feat) ---- + # Per peer: [peer_attrs, target_attrs, peer_vol_lag1, overlap] + # peer_vol_lag1 is the peer's volume at t-1 + vol_mean = float(np.nanmean(vol_matrix)) + vol_std = max(float(np.nanstd(vol_matrix)), 1e-6) + + # Static peer features (per pool) + peer_attrs = np.zeros((n_pools, n_peers, k_attr), dtype=np.float32) + for i in range(n_pools): + peer_attrs[i] = X_attr_norm[peer_idx[i]] + + # Per-sample peer features + peer_vol_lag1 = np.zeros((n_samples, n_peers), dtype=np.float32) + peer_mask = np.zeros((n_samples, n_peers), dtype=np.float32) + + for s in range(n_samples): + i = sample_pools[s] + t = sample_days[s] + cols = peer_idx[i] + pvols = vol_matrix[t - 1, cols] + valid = ~np.isnan(pvols) + peer_mask[s] = valid.astype(np.float32) + peer_vol_lag1[s] = np.where(valid, (pvols - vol_mean) / vol_std, 0.0) + + # Assemble peer encoder input: (n_samples, n_peers, n_peer_feat) + # [peer_attrs(k_attr), target_attrs(k_attr), vol_lag1(1), overlap(1)] + target_attrs_broad = np.broadcast_to( + X_attr_norm[sample_pools][:, None, :], + (n_samples, n_peers, k_attr)) + peer_input = np.concatenate([ + peer_attrs[sample_pools], # (n_samples, n_peers, k_attr) + target_attrs_broad, # (n_samples, n_peers, k_attr) + peer_vol_lag1[:, :, None], # (n_samples, n_peers, 1) + peer_overlap[sample_pools][:, :, None], # (n_samples, n_peers, 1) + ], axis=-1).astype(np.float32) + n_peer_feat = peer_input.shape[-1] + + # Combine linear features (x_obs + market) + x_base = np.concatenate([x_obs, x_market], axis=1).astype(np.float32) + base_names = [f"xobs_{i}" for i in range(K_OBS_CROSS)] + market_names + + # Standardize base features (except intercept) + x_mean = np.mean(x_base, axis=0) + x_std_arr = np.std(x_base, axis=0) + x_std_arr[x_std_arr < 1e-6] = 1.0 + x_mean[0] = 0.0 + x_std_arr[0] = 1.0 + x_base = ((x_base - x_mean) / x_std_arr).astype(np.float32) + + # Build named column index for interaction construction + col_idx = {name: i for i, name in enumerate(base_names)} + + # Interaction terms (products of standardized features) + interactions = [] + interaction_names = [] + + def _add_interaction(name_a, name_b): + if name_a in col_idx and name_b in col_idx: + interactions.append( + x_base[:, col_idx[name_a]] * x_base[:, col_idx[name_b]]) + interaction_names.append(f"{name_a}×{name_b}") + + # TVL × volatility: deep pools respond differently to market stress + _add_interaction("xobs_1", "btc_realized_vol_7d") # tvl × btc vol + _add_interaction("xobs_1", "tok_a_realized_vol_7d") # tvl × tok_a vol + _add_interaction("xobs_1", "pair_realized_vol_7d") # tvl × pair vol + + # Cross-token volatility interaction: both tokens moving = pair activity + _add_interaction("tok_a_realized_vol_7d", "tok_b_realized_vol_7d") + + if interactions: + x_interactions = np.column_stack(interactions).astype(np.float32) + x_linear = np.concatenate([x_base, x_interactions], axis=1) + linear_names = base_names + interaction_names + else: + x_linear = x_base + linear_names = base_names + + # Track which columns are tvl and btc_vol for peer_effect interactions in loss + tvl_col = col_idx.get("xobs_1", 1) + btc_vol_col = col_idx.get("btc_realized_vol_7d") + + # Targets and indices + y_total = np.array([vol_matrix[sample_days[s], sample_pools[s]] + for s in range(n_samples)], dtype=np.float32) + sample_grid_days = common_to_grid[sample_pools, sample_days] + + return { + "x_linear": x_linear, # (n_samples, n_linear_feat) + "peer_input": peer_input, # (n_samples, n_peers, n_peer_feat) + "peer_mask": peer_mask, # (n_samples, n_peers) + "y_total": y_total, + "pool_idx": sample_pools, + "day_idx": sample_days, + "sample_grid_days": sample_grid_days, + "pool_coeffs": pool_coeffs, + "pool_gas": pool_gas, + "init_log_cadences": init_log_cadences, + "n_pools": n_pools, + "n_peers": n_peers, + "n_linear_feat": x_linear.shape[1], + "n_peer_feat": n_peer_feat, + "pool_ids": pool_ids, + "linear_names": linear_names, + "tvl_col": tvl_col, + "btc_vol_col": btc_vol_col, + } + + +# ---- Model ---- + +_SAMPLE_KEYS = { + "x_linear", "peer_input", "peer_mask", "y_total", + "pool_idx", "day_idx", "sample_grid_days", +} + + +def _subset(d, mask): + out = {} + for k, v in d.items(): + if k in _SAMPLE_KEYS and isinstance(v, np.ndarray): + out[k] = v[mask] + else: + out[k] = v + return out + + +def init_params(key, n_peer_feat, n_linear_feat, encoder_hidden, + n_peer_outputs, n_pools, init_log_cadences, + encoder_depth=1): + """Initialize all parameters. + + Encoder: peer_input → hidden (× depth) → n_peer_outputs (per peer, mean-pooled) + Linear: [x_linear, peer_outputs, peer×tvl, peer×btc_vol] @ coeffs + + encoder_depth: number of hidden layers (1-4). + params["enc_depth"] stores the depth as a scalar for forward_encoder. + """ + keys = jax.random.split(key, encoder_depth + 2) + + n_peer_linear = n_peer_outputs * 3 + n_total_linear = n_linear_feat + n_peer_linear + + params = {} + + # First layer: input → hidden + params["enc_W1"] = jax.random.normal(keys[0], (n_peer_feat, encoder_hidden)) * np.sqrt(2.0 / n_peer_feat) + params["enc_b1"] = jnp.zeros(encoder_hidden) + + # Hidden layers 2..depth: hidden → hidden + for d in range(2, encoder_depth + 1): + params[f"enc_W{d}"] = jax.random.normal(keys[d - 1], (encoder_hidden, encoder_hidden)) * np.sqrt(2.0 / encoder_hidden) + params[f"enc_b{d}"] = jnp.zeros(encoder_hidden) + + # Output layer: hidden → n_peer_outputs + out_idx = encoder_depth + 1 + params[f"enc_W{out_idx}"] = jax.random.normal(keys[-1], (encoder_hidden, n_peer_outputs)) * 0.01 + params[f"enc_b{out_idx}"] = jnp.zeros(n_peer_outputs) + + params["noise_coeffs"] = jnp.zeros(n_total_linear) + params["log_cadence"] = jnp.array(init_log_cadences) + + return params, n_total_linear + + +def forward_encoder(params, peer_input, peer_mask): + """DeepSets encoder: per-peer MLP → masked mean → scalar(s). + + Depth determined by counting enc_W* keys. + Returns (n_samples, n_peer_outputs). + """ + batch, n_peers, _ = peer_input.shape + flat = peer_input.reshape(-1, peer_input.shape[-1]) + + # Count layers: enc_W1, enc_W2, ..., enc_W{depth+1} + n_layers = sum(1 for k in params if k.startswith("enc_W")) + + # Hidden layers with ReLU + h = flat + for i in range(1, n_layers): + h = jnp.maximum(h @ params[f"enc_W{i}"] + params[f"enc_b{i}"], 0.0) + + # Output layer (no activation) + h = h @ params[f"enc_W{n_layers}"] + params[f"enc_b{n_layers}"] + + h = h.reshape(batch, n_peers, -1) + h_masked = h * peer_mask[:, :, None] + n_valid = jnp.maximum(jnp.sum(peer_mask, axis=1, keepdims=True), 1.0) + return jnp.sum(h_masked, axis=1) / n_valid + + +def make_loss_fn(pool_coeffs, pool_gas, n_pools, tvl_col, btc_vol_col): + """Loss with learnable cadence + encoder + linear noise model.""" + from quantammsim.calibration.grid_interpolation import interpolate_pool_daily + + def loss_fn(params, x_linear, peer_input, peer_mask, y_total, + sample_grid_days, pool_idx, l2_alpha, huber_delta): + + # Encoder → peer effect scalar(s) + peer_effect = forward_encoder(params, peer_input, peer_mask) + + # Build full linear input: [x_linear, peer, peer×tvl, peer×btc_vol] + tvl = x_linear[:, tvl_col:tvl_col + 1] + btc_vol = x_linear[:, btc_vol_col:btc_vol_col + 1] + peer_x_tvl = peer_effect * tvl + peer_x_btcvol = peer_effect * btc_vol + + x_full = jnp.concatenate( + [x_linear, peer_effect, peer_x_tvl, peer_x_btcvol], axis=1) + log_v_noise = x_full @ params["noise_coeffs"] + + # V_arb from PCHIP + log_cadence = params["log_cadence"] + n_samples = y_total.shape[0] + v_arb = jnp.zeros(n_samples) + for i in range(n_pools): + v_arb_all = interpolate_pool_daily( + pool_coeffs[i], log_cadence[i], pool_gas[i]) + safe_days = jnp.clip(sample_grid_days, 0, v_arb_all.shape[0] - 1) + v_arb = jnp.where(pool_idx == i, v_arb_all[safe_days], v_arb) + + log_v_arb = jnp.log(jnp.maximum(v_arb, 1e-10)) + log_v_total = jnp.logaddexp(log_v_arb, log_v_noise) + + # Huber loss with per-pool weighting + residuals = log_v_total - y_total + abs_r = jnp.abs(residuals) + huber_vals = jnp.where(abs_r <= huber_delta, 0.5 * residuals ** 2, + huber_delta * (abs_r - 0.5 * huber_delta)) + + pool_counts = jnp.zeros(n_pools).at[pool_idx].add( + jnp.ones_like(pool_idx, dtype=jnp.float32)) + active = (pool_counts > 0).astype(jnp.float32) + n_active = jnp.maximum(jnp.sum(active), 1.0) + pool_counts = jnp.maximum(pool_counts, 1.0) + pool_sums = jnp.zeros(n_pools).at[pool_idx].add(huber_vals) + data_loss = jnp.sum((pool_sums / pool_counts) * active) / n_active + + # L2 on all encoder weights + noise coeffs + reg = l2_alpha * ( + sum(jnp.sum(v ** 2) for k, v in params.items() if k.startswith("enc_W")) + + jnp.sum(params["noise_coeffs"] ** 2) + ) + return data_loss + reg + + return jax.jit(jax.value_and_grad(loss_fn)) + + +def train(params, data, grad_fn, n_epochs, lr, l2_alpha, huber_delta, + verbose=True): + m = {k: jnp.zeros_like(v) for k, v in params.items()} + v = {k: jnp.zeros_like(v) for k, v in params.items()} + + xl = jnp.array(data["x_linear"]) + pi = jnp.array(data["peer_input"]) + pm = jnp.array(data["peer_mask"]) + yt = jnp.array(data["y_total"]) + sgd = jnp.array(data["sample_grid_days"]) + pidx = jnp.array(data["pool_idx"]) + + for epoch in range(n_epochs): + loss_val, grads = grad_fn( + params, xl, pi, pm, yt, sgd, pidx, l2_alpha, huber_delta) + loss_f = float(loss_val) + + for k in params: + m[k] = 0.9 * m[k] + 0.1 * grads[k] + v[k] = 0.999 * v[k] + 0.001 * grads[k] ** 2 + m_hat = m[k] / (1.0 - 0.9 ** (epoch + 1)) + v_hat = v[k] / (1.0 - 0.999 ** (epoch + 1)) + params[k] = params[k] - lr * m_hat / (jnp.sqrt(v_hat) + 1e-8) + + if verbose and (epoch % 200 == 0 or epoch == n_epochs - 1): + cads = np.exp(np.array(params["log_cadence"])) + pe = np.array(forward_encoder( + params, jnp.array(data["peer_input"][:100]), + jnp.array(data["peer_mask"][:100]))) + print(f" epoch {epoch:4d} loss={loss_f:.6f}" + f" cad=[{cads.min():.1f}-{np.median(cads):.1f}-{cads.max():.1f}]" + f" peer_eff=[{pe.min():.2f},{pe.mean():.2f},{pe.max():.2f}]") + + return params + + +def evaluate(params, data, label=""): + """Evaluate decomposition.""" + from quantammsim.calibration.grid_interpolation import interpolate_pool_daily + + x_linear = np.array(data["x_linear"]) + peer_input = data["peer_input"] + peer_mask = data["peer_mask"] + y_total = np.array(data["y_total"]) + pool_idx = np.array(data["pool_idx"]) + sgd = np.array(data["sample_grid_days"]) + log_cadence = np.array(params["log_cadence"]) + init_cads = data["init_log_cadences"] + pool_ids = data["pool_ids"] + n_pools = data["n_pools"] + + # Encoder + peer_effect = np.array(forward_encoder( + params, jnp.array(peer_input), jnp.array(peer_mask))) + + # Build full linear input (must match loss_fn construction) + tvl_col = data["tvl_col"] + btc_vol_col = data["btc_vol_col"] + tvl = x_linear[:, tvl_col:tvl_col + 1] + btc_vol = x_linear[:, btc_vol_col:btc_vol_col + 1] + peer_x_tvl = peer_effect * tvl + peer_x_btcvol = peer_effect * btc_vol + x_full = np.concatenate( + [x_linear, peer_effect, peer_x_tvl, peer_x_btcvol], axis=1) + + noise_coeffs = np.array(params["noise_coeffs"]) + log_v_noise = x_full @ noise_coeffs + v_noise = np.exp(log_v_noise) + + # V_arb + v_arb = np.zeros(len(y_total)) + for i in range(n_pools): + mask = pool_idx == i + if not mask.any(): + continue + v_arb_all = np.array(interpolate_pool_daily( + data["pool_coeffs"][i], jnp.float64(log_cadence[i]), + data["pool_gas"][i])) + v_arb[mask] = v_arb_all[sgd[mask]] + + v_obs = np.exp(y_total) + log_v_arb = np.log(np.maximum(v_arb, 1e-10)) + pred_total = np.logaddexp(log_v_arb, log_v_noise) + + if label: + print(f"\n {label}:") + print(f" {'Pool'[:16]:16s} {'R²':>6s} {'Cad':>5s} → {'learn':>5s}" + f" {'Arb%':>6s} {'Noise%':>7s} {'PeerEff':>8s} {'Flag':>5s}") + print(f" {'-'*65}") + + r2s = {} + pool_diag = [] + for i in range(n_pools): + mask = pool_idx == i + if mask.sum() < 2: + continue + yt = y_total[mask] + pt = pred_total[mask] + ss_res = np.sum((yt - pt) ** 2) + ss_tot = np.sum((yt - yt.mean()) ** 2) + r2s[i] = 1 - ss_res / max(ss_tot, 1e-10) + + pid = pool_ids[i] + ci = np.exp(init_cads[i]) + cl = np.exp(log_cadence[i]) + arb_pct = np.median(v_arb[mask] / v_obs[mask]) * 100 + noise_pct = np.median(v_noise[mask] / v_obs[mask]) * 100 + pe_mean = np.mean(peer_effect[mask]) + + flags = [] + if arb_pct > 150: + flags.append("A") + if cl <= 1.01 or cl >= 59.9: + flags.append("B") + if r2s[i] < 0: + flags.append("X") + flag_str = "".join(flags) + + pool_diag.append({ + "pid": pid, "r2": r2s[i], "cad_init": ci, "cad_learned": cl, + "arb_pct": arb_pct, "noise_pct": noise_pct, + "peer_effect": pe_mean, "flags": flag_str, + }) + + print(f" {pid[:16]:16s} {r2s[i]:6.3f} {ci:5.1f} → {cl:5.1f}" + f" {arb_pct:6.0f}% {noise_pct:6.0f}% {pe_mean:+8.3f} {flag_str:>5s}") + + vals = [x for x in r2s.values() if np.isfinite(x)] + med = np.median(vals) if vals else float("nan") + healthy = [d for d in pool_diag if d["arb_pct"] <= 150 and d["r2"] > 0] + med_h = np.median([d["r2"] for d in healthy]) if healthy else float("nan") + n_path = sum(1 for d in pool_diag if d["arb_pct"] > 150) + n_bound = sum(1 for d in pool_diag + if d["cad_learned"] <= 1.01 or d["cad_learned"] >= 59.9) + + # Print coefficient analysis + nc = np.array(params["noise_coeffs"]) + n_linear = data["n_linear_feat"] + n_po = params["enc_W2"].shape[1] # n_peer_outputs + + print(f"\n Median R²: {med:.4f} (healthy: {med_h:.4f})") + print(f" Healthy: {len(pool_diag) - n_path}/{len(pool_diag)}," + f" at bounds: {n_bound}") + + print(f"\n Linear coefficients:") + for j, name in enumerate(data["linear_names"]): + print(f" {name:30s} {nc[j]:+8.4f}") + for j in range(n_po): + print(f" {'peer_effect_' + str(j):30s} {nc[n_linear + j]:+8.4f}") + for j in range(n_po): + print(f" {'peer_eff_' + str(j) + '×tvl':30s} {nc[n_linear + n_po + j]:+8.4f}") + for j in range(n_po): + print(f" {'peer_eff_' + str(j) + '×btc_vol':30s} {nc[n_linear + 2*n_po + j]:+8.4f}") + + return med, r2s, pool_diag + + +def run_optuna(matched_clean, option_c_clean, n_trials): + """Optuna sweep over encoder + linear model hyperparameters.""" + import optuna + from quantammsim.calibration.grid_interpolation import interpolate_pool_daily + + # Build data for each trend_windows config (cache to avoid rebuilding) + data_cache = {} + + def _get_data(trend_key): + if trend_key not in data_cache: + data_cache[trend_key] = build_data( + matched_clean, option_c_clean, + trend_windows=trend_key) + return data_cache[trend_key] + + # Cache grad_fn per (n_pools, tvl_col, btc_vol_col) — these are stable + grad_fn_cache = {} + + def objective(trial): + trend_w = trial.suggest_categorical("trend_window", [7, 14, 30]) + data = _get_data((trend_w,)) + n_pools = data["n_pools"] + + encoder_hidden = trial.suggest_categorical("encoder_hidden", [16, 32, 64, 128]) + encoder_depth = trial.suggest_categorical("encoder_depth", [1, 2, 3, 4]) + n_peer_outputs = trial.suggest_categorical("n_peer_outputs", [1, 2, 4]) + lr = trial.suggest_float("lr", 3e-4, 3e-3, log=True) + l2_alpha = trial.suggest_float("l2_alpha", 1e-4, 1e-2, log=True) + huber_delta = trial.suggest_categorical("huber_delta", [0.5, 1.0, 1.5]) + n_epochs = trial.suggest_categorical("n_epochs", [1000, 2000, 3000]) + + # Temporal split + day_idx = data["day_idx"] + split_day = int(day_idx.max() * 0.7) + train_mask = day_idx <= split_day + eval_mask = day_idx > split_day + train_data = _subset(data, train_mask) + eval_data = _subset(data, eval_mask) + + # Init + params, n_total_linear = init_params( + jax.random.PRNGKey(42), + data["n_peer_feat"], data["n_linear_feat"], + encoder_hidden, n_peer_outputs, + n_pools, data["init_log_cadences"], + encoder_depth=encoder_depth, + ) + + # OLS warm-start + x_trn = data["x_linear"][train_mask] + y_trn = data["y_total"][train_mask] + n_peer_cols = n_peer_outputs * 3 + x_trn_padded = np.concatenate([ + x_trn, np.zeros((x_trn.shape[0], n_peer_cols), dtype=np.float32) + ], axis=1) + sol, _, _, _ = np.linalg.lstsq(x_trn_padded, y_trn, rcond=None) + params["noise_coeffs"] = jnp.array(sol.astype(np.float32)) + + # Build grad_fn (cache by config) + cache_key = (n_pools, data["tvl_col"], data["btc_vol_col"]) + if cache_key not in grad_fn_cache: + grad_fn_cache[cache_key] = make_loss_fn( + data["pool_coeffs"], data["pool_gas"], n_pools, + data["tvl_col"], data["btc_vol_col"]) + grad_fn = grad_fn_cache[cache_key] + + # Train + params = train(params, train_data, grad_fn, n_epochs, lr, + l2_alpha, huber_delta, verbose=False) + + # Eval: compute total R² + x_linear = np.array(eval_data["x_linear"]) + peer_input = eval_data["peer_input"] + peer_mask = eval_data["peer_mask"] + y_total = np.array(eval_data["y_total"]) + pool_idx = np.array(eval_data["pool_idx"]) + sgd = np.array(eval_data["sample_grid_days"]) + log_cadence = np.array(params["log_cadence"]) + + peer_effect = np.array(forward_encoder( + params, jnp.array(peer_input), jnp.array(peer_mask))) + + tvl_col = data["tvl_col"] + btc_vol_col = data["btc_vol_col"] + tvl = x_linear[:, tvl_col:tvl_col + 1] + btc_vol = x_linear[:, btc_vol_col:btc_vol_col + 1] + x_full = np.concatenate([ + x_linear, peer_effect, peer_effect * tvl, peer_effect * btc_vol + ], axis=1) + + noise_coeffs = np.array(params["noise_coeffs"]) + log_v_noise = x_full @ noise_coeffs + + v_arb = np.zeros(len(y_total)) + for i in range(n_pools): + mask = pool_idx == i + if not mask.any(): + continue + v_arb_all = np.array(interpolate_pool_daily( + data["pool_coeffs"][i], jnp.float64(log_cadence[i]), + data["pool_gas"][i])) + v_arb[mask] = v_arb_all[sgd[mask]] + + log_v_arb = np.log(np.maximum(v_arb, 1e-10)) + pred_total = np.logaddexp(log_v_arb, log_v_noise) + + r2s = [] + for i in range(n_pools): + mask = pool_idx == i + if mask.sum() < 2: + continue + yt = y_total[mask] + pt = pred_total[mask] + ss_res = np.sum((yt - pt) ** 2) + ss_tot = np.sum((yt - yt.mean()) ** 2) + r2s.append(1 - ss_res / max(ss_tot, 1e-10)) + + med_total = float(np.median(r2s)) if r2s else -10.0 + + cads = np.exp(log_cadence) + print(f" Trial {trial.number}: total={med_total:.4f}" + f" enc_h={encoder_hidden} d={encoder_depth} n_po={n_peer_outputs}" + f" hub={huber_delta} lr={lr:.1e} l2={l2_alpha:.1e}" + f" ep={n_epochs} tw={trend_w}" + f" cad=[{cads.min():.0f}-{np.median(cads):.0f}-{cads.max():.0f}]") + + return med_total + + study = optuna.create_study(direction="maximize") + study.optimize(objective, n_trials=n_trials) + + print(f"\n{'='*70}") + print("Optuna Results (hybrid)") + print(f"{'='*70}") + print(f" Best eval total R²: {study.best_value:.4f}") + print(f" Best params:") + for k, v in sorted(study.best_params.items()): + print(f" {k}: {v}") + + print(f"\n Top 10:") + trials = sorted(study.trials, key=lambda t: t.value if t.value else -999, + reverse=True) + for t in trials[:10]: + if t.value is not None: + print(f" #{t.number}: total={t.value:.4f}" + f" enc_h={t.params['encoder_hidden']}" + f" d={t.params['encoder_depth']}" + f" n_po={t.params['n_peer_outputs']}" + f" ep={t.params['n_epochs']}" + f" tw={t.params['trend_window']}") + + return study + + +def main(): + parser = argparse.ArgumentParser() + parser.add_argument("--tune", type=int, default=0, + help="Number of Optuna trials (0 = single run)") + parser.add_argument("--epochs", type=int, default=2000) + parser.add_argument("--lr", type=float, default=1e-3) + parser.add_argument("--l2-alpha", type=float, default=1e-3) + parser.add_argument("--huber-delta", type=float, default=1.0) + parser.add_argument("--encoder-hidden", type=int, default=16) + parser.add_argument("--encoder-depth", type=int, default=1) + parser.add_argument("--n-peer-outputs", type=int, default=1) + parser.add_argument("--trend-windows", type=int, nargs="+", default=[7]) + args = parser.parse_args() + + os.environ.setdefault("JAX_PLATFORMS", "cpu") + + print("=" * 70) + print("Hybrid: DeepSets Peer Encoder + Linear Noise Model") + print(f" encoder_hidden={args.encoder_hidden}," + f" n_peer_outputs={args.n_peer_outputs}") + print(f" epochs={args.epochs}, lr={args.lr}, l2={args.l2_alpha}") + print("=" * 70) + + matched_clean, option_c_clean = load_stage1() + + if args.tune > 0: + run_optuna(matched_clean, option_c_clean, args.tune) + return + + print("\nBuilding data...") + t0 = time.time() + data = build_data(matched_clean, option_c_clean, + trend_windows=tuple(args.trend_windows)) + n_pools = data["n_pools"] + print(f" {len(data['pool_idx'])} samples, {n_pools} pools") + print(f" Linear features: {data['n_linear_feat']}") + print(f" Peer encoder input: {data['n_peer_feat']} per peer," + f" {data['n_peers']} peers") + print(f" Build time: {time.time() - t0:.1f}s") + + # Temporal split + day_idx = data["day_idx"] + split_day = int(day_idx.max() * 0.7) + train_mask = day_idx <= split_day + eval_mask = day_idx > split_day + + train_data = _subset(data, train_mask) + eval_data = _subset(data, eval_mask) + + # Init + params, n_total_linear = init_params( + jax.random.PRNGKey(42), + data["n_peer_feat"], data["n_linear_feat"], + args.encoder_hidden, args.n_peer_outputs, + n_pools, data["init_log_cadences"], + encoder_depth=args.encoder_depth, + ) + + # Warm-start linear coeffs via OLS (peer_effect = 0 initially) + x_trn = data["x_linear"][train_mask] + y_trn = data["y_total"][train_mask] + # Pad with zeros for peer_effect columns (raw + ×tvl + ×btc_vol) + n_peer_cols = args.n_peer_outputs * 3 + x_trn_padded = np.concatenate([ + x_trn, + np.zeros((x_trn.shape[0], n_peer_cols), dtype=np.float32) + ], axis=1) + sol, _, _, _ = np.linalg.lstsq(x_trn_padded, y_trn, rcond=None) + params["noise_coeffs"] = jnp.array(sol.astype(np.float32)) + + n_enc_params = (args.encoder_hidden * data["n_peer_feat"] + + args.encoder_hidden + + args.encoder_hidden * args.n_peer_outputs + + args.n_peer_outputs) + print(f"\n Params: {n_total_linear} linear + {n_enc_params} encoder" + f" + {n_pools} cadences = {n_total_linear + n_enc_params + n_pools}") + print(f" Init cadence: {np.exp(data['init_log_cadences']).min():.1f}" + f"-{np.median(np.exp(data['init_log_cadences'])):.1f}" + f"-{np.exp(data['init_log_cadences']).max():.1f} min") + + # Train + grad_fn = make_loss_fn(data["pool_coeffs"], data["pool_gas"], n_pools, + data["tvl_col"], data["btc_vol_col"]) + + print("\n Compiling...") + t0 = time.time() + params = train(params, train_data, grad_fn, args.epochs, args.lr, + args.l2_alpha, args.huber_delta) + print(f" Training: {time.time() - t0:.1f}s") + + # Evaluate + print("\n --- Train ---") + evaluate(params, train_data) + print("\n --- Eval ---") + evaluate(params, eval_data) + + print(f"\n Baselines (eval, total volume R²):") + print(f" V_arb only: median R² = -0.33") + print(f" Linear shared: median R² = 0.39") + print(f" Linear+intercept: median R² = 0.39") + print(f" DeepSets: median R² = 0.43") + + +if __name__ == "__main__": + main() diff --git a/experiments/run_linear_market_noise.py b/experiments/run_linear_market_noise.py new file mode 100644 index 0000000..56524ce --- /dev/null +++ b/experiments/run_linear_market_noise.py @@ -0,0 +1,627 @@ +"""Linear noise model with market features and learnable cadence. + +V_total = V_arb(cadence) + exp(x @ coeffs) + +where x includes: + - Option C x_obs (intercept, log_tvl_lag1, dow_sin, dow_cos) + - Cross-pool lagged volumes (token-A, token-B, chain peers) + - Market features (BTC price/vol/trend, token prices/vol/trend) + +Cadence is per-pool, optimized jointly with noise coefficients via Adam +through the differentiable PCHIP grid. + +Usage: + python experiments/run_linear_market_noise.py + python experiments/run_linear_market_noise.py --trend-windows 7 14 30 + python experiments/run_linear_market_noise.py --no-market # x_obs only +""" + +import argparse +import os +import pickle +import sys +import time + +import jax +import jax.numpy as jnp +import numpy as np +import pandas as pd + +CACHE_DIR = os.path.join( + os.path.dirname(os.path.dirname(__file__)), + "results", "token_factored_calibration", "_cache", +) + + +def load_stage1(): + path = os.path.join(CACHE_DIR, "stage1.pkl") + if not os.path.exists(path): + print("ERROR: no stage1 cache.") + sys.exit(1) + with open(path, "rb") as f: + data = pickle.load(f) + return data["matched_clean"], data["option_c_clean"] + + +def build_data(matched_clean, option_c_clean, trend_windows=(7, 14, 30), + include_market=True, include_cross_pool=True): + """Build feature matrix and targets.""" + from quantammsim.calibration.grid_interpolation import interpolate_pool_daily + from quantammsim.calibration.pool_data import ( + build_x_obs, build_cross_pool_x_obs, K_OBS_REDUCED, K_OBS_CROSS, + ) + from quantammsim.calibration.market_features import ( + build_pool_market_features, pool_market_features_to_matrix, + ) + + pool_ids = sorted(matched_clean.keys()) + n_pools = len(pool_ids) + + # Common date grid + all_dates = set() + for pid in pool_ids: + all_dates.update(matched_clean[pid]["panel"]["date"].values) + date_list = sorted(all_dates) + n_dates = len(date_list) + date_to_idx = {d: i for i, d in enumerate(date_list)} + + # Per-pool: V_arb, volumes, coeffs, gas, grid day mapping + vol_matrix = np.full((n_dates, n_pools), np.nan) + pool_coeffs = [] + pool_gas = [] + init_log_cadences = np.zeros(n_pools, dtype=np.float32) + common_to_grid = np.full((n_pools, n_dates), 0, dtype=np.int32) + + for j, pid in enumerate(pool_ids): + entry = matched_clean[pid] + oc = option_c_clean[pid] + panel = entry["panel"] + + pool_coeffs.append(entry["coeffs"]) + pool_gas.append(jnp.float64(np.exp(oc["log_gas"]))) + init_log_cadences[j] = oc["log_cadence"] + + dates = panel["date"].values + log_vols = panel["log_volume"].values.astype(float) + for k, date in enumerate(dates): + t = date_to_idx[date] + vol_matrix[t, j] = log_vols[k] + common_to_grid[j, t] = entry["day_indices"][k] + + # Build samples: require t >= 1 (for lag) + sample_pools, sample_days = [], [] + for i in range(n_pools): + for t in range(1, n_dates): + if np.isnan(vol_matrix[t, i]) or np.isnan(vol_matrix[t - 1, i]): + continue + sample_pools.append(i) + sample_days.append(t) + sample_pools = np.array(sample_pools, dtype=np.int32) + sample_days = np.array(sample_days, dtype=np.int32) + n_samples = len(sample_pools) + + # x_obs: reduced (4) or cross-pool (7) + if include_cross_pool: + k_obs = K_OBS_CROSS + x_obs_grid = np.full((n_dates, n_pools, k_obs), np.nan) + for j, pid in enumerate(pool_ids): + panel = matched_clean[pid]["panel"] + xc = build_cross_pool_x_obs(panel, matched_clean, pid) # (n_obs-1, 7) + dates = panel["date"].values + for k, date in enumerate(dates[1:]): + x_obs_grid[date_to_idx[date], j] = xc[k] + else: + k_obs = K_OBS_REDUCED + x_obs_grid = np.full((n_dates, n_pools, k_obs), np.nan) + for j, pid in enumerate(pool_ids): + panel = matched_clean[pid]["panel"] + xr = build_x_obs(panel, reduced=True) + dates = panel["date"].values + for k, date in enumerate(dates): + x_obs_grid[date_to_idx[date], j] = xr[k] + + # Per-sample x_obs + x_obs = np.zeros((n_samples, k_obs), dtype=np.float32) + for s in range(n_samples): + xval = x_obs_grid[sample_days[s], sample_pools[s]] + if np.all(np.isfinite(xval)): + x_obs[s] = xval + + # Market features + if include_market: + print(" Building market features...") + pool_feat = build_pool_market_features( + matched_clean, trend_windows=list(trend_windows)) + x_market, market_names = pool_market_features_to_matrix( + pool_feat, matched_clean, date_to_idx, pool_ids, + sample_pools, sample_days) + print(f" Market features: {len(market_names)} columns") + else: + x_market = np.zeros((n_samples, 0), dtype=np.float32) + market_names = [] + + # Combine base features + x_base = np.concatenate([x_obs, x_market], axis=1).astype(np.float32) + base_names = [f"xobs_{i}" for i in range(k_obs)] + market_names + + # Feature-appropriate scaling: + # - intercept: untouched + # - log_tvl, btc_log_price: raw log scale (absolute level carries info) + # - dow_sin/cos: already [-1,1], no scaling + # - returns, trends, vol_zscore: already comparable, no scaling + # - realized_vol, pair_vol: small positive, light centering + # - cross-pool volumes: z-score (different scales across pool groups) + x_mean = np.zeros(x_base.shape[1], dtype=np.float32) + x_std = np.ones(x_base.shape[1], dtype=np.float32) + + for i, name in enumerate(base_names): + if name == "xobs_0": + # Intercept: leave as-is + pass + elif name in ("xobs_1", "btc_log_price"): + # Log levels: leave in raw log scale (range ~10-20) + pass + elif name in ("xobs_2", "xobs_3"): + # dow_sin, dow_cos: already [-1,1] + pass + elif "volume_zscore" in name: + # Already z-scored by construction + pass + elif "log_return" in name or "trend_" in name: + # Returns and trends: small, centered around 0, comparable + pass + elif "realized_vol" in name or "pair_realized" in name: + # Volatilities: small positive, center but don't squeeze + x_mean[i] = float(np.mean(x_base[:, i])) + # Use std but don't over-compress — floor at 0.01 + x_std[i] = max(float(np.std(x_base[:, i])), 0.01) + elif name.startswith("xobs_") and int(name.split("_")[1]) >= 4: + # Cross-pool volumes (xobs_4,5,6): z-score (different scales) + x_mean[i] = float(np.mean(x_base[:, i])) + x_std[i] = max(float(np.std(x_base[:, i])), 1e-6) + # else: leave untouched + + x_base = ((x_base - x_mean) / x_std).astype(np.float32) + + # Interaction terms (products of standardized features) + col_idx = {name: i for i, name in enumerate(base_names)} + interactions = [] + interaction_names = [] + + def _add_interaction(name_a, name_b): + if name_a in col_idx and name_b in col_idx: + interactions.append( + x_base[:, col_idx[name_a]] * x_base[:, col_idx[name_b]]) + interaction_names.append(f"{name_a}×{name_b}") + + _add_interaction("xobs_1", "btc_realized_vol_7d") # tvl × btc vol + _add_interaction("xobs_1", "tok_a_realized_vol_7d") # tvl × tok_a vol + _add_interaction("xobs_1", "pair_realized_vol_7d") # tvl × pair vol + _add_interaction("tok_a_realized_vol_7d", "tok_b_realized_vol_7d") # cross-token vol + + if interactions: + x_interactions = np.column_stack(interactions).astype(np.float32) + x_all = np.concatenate([x_base, x_interactions], axis=1) + feat_names = base_names + interaction_names + # Extend x_mean/x_std for interaction columns (already standardized → 0/1) + x_mean = np.concatenate([x_mean, np.zeros(len(interactions))]) + x_std = np.concatenate([x_std, np.ones(len(interactions))]) + else: + x_all = x_base + feat_names = base_names + + # Targets + y_total = np.array([vol_matrix[sample_days[s], sample_pools[s]] + for s in range(n_samples)], dtype=np.float32) + sample_grid_days = common_to_grid[sample_pools, sample_days] + + return { + "x": x_all, # (n_samples, n_feat) + "y_total": y_total, # (n_samples,) + "pool_idx": sample_pools, # (n_samples,) + "day_idx": sample_days, # (n_samples,) + "sample_grid_days": sample_grid_days, # (n_samples,) + "pool_coeffs": pool_coeffs, + "pool_gas": pool_gas, + "init_log_cadences": init_log_cadences, + "n_pools": n_pools, + "n_feat": x_all.shape[1], + "pool_ids": pool_ids, + "feat_names": feat_names, + "x_mean": x_mean, + "x_std": x_std, + } + + +def make_loss_fn(pool_coeffs, pool_gas, n_pools): + """Loss function with learnable cadence + linear noise model. + + Supports both shared coefficients (noise_coeffs shape: (n_feat,)) and + per-pool coefficients (noise_coeffs shape: (n_pools, n_feat)). + Detected at trace time from the array shape. + """ + from quantammsim.calibration.grid_interpolation import interpolate_pool_daily + + def loss_fn(params, x, y_total, sample_grid_days, pool_idx, + l2_alpha, huber_delta): + log_cadence = params["log_cadence"] + noise_coeffs = params["noise_coeffs"] + + # Per-pool or shared coefficients + if noise_coeffs.ndim == 2: + # Per-pool: (n_pools, n_feat) — gather each sample's pool coeffs + per_sample_coeffs = noise_coeffs[pool_idx] # (n_samples, n_feat) + log_v_noise = jnp.sum(x * per_sample_coeffs, axis=1) + else: + # Shared: (n_feat,) + log_v_noise = x @ noise_coeffs + + if "pool_intercepts" in params: + log_v_noise = log_v_noise + params["pool_intercepts"][pool_idx] + + # V_arb from PCHIP at learned cadence + n_samples = y_total.shape[0] + v_arb = jnp.zeros(n_samples) + for i in range(n_pools): + v_arb_all = interpolate_pool_daily( + pool_coeffs[i], log_cadence[i], pool_gas[i]) + safe_days = jnp.clip(sample_grid_days, 0, v_arb_all.shape[0] - 1) + v_arb = jnp.where(pool_idx == i, v_arb_all[safe_days], v_arb) + + log_v_arb = jnp.log(jnp.maximum(v_arb, 1e-10)) + log_v_total = jnp.logaddexp(log_v_arb, log_v_noise) + + # Huber loss with per-pool weighting + residuals = log_v_total - y_total + abs_r = jnp.abs(residuals) + huber_vals = jnp.where(abs_r <= huber_delta, 0.5 * residuals ** 2, + huber_delta * (abs_r - 0.5 * huber_delta)) + + pool_counts = jnp.zeros(n_pools).at[pool_idx].add( + jnp.ones_like(pool_idx, dtype=jnp.float32)) + active = (pool_counts > 0).astype(jnp.float32) + n_active = jnp.maximum(jnp.sum(active), 1.0) + pool_counts = jnp.maximum(pool_counts, 1.0) + pool_sums = jnp.zeros(n_pools).at[pool_idx].add(huber_vals) + data_loss = jnp.sum((pool_sums / pool_counts) * active) / n_active + + reg = l2_alpha * jnp.sum(noise_coeffs ** 2) + return data_loss + reg + + return jax.jit(jax.value_and_grad(loss_fn)) + + +def train(params, data, grad_fn, n_epochs, lr, l2_alpha, huber_delta, + verbose=True): + m = {k: jnp.zeros_like(v) for k, v in params.items()} + v = {k: jnp.zeros_like(v) for k, v in params.items()} + + x = jnp.array(data["x"]) + y = jnp.array(data["y_total"]) + sgd = jnp.array(data["sample_grid_days"]) + pidx = jnp.array(data["pool_idx"]) + + for epoch in range(n_epochs): + loss_val, grads = grad_fn( + params, x, y, sgd, pidx, l2_alpha, huber_delta) + loss_f = float(loss_val) + + for k in params: + m[k] = 0.9 * m[k] + 0.1 * grads[k] + v[k] = 0.999 * v[k] + 0.001 * grads[k] ** 2 + m_hat = m[k] / (1.0 - 0.9 ** (epoch + 1)) + v_hat = v[k] / (1.0 - 0.999 ** (epoch + 1)) + params[k] = params[k] - lr * m_hat / (jnp.sqrt(v_hat) + 1e-8) + + if verbose and (epoch % 200 == 0 or epoch == n_epochs - 1): + cads = np.exp(np.array(params["log_cadence"])) + nc = np.array(params["noise_coeffs"]) + print(f" epoch {epoch:4d} loss={loss_f:.6f}" + f" cad=[{cads.min():.1f}-{np.median(cads):.1f}-{cads.max():.1f}]" + f" |coeffs|={np.mean(np.abs(nc)):.3f}") + + return params + + +def evaluate(params, data, label=""): + """Evaluate decomposition quality.""" + from quantammsim.calibration.grid_interpolation import interpolate_pool_daily + + x = np.array(data["x"]) + y_total = np.array(data["y_total"]) + pool_idx = np.array(data["pool_idx"]) + sgd = np.array(data["sample_grid_days"]) + log_cadence = np.array(params["log_cadence"]) + noise_coeffs = np.array(params["noise_coeffs"]) + init_cads = data["init_log_cadences"] + pool_ids = data["pool_ids"] + n_pools = data["n_pools"] + + if noise_coeffs.ndim == 2: + # Per-pool: (n_pools, n_feat) + per_sample_coeffs = noise_coeffs[pool_idx] + log_v_noise = np.sum(x * per_sample_coeffs, axis=1) + else: + log_v_noise = x @ noise_coeffs + if "pool_intercepts" in params: + pool_intercepts = np.array(params["pool_intercepts"]) + log_v_noise = log_v_noise + pool_intercepts[pool_idx] + v_noise = np.exp(log_v_noise) + + v_arb = np.zeros(len(y_total)) + for i in range(n_pools): + mask = pool_idx == i + if not mask.any(): + continue + v_arb_all = np.array(interpolate_pool_daily( + data["pool_coeffs"][i], jnp.float64(log_cadence[i]), + data["pool_gas"][i])) + v_arb[mask] = v_arb_all[sgd[mask]] + + v_obs = np.exp(y_total) + log_v_arb = np.log(np.maximum(v_arb, 1e-10)) + pred_total = np.logaddexp(log_v_arb, log_v_noise) + + if label: + print(f"\n {label}:") + print(f" {'Pool'[:16]:16s} {'R²':>6s} {'Cad':>5s} {'→':>2s} {'learn':>5s}" + f" {'Arb%':>6s} {'Noise%':>7s} {'Flag':>5s}") + print(f" {'-'*60}") + + r2s = {} + pool_diag = [] + for i in range(n_pools): + mask = pool_idx == i + if mask.sum() < 2: + continue + yt = y_total[mask] + pt = pred_total[mask] + ss_res = np.sum((yt - pt) ** 2) + ss_tot = np.sum((yt - yt.mean()) ** 2) + r2s[i] = 1 - ss_res / max(ss_tot, 1e-10) + + pid = pool_ids[i] + ci = np.exp(init_cads[i]) + cl = np.exp(log_cadence[i]) + arb_pct = np.median(v_arb[mask] / v_obs[mask]) * 100 + noise_pct = np.median(v_noise[mask] / v_obs[mask]) * 100 + + flags = [] + if arb_pct > 150: + flags.append("A") + if cl <= 1.01 or cl >= 59.9: + flags.append("B") + if r2s[i] < 0: + flags.append("X") + flag_str = "".join(flags) + + pool_diag.append({ + "pid": pid, "r2": r2s[i], "cad_init": ci, "cad_learned": cl, + "arb_pct": arb_pct, "noise_pct": noise_pct, "flags": flag_str, + }) + + print(f" {pid[:16]:16s} {r2s[i]:6.3f} {ci:5.1f} → {cl:5.1f}" + f" {arb_pct:6.0f}% {noise_pct:6.0f}% {flag_str:>5s}") + + vals = [x for x in r2s.values() if np.isfinite(x)] + med = np.median(vals) if vals else float("nan") + healthy = [d for d in pool_diag if d["arb_pct"] <= 150 and d["r2"] > 0] + med_h = np.median([d["r2"] for d in healthy]) if healthy else float("nan") + n_path = sum(1 for d in pool_diag if d["arb_pct"] > 150) + n_bound = sum(1 for d in pool_diag + if d["cad_learned"] <= 1.01 or d["cad_learned"] >= 59.9) + + print(f"\n Median R²: {med:.4f} (healthy: {med_h:.4f})") + print(f" Healthy: {len(pool_diag) - n_path}/{len(pool_diag)}," + f" at bounds: {n_bound}") + + return med, r2s, pool_diag + + +def main(): + parser = argparse.ArgumentParser() + parser.add_argument("--epochs", type=int, default=2000) + parser.add_argument("--lr", type=float, default=1e-3) + parser.add_argument("--l2-alpha", type=float, default=1e-3) + parser.add_argument("--huber-delta", type=float, default=1.0) + parser.add_argument("--trend-windows", type=int, nargs="+", default=[7]) + parser.add_argument("--no-market", action="store_true", + help="x_obs only, no market features") + parser.add_argument("--no-cross-pool", action="store_true", + help="Reduced x_obs (4) instead of cross-pool (7)") + parser.add_argument("--pool-intercepts", action="store_true", + help="Per-pool intercept (shared slopes + per-pool bias)") + parser.add_argument("--per-pool", action="store_true", + help="Per-pool noise coefficients (Option A)") + parser.add_argument("--no-split", action="store_true", + help="Train on all data (no temporal holdout)") + args = parser.parse_args() + + os.environ.setdefault("JAX_PLATFORMS", "cpu") + + print("=" * 70) + print("Linear Noise Model + Learnable Cadence") + mode = "per-pool" if args.per_pool else ( + "shared+intercepts" if args.pool_intercepts else "shared") + print(f" mode={mode}, market={not args.no_market}," + f" cross_pool={not args.no_cross_pool}") + print(f" trend_windows={args.trend_windows}") + print(f" epochs={args.epochs}, lr={args.lr}, l2={args.l2_alpha}") + print("=" * 70) + + matched_clean, option_c_clean = load_stage1() + + print("\nBuilding data...") + t0 = time.time() + data = build_data( + matched_clean, option_c_clean, + trend_windows=tuple(args.trend_windows), + include_market=not args.no_market, + include_cross_pool=not args.no_cross_pool, + ) + print(f" {len(data['pool_idx'])} samples, {data['n_pools']} pools," + f" {data['n_feat']} features, {time.time() - t0:.1f}s") + print(f" Features: {data['feat_names']}") + + # Split + day_idx = data["day_idx"] + n_samples = len(day_idx) + if args.no_split: + train_mask = np.ones(n_samples, dtype=bool) + eval_mask = None + else: + split_day = int(day_idx.max() * 0.7) + train_mask = day_idx <= split_day + eval_mask = day_idx > split_day + + train_data = {k: v[train_mask] if isinstance(v, np.ndarray) + and v.shape[0] == n_samples else v + for k, v in data.items()} + if eval_mask is not None: + eval_data = {k: v[eval_mask] if isinstance(v, np.ndarray) + and v.shape[0] == n_samples else v + for k, v in data.items()} + else: + eval_data = None + + # Init params + n_feat = data["n_feat"] + n_pools = data["n_pools"] + x_trn = data["x"][train_mask] + y_trn = data["y_total"][train_mask] + pool_idx_trn = data["pool_idx"][train_mask] + + if args.per_pool: + # Per-pool coefficients: (n_pools, n_feat) + # Warm-start each pool via per-pool Ridge (not OLS — avoids blowup + # on pools with few samples or near-singular features) + from sklearn.linear_model import RidgeCV + coeffs_init = np.zeros((n_pools, n_feat), dtype=np.float32) + # Shared Ridge as fallback + ridge_shared = RidgeCV(alphas=np.logspace(-2, 4, 50)) + ridge_shared.fit(x_trn, y_trn) + for i in range(n_pools): + mask_i = pool_idx_trn == i + if mask_i.sum() >= 20: + ridge_i = RidgeCV(alphas=np.logspace(-2, 4, 50)) + ridge_i.fit(x_trn[mask_i], y_trn[mask_i]) + coeffs_init[i] = ridge_i.coef_ + coeffs_init[i, 0] += ridge_i.intercept_ # fold intercept into xobs_0 + else: + coeffs_init[i] = ridge_shared.coef_ + coeffs_init[i, 0] += ridge_shared.intercept_ + params = { + "log_cadence": jnp.array(data["init_log_cadences"]), + "noise_coeffs": jnp.array(coeffs_init), + } + print(f"\n Per-pool coefficients: {n_pools} × {n_feat} = {n_pools * n_feat} params") + print(f" Ridge warm-start |coeffs|={np.mean(np.abs(coeffs_init)):.3f}") + else: + params = { + "log_cadence": jnp.array(data["init_log_cadences"]), + "noise_coeffs": jnp.zeros(n_feat), + } + # Warm-start noise_coeffs via OLS on train + sol, _, _, _ = np.linalg.lstsq(x_trn, y_trn, rcond=None) + params["noise_coeffs"] = jnp.array(sol.astype(np.float32)) + + if args.pool_intercepts and not args.per_pool: + # Init per-pool intercepts from OLS residuals + ols_pred = x_trn @ sol + ols_resid = y_trn - ols_pred + pool_idx_trn = data["pool_idx"][train_mask] + intercepts = np.zeros(n_pools, dtype=np.float32) + for i in range(n_pools): + mask_i = pool_idx_trn == i + if mask_i.sum() > 0: + intercepts[i] = np.mean(ols_resid[mask_i]) + params["pool_intercepts"] = jnp.array(intercepts) + print(f" Per-pool intercepts: {n_pools} pools" + f" (range {intercepts.min():.2f} to {intercepts.max():.2f})") + + print(f"\n Init cadence: {np.exp(data['init_log_cadences']).min():.1f}" + f"-{np.median(np.exp(data['init_log_cadences'])):.1f}" + f"-{np.exp(data['init_log_cadences']).max():.1f} min") + total_params = sum(v.size for v in params.values()) + print(f" Total params: {total_params}") + + # Build loss and train + grad_fn = make_loss_fn(data["pool_coeffs"], data["pool_gas"], data["n_pools"]) + + print("\n Compiling...") + t0 = time.time() + params = train(params, train_data, grad_fn, args.epochs, args.lr, + args.l2_alpha, args.huber_delta) + print(f" Training: {time.time() - t0:.1f}s") + + # Print learned coefficients + nc = np.array(params["noise_coeffs"]) + if nc.ndim == 2: + # Per-pool: print median coefficient across pools + print(f"\n Per-pool noise coefficients — median across {n_pools} pools:") + for i, name in enumerate(data["feat_names"]): + vals = nc[:, i] + print(f" {name:30s} med={np.median(vals):+7.3f}" + f" [{vals.min():+7.3f}, {vals.max():+7.3f}]") + else: + print(f"\n Noise coefficients ({len(nc)}):") + for i, name in enumerate(data["feat_names"]): + print(f" {name:30s} {nc[i]:+8.4f}") + + # Evaluate + if eval_data is not None: + print("\n --- Train ---") + evaluate(params, train_data) + print("\n --- Eval ---") + evaluate(params, eval_data) + else: + print("\n --- All data ---") + evaluate(params, train_data) + + # Save artifact + artifact_dir = os.path.join( + os.path.dirname(os.path.dirname(__file__)), + "results", "linear_market_noise", + ) + os.makedirs(artifact_dir, exist_ok=True) + artifact = { + "noise_coeffs": np.array(params["noise_coeffs"]), + "log_cadence": np.array(params["log_cadence"]), + "init_log_cadences": data["init_log_cadences"], + "feat_names": data["feat_names"], + "pool_ids": data["pool_ids"], + "n_pools": data["n_pools"], + "n_feat": data["n_feat"], + "x_mean": data["x_mean"], + "x_std": data["x_std"], + "hparams": { + "epochs": args.epochs, "lr": args.lr, + "l2_alpha": args.l2_alpha, "huber_delta": args.huber_delta, + "trend_windows": args.trend_windows, + "per_pool": args.per_pool, + "pool_intercepts": args.pool_intercepts, + }, + } + if "pool_intercepts" in params: + artifact["pool_intercepts"] = np.array(params["pool_intercepts"]) + artifact_path = os.path.join(artifact_dir, "model.npz") + np.savez(artifact_path, **{k: v for k, v in artifact.items() + if isinstance(v, np.ndarray)}) + # Save non-array metadata separately + import json + meta_path = os.path.join(artifact_dir, "meta.json") + meta = {k: v for k, v in artifact.items() if not isinstance(v, np.ndarray)} + with open(meta_path, "w") as f: + json.dump(meta, f, indent=2, default=str) + print(f"\n Saved artifact: {artifact_path}") + print(f" Saved metadata: {meta_path}") + + # Baselines + print(f"\n Baselines (eval, total volume R²):") + print(f" V_arb only: median R² = -0.33") + print(f" Naive lag: median R² = 0.01") + print(f" DeepSets best: median R² = 0.43") + + +if __name__ == "__main__": + main() diff --git a/experiments/run_mlp_noise.py b/experiments/run_mlp_noise.py new file mode 100644 index 0000000..35d5418 --- /dev/null +++ b/experiments/run_mlp_noise.py @@ -0,0 +1,623 @@ +"""MLP noise model with Binance market features and learnable cadence. + +No cross-pool DEX dependency — only uses this pool's TVL + public market +data (Binance prices/volumes for BTC and the pool's tokens). + +Architecture: + log(V_noise) = MLP(x_market) + V_total = V_arb(cadence) + exp(log_v_noise) + +where x_market = [log_tvl, dow_sin, dow_cos, btc_features, tok_a_features, +tok_b_features, pair_vol, interactions]. + +Cadence is per-pool, learned jointly via Adam through PCHIP. + +Usage: + python experiments/run_mlp_noise.py + python experiments/run_mlp_noise.py --hidden 64 32 --epochs 3000 + python experiments/run_mlp_noise.py --per-pool --hidden 32 +""" + +import argparse +import os +import time + +import jax +import jax.numpy as jnp +import numpy as np + + +# ---- Model ---- + + +def init_mlp_params(key, n_input, hidden_sizes, n_pools, init_log_cadences, + per_pool=False): + """Initialize MLP parameters. + + MLP: input → hidden1 → ... → hiddenN → 1 (with ReLU activations). + If per_pool: separate output bias per pool. + """ + params = {} + keys = jax.random.split(key, len(hidden_sizes) + 2) + + # Hidden layers + in_dim = n_input + for i, h in enumerate(hidden_sizes): + params[f"W{i}"] = jax.random.normal(keys[i], (in_dim, h)) * np.sqrt(2.0 / in_dim) + params[f"b{i}"] = jnp.zeros(h) + in_dim = h + + # Output layer → scalar + params["W_out"] = jax.random.normal(keys[-2], (in_dim, 1)) * 0.01 + params["b_out"] = jnp.zeros(1) + + if per_pool: + params["pool_bias"] = jnp.zeros(n_pools) + + params["log_cadence"] = jnp.array(init_log_cadences) + return params + + +def forward_mlp(params, x, pool_idx=None): + """MLP forward pass. Returns (n_samples,) log_v_noise.""" + h = x + i = 0 + while f"W{i}" in params: + h = jnp.maximum(h @ params[f"W{i}"] + params[f"b{i}"], 0.0) + i += 1 + out = (h @ params["W_out"] + params["b_out"])[:, 0] + + if "pool_bias" in params and pool_idx is not None: + out = out + params["pool_bias"][pool_idx] + + return out + + +def make_loss_fn(pool_coeffs, pool_gas, n_pools): + """Loss with learnable cadence + MLP noise model.""" + from quantammsim.calibration.grid_interpolation import interpolate_pool_daily + + def loss_fn(params, x, y_total, sample_grid_days, pool_idx, + l2_alpha, huber_delta): + log_v_noise = forward_mlp(params, x, pool_idx) + + log_cadence = params["log_cadence"] + n_samples = y_total.shape[0] + v_arb = jnp.zeros(n_samples) + for i in range(n_pools): + v_arb_all = interpolate_pool_daily( + pool_coeffs[i], log_cadence[i], pool_gas[i]) + safe_days = jnp.clip(sample_grid_days, 0, v_arb_all.shape[0] - 1) + v_arb = jnp.where(pool_idx == i, v_arb_all[safe_days], v_arb) + + log_v_arb = jnp.log(jnp.maximum(v_arb, 1e-10)) + log_v_total = jnp.logaddexp(log_v_arb, log_v_noise) + + residuals = log_v_total - y_total + abs_r = jnp.abs(residuals) + huber_vals = jnp.where(abs_r <= huber_delta, 0.5 * residuals ** 2, + huber_delta * (abs_r - 0.5 * huber_delta)) + + pool_counts = jnp.zeros(n_pools).at[pool_idx].add( + jnp.ones_like(pool_idx, dtype=jnp.float32)) + active = (pool_counts > 0).astype(jnp.float32) + n_active = jnp.maximum(jnp.sum(active), 1.0) + pool_counts = jnp.maximum(pool_counts, 1.0) + pool_sums = jnp.zeros(n_pools).at[pool_idx].add(huber_vals) + data_loss = jnp.sum((pool_sums / pool_counts) * active) / n_active + + reg = l2_alpha * sum(jnp.sum(v ** 2) for k, v in params.items() + if k.startswith("W")) + return data_loss + reg + + return jax.jit(jax.value_and_grad(loss_fn)) + + +def train(params, data, grad_fn, n_epochs, lr, l2_alpha, huber_delta, + verbose=True, use_cosine=False, warmup_steps=100): + x = jnp.array(data["x"]) + y = jnp.array(data["y_total"]) + sgd = jnp.array(data["sample_grid_days"]) + pidx = jnp.array(data["pool_idx"]) + + if use_cosine: + import optax + schedule = optax.warmup_cosine_decay_schedule( + init_value=lr * 0.01, + peak_value=lr, + warmup_steps=warmup_steps, + decay_steps=n_epochs, + end_value=lr * 0.01, + ) + optimizer = optax.adam(learning_rate=schedule) + opt_state = optimizer.init(params) + + for epoch in range(n_epochs): + loss_val, grads = grad_fn( + params, x, y, sgd, pidx, l2_alpha, huber_delta) + updates, opt_state = optimizer.update(grads, opt_state, params) + params = optax.apply_updates(params, updates) + + if verbose and (epoch % 500 == 0 or epoch == n_epochs - 1): + cads = np.exp(np.array(params["log_cadence"])) + cur_lr = float(schedule(epoch)) + print(f" epoch {epoch:5d} loss={float(loss_val):.6f}" + f" lr={cur_lr:.2e}" + f" cad=[{cads.min():.1f}-{np.median(cads):.1f}-{cads.max():.1f}]") + else: + m = {k: jnp.zeros_like(v) for k, v in params.items()} + v = {k: jnp.zeros_like(v) for k, v in params.items()} + + for epoch in range(n_epochs): + loss_val, grads = grad_fn( + params, x, y, sgd, pidx, l2_alpha, huber_delta) + + for k in params: + m[k] = 0.9 * m[k] + 0.1 * grads[k] + v[k] = 0.999 * v[k] + 0.001 * grads[k] ** 2 + m_hat = m[k] / (1.0 - 0.9 ** (epoch + 1)) + v_hat = v[k] / (1.0 - 0.999 ** (epoch + 1)) + params[k] = params[k] - lr * m_hat / (jnp.sqrt(v_hat) + 1e-8) + + if verbose and (epoch % 500 == 0 or epoch == n_epochs - 1): + cads = np.exp(np.array(params["log_cadence"])) + print(f" epoch {epoch:5d} loss={float(loss_val):.6f}" + f" cad=[{cads.min():.1f}-{np.median(cads):.1f}-{cads.max():.1f}]") + + return params + + +def evaluate(params, data, label=""): + """Evaluate decomposition.""" + from quantammsim.calibration.grid_interpolation import interpolate_pool_daily + + x = np.array(data["x"]) + y_total = np.array(data["y_total"]) + pool_idx = np.array(data["pool_idx"]) + sgd = np.array(data["sample_grid_days"]) + log_cadence = np.array(params["log_cadence"]) + init_cads = data["init_log_cadences"] + pool_ids = data["pool_ids"] + n_pools = data["n_pools"] + + log_v_noise = np.array(forward_mlp( + params, jnp.array(x), + jnp.array(pool_idx) if "pool_bias" in params else None)) + v_noise = np.exp(log_v_noise) + + v_arb = np.zeros(len(y_total)) + for i in range(n_pools): + mask = pool_idx == i + if not mask.any(): + continue + v_arb_all = np.array(interpolate_pool_daily( + data["pool_coeffs"][i], jnp.float64(log_cadence[i]), + data["pool_gas"][i])) + v_arb[mask] = v_arb_all[sgd[mask]] + + v_obs = np.exp(y_total) + log_v_arb = np.log(np.maximum(v_arb, 1e-10)) + pred_total = np.logaddexp(log_v_arb, log_v_noise) + + if label: + print(f"\n {label}:") + print(f" {'Pool'[:16]:16s} {'R²':>6s} {'Cad':>5s} → {'learn':>5s}" + f" {'Arb%':>6s} {'Noise%':>7s} {'Flag':>5s}") + print(f" {'-'*55}") + + r2s = {} + for i in range(n_pools): + mask = pool_idx == i + if mask.sum() < 2: + continue + yt = y_total[mask] + pt = pred_total[mask] + ss_res = np.sum((yt - pt) ** 2) + ss_tot = np.sum((yt - yt.mean()) ** 2) + r2s[i] = 1 - ss_res / max(ss_tot, 1e-10) + + pid = pool_ids[i] + ci = np.exp(init_cads[i]) + cl = np.exp(log_cadence[i]) + arb_pct = np.median(v_arb[mask] / v_obs[mask]) * 100 + noise_pct = np.median(v_noise[mask] / v_obs[mask]) * 100 + flags = [] + if arb_pct > 150: flags.append("A") + if cl <= 1.01 or cl >= 59.9: flags.append("B") + if r2s[i] < 0: flags.append("X") + print(f" {pid[:16]:16s} {r2s[i]:6.3f} {ci:5.1f} → {cl:5.1f}" + f" {arb_pct:6.0f}% {noise_pct:6.0f}% {''.join(flags):>5s}") + + vals = [x for x in r2s.values() if np.isfinite(x)] + med = np.median(vals) if vals else float("nan") + healthy = [r for r in r2s.values() if r > 0 and np.isfinite(r)] + med_h = np.median(healthy) if healthy else float("nan") + print(f"\n Median R²: {med:.4f} (healthy: {med_h:.4f})") + return med, r2s + + +def run_optuna(data, n_trials): + """Optuna sweep over MLP architecture and training hyperparameters.""" + import optuna + + day_idx = data["day_idx"] + n_samples = len(day_idx) + split_day = int(day_idx.max() * 0.7) + train_mask = day_idx <= split_day + eval_mask = day_idx > split_day + train_data = {k: v[train_mask] if isinstance(v, np.ndarray) + and v.shape[0] == n_samples else v + for k, v in data.items()} + eval_data = {k: v[eval_mask] if isinstance(v, np.ndarray) + and v.shape[0] == n_samples else v + for k, v in data.items()} + + n_pools = data["n_pools"] + n_feat = data["n_feat"] + + def objective(trial): + # Architecture + n_layers = trial.suggest_int("n_layers", 1, 7) + first_hidden = trial.suggest_categorical("first_hidden", [8, 16, 32, 64, 128, 256]) + # Bottleneck: each layer is half the previous (min 2) + bottleneck_ratio = trial.suggest_categorical("bottleneck_ratio", [0.5, 0.75, 1.0]) + hidden = [] + h = first_hidden + for _ in range(n_layers): + hidden.append(h) + h = max(int(h * bottleneck_ratio), 2) + + # Training + lr = trial.suggest_float("lr", 1e-4, 5e-2, log=True) + l2_alpha = trial.suggest_float("l2_alpha", 1e-5, 5e-1, log=True) + huber_delta = trial.suggest_categorical("huber_delta", [0.5, 1.0, 1.5, 2.0]) + n_epochs = trial.suggest_categorical("n_epochs", [2000, 5000, 10000, 20000]) + use_cosine = trial.suggest_categorical("use_cosine", [True, False]) + per_pool = trial.suggest_categorical("per_pool", [True, False]) + + params = init_mlp_params( + jax.random.PRNGKey(42), n_feat, hidden, n_pools, + data["init_log_cadences"], per_pool=per_pool) + + # OLS warm-start + x_trn = jnp.array(train_data["x"]) + y_trn = np.array(train_data["y_total"]) + h_act = np.array(x_trn) + i = 0 + while f"W{i}" in params: + h_act = np.maximum( + h_act @ np.array(params[f"W{i}"]) + np.array(params[f"b{i}"]), 0.0) + i += 1 + h_bias = np.concatenate([h_act, np.ones((h_act.shape[0], 1))], axis=1) + sol, _, _, _ = np.linalg.lstsq(h_bias, y_trn[:, None], rcond=None) + params["W_out"] = jnp.array(sol[:-1].astype(np.float32)) + params["b_out"] = jnp.array(sol[-1:].astype(np.float32)) + + grad_fn = make_loss_fn(data["pool_coeffs"], data["pool_gas"], n_pools) + params = train(params, train_data, grad_fn, n_epochs, lr, + l2_alpha, huber_delta, verbose=False, + use_cosine=use_cosine) + + # Eval + x_eval = np.array(eval_data["x"]) + y_eval = np.array(eval_data["y_total"]) + pool_idx_eval = np.array(eval_data["pool_idx"]) + sgd_eval = np.array(eval_data["sample_grid_days"]) + log_cadence = np.array(params["log_cadence"]) + + log_v_noise = np.array(forward_mlp( + params, jnp.array(x_eval), + jnp.array(pool_idx_eval) if per_pool else None)) + + from quantammsim.calibration.grid_interpolation import interpolate_pool_daily + v_arb = np.zeros(len(y_eval)) + for i in range(n_pools): + mask = pool_idx_eval == i + if not mask.any(): + continue + v_arb_all = np.array(interpolate_pool_daily( + data["pool_coeffs"][i], jnp.float64(log_cadence[i]), + data["pool_gas"][i])) + v_arb[mask] = v_arb_all[sgd_eval[mask]] + + log_v_arb = np.log(np.maximum(v_arb, 1e-10)) + pred_total = np.logaddexp(log_v_arb, log_v_noise) + + r2s = [] + for i in range(n_pools): + mask = pool_idx_eval == i + if mask.sum() < 2: + continue + yt = y_eval[mask] + pt = pred_total[mask] + ss_res = np.sum((yt - pt) ** 2) + ss_tot = np.sum((yt - yt.mean()) ** 2) + r2s.append(1 - ss_res / max(ss_tot, 1e-10)) + + med_r2 = float(np.median(r2s)) if r2s else -10.0 + arch_str = "×".join(str(h) for h in hidden) + print(f" Trial {trial.number}: eval={med_r2:.4f}" + f" arch=[{arch_str}]" + f" {'cosine' if use_cosine else 'const'}" + f" {'per_pool' if per_pool else 'shared'}" + f" lr={lr:.1e} l2={l2_alpha:.1e}" + f" hub={huber_delta} ep={n_epochs}") + + # Save every trial's model + trial_dir = os.path.join("results", "mlp_noise", "trials", f"trial_{trial.number:04d}") + os.makedirs(trial_dir, exist_ok=True) + save_dict = {k: np.array(v) for k, v in params.items()} + save_dict["x_mean"] = data.get("x_mean", np.zeros(n_feat)) + save_dict["x_std"] = data.get("x_std", np.ones(n_feat)) + np.savez(os.path.join(trial_dir, "model.npz"), **save_dict) + import json as _json + _meta = { + "pool_ids": data["pool_ids"], + "feat_names": data["feat_names"], + "n_feat": n_feat, + "hidden": hidden, + "per_pool": per_pool, + "eval_r2": med_r2, + "hparams": { + "hidden": hidden, "lr": lr, "l2_alpha": l2_alpha, + "huber_delta": huber_delta, "n_epochs": n_epochs, + "use_cosine": use_cosine, "per_pool": per_pool, + "bottleneck_ratio": bottleneck_ratio, + "first_hidden": first_hidden, "n_layers": n_layers, + }, + } + with open(os.path.join(trial_dir, "meta.json"), "w") as _f: + _json.dump(_meta, _f, indent=2) + + return med_r2 + + study = optuna.create_study(direction="maximize") + study.optimize(objective, n_trials=n_trials) + + print(f"\n{'='*70}") + print(f"Optuna Results (MLP noise)") + print(f"{'='*70}") + print(f" Best eval R²: {study.best_value:.4f}") + print(f" Best params:") + for k, v in sorted(study.best_params.items()): + print(f" {k}: {v}") + + trials = sorted(study.trials, key=lambda t: t.value if t.value else -999, + reverse=True) + print(f"\n Top 10:") + for t in trials[:10]: + if t.value is not None: + n_l = t.params["n_layers"] + fh = t.params["first_hidden"] + h = fh + arch = [] + for _ in range(n_l): + arch.append(h) + h = max(int(h * t.params.get("bottleneck_ratio", 0.5)), 2) + print(f" #{t.number}: eval={t.value:.4f}" + f" arch={arch}" + f" ep={t.params['n_epochs']}" + f" {'cos' if t.params['use_cosine'] else 'cst'}" + f" {'pp' if t.params['per_pool'] else 'sh'}") + + # Copy best trial to top-level artifact + best_trial = study.best_trial + best_trial_dir = os.path.join("results", "mlp_noise", "trials", + f"trial_{best_trial.number:04d}") + save_dir = "results/mlp_noise" + if os.path.exists(os.path.join(best_trial_dir, "model.npz")): + import shutil + shutil.copy2(os.path.join(best_trial_dir, "model.npz"), + os.path.join(save_dir, "model.npz")) + shutil.copy2(os.path.join(best_trial_dir, "meta.json"), + os.path.join(save_dir, "meta.json")) + print(f"\n Copied best trial ({best_trial.number}) to: {save_dir}") + + # TVL response check on best model + import json as _json + art = dict(np.load(os.path.join(save_dir, "model.npz"), allow_pickle=True)) + with open(os.path.join(save_dir, "meta.json")) as _f: + meta = _json.load(_f) + best_params = {k: jnp.array(art[k]) for k in art + if k.startswith("W") or k.startswith("b") + or k == "log_cadence" or k == "pool_bias"} + per_pool = meta.get("per_pool", False) + pool_idx_probe = jnp.array([0]) if per_pool else None + + print(f"\n TVL Response Check (best model, trial {best_trial.number}):") + x_probe = np.zeros((1, n_feat), dtype=np.float32) + x_probe[0, 0] = 1.0 + x_probe[0, 4] = 10.5 # typical btc_log_price + prev_noise = None + for tvl in [1e4, 1e5, 5e5, 1e6, 5e6, 1e7, 5e7, 1e8, 5e8]: + x_probe[0, 1] = np.log(tvl) + out = np.array(forward_mlp(best_params, jnp.array(x_probe), + pool_idx_probe)) + noise = np.exp(out[0]) + if prev_noise is not None and prev_noise > 0: + ratio = noise / prev_noise + print(f" TVL=${tvl:>11,.0f} noise=${noise:>12,.0f}/day" + f" ({ratio:.2f}x prev)") + else: + print(f" TVL=${tvl:>11,.0f} noise=${noise:>12,.0f}/day") + prev_noise = noise + + return study + + +def main(): + parser = argparse.ArgumentParser(description=__doc__, + formatter_class=argparse.RawDescriptionHelpFormatter) + parser.add_argument("--hidden", type=int, nargs="+", default=[32], + help="Hidden layer sizes (e.g. --hidden 64 32)") + parser.add_argument("--epochs", type=int, default=2000) + parser.add_argument("--lr", type=float, default=1e-3) + parser.add_argument("--l2-alpha", type=float, default=1e-3) + parser.add_argument("--huber-delta", type=float, default=1.0) + parser.add_argument("--cosine", action="store_true", + help="Use optax Adam with cosine LR decay") + parser.add_argument("--tune", type=int, default=0, + help="Optuna sweep (0 = single run)") + parser.add_argument("--trend-windows", type=int, nargs="+", default=[7]) + parser.add_argument("--per-pool", action="store_true", + help="Per-pool output bias") + parser.add_argument("--pool-attrs", action="store_true", + help="Append static pool attributes to input") + parser.add_argument("--no-split", action="store_true", + help="Train on all data") + parser.add_argument("--save-artifact", default=None, + help="Save model to this directory") + args = parser.parse_args() + + os.environ.setdefault("JAX_PLATFORMS", "cpu") + + print("=" * 70) + print("MLP Noise Model (Binance features only, no cross-pool DEX)") + print(f" hidden={args.hidden}, per_pool={args.per_pool}") + print(f" epochs={args.epochs}, lr={args.lr}, l2={args.l2_alpha}") + print("=" * 70) + + # Build data WITHOUT cross-pool features + from experiments.run_linear_market_noise import load_stage1, build_data + + matched_clean, option_c_clean = load_stage1() + + print("\nBuilding data...") + t0 = time.time() + data = build_data( + matched_clean, option_c_clean, + trend_windows=tuple(args.trend_windows), + include_market=True, + include_cross_pool=False, # No DEX peer features + ) + n_pools = data["n_pools"] + n_feat = data["n_feat"] + print(f" {len(data['pool_idx'])} samples, {n_pools} pools," + f" {n_feat} features, {time.time() - t0:.1f}s") + + # Append pool attributes if requested + if args.pool_attrs: + from quantammsim.calibration.pool_data import build_pool_attributes + X_attr, attr_names, _ = build_pool_attributes(matched_clean) + # Standardize + attr_mean = X_attr.mean(axis=0) + attr_std = X_attr.std(axis=0) + attr_std[attr_std < 1e-6] = 1.0 + X_attr_norm = ((X_attr - attr_mean) / attr_std).astype(np.float32) + + # Broadcast to per-sample: each sample gets its pool's attributes + pool_idx = data["pool_idx"] + x_attr_samples = X_attr_norm[pool_idx] + data["x"] = np.concatenate([data["x"], x_attr_samples], axis=1) + data["n_feat"] = data["x"].shape[1] + data["feat_names"] = data["feat_names"] + attr_names + n_feat = data["n_feat"] + print(f" + {len(attr_names)} pool attributes → {n_feat} total features") + + print(f" Features: {data['feat_names']}") + + if args.tune > 0: + run_optuna(data, args.tune) + return + + # Split + if args.no_split: + train_data = data + eval_data = None + else: + day_idx = data["day_idx"] + n_samples = len(day_idx) + split_day = int(day_idx.max() * 0.7) + train_mask = day_idx <= split_day + eval_mask = day_idx > split_day + train_data = {k: v[train_mask] if isinstance(v, np.ndarray) + and v.shape[0] == n_samples else v + for k, v in data.items()} + eval_data = {k: v[eval_mask] if isinstance(v, np.ndarray) + and v.shape[0] == n_samples else v + for k, v in data.items()} + + # Init + params = init_mlp_params( + jax.random.PRNGKey(42), n_feat, args.hidden, n_pools, + data["init_log_cadences"], per_pool=args.per_pool) + + n_params = sum(v.size for v in params.values()) + print(f"\n Total params: {n_params}" + f" (MLP: {n_params - n_pools - (n_pools if args.per_pool else 0)}," + f" cadence: {n_pools}" + f"{',' + str(n_pools) + ' pool biases' if args.per_pool else ''})") + + # Warm-start output layer via OLS through hidden activations + x_trn = jnp.array(train_data["x"]) + y_trn = np.array(train_data["y_total"]) + h = np.array(x_trn) + i = 0 + while f"W{i}" in params: + h = np.maximum(h @ np.array(params[f"W{i}"]) + np.array(params[f"b{i}"]), 0.0) + i += 1 + h_bias = np.concatenate([h, np.ones((h.shape[0], 1))], axis=1) + sol, _, _, _ = np.linalg.lstsq(h_bias, y_trn[:, None], rcond=None) + params["W_out"] = jnp.array(sol[:-1].astype(np.float32)) + params["b_out"] = jnp.array(sol[-1:].astype(np.float32)) + print(f" OLS warm-start on hidden activations") + + # Train + grad_fn = make_loss_fn(data["pool_coeffs"], data["pool_gas"], n_pools) + + print(f"\n Compiling + training...") + t0 = time.time() + params = train(params, train_data, grad_fn, args.epochs, args.lr, + args.l2_alpha, args.huber_delta, + use_cosine=args.cosine) + print(f" Training: {time.time() - t0:.1f}s") + + # Evaluate + if eval_data is not None: + print("\n --- Train ---") + evaluate(params, train_data) + print("\n --- Eval ---") + evaluate(params, eval_data) + else: + print("\n --- All data ---") + evaluate(params, train_data) + + print(f"\n Baselines:") + print(f" Linear (no cross-pool): median R² ≈ 0.48") + print(f" Linear (with cross-pool): median R² ≈ 0.53") + print(f" Per-pool linear: median R² ≈ 0.61") + + # Save artifact + if args.save_artifact: + import json + os.makedirs(args.save_artifact, exist_ok=True) + # Save params as npz + save_dict = {k: np.array(v) for k, v in params.items()} + save_dict["x_mean"] = data["x_mean"] if "x_mean" in data else np.zeros(n_feat) + save_dict["x_std"] = data["x_std"] if "x_std" in data else np.ones(n_feat) + np.savez(os.path.join(args.save_artifact, "model.npz"), **save_dict) + # Save meta + meta = { + "pool_ids": data["pool_ids"], + "feat_names": data["feat_names"], + "n_feat": n_feat, + "hidden": args.hidden, + "per_pool": args.per_pool, + "hparams": { + "hidden": args.hidden, + "lr": args.lr, + "l2_alpha": args.l2_alpha, + "huber_delta": args.huber_delta, + "epochs": args.epochs, + "trend_windows": args.trend_windows, + "use_cosine": args.cosine, + "per_pool": args.per_pool, + }, + } + with open(os.path.join(args.save_artifact, "meta.json"), "w") as f: + json.dump(meta, f, indent=2) + print(f"\n Saved artifact to: {args.save_artifact}") + + +if __name__ == "__main__": + main() diff --git a/experiments/run_model_comparison.py b/experiments/run_model_comparison.py new file mode 100644 index 0000000..398ec48 --- /dev/null +++ b/experiments/run_model_comparison.py @@ -0,0 +1,338 @@ +"""Compare linear vs MLP noise models across TVL levels. + +Evaluates both noise models for a given pool over the same date range, +sweeping initial TVL. Uses real price data, the PCHIP arb grid, and +both noise models to predict daily volume decomposition. + +Produces a plot: predicted daily noise volume vs TVL for each model, +with the real observed volume overlaid where available. + +Usage: + python experiments/run_model_comparison.py + python experiments/run_model_comparison.py --tvl-range 1e5 1e6 5e6 7e6 20e6 50e6 +""" + +import argparse +import json +import os +import pickle +import time + +import matplotlib +matplotlib.use("Agg") +import matplotlib.pyplot as plt +import numpy as np +import pandas as pd + +import jax.numpy as jnp + + +CACHE_DIR = os.path.join( + os.path.dirname(os.path.dirname(__file__)), + "results", "token_factored_calibration", "_cache", +) +LINEAR_DIR = os.path.join( + os.path.dirname(os.path.dirname(__file__)), + "results", "linear_market_noise", +) +MLP_DIR = os.path.join( + os.path.dirname(os.path.dirname(__file__)), + "results", "mlp_noise", +) + + +def load_linear_model(artifact_dir, pool_id): + """Load linear noise model for a pool.""" + art = np.load(os.path.join(artifact_dir, "model.npz")) + with open(os.path.join(artifact_dir, "meta.json")) as f: + meta = json.load(f) + pool_ids = meta["pool_ids"] + idx = next((i for i, p in enumerate(pool_ids) + if p.startswith(pool_id) or pool_id.startswith(p)), -1) + nc = art["noise_coeffs"] + coeffs = nc[idx] if nc.ndim == 2 and idx >= 0 else (nc if nc.ndim == 1 else np.median(nc, axis=0)) + return { + "coeffs": coeffs, + "log_cadence": art["log_cadence"][idx] if idx >= 0 else np.log(10.0), + "x_mean": art["x_mean"], + "x_std": art["x_std"], + "feat_names": meta["feat_names"], + "type": "linear", + } + + +def load_mlp_model(artifact_dir, pool_id): + """Load MLP noise model.""" + art = dict(np.load(os.path.join(artifact_dir, "model.npz"), allow_pickle=True)) + with open(os.path.join(artifact_dir, "meta.json")) as f: + meta = json.load(f) + pool_ids = meta["pool_ids"] + idx = next((i for i, p in enumerate(pool_ids) + if p.startswith(pool_id) or pool_id.startswith(p)), -1) + + # Extract MLP params + params = {} + for k in art: + if k.startswith("W") or k.startswith("b") or k == "log_cadence" or k == "pool_bias": + params[k] = art[k] + + return { + "params": params, + "log_cadence": art["log_cadence"][idx] if idx >= 0 else np.log(10.0), + "pool_idx": idx, + "x_mean": art["x_mean"], + "x_std": art["x_std"], + "feat_names": meta["feat_names"], + "hidden": meta["hidden"], + "per_pool": meta.get("per_pool", False), + "type": "mlp", + } + + +def predict_noise_linear(model, x_daily, tvl_values): + """Predict noise volume at multiple TVL levels using linear model.""" + tvl_col = 1 # xobs_1 + results = {} + for tvl in tvl_values: + x = x_daily.copy() + x[:, tvl_col] = (np.log(tvl) - model["x_mean"][tvl_col]) / model["x_std"][tvl_col] + # Update TVL interaction terms + for i, name in enumerate(model["feat_names"]): + if name.startswith("xobs_1\u00d7"): + paired = name.split("\u00d7")[1] + if paired in model["feat_names"]: + j = model["feat_names"].index(paired) + x[:, i] = x[:, tvl_col] * x_daily[:, j] + log_noise = x @ model["coeffs"] + results[tvl] = np.exp(log_noise) + return results + + +def predict_noise_mlp(model, x_daily, tvl_values): + """Predict noise volume at multiple TVL levels using MLP model.""" + from experiments.run_mlp_noise import forward_mlp + tvl_col = 1 + params = model["params"] + pool_idx_arr = (jnp.full(x_daily.shape[0], model["pool_idx"]) + if model["per_pool"] and model["pool_idx"] >= 0 else None) + results = {} + for tvl in tvl_values: + x = x_daily.copy() + x[:, tvl_col] = (np.log(tvl) - model["x_mean"][tvl_col]) / model["x_std"][tvl_col] + for i, name in enumerate(model["feat_names"]): + if name.startswith("xobs_1\u00d7"): + paired = name.split("\u00d7")[1] + if paired in model["feat_names"]: + j = model["feat_names"].index(paired) + x[:, i] = x[:, tvl_col] * x_daily[:, j] + log_noise = np.array(forward_mlp(params, jnp.array(x), pool_idx_arr)) + results[tvl] = np.exp(log_noise) + return results + + +def main(): + parser = argparse.ArgumentParser(description=__doc__, + formatter_class=argparse.RawDescriptionHelpFormatter) + parser.add_argument("--pool", default="0x9d1fcf346ea1b0") + parser.add_argument("--tvl-range", type=float, nargs="+", + default=[100_000, 500_000, 1_000_000, 5_000_000, + 7_000_000, 20_000_000, 50_000_000]) + parser.add_argument("--linear-dir", default=LINEAR_DIR) + parser.add_argument("--mlp-dir", default=MLP_DIR) + parser.add_argument("--output-dir", default="results/model_comparison") + args = parser.parse_args() + + os.environ.setdefault("JAX_PLATFORMS", "cpu") + os.makedirs(args.output_dir, exist_ok=True) + + # Load data + with open(os.path.join(CACHE_DIR, "stage1.pkl"), "rb") as f: + data = pickle.load(f) + mc = data["matched_clean"] + oc = data["option_c_clean"] + + pid = args.pool + entry = mc[pid] + panel = entry["panel"] + dates = pd.to_datetime(panel["date"]) + vol_obs = np.exp(panel["log_volume"].values.astype(float)) + tvl_obs = np.exp(panel["log_tvl_lag1"].values.astype(float)) + + print(f"Pool: {pid} ({entry['tokens']}, {entry['chain']})") + print(f"{len(dates)} days: {dates.min().date()} → {dates.max().date()}") + + # Build feature matrix (same for both models) + from experiments.run_linear_market_noise import build_data + data_full = build_data(mc, oc, trend_windows=(7,), + include_market=True, include_cross_pool=False) + pool_ids = data_full["pool_ids"] + pool_i = pool_ids.index(pid) + pool_mask = data_full["pool_idx"] == pool_i + x_pool = data_full["x"][pool_mask] + day_idx = data_full["day_idx"][pool_mask] + sgd = data_full["sample_grid_days"][pool_mask] + + all_dates = set() + for p in pool_ids: + all_dates.update(mc[p]["panel"]["date"].values) + date_list = sorted(all_dates) + sample_dates = np.array([pd.Timestamp(date_list[d]) for d in day_idx]) + + n_days = len(sample_dates) + print(f"Feature samples: {n_days}") + + # Load models + print("\nLoading models...") + linear_model = load_linear_model(args.linear_dir, pid) + print(f" Linear: {len(linear_model['coeffs'])} coefficients," + f" cadence={np.exp(linear_model['log_cadence']):.1f}min") + + has_mlp = os.path.exists(os.path.join(args.mlp_dir, "model.npz")) + if has_mlp: + mlp_model = load_mlp_model(args.mlp_dir, pid) + print(f" MLP: hidden={mlp_model['hidden']}," + f" cadence={np.exp(mlp_model['log_cadence']):.1f}min") + else: + print(f" MLP: no artifact at {args.mlp_dir}") + mlp_model = None + + # V_arb from PCHIP (same for both — uses linear model's cadence) + from quantammsim.calibration.grid_interpolation import interpolate_pool_daily + cadence = float(np.exp(linear_model["log_cadence"])) + gas = float(np.exp(oc[pid]["log_gas"])) + v_arb_all = np.array(interpolate_pool_daily( + entry["coeffs"], jnp.float64(np.log(cadence)), jnp.float64(gas))) + v_arb = v_arb_all[sgd] + + # Predict at each TVL + print(f"\nPredicting noise at {len(args.tvl_range)} TVL levels...") + linear_noise = predict_noise_linear(linear_model, x_pool, args.tvl_range) + mlp_noise = predict_noise_mlp(mlp_model, x_pool, args.tvl_range) if mlp_model else {} + + # Real observed volume for comparison + tvl_for_samples = np.zeros(n_days) + vol_for_samples = np.zeros(n_days) + for i, sd in enumerate(sample_dates): + matches = np.where(dates == sd)[0] + if len(matches) > 0: + tvl_for_samples[i] = tvl_obs[matches[0]] + vol_for_samples[i] = vol_obs[matches[0]] + + # ---- Plot 1: Median noise volume vs TVL ---- + fig, axes = plt.subplots(1, 2, figsize=(14, 6)) + + tvls = np.array(args.tvl_range) + lin_medians = np.array([np.median(linear_noise[t]) for t in tvls]) + lin_q25 = np.array([np.percentile(linear_noise[t], 25) for t in tvls]) + lin_q75 = np.array([np.percentile(linear_noise[t], 75) for t in tvls]) + + ax = axes[0] + ax.fill_between(tvls / 1e6, lin_q25 / 1e6, lin_q75 / 1e6, + alpha=0.2, color="steelblue") + ax.plot(tvls / 1e6, lin_medians / 1e6, "o-", color="steelblue", + linewidth=2, label="Linear noise (median)") + + if mlp_noise: + mlp_medians = np.array([np.median(mlp_noise[t]) for t in tvls]) + mlp_q25 = np.array([np.percentile(mlp_noise[t], 25) for t in tvls]) + mlp_q75 = np.array([np.percentile(mlp_noise[t], 75) for t in tvls]) + ax.fill_between(tvls / 1e6, mlp_q25 / 1e6, mlp_q75 / 1e6, + alpha=0.2, color="coral") + ax.plot(tvls / 1e6, mlp_medians / 1e6, "s-", color="coral", + linewidth=2, label="MLP noise (median)") + + # Add real observed volume at real TVL + valid = tvl_for_samples > 100 + ax.scatter(tvl_for_samples[valid] / 1e6, vol_for_samples[valid] / 1e6, + c="black", s=3, alpha=0.2, label="Observed total vol", zorder=1) + + ax.set_xlabel("Effective TVL ($M)") + ax.set_ylabel("Daily volume ($M)") + ax.set_xscale("log") + ax.set_yscale("log") + ax.set_title(f"{entry['tokens']} — Noise Volume vs TVL") + ax.legend(fontsize=8) + ax.grid(True, alpha=0.3) + + # ---- Plot 2: Noise/TVL ratio vs TVL ---- + ax = axes[1] + ax.plot(tvls / 1e6, lin_medians / tvls * 100, "o-", color="steelblue", + linewidth=2, label="Linear noise/TVL") + if mlp_noise: + ax.plot(tvls / 1e6, mlp_medians / tvls * 100, "s-", color="coral", + linewidth=2, label="MLP noise/TVL") + + # Real vol/TVL + ax.scatter(tvl_for_samples[valid] / 1e6, + vol_for_samples[valid] / tvl_for_samples[valid] * 100, + c="black", s=3, alpha=0.2, label="Observed vol/TVL") + + ax.set_xlabel("Effective TVL ($M)") + ax.set_ylabel("Noise / TVL (%)") + ax.set_xscale("log") + ax.set_title("Noise as Fraction of TVL") + ax.legend(fontsize=8) + ax.grid(True, alpha=0.3) + + fig.suptitle(f"Linear vs MLP Noise Model — {entry['tokens']}", fontsize=12) + fig.tight_layout() + out = os.path.join(args.output_dir, f"{pid[:16]}_model_comparison.png") + fig.savefig(out, dpi=150, bbox_inches="tight") + plt.close(fig) + print(f"\nSaved: {out}") + + # ---- Plot 3: Time series at selected TVLs ---- + fig, axes = plt.subplots(len(args.tvl_range), 1, + figsize=(14, 3 * len(args.tvl_range)), + sharex=True) + if len(args.tvl_range) == 1: + axes = [axes] + + for k, tvl in enumerate(args.tvl_range): + ax = axes[k] + v_total_lin = v_arb + linear_noise[tvl] + ax.plot(sample_dates, v_total_lin / 1e6, "b-", linewidth=0.6, + alpha=0.7, label="Linear (arb+noise)") + if mlp_noise: + v_total_mlp = v_arb + mlp_noise[tvl] + ax.plot(sample_dates, v_total_mlp / 1e6, "r-", linewidth=0.6, + alpha=0.7, label="MLP (arb+noise)") + ax.plot(sample_dates, vol_for_samples / 1e6, "k-", linewidth=0.5, + alpha=0.3, label="Observed (at real TVL)") + ax.set_ylabel(f"$M/day\nTVL=${tvl/1e6:.1f}M") + ax.set_yscale("log") + if k == 0: + ax.legend(fontsize=7, loc="upper right") + ax.grid(True, alpha=0.3) + + axes[-1].set_xlabel("Date") + fig.suptitle(f"Volume Time Series at Different TVLs — {entry['tokens']}", fontsize=11) + fig.tight_layout() + out2 = os.path.join(args.output_dir, f"{pid[:16]}_tvl_sweep_timeseries.png") + fig.savefig(out2, dpi=150, bbox_inches="tight") + plt.close(fig) + print(f"Saved: {out2}") + + # Summary table + print(f"\n{'='*70}") + print(f"Summary: Median daily noise volume by TVL") + print(f"{'='*70}") + print(f"{'TVL':>14s} {'Linear':>12s} {'Lin/TVL':>8s}", end="") + if mlp_noise: + print(f" {'MLP':>12s} {'MLP/TVL':>8s} {'MLP/Lin':>8s}") + else: + print() + + for tvl in args.tvl_range: + lin = np.median(linear_noise[tvl]) + print(f"${tvl:>13,.0f} ${lin:>11,.0f} {lin/tvl*100:>7.1f}%", end="") + if mlp_noise: + mlp = np.median(mlp_noise[tvl]) + ratio = mlp / lin if lin > 0 else 0 + print(f" ${mlp:>11,.0f} {mlp/tvl*100:>7.1f}% {ratio:>7.2f}x") + else: + print() + + +if __name__ == "__main__": + main() diff --git a/experiments/run_pool_battery.py b/experiments/run_pool_battery.py new file mode 100644 index 0000000..def2c47 --- /dev/null +++ b/experiments/run_pool_battery.py @@ -0,0 +1,914 @@ +"""Sim-vs-world gas + arb-frequency calibration for on-chain reClAMM pools. + +For each pool in the registry, runs quantammsim forward passes with exact +on-chain parameters across a 2D grid of (gas_cost, arb_frequency), then +compares the simulated pool value trajectory against the actual on-chain +trajectory. + +arb_frequency is the period between arb trades in minutes (1 = every minute, +the most aggressive; higher = sparser arb). + +Generalizes scripts/sim_vs_world_comparison.py to work for any pool. + +Usage: + cd /Users/matthew/Projects/quantammsim-reclamm + source ~/miniconda3/etc/profile.d/conda.sh && conda activate qsim-reclamm + + # Single pool (default gas + arb_freq grid) + python experiments/run_pool_battery.py cbBTC_WETH + + # All pools with data available + python experiments/run_pool_battery.py --all + + # Custom grids + python experiments/run_pool_battery.py cbBTC_WETH --gas-costs 0.0 0.5 1.0 --arb-freqs 1 5 15 60 + + # Dry run (show config without running) + python experiments/run_pool_battery.py cbBTC_WETH --dry-run + + # List available pools + python experiments/run_pool_battery.py --list +""" + +import argparse +import json +import os +import time + +import jax.numpy as jnp +import matplotlib +matplotlib.use("Agg") +import matplotlib.pyplot as plt +import numpy as np +from datetime import datetime, timezone + +from experiments.pool_registry import ( + POOL_REGISTRY, + PoolConfig, + extract_initial_state, + extract_on_chain_state, + get_data_end_date, + get_gas_costs, + load_bpt_supply_df, + load_gas_csv, + load_world_history, + print_pool_summary, +) +from quantammsim.runners.jax_runners import do_run_on_historic_data + +PROTOCOL_FEE_SPLIT = 0.5 +DEFAULT_ARB_FREQS = [1, 2, 3, 5, 10, 15, 20] + + +# --------------------------------------------------------------------------- +# Helpers +# --------------------------------------------------------------------------- + +def sample_at_timestamps(minute_vals, start_unix_sec, timestamps_sec): + """Sample a minute-level array at specific Unix timestamps. + + For each target timestamp, finds the nearest minute index in the + sim output and returns the corresponding value. + """ + indices = np.round((timestamps_sec - start_unix_sec) / 60).astype(int) + indices = np.clip(indices, 0, len(minute_vals) - 1) + return minute_vals[indices] + + +def compute_log_rmse(sim_growth, world_growth): + """RMSE of log(sim/world) across all trajectory points. + + Symmetric in over/under-estimation, natural for multiplicative processes. + A score of 0.02 means typical 2% deviation at any point in time. + """ + log_ratio = np.log(sim_growth / world_growth) + return np.sqrt(np.mean(log_ratio ** 2)) + + +def _start_str_from_pool(pool): + """Derive sim start time from pool's plausible_start, rounded to minute.""" + ts = int( + datetime.strptime(pool.plausible_start, "%Y-%m-%d") + .replace(tzinfo=timezone.utc) + .timestamp() + ) + ts_minute = (ts // 60) * 60 + return datetime.utcfromtimestamp(ts_minute).strftime("%Y-%m-%d %H:%M:%S") + + +def _onchain_params_to_sim(pool): + """Map DB param names to quantammsim param dict (jnp arrays).""" + p = pool.on_chain_params + return { + "price_ratio": jnp.array(p["price_ratio"]), + "centeredness_margin": jnp.array(p["margin"]), + "shift_exponent": jnp.array(p["shift_rate"]), + } + + +# --------------------------------------------------------------------------- +# Core sim runner +# --------------------------------------------------------------------------- + +def run_sim(pool, gas_cost, arb_frequency, initial_state, start, end, + protocol_fee_split=PROTOCOL_FEE_SPLIT, lp_supply_df=None, + noise_config=None): + """Run a single forward pass with exact on-chain params. + + gas_cost can be: + - float: flat gas cost in USD (e.g. 0.0, 0.5) + - str: gas percentile label (e.g. "50p", "90p") — loads time-varying + gas from CSV + + noise_config can be: + - None: no noise model (arb-only, default) + - dict with keys 'noise_model' and 'reclamm_noise_params': inject + Tsoukalas noise model into the sim + + Returns dict with minute-level per-LP value (USD), prices (USD per token), + and start_unix_sec. When lp_supply_df is provided, value_usd is divided + by the interpolated LP supply so it is comparable to BPT-normalized world + balances. + """ + params = _onchain_params_to_sim(pool) + + # Resolve gas: percentile string → DataFrame, float → scalar + gas_cost_df = None + if isinstance(gas_cost, str): + gas_cost_df = load_gas_csv(gas_cost) + flat_gas = 0.0 # placeholder; gas_cost_df overrides + else: + flat_gas = gas_cost + + fp = { + "tokens": pool.tokens, + "rule": "reclamm", + "startDateString": start, + "endDateString": end, + "initial_pool_value": pool.initial_pool_value_usd, + "fees": pool.swap_fee, + "gas_cost": flat_gas, + "arb_fees": 0.0, + "do_arb": True, + "arb_frequency": arb_frequency, + "chunk_period": 1440, + "weight_interpolation_period": 1440, + "reclamm_use_shift_exponent": True, + "reclamm_interpolation_method": "geometric", + "reclamm_centeredness_scaling": False, + "protocol_fee_split": protocol_fee_split, + "reclamm_initial_state": initial_state, + } + + if noise_config is not None: + fp["noise_model"] = noise_config["noise_model"] + fp["reclamm_noise_params"] = noise_config["reclamm_noise_params"] + + result = do_run_on_historic_data( + run_fingerprint=fp, params=params, lp_supply_df=lp_supply_df, + gas_cost_df=gas_cost_df, + ) + + start_unix_sec = datetime.strptime( + start, "%Y-%m-%d %H:%M:%S" + ).replace(tzinfo=timezone.utc).timestamp() + + value_usd = np.array(result["value"]) + + # Normalize to per-LP value so comparison with BPT-normalized world is valid. + # + # Subtlety: the scan applies lp_supply every arb_frequency minutes. + # Between scan steps, reserves are constant (no arb), so the pool value + # reflects the lp_supply from the LAST scan step. If a BPT event occurs + # between scan steps (e.g. at minute 3 when arb_frequency=5), the value + # at minutes 3-4 still reflects the old lp_supply. We must divide by + # the scan-step-aligned lp_supply, not the current minute's lp_supply, + # otherwise we get transient spikes. + if lp_supply_df is not None: + n_minutes = len(value_usd) + # Map each minute to its most recent scan-step time + scan_step_minutes = ( + np.arange(n_minutes) // arb_frequency * arb_frequency + ) + scan_step_times_ms = ( + start_unix_sec * 1000 + scan_step_minutes * 60_000 + ) + lp_unix = np.array(lp_supply_df["unix"]) + lp_vals = np.array(lp_supply_df["lp_supply"]) + indices = np.searchsorted(lp_unix, scan_step_times_ms, side="right") - 1 + indices = np.clip(indices, 0, len(lp_vals) - 1) + value_usd = value_usd / lp_vals[indices] + + return { + "value_usd": value_usd, + "prices": np.array(result["prices"]), # (T, n_tokens) in USD + "start_unix_sec": start_unix_sec, + } + + +# --------------------------------------------------------------------------- +# Pool calibration (2D grid: gas_cost × arb_frequency) +# --------------------------------------------------------------------------- + +def run_pool_calibration(pool, gas_costs, arb_freqs, verbose=True, + noise_config=None): + """Run 2D gas × arb_frequency calibration for a single pool. + + Parameters + ---------- + noise_config : dict, optional + If provided, passed through to run_sim to inject noise model. + Keys: 'noise_model', 'reclamm_noise_params'. + + Returns dict with: + world_growth: array of world growth factors + sim_growths: {(gas_cost, arb_freq): growth array} + timestamps: world timestamps (seconds) + governance_idx: index of first governance event (or n_points) + n_points: number of comparison points + days: array of days from start + gas_costs: list of gas costs + arb_freqs: list of arb frequencies + """ + # Extract on-chain state + initial reserves + extract_on_chain_state(pool) + initial_state = extract_initial_state(pool) + + if verbose: + print_pool_summary(pool) + print(f" Initial state: Ra={initial_state['Ra']:.4f}, " + f"Rb={initial_state['Rb']:.4f}, " + f"Va={initial_state['Va']:.4f}, Vb={initial_state['Vb']:.4f}") + + start_str = _start_str_from_pool(pool) + end_str = get_data_end_date(pool.tokens) + + # Load BPT supply history for LP supply scaling + lp_supply_df = load_bpt_supply_df(pool, end_date=end_str) + + if verbose: + bpt_start = lp_supply_df["lp_supply"].iloc[0] + bpt_end = lp_supply_df["lp_supply"].iloc[-1] + print(f" BPT supply: {bpt_start:.4f} → {bpt_end:.4f} " + f"({(bpt_end/bpt_start - 1)*100:+.1f}%)") + print(f" Sim period: {start_str} to {end_str}") + n_runs = len(gas_costs) * len(arb_freqs) + print(f" Grid: {len(gas_costs)} gas × {len(arb_freqs)} arb_freq = {n_runs} runs") + + # Load world history (BPT-normalized balances + governance events) + # BPT-normalized is correct for the growth ratio metric since LP supply + # cancels out of sim_growth/world_growth. Raw balances are available + # in world["raw_bal_0/1"] for absolute trajectory comparison. + world = load_world_history(pool, end_date=end_str) + world_ts = world["timestamps"] + world_bal_0 = world["bal_0"] + world_bal_1 = world["bal_1"] + gov_events = world["governance_events"] + + if verbose: + print(f" World points: {len(world_ts)}") + if gov_events: + for ts, field, old, new in gov_events: + dt = datetime.utcfromtimestamp(ts).strftime("%Y-%m-%d") + print(f" Governance: {field} {old:.6f} -> {new:.6f} on {dt}") + else: + print(" No governance events") + + # Governance cutoff index + if gov_events: + gov_idx = np.searchsorted(world_ts, gov_events[0][0]) + else: + gov_idx = len(world_ts) + + # Run sims across the 2D grid + sim_results = {} + prices_min = None + start_sec = None + + for gc in gas_costs: + for af in arb_freqs: + if verbose: + print(f"\n Running gas=${gc}, arb_freq={af}min...") + t0 = time.time() + result = run_sim(pool, gc, af, initial_state, start_str, end_str, + lp_supply_df=lp_supply_df, + noise_config=noise_config) + elapsed = time.time() - t0 + if verbose: + print(f" Done in {elapsed:.1f}s") + sim_results[(gc, af)] = result + if prices_min is None: + prices_min = result["prices"] + start_sec = result["start_unix_sec"] + + # Truncate at governance + n = min(gov_idx, len(world_ts)) + world_ts_trunc = world_ts[:n] + + # Sample USD prices at world timestamps for world valuation + prices_at_world = np.stack([ + sample_at_timestamps(prices_min[:, i], start_sec, world_ts_trunc) + for i in range(prices_min.shape[1]) + ], axis=1) + + # World value in USD = sum(bal_i * price_usd_i) + world_value = ( + world_bal_0[:n] * prices_at_world[:, 0] + + world_bal_1[:n] * prices_at_world[:, 1] + ) + world_growth = world_value / world_value[0] + + # Sim growths at world timestamps + sim_growths = {} + for key, result in sim_results.items(): + sim_val = sample_at_timestamps( + result["value_usd"], start_sec, world_ts_trunc, + ) + sim_growths[key] = sim_val / sim_val[0] + + days = (world_ts_trunc - world_ts_trunc[0]) / 86400 + + return { + "world_growth": world_growth, + "sim_growths": sim_growths, + "timestamps": world_ts_trunc, + "governance_idx": gov_idx, + "n_points": n, + "days": days, + "gas_costs": list(gas_costs), + "arb_freqs": list(arb_freqs), + } + + +# --------------------------------------------------------------------------- +# Plotting +# --------------------------------------------------------------------------- + +def plot_pool_calibration(pool, calibration, output_dir="results", suffix=""): + """Plot 2D gas × arb_freq calibration as heatmap + time series. + + Left: heatmap of final % deviation (gas_cost × arb_freq). + Right: time series for each arb_freq at best gas cost. + """ + os.makedirs(output_dir, exist_ok=True) + + world_growth = calibration["world_growth"] + sim_growths = calibration["sim_growths"] + days = calibration["days"] + gas_costs = calibration["gas_costs"] + arb_freqs = calibration["arb_freqs"] + + # Build RMSE matrix (log ratio, %) + rmse_matrix = np.zeros((len(arb_freqs), len(gas_costs))) + for i, af in enumerate(arb_freqs): + for j, gc in enumerate(gas_costs): + rmse_matrix[i, j] = compute_log_rmse( + sim_growths[(gc, af)], world_growth + ) * 100 + + fig, (ax_heat, ax_ts) = plt.subplots( + 1, 2, figsize=(18, 7), + gridspec_kw={"width_ratios": [1, 1.5]}, + ) + + # Left: heatmap of trajectory RMSE + im = ax_heat.imshow( + rmse_matrix, aspect="auto", cmap="RdYlGn_r", + vmin=0, vmax=rmse_matrix.max(), origin="lower", + ) + ax_heat.set_xticks(range(len(gas_costs))) + gas_labels = [ + f"gas {gc}" if isinstance(gc, str) else f"${gc}" + for gc in gas_costs + ] + ax_heat.set_xticklabels(gas_labels, fontsize=9) + ax_heat.set_yticks(range(len(arb_freqs))) + ax_heat.set_yticklabels([f"{af}min" for af in arb_freqs], fontsize=9) + ax_heat.set_xlabel("gas cost (USD)") + ax_heat.set_ylabel("arb frequency (minutes)") + ax_heat.set_title("Trajectory RMSE (log ratio, %)") + + # Annotate cells + for i in range(len(arb_freqs)): + for j in range(len(gas_costs)): + val = rmse_matrix[i, j] + color = "white" if val > rmse_matrix.max() * 0.6 else "black" + ax_heat.text(j, i, f"{val:.2f}%", ha="center", va="center", + fontsize=8, color=color) + + # Mark cell with least-negative mean bias (closest to 0 from below). + # If no cell is below world on average, fall back to lowest RMSE. + bias_matrix = np.zeros_like(rmse_matrix) + for i, af in enumerate(arb_freqs): + for j, gc in enumerate(gas_costs): + bias_matrix[i, j] = float(np.mean( + np.log(sim_growths[(gc, af)] / world_growth) + )) + negative_mask = bias_matrix < 0 + if negative_mask.any(): + # Among negative cells, find the one closest to 0 (max value) + masked = np.where(negative_mask, bias_matrix, -np.inf) + best_idx = np.unravel_index(np.argmax(masked), masked.shape) + else: + best_idx = np.unravel_index(np.argmin(rmse_matrix), rmse_matrix.shape) + ax_heat.add_patch(plt.Rectangle( + (best_idx[1] - 0.5, best_idx[0] - 0.5), 1, 1, + fill=False, edgecolor="lime", linewidth=3, + )) + + fig.colorbar(im, ax=ax_heat, label="RMSE (%)", shrink=0.8) + + # Right: 4 closest from below + 1 first above world + # Rationale: sim should underestimate (can't capture organic swaps, MEV + # rebates, etc.), so being below world is expected. The one-above config + # brackets where the sim crosses from conservative to optimistic. + ax_ts.axhline(y=0.0, color="brown", linewidth=2, label="world (on-chain)") + + # Classify configs by mean log ratio (trajectory-average bias). + # Using the mean rather than endpoint avoids a curve that's above + # world for 80% of the trajectory being classified as "below" + # just because it dips at the end. + below = [] # (mean_bias, rmse, gc, af) where sim < world on average + above = [] # (mean_bias, rmse, gc, af) where sim >= world on average + for (gc, af), sg in sim_growths.items(): + mean_bias = float(np.mean(np.log(sg / world_growth))) + rmse = compute_log_rmse(sg, world_growth) + if mean_bias < 0: + below.append((mean_bias, rmse, gc, af)) + else: + above.append((mean_bias, rmse, gc, af)) + + # Sort: below by mean_bias descending (closest to 0 first) + below.sort(key=lambda x: x[0], reverse=True) + # Sort: above by mean_bias ascending (closest to 0 first) + above.sort(key=lambda x: x[0]) + + # Select: up to 4 from below, 1 from above, fill if needed + selected = [] + n_below = min(4, len(below)) + n_above = min(1, len(above)) + selected.extend(below[:n_below]) + selected.extend(above[:n_above]) + remaining = 5 - len(selected) + if remaining > 0 and len(below) > n_below: + selected.extend(below[n_below:n_below + remaining]) + remaining = 5 - len(selected) + if remaining > 0 and len(above) > n_above: + selected.extend(above[n_above:n_above + remaining]) + + colors_below = plt.cm.Blues(np.linspace(0.4, 0.8, n_below)) + colors_above = np.array([[0.8, 0.2, 0.2, 1.0]]) # red for above + plot_colors = list(colors_below) + list(colors_above[:n_above]) + # Fill remaining with grey + while len(plot_colors) < len(selected): + plot_colors.append([0.5, 0.5, 0.5, 1.0]) + + for rank, (mean_bias, rmse, gc, af) in enumerate(selected): + dev = (sim_growths[(gc, af)] / world_growth - 1) * 100 + gc_label = f"gas {gc}" if isinstance(gc, str) else f"gas=${gc}" + marker = "\u25b2" if mean_bias >= 0 else "\u25bc" # ▲ above, ▼ below + ax_ts.plot(days, dev, color=plot_colors[rank], linewidth=2, + label=f"{marker} {gc_label}, arb={af}min " + f"bias={mean_bias*100:+.2f}% RMSE={rmse*100:.2f}%") + + ax_ts.set_xlabel("days") + ax_ts.set_ylabel("% deviation from world") + trunc = " (pre-governance)" if calibration["governance_idx"] < calibration["n_points"] + 1 else "" + ax_ts.set_title(f"Best bracket: {n_below} below + {n_above} above world{trunc}") + ax_ts.legend(fontsize=7, loc="best") + ax_ts.grid(True, alpha=0.2) + + p = pool.on_chain_params + fig.suptitle( + f"{pool.label} ({pool.chain}) — {pool.tokens[0]}/{pool.tokens[1]}\n" + f"PR={p['price_ratio']:.4f} margin={p['margin']} " + f"shift={p['shift_rate']} fee={pool.swap_fee} " + f"TVL=${pool.initial_pool_value_usd:,.0f} " + f"protocol_fee={PROTOCOL_FEE_SPLIT}", + fontsize=10, + ) + plt.tight_layout() + + out = os.path.join(output_dir, f"gas_calibration_{pool.label}{suffix}.png") + plt.savefig(out, dpi=150, bbox_inches="tight") + plt.close() + print(f" Saved: {out}") + return out + + +def plot_cross_pool_summary(all_results, output_dir="results"): + """Plot cross-pool comparison: best (gas, arb_freq) and residual deviation.""" + os.makedirs(output_dir, exist_ok=True) + + pool_labels = [] + best_configs = [] + best_devs = [] + + best_rmses = [] + best_biases = [] + for pool, cal in all_results: + wg = cal["world_growth"] + # Best = least-negative mean bias (closest from below) + below_keys = [ + k for k in cal["sim_growths"] + if np.mean(np.log(cal["sim_growths"][k] / wg)) < 0 + ] + if below_keys: + best_key = max( + below_keys, + key=lambda k: np.mean(np.log(cal["sim_growths"][k] / wg)), + ) + else: + best_key = min( + cal["sim_growths"].keys(), + key=lambda k: compute_log_rmse(cal["sim_growths"][k], wg), + ) + best_rmse = compute_log_rmse(cal["sim_growths"][best_key], wg) * 100 + best_bias = float(np.mean(np.log(cal["sim_growths"][best_key] / wg))) * 100 + pool_labels.append(f"{pool.label}\n({pool.chain})") + best_configs.append(best_key) + best_rmses.append(best_rmse) + best_biases.append(best_bias) + + fig, (ax_cfg, ax_dev) = plt.subplots(1, 2, figsize=(16, 6)) + + x = np.arange(len(pool_labels)) + + # Left: RMSE at best config + config_strs = [ + f"gas {gc}\narb={af}min" if isinstance(gc, str) + else f"gas=${gc}\narb={af}min" + for gc, af in best_configs + ] + ax_cfg.barh(x, best_rmses, color="steelblue") + ax_cfg.set_yticks(x) + ax_cfg.set_yticklabels(pool_labels, fontsize=9) + ax_cfg.set_xlabel("Trajectory RMSE (%)") + ax_cfg.set_title("RMSE at best config") + for i, (cs, rmse) in enumerate(zip(config_strs, best_rmses)): + ax_cfg.text(rmse + 0.05, i, f"{cs} (RMSE={rmse:.2f}%)", va="center", fontsize=8) + ax_cfg.grid(True, alpha=0.2, axis="x") + + # Right: mean bias at best config (negative = conservative) + colors = ["green" if d < 0 else "orange" if d < 1 else "red" + for d in best_biases] + ax_dev.bar(x, best_biases, color=colors) + ax_dev.axhline(y=0, color="brown", linewidth=1) + ax_dev.set_xticks(x) + ax_dev.set_xticklabels(pool_labels, fontsize=8) + ax_dev.set_ylabel("Mean bias (%)") + ax_dev.set_title("Mean trajectory bias at best config") + ax_dev.grid(True, alpha=0.2, axis="y") + + fig.suptitle("Cross-pool gas + arb frequency calibration", fontsize=12) + plt.tight_layout() + + out = os.path.join(output_dir, "gas_calibration_cross_pool.png") + plt.savefig(out, dpi=150, bbox_inches="tight") + plt.close() + print(f"\nSaved cross-pool summary: {out}") + return out + + +# --------------------------------------------------------------------------- +# Summary printing +# --------------------------------------------------------------------------- + +def print_calibration_summary(pool, calibration): + """Print a summary table of the 2D grid results (trajectory RMSE).""" + world_growth = calibration["world_growth"] + sim_growths = calibration["sim_growths"] + n_days = calibration["days"][-1] + gas_costs = calibration["gas_costs"] + arb_freqs = calibration["arb_freqs"] + + print(f"\n {pool.label} ({pool.chain}) — {n_days:.0f} days") + print(f" World growth: {world_growth[-1]:.4f}") + + # Print as table: rows=arb_freq, cols=gas_cost (values = trajectory RMSE %) + col_label = "arb\\gas" + gas_labels = [ + f"gas {gc}" if isinstance(gc, str) else f"${gc}" + for gc in gas_costs + ] + header = f" {col_label:<10}" + "".join(f"{gl:<10}" for gl in gas_labels) + print(header) + print(f" {'-'*len(header)}") + for af in arb_freqs: + row = f" {af:>4}min " + for gc in gas_costs: + rmse = compute_log_rmse( + sim_growths[(gc, af)], world_growth + ) * 100 + row += f"{rmse:>8.2f}% " + print(row) + + +# --------------------------------------------------------------------------- +# Data availability check +# --------------------------------------------------------------------------- + +def check_data_available(pool, data_root=None): + """Check that all required parquet files exist for a pool.""" + if data_root is None: + data_root = os.path.join( + os.path.dirname(__file__), "..", "quantammsim", "data" + ) + for ticker in pool.tokens: + if ticker == "USDC": + continue + path = os.path.join(data_root, f"{ticker}_USD.parquet") + if not os.path.exists(path): + return False + return True + + +# --------------------------------------------------------------------------- +# CLI +# --------------------------------------------------------------------------- + +def main(): + parser = argparse.ArgumentParser( + description="Sim-vs-world gas + arb-frequency calibration for on-chain reClAMM pools" + ) + parser.add_argument("pool", nargs="?", + help="Pool label (e.g. cbBTC_WETH)") + parser.add_argument("--all", action="store_true", + help="Run all pools with available data") + parser.add_argument("--list", action="store_true", + help="List available pools and exit") + parser.add_argument("--dry-run", action="store_true", + help="Show pool config without running") + parser.add_argument("--gas-costs", nargs="+", type=float, default=None, + help="Override gas cost battery (flat USD values)") + parser.add_argument("--arb-freqs", nargs="+", type=int, + default=DEFAULT_ARB_FREQS, + help="Arb frequency values in minutes (default: 1 2 3 5 10 15 20)") + parser.add_argument("--protocol-fee", type=float, default=PROTOCOL_FEE_SPLIT, + help="Protocol fee split (default 0.5)") + parser.add_argument("--output-dir", default="results", + help="Directory for output plots and JSON") + parser.add_argument("--calibrate-noise", action="store_true", + help="Calibrate Tsoukalas noise model from Balancer API + DB " + "and inject into sim fingerprints") + parser.add_argument("--noise-model", choices=["sqrt", "log", "loglinear"], + default="sqrt", + help="Noise model variant (default: sqrt)") + parser.add_argument("--noise-params-json", default=None, + help="Path to hierarchical noise params JSON " + "(from calibrate_noise_hierarchical.py). " + "Looks up pool by address or uses --predict.") + args = parser.parse_args() + + # --list mode + if args.list: + print("\nAvailable pools:\n") + for label, pool in POOL_REGISTRY.items(): + has_data = check_data_available(pool) + status = "READY" if has_data else "MISSING DATA" + try: + extract_on_chain_state(pool) + print_pool_summary(pool) + except Exception as e: + print(f" {label}: {e}") + print(f" Data: {status}") + return + + # Determine which pools to run + if args.all: + pool_labels = [ + label for label, pool in POOL_REGISTRY.items() + if check_data_available(pool) + ] + if not pool_labels: + print("No pools have all required data files.") + return + elif args.pool: + if args.pool not in POOL_REGISTRY: + print(f"Unknown pool: {args.pool}") + print(f"Available: {list(POOL_REGISTRY.keys())}") + return + if not check_data_available(POOL_REGISTRY[args.pool]): + missing = [ + f"{t}_USD.parquet" for t in POOL_REGISTRY[args.pool].tokens + if t != "USDC" and not os.path.exists( + os.path.join( + os.path.dirname(__file__), "..", "quantammsim", + "data", f"{t}_USD.parquet" + ) + ) + ] + print(f"Missing data for {args.pool}: {missing}") + return + pool_labels = [args.pool] + else: + parser.print_help() + return + + # Collect runs + runs = [] + for label in pool_labels: + pool = POOL_REGISTRY[label] + gas_costs = get_gas_costs(pool, args.gas_costs) + runs.append((pool, gas_costs)) + + arb_freqs = args.arb_freqs + n_total = sum(len(gcs) * len(arb_freqs) for _, gcs in runs) + + print(f"\n{'='*60}") + print(f"GAS + ARB CALIBRATION: {len(runs)} pool(s), {n_total} total runs") + for pool, gcs in runs: + print(f" {pool.label:15s} ({pool.chain:10s}) " + f"gas={gcs} arb_freq={arb_freqs}") + print(f" Protocol fee split: {args.protocol_fee}") + print(f"{'='*60}") + + if args.dry_run: + print("\n--- DRY RUN ---\n") + for pool, gas_costs in runs: + extract_on_chain_state(pool) + initial_state = extract_initial_state(pool) + print_pool_summary(pool) + print(f" Initial state: {initial_state}") + start = _start_str_from_pool(pool) + end = get_data_end_date(pool.tokens) + print(f" Sim period: {start} to {end}") + world = load_world_history(pool, end_date=end) + n_gov = len(world["governance_events"]) + print(f" World points: {len(world['timestamps'])}, " + f"governance events: {n_gov}") + if world["governance_events"]: + for ts, field, old, new in world["governance_events"]: + dt = datetime.utcfromtimestamp(ts).strftime("%Y-%m-%d") + print(f" {field} {old:.6f} -> {new:.6f} on {dt}") + print(f" Gas battery: {gas_costs}") + print(f" Arb freqs: {arb_freqs}") + n_runs = len(gas_costs) * len(arb_freqs) + print(f" Total runs: {n_runs}") + return + + # Execute calibration for each pool + all_results = [] + for pool, gas_costs in runs: + print(f"\n{'#'*60}") + print(f"POOL: {pool.label}") + print(f"{'#'*60}") + + # Noise calibration (if requested) + noise_config = None + if args.noise_params_json and args.noise_model == "loglinear": + # Load hierarchical noise params from JSON + with open(args.noise_params_json) as f: + hier_data = json.load(f) + # Look up pool by address + addr = pool.pool_address.lower() + pool_params = None + for p in hier_data["pools"]: + pid = p["pool_id"].lower().replace("0x", "") + if pid.startswith(addr) or addr.startswith(pid): + pool_params = p["noise_params"] + break + if pool_params is None: + # Fall back to population-level prediction + from scripts.calibrate_noise_hierarchical import predict_new_pool + chain_map = {"ethereum": "MAINNET", "base": "BASE", + "gnosis": "GNOSIS", "arbitrum": "ARBITRUM", + "polygon": "POLYGON", "optimism": "OPTIMISM", + "sonic": "SONIC", "avalanche": "AVALANCHE"} + api_chain = chain_map.get(pool.chain, pool.chain.upper()) + # Reconstruct posteriors + encoding from the JSON + posteriors_from_json = { + "Phi_mean": np.array(hier_data["Phi"]), + } + encoding_from_json = { + "covariate_names": hier_data["covariate_names"], + } + pool_params = predict_new_pool( + posteriors_from_json, encoding_from_json, + api_chain, pool.tokens, pool.swap_fee, + ) + print(f"\n Using population-level loglinear params (pool not in JSON)") + else: + print(f"\n Using hierarchical loglinear params for {pool.label}") + print(f" b_0 = {pool_params['b_0']:.4f}, " + f"b_sigma = {pool_params['b_sigma']:.6f}, " + f"b_c = {pool_params['b_c']:.4f}") + # Strip metadata keys (prefixed with _) — JAX can't trace strings + sim_params = {k: v for k, v in pool_params.items() + if not k.startswith("_")} + noise_config = { + "noise_model": "loglinear", + "reclamm_noise_params": sim_params, + } + elif args.calibrate_noise: + from scripts.calibrate_reclamm_noise import ( + build_calibration_df, + run_ols_calibration, + ) + noise_model_name = ( + "tsoukalas_sqrt" if args.noise_model == "sqrt" + else "tsoukalas_log" + ) + print(f"\n Calibrating noise model ({args.noise_model})...") + cal_df = build_calibration_df(pool) + noise_params, diag = run_ols_calibration( + cal_df, pool.swap_fee, args.noise_model, + ) + print(f" R² = {diag['r_squared']:.4f}, n = {diag['n_obs']}") + for key in ["a_0", "a_sigma", "a_c"]: + param_key = "a_0_base" if key == "a_0" else key + val = noise_params[param_key] + se = diag["se"][key] + t_stat = val / se if se > 0 else float("inf") + print(f" {key:>8} = {val:>10.4f} (SE={se:.4f}, t={t_stat:.2f})") + noise_config = { + "noise_model": noise_model_name, + "reclamm_noise_params": noise_params, + } + + t0 = time.time() + calibration = run_pool_calibration( + pool, gas_costs, arb_freqs, noise_config=noise_config, + ) + elapsed = time.time() - t0 + + print_calibration_summary(pool, calibration) + plot_pool_calibration(pool, calibration, output_dir=args.output_dir) + all_results.append((pool, calibration)) + + print(f"\n Total time for {pool.label}: {elapsed:.0f}s") + + # Cross-pool summary + if len(all_results) > 1: + plot_cross_pool_summary(all_results, output_dir=args.output_dir) + + # Final summary table + print(f"\n{'='*60}") + print("CALIBRATION COMPLETE") + print(f"{'='*60}") + print(f"\n{'Pool':<16} {'Chain':<10} {'Best Gas':>9} {'Best Arb':>9} " + f"{'Bias':>8} {'RMSE':>8} {'Days':>6}") + print("-" * 70) + for pool, cal in all_results: + wg = cal["world_growth"] + # Best = least-negative mean bias (closest from below) + below_keys = [ + k for k in cal["sim_growths"] + if np.mean(np.log(cal["sim_growths"][k] / wg)) < 0 + ] + if below_keys: + best_key = max( + below_keys, + key=lambda k: np.mean(np.log(cal["sim_growths"][k] / wg)), + ) + else: + best_key = min( + cal["sim_growths"].keys(), + key=lambda k: compute_log_rmse(cal["sim_growths"][k], wg), + ) + best_bias = float(np.mean(np.log(cal["sim_growths"][best_key] / wg))) * 100 + best_rmse = compute_log_rmse(cal["sim_growths"][best_key], wg) * 100 + n_days = cal["days"][-1] + gc_label = f"gas {best_key[0]}" if isinstance(best_key[0], str) else f"${best_key[0]}" + print(f"{pool.label:<16} {pool.chain:<10} {gc_label:<9} " + f"{best_key[1]:>4}min {best_bias:>+7.2f}% {best_rmse:>7.2f}% {n_days:>5.0f}d") + + # Save JSON summary + os.makedirs(args.output_dir, exist_ok=True) + summary = [] + for pool, cal in all_results: + wg_arr = cal["world_growth"] + wg_final = float(wg_arr[-1]) + pool_summary = { + "label": pool.label, + "chain": pool.chain, + "tokens": pool.tokens, + "swap_fee": pool.swap_fee, + "tvl_usd": pool.initial_pool_value_usd, + "on_chain_params": pool.on_chain_params, + "n_days": float(cal["days"][-1]), + "n_governance_events": 1 if cal["governance_idx"] < cal["n_points"] else 0, + "world_growth": wg_final, + "grid_results": {}, + } + for (gc, af) in sorted(cal["sim_growths"].keys(), key=lambda k: (str(k[0]), k[1])): + sg_arr = cal["sim_growths"][(gc, af)] + rmse = compute_log_rmse(sg_arr, wg_arr) * 100 + pool_summary["grid_results"][f"gas={gc}_arb={af}"] = { + "gas_cost": gc, + "arb_frequency": af, + "sim_growth": float(sg_arr[-1]), + "pct_deviation": float((sg_arr[-1] / wg_final - 1) * 100), + "trajectory_rmse_pct": float(rmse), + } + summary.append(pool_summary) + + ts = datetime.now().strftime("%Y%m%d_%H%M%S") + json_path = os.path.join(args.output_dir, f"gas_calibration_{ts}.json") + with open(json_path, "w") as f: + json.dump(summary, f, indent=2, default=str) + print(f"\nSummary saved to {json_path}") + + +if __name__ == "__main__": + main() diff --git a/experiments/run_residual_comparison.py b/experiments/run_residual_comparison.py new file mode 100644 index 0000000..201c403 --- /dev/null +++ b/experiments/run_residual_comparison.py @@ -0,0 +1,235 @@ +"""Apples-to-apples R² comparison on noise residuals. + +Target for all methods: r_it = log(V_total_it) - log(V_arb_it) + +Methods: + 1. Option C: log(1 + exp(x_obs @ noise_coeffs) / V_arb) + 2. AR1 on residuals: r_{i, t-1} + 3. Ridge on residuals (peers only, in-sample) + 4. Ridge on residuals (peers + own lag, in-sample) + 5. Constant zero (predict r=0, i.e. V_total = V_arb) +""" + +import os +import pickle +import sys + +import numpy as np +from sklearn.linear_model import RidgeCV + +CACHE_DIR = os.path.join( + os.path.dirname(os.path.dirname(__file__)), + "results", "token_factored_calibration", "_cache", +) + + +def load_stage1(): + path = os.path.join(CACHE_DIR, "stage1.pkl") + if not os.path.exists(path): + print("ERROR: no stage1 cache.") + sys.exit(1) + with open(path, "rb") as f: + data = pickle.load(f) + return data["matched_clean"], data["option_c_clean"] + + +def r2_score(y_true, y_pred): + ss_res = np.sum((y_true - y_pred) ** 2) + ss_tot = np.sum((y_true - y_true.mean()) ** 2) + return 1 - ss_res / max(ss_tot, 1e-10) + + +def main(): + os.environ.setdefault("JAX_PLATFORMS", "cpu") + + import jax.numpy as jnp + from quantammsim.calibration.grid_interpolation import interpolate_pool_daily + from quantammsim.calibration.pool_data import ( + K_OBS_REDUCED, build_x_obs, _parse_tokens, _canonicalize_token, + ) + + print("=" * 70) + print("Apples-to-Apples: All methods on noise residual target") + print(" target = log(V_total) - log(V_arb)") + print("=" * 70) + + matched_clean, option_c_clean = load_stage1() + pool_ids = sorted(matched_clean.keys()) + n_pools = len(pool_ids) + + # ---- Build aligned data per pool ---- + # For each pool: residual, Option C prediction of residual, dates + pool_data = {} + all_dates = set() + + for pid in pool_ids: + entry = matched_clean[pid] + oc = option_c_clean[pid] + panel = entry["panel"] + + # V_arb + v_arb_all = np.array(interpolate_pool_daily( + entry["coeffs"], + jnp.float64(oc["log_cadence"]), + jnp.float64(np.exp(oc["log_gas"])), + )) + v_arb = v_arb_all[entry["day_indices"]] + log_v_arb = np.log(np.maximum(v_arb, 1e-6)) + + # Observed + log_vol = panel["log_volume"].values.astype(float) + dates = panel["date"].values + + # Noise residual target + resid = log_vol - log_v_arb + + # Option C noise prediction (in residual space) + x_obs = build_x_obs(panel, reduced=True) + noise_coeffs = oc["noise_coeffs"][:K_OBS_REDUCED] + v_noise_oc = np.exp(x_obs @ noise_coeffs) + resid_pred_oc = np.log(np.maximum(1.0 + v_noise_oc / np.maximum(v_arb, 1e-6), 1e-10)) + + pool_data[pid] = { + "dates": dates, + "resid": resid, + "resid_pred_oc": resid_pred_oc, + "log_vol": log_vol, + "v_arb": v_arb, + } + all_dates.update(dates) + + # ---- Build residual matrix for cross-pool methods ---- + date_list = sorted(all_dates) + n_dates = len(date_list) + date_to_idx = {d: i for i, d in enumerate(date_list)} + + resid_matrix = np.full((n_dates, n_pools), np.nan) + for j, pid in enumerate(pool_ids): + pd = pool_data[pid] + for k, date in enumerate(pd["dates"]): + resid_matrix[date_to_idx[date], j] = pd["resid"][k] + + # ---- Token overlap for peer identification ---- + pool_tokens = {} + for i, pid in enumerate(pool_ids): + toks = _parse_tokens(matched_clean[pid]["tokens"]) + pool_tokens[i] = {_canonicalize_token(t) for t in toks[:2]} + + # ---- Compute R² for each method, per pool ---- + results = {m: [] for m in [ + "option_c", "ar1", "ridge_peers", "ridge_peers_own", + "constant_zero", "peer_mean", + ]} + + print(f"\n{'Pool':<18} {'Tokens':<14} {'OptC':>7} {'AR1':>7} " + f"{'R_peer':>7} {'R_p+own':>7} {'zero':>7} {'pmean':>7} {'n':>5}") + print("-" * 90) + + for i, pid in enumerate(pool_ids): + pd = pool_data[pid] + resid = pd["resid"] + n_obs = len(resid) + + # --- Option C --- + r2_oc = r2_score(resid, pd["resid_pred_oc"]) + + # --- Constant zero (V_total = V_arb) --- + r2_zero = r2_score(resid, np.zeros_like(resid)) + + # --- AR1 on residuals --- + if n_obs >= 3: + r2_ar1 = r2_score(resid[1:], resid[:-1]) + else: + r2_ar1 = np.nan + + # --- Ridge peers only (in-sample) --- + X_lag = resid_matrix[:-1, :] + y_cur = resid_matrix[1:, i] + own_lag = X_lag[:, i] + valid = ~np.isnan(y_cur) + + X_others = np.delete(X_lag, i, axis=1) + X_filled = X_others.copy() + for c in range(X_filled.shape[1]): + col = X_filled[:, c] + m = np.nanmean(col) + col[np.isnan(col)] = m if np.isfinite(m) else 0.0 + X_filled[:, c] = col + + X_peers = X_filled[valid] + y_i = y_cur[valid] + + if len(y_i) >= 10: + model_p = RidgeCV(alphas=np.logspace(-2, 4, 50)) + model_p.fit(X_peers, y_i) + r2_rp = r2_score(y_i, model_p.predict(X_peers)) + else: + r2_rp = np.nan + + # --- Ridge peers + own lag (in-sample) --- + valid_own = valid & ~np.isnan(own_lag) + X_both = np.column_stack([X_filled, own_lag[:, None]]) + X_both_v = X_both[valid_own] + y_both = y_cur[valid_own] + + if len(y_both) >= 10: + model_po = RidgeCV(alphas=np.logspace(-2, 4, 50)) + model_po.fit(X_both_v, y_both) + r2_rpo = r2_score(y_both, model_po.predict(X_both_v)) + else: + r2_rpo = np.nan + + # --- Peer mean (zero parameter) --- + peers = [j for j in range(n_pools) if j != i + and len(pool_tokens[i] & pool_tokens[j]) >= 1] + if peers: + peer_lag = resid_matrix[:-1, :][:, peers] + peer_mean = np.nanmean(peer_lag, axis=1) + y_pm = y_cur[valid] + pm_pred = peer_mean[valid] + pm_valid = ~np.isnan(pm_pred) + if pm_valid.sum() >= 3: + r2_pm = r2_score(y_pm[pm_valid], pm_pred[pm_valid]) + else: + r2_pm = np.nan + else: + r2_pm = np.nan + + results["option_c"].append(r2_oc) + results["ar1"].append(r2_ar1) + results["ridge_peers"].append(r2_rp) + results["ridge_peers_own"].append(r2_rpo) + results["constant_zero"].append(r2_zero) + results["peer_mean"].append(r2_pm) + + tokens = matched_clean[pid]["tokens"] + print(f" {pid[:16]} {tokens:<14} {r2_oc:>7.3f} {r2_ar1:>7.3f} " + f"{r2_rp:>7.3f} {r2_rpo:>7.3f} {r2_zero:>7.3f} " + f"{r2_pm:>7.3f} {n_obs:>5}") + + # ---- Summary ---- + def safe_median(xs): + v = [x for x in xs if np.isfinite(x)] + return np.median(v) if v else float("nan") + + print(f"\n{'='*70}") + print("SUMMARY — all on noise residual target") + print(f"{'='*70}") + for name, label in [ + ("option_c", "Option C (per-pool fitted)"), + ("ar1", "AR1 on residuals"), + ("ridge_peers", "Ridge peers only (in-sample)"), + ("ridge_peers_own", "Ridge peers + own lag (in-sample)"), + ("peer_mean", "Peer mean (0 params)"), + ("constant_zero", "Constant zero (V_total=V_arb)"), + ]: + vals = results[name] + med = safe_median(vals) + mean = np.nanmean([x for x in vals if np.isfinite(x)]) + n_neg = sum(1 for x in vals if np.isfinite(x) and x < 0) + print(f" {label:<35} median R² = {med:>7.4f} " + f"mean = {mean:>7.4f} n_neg = {n_neg}") + + +if __name__ == "__main__": + main() diff --git a/experiments/scan_lp_events.py b/experiments/scan_lp_events.py new file mode 100644 index 0000000..7194b84 --- /dev/null +++ b/experiments/scan_lp_events.py @@ -0,0 +1,522 @@ +"""Scan all pools for large LP deposit/withdrawal events and estimate TVL→noise elasticity. + +Identifies "semi-exogenous" LP flow events — large share changes that represent +genuine deposit/withdrawal decisions, not pool creation or dust. + +Filters: + - |Δlog(shares)| > threshold (default 20%) + - Pool must have been active for at least --min-age days before the event + - Pre-event TVL must be above --min-tvl (filters out pool creation events + where initial TVL is dust) + - Enough pre/post data to estimate volume change + +For each event, computes the volume response and implied elasticity. + +Usage: + python experiments/scan_lp_events.py + python experiments/scan_lp_events.py --threshold 0.1 --window 7 + python experiments/scan_lp_events.py --use-api # fetch fresh snapshots +""" + +import argparse +import os +import time + +import matplotlib +matplotlib.use("Agg") +import matplotlib.pyplot as plt +import numpy as np +import pandas as pd + + +def load_panel_data(use_api=False): + """Load pool panel data from calibration cache or API.""" + import pickle + + pools = {} + + # Stage1 calibration pools + cache_path = os.path.join( + os.path.dirname(os.path.dirname(__file__)), + "results", "token_factored_calibration", "_cache", "stage1.pkl", + ) + if os.path.exists(cache_path): + with open(cache_path, "rb") as f: + data = pickle.load(f) + for pid, entry in data["matched_clean"].items(): + panel = entry["panel"].copy() + panel["pool_id"] = pid + panel["chain"] = entry["chain"] + panel["tokens"] = entry["tokens"] + pools[pid] = panel + + # Noise calibration panel (broader set) + noise_panel_path = os.path.join( + os.path.dirname(os.path.dirname(__file__)), + "local_data", "noise_calibration", "panel.parquet", + ) + if os.path.exists(noise_panel_path): + panel_all = pd.read_parquet(noise_panel_path) + for pid in panel_all["pool_id"].unique(): + if pid[:16] not in pools: # don't duplicate + pp = panel_all[panel_all["pool_id"] == pid].copy() + if len(pp) >= 30: + pools[pid[:16]] = pp + + # Top50 snapshots (even broader) + snap_dir = os.path.join( + os.path.dirname(os.path.dirname(__file__)), + "local_data", "noise_top50", "snapshots", + ) + if os.path.exists(snap_dir): + import glob + for f in glob.glob(os.path.join(snap_dir, "*.parquet")): + pid = os.path.basename(f).replace(".parquet", "") + if pid[:16] not in pools: + try: + df = pd.read_parquet(f) + if len(df) >= 30 and "total_shares" in df.columns: + df["pool_id"] = pid + pools[pid[:16]] = df + except Exception: + pass + + if use_api: + print(" Fetching fresh snapshots from Balancer API...") + from quantammsim.noise_calibration import ( + fetch_pool_snapshots, BALANCER_API_CHAINS, + ) + for pid_short, panel in list(pools.items()): + if "chain" in panel.columns: + chain = panel["chain"].iloc[0] + else: + chain = "MAINNET" + full_pid = panel["pool_id"].iloc[0] if "pool_id" in panel.columns else pid_short + try: + fresh = fetch_pool_snapshots(full_pid, chain) + if len(fresh) > len(panel): + fresh["pool_id"] = full_pid + fresh["chain"] = chain + if "tokens" in panel.columns: + fresh["tokens"] = panel["tokens"].iloc[0] + pools[pid_short] = fresh + time.sleep(0.3) + except Exception: + pass + + print(f" Loaded {len(pools)} pools") + return pools + + +def find_lp_events(panel, threshold=0.2, min_age_days=30, min_tvl=10_000): + """Find large LP deposit/withdrawal events in a single pool's panel. + + Returns list of event dicts. + """ + dates = pd.to_datetime(panel["date"]) + + # Need shares and TVL + if "total_shares" not in panel.columns: + return [] + shares = panel["total_shares"].values.astype(float) + if np.all(shares <= 0) or np.all(np.isnan(shares)): + return [] + + # TVL + if "total_liquidity_usd" in panel.columns: + tvl = panel["total_liquidity_usd"].values.astype(float) + elif "log_tvl" in panel.columns: + tvl = np.exp(panel["log_tvl"].values.astype(float)) + elif "log_tvl_lag1" in panel.columns: + tvl = np.exp(panel["log_tvl_lag1"].values.astype(float)) + else: + return [] + + # Volume + if "volume_usd" in panel.columns: + vol = panel["volume_usd"].values.astype(float) + elif "log_volume" in panel.columns: + vol = np.exp(panel["log_volume"].values.astype(float)) + else: + return [] + + log_shares = np.log(np.maximum(shares, 1e-10)) + d_log_shares = np.diff(log_shares) + + events = [] + for i in range(len(d_log_shares)): + if abs(d_log_shares[i]) < np.log(1 + threshold): + continue + + # Check min age: pool must have been active for min_age_days + days_active = (dates.iloc[i + 1] - dates.iloc[0]).days + if days_active < min_age_days: + continue + + # Check min TVL before event + if tvl[i] < min_tvl: + continue + + # Check shares aren't near-zero before (not pool creation) + if shares[i] < 1: + continue + + pct_change = (np.exp(d_log_shares[i]) - 1) * 100 + event_type = "deposit" if d_log_shares[i] > 0 else "withdrawal" + + events.append({ + "date": dates.iloc[i + 1], + "idx": i + 1, + "type": event_type, + "d_log_shares": float(d_log_shares[i]), + "pct_change": float(pct_change), + "shares_before": float(shares[i]), + "shares_after": float(shares[i + 1]), + "tvl_before": float(tvl[i]), + "tvl_after": float(tvl[i + 1]), + "vol_on_day": float(vol[i + 1]), + }) + + return events + + +def compute_event_elasticity(panel, event, window=7): + """Compute volume response around an LP event. + + Compares median volume in [event-window, event) vs [event+1, event+window+1). + """ + dates = pd.to_datetime(panel["date"]) + idx = event["idx"] + + if "volume_usd" in panel.columns: + vol = panel["volume_usd"].values.astype(float) + elif "log_volume" in panel.columns: + vol = np.exp(panel["log_volume"].values.astype(float)) + else: + return None + + if "total_liquidity_usd" in panel.columns: + tvl = panel["total_liquidity_usd"].values.astype(float) + elif "log_tvl" in panel.columns: + tvl = np.exp(panel["log_tvl"].values.astype(float)) + elif "log_tvl_lag1" in panel.columns: + tvl = np.exp(panel["log_tvl_lag1"].values.astype(float)) + else: + return None + + pre_start = max(0, idx - window) + post_end = min(len(vol), idx + 1 + window) + + if idx - pre_start < 3 or post_end - (idx + 1) < 3: + return None + + vol_pre = np.median(vol[pre_start:idx]) + vol_post = np.median(vol[idx + 1:post_end]) + tvl_pre = np.median(tvl[pre_start:idx]) + tvl_post = np.median(tvl[idx + 1:post_end]) + + if vol_pre <= 0 or tvl_pre <= 0 or tvl_post <= 0: + return None + + vol_ratio = vol_post / vol_pre + tvl_ratio = tvl_post / tvl_pre + + if abs(np.log(tvl_ratio)) < 0.05: # TVL didn't actually change much + return None + + elasticity = np.log(vol_ratio) / np.log(tvl_ratio) + + return { + "vol_pre": vol_pre, + "vol_post": vol_post, + "tvl_pre": tvl_pre, + "tvl_post": tvl_post, + "vol_ratio": vol_ratio, + "tvl_ratio": tvl_ratio, + "elasticity": elasticity, + } + + +def main(): + parser = argparse.ArgumentParser(description=__doc__, + formatter_class=argparse.RawDescriptionHelpFormatter) + parser.add_argument("--threshold", type=float, default=0.2, + help="Min |share change| to count as event (0.2 = 20%%)") + parser.add_argument("--window", type=int, default=7, + help="Days before/after event for volume comparison") + parser.add_argument("--min-age", type=int, default=30, + help="Min days pool must be active before event") + parser.add_argument("--min-tvl", type=float, default=10_000, + help="Min TVL before event (filters pool creation)") + parser.add_argument("--use-api", action="store_true", + help="Fetch fresh snapshots from Balancer API") + parser.add_argument("--output-dir", default="results/lp_events", + help="Output directory for CSV and plots") + args = parser.parse_args() + + print("=" * 70) + print("LP Event Scanner: Semi-Exogenous TVL Shocks") + print(f" threshold={args.threshold:.0%}, window={args.window}d," + f" min_age={args.min_age}d, min_tvl=${args.min_tvl:,.0f}") + print("=" * 70) + + pools = load_panel_data(use_api=args.use_api) + + all_events = [] + print(f"\nScanning {len(pools)} pools for LP events...") + + for pid_short, panel in pools.items(): + tokens = (panel["tokens"].iloc[0] if "tokens" in panel.columns + else "?") + chain = (panel["chain"].iloc[0] if "chain" in panel.columns + else "?") + + events = find_lp_events( + panel, threshold=args.threshold, + min_age_days=args.min_age, min_tvl=args.min_tvl) + + for ev in events: + result = compute_event_elasticity(panel, ev, window=args.window) + ev["pool_id"] = pid_short + ev["tokens"] = tokens + ev["chain"] = chain + ev["result"] = result + all_events.append(ev) + + # Sort by absolute share change + all_events.sort(key=lambda e: abs(e["d_log_shares"]), reverse=True) + + print(f"\nFound {len(all_events)} LP events across {len(pools)} pools") + events_with_elasticity = [e for e in all_events if e["result"] is not None] + print(f" {len(events_with_elasticity)} with computable elasticity") + + # Print event table + print(f"\n{'Date':12s} {'Pool':16s} {'Tokens':18s} {'Type':10s}" + f" {'Δshares':>8s} {'TVL before':>12s} {'TVL after':>12s}" + f" {'VolPre':>10s} {'VolPost':>10s} {'Elast':>7s}") + print("-" * 120) + + for ev in all_events: + r = ev["result"] + if r: + elast_str = f"{r['elasticity']:+7.2f}" + vol_pre_str = f"${r['vol_pre']:>9,.0f}" + vol_post_str = f"${r['vol_post']:>9,.0f}" + else: + elast_str = " n/a" + vol_pre_str = " n/a" + vol_post_str = " n/a" + + print(f"{str(ev['date'].date()):12s} {ev['pool_id'][:16]:16s}" + f" {str(ev['tokens'])[:18]:18s} {ev['type']:10s}" + f" {ev['pct_change']:+7.0f}%" + f" ${ev['tvl_before']:>11,.0f} ${ev['tvl_after']:>11,.0f}" + f" {vol_pre_str} {vol_post_str} {elast_str}") + + # Summary statistics + if not events_with_elasticity: + print("No events with computable elasticity.") + return + + deposits = [e for e in events_with_elasticity if e["type"] == "deposit"] + withdrawals = [e for e in events_with_elasticity if e["type"] == "withdrawal"] + + all_elast = [e["result"]["elasticity"] for e in events_with_elasticity] + dep_elast = [e["result"]["elasticity"] for e in deposits] + wth_elast = [e["result"]["elasticity"] for e in withdrawals] + clean = [e for e in events_with_elasticity + if -1 < e["result"]["elasticity"] < 5] + clean_elast = [e["result"]["elasticity"] for e in clean] + + print(f"\n{'='*70}") + print("Summary: Implied TVL→Volume Elasticity") + print(f"{'='*70}") + print(f" All events ({len(all_elast)}):" + f" median={np.median(all_elast):+.2f}" + f" mean={np.mean(all_elast):+.2f}" + f" std={np.std(all_elast):.2f}") + if dep_elast: + print(f" Deposits ({len(dep_elast)}):" + f" median={np.median(dep_elast):+.2f}" + f" mean={np.mean(dep_elast):+.2f}") + if wth_elast: + print(f" Withdrawals ({len(wth_elast)}):" + f" median={np.median(wth_elast):+.2f}" + f" mean={np.mean(wth_elast):+.2f}") + if clean_elast: + print(f"\n Clean events (elasticity in [-1, 5], n={len(clean_elast)}):") + print(f" median={np.median(clean_elast):+.2f}" + f" mean={np.mean(clean_elast):+.2f}" + f" [Q25={np.percentile(clean_elast, 25):+.2f}," + f" Q75={np.percentile(clean_elast, 75):+.2f}]") + + print(f"\n For comparison:") + print(f" Per-pool observational b_tvl: ~1.0") + print(f" Shared observational b_tvl: ~2.5") + print(f" Daily Δ within-pool: ~0.1") + + # ---- Save CSV ---- + out_dir = args.output_dir + os.makedirs(out_dir, exist_ok=True) + + rows = [] + for ev in all_events: + r = ev.get("result") or {} + rows.append({ + "date": ev["date"], + "pool_id": ev["pool_id"], + "tokens": str(ev["tokens"]), + "chain": str(ev["chain"]), + "type": ev["type"], + "pct_change": ev["pct_change"], + "tvl_before": ev["tvl_before"], + "tvl_after": ev["tvl_after"], + "shares_before": ev["shares_before"], + "shares_after": ev["shares_after"], + "vol_pre": r.get("vol_pre"), + "vol_post": r.get("vol_post"), + "tvl_ratio": r.get("tvl_ratio"), + "vol_ratio": r.get("vol_ratio"), + "elasticity": r.get("elasticity"), + }) + df = pd.DataFrame(rows) + csv_path = os.path.join(out_dir, "lp_events.csv") + df.to_csv(csv_path, index=False) + print(f"\n Saved: {csv_path} ({len(df)} events)") + + # ---- Plots ---- + # 1. Elasticity histogram (clean events, deposits vs withdrawals) + fig, axes = plt.subplots(1, 3, figsize=(16, 5)) + + ax = axes[0] + ax.hist(clean_elast, bins=40, color="steelblue", alpha=0.7, edgecolor="white") + ax.axvline(np.median(clean_elast), color="red", linestyle="--", linewidth=2, + label=f"median={np.median(clean_elast):+.2f}") + ax.axvline(1.0, color="gray", linestyle=":", alpha=0.5, label="elasticity=1") + ax.set_xlabel("Elasticity (Δlog vol / Δlog TVL)") + ax.set_ylabel("Count") + ax.set_title(f"All clean events (n={len(clean_elast)})") + ax.legend(fontsize=8) + + ax = axes[1] + dep_clean = [e["result"]["elasticity"] for e in clean if e["type"] == "deposit"] + wth_clean = [e["result"]["elasticity"] for e in clean if e["type"] == "withdrawal"] + ax.hist(dep_clean, bins=30, color="green", alpha=0.6, label=f"deposits (n={len(dep_clean)})", edgecolor="white") + ax.hist(wth_clean, bins=30, color="coral", alpha=0.6, label=f"withdrawals (n={len(wth_clean)})", edgecolor="white") + ax.axvline(1.0, color="gray", linestyle=":", alpha=0.5) + ax.set_xlabel("Elasticity") + ax.set_title("Deposits vs Withdrawals") + ax.legend(fontsize=8) + + # 2. Elasticity vs event size (|Δlog shares|) + ax = axes[2] + sizes = [abs(e["d_log_shares"]) for e in clean] + elasts = [e["result"]["elasticity"] for e in clean] + colors = ["green" if e["type"] == "deposit" else "coral" for e in clean] + ax.scatter(sizes, elasts, c=colors, alpha=0.4, s=15, edgecolors="none") + ax.axhline(1.0, color="gray", linestyle=":", alpha=0.5) + ax.axhline(np.median(elasts), color="red", linestyle="--", alpha=0.7, + label=f"median={np.median(elasts):+.2f}") + ax.set_xlabel("|Δlog(shares)| (event size)") + ax.set_ylabel("Elasticity") + ax.set_title("Elasticity vs Event Size") + ax.legend(fontsize=8) + + fig.suptitle(f"LP Event Elasticity Analysis — {len(clean)} clean events" + f" from {len(pools)} pools", fontsize=11) + fig.tight_layout() + p1 = os.path.join(out_dir, "elasticity_histograms.png") + fig.savefig(p1, dpi=150, bbox_inches="tight") + plt.close(fig) + print(f" Saved: {p1}") + + # 3. Elasticity vs pre-event TVL (does pool size affect elasticity?) + fig, axes = plt.subplots(1, 2, figsize=(14, 5)) + + ax = axes[0] + tvl_pre = [e["result"]["tvl_pre"] for e in clean] + ax.scatter(tvl_pre, elasts, c=colors, alpha=0.4, s=15, edgecolors="none") + ax.set_xscale("log") + ax.axhline(1.0, color="gray", linestyle=":", alpha=0.5) + ax.set_xlabel("Pre-event TVL (USD)") + ax.set_ylabel("Elasticity") + ax.set_title("Elasticity vs Pool Size") + + # Bin by TVL decile and show median elasticity + tvl_arr = np.array(tvl_pre) + el_arr = np.array(elasts) + for q_lo, q_hi in [(0, 25), (25, 50), (50, 75), (75, 100)]: + lo = np.percentile(tvl_arr, q_lo) + hi = np.percentile(tvl_arr, q_hi) + mask = (tvl_arr >= lo) & (tvl_arr < hi + 1) + if mask.sum() > 5: + med_tvl = np.median(tvl_arr[mask]) + med_el = np.median(el_arr[mask]) + ax.plot(med_tvl, med_el, "rs", markersize=10, zorder=5) + ax.annotate(f"{med_el:.2f}", (med_tvl, med_el), + textcoords="offset points", xytext=(8, 5), fontsize=7) + + # 4. log(vol_post/vol_pre) vs log(tvl_post/tvl_pre) scatter + ax = axes[1] + log_tvl_ratio = [np.log(e["result"]["tvl_ratio"]) for e in clean] + log_vol_ratio = [np.log(e["result"]["vol_ratio"]) for e in clean] + ax.scatter(log_tvl_ratio, log_vol_ratio, c=colors, alpha=0.4, s=15, + edgecolors="none") + + # OLS fit line + x_fit = np.array(log_tvl_ratio) + y_fit = np.array(log_vol_ratio) + slope, intercept = np.polyfit(x_fit, y_fit, 1) + x_line = np.linspace(x_fit.min(), x_fit.max(), 100) + ax.plot(x_line, slope * x_line + intercept, "r-", linewidth=2, + label=f"OLS slope={slope:.2f}") + ax.plot(x_line, x_line, "k--", alpha=0.3, label="1:1 line") + ax.set_xlabel("Δlog(TVL)") + ax.set_ylabel("Δlog(Volume)") + ax.set_title("Volume Response to TVL Shocks") + ax.legend(fontsize=8) + + fig.suptitle("TVL→Volume Elasticity: Event Study", fontsize=11) + fig.tight_layout() + p2 = os.path.join(out_dir, "elasticity_scatter.png") + fig.savefig(p2, dpi=150, bbox_inches="tight") + plt.close(fig) + print(f" Saved: {p2}") + + # 5. Elasticity by chain + fig, ax = plt.subplots(figsize=(10, 5)) + chain_data = {} + for e in clean: + ch = str(e["chain"]) + if ch not in chain_data: + chain_data[ch] = [] + chain_data[ch].append(e["result"]["elasticity"]) + chains_sorted = sorted(chain_data.keys(), + key=lambda c: len(chain_data[c]), reverse=True) + chains_plot = [c for c in chains_sorted if len(chain_data[c]) >= 5] + if chains_plot: + positions = range(len(chains_plot)) + bp = ax.boxplot([chain_data[c] for c in chains_plot], + positions=positions, widths=0.6, patch_artist=True) + for patch in bp["boxes"]: + patch.set_facecolor("steelblue") + patch.set_alpha(0.6) + ax.set_xticks(positions) + ax.set_xticklabels([f"{c}\n(n={len(chain_data[c])})" for c in chains_plot], + fontsize=8) + ax.axhline(1.0, color="gray", linestyle=":", alpha=0.5) + ax.axhline(np.median(clean_elast), color="red", linestyle="--", alpha=0.5, + label=f"overall median={np.median(clean_elast):.2f}") + ax.set_ylabel("Elasticity") + ax.set_title("Elasticity by Chain") + ax.set_ylim(-2, 5) + ax.legend(fontsize=8) + fig.tight_layout() + p3 = os.path.join(out_dir, "elasticity_by_chain.png") + fig.savefig(p3, dpi=150, bbox_inches="tight") + plt.close(fig) + print(f" Saved: {p3}") + + +if __name__ == "__main__": + main() diff --git a/experiments/tune_reclamm_params.py b/experiments/tune_reclamm_params.py index 0951e2c..e2448bf 100644 --- a/experiments/tune_reclamm_params.py +++ b/experiments/tune_reclamm_params.py @@ -13,9 +13,16 @@ # More trials, custom fees python experiments/tune_reclamm_params.py --n-trials 200 --fees 0.005 + + # With calibrated 8-covariate noise model and arb frequency from calibration + python experiments/tune_reclamm_params.py --noise-model calibrated \ + --noise-params-json results/mlp_calibration/option_c_reduced.json \ + --noise-pool-id 0x9d1fcf346ea1b0 """ import argparse +import json +import math from quantammsim.runners.jax_runners import train_on_historic_data PARAMETER_CONFIG = { @@ -38,6 +45,28 @@ def main(): parser.add_argument("--interpolation", default="geometric", choices=["geometric", "constant_arc_length"]) parser.add_argument("--centeredness-scaling", action="store_true") + parser.add_argument("--noise-trader-ratio", type=float, default=0.0) + parser.add_argument("--noise-model", default=None, + choices=["ratio", "loglinear", "calibrated", "arb_only"], + help="Noise volume model (default: ratio via noise-trader-ratio)") + parser.add_argument("--noise-params-json", default=None, + help="JSON file with per-pool calibration results") + parser.add_argument("--noise-pool-id", default=None, + help="Pool ID to load noise params for (from --noise-params-json)") + parser.add_argument("--arb-frequency", type=int, default=None, + help="Arb frequency in minutes (default: from calibrated cadence or 1)") + parser.add_argument("--start-date", default="2024-06-01 00:00:00") + parser.add_argument("--end-date", default="2025-01-01 00:00:00", + help="End of training / start of test") + parser.add_argument("--end-test-date", default="2025-06-01 00:00:00") + parser.add_argument("--bout-offset", type=int, default=None, + help="bout_offset in minutes (default: 10080 = 7 days)") + parser.add_argument("--val-fraction", type=float, default=None, + help="Validation holdout fraction (default: 0.2, use 0 to disable)") + parser.add_argument("--overfitting-penalty", type=float, default=None, + help="Overfitting penalty weight (default: 0.2)") + parser.add_argument("--n-eval-points", type=int, default=None, + help="Number of evaluation sub-windows (default: 20, use 1 for full-window)") args = parser.parse_args() learn_speed = args.interpolation == "constant_arc_length" @@ -45,32 +74,90 @@ def main(): if learn_speed: param_config.update(ARC_LENGTH_SPEED_CONFIG) + # --- Noise model setup --- + pool_tokens = ["AAVE", "ETH"] # default + noise_fp = {"noise_trader_ratio": args.noise_trader_ratio} + if args.noise_model: + noise_fp["noise_model"] = args.noise_model + if args.noise_params_json and args.noise_pool_id: + with open(args.noise_params_json) as f: + all_results = json.load(f) + # Support both {"option_c_reduced": {pid: ...}} and {pid: ...} formats + pool_results = all_results + for key in all_results: + if isinstance(all_results[key], dict) and args.noise_pool_id in all_results[key]: + pool_results = all_results[key] + break + pool_data = pool_results[args.noise_pool_id] + coeffs = pool_data["noise_coeffs"] + if len(coeffs) == 8: + # Full 8-covariate model: [intercept, log_tvl, log_sigma, + # tvl*sigma, tvl*fee, sigma*fee, dow_sin, dow_cos] + noise_fp["reclamm_noise_params"] = { + f"c_{i}": c for i, c in enumerate(coeffs) + } + elif len(coeffs) == 4: + # Reduced 4-covariate model: [intercept, log_tvl, dow_sin, dow_cos] + # Map to c_0, c_1, c_6, c_7 (sigma/fee terms stay at 0) + noise_fp["reclamm_noise_params"] = { + "c_0": coeffs[0], "c_1": coeffs[1], + "c_6": coeffs[2], "c_7": coeffs[3], + } + else: + raise ValueError(f"Expected 4 or 8 noise_coeffs, got {len(coeffs)}") + # Derive arb_frequency from calibrated cadence if not explicitly set + if args.arb_frequency is None: + log_cad = pool_data["log_cadence"] + args.arb_frequency = max(1, round(math.exp(log_cad))) + print(f" arb_frequency={args.arb_frequency} " + f"(from log_cadence={log_cad:.2f}, " + f"cadence={math.exp(log_cad):.1f} min)") + # Use pool's fee and gas from calibration as defaults + if "fee" in pool_data: + args.fees = pool_data["fee"] + if "gas_usd" in pool_data: + args.gas_cost = pool_data["gas_usd"] + # Pick up token pair from calibration + # Map on-chain names (WETH, WBTC) to data-file names (ETH, BTC) + _TOKEN_MAP = {"WETH": "ETH", "WBTC": "BTC"} + if "tokens" in pool_data: + pool_tokens = [ + _TOKEN_MAP.get(t, t) for t in pool_data["tokens"].split(",") + ] + print(f" tokens={pool_tokens}, fee={args.fees}, gas={args.gas_cost}") + fp = { "rule": "reclamm", - "tokens": ["AAVE", "ETH"], - "startDateString": "2024-06-01 00:00:00", - "endDateString": "2025-01-01 00:00:00", - "endTestDateString": "2025-06-01 00:00:00", + "tokens": pool_tokens, + "startDateString": args.start_date, + "endDateString": args.end_date, + "endTestDateString": args.end_test_date, "initial_pool_value": 1_000_000.0, "do_arb": True, + **({"arb_frequency": args.arb_frequency} if args.arb_frequency is not None else {}), "fees": args.fees, "gas_cost": args.gas_cost, "arb_fees": 0.0, "protocol_fee_split": 0.5, + **noise_fp, "return_val": args.objective, "reclamm_interpolation_method": args.interpolation, "reclamm_centeredness_scaling": args.centeredness_scaling, "reclamm_learn_arc_length_speed": learn_speed, "reclamm_use_shift_exponent": True, + **({"bout_offset": args.bout_offset} if args.bout_offset is not None else {}), "optimisation_settings": { "method": "optuna", "n_parameter_sets": 1, + **({"val_fraction": args.val_fraction} if args.val_fraction is not None else {}), "optuna_settings": { "make_scalar": True, "expand_around": False, "n_trials": args.n_trials, "multi_objective": False, "parameter_config": param_config, + **({"overfitting_penalty": args.overfitting_penalty} if args.overfitting_penalty is not None else {}), + **({"n_evaluation_points": args.n_eval_points} if args.n_eval_points is not None else {}), }, }, } diff --git a/experiments/validate_tvl_counterfactual.py b/experiments/validate_tvl_counterfactual.py new file mode 100644 index 0000000..d4982b5 --- /dev/null +++ b/experiments/validate_tvl_counterfactual.py @@ -0,0 +1,236 @@ +"""Validate the noise model's TVL counterfactual predictions. + +Uses the AAVE/WETH reClAMM pool's natural experiment (70x TVL increase +from LP deposit in Jan 2026) to check whether the model's combined +V_arb + V_noise prediction matches observed volume changes. + +Tests whether the full model (PCHIP arb grid + per-pool linear noise +with b_tvl on standardized features) produces the right total volume +response, even though the noise-specific elasticity (~0.42 raw) is +lower than the event study's total elasticity (~0.9). + +Also evaluates counterfactual noise volumes at specified TVL levels. + +Usage: + python experiments/validate_tvl_counterfactual.py + python experiments/validate_tvl_counterfactual.py --pool 0x9d1fcf346ea1b0 + python experiments/validate_tvl_counterfactual.py --counterfactual-tvl 1e6 5e6 20e6 50e6 +""" + +import argparse +import json +import os +import pickle + +import numpy as np +import pandas as pd + +import jax.numpy as jnp +from quantammsim.calibration.grid_interpolation import interpolate_pool_daily +from quantammsim.calibration.noise_model_arrays import load_artifact + + +CACHE_DIR = os.path.join( + os.path.dirname(os.path.dirname(__file__)), + "results", "token_factored_calibration", "_cache", +) +ARTIFACT_DIR = os.path.join( + os.path.dirname(os.path.dirname(__file__)), + "results", "linear_market_noise", +) + + +def main(): + parser = argparse.ArgumentParser(description=__doc__, + formatter_class=argparse.RawDescriptionHelpFormatter) + parser.add_argument("--pool", default="0x9d1fcf346ea1b0", + help="Pool ID prefix") + parser.add_argument("--artifact-dir", default=ARTIFACT_DIR) + parser.add_argument("--pre-cutoff", default="2026-01-10", + help="Date before which = pre-deposit") + parser.add_argument("--post-cutoff", default="2026-01-20", + help="Date after which = post-deposit") + parser.add_argument("--counterfactual-tvl", type=float, nargs="+", + default=[70_000, 500_000, 5_000_000, 20_000_000], + help="TVL values for counterfactual evaluation") + args = parser.parse_args() + + os.environ.setdefault("JAX_PLATFORMS", "cpu") + + # ---- Load model artifact ---- + art, meta = load_artifact(args.artifact_dir) + nc = art["noise_coeffs"] + log_cad = art["log_cadence"] + x_mean = art["x_mean"] + x_std = art["x_std"] + pool_ids = meta["pool_ids"] + feat_names = meta["feat_names"] + per_pool = nc.ndim == 2 + + # ---- Load pool data ---- + with open(os.path.join(CACHE_DIR, "stage1.pkl"), "rb") as f: + data = pickle.load(f) + mc = data["matched_clean"] + oc = data["option_c_clean"] + + pid = args.pool + if pid not in pool_ids: + # Try prefix match + matches = [p for p in pool_ids if p.startswith(pid) or pid.startswith(p)] + if matches: + pid = matches[0] + else: + print(f"Pool {args.pool} not found in calibration set") + return + + idx = pool_ids.index(pid) + coeffs = nc[idx] if per_pool else nc + cadence = float(np.exp(log_cad[idx])) + gas = float(np.exp(oc[pid]["log_gas"])) + tvl_col = feat_names.index("xobs_1") + + print("=" * 70) + print("TVL Counterfactual Validation") + print(f" Pool: {pid} ({mc[pid]['tokens']}, {mc[pid]['chain']})") + print(f" Learned cadence: {cadence:.1f} min") + print(f" Gas: ${gas:.2f}") + print(f" b_tvl (standardized): {coeffs[tvl_col]:.4f}") + print(f" TVL standardization: mean={x_mean[tvl_col]:.2f}," + f" std={x_std[tvl_col]:.2f}") + print(f" Raw noise elasticity: {coeffs[tvl_col]/x_std[tvl_col]:.4f}") + print("=" * 70) + + # ---- V_arb from PCHIP ---- + entry = mc[pid] + v_arb_all = np.array(interpolate_pool_daily( + entry["coeffs"], jnp.float64(np.log(cadence)), jnp.float64(gas))) + day_indices = entry["day_indices"] + v_arb = v_arb_all[day_indices] + + panel = entry["panel"] + log_vol = panel["log_volume"].values.astype(float) + log_tvl = panel["log_tvl_lag1"].values.astype(float) + vol_obs = np.exp(log_vol) + tvl = np.exp(log_tvl) + dates = pd.to_datetime(panel["date"]) + + pre_mask = dates < args.pre_cutoff + post_mask = dates >= args.post_cutoff + + # ---- Build full feature vectors ---- + from experiments.run_linear_market_noise import build_data + data_full = build_data(mc, oc, trend_windows=(7,), + include_market=True, include_cross_pool=True) + x_full = data_full["x"] + pool_idx_full = data_full["pool_idx"] + day_idx_full = data_full["day_idx"] + + pool_i = pool_ids.index(pid) + pool_mask = pool_idx_full == pool_i + + all_dates = set() + for p in pool_ids: + all_dates.update(mc[p]["panel"]["date"].values) + date_list = sorted(all_dates) + + sample_dates = np.array([pd.Timestamp(date_list[d]) + for d in day_idx_full[pool_mask]]) + sample_x = x_full[pool_mask] + sgd = data_full["sample_grid_days"][pool_mask] + v_arb_samples = v_arb_all[sgd] + + # Per-sample noise prediction + if per_pool: + log_v_noise = sample_x @ coeffs + else: + log_v_noise = sample_x @ coeffs + v_noise = np.exp(log_v_noise) + + sample_pre = sample_dates < pd.Timestamp(args.pre_cutoff) + sample_post = sample_dates >= pd.Timestamp(args.post_cutoff) + + # ---- Pre/post comparison ---- + print(f"\n=== Pre-deposit (before {args.pre_cutoff}) ===") + print(f" Median TVL: ${np.median(tvl[pre_mask]):>14,.0f}") + print(f" Median V_obs: ${np.median(vol_obs[pre_mask]):>14,.0f}") + print(f" Median V_arb: ${np.median(v_arb[pre_mask]):>14,.0f} (PCHIP)") + print(f" Median V_noise: ${np.median(v_noise[sample_pre]):>14,.0f} (model)") + v_total_pre = v_arb_samples[sample_pre] + v_noise[sample_pre] + print(f" Median V_total: ${np.median(v_total_pre):>14,.0f} (V_arb + V_noise)") + + print(f"\n=== Post-deposit (after {args.post_cutoff}) ===") + print(f" Median TVL: ${np.median(tvl[post_mask]):>14,.0f}") + print(f" Median V_obs: ${np.median(vol_obs[post_mask]):>14,.0f}") + print(f" Median V_arb: ${np.median(v_arb[post_mask]):>14,.0f} (PCHIP)") + print(f" Median V_noise: ${np.median(v_noise[sample_post]):>14,.0f} (model)") + v_total_post = v_arb_samples[sample_post] + v_noise[sample_post] + print(f" Median V_total: ${np.median(v_total_post):>14,.0f} (V_arb + V_noise)") + + # ---- Ratios ---- + tvl_ratio = np.median(tvl[post_mask]) / np.median(tvl[pre_mask]) + vol_ratio = np.median(vol_obs[post_mask]) / np.median(vol_obs[pre_mask]) + varb_ratio = np.median(v_arb[post_mask]) / np.median(v_arb[pre_mask]) + vnoise_ratio = np.median(v_noise[sample_post]) / np.median(v_noise[sample_pre]) + vtotal_ratio = np.median(v_total_post) / np.median(v_total_pre) + + print(f"\n=== Ratios (post / pre) ===") + print(f" TVL: {tvl_ratio:>8.1f}x") + print(f" V_obs: {vol_ratio:>8.1f}x (ground truth)") + print(f" V_arb: {varb_ratio:>8.1f}x (PCHIP grid)") + print(f" V_noise: {vnoise_ratio:>8.1f}x (noise model)") + print(f" V_total: {vtotal_ratio:>8.1f}x (V_arb + V_noise)") + print(f" Gap: {vtotal_ratio/vol_ratio:>8.2f}x (pred/obs)") + + # ---- Decomposition shares ---- + print(f"\n=== Decomposition shares ===") + arb_share_pre = np.median(v_arb[pre_mask]) / np.median(vol_obs[pre_mask]) * 100 + noise_share_pre = np.median(v_noise[sample_pre]) / np.median(vol_obs[pre_mask]) * 100 + arb_share_post = np.median(v_arb[post_mask]) / np.median(vol_obs[post_mask]) * 100 + noise_share_post = np.median(v_noise[sample_post]) / np.median(vol_obs[post_mask]) * 100 + + print(f" Pre: arb={arb_share_pre:.0f}% noise={noise_share_pre:.0f}%") + print(f" Post: arb={arb_share_post:.0f}% noise={noise_share_post:.0f}%") + + # ---- Counterfactual evaluation ---- + print(f"\n=== Counterfactual noise volumes ===") + print(f" (Using median pre-deposit market features, varying TVL only)") + print(f" {'TVL':>14s} {'V_noise/day':>12s} {'V_noise/min':>12s}" + f" {'Ratio vs 70K':>12s}") + print(f" {'-'*55}") + + x_base = np.median(sample_x[sample_pre], axis=0).copy() + baseline_tvl = 70_000 + x_baseline = x_base.copy() + x_baseline[tvl_col] = (np.log(baseline_tvl) - x_mean[tvl_col]) / x_std[tvl_col] + for i, name in enumerate(feat_names): + if name.startswith("xobs_1" + "\u00d7"): + paired_name = name.split("\u00d7")[1] + if paired_name in feat_names: + paired_idx = feat_names.index(paired_name) + x_baseline[i] = x_baseline[tvl_col] * x_base[paired_idx] + vn_baseline = np.exp(x_baseline @ coeffs) + + for cf_tvl in args.counterfactual_tvl: + x_cf = x_base.copy() + std_log_tvl = (np.log(cf_tvl) - x_mean[tvl_col]) / x_std[tvl_col] + x_cf[tvl_col] = std_log_tvl + for i, name in enumerate(feat_names): + if name.startswith("xobs_1" + "\u00d7"): + paired_name = name.split("\u00d7")[1] + if paired_name in feat_names: + paired_idx = feat_names.index(paired_name) + x_cf[i] = std_log_tvl * x_base[paired_idx] + + vn = np.exp(x_cf @ coeffs) + ratio = vn / vn_baseline + print(f" ${cf_tvl:>13,.0f} ${vn:>11,.0f} ${vn/1440:>11,.0f}" + f" {ratio:>11.1f}x") + + print(f"\n Key finding: model predicts {vtotal_ratio:.1f}x total volume" + f" increase vs {vol_ratio:.1f}x observed ({vtotal_ratio/vol_ratio:.0%} accuracy).") + print(f" V_arb ({varb_ratio:.0f}x) carries most of the response;" + f" V_noise ({vnoise_ratio:.1f}x) is secondary but adds up.") + + +if __name__ == "__main__": + main() diff --git a/experiments/verify_vol_volume_slope.py b/experiments/verify_vol_volume_slope.py new file mode 100644 index 0000000..1758bd7 --- /dev/null +++ b/experiments/verify_vol_volume_slope.py @@ -0,0 +1,372 @@ +"""Verify: is the volatility-volume slope identical across fee tiers? + +The claim (from noise_calibration_review.md): "the relationship between +price volatility and swap volume is identical across fee tiers (slope 0.91 +for both low-fee and high-fee pools)." + +This script tests the claim by: +1. Loading all pool panel data +2. Splitting pools by fee tier (low vs high) +3. Regressing log(volume) on log(volatility) within each group +4. Comparing slopes + +If the slopes are similar, it means volatility drives organic volume +identically regardless of fee — supporting the arb/noise decomposition +(since arb intensity differs across fee tiers but noise doesn't). + +Usage: + python experiments/verify_vol_volume_slope.py +""" + +import os +import pickle +import sys + +import numpy as np +import pandas as pd + +CACHE_DIR = os.path.join( + os.path.dirname(os.path.dirname(__file__)), + "results", "token_factored_calibration", "_cache", +) + + +def main(): + path = os.path.join(CACHE_DIR, "stage1.pkl") + if not os.path.exists(path): + print("ERROR: no stage1 cache.") + sys.exit(1) + with open(path, "rb") as f: + data = pickle.load(f) + matched_clean = data["matched_clean"] + + # Collect per-observation data + rows = [] + for pid, entry in matched_clean.items(): + panel = entry["panel"] + fee = entry.get("fee", np.exp(panel["log_fee"].values[0])) + chain = entry.get("chain", "unknown") + tokens = entry.get("tokens", "?") + + log_vol = panel["log_volume"].values.astype(float) + vol_raw = panel["volatility"].values.astype(float) + log_tvl = panel["log_tvl_lag1"].values.astype(float) + + for i in range(len(log_vol)): + if vol_raw[i] > 1e-10 and np.isfinite(log_vol[i]): + rows.append({ + "pool_id": pid, + "tokens": tokens, + "chain": chain, + "fee": fee, + "log_fee": np.log(fee), + "log_volume": log_vol[i], + "log_sigma": np.log(max(vol_raw[i], 1e-10)), + "log_tvl": log_tvl[i] if np.isfinite(log_tvl[i]) else np.nan, + }) + + df = pd.DataFrame(rows).dropna() + print(f"Loaded {len(df)} observations from {df['pool_id'].nunique()} pools") + + # Fee tier split + fees = df.groupby("pool_id")["fee"].first() + median_fee = fees.median() + print(f"\nFee distribution:") + print(f" min={fees.min():.5f} median={median_fee:.5f} max={fees.max():.5f}") + print(f" Unique fees: {sorted(fees.unique())}") + + low_fee_pools = set(fees[fees <= median_fee].index) + high_fee_pools = set(fees[fees > median_fee].index) + print(f" Low-fee pools (≤{median_fee:.4f}): {len(low_fee_pools)}") + print(f" High-fee pools (>{median_fee:.4f}): {len(high_fee_pools)}") + + df_low = df[df["pool_id"].isin(low_fee_pools)] + df_high = df[df["pool_id"].isin(high_fee_pools)] + + # OLS: log_volume ~ intercept + log_sigma + def ols_slope(x, y): + X = np.column_stack([np.ones(len(x)), x]) + beta = np.linalg.lstsq(X, y, rcond=None)[0] + y_hat = X @ beta + ss_res = np.sum((y - y_hat) ** 2) + ss_tot = np.sum((y - y.mean()) ** 2) + r2 = 1 - ss_res / ss_tot + # Standard error of slope + n = len(x) + se = np.sqrt(ss_res / (n - 2) / np.sum((x - x.mean()) ** 2)) + return beta[1], beta[0], r2, se + + print(f"\n{'='*60}") + print("OLS: log(volume) ~ intercept + log(sigma)") + print(f"{'='*60}") + + # All pools + slope, intercept, r2, se = ols_slope(df["log_sigma"].values, df["log_volume"].values) + print(f"\n All pools ({len(df)} obs):") + print(f" slope = {slope:.4f} ± {1.96*se:.4f} (95% CI)") + print(f" R² = {r2:.4f}") + + # Low fee + slope_l, int_l, r2_l, se_l = ols_slope(df_low["log_sigma"].values, df_low["log_volume"].values) + print(f"\n Low-fee pools ({len(df_low)} obs, {len(low_fee_pools)} pools):") + print(f" slope = {slope_l:.4f} ± {1.96*se_l:.4f}") + print(f" R² = {r2_l:.4f}") + + # High fee + slope_h, int_h, r2_h, se_h = ols_slope(df_high["log_sigma"].values, df_high["log_volume"].values) + print(f"\n High-fee pools ({len(df_high)} obs, {len(high_fee_pools)} pools):") + print(f" slope = {slope_h:.4f} ± {1.96*se_h:.4f}") + print(f" R² = {r2_h:.4f}") + + print(f"\n Difference: {abs(slope_l - slope_h):.4f}") + print(f" Ratio: {slope_l/slope_h:.3f}") + + # Per-pool slopes + print(f"\n{'='*60}") + print("Per-pool OLS slopes") + print(f"{'='*60}") + pool_slopes = [] + print(f"\n {'Pool':>16s} {'Tokens':>20s} {'Fee':>8s} {'Slope':>8s}" + f" {'R²':>6s} {'N':>5s}") + for pid in sorted(matched_clean.keys()): + pool_df = df[df["pool_id"] == pid] + if len(pool_df) < 20: + continue + s, _, r, se = ols_slope(pool_df["log_sigma"].values, pool_df["log_volume"].values) + fee = pool_df["fee"].iloc[0] + tokens = pool_df["tokens"].iloc[0] + pool_slopes.append({"pool_id": pid, "tokens": tokens, "fee": fee, + "slope": s, "r2": r, "n": len(pool_df)}) + print(f" {pid[:16]} {tokens:>20s} {fee:>8.5f} {s:>8.4f}" + f" {r:>6.3f} {len(pool_df):>5d}") + + ps = pd.DataFrame(pool_slopes) + if len(ps) > 0: + low_slopes = ps[ps["fee"] <= median_fee]["slope"] + high_slopes = ps[ps["fee"] > median_fee]["slope"] + print(f"\n Per-pool slope summary:") + print(f" Low-fee: median={low_slopes.median():.4f}," + f" mean={low_slopes.mean():.4f} (n={len(low_slopes)})") + print(f" High-fee: median={high_slopes.median():.4f}," + f" mean={high_slopes.mean():.4f} (n={len(high_slopes)})") + + # Also try with TVL control: log_volume ~ log_sigma + log_tvl + print(f"\n{'='*60}") + print("OLS: log(volume) ~ intercept + log(sigma) + log(tvl)") + print(f"{'='*60}") + + def ols_multi(df_sub): + x = np.column_stack([ + np.ones(len(df_sub)), + df_sub["log_sigma"].values, + df_sub["log_tvl"].values, + ]) + y = df_sub["log_volume"].values + beta = np.linalg.lstsq(x, y, rcond=None)[0] + y_hat = x @ beta + ss_res = np.sum((y - y_hat) ** 2) + ss_tot = np.sum((y - y.mean()) ** 2) + return beta, 1 - ss_res / ss_tot + + beta_all, r2_all = ols_multi(df) + print(f"\n All: σ_slope={beta_all[1]:.4f}, tvl_slope={beta_all[2]:.4f}, R²={r2_all:.4f}") + + beta_l, r2_l = ols_multi(df_low) + print(f" Low: σ_slope={beta_l[1]:.4f}, tvl_slope={beta_l[2]:.4f}, R²={r2_l:.4f}") + + beta_h, r2_h = ols_multi(df_high) + print(f" High: σ_slope={beta_h[1]:.4f}, tvl_slope={beta_h[2]:.4f}, R²={r2_h:.4f}") + + print(f"\n σ slope difference (low-high): {beta_l[1] - beta_h[1]:.4f}") + + + # ---- Volume/TVL vs TVL (cross-pool) ---- + print(f"\n{'='*60}") + print("Volume/TVL vs TVL (cross-pool)") + print(f"{'='*60}") + + # Per-pool median volume and TVL + pool_stats = [] + for pid in sorted(matched_clean.keys()): + pool_df = df[df["pool_id"] == pid] + if len(pool_df) < 20: + continue + med_vol = np.exp(np.median(pool_df["log_volume"].values)) + med_tvl = np.exp(np.median(pool_df["log_tvl"].values)) + tokens = pool_df["tokens"].iloc[0] + fee = pool_df["fee"].iloc[0] + vol_tvl = med_vol / med_tvl + pool_stats.append({ + "pool_id": pid, "tokens": tokens, "fee": fee, + "med_vol": med_vol, "med_tvl": med_tvl, + "vol_tvl_pct": vol_tvl * 100, + "log_med_tvl": np.log(med_tvl), + }) + + ps = pd.DataFrame(pool_stats) + print(f"\n {'Pool':>16s} {'Tokens':>20s} {'TVL':>14s} {'Vol/day':>14s} {'Vol/TVL':>8s}") + for _, row in ps.sort_values("med_tvl").iterrows(): + print(f" {row['pool_id'][:16]} {row['tokens']:>20s}" + f" ${row['med_tvl']:>13,.0f} ${row['med_vol']:>13,.0f}" + f" {row['vol_tvl_pct']:>7.1f}%") + + # OLS: log(vol/tvl) ~ log(tvl) + log_vol_tvl = np.log(ps["med_vol"].values / ps["med_tvl"].values) + log_tvl_vals = ps["log_med_tvl"].values + slope_vt, int_vt, r2_vt, se_vt = ols_slope(log_tvl_vals, log_vol_tvl) + print(f"\n OLS: log(Vol/TVL) ~ log(TVL)") + print(f" slope = {slope_vt:.4f} ± {1.96*se_vt:.4f}") + print(f" R² = {r2_vt:.4f}") + print(f" (slope < 0 means Vol/TVL declines with TVL)") + + # Equivalent: log(Vol) ~ α + β*log(TVL), β < 1 means sublinear + slope_v, int_v, r2_v, se_v = ols_slope(log_tvl_vals, np.log(ps["med_vol"].values)) + print(f"\n OLS: log(Vol) ~ log(TVL)") + print(f" slope = {slope_v:.4f} ± {1.96*se_v:.4f}") + print(f" R² = {r2_v:.4f}") + print(f" (slope < 1 means sublinear = Vol/TVL declines)") + + # ---- TVL elasticity by TVL quartile (MM signature) ---- + print(f"\n{'='*60}") + print("TVL Elasticity by TVL Quartile") + print(f"{'='*60}") + + # Use observation-level data, not pool medians — more power + # Within-quartile regression: log(vol) ~ log(tvl) for pools in each bin + ps_sorted = ps.sort_values("med_tvl") + n_q = len(ps_sorted) // 4 + quartiles = [] + for q in range(4): + start = q * n_q + end = (q + 1) * n_q if q < 3 else len(ps_sorted) + q_pools = set(ps_sorted.iloc[start:end]["pool_id"]) + q_df = df[df["pool_id"].isin(q_pools)] + if len(q_df) < 20: + continue + s, intercept, r2, se = ols_slope(q_df["log_tvl"].values, + q_df["log_volume"].values) + tvl_lo = np.exp(q_df["log_tvl"].min()) + tvl_hi = np.exp(q_df["log_tvl"].max()) + tvl_med = np.exp(q_df["log_tvl"].median()) + quartiles.append({ + "q": q + 1, "n_pools": len(q_pools), "n_obs": len(q_df), + "tvl_lo": tvl_lo, "tvl_hi": tvl_hi, "tvl_med": tvl_med, + "slope": s, "se": se, "r2": r2, + }) + print(f"\n Q{q+1}: TVL ${tvl_lo:,.0f} – ${tvl_hi:,.0f}" + f" (median ${tvl_med:,.0f})") + print(f" {len(q_pools)} pools, {len(q_df)} obs") + print(f" slope = {s:.4f} ± {1.96*se:.4f} R² = {r2:.4f}") + + if len(quartiles) >= 2: + print(f"\n Summary:") + print(f" {'Quartile':>10s} {'Med TVL':>14s} {'Slope':>8s} {'95% CI':>16s}") + for q in quartiles: + ci = f"[{q['slope']-1.96*q['se']:.3f}, {q['slope']+1.96*q['se']:.3f}]" + print(f" Q{q['q']:>9d} ${q['tvl_med']:>13,.0f} {q['slope']:>8.4f} {ci:>16s}") + + slope_q1 = quartiles[0]["slope"] + slope_q4 = quartiles[-1]["slope"] + print(f"\n Q1→Q4 slope change: {slope_q4 - slope_q1:+.4f}") + print(f" (Negative = elasticity declines with TVL = MM signature)") + + # Also try: rolling window across pools sorted by TVL + print(f"\n Rolling 10-pool window:") + print(f" {'Window':>8s} {'Med TVL':>14s} {'Slope':>8s} {'R²':>6s}") + window = 10 + for start_i in range(0, len(ps_sorted) - window + 1, 3): + w_pools = set(ps_sorted.iloc[start_i:start_i + window]["pool_id"]) + w_df = df[df["pool_id"].isin(w_pools)] + if len(w_df) < 30: + continue + s, _, r2, se = ols_slope(w_df["log_tvl"].values, + w_df["log_volume"].values) + tvl_med = np.exp(w_df["log_tvl"].median()) + print(f" {start_i:>3d}-{start_i+window:>3d} ${tvl_med:>13,.0f} {s:>8.4f} {r2:>6.3f}") + + # Plot + try: + import matplotlib + matplotlib.use("Agg") + import matplotlib.pyplot as plt + + fig, axes = plt.subplots(1, 3, figsize=(16, 5)) + + # Panel 1: Vol/TVL vs TVL + ax = axes[0] + ax.scatter(ps["med_tvl"] / 1e6, ps["vol_tvl_pct"], + s=30, alpha=0.7, c="steelblue") + for _, row in ps.iterrows(): + ax.annotate(row["tokens"].split(",")[0], + (row["med_tvl"] / 1e6, row["vol_tvl_pct"]), + fontsize=5, alpha=0.6) + ax.set_xscale("log") + ax.set_xlabel("Median TVL ($M)") + ax.set_ylabel("Median Vol/TVL (%)") + ax.set_title(f"Volume/TVL Declines with TVL\n" + f"log(Vol/TVL) ~ {slope_vt:.2f}·log(TVL), R²={r2_vt:.2f}") + ax.grid(True, alpha=0.3) + + # Fit line + tvl_fit = np.logspace(np.log10(ps["med_tvl"].min()), + np.log10(ps["med_tvl"].max()), 100) + vol_tvl_fit = np.exp(int_vt + slope_vt * np.log(tvl_fit)) * 100 + ax.plot(tvl_fit / 1e6, vol_tvl_fit, "r--", linewidth=1, alpha=0.7) + + # Panel 2: Vol vs TVL (log-log) + ax = axes[1] + ax.scatter(ps["med_tvl"] / 1e6, ps["med_vol"] / 1e6, + s=30, alpha=0.7, c="coral") + for _, row in ps.iterrows(): + ax.annotate(row["tokens"].split(",")[0], + (row["med_tvl"] / 1e6, row["med_vol"] / 1e6), + fontsize=5, alpha=0.6) + ax.set_xscale("log") + ax.set_yscale("log") + ax.set_xlabel("Median TVL ($M)") + ax.set_ylabel("Median Daily Volume ($M)") + ax.set_title(f"Volume vs TVL (cross-pool)\n" + f"log(Vol) ~ {slope_v:.2f}·log(TVL), R²={r2_v:.2f}") + ax.grid(True, alpha=0.3) + + # Fit line + linear reference + vol_fit = np.exp(int_v + slope_v * np.log(tvl_fit)) + ax.plot(tvl_fit / 1e6, vol_fit / 1e6, "r--", linewidth=1, + alpha=0.7, label=f"slope={slope_v:.2f}") + # Linear reference (slope=1) + vol_linear = np.exp(int_v + 1.0 * np.log(tvl_fit)) + ax.plot(tvl_fit / 1e6, vol_linear / 1e6, "k:", linewidth=0.5, + alpha=0.3, label="slope=1 (linear)") + ax.legend(fontsize=8) + + # Panel 3: by fee tier + ax = axes[2] + for _, row in ps.iterrows(): + color = "steelblue" if row["fee"] <= median_fee else "coral" + ax.scatter(row["med_tvl"] / 1e6, row["vol_tvl_pct"], + s=30, alpha=0.7, c=color) + ax.annotate(row["tokens"].split(",")[0], + (row["med_tvl"] / 1e6, row["vol_tvl_pct"]), + fontsize=5, alpha=0.6) + ax.set_xscale("log") + ax.set_xlabel("Median TVL ($M)") + ax.set_ylabel("Median Vol/TVL (%)") + ax.set_title("Vol/TVL by Fee Tier\n" + f"blue=low fee (≤{median_fee:.4f}), red=high fee") + ax.grid(True, alpha=0.3) + + fig.suptitle("Cross-Pool Evidence for Volume Saturation", fontsize=13) + fig.tight_layout() + out = os.path.join(os.path.dirname(os.path.dirname(__file__)), + "results", "mm_noise", "plots", + "cross_pool_vol_tvl.png") + os.makedirs(os.path.dirname(out), exist_ok=True) + fig.savefig(out, dpi=150, bbox_inches="tight") + plt.close(fig) + print(f"\n Saved: {out}") + except Exception as e: + print(f" Plot failed: {e}") + + +if __name__ == "__main__": + main() diff --git a/pyproject.toml b/pyproject.toml index 413faf9..b5ce069 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -15,6 +15,7 @@ dependencies = [ "flask", "flask-jwt-extended", "scipy", + "scikit-learn", "seaborn", "cvxpy", "matplotlib", @@ -24,6 +25,7 @@ dependencies = [ "plotly", "dask", "Historic-Crypto", + "binance_historical_data", "bidask", "optax", "jsonpickle", @@ -45,6 +47,10 @@ docs = [ "sphinx-automodapi", "sphinx-rtd-theme", ] +calibration = [ + "numpyro>=0.15.0", + "arviz>=0.15.0", +] [tool.hatch.build.targets.wheel] packages = [ @@ -109,4 +115,4 @@ exclude_lines = [ "def __repr__", "raise NotImplementedError", "if __name__ == .__main__.:", -] \ No newline at end of file +] diff --git a/quantammsim/calibration/__init__.py b/quantammsim/calibration/__init__.py new file mode 100644 index 0000000..48a556a --- /dev/null +++ b/quantammsim/calibration/__init__.py @@ -0,0 +1,53 @@ +from quantammsim.calibration.grid_interpolation import ( + PoolCoeffs, + PoolCoeffsDaily, + PoolGridInterpolator, + build_scipy_interpolator, + interpolate_pool, + interpolate_pool_daily, + load_daily_grid, + load_valid_pool_grids, + pivot_grid, + precompute_pool_coeffs, + precompute_pool_coeffs_daily, +) +from quantammsim.calibration.joint_fit import ( + JointData, + fit_joint, + predict_new_pool_joint, +) +from quantammsim.calibration.learned_mapping import ( + build_targets, + cross_validate_loo, + fit_mapping, + predict_pool, +) +from quantammsim.calibration.loss import ( + K_OBS, + noise_volume, + pack_params, + pool_loss, + unpack_params, +) +from quantammsim.calibration.per_pool_fit import ( + fit_all_pools, + fit_single_pool, + make_initial_guess, +) +from quantammsim.calibration.pool_data import ( + build_pool_attributes, + build_x_obs, + match_grids_to_panel, +) +from quantammsim.calibration.calibration_model import CalibrationModel +from quantammsim.calibration.heads import ( + FixedHead, + Head, + LinearHead, + MLPHead, + MLPNoiseHead, + PerPoolHead, + PerPoolNoiseHead, + SharedLinearNoiseHead, +) +from quantammsim.calibration.loss import _compute_loss_huber diff --git a/quantammsim/calibration/calibration_model.py b/quantammsim/calibration/calibration_model.py new file mode 100644 index 0000000..86471ec --- /dev/null +++ b/quantammsim/calibration/calibration_model.py @@ -0,0 +1,358 @@ +"""Composable CalibrationModel with pluggable Head components. + +The CalibrationModel coordinates three heads (cadence, gas, noise) and +provides: + - Parameter packing/unpacking across all heads + - Per-pool JIT-compiled loss closures (same pattern as existing code) + - Joint loss aggregation with head regularization + - scipy L-BFGS-B fitting for both per-pool and joint modes + - Prediction for new pools + +All heads are concatenated in order [cadence | gas | noise] in the flat +parameter vector. +""" + +from __future__ import annotations + +from dataclasses import dataclass, field +from typing import Callable, Dict, Optional + +import jax +import jax.numpy as jnp +import numpy as np +import scipy.optimize + +from quantammsim.calibration.grid_interpolation import interpolate_pool_daily +from quantammsim.calibration.heads import Head +from quantammsim.calibration.loss import K_OBS, noise_volume + + +@dataclass +class CalibrationModel: + """Composable calibration model with pluggable heads. + + Coordinates cadence_head, gas_head, and noise_head to build a single + flat parameter vector and produce per-pool JIT-compiled loss functions. + """ + + cadence_head: Head + gas_head: Head + noise_head: Head + loss_type: str = "l2" + huber_delta: float = 1.5 + + # ── Parameter geometry ───────────────────────────────────────────── + + def n_params(self, n_pools: int, k_attr: int) -> int: + """Total parameter count across all heads.""" + return ( + self.cadence_head.n_params(n_pools, k_attr) + + self.gas_head.n_params(n_pools, k_attr) + + self.noise_head.n_params(n_pools, k_attr) + ) + + def _head_slices(self, n_pools: int, k_attr: int): + """Return (start, end) index pairs for each head's param slice.""" + n_cad = self.cadence_head.n_params(n_pools, k_attr) + n_gas = self.gas_head.n_params(n_pools, k_attr) + n_noise = self.noise_head.n_params(n_pools, k_attr) + cad_end = n_cad + gas_end = cad_end + n_gas + noise_end = gas_end + n_noise + return (0, cad_end), (cad_end, gas_end), (gas_end, noise_end) + + # ── Initialization ───────────────────────────────────────────────── + + def pack_init(self, jdata, warm_start=None) -> np.ndarray: + """Concatenate head inits into a single flat NumPy vector.""" + cad_init = self.cadence_head.init(jdata, warm_start) + gas_init = self.gas_head.init(jdata, warm_start) + noise_init = self.noise_head.init(jdata, warm_start) + return np.concatenate([cad_init, gas_init, noise_init]) + + # ── Bounds ───────────────────────────────────────────────────────── + + def make_bounds(self, n_pools: int, k_attr: int) -> list: + """Concatenate per-head scipy bounds.""" + return ( + self.cadence_head.make_bounds(n_pools, k_attr) + + self.gas_head.make_bounds(n_pools, k_attr) + + self.noise_head.make_bounds(n_pools, k_attr) + ) + + # ── Loss functions ───────────────────────────────────────────────── + + def _compute_loss(self, residuals: jnp.ndarray) -> jnp.ndarray: + """Compute loss from residuals based on loss_type.""" + if self.loss_type == "huber": + delta = self.huber_delta + abs_r = jnp.abs(residuals) + huber = jnp.where( + abs_r <= delta, + 0.5 * residuals ** 2, + delta * (abs_r - 0.5 * delta), + ) + return jnp.mean(huber) + return jnp.mean(residuals ** 2) + + def make_pool_loss_fn( + self, + pool_idx: int, + pool_data_i: dict, + x_attr_i: jnp.ndarray, + n_pools: int, + k_attr: int, + ) -> Callable: + """Create a JIT-compiled loss function for a single pool. + + Closes over pool-specific data. Takes params_flat as sole argument. + Returns scalar loss (no regularization — that's added at aggregate level). + """ + coeffs = pool_data_i["coeffs"] + x_obs = pool_data_i["x_obs"] + y_obs = pool_data_i["y_obs"] + day_indices = pool_data_i["day_indices"] + + (cad_s, cad_e), (gas_s, gas_e), (noise_s, noise_e) = \ + self._head_slices(n_pools, k_attr) + + cad_head = self.cadence_head + gas_head = self.gas_head + noise_head = self.noise_head + compute_loss = self._compute_loss + i = pool_idx + + @jax.jit + def pool_loss_fn(params_flat): + cad_slice = params_flat[cad_s:cad_e] + gas_slice = params_flat[gas_s:gas_e] + noise_slice = params_flat[noise_s:noise_e] + + log_cad = cad_head.predict(cad_slice, i, x_attr_i) + log_gas = gas_head.predict(gas_slice, i, x_attr_i) + noise_c = noise_head.predict(noise_slice, i, x_attr_i) + + v_arb_all = interpolate_pool_daily( + coeffs, log_cad, jnp.exp(log_gas) + ) + v_arb = v_arb_all[day_indices] + v_noise = jnp.exp(x_obs @ noise_c) + log_v_pred = jnp.log(jnp.maximum(v_arb + v_noise, 1e-6)) + + return compute_loss(log_v_pred - y_obs) + + return pool_loss_fn + + def make_joint_loss_fn(self, jdata) -> Callable: + """Create the joint loss function over all pools. + + Returns loss_fn(params_flat) -> scalar. Also attaches helper + attributes for the scipy wrapper (_pool_val_and_grad_fns, etc.). + """ + n_pools = len(jdata.pool_data) + k_attr = jdata.x_attr.shape[1] + + pool_loss_fns = [] + pool_val_and_grad_fns = [] + for i in range(n_pools): + fn = self.make_pool_loss_fn( + i, jdata.pool_data[i], jdata.x_attr[i], n_pools, k_attr + ) + pool_loss_fns.append(fn) + pool_val_and_grad_fns.append(jax.value_and_grad(fn)) + + (cad_s, cad_e), (gas_s, gas_e), (noise_s, noise_e) = \ + self._head_slices(n_pools, k_attr) + + cad_head = self.cadence_head + gas_head = self.gas_head + noise_head = self.noise_head + + def loss_fn(params_flat): + total = sum(fn(params_flat) for fn in pool_loss_fns) + data_loss = total / n_pools + + reg = cad_head.regularization(params_flat[cad_s:cad_e]) + reg = reg + gas_head.regularization(params_flat[gas_s:gas_e]) + reg = reg + noise_head.regularization(params_flat[noise_s:noise_e]) + + return data_loss + reg + + # Attach for the scipy wrapper + loss_fn._pool_loss_fns = pool_loss_fns + loss_fn._pool_val_and_grad_fns = pool_val_and_grad_fns + loss_fn._n_pools = n_pools + loss_fn._head_slices = (cad_s, cad_e), (gas_s, gas_e), (noise_s, noise_e) + loss_fn._cad_head = cad_head + loss_fn._gas_head = gas_head + loss_fn._noise_head = noise_head + + return loss_fn + + # ── Fitting ──────────────────────────────────────────────────────── + + def fit( + self, + jdata, + maxiter: int = 500, + warm_start: Optional[Dict[str, dict]] = None, + ) -> dict: + """Fit the model on joint data via L-BFGS-B. + + Returns a result dict with fitted parameters and diagnostics. + """ + n_pools = len(jdata.pool_data) + k_attr = jdata.x_attr.shape[1] + + loss_fn = self.make_joint_loss_fn(jdata) + init = self.pack_init(jdata, warm_start) + bounds = self.make_bounds(n_pools, k_attr) + + pool_vg_fns = loss_fn._pool_val_and_grad_fns + (cad_s, cad_e), (gas_s, gas_e), (noise_s, noise_e) = \ + loss_fn._head_slices + + cad_head = self.cadence_head + gas_head = self.gas_head + noise_head = self.noise_head + + def scipy_wrapper(params_np): + params_j = jnp.array(params_np) + + total_val = 0.0 + total_grad = jnp.zeros_like(params_j) + for vg_fn in pool_vg_fns: + v, g = vg_fn(params_j) + total_val += float(v) + total_grad = total_grad + g + + data_loss = total_val / n_pools + data_grad = total_grad / n_pools + + # Regularization — compute value and gradient + reg_val = 0.0 + reg_grad = jnp.zeros_like(params_j) + + # Cadence head regularization + cad_slice = params_j[cad_s:cad_e] + if cad_e > cad_s: + cad_reg_fn = lambda p: cad_head.regularization(p) + cr = float(cad_reg_fn(cad_slice)) + if cr != 0.0: + cad_rg = jax.grad(cad_reg_fn)(cad_slice) + reg_val += cr + reg_grad = reg_grad.at[cad_s:cad_e].set(cad_rg) + + # Gas head regularization + gas_slice = params_j[gas_s:gas_e] + if gas_e > gas_s: + gas_reg_fn = lambda p: gas_head.regularization(p) + gr = float(gas_reg_fn(gas_slice)) + if gr != 0.0: + gas_rg = jax.grad(gas_reg_fn)(gas_slice) + reg_val += gr + reg_grad = reg_grad.at[gas_s:gas_e].set(gas_rg) + + # Noise head regularization + noise_slice = params_j[noise_s:noise_e] + if noise_e > noise_s: + noise_reg_fn = lambda p: noise_head.regularization(p) + nr = float(noise_reg_fn(noise_slice)) + if nr != 0.0: + noise_rg = jax.grad(noise_reg_fn)(noise_slice) + reg_val += nr + reg_grad = reg_grad.at[noise_s:noise_e].set(noise_rg) + + val = data_loss + reg_val + grad = data_grad + reg_grad + return val, np.array(grad, dtype=np.float64) + + init_np = np.array(init, dtype=np.float64) + init_loss = float(loss_fn(jnp.array(init_np))) + + result = scipy.optimize.minimize( + scipy_wrapper, + init_np, + method="L-BFGS-B", + jac=True, + bounds=bounds, + options={"maxiter": maxiter, "ftol": 1e-10, "gtol": 1e-8}, + ) + + fitted = jnp.array(result.x) + (cad_s, cad_e), (gas_s, gas_e), (noise_s, noise_e) = \ + self._head_slices(n_pools, k_attr) + + # Compute data_loss and reg_loss at optimum + fitted_j = jnp.array(result.x) + data_loss_val = sum( + float(fn(fitted_j)) for fn in loss_fn._pool_loss_fns + ) / n_pools + reg_loss_val = float(result.fun) - data_loss_val + + out = { + "init_loss": init_loss, + "loss": float(result.fun), + "data_loss": data_loss_val, + "reg_loss": reg_loss_val, + "converged": result.success, + "params_flat": np.array(result.x), + } + + # Unpack each head's result + out.update(self.cadence_head.unpack_result( + np.array(fitted[cad_s:cad_e]), n_pools, k_attr)) + out.update(self.gas_head.unpack_result( + np.array(fitted[gas_s:gas_e]), n_pools, k_attr)) + out.update(self.noise_head.unpack_result( + np.array(fitted[noise_s:noise_e]), n_pools, k_attr)) + + out["pool_ids"] = jdata.pool_ids + out["attr_names"] = jdata.attr_names + out["k_attr"] = k_attr + out["n_pools"] = n_pools + + return out + + # ── Prediction ───────────────────────────────────────────────────── + + def predict_new_pool( + self, + result: dict, + x_attr: np.ndarray, + ) -> dict: + """Predict simulator settings for a new pool. + + Delegates to each head's predict_new. Heads that can't + generalize (PerPoolHead, FixedHead) will raise ValueError. + """ + n_pools = result["n_pools"] + k_attr = result["k_attr"] + params = result["params_flat"] + + (cad_s, cad_e), (gas_s, gas_e), (noise_s, noise_e) = \ + self._head_slices(n_pools, k_attr) + + log_cadence = self.cadence_head.predict_new( + params[cad_s:cad_e], x_attr + ) + log_gas = self.gas_head.predict_new( + params[gas_s:gas_e], x_attr + ) + + out = { + "log_cadence": float(log_cadence), + "log_gas": float(log_gas), + "cadence_minutes": float(np.exp(log_cadence)), + "gas_usd": float(np.exp(log_gas)), + } + + try: + noise_coeffs = self.noise_head.predict_new( + params[noise_s:noise_e], x_attr + ) + out["noise_coeffs"] = np.array(noise_coeffs) + except ValueError: + pass # PerPoolNoiseHead can't generalize + + return out diff --git a/quantammsim/calibration/grid_interpolation.py b/quantammsim/calibration/grid_interpolation.py new file mode 100644 index 0000000..bf0c55f --- /dev/null +++ b/quantammsim/calibration/grid_interpolation.py @@ -0,0 +1,463 @@ +"""PCHIP interpolation layer for precomputed arb-volume grids. + +Two grid formats: + - v1 (scalar): cadence x gas_cost -> median daily V_arb (single scalar) + - v2 (daily): cadence x gas_cost x day -> per-day V_arb (vector output) + +Interpolation in (log(cadence), gas_cost) space using PCHIP (monotone +piecewise cubic Hermite), which avoids Runge oscillation on non-uniform grids. + +Two interfaces: + 1. scipy-based: RegularGridInterpolator(method='pchip') for validation/plotting + 2. JAX-compatible: precomputed slopes + Hermite cubic eval, fully differentiable + +The JAX path uses tensor-product evaluation: + - Along cadence: Hermite cubic with scipy-precomputed PCHIP slopes + - Along gas: PCHIP slopes computed on the fly from intermediate values +""" + +import os +from typing import Dict, NamedTuple, Tuple + +import jax +import jax.numpy as jnp +import numpy as np +import pandas as pd +from scipy.interpolate import PchipInterpolator, RegularGridInterpolator + +GRID_DIR = os.path.join( + os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))), + "results", + "pool_grids", +) + +GRID_DIR_V2 = os.path.join( + os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))), + "results", + "pool_grids_v2", +) + + +# ── Grid loading ───────────────────────────────────────────────────────── + + +def load_pool_grid(pool_id_prefix: str, grid_dir: str = GRID_DIR) -> pd.DataFrame: + """Load a single pool's grid CSV.""" + path = os.path.join(grid_dir, f"{pool_id_prefix}_grid.csv") + return pd.read_csv(path) + + +def load_valid_pool_grids(grid_dir: str = GRID_DIR) -> Dict[str, pd.DataFrame]: + """Load all pool grid CSVs that have valid (non-NaN) data.""" + grids = {} + for f in sorted(os.listdir(grid_dir)): + if f.endswith("_grid.csv") and f != "grid_summary.csv": + prefix = f.replace("_grid.csv", "") + df = pd.read_csv(os.path.join(grid_dir, f)) + if df["median_daily_arb_volume"].notna().any(): + grids[prefix] = df + return grids + + +def pivot_grid( + df: pd.DataFrame, value_col: str = "median_daily_arb_volume" +) -> Tuple[np.ndarray, np.ndarray, np.ndarray]: + """Pivot grid DataFrame to (log_cadences, gas_costs, values) arrays.""" + pivot = df.pivot(index="cadence", columns="gas_cost", values=value_col) + cadences = pivot.index.values.astype(float) + gas_costs = pivot.columns.values.astype(float) + values = pivot.values.astype(float) + return np.log(cadences), gas_costs, values + + +# ── Scipy interpolation ───────────────────────────────────────────────── + + +def build_scipy_interpolator( + df: pd.DataFrame, value_col: str = "median_daily_arb_volume" +) -> RegularGridInterpolator: + """Build a scipy RegularGridInterpolator with PCHIP method.""" + log_cadences, gas_costs, values = pivot_grid(df, value_col) + return RegularGridInterpolator( + (log_cadences, gas_costs), + values, + method="pchip", + bounds_error=False, + fill_value=None, + ) + + +def query_scipy( + interp: RegularGridInterpolator, cadence: float, gas_cost: float +) -> float: + """Query scipy interpolator at (cadence, gas_cost). Cadence in minutes.""" + log_cad = np.log(np.clip(cadence, 1.0, 60.0)) + return float(interp(np.array([[log_cad, gas_cost]]))[0]) + + +# ── JAX-compatible PCHIP ──────────────────────────────────────────────── + + +class PoolCoeffs(NamedTuple): + """Precomputed coefficients for one pool's 2D PCHIP interpolation.""" + + log_cadences: jnp.ndarray # (n_cad,) + gas_costs: jnp.ndarray # (n_gas,) + values: jnp.ndarray # (n_cad, n_gas) + slopes_cad: jnp.ndarray # (n_cad, n_gas) PCHIP slopes along cadence axis + + +def precompute_pool_coeffs( + df: pd.DataFrame, value_col: str = "median_daily_arb_volume" +) -> PoolCoeffs: + """Precompute PCHIP slopes along cadence axis using scipy. + + These slopes are used by the JAX evaluation function for the first + interpolation axis (cadence). The second axis (gas) is computed on + the fly in JAX to maintain full differentiability. + """ + log_cadences, gas_costs, values = pivot_grid(df, value_col) + + n_gas = values.shape[1] + slopes = np.zeros_like(values) + for j in range(n_gas): + pchip = PchipInterpolator(log_cadences, values[:, j]) + slopes[:, j] = pchip.derivative()(log_cadences) + + return PoolCoeffs( + log_cadences=jnp.array(log_cadences), + gas_costs=jnp.array(gas_costs), + values=jnp.array(values), + slopes_cad=jnp.array(slopes), + ) + + +@jax.jit +def _pchip_slopes(x: jnp.ndarray, y: jnp.ndarray) -> jnp.ndarray: + """Compute PCHIP slopes via Fritsch-Carlson method. JAX-compatible. + + x: (n,) sorted knot positions + y: (n,) values at knots + Returns: (n,) slopes at knots + """ + h = x[1:] - x[:-1] + delta = (y[1:] - y[:-1]) / h + + # Interior points: weighted harmonic mean of neighboring secants + w1 = 2 * h[1:] + h[:-1] + w2 = h[1:] + 2 * h[:-1] + + sign_agree = (delta[:-1] * delta[1:]) > 0 + + # When sign_agree is False, d_mid is masked to 0. But the harmonic mean + # can produce Inf when deltas have opposite signs and w1==w2 (denominator + # cancels). JAX's where can't mask the NaN gradient of Inf (0*NaN=NaN). + # Fix: replace deltas with 1.0 when sign_agree is False, ensuring hm + # is always finite. The value is irrelevant since it gets masked. + d0 = jnp.where(sign_agree, delta[:-1], 1.0) + d1 = jnp.where(sign_agree, delta[1:], 1.0) + d0 = jnp.where(d0 == 0, 1e-30, d0) + d1 = jnp.where(d1 == 0, 1e-30, d1) + + hm = (w1 + w2) / (w1 / d0 + w2 / d1) + hm = jnp.where(jnp.isfinite(hm), hm, 0.0) + d_mid = jnp.where(sign_agree, hm, 0.0) + + # Endpoints: one-sided shape-preserving + d0 = ((2 * h[0] + h[1]) * delta[0] - h[0] * delta[1]) / (h[0] + h[1]) + d0 = jnp.where(d0 * delta[0] <= 0, 0.0, d0) + d0 = jnp.where( + (delta[0] * delta[1] < 0) & (jnp.abs(d0) > 3 * jnp.abs(delta[0])), + 3 * delta[0], + d0, + ) + + dn = ((2 * h[-1] + h[-2]) * delta[-1] - h[-1] * delta[-2]) / (h[-1] + h[-2]) + dn = jnp.where(dn * delta[-1] <= 0, 0.0, dn) + dn = jnp.where( + (delta[-1] * delta[-2] < 0) & (jnp.abs(dn) > 3 * jnp.abs(delta[-1])), + 3 * delta[-1], + dn, + ) + + return jnp.concatenate([d0[None], d_mid, dn[None]]) + + +@jax.jit +def interpolate_pool( + coeffs: PoolCoeffs, log_cadence: jnp.ndarray, gas_cost: jnp.ndarray +) -> jnp.ndarray: + """Evaluate 2D PCHIP at (log_cadence, gas_cost). JAX-differentiable. + + Tensor-product approach: + 1. Hermite cubic along cadence for all gas columns (precomputed slopes) + 2. PCHIP slopes along gas through intermediate values (computed on the fly) + 3. Hermite cubic along gas to final value + + Args: + coeffs: PoolCoeffs from precompute_pool_coeffs + log_cadence: scalar, log of cadence in minutes + gas_cost: scalar, effective profit threshold in USD + Returns: + V_arb: scalar, interpolated median daily arb volume + """ + log_cads = coeffs.log_cadences + gas = coeffs.gas_costs + vals = coeffs.values + sl_cad = coeffs.slopes_cad + + # Clamp to grid bounds + log_cadence = jnp.clip(log_cadence, log_cads[0], log_cads[-1]) + gas_cost = jnp.clip(gas_cost, gas[0], gas[-1]) + + # ── Step 1: Hermite along cadence for all gas columns ── + idx = jnp.searchsorted(log_cads, log_cadence) - 1 + idx = jnp.clip(idx, 0, log_cads.shape[0] - 2) + + h = log_cads[idx + 1] - log_cads[idx] + t = (log_cadence - log_cads[idx]) / h + t2 = t * t + t3 = t2 * t + + h00 = 2 * t3 - 3 * t2 + 1 + h10 = t3 - 2 * t2 + t + h01 = -2 * t3 + 3 * t2 + h11 = t3 - t2 + + v_at_gas = ( + h00 * vals[idx, :] + + h01 * vals[idx + 1, :] + + h * (h10 * sl_cad[idx, :] + h11 * sl_cad[idx + 1, :]) + ) + + # ── Step 2: PCHIP slopes along gas ── + gas_slopes = _pchip_slopes(gas, v_at_gas) + + # ── Step 3: Hermite along gas ── + jdx = jnp.searchsorted(gas, gas_cost) - 1 + jdx = jnp.clip(jdx, 0, gas.shape[0] - 2) + + hg = gas[jdx + 1] - gas[jdx] + s = (gas_cost - gas[jdx]) / hg + s2 = s * s + s3 = s2 * s + + g00 = 2 * s3 - 3 * s2 + 1 + g10 = s3 - 2 * s2 + s + g01 = -2 * s3 + 3 * s2 + g11 = s3 - s2 + + return ( + g00 * v_at_gas[jdx] + + g01 * v_at_gas[jdx + 1] + + hg * (g10 * gas_slopes[jdx] + g11 * gas_slopes[jdx + 1]) + ) + + +# ── Per-day (v2) grid support ────────────────────────────────────────────── + + +class PoolCoeffsDaily(NamedTuple): + """Precomputed coefficients for per-day 2D PCHIP interpolation. + + Like PoolCoeffs but values/slopes have a day dimension: + values: (n_cad, n_gas, n_days) + slopes_cad: (n_cad, n_gas, n_days) + dates: (n_days,) ordinal dates for alignment with panel + """ + + log_cadences: jnp.ndarray # (n_cad,) + gas_costs: jnp.ndarray # (n_gas,) + values: jnp.ndarray # (n_cad, n_gas, n_days) + slopes_cad: jnp.ndarray # (n_cad, n_gas, n_days) + dates: jnp.ndarray # (n_days,) ordinal dates + + +def load_daily_grid( + pool_id_prefix: str, grid_dir: str = GRID_DIR_V2 +) -> pd.DataFrame: + """Load a pool's per-day grid parquet.""" + path = os.path.join(grid_dir, f"{pool_id_prefix}_daily.parquet") + return pd.read_parquet(path) + + +def precompute_pool_coeffs_daily(df: pd.DataFrame) -> PoolCoeffsDaily: + """Build PoolCoeffsDaily from per-day grid DataFrame. + + Args: + df: DataFrame with columns [cadence, gas_cost, date, daily_arb_volume] + + Returns: + PoolCoeffsDaily with 3D values (n_cad, n_gas, n_days) + """ + df = df.copy() + df["date"] = pd.to_datetime(df["date"]) + + cadences = np.array(sorted(df["cadence"].unique()), dtype=float) + gas_costs = np.array(sorted(df["gas_cost"].unique()), dtype=float) + dates = np.array(sorted(df["date"].unique())) + log_cadences = np.log(cadences) + + n_cad = len(cadences) + n_gas = len(gas_costs) + n_days = len(dates) + + # Build 3D array: cadence x gas x day + values = np.zeros((n_cad, n_gas, n_days)) + cad_idx = {c: i for i, c in enumerate(cadences)} + gas_idx = {g: i for i, g in enumerate(gas_costs)} + date_idx = {d: i for i, d in enumerate(dates)} + + for _, row in df.iterrows(): + ci = cad_idx.get(float(row["cadence"])) + gi = gas_idx.get(float(row["gas_cost"])) + di = date_idx.get(row["date"]) + if ci is not None and gi is not None and di is not None: + values[ci, gi, di] = row["daily_arb_volume"] + + # Compute PCHIP slopes along cadence axis for each (gas, day) + slopes = np.zeros_like(values) + for j in range(n_gas): + for k in range(n_days): + col = values[:, j, k] + if np.all(np.isfinite(col)): + pchip = PchipInterpolator(log_cadences, col) + slopes[:, j, k] = pchip.derivative()(log_cadences) + + # Convert dates to ordinals for JAX + date_ordinals = np.array([ + pd.Timestamp(d).toordinal() for d in dates + ], dtype=np.int32) + + return PoolCoeffsDaily( + log_cadences=jnp.array(log_cadences), + gas_costs=jnp.array(gas_costs), + values=jnp.array(values), + slopes_cad=jnp.array(slopes), + dates=jnp.array(date_ordinals), + ) + + +@jax.jit +def interpolate_pool_daily( + coeffs: PoolCoeffsDaily, + log_cadence: jnp.ndarray, + gas_cost: jnp.ndarray, +) -> jnp.ndarray: + """Evaluate 2D PCHIP at (log_cadence, gas_cost) for all days. + + Same tensor-product approach as interpolate_pool, but values are 3D + (n_cad, n_gas, n_days) so the output is (n_days,). + + The Hermite basis coefficients are scalars that broadcast over the + day dimension of values. + + Args: + coeffs: PoolCoeffsDaily from precompute_pool_coeffs_daily + log_cadence: scalar, log of cadence in minutes + gas_cost: scalar, effective profit threshold in USD + Returns: + V_arb: (n_days,) interpolated daily arb volume + """ + log_cads = coeffs.log_cadences + gas = coeffs.gas_costs + vals = coeffs.values # (n_cad, n_gas, n_days) + sl_cad = coeffs.slopes_cad # (n_cad, n_gas, n_days) + + # Clamp to grid bounds + log_cadence = jnp.clip(log_cadence, log_cads[0], log_cads[-1]) + gas_cost = jnp.clip(gas_cost, gas[0], gas[-1]) + + # ── Step 1: Hermite along cadence for all gas columns, all days ── + idx = jnp.searchsorted(log_cads, log_cadence) - 1 + idx = jnp.clip(idx, 0, log_cads.shape[0] - 2) + + h = log_cads[idx + 1] - log_cads[idx] + t = (log_cadence - log_cads[idx]) / h + t2 = t * t + t3 = t2 * t + + h00 = 2 * t3 - 3 * t2 + 1 + h10 = t3 - 2 * t2 + t + h01 = -2 * t3 + 3 * t2 + h11 = t3 - t2 + + # vals[idx, :, :] is (n_gas, n_days) — scalars broadcast + v_at_gas = ( + h00 * vals[idx, :, :] + + h01 * vals[idx + 1, :, :] + + h * (h10 * sl_cad[idx, :, :] + h11 * sl_cad[idx + 1, :, :]) + ) # (n_gas, n_days) + + # ── Step 2: PCHIP slopes along gas, vmapped over days ── + gas_slopes = jax.vmap( + lambda y_col: _pchip_slopes(gas, y_col), + in_axes=1, out_axes=1, + )(v_at_gas) # (n_gas, n_days) + + # ── Step 3: Hermite along gas ── + jdx = jnp.searchsorted(gas, gas_cost) - 1 + jdx = jnp.clip(jdx, 0, gas.shape[0] - 2) + + hg = gas[jdx + 1] - gas[jdx] + s = (gas_cost - gas[jdx]) / hg + s2 = s * s + s3 = s2 * s + + g00 = 2 * s3 - 3 * s2 + 1 + g10 = s3 - 2 * s2 + s + g01 = -2 * s3 + 3 * s2 + g11 = s3 - s2 + + # v_at_gas[jdx] is (n_days,) — scalars broadcast + return ( + g00 * v_at_gas[jdx] + + g01 * v_at_gas[jdx + 1] + + hg * (g10 * gas_slopes[jdx] + g11 * gas_slopes[jdx + 1]) + ) # (n_days,) + + +# ── Convenience class ──────────────────────────────────────────────────── + + +class PoolGridInterpolator: + """Collection of PCHIP interpolators for all valid pool grids. + + Provides both scipy (for validation) and JAX (for optimization) access. + """ + + def __init__(self, grid_dir: str = GRID_DIR): + self.grid_dir = grid_dir + grids = load_valid_pool_grids(grid_dir) + + self._scipy_interps = {} + self._jax_coeffs = {} + self._pool_ids = sorted(grids.keys()) + + for pid, df in grids.items(): + self._scipy_interps[pid] = build_scipy_interpolator(df) + self._jax_coeffs[pid] = precompute_pool_coeffs(df) + + @property + def pool_ids(self): + return list(self._pool_ids) + + @property + def n_pools(self): + return len(self._pool_ids) + + def query_scipy(self, pool_id: str, cadence: float, gas_cost: float) -> float: + """Query scipy PCHIP at (cadence_minutes, gas_cost_usd).""" + return query_scipy(self._scipy_interps[pool_id], cadence, gas_cost) + + def query_jax(self, pool_id: str, log_cadence, gas_cost): + """Query JAX PCHIP at (log_cadence, gas_cost). Differentiable.""" + return interpolate_pool(self._jax_coeffs[pool_id], log_cadence, gas_cost) + + def get_coeffs(self, pool_id: str) -> PoolCoeffs: + """Get precomputed JAX coefficients for a single pool.""" + return self._jax_coeffs[pool_id] + + def get_scipy(self, pool_id: str) -> RegularGridInterpolator: + """Get scipy interpolator for a single pool.""" + return self._scipy_interps[pool_id] diff --git a/quantammsim/calibration/heads.py b/quantammsim/calibration/heads.py new file mode 100644 index 0000000..b051315 --- /dev/null +++ b/quantammsim/calibration/heads.py @@ -0,0 +1,917 @@ +"""Pluggable Head components for the composable CalibrationModel. + +Each Head encapsulates a specific parameterization strategy (per-pool, +fixed, linear) for one of the three model components: cadence, gas, or noise. + +Heads define how many parameters they need, how to predict from a parameter +slice, and how to compute regularization. The CalibrationModel concatenates +head parameter slices into a single flat vector for scipy L-BFGS-B. +""" + +from __future__ import annotations + +from typing import Optional, Protocol, runtime_checkable + +import jax.numpy as jnp +import numpy as np + +from quantammsim.calibration.loss import K_OBS +from quantammsim.calibration.pool_data import ( + D_TOKEN, K_OBS_REDUCED, _canonicalize_token, _classify_token, + _load_token_mcaps, +) + + +# --------------------------------------------------------------------------- +# Protocol +# --------------------------------------------------------------------------- + + +@runtime_checkable +class Head(Protocol): + """Protocol that all head implementations must satisfy.""" + + name: str + + def n_params(self, n_pools: int, k_attr: int) -> int: + """Number of scalar parameters this head contributes.""" + ... + + def predict( + self, + params_slice: jnp.ndarray, + pool_idx: int, + x_attr_i: jnp.ndarray, + ) -> jnp.ndarray: + """Predict value(s) for *pool_idx* given its attribute vector. + + Called inside a JIT-compiled per-pool closure, so this must be + JAX-traceable. Returns a scalar for cadence/gas heads, or a + (K_OBS,) vector for noise heads. + """ + ... + + def regularization(self, params_slice: jnp.ndarray) -> jnp.ndarray: + """Scalar regularization penalty added to the joint loss.""" + ... + + def init( + self, + jdata, + warm_start: Optional[dict] = None, + ) -> np.ndarray: + """Return initial NumPy parameter vector (flat).""" + ... + + def predict_new( + self, + params_slice: np.ndarray, + x_attr: np.ndarray, + ) -> np.ndarray: + """Predict for a *new* pool not seen during training (NumPy).""" + ... + + def unpack_result( + self, + params_slice: np.ndarray, + n_pools: int, + k_attr: int, + ) -> dict: + """Convert the optimized parameter slice to human-readable dict.""" + ... + + def make_bounds(self, n_pools: int, k_attr: int) -> list: + """Scipy (lo, hi) bounds for each parameter.""" + ... + + +# --------------------------------------------------------------------------- +# PerPoolHead — one free scalar per pool (Option C cadence / gas) +# --------------------------------------------------------------------------- + + +class PerPoolHead: + """One free scalar parameter per pool. + + Used for Option C per-pool cadence or gas. + """ + + def __init__(self, name: str, default: float = 0.0): + self.name = name + self._default = default + + def n_params(self, n_pools: int, k_attr: int) -> int: + return n_pools + + def predict( + self, + params_slice: jnp.ndarray, + pool_idx: int, + x_attr_i: jnp.ndarray, + ) -> jnp.ndarray: + return params_slice[pool_idx] + + def regularization(self, params_slice: jnp.ndarray) -> jnp.ndarray: + return jnp.float32(0.0) + + def init(self, jdata, warm_start=None) -> np.ndarray: + n_pools = len(jdata.pool_data) + if warm_start is not None: + vals = [] + for pid in jdata.pool_ids: + if pid in warm_start and self.name in warm_start[pid]: + vals.append(warm_start[pid][self.name]) + else: + vals.append(self._default) + return np.array(vals, dtype=np.float64) + return np.full(n_pools, self._default, dtype=np.float64) + + def predict_new(self, params_slice, x_attr): + raise ValueError( + f"PerPoolHead('{self.name}') cannot predict for unseen pools" + ) + + def unpack_result(self, params_slice, n_pools, k_attr): + return {f"{self.name}_per_pool": np.array(params_slice)} + + def make_bounds(self, n_pools, k_attr): + return [(None, None)] * n_pools + + +# --------------------------------------------------------------------------- +# FixedHead — zero parameters, returns pre-set values +# --------------------------------------------------------------------------- + + +class FixedHead: + """Zero-parameter head that returns pre-set per-pool values. + + Used when gas is fixed to known chain-level costs. + """ + + def __init__(self, name: str, values: np.ndarray): + self.name = name + self._values = np.asarray(values, dtype=np.float64) + self._values_jax = jnp.array(self._values) + + def n_params(self, n_pools: int, k_attr: int) -> int: + return 0 + + def predict(self, params_slice, pool_idx, x_attr_i): + return self._values_jax[pool_idx] + + def regularization(self, params_slice): + return jnp.float32(0.0) + + def init(self, jdata, warm_start=None): + return np.array([], dtype=np.float64) + + def predict_new(self, params_slice, x_attr): + raise ValueError( + f"FixedHead('{self.name}') cannot predict for unseen pools — " + "values are pool-specific" + ) + + def unpack_result(self, params_slice, n_pools, k_attr): + return {f"{self.name}_fixed": np.array(self._values)} + + def make_bounds(self, n_pools, k_attr): + return [] + + +# --------------------------------------------------------------------------- +# LinearHead — bias + x_attr @ W (Option A cadence / gas) +# --------------------------------------------------------------------------- + + +class LinearHead: + """Linear mapping from pool attributes: bias + x_attr @ W. + + L2 regularization on W (not bias) with strength ``alpha``. + """ + + def __init__(self, name: str, alpha: float = 0.01, + output_lo: float = None, output_hi: float = None): + self.name = name + self.alpha = alpha + self.output_lo = output_lo + self.output_hi = output_hi + + def n_params(self, n_pools: int, k_attr: int) -> int: + return 1 + k_attr # bias + W + + def predict(self, params_slice, pool_idx, x_attr_i): + bias = params_slice[0] + W = params_slice[1:] + out = bias + jnp.dot(x_attr_i, W) + if self.output_lo is not None or self.output_hi is not None: + out = jnp.clip(out, self.output_lo, self.output_hi) + return out + + def regularization(self, params_slice): + W = params_slice[1:] + return self.alpha * jnp.sum(W ** 2) + + def init(self, jdata, warm_start=None): + k_attr = jdata.x_attr.shape[1] + n_pools = len(jdata.pool_data) + + if warm_start is not None: + # Fit linear regression from per-pool values + vals = [] + for pid in jdata.pool_ids: + if pid in warm_start and self.name in warm_start[pid]: + vals.append(warm_start[pid][self.name]) + else: + vals.append(self._default_bias()) + y = np.array(vals) + X_aug = np.column_stack([np.ones(n_pools), np.array(jdata.x_attr)]) + params, _, _, _ = np.linalg.lstsq(X_aug, y, rcond=None) + return params.astype(np.float64) + + init = np.zeros(1 + k_attr, dtype=np.float64) + init[0] = self._default_bias() + return init + + def _default_bias(self): + if "cad" in self.name: + return np.log(12.0) + elif "gas" in self.name: + return np.log(1.0) + return 0.0 + + def predict_new(self, params_slice, x_attr): + bias = params_slice[0] + W = params_slice[1:] + return bias + np.dot(x_attr, W) + + def unpack_result(self, params_slice, n_pools, k_attr): + return { + f"bias_{self.name}": float(params_slice[0]), + f"W_{self.name}": np.array(params_slice[1:]), + } + + def make_bounds(self, n_pools, k_attr): + return [(None, None)] * (1 + k_attr) + + +# --------------------------------------------------------------------------- +# PerPoolNoiseHead — K_OBS free coefficients per pool +# --------------------------------------------------------------------------- + + +class PerPoolNoiseHead: + """Per-pool noise coefficients: each pool has k_obs free parameters. + + Used for Option C noise or Option A with per-pool noise. + """ + + def __init__(self, alpha: float = 0.0, k_obs: int = None): + self.name = "noise" + self.alpha = alpha + self.k_obs = k_obs if k_obs is not None else K_OBS + + def n_params(self, n_pools: int, k_attr: int) -> int: + return n_pools * self.k_obs + + def predict(self, params_slice, pool_idx, x_attr_i): + start = pool_idx * self.k_obs + return params_slice[start:start + self.k_obs] + + def regularization(self, params_slice): + if self.alpha == 0.0: + return jnp.float32(0.0) + return self.alpha * jnp.sum(params_slice ** 2) + + def init(self, jdata, warm_start=None): + n_pools = len(jdata.pool_data) + + if warm_start is not None: + noise_all = np.zeros((n_pools, self.k_obs), dtype=np.float64) + for i, pid in enumerate(jdata.pool_ids): + if pid in warm_start and "noise_coeffs" in warm_start[pid]: + noise_all[i] = warm_start[pid]["noise_coeffs"] + return noise_all.ravel() + + noise_all = np.zeros((n_pools, self.k_obs), dtype=np.float64) + for i, pd in enumerate(jdata.pool_data): + x_obs_np = np.array(pd["x_obs"]) + y_obs_np = np.array(pd["y_obs"]) + c, _, _, _ = np.linalg.lstsq(x_obs_np, y_obs_np, rcond=None) + noise_all[i] = c + return noise_all.ravel() + + def predict_new(self, params_slice, x_attr): + raise ValueError( + "PerPoolNoiseHead cannot predict noise for unseen pools" + ) + + def unpack_result(self, params_slice, n_pools, k_attr): + return { + "noise_coeffs": np.array(params_slice).reshape(n_pools, self.k_obs), + } + + def make_bounds(self, n_pools, k_attr): + return [(None, None)] * (n_pools * self.k_obs) + + +# --------------------------------------------------------------------------- +# SharedLinearNoiseHead — bias_noise + x_attr @ W_noise +# --------------------------------------------------------------------------- + + +class SharedLinearNoiseHead: + """Shared linear mapping for noise: bias_noise + x_attr @ W_noise. + + Output is (k_obs,) noise coefficients, predicted from pool attributes. + L2 regularization on W_noise (not bias_noise). + """ + + def __init__(self, alpha: float = 0.01, k_obs: int = None): + self.name = "noise" + self.alpha = alpha + self.k_obs = k_obs if k_obs is not None else K_OBS + + def n_params(self, n_pools: int, k_attr: int) -> int: + return (1 + k_attr) * self.k_obs + + def predict(self, params_slice, pool_idx, x_attr_i): + k_attr = x_attr_i.shape[0] + W_full = params_slice.reshape(1 + k_attr, self.k_obs) + bias_noise = W_full[0] + W_noise = W_full[1:] + return bias_noise + jnp.dot(x_attr_i, W_noise) + + def regularization(self, params_slice): + W_full = params_slice.reshape(-1, self.k_obs) + W_noise = W_full[1:] + return self.alpha * jnp.sum(W_noise ** 2) + + def init(self, jdata, warm_start=None): + k_attr = jdata.x_attr.shape[1] + n_pools = len(jdata.pool_data) + + if warm_start is not None: + noise_all = np.zeros((n_pools, self.k_obs), dtype=np.float64) + for i, pid in enumerate(jdata.pool_ids): + if pid in warm_start and "noise_coeffs" in warm_start[pid]: + noise_all[i] = warm_start[pid]["noise_coeffs"] + X_aug = np.column_stack([np.ones(n_pools), np.array(jdata.x_attr)]) + params, _, _, _ = np.linalg.lstsq(X_aug, noise_all, rcond=None) + return params.ravel().astype(np.float64) + + all_x = np.vstack([np.array(pd["x_obs"]) for pd in jdata.pool_data]) + all_y = np.concatenate([np.array(pd["y_obs"]) for pd in jdata.pool_data]) + c, _, _, _ = np.linalg.lstsq(all_x, all_y, rcond=None) + params = np.zeros((1 + k_attr, self.k_obs), dtype=np.float64) + params[0, :] = c + return params.ravel() + + def predict_new(self, params_slice, x_attr): + k_attr = len(x_attr) + W_full = np.array(params_slice).reshape(1 + k_attr, self.k_obs) + bias_noise = W_full[0] + W_noise = W_full[1:] + return bias_noise + x_attr @ W_noise + + def unpack_result(self, params_slice, n_pools, k_attr): + W_full = np.array(params_slice).reshape(1 + k_attr, self.k_obs) + return { + "bias_noise": W_full[0], + "W_noise": W_full[1:], + } + + def make_bounds(self, n_pools, k_attr): + return [(None, None)] * ((1 + k_attr) * self.k_obs) + + +# --------------------------------------------------------------------------- +# MLPHead — x_attr → Dense(hidden, relu) → Dense(1) +# --------------------------------------------------------------------------- + + +class MLPHead: + """Two-layer MLP mapping from pool attributes to a scalar. + + Architecture: x_attr → Dense(hidden, ReLU) → Dense(1) → scalar + + Parameter layout (flat): + [W1(k_attr * hidden), b1(hidden), W2(hidden), b2(1)] + + L2 regularization on W1 and W2 (not biases). + + Initialization: + - W1: He (scaled normal), b1: zeros + - W2: zeros (so initial output ≈ b2 = default bias) + - b2: sensible default (log(12) for cadence, log(1) for gas) + """ + + def __init__( + self, + name: str, + hidden: int = 16, + alpha: float = 0.01, + seed: int = 0, + output_lo: float = None, + output_hi: float = None, + ): + self.name = name + self.hidden = hidden + self.alpha = alpha + self._seed = seed + self.output_lo = output_lo + self.output_hi = output_hi + + def n_params(self, n_pools: int, k_attr: int) -> int: + h = self.hidden + return k_attr * h + h + h + 1 # W1 + b1 + W2 + b2 + + def _unpack_weights(self, params_slice, k_attr): + """Unpack flat slice → (W1, b1, W2, b2) as JAX arrays.""" + h = self.hidden + idx = 0 + W1 = params_slice[idx:idx + k_attr * h].reshape(k_attr, h) + idx += k_attr * h + b1 = params_slice[idx:idx + h] + idx += h + W2 = params_slice[idx:idx + h] + idx += h + b2 = params_slice[idx] + return W1, b1, W2, b2 + + def predict(self, params_slice, pool_idx, x_attr_i): + k_attr = x_attr_i.shape[0] + W1, b1, W2, b2 = self._unpack_weights(params_slice, k_attr) + hidden = jnp.maximum(x_attr_i @ W1 + b1, 0.0) # ReLU + out = hidden @ W2 + b2 + if self.output_lo is not None or self.output_hi is not None: + out = jnp.clip(out, self.output_lo, self.output_hi) + return out + + def regularization(self, params_slice): + # Regularize W1 and W2, not biases + # We can't call _unpack_weights without k_attr, so compute + # the total weight norm from the full slice minus biases. + # Layout: [W1(k*h), b1(h), W2(h), b2(1)] + # But we don't know k_attr here. Use a simpler approach: + # regularize the entire slice — biases are small relative to + # weights and the approximation error is negligible. + # Actually, let's extract properly by computing h from params. + h = self.hidden + total = params_slice.shape[0] + k_attr = (total - 2 * h - 1) // h + W1 = params_slice[:k_attr * h] + # b1 = params_slice[k_attr*h : k_attr*h + h] # skip + W2 = params_slice[k_attr * h + h:k_attr * h + 2 * h] + # b2 = params_slice[-1] # skip + return self.alpha * (jnp.sum(W1 ** 2) + jnp.sum(W2 ** 2)) + + def init(self, jdata, warm_start=None): + k_attr = jdata.x_attr.shape[1] + n_pools = len(jdata.pool_data) + h = self.hidden + rng = np.random.RandomState(self._seed) + + # He initialization for W1 + std = np.sqrt(2.0 / k_attr) + W1 = rng.randn(k_attr, h).astype(np.float64) * std + b1 = np.zeros(h, dtype=np.float64) + + b2 = np.array([self._default_bias()], dtype=np.float64) + W2 = np.zeros(h, dtype=np.float64) + + if warm_start is not None: + vals = [] + for pid in jdata.pool_ids: + if pid in warm_start and self.name in warm_start[pid]: + vals.append(warm_start[pid][self.name]) + else: + vals.append(self._default_bias()) + y = np.array(vals) + b2 = np.array([np.mean(y)], dtype=np.float64) + + # Warm-start W2 by least-squares through hidden activations + # so the MLP init approximates the per-pool warm-start values + x_attr = np.array(jdata.x_attr) + H = np.maximum(x_attr @ W1 + b1, 0.0) # (n_pools, h) + residuals = y - float(b2) # what W2 needs to produce + W2, _, _, _ = np.linalg.lstsq(H, residuals, rcond=None) + + return np.concatenate([W1.ravel(), b1, W2, b2]) + + def _default_bias(self): + if "cad" in self.name: + return np.log(12.0) + elif "gas" in self.name: + return np.log(1.0) + return 0.0 + + def predict_new(self, params_slice, x_attr): + k_attr = len(x_attr) + W1, b1, W2, b2 = self._unpack_weights( + np.asarray(params_slice), k_attr + ) + hidden = np.maximum(x_attr @ W1 + b1, 0.0) + return float(hidden @ W2 + b2) + + def unpack_result(self, params_slice, n_pools, k_attr): + params_np = np.array(params_slice) + W1, b1, W2, b2 = self._unpack_weights(params_np, k_attr) + return { + f"mlp_{self.name}_W1": np.array(W1), + f"mlp_{self.name}_b1": np.array(b1), + f"mlp_{self.name}_W2": np.array(W2), + f"mlp_{self.name}_b2": float(b2), + } + + def make_bounds(self, n_pools, k_attr): + return [(None, None)] * self.n_params(n_pools, k_attr) + + +# --------------------------------------------------------------------------- +# TokenFactoredNoiseHead — additive token + chain + fee composition +# --------------------------------------------------------------------------- + + +class TokenFactoredNoiseHead: + """Noise coefficients from additive token + chain + fee composition. + + noise_coeffs_i = u[token_a_i] + u[token_b_i] + alpha[chain_i] + + beta_fee * log(fee_i) + delta_i + + Token effects u_t are regularized toward x_token_t @ Gamma (population + prediction from token covariates). Per-pool deltas are L2-regularized, + controlling the shrinkage between per-pool and population estimates. + + Parameter layout (flat): + [u (n_tokens * k_obs), + Gamma (d_token * k_obs), + alpha (n_chains * k_obs), + beta_fee (k_obs), + delta (n_pools * k_obs)] + """ + + def __init__( + self, + token_a_idx: np.ndarray, + token_b_idx: np.ndarray, + chain_idx: np.ndarray, + log_fees: np.ndarray, + x_token: np.ndarray, + n_tokens: int, + n_chains: int, + token_index: dict, + chain_index: dict, + k_obs: int = K_OBS_REDUCED, + lambda_delta: float = 1.0, + lambda_token: float = 0.1, + lambda_chain: float = 0.1, + lambda_fee: float = 0.01, + mcap_path: str = None, + ): + self.name = "noise" + self.token_a_idx = np.asarray(token_a_idx, dtype=np.int32) + self.token_b_idx = np.asarray(token_b_idx, dtype=np.int32) + self.chain_idx = np.asarray(chain_idx, dtype=np.int32) + self.log_fees = np.asarray(log_fees, dtype=np.float64) + self.x_token = np.asarray(x_token, dtype=np.float64) + self.n_tokens = n_tokens + self.n_chains = n_chains + self.d_token = x_token.shape[1] + self.k_obs = k_obs + self.token_index = dict(token_index) + self.chain_index = dict(chain_index) + self.lambda_delta = lambda_delta + self.lambda_token = lambda_token + self.lambda_chain = lambda_chain + self.lambda_fee = lambda_fee + self._mcap_path = mcap_path + # Pre-convert to JAX for predict() + self._token_a_jax = jnp.array(self.token_a_idx) + self._token_b_jax = jnp.array(self.token_b_idx) + self._chain_jax = jnp.array(self.chain_idx) + self._log_fees_jax = jnp.array(self.log_fees) + self._x_token_jax = jnp.array(self.x_token) + + def n_params(self, n_pools: int, k_attr: int) -> int: + k = self.k_obs + return (self.n_tokens * k # u + + self.d_token * k # Gamma + + self.n_chains * k # alpha + + k # beta_fee + + n_pools * k) # delta + + def _unpack(self, params_slice, n_pools): + k = self.k_obs + idx = 0 + u = params_slice[idx:idx + self.n_tokens * k].reshape(self.n_tokens, k) + idx += self.n_tokens * k + Gamma = params_slice[idx:idx + self.d_token * k].reshape(self.d_token, k) + idx += self.d_token * k + alpha = params_slice[idx:idx + self.n_chains * k].reshape(self.n_chains, k) + idx += self.n_chains * k + beta_fee = params_slice[idx:idx + k] + idx += k + delta = params_slice[idx:idx + n_pools * k].reshape(n_pools, k) + return u, Gamma, alpha, beta_fee, delta + + def _infer_n_pools(self, params_slice): + k = self.k_obs + n_shared = self.n_tokens * k + self.d_token * k + self.n_chains * k + k + return (params_slice.shape[0] - n_shared) // k + + def predict(self, params_slice, pool_idx, x_attr_i): + n_pools = self._infer_n_pools(params_slice) + u, Gamma, alpha, beta_fee, delta = self._unpack(params_slice, n_pools) + ta = self._token_a_jax[pool_idx] + tb = self._token_b_jax[pool_idx] + ch = self._chain_jax[pool_idx] + lf = self._log_fees_jax[pool_idx] + return u[ta] + u[tb] + alpha[ch] + beta_fee * lf + delta[pool_idx] + + def regularization(self, params_slice): + n_pools = self._infer_n_pools(params_slice) + u, Gamma, alpha, beta_fee, delta = self._unpack(params_slice, n_pools) + u_pred = self._x_token_jax @ Gamma + reg_token = self.lambda_token * jnp.sum((u - u_pred) ** 2) + reg_chain = self.lambda_chain * jnp.sum(alpha ** 2) + reg_fee = self.lambda_fee * jnp.sum(beta_fee ** 2) + reg_delta = self.lambda_delta * jnp.sum(delta ** 2) + return reg_token + reg_chain + reg_fee + reg_delta + + def init(self, jdata, warm_start=None): + n_pools = len(jdata.pool_data) + k = self.k_obs + + if warm_start is not None: + # Collect per-pool noise_coeffs from warm_start + noise_all = np.zeros((n_pools, k), dtype=np.float64) + for i, pid in enumerate(jdata.pool_ids): + if pid in warm_start and "noise_coeffs" in warm_start[pid]: + nc = np.asarray(warm_start[pid]["noise_coeffs"]) + n_copy = min(len(nc), k) + noise_all[i, :n_copy] = nc[:n_copy] + + # Solve: u[ta_i] + u[tb_i] + alpha[ch_i] + beta_fee * lf_i ≈ noise_all[i] + n_cols = self.n_tokens + self.n_chains + 1 + A = np.zeros((n_pools, n_cols), dtype=np.float64) + for i in range(n_pools): + A[i, self.token_a_idx[i]] = 1.0 + A[i, self.token_b_idx[i]] += 1.0 + A[i, self.n_tokens + self.chain_idx[i]] = 1.0 + A[i, -1] = self.log_fees[i] + + lam_reg = 0.1 + AtA = A.T @ A + lam_reg * np.eye(n_cols) + u_init = np.zeros((self.n_tokens, k)) + alpha_init = np.zeros((self.n_chains, k)) + beta_fee_init = np.zeros(k) + + for j in range(k): + sol = np.linalg.solve(AtA, A.T @ noise_all[:, j]) + u_init[:, j] = sol[:self.n_tokens] + alpha_init[:, j] = sol[self.n_tokens:self.n_tokens + self.n_chains] + beta_fee_init[j] = sol[-1] + + # Delta = residuals + predicted = np.zeros_like(noise_all) + for i in range(n_pools): + predicted[i] = (u_init[self.token_a_idx[i]] + + u_init[self.token_b_idx[i]] + + alpha_init[self.chain_idx[i]] + + beta_fee_init * self.log_fees[i]) + delta_init = noise_all - predicted + + # Gamma from post-hoc regression of u on x_token + Gamma_init, _, _, _ = np.linalg.lstsq( + self.x_token, u_init, rcond=None + ) + else: + # Cold start: pooled OLS for baseline, then decompose + all_x = np.vstack([np.array(pd["x_obs"]) for pd in jdata.pool_data]) + all_y = np.concatenate([np.array(pd["y_obs"]) for pd in jdata.pool_data]) + pooled_coeffs, _, _, _ = np.linalg.lstsq(all_x, all_y, rcond=None) + pooled_coeffs = pooled_coeffs[:k] + + u_init = np.tile(pooled_coeffs / 2.0, (self.n_tokens, 1)) + Gamma_init, _, _, _ = np.linalg.lstsq( + self.x_token, u_init, rcond=None + ) + alpha_init = np.zeros((self.n_chains, k)) + beta_fee_init = np.zeros(k) + delta_init = np.zeros((n_pools, k)) + + return np.concatenate([ + u_init.ravel(), + Gamma_init.ravel(), + alpha_init.ravel(), + beta_fee_init, + delta_init.ravel(), + ]).astype(np.float64) + + def predict_new(self, params_slice, x_attr): + raise ValueError( + "TokenFactoredNoiseHead.predict_new() requires token identifiers. " + "Use predict_new_pool(params, token_a, token_b, chain, fee) instead." + ) + + def predict_new_pool( + self, params_slice, token_a, token_b, chain, fee, n_pools, + ) -> dict: + """Predict noise coefficients for a new pool from token composition. + + Seen tokens use learned u_t. Unseen tokens fall back to x_t @ Gamma. + Unseen chains use alpha = zeros. No delta for new pools. + Input token names are canonicalized before lookup. + """ + params_np = np.asarray(params_slice) + u, Gamma, alpha, beta_fee, delta = self._unpack(params_np, n_pools) + u, Gamma, alpha, beta_fee = ( + np.array(u), np.array(Gamma), np.array(alpha), np.array(beta_fee) + ) + mcaps = _load_token_mcaps(self._mcap_path) + + # Canonicalize input tokens + token_a = _canonicalize_token(token_a) + token_b = _canonicalize_token(token_b) + + def _get_token_effect(token): + if token in self.token_index: + return u[self.token_index[token]] + x_t = np.zeros(self.d_token) + x_t[0] = 1.0 + cls = _classify_token(token, mcaps) + x_t[1] = cls["log_mcap"] + x_t[2] = cls["is_stable"] + x_t[3] = cls["is_eth_derivative"] + x_t[4] = cls["is_L1_native"] + return x_t @ Gamma + + u_a = _get_token_effect(token_a) + u_b = _get_token_effect(token_b) + + if chain in self.chain_index: + alpha_c = alpha[self.chain_index[chain]] + else: + alpha_c = np.zeros(self.k_obs) + + fee_effect = beta_fee * np.log(fee) + noise_coeffs = u_a + u_b + alpha_c + fee_effect + + return { + "noise_coeffs": noise_coeffs, + "components": { + "token_a": u_a, + "token_b": u_b, + "chain": alpha_c, + "fee": fee_effect, + }, + } + + def unpack_result(self, params_slice, n_pools, k_attr): + params_np = np.asarray(params_slice) + u, Gamma, alpha, beta_fee, delta = self._unpack(params_np, n_pools) + u, Gamma, alpha, beta_fee, delta = ( + np.array(u), np.array(Gamma), np.array(alpha), + np.array(beta_fee), np.array(delta), + ) + # Reconstruct per-pool noise_coeffs + noise_coeffs = np.zeros((n_pools, self.k_obs)) + for i in range(n_pools): + noise_coeffs[i] = (u[self.token_a_idx[i]] + u[self.token_b_idx[i]] + + alpha[self.chain_idx[i]] + + beta_fee * self.log_fees[i] + + delta[i]) + return { + "token_effects": u, + "Gamma": Gamma, + "chain_effects": alpha, + "beta_fee": beta_fee, + "noise_deltas": delta, + "noise_coeffs": noise_coeffs, + } + + def make_bounds(self, n_pools, k_attr): + return [(None, None)] * self.n_params(n_pools, k_attr) + + +# --------------------------------------------------------------------------- +# MLPNoiseHead — x_attr → Dense(hidden, relu) → Dense(K_OBS) +# --------------------------------------------------------------------------- + + +class MLPNoiseHead: + """Two-layer MLP mapping from pool attributes to noise coefficients. + + Architecture: x_attr → Dense(hidden, ReLU) → Dense(k_obs) + + Parameter layout (flat): + [W1(k_attr * hidden), b1(hidden), W2(hidden * k_obs), b2(k_obs)] + + L2 regularization on W1 and W2 (not biases). + + Initialization: + - W1: He (scaled normal), b1: zeros + - W2: zeros (so initial output = b2 = pooled OLS noise coefficients) + - b2: pooled OLS noise from training data + """ + + def __init__( + self, + hidden: int = 16, + alpha: float = 0.01, + seed: int = 0, + k_obs: int = None, + ): + self.name = "noise" + self.hidden = hidden + self.alpha = alpha + self._seed = seed + self.k_obs = k_obs if k_obs is not None else K_OBS + + def n_params(self, n_pools: int, k_attr: int) -> int: + h = self.hidden + return k_attr * h + h + h * self.k_obs + self.k_obs + + def _unpack_weights(self, params_slice, k_attr): + """Unpack flat slice → (W1, b1, W2, b2).""" + h = self.hidden + ko = self.k_obs + idx = 0 + W1 = params_slice[idx:idx + k_attr * h].reshape(k_attr, h) + idx += k_attr * h + b1 = params_slice[idx:idx + h] + idx += h + W2 = params_slice[idx:idx + h * ko].reshape(h, ko) + idx += h * ko + b2 = params_slice[idx:idx + ko] + return W1, b1, W2, b2 + + def predict(self, params_slice, pool_idx, x_attr_i): + k_attr = x_attr_i.shape[0] + W1, b1, W2, b2 = self._unpack_weights(params_slice, k_attr) + hidden = jnp.maximum(x_attr_i @ W1 + b1, 0.0) # ReLU + return hidden @ W2 + b2 # (k_obs,) + + def regularization(self, params_slice): + h = self.hidden + ko = self.k_obs + total = params_slice.shape[0] + # Solve for k_attr: total = k*h + h + h*ko + ko + k_attr = (total - h - h * ko - ko) // h + W1 = params_slice[:k_attr * h] + W2 = params_slice[k_attr * h + h:k_attr * h + h + h * ko] + return self.alpha * (jnp.sum(W1 ** 2) + jnp.sum(W2 ** 2)) + + def init(self, jdata, warm_start=None): + k_attr = jdata.x_attr.shape[1] + n_pools = len(jdata.pool_data) + h = self.hidden + ko = self.k_obs + rng = np.random.RandomState(self._seed) + + # He initialization for W1 + std = np.sqrt(2.0 / k_attr) + W1 = rng.randn(k_attr, h).astype(np.float64) * std + b1 = np.zeros(h, dtype=np.float64) + + W2 = np.zeros((h, ko), dtype=np.float64) + + if warm_start is not None: + noise_all = np.zeros((n_pools, ko), dtype=np.float64) + for i, pid in enumerate(jdata.pool_ids): + if pid in warm_start and "noise_coeffs" in warm_start[pid]: + noise_all[i] = warm_start[pid]["noise_coeffs"] + b2 = np.mean(noise_all, axis=0) + + # Warm-start W2 by least-squares through hidden activations + x_attr = np.array(jdata.x_attr) + H = np.maximum(x_attr @ W1 + b1, 0.0) # (n_pools, h) + residuals = noise_all - b2 # (n_pools, ko) + W2, _, _, _ = np.linalg.lstsq(H, residuals, rcond=None) + else: + # Pooled OLS noise as b2 + all_x = np.vstack([np.array(pd["x_obs"]) for pd in jdata.pool_data]) + all_y = np.concatenate([np.array(pd["y_obs"]) for pd in jdata.pool_data]) + b2, _, _, _ = np.linalg.lstsq(all_x, all_y, rcond=None) + + return np.concatenate([W1.ravel(), b1, W2.ravel(), b2]) + + def predict_new(self, params_slice, x_attr): + k_attr = len(x_attr) + W1, b1, W2, b2 = self._unpack_weights(np.asarray(params_slice), k_attr) + hidden = np.maximum(x_attr @ W1 + b1, 0.0) + return hidden @ W2 + b2 # (k_obs,) + + def unpack_result(self, params_slice, n_pools, k_attr): + params_np = np.array(params_slice) + W1, b1, W2, b2 = self._unpack_weights(params_np, k_attr) + return { + "mlp_noise_W1": np.array(W1), + "mlp_noise_b1": np.array(b1), + "mlp_noise_W2": np.array(W2), + "mlp_noise_b2": np.array(b2), + } + + def make_bounds(self, n_pools, k_attr): + return [(None, None)] * self.n_params(n_pools, k_attr) diff --git a/quantammsim/calibration/joint_fit.py b/quantammsim/calibration/joint_fit.py new file mode 100644 index 0000000..8eaa7b8 --- /dev/null +++ b/quantammsim/calibration/joint_fit.py @@ -0,0 +1,631 @@ +"""Joint end-to-end optimization (Option A) for the direct calibration pipeline. + +A parametric f_params maps pool_attributes → (cadence, gas, noise_coeffs), +optimized simultaneously across all pools through the grid interpolation loss. + +Two noise modes: + - "per_pool_noise": each pool has independent noise_coeffs (most flexible) + - "shared_noise": noise_coeffs = bias_noise + x_attr @ W_noise (generalizes) + +The cadence/gas mapping is always shared: + log_cadence = bias_cad + x_attr @ W_cad + log_gas = bias_gas + x_attr @ W_gas +""" + +from typing import Dict, List, NamedTuple, Optional + +import jax +import jax.numpy as jnp +import numpy as np +import scipy.optimize + +from quantammsim.calibration.grid_interpolation import ( + PoolCoeffsDaily, + interpolate_pool_daily, +) +from quantammsim.calibration.loss import K_OBS +from quantammsim.calibration.pool_data import build_pool_attributes, build_x_obs + + +class JointData(NamedTuple): + """Batched data for joint optimization.""" + pool_data: list # list of dicts with coeffs, x_obs, y_obs, day_indices + x_attr: jnp.ndarray # (n_pools, K_attr) pool attributes (no intercept) + pool_ids: list # list of pool_id prefixes + attr_names: list # attribute column names + + +def prepare_joint_data( + matched: Dict[str, dict], + drop_chain_dummies: bool = False, + fix_gas_to_chain: bool = False, + reduced_x_obs: bool = False, +) -> JointData: + """Build batched JAX arrays from matched pool data. + + Args: + matched: dict from match_grids_to_panel + drop_chain_dummies: if True, remove chain_* columns from attributes + fix_gas_to_chain: if True, store fixed_log_gas per pool from CHAIN_GAS_USD + reduced_x_obs: if True, use 4-column reduced x_obs + (removes sigma/fee terms to avoid identification problems) + + Returns: + JointData with per-pool JAX arrays and shared attribute matrix. + """ + from quantammsim.calibration.loss import CHAIN_GAS_USD + + X_attr, attr_names, pool_ids = build_pool_attributes(matched) + + if drop_chain_dummies: + keep = [i for i, name in enumerate(attr_names) + if not name.startswith("chain_")] + X_attr = X_attr[:, keep] + attr_names = [attr_names[i] for i in keep] + + pool_data = [] + for pid in pool_ids: + entry = matched[pid] + panel = entry["panel"] + x_obs = build_x_obs(panel, reduced=reduced_x_obs) + y_obs = panel["log_volume"].values.astype(float) + + d = { + "coeffs": entry["coeffs"], + "x_obs": jnp.array(x_obs), + "y_obs": jnp.array(y_obs), + "day_indices": jnp.array(entry["day_indices"]), + } + if fix_gas_to_chain: + chain = entry["chain"] + gas_usd = CHAIN_GAS_USD.get(chain, 1.0) + d["fixed_log_gas"] = jnp.float64(np.log(max(gas_usd, 1e-6))) + + pool_data.append(d) + + return JointData( + pool_data=pool_data, + x_attr=jnp.array(X_attr), + pool_ids=pool_ids, + attr_names=attr_names, + ) + + +def prepare_token_factored_data( + matched: Dict[str, dict], + reduced_x_obs: bool = True, + fix_gas_to_chain: bool = True, + canonicalize: bool = True, + cross_pool: bool = False, +) -> tuple: + """Prepare JointData + token encoding for TokenFactoredNoiseHead. + + Args: + matched: dict from match_grids_to_panel + reduced_x_obs: if True, use 4-column reduced x_obs + fix_gas_to_chain: if True, fix gas to chain-level costs + canonicalize: if True, canonicalize token names before building index + cross_pool: if True, use cross-pool lag features (K_OBS_CROSS=7) + + Returns (jdata, token_encoding) where token_encoding is the dict from + encode_tokens() containing token/chain structure for constructing the head. + """ + from quantammsim.calibration.pool_data import encode_tokens + + if cross_pool: + from quantammsim.calibration.pool_data import ( + K_OBS_CROSS, build_cross_pool_x_obs, + ) + + jdata = prepare_joint_data( + matched, + fix_gas_to_chain=fix_gas_to_chain, + reduced_x_obs=reduced_x_obs, + ) + + if cross_pool: + # Replace x_obs with cross-pool version for each pool + pool_ids = jdata.pool_ids + new_pool_data = [] + for i, pid in enumerate(pool_ids): + entry = matched[pid] + x_obs_cross = build_cross_pool_x_obs( + entry["panel"], matched, pid, canonicalize=canonicalize, + ) + # x_obs_cross has n_obs-1 rows (first day dropped); + # trim y_obs and day_indices to match + d = dict(jdata.pool_data[i]) + d["x_obs"] = jnp.array(x_obs_cross) + d["y_obs"] = d["y_obs"][1:] + d["day_indices"] = d["day_indices"][1:] + new_pool_data.append(d) + jdata = JointData( + pool_data=new_pool_data, + x_attr=jdata.x_attr, + pool_ids=jdata.pool_ids, + attr_names=jdata.attr_names, + ) + + token_encoding = encode_tokens(matched, canonicalize=canonicalize) + + return jdata, token_encoding + + +def pack_joint_params( + bias_cad: float, + bias_gas: float, + W_cad: jnp.ndarray, + W_gas: jnp.ndarray, + noise_params: jnp.ndarray, +) -> jnp.ndarray: + """Pack joint params into flat array. + + Layout: [bias_cad, bias_gas, W_cad(k_attr), W_gas(k_attr), noise_params...] + + noise_params is either: + - (n_pools, K_OBS) for per_pool_noise mode + - (1 + K_attr, K_OBS) for shared_noise mode (row 0 = noise bias) + """ + return jnp.concatenate([ + jnp.array([bias_cad, bias_gas]), + W_cad.ravel(), + W_gas.ravel(), + noise_params.ravel(), + ]) + + +def pack_joint_params_fixed_gas( + bias_cad: float, + W_cad: jnp.ndarray, + noise_params: jnp.ndarray, +) -> jnp.ndarray: + """Pack joint params with gas excluded. + + Layout: [bias_cad, W_cad(k_attr), noise_params...] + """ + return jnp.concatenate([ + jnp.array([bias_cad]), + W_cad.ravel(), + noise_params.ravel(), + ]) + + +def unpack_joint_params( + flat: jnp.ndarray, config: dict +) -> dict: + """Unpack flat array to structured params. + + config must have: k_attr, n_pools, mode + config may have: fix_gas (bool) — if True, no bias_gas/W_gas in flat array + """ + k_attr = config["k_attr"] + mode = config["mode"] + fix_gas = config.get("fix_gas", False) + + if fix_gas: + bias_cad = flat[0] + W_cad = flat[1:1 + k_attr] + rest = flat[1 + k_attr:] + else: + bias_cad = flat[0] + bias_gas = flat[1] + W_cad = flat[2:2 + k_attr] + W_gas = flat[2 + k_attr:2 + 2 * k_attr] + rest = flat[2 + 2 * k_attr:] + + if mode == "per_pool_noise": + n_pools = config["n_pools"] + noise_coeffs = rest.reshape(n_pools, K_OBS) + if fix_gas: + return {"bias_cad": bias_cad, "W_cad": W_cad, + "noise_coeffs": noise_coeffs} + return { + "bias_cad": bias_cad, "bias_gas": bias_gas, + "W_cad": W_cad, "W_gas": W_gas, + "noise_coeffs": noise_coeffs, + } + else: # shared_noise + W_noise_full = rest.reshape(1 + k_attr, K_OBS) + if fix_gas: + return {"bias_cad": bias_cad, "W_cad": W_cad, + "bias_noise": W_noise_full[0], "W_noise": W_noise_full[1:]} + return { + "bias_cad": bias_cad, "bias_gas": bias_gas, + "W_cad": W_cad, "W_gas": W_gas, + "bias_noise": W_noise_full[0], + "W_noise": W_noise_full[1:], + } + + +def _make_pool_loss_fn( + pool_idx: int, + pool_data_i: dict, + x_attr_i: jnp.ndarray, + config: dict, +): + """Create a JIT'd loss function for a single pool. + + Closes over pool-specific data; takes only params_flat as input. + Each pool gets its own small JIT'd computation graph. + + If config["fix_gas"] is True, gas comes from pool_data_i["fixed_log_gas"] + instead of being predicted from attributes. + """ + coeffs = pool_data_i["coeffs"] + x_obs = pool_data_i["x_obs"] + y_obs = pool_data_i["y_obs"] + day_indices = pool_data_i["day_indices"] + mode = config["mode"] + fix_gas = config.get("fix_gas", False) + i = pool_idx + + if fix_gas: + fixed_log_gas = pool_data_i["fixed_log_gas"] + + @jax.jit + def pool_loss_fn(params_flat): + params = unpack_joint_params(params_flat, config) + log_cad = params["bias_cad"] + jnp.dot(x_attr_i, params["W_cad"]) + + if mode == "per_pool_noise": + noise_c = params["noise_coeffs"][i] + else: + noise_c = params["bias_noise"] + jnp.dot(x_attr_i, params["W_noise"]) + + v_arb_all = interpolate_pool_daily(coeffs, log_cad, jnp.exp(fixed_log_gas)) + v_arb = v_arb_all[day_indices] + v_noise = jnp.exp(x_obs @ noise_c) + log_v_pred = jnp.log(jnp.maximum(v_arb + v_noise, 1e-6)) + return jnp.mean((log_v_pred - y_obs) ** 2) + else: + @jax.jit + def pool_loss_fn(params_flat): + params = unpack_joint_params(params_flat, config) + log_cad = params["bias_cad"] + jnp.dot(x_attr_i, params["W_cad"]) + log_gas = params["bias_gas"] + jnp.dot(x_attr_i, params["W_gas"]) + + if mode == "per_pool_noise": + noise_c = params["noise_coeffs"][i] + else: + noise_c = params["bias_noise"] + jnp.dot(x_attr_i, params["W_noise"]) + + v_arb_all = interpolate_pool_daily(coeffs, log_cad, jnp.exp(log_gas)) + v_arb = v_arb_all[day_indices] + v_noise = jnp.exp(x_obs @ noise_c) + log_v_pred = jnp.log(jnp.maximum(v_arb + v_noise, 1e-6)) + return jnp.mean((log_v_pred - y_obs) ** 2) + + return pool_loss_fn + + +def make_joint_loss_fn( + jdata: JointData, + mode: str = "per_pool_noise", + alpha_cad: float = 0.01, + alpha_gas: float = 0.01, + fix_gas: bool = False, +): + """Create per-pool JIT'd loss functions and a Python-level aggregator. + + Each pool gets its own small JIT'd computation graph (compiled + independently), avoiding a massive unrolled trace. The outer + function sums per-pool losses in Python and adds regularization. + + Loss averages over pools (not observations), giving equal weight + to each pool regardless of observation count. + + L2 regularization is applied to W_cad (and W_gas if not fixed). + + Args: + jdata: JointData from prepare_joint_data + mode: "per_pool_noise" or "shared_noise" + alpha_cad: L2 regularization on W_cad + alpha_gas: L2 regularization on W_gas (ignored if fix_gas=True) + fix_gas: if True, gas is fixed per pool (no W_gas in params) + + Returns: + loss_fn(params_flat) -> scalar loss + """ + n_pools = len(jdata.pool_data) + k_attr = jdata.x_attr.shape[1] + config = {"k_attr": k_attr, "n_pools": n_pools, "mode": mode, + "fix_gas": fix_gas} + + # Build per-pool JIT'd loss functions + pool_loss_fns = [] + pool_val_and_grad_fns = [] + for i in range(n_pools): + fn = _make_pool_loss_fn(i, jdata.pool_data[i], jdata.x_attr[i], config) + pool_loss_fns.append(fn) + pool_val_and_grad_fns.append(jax.value_and_grad(fn)) + + def loss_fn(params_flat): + total = sum(fn(params_flat) for fn in pool_loss_fns) + data_loss = total / n_pools + + params = unpack_joint_params(params_flat, config) + reg = alpha_cad * jnp.sum(params["W_cad"] ** 2) + if not fix_gas: + reg = reg + alpha_gas * jnp.sum(params["W_gas"] ** 2) + return data_loss + reg + + # Attach per-pool functions for the value_and_grad wrapper + loss_fn._pool_val_and_grad_fns = pool_val_and_grad_fns + loss_fn._n_pools = n_pools + loss_fn._config = config + loss_fn._alpha_cad = alpha_cad + loss_fn._alpha_gas = alpha_gas + + return loss_fn + + +def make_initial_joint_params( + jdata: JointData, + mode: str = "per_pool_noise", + init_from_option_c: Optional[Dict[str, dict]] = None, + fix_gas: bool = False, +) -> jnp.ndarray: + """Create initial parameter vector. + + If init_from_option_c is provided, warm-start from Option C per-pool fits. + If fix_gas is True, excludes bias_gas and W_gas from the parameter vector. + """ + n_pools = len(jdata.pool_data) + k_attr = jdata.x_attr.shape[1] + x_attr_np = np.array(jdata.x_attr) + + if init_from_option_c is not None: + pool_ids = jdata.pool_ids + valid = {p: init_from_option_c[p] for p in pool_ids + if p in init_from_option_c + and np.isfinite(init_from_option_c[p].get("loss", float("nan")))} + + if len(valid) < len(pool_ids): + default_lc = np.log(12.0) + default_lg = np.log(1.0) + for p in pool_ids: + if p not in valid: + valid[p] = { + "log_cadence": default_lc, + "log_gas": default_lg, + "noise_coeffs": np.zeros(K_OBS), + } + + log_cads = np.array([valid[p]["log_cadence"] for p in pool_ids]) + noise_all = np.array([valid[p]["noise_coeffs"] for p in pool_ids]) + + X_aug = np.column_stack([np.ones(n_pools), x_attr_np]) + cad_params, _, _, _ = np.linalg.lstsq(X_aug, log_cads, rcond=None) + bias_cad, W_cad = cad_params[0], cad_params[1:] + + if not fix_gas: + log_gases = np.array([valid[p]["log_gas"] for p in pool_ids]) + gas_params, _, _, _ = np.linalg.lstsq(X_aug, log_gases, rcond=None) + bias_gas, W_gas = gas_params[0], gas_params[1:] + + if mode == "per_pool_noise": + noise_params = noise_all + else: + noise_aug, _, _, _ = np.linalg.lstsq(X_aug, noise_all, rcond=None) + noise_params = noise_aug + else: + bias_cad = np.log(12.0) + W_cad = np.zeros(k_attr) + + if not fix_gas: + bias_gas = np.log(1.0) + W_gas = np.zeros(k_attr) + + if mode == "per_pool_noise": + noise_params = np.zeros((n_pools, K_OBS)) + for i, pd in enumerate(jdata.pool_data): + x_obs_np = np.array(pd["x_obs"]) + y_obs_np = np.array(pd["y_obs"]) + c, _, _, _ = np.linalg.lstsq(x_obs_np, y_obs_np, rcond=None) + noise_params[i] = c + else: + all_x = np.vstack([np.array(pd["x_obs"]) for pd in jdata.pool_data]) + all_y = np.concatenate([np.array(pd["y_obs"]) for pd in jdata.pool_data]) + c, _, _, _ = np.linalg.lstsq(all_x, all_y, rcond=None) + noise_params = np.zeros((1 + k_attr, K_OBS)) + noise_params[0, :] = c + + if fix_gas: + return pack_joint_params_fixed_gas( + float(bias_cad), + jnp.array(W_cad), + jnp.array(noise_params), + ) + else: + return pack_joint_params( + float(bias_cad), + float(bias_gas), + jnp.array(W_cad), + jnp.array(W_gas), + jnp.array(noise_params), + ) + + +def _make_bounds(k_attr, n_pools, mode, fix_gas=False): + """Build scipy bounds for joint params.""" + if fix_gas: + # bias_cad only + bounds = [(None, None)] * 1 + # W_cad only + bounds += [(None, None)] * k_attr + else: + # bias_cad, bias_gas + bounds = [(None, None)] * 2 + # W_cad, W_gas + bounds += [(None, None)] * (2 * k_attr) + + if mode == "per_pool_noise": + bounds += [(None, None)] * (n_pools * K_OBS) + else: + bounds += [(None, None)] * ((1 + k_attr) * K_OBS) + + return bounds + + +def fit_joint( + matched: Dict[str, dict], + mode: str = "per_pool_noise", + init_from_option_c: Optional[Dict[str, dict]] = None, + maxiter: int = 500, + alpha_cad: float = 0.01, + alpha_gas: float = 0.01, + drop_chain_dummies: bool = False, + fix_gas_to_chain: bool = False, +) -> dict: + """Joint end-to-end optimization across all pools. + + Args: + matched: dict from match_grids_to_panel + mode: "per_pool_noise" or "shared_noise" + init_from_option_c: Optional Option C results for warm start. + maxiter: max L-BFGS-B iterations + alpha_cad: L2 regularization on W_cad (not bias) + alpha_gas: L2 regularization on W_gas (not bias, ignored if fix_gas) + drop_chain_dummies: if True, remove chain_* columns from attributes + fix_gas_to_chain: if True, gas is fixed to known chain-level costs + + Returns dict with fitted params and diagnostics. + """ + jdata = prepare_joint_data(matched, drop_chain_dummies=drop_chain_dummies, + fix_gas_to_chain=fix_gas_to_chain) + loss_fn = make_joint_loss_fn(jdata, mode=mode, + alpha_cad=alpha_cad, alpha_gas=alpha_gas, + fix_gas=fix_gas_to_chain) + init = make_initial_joint_params(jdata, mode=mode, + init_from_option_c=init_from_option_c, + fix_gas=fix_gas_to_chain) + + n_pools = len(jdata.pool_data) + k_attr = jdata.x_attr.shape[1] + config = {"k_attr": k_attr, "n_pools": n_pools, "mode": mode, + "fix_gas": fix_gas_to_chain} + bounds = _make_bounds(k_attr, n_pools, mode, fix_gas=fix_gas_to_chain) + + pool_vg_fns = loss_fn._pool_val_and_grad_fns + + if fix_gas_to_chain: + w_cad_start = 1 + w_cad_end = 1 + k_attr + else: + w_cad_start = 2 + w_cad_end = 2 + k_attr + w_gas_start = 2 + k_attr + w_gas_end = 2 + 2 * k_attr + + def scipy_wrapper(params_np): + params_j = jnp.array(params_np) + + total_val = 0.0 + total_grad = jnp.zeros_like(params_j) + for vg_fn in pool_vg_fns: + v, g = vg_fn(params_j) + total_val += float(v) + total_grad = total_grad + g + + data_loss = total_val / n_pools + data_grad = total_grad / n_pools + + reg = alpha_cad * float(jnp.sum(params_j[w_cad_start:w_cad_end] ** 2)) + reg_grad = jnp.zeros_like(params_j) + reg_grad = reg_grad.at[w_cad_start:w_cad_end].set( + 2 * alpha_cad * params_j[w_cad_start:w_cad_end]) + + if not fix_gas_to_chain: + reg += alpha_gas * float(jnp.sum(params_j[w_gas_start:w_gas_end] ** 2)) + reg_grad = reg_grad.at[w_gas_start:w_gas_end].set( + 2 * alpha_gas * params_j[w_gas_start:w_gas_end]) + + val = data_loss + reg + grad = data_grad + reg_grad + return val, np.array(grad, dtype=np.float64) + + init_np = np.array(init, dtype=np.float64) + init_loss = float(loss_fn(jnp.array(init_np))) + + result = scipy.optimize.minimize( + scipy_wrapper, + init_np, + method="L-BFGS-B", + jac=True, + bounds=bounds, + options={"maxiter": maxiter, "ftol": 1e-10, "gtol": 1e-8}, + ) + + params = unpack_joint_params(jnp.array(result.x), config) + + out = { + "init_loss": init_loss, + "bias_cad": float(params["bias_cad"]), + "W_cad": np.array(params["W_cad"]), + "loss": float(result.fun), + "converged": result.success, + "mode": mode, + "k_attr": k_attr, + "pool_ids": jdata.pool_ids, + "attr_names": jdata.attr_names, + "fix_gas": fix_gas_to_chain, + } + + if fix_gas_to_chain: + # Store per-pool fixed gas values for downstream use + from quantammsim.calibration.loss import CHAIN_GAS_USD + gas_per_pool = [] + for pid in jdata.pool_ids: + chain = matched[pid]["chain"] + gas_per_pool.append(CHAIN_GAS_USD.get(chain, 1.0)) + out["gas_per_pool"] = np.array(gas_per_pool) + out["bias_gas"] = 0.0 + out["W_gas"] = np.zeros(k_attr) + else: + out["bias_gas"] = float(params["bias_gas"]) + out["W_gas"] = np.array(params["W_gas"]) + + if mode == "per_pool_noise": + out["noise_coeffs"] = np.array(params["noise_coeffs"]) + else: + out["bias_noise"] = np.array(params["bias_noise"]) + out["W_noise"] = np.array(params["W_noise"]) + + return out + + +def predict_new_pool_joint( + result: dict, + x_attr: np.ndarray, +) -> dict: + """Predict simulator settings for a new pool using joint-fitted mapping. + + In per_pool_noise mode, only cadence and gas are predicted (noise + coefficients are per-pool and can't generalize). Use shared_noise + mode for full deployment predictions including noise_coeffs. + + Args: + result: dict from fit_joint + x_attr: (K_attr,) pool attribute vector — must match the k_attr + from training (check result['attr_names'] for the feature order). + No intercept — just the real features. + + Returns dict with cadence_minutes, gas_usd, and noise_coeffs (shared_noise only). + """ + x = np.asarray(x_attr) + log_cadence = result["bias_cad"] + float(x @ result["W_cad"]) + log_gas = result["bias_gas"] + float(x @ result["W_gas"]) + + out = { + "log_cadence": log_cadence, + "log_gas": log_gas, + "cadence_minutes": float(np.exp(log_cadence)), + "gas_usd": float(np.exp(log_gas)), + } + + if result["mode"] == "shared_noise": + out["noise_coeffs"] = np.array( + result["bias_noise"] + x @ result["W_noise"] + ) + + return out diff --git a/quantammsim/calibration/learned_mapping.py b/quantammsim/calibration/learned_mapping.py new file mode 100644 index 0000000..294d008 --- /dev/null +++ b/quantammsim/calibration/learned_mapping.py @@ -0,0 +1,126 @@ +"""Learned mapping: pool attributes -> (cadence, gas, noise_coeffs). + +Ridge regression from pool-level features to per-pool fitted parameters. +Trained on per-pool fit results from per_pool_fit.py; used to predict +simulator settings for new/hypothetical pools. +""" + +from typing import Dict, List + +import numpy as np + +from quantammsim.calibration.loss import K_OBS + + +def build_targets( + fit_results: Dict[str, dict], + pool_order: List[str], +) -> np.ndarray: + """Stack per-pool fitted params into (n_pools, 2+K_OBS) target matrix. + + Columns: [log_cadence, log_gas, noise_coeffs...] + Row ordering matches pool_order. + """ + n_pools = len(pool_order) + Y = np.zeros((n_pools, 2 + K_OBS)) + for i, pid in enumerate(pool_order): + r = fit_results[pid] + Y[i, 0] = r["log_cadence"] + Y[i, 1] = r["log_gas"] + Y[i, 2:] = r["noise_coeffs"] + return Y + + +def fit_mapping( + X_attr: np.ndarray, + Y_target: np.ndarray, + alpha: float = 1.0, +) -> dict: + """Fit Ridge regression: X_attr -> Y_target. + + Multi-output Ridge: one shared regularization strength across all + target columns. + + Returns dict with weights, intercept, and diagnostics. + """ + # Ridge with intercept: center Y, solve on centered data + n, k = X_attr.shape + Y_mean = Y_target.mean(axis=0) + X_mean = X_attr.mean(axis=0) + Xc = X_attr - X_mean + Yc = Y_target - Y_mean + + # W = (Xc^T Xc + alpha * I)^{-1} Xc^T Yc + A = Xc.T @ Xc + alpha * np.eye(k) + W = np.linalg.solve(A, Xc.T @ Yc) # (K_attr, K_target) + intercept = Y_mean - X_mean @ W # (K_target,) + + Y_pred = X_attr @ W + intercept + ss_res = np.sum((Y_target - Y_pred) ** 2) + ss_tot = np.sum((Y_target - Y_target.mean(axis=0)) ** 2) + r2 = 1 - ss_res / max(ss_tot, 1e-10) + + return { + "weights": W, # (K_attr, K_target) + "intercept": intercept, # (K_target,) + "alpha": alpha, + "r2_train": float(r2), + } + + +def predict_pool( + mapping: dict, + x_attr: np.ndarray, +) -> dict: + """Predict simulator settings for a new pool. + + Args: + mapping: dict from fit_mapping + x_attr: (K_attr,) single pool attribute vector + + Returns dict with cadence_minutes, gas_usd, noise_coeffs, etc. + """ + y = x_attr @ mapping["weights"] + mapping["intercept"] + + log_cadence = float(y[0]) + log_gas = float(y[1]) + noise_coeffs = np.array(y[2:]) + + return { + "log_cadence": log_cadence, + "log_gas": log_gas, + "cadence_minutes": float(np.exp(log_cadence)), + "gas_usd": float(np.exp(log_gas)), + "noise_coeffs": noise_coeffs, + } + + +def cross_validate_loo( + X_attr: np.ndarray, + Y_target: np.ndarray, + alpha: float = 1.0, +) -> dict: + """Leave-one-out cross-validation. + + Returns per-pool prediction errors and summary statistics. + """ + n = X_attr.shape[0] + errors = np.zeros((n, Y_target.shape[1])) + + for i in range(n): + mask = np.ones(n, dtype=bool) + mask[i] = False + X_train = X_attr[mask] + Y_train = Y_target[mask] + + model = fit_mapping(X_train, Y_train, alpha=alpha) + y_pred = X_attr[i] @ model["weights"] + model["intercept"] + errors[i] = Y_target[i] - y_pred + + mse = np.mean(errors ** 2, axis=0) + return { + "per_pool_errors": errors, + "mse_per_target": mse, + "rmse_per_target": np.sqrt(mse), + "mean_rmse": float(np.mean(np.sqrt(mse))), + } diff --git a/quantammsim/calibration/loss.py b/quantammsim/calibration/loss.py new file mode 100644 index 0000000..cdc8c1e --- /dev/null +++ b/quantammsim/calibration/loss.py @@ -0,0 +1,139 @@ +"""Per-pool loss function for the direct calibration pipeline. + +JAX-differentiable loss: sum((log(V_arb_i + V_noise_i) - log(V_obs_i))^2) +where V_arb comes from per-day grid interpolation and V_noise from +log-linear regression on observation covariates. +""" + +from typing import Tuple + +import jax.numpy as jnp + +from quantammsim.calibration.grid_interpolation import ( + PoolCoeffsDaily, + interpolate_pool_daily, +) + +K_OBS = 8 # observation-level covariates + +# Known chain gas costs (USD) — used when fixing gas to chain-level values. +# These are effective per-transaction costs, not per-gas-unit. +CHAIN_GAS_USD = { + "MAINNET": 1.0, + "POLYGON": 0.005, + "GNOSIS": 0.001, + "ARBITRUM": 0.01, + "BASE": 0.005, + "SONIC": 0.005, +} + + +def noise_volume( + noise_coeffs: jnp.ndarray, x_obs: jnp.ndarray +) -> jnp.ndarray: + """V_noise = exp(x_obs @ noise_coeffs). Shape: (n_obs,).""" + return jnp.exp(x_obs @ noise_coeffs) + + +def pack_params( + log_cadence: float, log_gas: float, noise_coeffs: jnp.ndarray +) -> jnp.ndarray: + """Pack into flat array: [log_cadence, log_gas, noise_coeffs...].""" + return jnp.concatenate([ + jnp.array([log_cadence, log_gas]), + jnp.asarray(noise_coeffs), + ]) + + +def unpack_params( + flat: jnp.ndarray, +) -> Tuple[float, float, jnp.ndarray]: + """Unpack flat array to (log_cadence, log_gas, noise_coeffs).""" + return flat[0], flat[1], flat[2:] + + +def pack_params_fixed_gas( + log_cadence: float, noise_coeffs: jnp.ndarray +) -> jnp.ndarray: + """Pack into flat array with gas excluded: [log_cadence, noise_coeffs...].""" + return jnp.concatenate([ + jnp.array([log_cadence]), + jnp.asarray(noise_coeffs), + ]) + + +def unpack_params_fixed_gas( + flat: jnp.ndarray, +) -> Tuple[float, jnp.ndarray]: + """Unpack flat array to (log_cadence, noise_coeffs). Gas not included.""" + return flat[0], flat[1:] + + +def _compute_loss_huber( + residuals: jnp.ndarray, + delta: float = 1.5, +) -> jnp.ndarray: + """Huber loss: 0.5*r^2 for |r|<=delta, delta*(|r|-0.5*delta) otherwise.""" + abs_r = jnp.abs(residuals) + return jnp.mean( + jnp.where(abs_r <= delta, 0.5 * residuals ** 2, + delta * (abs_r - 0.5 * delta)) + ) + + +def pool_loss( + params_flat: jnp.ndarray, + coeffs: PoolCoeffsDaily, + x_obs: jnp.ndarray, + y_obs: jnp.ndarray, + day_indices: jnp.ndarray, +) -> jnp.ndarray: + """Per-pool log-space L2 loss with per-day V_arb. + + Args: + params_flat: [log_cadence, log_gas, noise_coeffs...] from pack_params + coeffs: PoolCoeffsDaily with per-day grid values + x_obs: (n_obs, K_OBS) observation covariates + y_obs: (n_obs,) log(V_obs) — observed log volume + day_indices: (n_obs,) int indices mapping panel rows to grid days + + Returns: + Scalar mean squared error in log space. + """ + log_cadence, log_gas, noise_coeffs = unpack_params(params_flat) + + # Per-day V_arb from grid interpolation + v_arb_all = interpolate_pool_daily(coeffs, log_cadence, jnp.exp(log_gas)) # (n_days,) + v_arb = v_arb_all[day_indices] # (n_obs,) + + # Per-day V_noise from covariates + v_noise = noise_volume(noise_coeffs, x_obs) # (n_obs,) + + # Log-space L2 loss + log_v_pred = jnp.log(jnp.maximum(v_arb + v_noise, 1e-6)) + return jnp.mean((log_v_pred - y_obs) ** 2) + + +def pool_loss_fixed_gas( + params_flat: jnp.ndarray, + fixed_log_gas: float, + coeffs: PoolCoeffsDaily, + x_obs: jnp.ndarray, + y_obs: jnp.ndarray, + day_indices: jnp.ndarray, +) -> jnp.ndarray: + """Per-pool loss with gas fixed to a known chain-level value. + + Args: + params_flat: [log_cadence, noise_coeffs...] — no log_gas + fixed_log_gas: log(gas_usd) held constant (not optimized) + coeffs, x_obs, y_obs, day_indices: as in pool_loss + """ + log_cadence, noise_coeffs = unpack_params_fixed_gas(params_flat) + + v_arb_all = interpolate_pool_daily(coeffs, log_cadence, jnp.exp(fixed_log_gas)) + v_arb = v_arb_all[day_indices] + v_noise = noise_volume(noise_coeffs, x_obs) + + log_v_pred = jnp.log(jnp.maximum(v_arb + v_noise, 1e-6)) + return jnp.mean((log_v_pred - y_obs) ** 2) diff --git a/quantammsim/calibration/market_features.py b/quantammsim/calibration/market_features.py new file mode 100644 index 0000000..f2dd610 --- /dev/null +++ b/quantammsim/calibration/market_features.py @@ -0,0 +1,308 @@ +"""Market-level and token-level features for the noise volume model. + +Derives daily features from Binance minute-level price data and pool metadata. +Features are grounded in market microstructure — what mechanistically drives +organic (non-arb) trading volume: + +Market regime: + - BTC log price, log return — crypto market regime proxy + - BTC trend (rolling mean log return) — bull/bear at various horizons + +Token-level (per pool token): + - Token log price, daily log return + - Token realized volatility — higher vol → more hedging/speculative flow + - Token Binance volume — proxy for overall token trading interest + - Token trend (rolling mean log return) + +All features are computed daily and aligned to the panel date grid. +""" + +import os +from typing import Dict, List, Optional, Tuple + +import numpy as np +import pandas as pd + +DATA_DIR = os.path.join( + os.path.dirname(os.path.dirname(os.path.abspath(__file__))), + "data", +) + +# Map wrapped/derivative tokens to their Binance underlying +TOKEN_MAP = { + "WETH": "ETH", + "WBTC": "BTC", + "wstETH": "ETH", + "waEthLidowstETH": "ETH", + "waEthLidoWETH": "ETH", + "waGnowstETH": "ETH", + "waGnoGNO": "GNO", + "waBasUSDC": "USDC", + "waBasWETH": "ETH", + "sDAI": "DAI", + "scUSD": "USDC", + "stS": "S", + "JitoSOL": "SOL", + "USDT": "USDC", # treat as $1 stablecoin +} + + +def _load_binance_daily(symbol: str) -> pd.DataFrame: + """Load Binance minute data and resample to daily OHLCV.""" + mapped = TOKEN_MAP.get(symbol, symbol) + path = os.path.join(DATA_DIR, f"{mapped}_USD.parquet") + if not os.path.exists(path): + return None + + df = pd.read_parquet(path, columns=["date", "close", "Volume USD"]) + df["date"] = pd.to_datetime(df["date"]) + df = df.set_index("date").sort_index() + + daily = df.resample("1D").agg({ + "close": "last", + "Volume USD": "sum", + }).dropna(subset=["close"]) + + daily.columns = ["close", "volume_usd"] + return daily + + +def _compute_token_features( + daily: pd.DataFrame, + trend_windows: List[int] = (7, 14, 30), + is_market: bool = False, +) -> pd.DataFrame: + """Compute daily features from a token's OHLCV. + + For market-level tokens (BTC): includes log_price as regime proxy. + For pool tokens: only returns/vol/trends (comparable across tokens). + + Volume is normalised as z-score within each token: today's log-volume + relative to a 30-day trailing mean/std. This captures "is this token + unusually active today" without the cross-token scale problem. + + Returns DataFrame indexed by date. + """ + out = pd.DataFrame(index=daily.index) + log_price = np.log(daily["close"].clip(lower=1e-10)) + out["log_return"] = log_price.diff() + + if is_market: + # BTC log_price is a market regime proxy (same for all pools) + out["log_price"] = log_price + + # Realized volatility: std of log returns over trailing 7 days + out["realized_vol_7d"] = out["log_return"].rolling(7, min_periods=3).std() + + # Volume: z-score relative to trailing 30d mean/std of log-volume + # Captures "unusually active day for this token" — comparable across tokens + log_vol = np.log(daily["volume_usd"].clip(lower=1.0)) + vol_mean_30d = log_vol.rolling(30, min_periods=10).mean() + vol_std_30d = log_vol.rolling(30, min_periods=10).std().clip(lower=0.1) + out["volume_zscore"] = (log_vol - vol_mean_30d) / vol_std_30d + + # Trend: rolling mean log return at various horizons + for w in trend_windows: + out[f"trend_{w}d"] = out["log_return"].rolling(w, min_periods=max(w // 2, 2)).mean() + + return out + + +def build_btc_daily_features( + trend_windows: List[int] = (7, 14, 30), +) -> pd.DataFrame: + """BTC daily features as market regime proxy. + + Returns DataFrame indexed by date with columns prefixed 'btc_'. + """ + daily = _load_binance_daily("BTC") + if daily is None: + raise FileNotFoundError("BTC_USD.parquet not found") + + feat = _compute_token_features(daily, trend_windows, is_market=True) + feat.columns = [f"btc_{c}" for c in feat.columns] + return feat + + +def build_token_daily_features( + symbol: str, + trend_windows: List[int] = (7, 14, 30), +) -> Optional[pd.DataFrame]: + """Daily features for a single token. Returns None if no data.""" + daily = _load_binance_daily(symbol) + if daily is None: + return None + return _compute_token_features(daily, trend_windows) + + +def _compute_pair_volatility( + symbol_a: str, + symbol_b: str, +) -> Optional[pd.DataFrame]: + """Compute realized volatility of the A/B price ratio. + + vol(log(price_A/price_B)) = vol(log(price_A) - log(price_B)) + Symmetric: A/B and B/A give identical volatility. + + Returns DataFrame indexed by date with 'pair_realized_vol_7d'. + """ + daily_a = _load_binance_daily(symbol_a) + daily_b = _load_binance_daily(symbol_b) + if daily_a is None or daily_b is None: + return None + + # Align on common dates + log_a = np.log(daily_a["close"].clip(lower=1e-10)) + log_b = np.log(daily_b["close"].clip(lower=1e-10)) + common = log_a.index.intersection(log_b.index) + if len(common) < 10: + return None + + log_ratio = log_a.loc[common] - log_b.loc[common] + log_ratio_return = log_ratio.diff() + + out = pd.DataFrame(index=common) + out["pair_realized_vol_7d"] = log_ratio_return.rolling(7, min_periods=3).std() + return out + + +def build_pool_market_features( + matched_clean: Dict[str, dict], + trend_windows: List[int] = (7, 14, 30), +) -> Dict[str, pd.DataFrame]: + """Build per-pool market feature DataFrames. + + For each pool, produces a DataFrame aligned to the pool's panel dates with: + - BTC features (market regime) + - Token A features (vs USD) + - Token B features (vs USD) + - Pair volatility (A/B ratio) + + Returns dict: pool_id -> DataFrame with all features. + """ + from quantammsim.calibration.pool_data import _parse_tokens + + # Load BTC features once + btc_feat = build_btc_daily_features(trend_windows) + + # Cache token features and pair volatilities + token_cache = {} + pair_vol_cache = {} + + pool_features = {} + for pid, entry in matched_clean.items(): + panel = entry["panel"] + dates = pd.to_datetime(panel["date"]) + + # Parse tokens + toks = _parse_tokens(entry["tokens"]) + tok_a, tok_b = toks[0], toks[1] if len(toks) > 1 else toks[0] + + # Get token features + for tok in [tok_a, tok_b]: + mapped = TOKEN_MAP.get(tok, tok) + if mapped not in token_cache: + token_cache[mapped] = build_token_daily_features(mapped, trend_windows) + + feat_a = token_cache.get(TOKEN_MAP.get(tok_a, tok_a)) + feat_b = token_cache.get(TOKEN_MAP.get(tok_b, tok_b)) + + # Pair volatility (cache by sorted token pair to avoid duplicates) + mapped_a = TOKEN_MAP.get(tok_a, tok_a) + mapped_b = TOKEN_MAP.get(tok_b, tok_b) + pair_key = tuple(sorted([mapped_a, mapped_b])) + if pair_key not in pair_vol_cache: + pair_vol_cache[pair_key] = _compute_pair_volatility(mapped_a, mapped_b) + pair_vol = pair_vol_cache[pair_key] + + # Build per-date feature vectors + rows = [] + for date in dates: + day = pd.Timestamp(date).normalize() + row = {} + + # BTC features + if day in btc_feat.index: + for col in btc_feat.columns: + row[col] = btc_feat.loc[day, col] + + # Token A features + if feat_a is not None and day in feat_a.index: + for col in feat_a.columns: + row[f"tok_a_{col}"] = feat_a.loc[day, col] + + # Token B features + if feat_b is not None and day in feat_b.index: + for col in feat_b.columns: + row[f"tok_b_{col}"] = feat_b.loc[day, col] + + # Pair volatility + if pair_vol is not None and day in pair_vol.index: + row["pair_realized_vol_7d"] = pair_vol.loc[day, "pair_realized_vol_7d"] + + rows.append(row) + + df = pd.DataFrame(rows, index=dates) + pool_features[pid] = df + + return pool_features + + +def pool_market_features_to_matrix( + pool_features: Dict[str, pd.DataFrame], + matched_clean: Dict[str, dict], + date_to_idx: Dict, + pool_ids: List[str], + sample_pools: np.ndarray, + sample_days: np.ndarray, +) -> Tuple[np.ndarray, List[str]]: + """Convert per-pool market features to a (n_samples, n_feat) matrix. + + Aligns features to the common date grid and sample indices. + NaN-fills missing values, then imputes with column mean. + + Returns (feature_matrix, feature_names). + """ + # Get feature columns from first pool + first_pid = pool_ids[0] + feat_cols = sorted(pool_features[first_pid].columns) + n_feat = len(feat_cols) + n_pools = len(pool_ids) + + # Collect all dates + n_dates = max(date_to_idx.values()) + 1 + + # Build (n_dates, n_pools, n_feat) grid + feat_grid = np.full((n_dates, n_pools, n_feat), np.nan, dtype=np.float32) + + for j, pid in enumerate(pool_ids): + if pid not in pool_features: + continue + pf = pool_features[pid] + panel_dates = matched_clean[pid]["panel"]["date"].values + for k, date in enumerate(panel_dates): + t = date_to_idx.get(date) + if t is None: + continue + for f, col in enumerate(feat_cols): + if col in pf.columns and k < len(pf): + val = pf.iloc[k][col] if col in pf.columns else np.nan + if np.isfinite(val): + feat_grid[t, j, f] = val + + # Extract per-sample + n_samples = len(sample_pools) + X = np.zeros((n_samples, n_feat), dtype=np.float32) + for s in range(n_samples): + X[s] = feat_grid[sample_days[s], sample_pools[s]] + + # Impute NaN with column mean + for f in range(n_feat): + col = X[:, f] + mask = np.isnan(col) + if mask.all(): + col[:] = 0.0 + elif mask.any(): + col[mask] = np.nanmean(col) + + return X, feat_cols diff --git a/quantammsim/calibration/noise_model_arrays.py b/quantammsim/calibration/noise_model_arrays.py new file mode 100644 index 0000000..d04431f --- /dev/null +++ b/quantammsim/calibration/noise_model_arrays.py @@ -0,0 +1,497 @@ +"""Precompute noise_base and noise_tvl_coeff arrays for the simulator. + +Builds daily feature vectors from Binance price data only — no panel/API +dependency. Works for any date range covered by Binance parquets. + +Produces the two arrays needed by reclamm_market_linear_noise_volume(): + + log(V_daily_noise) = noise_base_t + noise_tvl_coeff_t * log(effective_TVL) + +Usage: + from quantammsim.calibration.noise_model_arrays import build_simulator_arrays + + arrays = build_simulator_arrays( + token_a="AAVE", token_b="ETH", + start_date="2024-06-01", + end_date="2026-03-01", + artifact_dir="results/linear_market_noise", + pool_id="0x9d1fcf346ea1b0", # for per-pool coeffs + ) +""" + +import json +import os +from typing import Dict, List, Optional, Tuple + +import numpy as np +import pandas as pd + + +def load_artifact(artifact_dir: str) -> Tuple[dict, dict]: + """Load model.npz + meta.json from artifact directory.""" + art = np.load(os.path.join(artifact_dir, "model.npz"), allow_pickle=True) + with open(os.path.join(artifact_dir, "meta.json")) as f: + meta = json.load(f) + return dict(art), meta + + +def _find_pool_index(pool_id: str, pool_ids: list) -> int: + """Match pool_id (full or prefix) to calibration pool list.""" + for i, cid in enumerate(pool_ids): + if pool_id.startswith(cid) or cid.startswith(pool_id): + return i + return -1 + + +def _identify_tvl_columns(feat_names: list) -> Tuple[int, list]: + """Identify which feature columns involve TVL. + + Returns: + tvl_col: index of the pure log_tvl feature (xobs_1) + tvl_interaction_cols: list of (col_idx, paired_col_idx) + """ + tvl_col = None + tvl_interaction_cols = [] + + for i, name in enumerate(feat_names): + if name == "xobs_1": + tvl_col = i + elif "xobs_1\u00d7" in name: + paired_name = name.split("\u00d7")[1] + for j, n2 in enumerate(feat_names): + if n2 == paired_name: + tvl_interaction_cols.append((i, j)) + break + + if tvl_col is None: + raise ValueError("xobs_1 (log_tvl) not found in feature names") + + return tvl_col, tvl_interaction_cols + + +def build_daily_features_from_binance( + token_a: str, + token_b: str, + start_date: str, + end_date: str, + feat_names: List[str], + x_mean: np.ndarray, + x_std: np.ndarray, + trend_windows: tuple = (7,), +) -> Tuple[np.ndarray, list]: + """Build daily feature matrix from Binance data only. + + No panel or API dependency. Features: + - xobs_0 (intercept), xobs_1 (log_tvl — filled with 0, handled at runtime), + xobs_2/3 (dow_sin/cos) + - BTC: log_price, log_return, realized_vol_7d, trend, volume_zscore + - Token A/B: log_return, realized_vol_7d, trend, volume_zscore + - Pair realized_vol_7d + - Interaction terms + """ + from quantammsim.calibration.market_features import ( + build_btc_daily_features, + build_token_daily_features, + _compute_pair_volatility, + TOKEN_MAP, + ) + + start = pd.Timestamp(start_date) + end = pd.Timestamp(end_date) + + # Generate complete daily date range + date_range = pd.date_range(start, end, freq="D") + n_days = len(date_range) + + # BTC features + btc_feat = build_btc_daily_features(list(trend_windows)) + + # Token features + mapped_a = TOKEN_MAP.get(token_a, token_a) + mapped_b = TOKEN_MAP.get(token_b, token_b) + feat_a = build_token_daily_features(mapped_a, list(trend_windows)) + feat_b = build_token_daily_features(mapped_b, list(trend_windows)) + + # Pair volatility + pair_vol = _compute_pair_volatility(token_a, token_b) + + # Identify which features are x_obs vs market + # x_obs features: xobs_0 (intercept), xobs_1 (tvl), xobs_2 (dow_sin), xobs_3 (dow_cos) + # Remaining xobs_4,5,6 are cross-pool — skip if not in feat_names + n_xobs = sum(1 for f in feat_names if f.startswith("xobs_")) + + # Build market feature column list (everything after x_obs, before interactions) + market_names = [f for f in feat_names + if not f.startswith("xobs_") and "\u00d7" not in f] + + # Build per-day feature vectors + x_base_cols = n_xobs + len(market_names) + x_base = np.zeros((n_days, x_base_cols), dtype=np.float32) + + for k, day in enumerate(date_range): + day_norm = day.normalize() + + # x_obs + x_base[k, 0] = 1.0 # intercept + # x_base[k, 1] = 0.0 # log_tvl — placeholder, handled at runtime + weekday = day.weekday() + if n_xobs > 2: + x_base[k, 2] = np.sin(2 * np.pi * weekday / 7) + if n_xobs > 3: + x_base[k, 3] = np.cos(2 * np.pi * weekday / 7) + # xobs_4,5,6 (cross-pool) left as 0 if present + + # Market features + col = n_xobs + for mname in market_names: + val = 0.0 + if mname.startswith("btc_") and btc_feat is not None: + bcol = mname[4:] # strip "btc_" + if day_norm in btc_feat.index and bcol in btc_feat.columns: + v = btc_feat.loc[day_norm, bcol] + if np.isfinite(v): + val = v + elif mname.startswith("tok_a_") and feat_a is not None: + acol = mname[6:] + if day_norm in feat_a.index and acol in feat_a.columns: + v = feat_a.loc[day_norm, acol] + if np.isfinite(v): + val = v + elif mname.startswith("tok_b_") and feat_b is not None: + bcol = mname[6:] + if day_norm in feat_b.index and bcol in feat_b.columns: + v = feat_b.loc[day_norm, bcol] + if np.isfinite(v): + val = v + elif mname == "pair_realized_vol_7d" and pair_vol is not None: + if day_norm in pair_vol.index: + v = pair_vol.loc[day_norm, "pair_realized_vol_7d"] + if np.isfinite(v): + val = v + x_base[k, col] = val + col += 1 + + # Selective standardization — must match build_data() in run_linear_market_noise.py. + # Only realized vols and cross-pool volumes are standardized; everything else is raw. + for i, name in enumerate(feat_names[:x_base_cols]): + if i >= len(x_mean): + break + if "realized_vol" in name or "pair_realized" in name: + x_base[:, i] = (x_base[:, i] - x_mean[i]) / max(float(x_std[i]), 0.01) + elif name.startswith("xobs_") and name != "xobs_0": + parts = name.split("_") + if len(parts) >= 2 and parts[1].isdigit() and int(parts[1]) >= 4: + x_base[:, i] = (x_base[:, i] - x_mean[i]) / max(float(x_std[i]), 1e-6) + # else: leave untouched (intercept, log_price, dow, returns, trends, vol_zscore) + x_base = x_base.astype(np.float32) + + # Interaction terms + base_feat_names = feat_names[:x_base_cols] + col_idx = {name: i for i, name in enumerate(base_feat_names)} + + interactions = [] + for fname in feat_names[x_base_cols:]: + if "\u00d7" in fname: + parts = fname.split("\u00d7") + if parts[0] in col_idx and parts[1] in col_idx: + interactions.append( + x_base[:, col_idx[parts[0]]] * x_base[:, col_idx[parts[1]]]) + else: + interactions.append(np.zeros(n_days, dtype=np.float32)) + else: + interactions.append(np.zeros(n_days, dtype=np.float32)) + + if interactions: + x_all = np.concatenate( + [x_base, np.column_stack(interactions)], axis=1).astype(np.float32) + else: + x_all = x_base + + return x_all, date_range.tolist() + + +def build_simulator_arrays( + token_a: str, + token_b: str, + start_date: str, + end_date: str, + artifact_dir: str = "results/linear_market_noise", + pool_id: Optional[str] = None, +) -> Dict: + """Build noise_base and noise_tvl_coeff arrays for the simulator. + + No panel dependency — uses Binance data only. + + Parameters + ---------- + token_a, token_b : str + Token symbols (e.g. "AAVE", "ETH"). Mapped to Binance symbols + internally (WETH→ETH, wstETH→ETH, etc.) + start_date, end_date : str + Date range (inclusive). + artifact_dir : str + Directory containing model.npz and meta.json. + pool_id : str, optional + Pool ID for per-pool coefficients. If None or not found, + uses median coefficients. + + Returns + ------- + dict with noise_base, noise_tvl_coeff, tvl_mean, tvl_std, dates, etc. + """ + art, meta = load_artifact(artifact_dir) + noise_coeffs = art["noise_coeffs"] + feat_names = meta["feat_names"] + pool_ids = meta["pool_ids"] + x_mean = art["x_mean"] + x_std = art["x_std"] + per_pool = noise_coeffs.ndim == 2 + trend_windows = tuple(meta["hparams"]["trend_windows"]) + + # Find pool coefficients + pool_idx = -1 + if pool_id is not None: + pool_idx = _find_pool_index(pool_id, pool_ids) + + if pool_idx >= 0 and per_pool: + coeffs = noise_coeffs[pool_idx] + print(f" Using per-pool coefficients (pool idx {pool_idx})") + elif per_pool: + coeffs = np.median(noise_coeffs, axis=0) + print(f" Pool not found, using median coefficients") + else: + coeffs = noise_coeffs + + # Build daily features from Binance + print(f" Building features from Binance data: {token_a}/{token_b}," + f" {start_date} → {end_date}") + x_daily, dates = build_daily_features_from_binance( + token_a, token_b, start_date, end_date, + feat_names, x_mean, x_std, trend_windows, + ) + n_days = len(dates) + print(f" {n_days} days, {len(feat_names)} features") + + # Decompose into base (non-TVL) and tvl_coeff + tvl_col, tvl_interactions = _identify_tvl_columns(feat_names) + + tvl_coeff_daily = np.full(n_days, coeffs[tvl_col], dtype=np.float64) + for inter_col, paired_col in tvl_interactions: + tvl_coeff_daily += coeffs[inter_col] * x_daily[:, paired_col] + + tvl_related = {tvl_col} | {ic for ic, _ in tvl_interactions} + base_daily = np.zeros(n_days, dtype=np.float64) + for j in range(len(feat_names)): + if j not in tvl_related: + base_daily += coeffs[j] * x_daily[:, j] + + # Expand to minute resolution + n_minutes = n_days * 1440 + noise_base = np.repeat(base_daily, 1440) + noise_tvl_coeff = np.repeat(tvl_coeff_daily, 1440) + + return { + "noise_base": noise_base, + "noise_tvl_coeff": noise_tvl_coeff, + "tvl_mean": float(x_mean[tvl_col]), + "tvl_std": float(x_std[tvl_col]), + "dates": dates, + "pool_index": pool_idx, + "n_days": n_days, + "n_minutes": n_minutes, + "coeffs": coeffs, + "tvl_col": tvl_col, + } + + +def build_mm_simulator_arrays( + token_a: str, + token_b: str, + start_date: str, + end_date: str, + mm_artifact_dir: str = "results/mm_noise", + competitor_tvl_path: str = "results/competitor_tvl/competitor_tvl.npz", + pool_id: Optional[str] = None, +) -> Dict: + """Build noise_base and competitor_tvl arrays for the MM simulator. + + The MM noise model evaluates:: + + V_noise = exp(noise_base_t) * TVL / (K_t + TVL) + + where noise_base_t = alpha_i + gamma_i @ x_market_t absorbs all + non-TVL terms, and K_t = competitor_tvl_t is observed from DeFi + Llama (network conductance model: direct + multi-hop). + + Builds the 18 market features directly from Binance data using the + same helper functions as the calibration pipeline. Standardization + follows the same selective rules as build_data(): only realized_vol + features are centered/scaled, everything else is left raw. + """ + from quantammsim.calibration.market_features import ( + build_btc_daily_features, + build_token_daily_features, + _compute_pair_volatility, + TOKEN_MAP, + ) + + art, meta = load_artifact(mm_artifact_dir) + pool_ids = meta["pool_ids"] + market_names = meta["market_names"] + n_market = meta["n_market_feat"] + per_pool_gamma = meta.get("per_pool_gamma", False) + + pool_idx = -1 + if pool_id is not None: + pool_idx = _find_pool_index(pool_id, pool_ids) + + log_alpha = art["log_alpha"] + gamma = art["gamma"] + + if pool_idx >= 0: + alpha_i = float(log_alpha[pool_idx]) + gamma_i = gamma[pool_idx] if per_pool_gamma else gamma + print(f" MM model: pool idx {pool_idx}, alpha={alpha_i:.3f}") + else: + alpha_i = float(np.median(log_alpha)) + gamma_i = np.median(gamma, axis=0) if per_pool_gamma else gamma + print(f" MM model: pool not found, using median alpha={alpha_i:.3f}") + + # Build daily market features from Binance (same helpers as calibration) + start_ts = pd.Timestamp(start_date) + end_ts = pd.Timestamp(end_date) + date_range = pd.date_range(start_ts, end_ts, freq="D") + n_days = len(date_range) + + mapped_a = TOKEN_MAP.get(token_a, token_a) + mapped_b = TOKEN_MAP.get(token_b, token_b) + btc_feat = build_btc_daily_features([7]) + feat_a = build_token_daily_features(mapped_a, [7]) + feat_b = build_token_daily_features(mapped_b, [7]) + pair_vol = _compute_pair_volatility(mapped_a, mapped_b) + + print(f" Building features from Binance: {token_a}/{token_b}," + f" {start_date} → {end_date}") + + x_market = np.zeros((n_days, n_market), dtype=np.float64) + for k, day in enumerate(date_range): + day_norm = day.normalize() + for mi, mname in enumerate(market_names): + val = 0.0 + if mname == "xobs_0": + val = 1.0 + elif mname == "xobs_2": + val = np.sin(2 * np.pi * day.weekday() / 7) + elif mname == "xobs_3": + val = np.cos(2 * np.pi * day.weekday() / 7) + elif mname.startswith("btc_") and btc_feat is not None: + if day_norm in btc_feat.index and mname in btc_feat.columns: + v = btc_feat.loc[day_norm, mname] + if np.isfinite(v): + val = v + elif mname.startswith("tok_a_") and feat_a is not None: + acol = mname[6:] + if day_norm in feat_a.index and acol in feat_a.columns: + v = feat_a.loc[day_norm, acol] + if np.isfinite(v): + val = v + elif mname.startswith("tok_b_") and feat_b is not None: + bcol = mname[6:] + if day_norm in feat_b.index and bcol in feat_b.columns: + v = feat_b.loc[day_norm, bcol] + if np.isfinite(v): + val = v + elif mname == "pair_realized_vol_7d" and pair_vol is not None: + if day_norm in pair_vol.index: + v = pair_vol.loc[day_norm, "pair_realized_vol_7d"] + if np.isfinite(v): + val = v + x_market[k, mi] = val + + # Selective standardization: only realized_vol features get centered/scaled. + # Must use the TRAINING panel's stats (saved in model.npz as x_mean/x_std + # in the 22-feature linear space). Map to the 18 MM features by removing + # the TVL column (index 1) and 3 TVL-interaction columns (indices 19-21). + x_mean_22 = art.get("x_mean") + x_std_22 = art.get("x_std") + if x_mean_22 is not None and x_std_22 is not None and len(x_mean_22) == 22: + keep_22_to_18 = [i for i in range(22) if i not in {1, 19, 20, 21}] + x_mean_18 = x_mean_22[keep_22_to_18] + x_std_18 = x_std_22[keep_22_to_18] + for mi, mname in enumerate(market_names): + if x_mean_18[mi] != 0 or x_std_18[mi] != 1: + x_market[:, mi] = (x_market[:, mi] - x_mean_18[mi]) / max(x_std_18[mi], 0.01) + else: + # Fallback: compute local stats (less accurate but won't crash) + print(" WARNING: training stats not found, using local standardization") + for mi, mname in enumerate(market_names): + if ("realized_vol" in mname or "pair_realized" in mname) and "\u00d7" not in mname: + col_mean = float(np.mean(x_market[:, mi])) + col_std = max(float(np.std(x_market[:, mi])), 0.01) + x_market[:, mi] = (x_market[:, mi] - col_mean) / col_std + + # Interaction terms + name_to_col = {n: i for i, n in enumerate(market_names)} + for mi, mname in enumerate(market_names): + if "\u00d7" in mname: + parts = mname.split("\u00d7") + if parts[0] in name_to_col and parts[1] in name_to_col: + x_market[:, mi] = (x_market[:, name_to_col[parts[0]]] * + x_market[:, name_to_col[parts[1]]]) + + # Compute noise_base = alpha_i + gamma_i @ x_market + noise_base_daily = alpha_i + x_market @ gamma_i + + # Load competitor TVL (K) + print(f" Loading competitor TVL from {competitor_tvl_path}") + comp_data = np.load(competitor_tvl_path, allow_pickle=True) + comp_pool_ids = list(comp_data["pool_ids"]) + comp_dates = list(comp_data["date_list"]) + k_eff = comp_data["k_eff"] + + # Find pool in competitor data + comp_pool_idx = -1 + if pool_id is not None: + comp_pool_idx = _find_pool_index(pool_id, comp_pool_ids) + + if comp_pool_idx < 0: + print(f" WARNING: pool not in competitor TVL data, using K=$10M") + K_daily = np.full(n_days, 10e6, dtype=np.float64) + else: + # Build date index for competitor data + comp_date_to_idx = {} + for ci, d in enumerate(comp_dates): + comp_date_to_idx[str(d)[:10]] = ci + + K_daily = np.full(n_days, np.nan, dtype=np.float64) + for k, day in enumerate(date_range): + ds = str(pd.Timestamp(day))[:10] + if ds in comp_date_to_idx: + ci = comp_date_to_idx[ds] + val = k_eff[ci, comp_pool_idx] + if np.isfinite(val) and val > 0: + K_daily[k] = val + + # Forward-fill / back-fill + s = pd.Series(K_daily).ffill().bfill() + K_daily = s.values.astype(np.float64) + + # Floor + K_daily = np.maximum(K_daily, 1.0) + med_K = np.median(K_daily[np.isfinite(K_daily)]) + print(f" K (competitor TVL): median=${med_K:,.0f}," + f" range=[${K_daily.min():,.0f}, ${K_daily.max():,.0f}]") + + # Expand to minute resolution + n_minutes = n_days * 1440 + noise_base = np.repeat(noise_base_daily, 1440) + competitor_tvl_array = np.repeat(K_daily, 1440) + + return { + "noise_base": noise_base, + "competitor_tvl": competitor_tvl_array, + "dates": date_range, + "pool_index": pool_idx, + "n_days": n_days, + "n_minutes": n_minutes, + } diff --git a/quantammsim/calibration/per_pool_fit.py b/quantammsim/calibration/per_pool_fit.py new file mode 100644 index 0000000..be724a9 --- /dev/null +++ b/quantammsim/calibration/per_pool_fit.py @@ -0,0 +1,207 @@ +"""Per-pool fitting via L-BFGS-B for the direct calibration pipeline. + +Fits (log_cadence, log_gas, noise_coeffs) per pool by minimizing +the log-space L2 loss using scipy.optimize.minimize with JAX gradients. + +Supports fixed-gas mode where gas is set to the known chain-level cost, +leaving only (log_cadence, noise_coeffs) to be optimized. +""" + +from typing import Dict, Optional + +import jax +import jax.numpy as jnp +import numpy as np +import scipy.optimize + +from quantammsim.calibration.grid_interpolation import PoolCoeffsDaily +from quantammsim.calibration.loss import ( + CHAIN_GAS_USD, + K_OBS, + pack_params, + pool_loss, + pool_loss_fixed_gas, +) +from quantammsim.calibration.pool_data import build_x_obs + + +def make_initial_guess(x_obs: np.ndarray, y_obs: np.ndarray) -> np.ndarray: + """Initial params: cadence=12min, gas=$1, noise_coeffs from OLS. + + OLS: noise_coeffs = lstsq(x_obs, y_obs) — assumes all volume is noise. + This overestimates noise but gives a reasonable starting point. + """ + k_obs = x_obs.shape[1] + noise_coeffs, _, _, _ = np.linalg.lstsq(x_obs, y_obs, rcond=None) + init = np.zeros(2 + k_obs) + init[0] = np.log(12.0) # log_cadence + init[1] = np.log(1.0) # log_gas (= 0.0) + init[2:] = noise_coeffs + return init + + +def make_initial_guess_fixed_gas(x_obs: np.ndarray, y_obs: np.ndarray) -> np.ndarray: + """Initial params for fixed-gas mode: cadence=12min, noise_coeffs from OLS.""" + k_obs = x_obs.shape[1] + noise_coeffs, _, _, _ = np.linalg.lstsq(x_obs, y_obs, rcond=None) + init = np.zeros(1 + k_obs) + init[0] = np.log(12.0) # log_cadence + init[1:] = noise_coeffs + return init + + +def fit_single_pool( + coeffs: PoolCoeffsDaily, + x_obs: np.ndarray, + y_obs: np.ndarray, + day_indices: np.ndarray, + init: Optional[np.ndarray] = None, + bounds: Optional[dict] = None, + fixed_gas_usd: Optional[float] = None, +) -> dict: + """Fit one pool via L-BFGS-B. + + If fixed_gas_usd is given, gas is held constant at that value and only + (log_cadence, noise_coeffs) are optimized. Otherwise fits all three. + + Returns dict with fitted params, loss, and convergence status. + """ + # Convert to JAX arrays + x_obs_j = jnp.array(x_obs) + y_obs_j = jnp.array(y_obs) + day_idx_j = jnp.array(day_indices) + + if bounds is None: + bounds = {} + log_cad_bounds = bounds.get("log_cadence", (np.log(1.0), np.log(60.0))) + noise_bounds = bounds.get("noise_coeffs", (-20.0, 20.0)) + + if fixed_gas_usd is not None: + # Fixed-gas mode + fixed_log_gas = jnp.float64(np.log(max(fixed_gas_usd, 1e-6))) + + if init is None: + init = make_initial_guess_fixed_gas(x_obs, y_obs) + + k_obs = x_obs.shape[1] + scipy_bounds = [log_cad_bounds] + [(noise_bounds[0], noise_bounds[1])] * k_obs + + @jax.jit + def loss_and_grad(params_flat): + loss = pool_loss_fixed_gas( + params_flat, fixed_log_gas, coeffs, x_obs_j, y_obs_j, day_idx_j) + grad = jax.grad(pool_loss_fixed_gas, argnums=0)( + params_flat, fixed_log_gas, coeffs, x_obs_j, y_obs_j, day_idx_j) + return loss, grad + + def scipy_wrapper(params_np): + params_j = jnp.array(params_np) + loss, grad = loss_and_grad(params_j) + return float(loss), np.array(grad, dtype=np.float64) + + result = scipy.optimize.minimize( + scipy_wrapper, init, method="L-BFGS-B", jac=True, + bounds=scipy_bounds, + options={"maxiter": 500, "ftol": 1e-10, "gtol": 1e-8}, + ) + + log_cadence = float(result.x[0]) + noise_coeffs = np.array(result.x[1:]) + log_gas = float(fixed_log_gas) + + return { + "log_cadence": log_cadence, + "log_gas": log_gas, + "noise_coeffs": noise_coeffs, + "loss": float(result.fun), + "converged": result.success, + "cadence_minutes": float(np.exp(log_cadence)), + "gas_usd": fixed_gas_usd, + "gas_fixed": True, + } + + else: + # Free-gas mode (original) + if init is None: + init = make_initial_guess(x_obs, y_obs) + + k_obs = x_obs.shape[1] + log_gas_bounds = bounds.get("log_gas", (np.log(0.001), np.log(50.0))) + scipy_bounds = [ + log_cad_bounds, log_gas_bounds, + ] + [(noise_bounds[0], noise_bounds[1])] * k_obs + + @jax.jit + def loss_and_grad(params_flat): + loss = pool_loss(params_flat, coeffs, x_obs_j, y_obs_j, day_idx_j) + grad = jax.grad(pool_loss, argnums=0)( + params_flat, coeffs, x_obs_j, y_obs_j, day_idx_j) + return loss, grad + + def scipy_wrapper(params_np): + params_j = jnp.array(params_np) + loss, grad = loss_and_grad(params_j) + return float(loss), np.array(grad, dtype=np.float64) + + result = scipy.optimize.minimize( + scipy_wrapper, init, method="L-BFGS-B", jac=True, + bounds=scipy_bounds, + options={"maxiter": 500, "ftol": 1e-10, "gtol": 1e-8}, + ) + + log_cadence = float(result.x[0]) + log_gas = float(result.x[1]) + noise_coeffs = np.array(result.x[2:]) + + return { + "log_cadence": log_cadence, + "log_gas": log_gas, + "noise_coeffs": noise_coeffs, + "loss": float(result.fun), + "converged": result.success, + "cadence_minutes": float(np.exp(log_cadence)), + "gas_usd": float(np.exp(log_gas)), + "gas_fixed": False, + } + + +def fit_all_pools( + matched: Dict[str, dict], + n_workers: int = 1, + fix_gas_to_chain: bool = False, + reduced: bool = False, +) -> Dict[str, dict]: + """Fit all matched pools. Returns prefix -> fit_result with metadata. + + If fix_gas_to_chain is True, gas is fixed to the known chain-level cost + from CHAIN_GAS_USD, and only (log_cadence, noise_coeffs) are optimized. + + If reduced is True, uses the 4-covariate x_obs (intercept, log_tvl_lag1, + dow_sin, dow_cos) instead of the full 8-covariate set. + """ + results = {} + + for prefix, entry in matched.items(): + panel = entry["panel"] + coeffs = entry["coeffs"] + day_indices = entry["day_indices"] + + x_obs = build_x_obs(panel, reduced=reduced) + y_obs = panel["log_volume"].values.astype(float) + + fixed_gas = None + if fix_gas_to_chain: + chain = entry["chain"] + fixed_gas = CHAIN_GAS_USD.get(chain, 1.0) + + result = fit_single_pool( + coeffs, x_obs, y_obs, day_indices, fixed_gas_usd=fixed_gas) + + # Add metadata + result["chain"] = entry["chain"] + result["fee"] = entry["fee"] + result["tokens"] = entry["tokens"] + + results[prefix] = result + + return results diff --git a/quantammsim/calibration/pool_data.py b/quantammsim/calibration/pool_data.py new file mode 100644 index 0000000..3b05bbb --- /dev/null +++ b/quantammsim/calibration/pool_data.py @@ -0,0 +1,753 @@ +"""Data assembly for the direct calibration pipeline. + +Matches precomputed per-day arb grids to panel observations and builds +model-ready arrays for the loss function. +""" + +import json +import os +from typing import Dict, List, Optional, Tuple + +import numpy as np +import pandas as pd + +from quantammsim.calibration.grid_interpolation import ( + PoolCoeffsDaily, + load_daily_grid, + precompute_pool_coeffs_daily, +) + +K_OBS = 8 # observation-level covariates +K_OBS_REDUCED = 4 # [intercept, log_tvl_lag1, dow_sin, dow_cos] +K_OBS_CROSS = 7 # [intercept, log_tvl_lag1, dow_sin, dow_cos, + # cross_vol_token_a_{t-1}, cross_vol_token_b_{t-1}, + # cross_vol_chain_{t-1}] + +# Default path for cached token market caps +_MCAP_PATH = os.path.join( + os.path.dirname(os.path.dirname(os.path.dirname(__file__))), + "local_data", "noise_calibration", "token_mcaps.json", +) + +# Default path for Binance minute parquets +_BINANCE_DATA_DIR = os.path.join( + os.path.dirname(os.path.dirname(__file__)), "data", +) + +# Asset type classification (fallback if not in mcap JSON) +_STABLECOINS = { + "USDC", "USDT", "DAI", "WXDAI", "xDAI", "GHO", "LUSD", "crvUSD", + "FRAX", "sDAI", "scUSD", "DOLA", + "waBasUSDC", "waEthUSDC", +} +_NATIVE_LST = { + "WETH", "ETH", "wstETH", "stETH", "rETH", "cbETH", + "WBTC", "BTC", "cbBTC", + "WMATIC", "MATIC", "POL", "wPOL", + "WAVAX", "AVAX", + "GNO", "S", "wS", "stS", + "JitoSOL", + "waEthLidoWETH", "waEthLidowstETH", + "waBasWETH", "waGnoGNO", "waGnowstETH", +} + +# Balancer token → Binance parquet symbol mapping. +# Matches build_pool_grids.py TOKEN_MAP. +TOKEN_MAP = { + "WBTC": "BTC", "WETH": "ETH", "cbBTC": "BTC", + "wstETH": "ETH", "stETH": "ETH", "rETH": "ETH", "cbETH": "ETH", + "waEthLidoWETH": "ETH", "waEthLidowstETH": "ETH", + "waBasWETH": "ETH", "waGnowstETH": "ETH", + "waGnoGNO": "GNO", "osGNO": "GNO", + "wS": "S", "stS": "S", + "JitoSOL": "SOL", + "wPOL": "POL", "WMATIC": "POL", "MATIC": "POL", + "USDC.e": "USDC", "USDbC": "USDC", "waBasUSDC": "USDC", + "DAI": "USDC", "WXDAI": "USDC", "sDAI": "USDC", + "USDT": "USDC", "DOLA": "USDC", "scUSD": "USDC", +} + + +def _load_token_mcaps(path: str = None) -> dict: + """Load cached token market caps. Returns {} if file missing.""" + path = path or _MCAP_PATH + if os.path.exists(path): + with open(path) as f: + return json.load(f) + return {} + + +def _get_asset_type(symbol: str, mcaps: dict) -> int: + """Return asset type: 0=stable, 1=native/LST, 2=volatile.""" + if symbol in mcaps and "asset_type" in mcaps[symbol]: + t = mcaps[symbol]["asset_type"] + return {"stable": 0, "native_lst": 1, "volatile": 2}.get(t, 2) + if symbol in _STABLECOINS: + return 0 + if symbol in _NATIVE_LST: + return 1 + return 2 + + +def _parse_tokens(tokens_str: str) -> List[str]: + """Parse comma-separated token string into list.""" + if isinstance(tokens_str, (list, tuple)): + return list(tokens_str) + return [t.strip() for t in tokens_str.split(",")] + + +def _resolve_binance_symbol(token: str) -> str: + """Map Balancer token name to Binance parquet symbol.""" + return TOKEN_MAP.get(token, token) + + +def _load_binance_minute(symbol: str, data_dir: str = None) -> Optional[pd.DataFrame]: + """Load Binance minute close prices. Returns DataFrame with unix index.""" + if data_dir is None: + data_dir = _BINANCE_DATA_DIR + path = os.path.join(data_dir, f"{symbol}_USD.parquet") + if not os.path.exists(path): + return None + df = pd.read_parquet(path, columns=["unix", "close"]) + if df.index.name != "unix": + df = df.set_index("unix") + return df + + +def compute_binance_pair_volatility( + token_a: str, token_b: str, data_dir: str = None, +) -> Optional[pd.Series]: + """Compute daily annualized realized volatility from Binance minute data. + + Resamples minute data to hourly, computes hourly log returns of the pair + ratio, then daily std × sqrt(24 × 365). Matches the Balancer hourly + pipeline's annualization convention. + + Args: + token_a, token_b: Balancer token symbols (e.g. "WETH", "USDC") + data_dir: directory containing {SYMBOL}_USD.parquet files + + Returns: + pd.Series with datetime.date index → annualized volatility, + or None if both tokens are stablecoins / same underlying / missing data. + """ + sym_a = _resolve_binance_symbol(token_a) + sym_b = _resolve_binance_symbol(token_b) + + is_stable_a = token_a in _STABLECOINS or sym_a == "USDC" + is_stable_b = token_b in _STABLECOINS or sym_b == "USDC" + + if is_stable_a and is_stable_b: + return None # caller should use constant 0.01 + + if sym_a == sym_b: + return None # same underlying (e.g. wstETH/WETH) + + # Load minute data and compute pair ratio + if is_stable_b: + df = _load_binance_minute(sym_a, data_dir) + if df is None: + return None + ratio = df["close"] + elif is_stable_a: + df = _load_binance_minute(sym_b, data_dir) + if df is None: + return None + ratio = 1.0 / df["close"] + else: + df_a = _load_binance_minute(sym_a, data_dir) + df_b = _load_binance_minute(sym_b, data_dir) + if df_a is None or df_b is None: + return None + merged = df_a.join(df_b, lsuffix="_a", rsuffix="_b", how="inner") + ratio = merged["close_a"] / merged["close_b"] + + # Resample to hourly (last close per hour) + ratio_df = pd.DataFrame({"ratio": ratio}) + ratio_df.index = pd.to_datetime(ratio_df.index, unit="ms", utc=True) + hourly = ratio_df.resample("1h").last().dropna() + + # Hourly log returns + hourly["log_return"] = np.log(hourly["ratio"] / hourly["ratio"].shift(1)) + hourly = hourly.dropna() + + # Daily std → annualized + hourly["date"] = hourly.index.date + daily_vol = hourly.groupby("date")["log_return"].std() + annualized = daily_vol * np.sqrt(24 * 365) + + # Clean + annualized = annualized.replace([np.inf, -np.inf], np.nan).dropna() + annualized = annualized[annualized > 0] + + return annualized + + +def replace_panel_volatility_with_binance( + panel: pd.DataFrame, data_dir: str = None, +) -> pd.DataFrame: + """Replace panel 'volatility' column with Binance-derived daily values. + + For each pool, computes daily realized volatility from Binance minute data. + Pools without Binance data keep their existing (possibly fallback) values. + Stablecoin-stablecoin and same-underlying pairs get vol=0.01. + + Returns a copy of the panel with updated volatility. + """ + panel = panel.copy() + panel["date"] = pd.to_datetime(panel["date"]) + + # Cache: (sym_a, sym_b) → vol_series to avoid reloading + _vol_cache: Dict[tuple, Optional[pd.Series]] = {} + + n_replaced = 0 + n_pools = 0 + + for pool_id, grp in panel.groupby("pool_id"): + tokens_str = grp.iloc[0]["tokens"] + toks = _parse_tokens(tokens_str) + if len(toks) < 2: + continue + + sym_a = _resolve_binance_symbol(toks[0]) + sym_b = _resolve_binance_symbol(toks[1]) + cache_key = (min(sym_a, sym_b), max(sym_a, sym_b)) + + if cache_key not in _vol_cache: + _vol_cache[cache_key] = compute_binance_pair_volatility( + toks[0], toks[1], data_dir) + + vol_series = _vol_cache[cache_key] + + if vol_series is None: + # Stablecoins or same underlying → low constant vol + is_stable_a = toks[0] in _STABLECOINS or sym_a == "USDC" + is_stable_b = toks[1] in _STABLECOINS or sym_b == "USDC" + if (is_stable_a and is_stable_b) or sym_a == sym_b: + panel.loc[grp.index, "volatility"] = 0.01 + n_pools += 1 + continue + + # Vectorized date matching + panel_dates = pd.to_datetime(grp["date"]).dt.date + vol_dict = vol_series.to_dict() + new_vol = panel_dates.map(vol_dict) + has_vol = new_vol.notna() + if has_vol.any(): + panel.loc[grp.index[has_vol.values], "volatility"] = ( + new_vol[has_vol].values.astype(float)) + n_replaced += has_vol.sum() + n_pools += 1 + + print(f" Binance volatility: {n_pools} pools, {n_replaced} obs replaced") + return panel + + +def match_grids_to_panel( + grid_dir: str, panel: pd.DataFrame, pools_path: str = None, +) -> Dict[str, dict]: + """Match grid parquets to panel rows by pool_id prefix. + + For each _daily.parquet in grid_dir, find the panel pool whose + pool_id starts with the same 16-char prefix. Build PoolCoeffsDaily + and compute day_indices mapping panel dates to grid date indices. + + Args: + grid_dir: directory containing {prefix}_daily.parquet files + panel: panel DataFrame with pool observations + pools_path: path to pools.parquet for weight metadata. + Defaults to local_data/noise_calibration/pools.parquet. + + Returns dict: prefix -> { + 'panel': DataFrame (obs for this pool), + 'coeffs': PoolCoeffsDaily (per-day), + 'day_indices': np.ndarray (panel date -> grid day index), + 'pool_id': full pool_id from panel, + 'chain': str, 'fee': float, 'tokens': str, + 'weights': list of float (pool weights, e.g. [0.5, 0.5]), + } + """ + # Discover grid files + grid_prefixes = [] + for f in sorted(os.listdir(grid_dir)): + if f.endswith("_daily.parquet"): + prefix = f.replace("_daily.parquet", "") + grid_prefixes.append(prefix) + + if not grid_prefixes: + return {} + + # Load pool metadata for weights + if pools_path is None: + pools_path = os.path.join( + os.path.dirname(os.path.dirname(os.path.dirname(__file__))), + "local_data", "noise_calibration", "pools.parquet", + ) + pools_meta = {} + if os.path.exists(pools_path): + pools_df = pd.read_parquet(pools_path) + pools_df["_prefix"] = pools_df["pool_id"].str[:16] + for _, row in pools_df.iterrows(): + w = row.get("weights") + if w is not None: + try: + weights = [float(x) for x in w] + except (TypeError, ValueError): + weights = [0.5, 0.5] + else: + weights = [0.5, 0.5] + pools_meta[row["_prefix"]] = {"weights": weights} + + # Ensure date column + panel = panel.copy() + if "date" in panel.columns: + panel["date"] = pd.to_datetime(panel["date"]) + + # Build prefix -> panel rows mapping + panel["_prefix"] = panel["pool_id"].str[:16] + + matched = {} + for prefix in grid_prefixes: + pool_rows = panel[panel["_prefix"] == prefix] + if len(pool_rows) == 0: + continue + + # Load and precompute grid + grid_df = load_daily_grid(prefix, grid_dir) + coeffs = precompute_pool_coeffs_daily(grid_df) + + # Build date alignment: panel date ordinals -> grid day indices + grid_ordinals = np.array(coeffs.dates) + grid_ord_to_idx = {int(o): i for i, o in enumerate(grid_ordinals)} + + panel_dates = pd.to_datetime(pool_rows["date"]) + panel_ordinals = np.array([d.toordinal() for d in panel_dates]) + + # Filter to dates present in both panel and grid + valid_mask = np.array([int(o) in grid_ord_to_idx for o in panel_ordinals]) + pool_rows = pool_rows[valid_mask].copy() + panel_ordinals = panel_ordinals[valid_mask] + + if len(pool_rows) == 0: + continue + + day_indices = np.array([grid_ord_to_idx[int(o)] for o in panel_ordinals]) + + row0 = pool_rows.iloc[0] + weights = pools_meta.get(prefix, {}).get("weights", [0.5, 0.5]) + + matched[prefix] = { + "panel": pool_rows.reset_index(drop=True), + "coeffs": coeffs, + "day_indices": day_indices, + "pool_id": row0["pool_id"], + "chain": row0["chain"], + "fee": float(np.exp(row0["log_fee"])) if "swap_fee" not in pool_rows.columns + else float(row0.get("swap_fee", np.exp(row0["log_fee"]))), + "tokens": row0["tokens"], + "weights": weights, + } + + return matched + + +# Token canonicalization — map wrapped/LST variants to base tokens +_CANON_MAP = { + "WETH": "ETH", "waBasWETH": "ETH", "waEthLidoWETH": "ETH", + "waEthLidowstETH": "wstETH", "waGnowstETH": "wstETH", + "waBasUSDC": "USDC", "scUSD": "USDC", "USDC.e": "USDC", + "USDbC": "USDC", "waEthUSDC": "USDC", + "sDAI": "DAI", "WXDAI": "DAI", + "WBTC": "BTC", "cbBTC": "BTC", + "stS": "S", "wS": "S", + "waGnoGNO": "GNO", "osGNO": "GNO", +} + + +def _canonicalize_token(symbol: str) -> str: + """Map wrapped/derivative token to its canonical base symbol.""" + return _CANON_MAP.get(symbol, symbol) + + +# Token classification for token-factored model +_ETH_DERIVATIVES = { + "WETH", "ETH", "wstETH", "stETH", "rETH", "cbETH", + "waEthLidoWETH", "waEthLidowstETH", "waBasWETH", "waGnowstETH", +} +_L1_NATIVE = { + "WETH", "ETH", "WMATIC", "MATIC", "POL", "wPOL", + "WAVAX", "AVAX", "GNO", "S", "wS", "stS", +} + +D_TOKEN = 5 # [intercept, log_mcap, is_stable, is_eth_derivative, is_L1_native] + + +def _classify_token(symbol: str, mcaps: dict) -> dict: + """Classify a token into binary feature flags.""" + return { + "is_stable": 1.0 if symbol in _STABLECOINS else 0.0, + "is_eth_derivative": 1.0 if symbol in _ETH_DERIVATIVES else 0.0, + "is_L1_native": 1.0 if symbol in _L1_NATIVE else 0.0, + "log_mcap": np.log(max(mcaps.get(symbol, {}).get("mcap_usd", 1e6), 1.0)), + } + + +def encode_tokens( + matched: Dict[str, dict], + mcap_path: str = None, + canonicalize: bool = True, +) -> dict: + """Build token index, per-pool token assignments, and token covariate matrix. + + Iterates over pools in sorted key order (same ordering as build_pool_attributes). + + When canonicalize=True (default), wrappd/derivative tokens are mapped to + their canonical base symbol via _CANON_MAP before building the index. + Raw symbols are still used for market cap lookup. + + Returns dict with: + token_index: dict[str, int] — symbol -> integer index (sorted alphabetically) + token_a_idx: np.ndarray (n_pools,) — index of token A for each pool + token_b_idx: np.ndarray (n_pools,) — index of token B for each pool + x_token: np.ndarray (n_tokens, D_TOKEN) — token covariate matrix + chain_idx: np.ndarray (n_pools,) — chain integer index per pool + chain_index: dict[str, int] — chain name -> integer index (sorted) + log_fees: np.ndarray (n_pools,) — log(fee) per pool + n_tokens: int + n_chains: int + """ + mcaps = _load_token_mcaps(mcap_path) + pool_ids = sorted(matched.keys()) + n_pools = len(pool_ids) + + # Collect all tokens and chains; store per-pool canonical pairs + all_tokens = set() + all_chains = set() + pool_canon_toks = [] # (canon_a, canon_b) per pool in sorted order + for pid in pool_ids: + entry = matched[pid] + toks = _parse_tokens(entry["tokens"]) + raw_a, raw_b = toks[0], toks[1] + canon_a = _canonicalize_token(raw_a) if canonicalize else raw_a + canon_b = _canonicalize_token(raw_b) if canonicalize else raw_b + all_tokens.update([canon_a, canon_b]) + all_chains.add(entry["chain"]) + pool_canon_toks.append((canon_a, canon_b)) + + # Build sorted indices + token_list = sorted(all_tokens) + token_index = {t: i for i, t in enumerate(token_list)} + n_tokens = len(token_list) + + chain_list = sorted(all_chains) + chain_index = {c: i for i, c in enumerate(chain_list)} + n_chains = len(chain_list) + + # Build per-pool arrays + token_a_idx = np.zeros(n_pools, dtype=np.int32) + token_b_idx = np.zeros(n_pools, dtype=np.int32) + chain_idx = np.zeros(n_pools, dtype=np.int32) + log_fees = np.zeros(n_pools, dtype=np.float64) + + for i, pid in enumerate(pool_ids): + entry = matched[pid] + canon_a, canon_b = pool_canon_toks[i] + token_a_idx[i] = token_index[canon_a] + token_b_idx[i] = token_index[canon_b] + chain_idx[i] = chain_index[entry["chain"]] + log_fees[i] = np.log(entry["fee"]) + + # Build token covariate matrix: (n_tokens, D_TOKEN) + # Columns: [intercept, log_mcap, is_stable, is_eth_derivative, is_L1_native] + x_token = np.zeros((n_tokens, D_TOKEN), dtype=np.float64) + for t, idx in token_index.items(): + cls = _classify_token(t, mcaps) + x_token[idx, 0] = 1.0 # intercept + x_token[idx, 1] = cls["log_mcap"] + x_token[idx, 2] = cls["is_stable"] + x_token[idx, 3] = cls["is_eth_derivative"] + x_token[idx, 4] = cls["is_L1_native"] + + return { + "token_index": token_index, + "token_a_idx": token_a_idx, + "token_b_idx": token_b_idx, + "x_token": x_token, + "chain_idx": chain_idx, + "chain_index": chain_index, + "log_fees": log_fees, + "n_tokens": n_tokens, + "n_chains": n_chains, + } + + +def build_x_obs(panel_rows: pd.DataFrame, reduced: bool = False) -> np.ndarray: + """Build observation covariate matrix from panel rows. + + Full (reduced=False): (n_obs, 8) + [1, log_tvl_lag1, log_sigma, tvl*sigma, tvl*fee, sigma*fee, dow_sin, dow_cos] + + Reduced (reduced=True): (n_obs, 4) + [1, log_tvl_lag1, dow_sin, dow_cos] + Removes sigma- and fee-dependent terms so the arb channel is the only + path for volatility-driven volume variation. + + Where: + log_sigma = log(max(volatility, 1e-6)) + tvl = log_tvl_lag1 + fee = log_fee + dow_sin = sin(2*pi*weekday/7), dow_cos = cos(2*pi*weekday/7) + weekday: Monday=0, ..., Sunday=6 + """ + n = len(panel_rows) + weekdays = pd.to_datetime(panel_rows["date"]).dt.weekday.values.astype(float) + + if reduced: + x = np.zeros((n, K_OBS_REDUCED)) + x[:, 0] = 1.0 + x[:, 1] = panel_rows["log_tvl_lag1"].values.astype(float) + x[:, 2] = np.sin(2 * np.pi * weekdays / 7) + x[:, 3] = np.cos(2 * np.pi * weekdays / 7) + return x + + x = np.zeros((n, K_OBS)) + + tvl = panel_rows["log_tvl_lag1"].values.astype(float) + sigma = np.log(np.maximum(panel_rows["volatility"].values.astype(float), 1e-6)) + fee = panel_rows["log_fee"].values.astype(float) + + x[:, 0] = 1.0 # intercept + x[:, 1] = tvl # log_tvl_lag1 + x[:, 2] = sigma # log_sigma + x[:, 3] = tvl * sigma # tvl × sigma + x[:, 4] = tvl * fee # tvl × fee + x[:, 5] = sigma * fee # sigma × fee + x[:, 6] = np.sin(2 * np.pi * weekdays / 7) # dow_sin + x[:, 7] = np.cos(2 * np.pi * weekdays / 7) # dow_cos + + return x + + +def build_cross_pool_x_obs( + panel_rows: pd.DataFrame, + matched: Dict[str, dict], + pool_id: str, + exclude_pool: Optional[str] = None, + canonicalize: bool = True, +) -> np.ndarray: + """Build x_obs with cross-pool lagged volume features. + + Columns 0-3: same as build_x_obs(reduced=True) + Column 4: mean log_volume at t-1 across pools sharing token A (excl self) + Column 5: mean log_volume at t-1 across pools sharing token B (excl self) + Column 6: mean log_volume at t-1 across pools on same chain (excl self) + + The first observation (day 0) is dropped because there is no lag available. + + Args: + panel_rows: DataFrame for this pool + matched: full matched dict (all pools) + pool_id: this pool's key in matched (prefix) + exclude_pool: optional pool to exclude from peer averages (for LOO) + canonicalize: if True, canonicalize tokens before peer matching + + Returns: + (n_obs - 1, K_OBS_CROSS) array + """ + # Get this pool's tokens and chain + entry = matched[pool_id] + toks = _parse_tokens(entry["tokens"]) + tok_a_raw, tok_b_raw = toks[0], toks[1] + tok_a = _canonicalize_token(tok_a_raw) if canonicalize else tok_a_raw + tok_b = _canonicalize_token(tok_b_raw) if canonicalize else tok_b_raw + this_chain = entry["chain"] + + # Build peer sets: token→set of pool_ids, chain→set of pool_ids + token_peers = {} # canonical_token → set of (prefix, panel_df) + chain_peers = {} # chain → set of (prefix, panel_df) + all_pool_ids = sorted(matched.keys()) + + for pid in all_pool_ids: + if pid == pool_id: + continue # always exclude self + if pid == exclude_pool: + continue + peer_entry = matched[pid] + peer_toks = _parse_tokens(peer_entry["tokens"]) + peer_canonical = set() + for t in peer_toks[:2]: + ct = _canonicalize_token(t) if canonicalize else t + peer_canonical.add(ct) + + for ct in peer_canonical: + if ct not in token_peers: + token_peers[ct] = [] + token_peers[ct].append(pid) + + peer_chain = peer_entry["chain"] + if peer_chain not in chain_peers: + chain_peers[peer_chain] = [] + chain_peers[peer_chain].append(pid) + + # Build (pool_id, date_ordinal) → log_volume lookup from all pools + vol_lookup = {} # (pid, date_ordinal) → log_volume + for pid in all_pool_ids: + if pid == pool_id or pid == exclude_pool: + continue + peer_panel = matched[pid]["panel"] + peer_dates = pd.to_datetime(peer_panel["date"]) + peer_ords = np.array([d.toordinal() for d in peer_dates]) + peer_vols = peer_panel["log_volume"].values.astype(float) + for ord_val, vol_val in zip(peer_ords, peer_vols): + vol_lookup[(pid, int(ord_val))] = vol_val + + # Compute global lagged mean for fallback + all_vols = list(vol_lookup.values()) + global_mean_vol = float(np.mean(all_vols)) if all_vols else 0.0 + + # Get this pool's dates + dates = pd.to_datetime(panel_rows["date"]) + date_ords = np.array([d.toordinal() for d in dates]) + n_obs = len(panel_rows) + + def _peer_mean_at_lag(peer_pids, date_ord_prev): + """Mean log_volume of peer pools at date_ord_prev.""" + vals = [] + for pid in peer_pids: + key = (pid, date_ord_prev) + if key in vol_lookup: + vals.append(vol_lookup[key]) + if vals: + return float(np.mean(vals)) + return np.nan + + # Build cross-pool features for each obs (starting from day 1) + cross_vol_a = np.full(n_obs, np.nan) + cross_vol_b = np.full(n_obs, np.nan) + cross_vol_chain = np.full(n_obs, np.nan) + + tok_a_peers = token_peers.get(tok_a, []) + tok_b_peers = token_peers.get(tok_b, []) + chain_peer_list = chain_peers.get(this_chain, []) + + for i in range(1, n_obs): + prev_ord = int(date_ords[i - 1]) + + if tok_a_peers: + cross_vol_a[i] = _peer_mean_at_lag(tok_a_peers, prev_ord) + if tok_b_peers: + cross_vol_b[i] = _peer_mean_at_lag(tok_b_peers, prev_ord) + if chain_peer_list: + cross_vol_chain[i] = _peer_mean_at_lag(chain_peer_list, prev_ord) + + # Drop first day, fill NaN with global mean + cross_vol_a = cross_vol_a[1:] + cross_vol_b = cross_vol_b[1:] + cross_vol_chain = cross_vol_chain[1:] + + cross_vol_a = np.where(np.isnan(cross_vol_a), global_mean_vol, cross_vol_a) + cross_vol_b = np.where(np.isnan(cross_vol_b), global_mean_vol, cross_vol_b) + cross_vol_chain = np.where(np.isnan(cross_vol_chain), global_mean_vol, cross_vol_chain) + + # Build base x_obs (reduced) and drop first row + x_base = build_x_obs(panel_rows, reduced=True) + x_base = x_base[1:] # drop first day + + # Assemble + x = np.zeros((n_obs - 1, K_OBS_CROSS)) + x[:, :4] = x_base + x[:, 4] = cross_vol_a + x[:, 5] = cross_vol_b + x[:, 6] = cross_vol_chain + + return x + + +def build_pool_attributes( + matched: Dict[str, dict], + mcap_path: str = None, +) -> Tuple[np.ndarray, List[str], List[str]]: + """Build (n_pools, K_attr) pool attribute matrix. + + Columns: + chain_dummies..., log_fee, mean_log_tvl, + log_mcap_product, has_stable, same_asset_type, weight_imbalance + + No intercept column — bias terms are handled by the model internally. + Chain dummies: one-hot with the first chain (alphabetically) as reference. + Market caps loaded from cached JSON (run scripts/fetch_token_mcaps.py). + + Returns: (X_attr, attr_names, pool_ids) + """ + mcaps = _load_token_mcaps(mcap_path) + + pool_ids = sorted(matched.keys()) + n_pools = len(pool_ids) + + # Collect per-pool attributes + chains = [] + log_fees = [] + mean_tvls = [] + log_mcap_products = [] + has_stables = [] + same_asset_types = [] + weight_imbalances = [] + + for pid in pool_ids: + entry = matched[pid] + chains.append(entry["chain"]) + log_fee = entry["panel"]["log_fee"].values[0] + log_fees.append(float(log_fee)) + mean_tvls.append(float(entry["panel"]["log_tvl_lag1"].mean())) + + # Token-level features + tokens = _parse_tokens(entry["tokens"]) + tok_a = tokens[0] if len(tokens) > 0 else "UNKNOWN" + tok_b = tokens[1] if len(tokens) > 1 else "UNKNOWN" + + # Market cap product + mcap_a = mcaps.get(tok_a, {}).get("mcap_usd", 1e6) # $1M fallback + mcap_b = mcaps.get(tok_b, {}).get("mcap_usd", 1e6) + log_mcap_products.append(np.log(max(mcap_a, 1.0) * max(mcap_b, 1.0))) + + # Asset type: 0=stable, 1=native/LST, 2=volatile + type_a = _get_asset_type(tok_a, mcaps) + type_b = _get_asset_type(tok_b, mcaps) + has_stables.append(1.0 if (type_a == 0 or type_b == 0) else 0.0) + same_asset_types.append(1.0 if type_a == type_b else 0.0) + + # Weight imbalance: 0 for 50/50, 0.3 for 80/20 + weights = entry.get("weights", [0.5, 0.5]) + if len(weights) >= 2: + weight_imbalances.append(abs(weights[0] - weights[1])) + else: + weight_imbalances.append(0.0) + + # Chain dummies (first alphabetically is reference) + unique_chains = sorted(set(chains)) + chain_dummies = unique_chains[1:] # drop reference + + attr_names = ( + [f"chain_{c}" for c in chain_dummies] + + [ + "log_fee", "mean_log_tvl", "log_mcap_product", + "has_stable", "same_asset_type", "weight_imbalance", + ] + ) + k_attr = len(attr_names) + + X = np.zeros((n_pools, k_attr)) + for i, pid in enumerate(pool_ids): + chain = chains[i] + for j, cd in enumerate(chain_dummies): + if chain == cd: + X[i, j] = 1.0 + base = len(chain_dummies) + X[i, base] = log_fees[i] + X[i, base + 1] = mean_tvls[i] + X[i, base + 2] = log_mcap_products[i] + X[i, base + 3] = has_stables[i] + X[i, base + 4] = same_asset_types[i] + X[i, base + 5] = weight_imbalances[i] + + return X, attr_names, pool_ids diff --git a/quantammsim/core_simulator/__init__.py b/quantammsim/core_simulator/__init__.py index 10c118a..010490e 100644 --- a/quantammsim/core_simulator/__init__.py +++ b/quantammsim/core_simulator/__init__.py @@ -17,6 +17,7 @@ import jax.numpy as jnp # noqa: F401 from jax import config config.update("jax_enable_x64", True) + config.update("jax_compilation_cache_dir", "/tmp/jax_cache") except ImportError as e: raise ImportError( "JAX is required for core simulator. Please install jax and jaxlib." diff --git a/quantammsim/core_simulator/dynamic_inputs.py b/quantammsim/core_simulator/dynamic_inputs.py new file mode 100644 index 0000000..367363e --- /dev/null +++ b/quantammsim/core_simulator/dynamic_inputs.py @@ -0,0 +1,195 @@ +from dataclasses import dataclass +from typing import Any, NamedTuple, Optional + +import jax.numpy as jnp + + +@dataclass(frozen=True) +class DynamicInputFrames: + """Outer-layer container for optional pandas-backed dynamic inputs.""" + + trades: Optional[Any] = None + fees: Optional[Any] = None + gas_cost: Optional[Any] = None + arb_fees: Optional[Any] = None + lp_supply: Optional[Any] = None + reclamm_price_ratio_updates: Optional[Any] = None + + +class DynamicInputArrays(NamedTuple): + """JAX pytree for dynamic simulation inputs with optional trade data.""" + + trades: Optional[jnp.ndarray] + fees: jnp.ndarray + gas_cost: jnp.ndarray + arb_fees: jnp.ndarray + lp_supply: jnp.ndarray + reclamm_price_ratio_updates: jnp.ndarray + + +def default_dynamic_input_flags() -> dict: + """Static dispatch flags for forward-pass path selection.""" + return { + "use_dynamic_inputs": False, + "has_trades": False, + "has_dynamic_fees": False, + "has_dynamic_gas_cost": False, + "has_dynamic_arb_fees": False, + "has_lp_supply": False, + "has_reclamm_price_ratio_updates": False, + } + + +def dynamic_input_flags_from_frames(dynamic_input_frames: Optional[DynamicInputFrames]) -> dict: + """Build stable dispatch flags from the outer-layer frame container.""" + if dynamic_input_frames is None: + return default_dynamic_input_flags() + + flags = { + "use_dynamic_inputs": False, + "has_trades": dynamic_input_frames.trades is not None, + "has_dynamic_fees": dynamic_input_frames.fees is not None, + "has_dynamic_gas_cost": dynamic_input_frames.gas_cost is not None, + "has_dynamic_arb_fees": dynamic_input_frames.arb_fees is not None, + "has_lp_supply": dynamic_input_frames.lp_supply is not None, + "has_reclamm_price_ratio_updates": ( + dynamic_input_frames.reclamm_price_ratio_updates is not None + ), + } + flags["use_dynamic_inputs"] = any(flags.values()) + return flags + + +def resolve_dynamic_input_flags( + dynamic_inputs: Optional[DynamicInputArrays], + dynamic_input_flags: Optional[dict] = None, +) -> dict: + """Return a safe dispatch flag set for the provided hot-path bundle.""" + flags = default_dynamic_input_flags() + if dynamic_input_flags is not None: + flags.update(dict(dynamic_input_flags)) + if dynamic_inputs is not None: + flags["use_dynamic_inputs"] = True + return flags + + +def empty_dynamic_input_arrays() -> DynamicInputArrays: + """Create a canonical empty bundle.""" + return DynamicInputArrays( + trades=None, + fees=jnp.zeros((1,)), + gas_cost=jnp.zeros((1,)), + arb_fees=jnp.zeros((1,)), + lp_supply=jnp.ones((1,)), + # Columns: has_event, target_price_ratio, end_step, start_price_ratio_override + reclamm_price_ratio_updates=jnp.array([[0.0, 0.0, 0.0, jnp.nan]]), + ) + + +def resolve_dynamic_input_components( + dynamic_inputs: Optional[DynamicInputArrays], + dynamic_input_flags: dict, + static_dict: dict, +) -> dict: + """Resolve dynamic-input leaves against static scalar defaults.""" + arrays = empty_dynamic_input_arrays() if dynamic_inputs is None else dynamic_inputs + return { + "trades": arrays.trades if dynamic_input_flags["has_trades"] else None, + "fees": ( + arrays.fees + if dynamic_input_flags["has_dynamic_fees"] + else jnp.asarray([static_dict["fees"]]) + ), + "gas_cost": ( + arrays.gas_cost + if dynamic_input_flags["has_dynamic_gas_cost"] + else jnp.asarray([static_dict["gas_cost"]]) + ), + "arb_fees": ( + arrays.arb_fees + if dynamic_input_flags["has_dynamic_arb_fees"] + else jnp.asarray([static_dict["arb_fees"]]) + ), + "lp_supply": ( + arrays.lp_supply + if dynamic_input_flags["has_lp_supply"] + else jnp.ones((1,)) + ), + "reclamm_price_ratio_updates": ( + arrays.reclamm_price_ratio_updates + if dynamic_input_flags["has_reclamm_price_ratio_updates"] + else empty_dynamic_input_arrays().reclamm_price_ratio_updates + ), + } + + +def _broadcast_dynamic_input_leaf( + input_name: str, + values: jnp.ndarray, + scan_len: int, + dtype, +) -> jnp.ndarray: + """Broadcast a singleton dynamic-input leaf to the scan length.""" + values = jnp.asarray(values, dtype=dtype) + if values.ndim == 0: + values = values.reshape((1,)) + if values.shape[0] == scan_len: + return values + if values.shape[0] == 1: + return jnp.broadcast_to(values, (scan_len,) + values.shape[1:]) + raise ValueError( + f"{input_name} has leading axis {values.shape[0]}, expected 1 or {scan_len}" + ) + + +def materialize_dynamic_inputs( + dynamic_inputs: Optional[DynamicInputArrays], + dynamic_input_flags: Optional[dict], + static_dict: dict, + scan_len: int, + do_trades: bool, + dtype=None, +) -> DynamicInputArrays: + """Resolve and broadcast dynamic inputs for a specific scan length.""" + if dynamic_input_flags is None and dynamic_inputs is not None: + flags = { + "use_dynamic_inputs": True, + "has_trades": do_trades, + "has_dynamic_fees": True, + "has_dynamic_gas_cost": True, + "has_dynamic_arb_fees": True, + "has_lp_supply": True, + "has_reclamm_price_ratio_updates": True, + } + else: + flags = resolve_dynamic_input_flags(dynamic_inputs, dynamic_input_flags) + + resolved = resolve_dynamic_input_components(dynamic_inputs, flags, static_dict) + + trades = None + if do_trades: + if resolved["trades"] is None: + raise ValueError("Trades must be provided when do_trades=True.") + trades = _broadcast_dynamic_input_leaf( + "trades", resolved["trades"], scan_len, dtype + ) + + return DynamicInputArrays( + trades=trades, + fees=_broadcast_dynamic_input_leaf("fees", resolved["fees"], scan_len, dtype), + gas_cost=_broadcast_dynamic_input_leaf( + "gas_cost", resolved["gas_cost"], scan_len, dtype + ), + arb_fees=_broadcast_dynamic_input_leaf( + "arb_fees", resolved["arb_fees"], scan_len, dtype + ), + lp_supply=_broadcast_dynamic_input_leaf( + "lp_supply", resolved["lp_supply"], scan_len, dtype + ), + reclamm_price_ratio_updates=_broadcast_dynamic_input_leaf( + "reclamm_price_ratio_updates", + resolved["reclamm_price_ratio_updates"], + scan_len, + dtype, + ), + ) diff --git a/quantammsim/core_simulator/forward_pass.py b/quantammsim/core_simulator/forward_pass.py index ef8eaa5..9fd8e57 100644 --- a/quantammsim/core_simulator/forward_pass.py +++ b/quantammsim/core_simulator/forward_pass.py @@ -44,6 +44,7 @@ config.update("jax_platform_name", "cpu") +import jax import jax.numpy as jnp import jax.random from jax import jit, vmap, devices @@ -53,11 +54,25 @@ import numpy as np from functools import partial +from quantammsim.core_simulator.dynamic_inputs import ( + DynamicInputArrays, + default_dynamic_input_flags, + resolve_dynamic_input_flags, +) np.seterr(all="raise") np.seterr(under="print") +def _resolve_dynamic_inputs(dynamic_inputs, static_dict): + """Return the incoming bundle plus static dispatch flags.""" + dynamic_input_flags = resolve_dynamic_input_flags( + dynamic_inputs, + static_dict.get("dynamic_input_flags"), + ) + return dynamic_inputs, dynamic_input_flags + + def _apply_price_noise(prices, sigma, seed_int): """Apply multiplicative log-normal noise to prices. @@ -98,6 +113,7 @@ def _apply_price_noise(prices, sigma, seed_int): DAILY_COMPATIBLE_METRICS = frozenset({ # Sharpe / VaR / ROVAR metrics naturally operate on day-boundary values. "daily_log_sharpe", + # "daily_log_sharpe_excess" excluded — needs HODL value series from prices "daily_sharpe", "daily_var_95%_trad", "daily_var_99%_trad", @@ -275,6 +291,46 @@ def _daily_log_sharpe(values: jnp.ndarray) -> jnp.ndarray: # Annualize daily stats (calendar days) return jnp.sqrt(365.0) * (mean / (std + 1e-8)) +def _daily_log_sharpe_excess( + pool_values: jnp.ndarray, + hodl_values: jnp.ndarray, +) -> jnp.ndarray: + r"""Annualized Sharpe ratio on daily log *excess* returns over HODL. + + Isolates LP alpha by subtracting the HODL benchmark return at each + daily interval. This removes crypto beta — a strategy that's just + long ETH no longer scores well in a bull-market window. + + .. math:: + + e_t = \log(V^{\mathrm{pool}}_t / V^{\mathrm{pool}}_{t-1}) + - \log(V^{\mathrm{hodl}}_t / V^{\mathrm{hodl}}_{t-1}) + + S_{\mathrm{excess}} = \sqrt{365} \cdot + \frac{\mu(e_t)}{\sigma(e_t) + \epsilon} + + Parameters + ---------- + pool_values : jnp.ndarray + Pool value time series at minute resolution, shape ``(T,)``. + hodl_values : jnp.ndarray + HODL value time series at minute resolution, shape ``(T,)``. + + Returns + ------- + jnp.ndarray + Scalar annualized excess-over-HODL log Sharpe. + """ + daily_pool = pool_values[::1440] + daily_hodl = hodl_values[::1440] + + log_ret_pool = jnp.diff(jnp.log(daily_pool + 1e-12)) + log_ret_hodl = jnp.diff(jnp.log(daily_hodl + 1e-12)) + + excess = log_ret_pool - log_ret_hodl + return jnp.sqrt(365.0) * (excess.mean() / (excess.std() + 1e-8)) + + def _calculate_max_drawdown(value_over_time, duration=7 * 24 * 60): """Calculate worst maximum drawdown across non-overlapping chunks. @@ -729,6 +785,10 @@ def _calculate_return_value( "daily_sharpe": lambda: jnp.sqrt(365) * (daily_returns.mean() / daily_returns.std()), "daily_log_sharpe": lambda: _daily_log_sharpe(value_over_time), + "daily_log_sharpe_excess": lambda: _daily_log_sharpe_excess( + value_over_time, + jnp.sum(stop_gradient(initial_reserves) * local_prices[:value_over_time.shape[0]], axis=-1), + ), "returns": lambda: value_over_time[-1] / value_over_time[0] - 1.0, "annualised_returns": lambda: ( (value_over_time[-1] / value_over_time[0]) @@ -839,15 +899,12 @@ def _calculate_return_value( return return_metrics[return_val]() -@partial(jit, static_argnums=(7, 8)) +@partial(jit, static_argnums=(4, 5)) def forward_pass( params, start_index, prices, - trades_array=None, - fees_array=None, - gas_cost_array=None, - arb_fees_array=None, + dynamic_inputs=None, pool=None, static_dict=None, ): @@ -870,17 +927,8 @@ def forward_pass( prices : array-like A 2D array of market prices for the assets involved in the simulation. - trades_array : array-like, optional - An array of trades to be considered in the simulation. Defaults to None. - - fees_array : array-like, optional - An array of fees to be applied during the simulation. Defaults to None. - - gas_cost_array : array-like, optional - An array of gas costs to be considered in the simulation. Defaults to None. - - arb_fees_array : array-like, optional - An array of arbitrage fees to be applied during the simulation. Defaults to None. + dynamic_inputs : DynamicInputArrays, optional + Fixed-structure bundle of dynamic trades/fees/gas/arb/LP arrays. pool : object An instance of a pool object that provides methods @@ -930,8 +978,8 @@ def forward_pass( - The function handles different cases for fees and trades, adjusting the calculation method accordingly: - 1. If any of `fees_array`, `gas_cost_array`, `arb_fees_array`, - or `trades_array` is provided, it uses `pool.calculate_reserves_with_dynamic_inputs`. + 1. If any dynamic-input flags are enabled, it uses + `pool.calculate_reserves_with_dynamic_inputs`. 2. If any of `fees`, `gas_cost`, or `arb_fees` in `static_dict` is a nonzero scalar value, it uses `pool.calculate_reserves_with_fees`. @@ -972,6 +1020,7 @@ def forward_pass( "training_data_kind": "historic", "arb_frequency": 1, "do_trades": False, + "dynamic_input_flags": default_dynamic_input_flags(), } # 'pool' has default of None only to handle how partial function @@ -1008,10 +1057,7 @@ def forward_pass( and static_dict["arb_frequency"] == 1 and static_dict.get("turnover_penalty", 0.0) == 0.0 and static_dict.get("price_noise_sigma", 0.0) == 0.0 - and all( - ele is None - for ele in [fees_array, gas_cost_array, arb_fees_array, trades_array] - ) + and dynamic_inputs is None and 1440 % static_dict["chunk_period"] == 0 # chunk_period divides metric_period and not pool._rule_outputs_are_weights # only delta-based pools validated and static_dict["bout_length"] > 1440 * 2 # need ≥2 metric periods @@ -1031,28 +1077,20 @@ def forward_pass( # 1. Any of Fees, gas costs, and arb fees are provided as arrays, or trades are provided # 2. Any of Fees, gas costs, and arb fees are nonzero scalar values, with no trades provided # 3. Fees, gas costs, and arb fees are all zero, with no trades provided + dynamic_inputs, dynamic_input_flags = _resolve_dynamic_inputs( + dynamic_inputs, static_dict + ) + fee_revenue = None - if any( - ele is not None - for ele in [fees_array, gas_cost_array, arb_fees_array, trades_array] - ): - # Case 1, at least one of fees, gas costs, or arb fees is not None - if fees_array is None: - fees_array = jnp.array([static_dict["fees"]]) - if gas_cost_array is None: - gas_cost_array = jnp.array([static_dict["gas_cost"]]) - if arb_fees_array is None: - arb_fees_array = jnp.array([static_dict["arb_fees"]]) + if dynamic_input_flags["use_dynamic_inputs"]: + # Case 1, at least one dynamic input is enabled if hasattr(pool, "calculate_reserves_and_fee_revenue_with_dynamic_inputs"): reserves, fee_revenue = pool.calculate_reserves_and_fee_revenue_with_dynamic_inputs( params, static_dict, prices, start_index, - fees_array=fees_array, - arb_thresh_array=gas_cost_array, - arb_fees_array=arb_fees_array, - trade_array=trades_array, + dynamic_inputs=dynamic_inputs, ) else: reserves = pool.calculate_reserves_with_dynamic_inputs( @@ -1060,10 +1098,7 @@ def forward_pass( static_dict, prices, start_index, - fees_array=fees_array, - arb_thresh_array=gas_cost_array, - arb_fees_array=arb_fees_array, - trade_array=trades_array, + dynamic_inputs=dynamic_inputs, ) elif True in ( ele > 0.0 @@ -1170,15 +1205,12 @@ def forward_pass( return base_metric -@partial(jit, static_argnums=(7, 8)) +@partial(jit, static_argnums=(4, 5)) def forward_pass_nograd( params, start_index, prices, - trades_array=None, - fees_array=None, - gas_cost_array=None, - arb_fees_array=None, + dynamic_inputs=None, pool=None, static_dict=None, ): @@ -1203,17 +1235,8 @@ def forward_pass_nograd( prices : array-like A 2D array of market prices for the assets involved in the simulation. - trades_array : array-like, optional - An array of trades to be considered in the simulation. Defaults to None. - - fees_array : array-like, optional - An array of fees to be applied during the simulation. Defaults to None. - - gas_cost_array : array-like, optional - An array of gas costs to be considered in the simulation. Defaults to None. - - arb_fees_array : array-like, optional - An array of arbitrage fees to be applied during the simulation. Defaults to None. + dynamic_inputs : DynamicInputArrays, optional + Fixed-structure bundle of dynamic trades/fees/gas/arb/LP arrays. pool : object An instance of a pool object that provides methods @@ -1263,8 +1286,8 @@ def forward_pass_nograd( - The function handles different cases for fees and trades, adjusting the calculation method accordingly: - 1. If any of `fees_array`, `gas_cost_array`, `arb_fees_array`, - or `trades_array` is provided, it uses `pool.calculate_reserves_with_dynamic_inputs`. + 1. If any dynamic-input flags are enabled, it uses + `pool.calculate_reserves_with_dynamic_inputs`. 2. If any of `fees`, `gas_cost`, or `arb_fees` in `static_dict` is a nonzero scalar value, it uses `pool.calculate_reserves_with_fees`. @@ -1289,14 +1312,26 @@ def forward_pass_nograd( params = {k: stop_gradient(v) for k, v in params.items()} start_index = stop_gradient(start_index) prices = stop_gradient(prices) + if dynamic_inputs is not None: + dynamic_inputs = DynamicInputArrays( + trades=( + None + if dynamic_inputs.trades is None + else stop_gradient(dynamic_inputs.trades) + ), + fees=stop_gradient(dynamic_inputs.fees), + gas_cost=stop_gradient(dynamic_inputs.gas_cost), + arb_fees=stop_gradient(dynamic_inputs.arb_fees), + lp_supply=stop_gradient(dynamic_inputs.lp_supply), + reclamm_price_ratio_updates=stop_gradient( + dynamic_inputs.reclamm_price_ratio_updates + ), + ) return forward_pass( params, start_index, prices, - trades_array, - fees_array, - gas_cost_array, - arb_fees_array, + dynamic_inputs, pool, static_dict, ) diff --git a/quantammsim/core_simulator/result_exporter.py b/quantammsim/core_simulator/result_exporter.py index 1c0fd27..d460c2f 100644 --- a/quantammsim/core_simulator/result_exporter.py +++ b/quantammsim/core_simulator/result_exporter.py @@ -222,8 +222,27 @@ def save_optuna_results_sgd_format( # Add metadata in SGD format param_dict["step"] = trial.number - param_dict["test_objective"] = float(trial.user_attrs.get("validation_value", float("-inf"))) - param_dict["train_objective"] = float(trial.user_attrs.get("train_value", float("-inf"))) + + # train_objective / test_objective / continuous_test_metrics in the + # same list-of-dict shape produced by save_multi_params (BFGS, + # CMA-ES). test_objective and continuous_test_metrics carry the + # same dict — the test-period metrics extracted from the continuous + # train→test forward pass — mirroring how save_multi_params is + # called in the BFGS/CMA-ES branches. + train_metrics_dict = trial.user_attrs.get("train_metrics_dict") + cont_test_metrics_dict = trial.user_attrs.get("continuous_test_metrics_dict") + if train_metrics_dict: + param_dict["train_objective"] = [train_metrics_dict] + else: + # Back-compat for trials saved before the rich dicts were + # captured: fall back to the scalar training-objective value. + param_dict["train_objective"] = float(trial.user_attrs.get("train_value", float("-inf"))) + if cont_test_metrics_dict: + param_dict["test_objective"] = [cont_test_metrics_dict] + param_dict["continuous_test_metrics"] = [cont_test_metrics_dict] + else: + param_dict["test_objective"] = float(trial.user_attrs.get("validation_value", float("-inf"))) + param_dict["objective"] = float(trial.value) if trial.value is not None else float("-inf") param_dict["hessian_trace"] = 0 # Not applicable for optuna param_dict["local_learning_rate"] = 0 # Not applicable for optuna diff --git a/quantammsim/core_simulator/windowing_utils.py b/quantammsim/core_simulator/windowing_utils.py index 052a77e..07fcca7 100644 --- a/quantammsim/core_simulator/windowing_utils.py +++ b/quantammsim/core_simulator/windowing_utils.py @@ -201,11 +201,12 @@ def raw_fee_like_amounts_to_fee_like_array( ).astype(int) // 10**6 )[:-1] + fill_value = np.nan if fill_method == "ffill" else 0.0 full_index_df = pd.DataFrame( - index=full_index, - columns=names, - data=0, - dtype=np.float64 + index=full_index, + columns=names, + data=fill_value, + dtype=np.float64, ) # Map raw data to the full index DataFrame @@ -231,15 +232,16 @@ def raw_fee_like_amounts_to_fee_like_array( # Ensure unix values are valid valid_unix = pd.to_numeric(raw_inputs['unix'], errors='coerce') valid_mask = valid_unix.notna() + valid_inputs = raw_inputs.loc[valid_mask].copy() + valid_inputs["unix"] = valid_unix.loc[valid_mask].astype(np.int64) + valid_inputs = valid_inputs.sort_values("unix") for name in names: initial_value = None - if valid_mask.any(): + if not valid_inputs.empty: # Try to get the last value before our start date - previous_values = raw_inputs[ - valid_mask & (valid_unix < start_unix) - ] + previous_values = valid_inputs[valid_inputs["unix"] < start_unix] if not previous_values.empty: try: @@ -249,9 +251,7 @@ def raw_fee_like_amounts_to_fee_like_array( if initial_value is None or pd.isna(initial_value): # Try to get first value in our date range - in_range_values = raw_inputs[ - valid_mask & (valid_unix >= start_unix) - ] + in_range_values = valid_inputs[valid_inputs["unix"] >= start_unix] if not in_range_values.empty: try: initial_value = pd.to_numeric(in_range_values[name].iloc[0]) @@ -259,17 +259,12 @@ def raw_fee_like_amounts_to_fee_like_array( initial_value = None if initial_value is not None and pd.notna(initial_value): - # this more complex logic is because of how we have started with prior-to-start values - # filled in, and then we want to ffill the rest - # Fill initial values - full_index_df[name] = full_index_df[name].mask( - full_index_df[name] == 0, - initial_value - ) - # Use ffill() - full_index_df[name] = full_index_df[name].where( - full_index_df[name] != 0 - ).ffill() + # Seed only the leading gap; explicit in-range updates must remain intact. + first_row = full_index_df.index[0] + if pd.isna(full_index_df.at[first_row, name]): + full_index_df.at[first_row, name] = initial_value + + full_index_df[name] = full_index_df[name].ffill().fillna(0.0) except (ValueError, KeyError, TypeError) as e: print(f"Warning: Error during ffill processing: {str(e)}") # On any error, return the original zero-filled DataFrame @@ -382,4 +377,4 @@ def filter_reserves_by_given_timestamp(reserves, unix_values, timestamp): unix_values == timestamp )[0][0] - return reserves[reserves_index].copy() \ No newline at end of file + return reserves[reserves_index].copy() diff --git a/quantammsim/hooks/dynamic_fee_base_hook.py b/quantammsim/hooks/dynamic_fee_base_hook.py index 40a9714..1ab0266 100644 --- a/quantammsim/hooks/dynamic_fee_base_hook.py +++ b/quantammsim/hooks/dynamic_fee_base_hook.py @@ -3,6 +3,10 @@ import jax.numpy as jnp from jax.lax import dynamic_slice +from quantammsim.core_simulator.dynamic_inputs import ( + DynamicInputArrays, + empty_dynamic_input_arrays, +) class BaseDynamicFeeHook(ABC): """Mixin class to add dynamic fee calculation capabilities to pools. @@ -113,16 +117,22 @@ def calculate_reserves_with_fees( (int((bout_length) / chunk_period), 1), ) dynamic_fees = raw_dynamic_fees.repeat(chunk_period, axis=0).squeeze() - # Use existing dynamic inputs infrastructure + empty_inputs = empty_dynamic_input_arrays() + dynamic_inputs = DynamicInputArrays( + trades=None, + fees=dynamic_fees, + gas_cost=jnp.asarray(run_fingerprint["gas_cost"], dtype=jnp.float64), + arb_fees=jnp.asarray(run_fingerprint["arb_fees"], dtype=jnp.float64), + lp_supply=empty_inputs.lp_supply, + reclamm_price_ratio_updates=empty_inputs.reclamm_price_ratio_updates, + ) + return self.calculate_reserves_with_dynamic_inputs( params, run_fingerprint, prices, start_index, - dynamic_fees, - run_fingerprint["gas_cost"], - run_fingerprint["arb_fees"], - dynamic_fees, + dynamic_inputs, additional_oracle_input, ) diff --git a/quantammsim/noise_calibration/__init__.py b/quantammsim/noise_calibration/__init__.py new file mode 100644 index 0000000..f1fddde --- /dev/null +++ b/quantammsim/noise_calibration/__init__.py @@ -0,0 +1,39 @@ +"""Noise calibration package for Balancer pool volume models. + +Public API re-exports from submodules. +""" + +# scipy.signal patch (must run before arviz import) +try: + from scipy.signal import gaussian as _ # noqa: F401 +except ImportError: + from scipy.signal.windows import gaussian as _gauss + import scipy.signal + scipy.signal.gaussian = _gauss + +from .constants import ( + K_COEFF, COEFF_NAMES, BALANCER_API_URL, BALANCER_API_CHAINS, CACHE_DIR, + K_CLUSTERS_DEFAULT, K_FEATURES_DEFAULT, +) +from .token_classification import classify_token_tier, _normalise_symbol +from .data_pipeline import ( + _graphql_request, enumerate_balancer_pools, fetch_pool_snapshots, + fetch_all_snapshots, fetch_token_prices, compute_pair_volatility, + assemble_panel, +) +from .data_validation import validate_panel +from .covariate_encoding import encode_covariates, encode_covariates_structural +from .model import noise_model, noise_model_dp_sigma, noise_model_ibp, noise_model_ibp_dp, stick_breaking_weights, structural_noise_model +from .formula_arb import formula_arb_volume_daily_jax +from .inference import ( + _get_theta_samples, _build_model_kwargs, run_svi, run_nuts, + run_svi_then_nuts, +) +from .postprocessing import ( + extract_noise_params, predict_new_pool, check_convergence, + run_prior_predictive, assign_dp_clusters, assign_ibp_dp_joint, + extract_structural_params, predict_new_pool_structural, +) +from .plotting import plot_diagnostics +from .output import generate_output_json, _save_sample_cache +from .cli import main diff --git a/quantammsim/noise_calibration/__main__.py b/quantammsim/noise_calibration/__main__.py new file mode 100644 index 0000000..2f05ddc --- /dev/null +++ b/quantammsim/noise_calibration/__main__.py @@ -0,0 +1,5 @@ +from .cli import main + + +if __name__ == "__main__": + main() diff --git a/quantammsim/noise_calibration/cli.py b/quantammsim/noise_calibration/cli.py new file mode 100644 index 0000000..30fc823 --- /dev/null +++ b/quantammsim/noise_calibration/cli.py @@ -0,0 +1,509 @@ +"""CLI entry point for noise calibration.""" + +from __future__ import annotations + +import argparse +import json +import os +import sys +from datetime import date, timedelta + +import numpy as np +import pandas as pd + +from .constants import CACHE_DIR, BALANCER_API_CHAINS +from .data_pipeline import ( + enumerate_balancer_pools, fetch_all_snapshots, + fetch_token_prices, assemble_panel, +) +from .data_validation import validate_panel +from .covariate_encoding import encode_covariates +from .inference import run_svi, run_nuts, run_svi_then_nuts +from .postprocessing import ( + extract_noise_params, predict_new_pool, + check_convergence, run_prior_predictive, +) +from .plotting import plot_diagnostics +from .output import generate_output_json, _save_sample_cache + + +CHAIN_NAME_ALIASES = { + "ethereum": "MAINNET", + "mainnet": "MAINNET", + "base": "BASE", + "gnosis": "GNOSIS", + "polygon": "POLYGON", + "arbitrum": "ARBITRUM", + "optimism": "OPTIMISM", + "avalanche": "AVALANCHE", + "sonic": "SONIC", +} + + +def normalize_chain_name(chain: str | None) -> str | None: + if chain is None: + return None + + normalized = chain.strip() + if not normalized: + return None + + upper = normalized.upper() + if upper in BALANCER_API_CHAINS: + return upper + + aliased = CHAIN_NAME_ALIASES.get(normalized.lower()) + if aliased is not None: + return aliased + + accepted = sorted(set(BALANCER_API_CHAINS) | set(CHAIN_NAME_ALIASES)) + raise ValueError( + f"Unknown chain '{chain}'. Expected one of: {', '.join(accepted)}" + ) + + +def merge_chain_scoped_cache( + existing_df: pd.DataFrame | None, + new_df: pd.DataFrame, + chains_to_replace: list[str], + sort_columns: list[str], +) -> pd.DataFrame: + if existing_df is None or existing_df.empty: + merged = new_df.copy() + else: + merged = existing_df[~existing_df["chain"].isin(chains_to_replace)].copy() + merged = pd.concat([merged, new_df], ignore_index=True) + + if sort_columns: + available_sort_columns = [col for col in sort_columns if col in merged.columns] + if available_sort_columns: + merged = merged.sort_values(available_sort_columns).reset_index(drop=True) + return merged + + +def _parse_args(): + parser = argparse.ArgumentParser( + description="Unified Bayesian hierarchical noise volume model " + "for Balancer pools (gold standard)" + ) + + # Actions + parser.add_argument("--fetch", action="store_true", + help="Fetch data from Balancer API") + parser.add_argument("--fit", action="store_true", + help="Run inference (SVI default)") + parser.add_argument("--nuts", action="store_true", + help="Use NUTS instead of SVI") + parser.add_argument("--svi-init-nuts", action="store_true", + help="SVI-initialized NUTS (fast warmup)") + parser.add_argument("--plot", action="store_true", + help="Generate diagnostic plots") + parser.add_argument("--prior-predictive", action="store_true", + help="Include prior predictive check") + parser.add_argument("--validate", action="store_true", + help="Run data validation pass") + parser.add_argument("--predict", action="store_true", + help="Predict for unseen pool") + + # Output + parser.add_argument("--output", default=None, + help="Output JSON path") + parser.add_argument("--output-dir", default="results", + help="Plot output directory (default: results)") + + # Predict args + parser.add_argument( + "--chain", + default=None, + help="Chain for --predict or to limit --fetch " + "(e.g. ethereum/base/gnosis or MAINNET/BASE/GNOSIS)", + ) + parser.add_argument("--tokens", nargs="+", default=None, + help="Tokens for --predict") + parser.add_argument("--fee", type=float, default=0.003, + help="Fee for --predict") + + # NUTS hyperparameters + parser.add_argument("--num-warmup", type=int, default=1000, + help="NUTS warmup iterations (default: 1000)") + parser.add_argument("--num-samples", type=int, default=2000, + help="NUTS/SVI samples (default: 2000)") + parser.add_argument("--num-chains", type=int, default=4, + help="NUTS chains (default: 4)") + parser.add_argument("--target-accept", type=float, default=0.85, + help="NUTS target accept prob (default: 0.85)") + parser.add_argument("--max-tree-depth", type=int, default=10, + help="NUTS max tree depth (default: 10)") + parser.add_argument("--seed", type=int, default=42, + help="Random seed (default: 42)") + + # SVI hyperparameters + parser.add_argument("--svi-steps", type=int, default=20000, + help="SVI optimization steps (default: 20000)") + parser.add_argument("--svi-lr", type=float, default=1e-3, + help="SVI learning rate (default: 1e-3)") + + # Model variant + parser.add_argument("--model", choices=["tier", "dp_sigma", "ibp", "ibp_dp", + "structural"], + default="tier", + help="Noise model variant: 'tier' (per-tier sigma_eps), " + "'dp_sigma' (DP mixture on sigma_eps), " + "'ibp' (IBP latent features), " + "'ibp_dp' (IBP features + DP noise clusters), or " + "'structural' (structural mixture: arb + MoE noise)") + parser.add_argument("--k-clusters", type=int, default=6, + help="Number of DP mixture components " + "(capacity ceiling, default: 6)") + parser.add_argument("--k-features", type=int, default=6, + help="Number of IBP latent features " + "(default: 6)") + + # Data + parser.add_argument("--train-days", type=int, default=90, + help="Use only the last N days of data for fitting " + "(default: 90). Aligns with Balancer API hourly price " + "coverage window. Set to 0 to use all data.") + parser.add_argument("--min-tvl", type=float, default=10000.0, + help="Pool enumeration TVL filter") + parser.add_argument("--cache-dir", default=None, + help="Cache directory") + parser.add_argument("--device", choices=["cpu", "gpu", "auto"], + default="auto", + help="JAX device (default: auto)") + + return parser.parse_args() + + +def main(): + args = _parse_args() + + if not any([args.fetch, args.fit, args.predict, args.validate]): + print("ERROR: At least one of --fetch, --fit, --predict, --validate " + "is required", file=sys.stderr) + sys.exit(1) + + try: + normalized_chain = normalize_chain_name(args.chain) + except ValueError as exc: + print(f"ERROR: {exc}", file=sys.stderr) + sys.exit(2) + + cache_dir = args.cache_dir or CACHE_DIR + + # --- JAX setup (BEFORE any JAX ops / imports) --- + if args.fit or args.predict or args.prior_predictive: + # Set device before importing JAX + if args.device == "cpu": + os.environ.setdefault("JAX_PLATFORMS", "cpu") + elif args.device == "gpu": + os.environ.setdefault("JAX_PLATFORMS", "cuda") + # auto: don't touch JAX_PLATFORMS, let JAX pick + + # Set host device count for NUTS multi-chain BEFORE JAX init + if args.nuts or args.svi_init_nuts: + import numpyro as _np_pre + _np_pre.set_host_device_count( + min(args.num_chains, os.cpu_count() or 4) + ) + + import jax + import numpyro + numpyro.enable_x64() + + # --- File paths --- + pools_cache = os.path.join(cache_dir, "pools.parquet") + snaps_cache = os.path.join(cache_dir, "pool_snapshots.parquet") + prices_cache = os.path.join(cache_dir, "token_prices") + panel_cache = os.path.join(cache_dir, "panel.parquet") + + # --- Fetch --- + if args.fetch: + print("Phase 1: Fetching data from Balancer API") + print("=" * 60) + + print("\n1. Enumerating pools...") + fetch_chains = [normalized_chain] if normalized_chain else None + if fetch_chains: + print(f" Limiting fetch to chain: {fetch_chains[0]}") + fetched_pools_df = enumerate_balancer_pools( + chains=fetch_chains, + min_tvl=args.min_tvl, + ) + if fetch_chains and os.path.exists(pools_cache): + existing_pools_df = pd.read_parquet(pools_cache) + pools_df = merge_chain_scoped_cache( + existing_pools_df, + fetched_pools_df, + fetch_chains, + sort_columns=["chain", "pool_id"], + ) + else: + pools_df = fetched_pools_df + os.makedirs(cache_dir, exist_ok=True) + pools_df.to_parquet(pools_cache, index=False) + print(f" Saved {len(pools_df)} pools -> {pools_cache}") + + print("\n2. Fetching daily snapshots...") + snapshots_df = fetch_all_snapshots(fetched_pools_df, cache_path=snaps_cache) + + print("\n3. Fetching token prices...") + token_addr_by_chain = {} + for _, pool in fetched_pools_df.iterrows(): + chain = pool["chain"] + tokens = pool["tokens"] + addresses = pool["token_addresses"] + if chain not in token_addr_by_chain: + token_addr_by_chain[chain] = {} + for sym, addr in zip(tokens, addresses): + if sym and addr: + token_addr_by_chain[chain][sym] = addr + + token_prices = fetch_token_prices( + token_addr_by_chain, cache_dir=prices_cache + ) + + print("\n4. Assembling panel (with lagged TVL)...") + fetched_panel = assemble_panel(fetched_pools_df, snapshots_df, token_prices) + if fetch_chains and os.path.exists(panel_cache): + existing_panel = pd.read_parquet(panel_cache) + panel = merge_chain_scoped_cache( + existing_panel, + fetched_panel, + fetch_chains, + sort_columns=["chain", "pool_id", "date"], + ) + else: + panel = fetched_panel + panel.to_parquet(panel_cache, index=False) + print(f" Saved panel -> {panel_cache}") + + print(f"\nFetch complete. Panel: {len(panel)} obs, " + f"{panel['pool_id'].nunique()} pools") + + # --- Validate --- + if args.validate: + if not os.path.exists(panel_cache): + print(f"ERROR: Panel cache not found at {panel_cache}", + file=sys.stderr) + print("Run with --fetch first.", file=sys.stderr) + sys.exit(1) + panel = pd.read_parquet(panel_cache) + validate_panel(panel) + + # --- Fit --- + if args.fit: + print("\nUnified Noise Volume Model") + print("=" * 60) + + # Load panel + if not os.path.exists(panel_cache): + print(f"ERROR: Panel cache not found at {panel_cache}", + file=sys.stderr) + print("Run with --fetch first.", file=sys.stderr) + sys.exit(1) + + panel = pd.read_parquet(panel_cache) + print(f" Loaded panel: {len(panel)} obs, " + f"{panel['pool_id'].nunique()} pools, " + f"{panel['chain'].nunique()} chains") + + # Filter to recent window for training + if args.train_days > 0: + max_date = panel["date"].max() + if not isinstance(max_date, date): + max_date = pd.Timestamp(max_date).date() + cutoff = max_date - timedelta(days=args.train_days) + n_before = len(panel) + panel = panel[ + panel["date"].apply( + lambda d: d >= cutoff if isinstance(d, date) + else pd.Timestamp(d).date() >= cutoff + ) + ].copy() + print(f" Filtered to last {args.train_days} days " + f"(>= {cutoff}): {len(panel)} obs " + f"(dropped {n_before - len(panel)})") + + # Ensure lagged TVL exists (in case loaded from old cache) + if "log_tvl_lag1" not in panel.columns: + print(" Adding lagged TVL to cached panel...") + panel = panel.sort_values(["pool_id", "date"]).reset_index(drop=True) + panel["log_tvl_lag1"] = panel.groupby("pool_id")["log_tvl"].shift(1) + panel = panel.dropna(subset=["log_tvl_lag1"]).reset_index(drop=True) + + # Filter: need at least 10 days per pool + pool_counts = panel.groupby("pool_id").size() + valid_pools = pool_counts[pool_counts >= 10].index + panel = panel[panel["pool_id"].isin(valid_pools)].copy() + print(f" After filtering (>= 10 days): {len(panel)} obs, " + f"{panel['pool_id'].nunique()} pools") + + # Select model variant + if args.model == "structural": + from .model import structural_noise_model + from .covariate_encoding import encode_covariates_structural + model_fn = structural_noise_model + data = encode_covariates_structural(panel) + print(f" Model: structural mixture (arb + MoE noise)") + elif args.model == "ibp_dp": + from .model import noise_model_ibp_dp + model_fn = noise_model_ibp_dp + data = encode_covariates(panel, include_tiers=False) + data["K_features"] = args.k_features + data["K_clusters"] = args.k_clusters + print(f" Model: IBP+DP hybrid " + f"(K_features={args.k_features}, " + f"K_clusters={args.k_clusters})") + elif args.model == "ibp": + from .model import noise_model_ibp + model_fn = noise_model_ibp + data = encode_covariates(panel, include_tiers=False) + data["K_features"] = args.k_features + print(f" Model: IBP latent features " + f"(K_features={args.k_features})") + elif args.model == "dp_sigma": + from .model import noise_model_dp_sigma + model_fn = noise_model_dp_sigma + data = encode_covariates(panel, include_tiers=False) + data["K_clusters"] = args.k_clusters + print(f" Model: DP mixture on sigma_eps " + f"(K_clusters={args.k_clusters})") + else: + model_fn = None # default = noise_model + data = encode_covariates(panel) + + # Prior predictive + prior_samples = None + if args.prior_predictive: + print("\n Running prior predictive check...") + prior_samples = run_prior_predictive(data, model_fn=model_fn) + + # Inference + mcmc_obj = None + elbo_losses = None + inference_config = {"seed": args.seed} + + if args.svi_init_nuts: + inference_config["method"] = "svi_init_nuts" + inference_config["svi_steps"] = args.svi_steps + inference_config["svi_lr"] = args.svi_lr + inference_config["num_warmup"] = args.num_warmup + inference_config["num_samples"] = args.num_samples + inference_config["num_chains"] = args.num_chains + inference_config["target_accept"] = args.target_accept + inference_config["max_tree_depth"] = args.max_tree_depth + + mcmc_obj, elbo_losses = run_svi_then_nuts( + data, + svi_steps=args.svi_steps, + svi_lr=args.svi_lr, + num_warmup=args.num_warmup, + num_samples=args.num_samples, + num_chains=args.num_chains, + target_accept=args.target_accept, + max_tree_depth=args.max_tree_depth, + seed=args.seed, + model_fn=model_fn, + ) + samples = mcmc_obj + convergence = check_convergence(mcmc_obj, method="nuts") + + elif args.nuts: + inference_config["method"] = "nuts" + inference_config["num_warmup"] = args.num_warmup + inference_config["num_samples"] = args.num_samples + inference_config["num_chains"] = args.num_chains + inference_config["target_accept"] = args.target_accept + inference_config["max_tree_depth"] = args.max_tree_depth + + mcmc_obj = run_nuts( + data, + num_warmup=args.num_warmup, + num_samples=args.num_samples, + num_chains=args.num_chains, + target_accept=args.target_accept, + max_tree_depth=args.max_tree_depth, + seed=args.seed, + model_fn=model_fn, + ) + samples = mcmc_obj + convergence = check_convergence(mcmc_obj, method="nuts") + + else: + inference_config["method"] = "svi" + inference_config["svi_steps"] = args.svi_steps + inference_config["svi_lr"] = args.svi_lr + inference_config["num_samples"] = args.num_samples + + samples, elbo_losses = run_svi( + data, + num_steps=args.svi_steps, + lr=args.svi_lr, + seed=args.seed, + num_samples=args.num_samples, + model_fn=model_fn, + ) + convergence = check_convergence(elbo_losses, method="svi") + + if args.model == "structural": + from .postprocessing import extract_structural_params + pool_params = extract_structural_params(samples, data) + arb_freqs = [p["arb_frequency"] for p in pool_params] + print(f"\n Per-pool arb_frequency: " + f"mean={np.mean(arb_freqs):.1f}, " + f"range=[{np.min(arb_freqs)}, {np.max(arb_freqs)}]") + else: + pool_params = extract_noise_params(samples, data) + b_c_vals = [p["noise_params"]["b_c"] for p in pool_params] + b_0_vals = [p["noise_params"]["b_0"] for p in pool_params] + print(f"\n Per-pool b_c: mean={np.mean(b_c_vals):.3f}, " + f"std={np.std(b_c_vals):.3f}, " + f"range=[{np.min(b_c_vals):.3f}, {np.max(b_c_vals):.3f}]") + print(f" Per-pool b_0: mean={np.mean(b_0_vals):.3f}, " + f"std={np.std(b_0_vals):.3f}") + + if args.output: + generate_output_json( + pool_params, samples, data, convergence, + args.output, inference_config, + ) + + if args.plot: + print("\nGenerating diagnostic plots...") + plot_diagnostics( + samples, data, output_dir=args.output_dir, + elbo_losses=elbo_losses, mcmc=mcmc_obj, + prior_samples=prior_samples, + ) + + # Cache samples for --predict + _save_sample_cache(samples, data, cache_dir) + + # --- Predict --- + if args.predict: + if args.chain is None or args.tokens is None: + print("ERROR: --predict requires --chain and --tokens", + file=sys.stderr) + sys.exit(1) + + # Load cached samples + sample_cache = os.path.join(cache_dir, "unified_samples.npz") + data_cache = os.path.join(cache_dir, "unified_data.json") + + if not os.path.exists(sample_cache): + print(f"ERROR: Sample cache not found at {sample_cache}", + file=sys.stderr) + print("Run with --fit first.", file=sys.stderr) + sys.exit(1) + + cached = np.load(sample_cache) + sample_dict = {k: cached[k] for k in cached.files} + + with open(data_cache) as f: + data_meta = json.load(f) + + result = predict_new_pool( + sample_dict, data_meta, normalized_chain, args.tokens, args.fee + ) + print(json.dumps(result, indent=2)) diff --git a/quantammsim/noise_calibration/constants.py b/quantammsim/noise_calibration/constants.py new file mode 100644 index 0000000..05a217d --- /dev/null +++ b/quantammsim/noise_calibration/constants.py @@ -0,0 +1,60 @@ +"""Constants for noise calibration.""" + +import os + +K_COEFF = 4 +COEFF_NAMES = ["intercept", "b_tvl", "b_sigma", "b_weekend"] + +BALANCER_API_URL = "https://api-v3.balancer.fi/" + +BALANCER_API_CHAINS = [ + "MAINNET", "POLYGON", "ARBITRUM", "GNOSIS", "BASE", "SONIC", "OPTIMISM", + "AVALANCHE", +] + +CACHE_DIR = os.path.join( + os.path.dirname(os.path.dirname(os.path.dirname(__file__))), + "local_data", "noise_calibration", +) + +# Tier 0: blue-chip — top by volume, wrapped native, major stables +_TIER_0 = { + "ETH", "WETH", "BTC", "WBTC", "cbBTC", "USDC", "USDT", "DAI", + "wstETH", "stETH", "rETH", "cbETH", "WMATIC", "MATIC", "POL", + "WAVAX", "AVAX", "GNO", "WXDAI", "xDAI", + "S", "wS", +} + +K_CLUSTERS_DEFAULT = 6 +K_FEATURES_DEFAULT = 6 + +# Structural model: observation-level covariates (expanded from K_COEFF=4) +K_OBS_COEFF = 8 +OBS_COEFF_NAMES = [ + "intercept", "b_tvl", "b_sigma", + "b_tvl_sigma", "b_tvl_fee", "b_sigma_fee", + "b_dow_sin", "b_dow_cos", +] + +# Gas costs per arb transaction (USD) by chain +GAS_COSTS = { + "MAINNET": None, # time-varying, loaded from CSV + "POLYGON": 0.005, + "ARBITRUM": 0.005, + "BASE": 0.005, + "GNOSIS": 0.01, + "OPTIMISM": 0.005, + "SONIC": 0.005, + "AVALANCHE": 0.005, + "MODE": 0.005, + "FRAXTAL": 0.005, +} + +# Tier 1: mid-cap DeFi blue-chips (approx CoinGecko rank < 200) +_TIER_1 = { + "AAVE", "LINK", "UNI", "BAL", "MKR", "CRV", "COMP", "SNX", + "LDO", "RPL", "SUSHI", "YFI", "1INCH", "ENS", "DYDX", + "FXS", "FRAX", "LUSD", "sDAI", "GHO", "crvUSD", + "ARB", "OP", "PENDLE", "ENA", "EIGEN", + "SAFE", "COW", +} diff --git a/quantammsim/noise_calibration/covariate_encoding.py b/quantammsim/noise_calibration/covariate_encoding.py new file mode 100644 index 0000000..a298a6b --- /dev/null +++ b/quantammsim/noise_calibration/covariate_encoding.py @@ -0,0 +1,228 @@ +"""Covariate encoding for the hierarchical noise model.""" + +import numpy as np +import pandas as pd + +from .constants import K_COEFF, K_OBS_COEFF, GAS_COSTS + + +def encode_covariates(panel: pd.DataFrame, include_tiers: bool = True) -> dict: + """Build NumPyro-ready arrays from the panel DataFrame. + + Returns dict with arrays for the model plus metadata for output/prediction. + Key difference from hierarchical script: x_obs uses log_tvl_lag1 not log_tvl. + """ + pool_meta = panel.drop_duplicates("pool_id").reset_index(drop=True) + pool_ids = pool_meta["pool_id"].values + pool_id_to_idx = {pid: i for i, pid in enumerate(pool_ids)} + N_pools = len(pool_ids) + + pool_idx = panel["pool_id"].map(pool_id_to_idx).values + + # --- Build X_pool (pool-level covariates, data-driven) --- + chains = sorted(panel["chain"].unique()) + ref_chain = chains[0] + chain_cols = [] + chain_names = [] + for c in chains[1:]: + chain_cols.append((pool_meta["chain"] == c).astype(float).values) + chain_names.append(f"chain_{c}") + + tier_a_vals = sorted(pool_meta["tier_A"].astype(str).unique()) + ref_tier_a = tier_a_vals[0] + tier_a_cols = [] + tier_a_names = [] + if include_tiers: + for t in tier_a_vals[1:]: + tier_a_cols.append( + (pool_meta["tier_A"].astype(str) == t).astype(float).values + ) + tier_a_names.append(f"tier_A_{t}") + + tier_b_vals = sorted(pool_meta["tier_B"].astype(str).unique()) + ref_tier_b = tier_b_vals[0] + tier_b_cols = [] + tier_b_names = [] + if include_tiers: + for t in tier_b_vals[1:]: + tier_b_cols.append( + (pool_meta["tier_B"].astype(str) == t).astype(float).values + ) + tier_b_names.append(f"tier_B_{t}") + + columns = [np.ones((N_pools, 1))] + col_names = ["intercept"] + + for arr, name in zip(chain_cols, chain_names): + columns.append(arr.reshape(-1, 1)) + col_names.append(name) + for arr, name in zip(tier_a_cols, tier_a_names): + columns.append(arr.reshape(-1, 1)) + col_names.append(name) + for arr, name in zip(tier_b_cols, tier_b_names): + columns.append(arr.reshape(-1, 1)) + col_names.append(name) + columns.append(pool_meta["log_fee"].values.reshape(-1, 1)) + col_names.append("log_fee") + + X_pool = np.hstack(columns) + K_cov = X_pool.shape[1] + + # --- Observation-level arrays (uses LAGGED TVL) --- + x_obs = np.column_stack([ + np.ones(len(panel)), + panel["log_tvl_lag1"].values, + panel["volatility"].values, + panel["weekend"].values, + ]).astype(np.float64) + + y_obs = panel["log_volume"].values.astype(np.float64) + + # --- Per-pool tier_A index for per-tier sigma_eps --- + tier_A_per_pool = pool_meta["tier_A"].values.astype(np.int32) + + print(f" Encoded: N_obs={len(y_obs)}, N_pools={N_pools}, " + f"K_coeff={K_COEFF}, K_cov={K_cov}") + print(f" Covariates: {col_names}") + print(f" Tier distribution: " + f"T0={np.sum(tier_A_per_pool == 0)}, " + f"T1={np.sum(tier_A_per_pool == 1)}, " + f"T2={np.sum(tier_A_per_pool == 2)}") + + return { + "pool_idx": pool_idx.astype(np.int32), + "X_pool": X_pool.astype(np.float64), + "x_obs": x_obs, + "y_obs": y_obs, + "pool_ids": list(pool_ids), + "pool_meta": pool_meta, + "covariate_names": col_names, + "tier_A_per_pool": tier_A_per_pool, + "N_pools": N_pools, + "K_cov": K_cov, + "ref_chain": ref_chain, + "ref_tier_a": ref_tier_a, + "ref_tier_b": ref_tier_b, + "chains": chains, + } + + +def _tier_pair_idx(a: int, b: int) -> int: + """Encode (tier_A, tier_B) pair as a single index. + + Upper triangle of 3x3 grid: + (0,0)->0, (0,1)->1, (0,2)->2, (1,1)->3, (1,2)->4, (2,2)->5. + """ + return a * (5 - a) // 2 + b - a + + +def encode_covariates_structural( + panel: pd.DataFrame, + gas: np.ndarray = None, +) -> dict: + """Build NumPyro-ready arrays for the structural mixture model. + + Extends encode_covariates with: + - x_obs: 8 columns (intercept, tvl, log_sigma, interactions, DOW harmonics) + - Additional arrays: sigma_daily, fee, gas, chain_idx, tier_idx, lag_log_tvl + - n_chains, n_tiers computed from panel + + Parameters + ---------- + panel : pd.DataFrame + Output of assemble_panel(), must have log_sigma, dow_sin, dow_cos, + tvl_x_sigma, tvl_x_fee, sigma_x_fee columns. + gas : np.ndarray, optional + Per-observation gas costs in USD. If None, uses default (0.01 for all). + """ + # Ensure structural columns exist (compute from base columns if missing) + if "log_sigma" not in panel.columns: + panel = panel.copy() + panel["log_sigma"] = np.log(np.maximum(panel["volatility"].values, 1e-6)) + dow = panel["date"].apply( + lambda d: d.weekday() if hasattr(d, "weekday") + else pd.Timestamp(d).weekday() + ) + panel["dow_sin"] = np.sin(2.0 * np.pi * dow / 7.0) + panel["dow_cos"] = np.cos(2.0 * np.pi * dow / 7.0) + panel["tvl_x_sigma"] = panel["log_tvl_lag1"] * panel["log_sigma"] + panel["tvl_x_fee"] = panel["log_tvl_lag1"] * panel["log_fee"] + panel["sigma_x_fee"] = panel["log_sigma"] * panel["log_fee"] + + # Reuse X_pool construction from encode_covariates (with tiers for gating) + base = encode_covariates(panel, include_tiers=True) + + # --- Observation-level x_obs: 8 columns --- + x_obs = np.column_stack([ + np.ones(len(panel)), # intercept + panel["log_tvl_lag1"].values, # lagged TVL + panel["log_sigma"].values, # log(volatility) + panel["tvl_x_sigma"].values, # tvl × sigma interaction + panel["tvl_x_fee"].values, # tvl × fee interaction + panel["sigma_x_fee"].values, # sigma × fee interaction + panel["dow_sin"].values, # DOW harmonic sin + panel["dow_cos"].values, # DOW harmonic cos + ]).astype(np.float64) + + # --- Additional arrays for the structural model --- + sigma_daily = (panel["volatility"] / np.sqrt(365.0)).values.astype(np.float64) + fee_per_obs = np.exp(panel["log_fee"].values).astype(np.float64) + lag_log_tvl = panel["log_tvl_lag1"].values.astype(np.float64) + + # Gas: per-observation + if gas is not None: + gas_arr = np.asarray(gas, dtype=np.float64) + else: + gas_arr = np.full(len(panel), 0.01, dtype=np.float64) + + # Chain index: integer per pool + pool_meta = base["pool_meta"] + chains = base["chains"] + chain_to_idx = {c: i for i, c in enumerate(chains)} + chain_idx_per_pool = np.array( + [chain_to_idx[c] for c in pool_meta["chain"]], dtype=np.int32, + ) + + # Tier pair index: per pool + tier_idx_per_pool = np.array( + [_tier_pair_idx(int(row["tier_A"]), int(row["tier_B"])) + for _, row in pool_meta.iterrows()], + dtype=np.int32, + ) + + # Count unique tier pairs and chains + n_chains = len(chains) + tier_pairs = set() + for _, row in pool_meta.iterrows(): + tier_pairs.add((int(row["tier_A"]), int(row["tier_B"]))) + n_tiers = 6 # fixed: upper triangle of 3x3 + + print(f" Structural encoding: N_obs={len(panel)}, " + f"N_pools={base['N_pools']}, n_chains={n_chains}, n_tiers={n_tiers}") + + return { + # Base arrays (same as encode_covariates) + "pool_idx": base["pool_idx"], + "X_pool": base["X_pool"], + "x_obs": x_obs, + "y_obs": base["y_obs"], + "pool_ids": base["pool_ids"], + "pool_meta": pool_meta, + "covariate_names": base["covariate_names"], + "tier_A_per_pool": base["tier_A_per_pool"], + "N_pools": base["N_pools"], + "K_cov": base["K_cov"], + "ref_chain": base["ref_chain"], + "ref_tier_a": base["ref_tier_a"], + "ref_tier_b": base["ref_tier_b"], + "chains": chains, + # Structural model extras + "sigma_daily": sigma_daily, + "fee": fee_per_obs, + "gas": gas_arr, + "chain_idx": chain_idx_per_pool, + "tier_idx": tier_idx_per_pool, + "lag_log_tvl": lag_log_tvl, + "n_chains": n_chains, + "n_tiers": n_tiers, + } diff --git a/quantammsim/noise_calibration/data_pipeline.py b/quantammsim/noise_calibration/data_pipeline.py new file mode 100644 index 0000000..b824cdc --- /dev/null +++ b/quantammsim/noise_calibration/data_pipeline.py @@ -0,0 +1,516 @@ +"""Data pipeline: fetch pools, snapshots, prices, and assemble panel.""" + +import json +import os +import time +import urllib.request +from datetime import datetime + +import numpy as np +import pandas as pd + +from .constants import BALANCER_API_URL, BALANCER_API_CHAINS +from .token_classification import classify_token_tier + + +def _graphql_request(query: dict, base_url: str = BALANCER_API_URL, + timeout: int = 30) -> dict: + """Send a GraphQL request to the Balancer V3 API.""" + data = json.dumps(query).encode("utf-8") + req = urllib.request.Request( + base_url, + data=data, + headers={ + "Content-Type": "application/json", + "User-Agent": "quantammsim/1.0", + }, + ) + with urllib.request.urlopen(req, timeout=timeout) as resp: + return json.loads(resp.read().decode("utf-8")) + + +def enumerate_balancer_pools( + chains: list = None, + pool_types: list = None, + min_tvl: float = 10000.0, +) -> pd.DataFrame: + """Enumerate all WEIGHTED + RECLAMM pools across chains from Balancer API.""" + if chains is None: + chains = BALANCER_API_CHAINS + if pool_types is None: + pool_types = ["WEIGHTED", "RECLAMM"] + + all_pools = [] + for chain in chains: + print(f" Querying {chain}...", end=" ", flush=True) + query = { + "query": """ + query GetPools($chain: GqlChain!, $types: [GqlPoolType!], + $minTvl: Float) { + poolGetPools( + where: { + chainIn: [$chain] + poolTypeIn: $types + minTvl: $minTvl + } + ) { + id + chain + type + createTime + protocolVersion + poolTokens { + symbol + weight + address + } + dynamicData { + totalLiquidity + swapFee + } + } + } + """, + "variables": { + "chain": chain, + "types": pool_types, + "minTvl": min_tvl, + }, + } + + try: + body = _graphql_request(query) + pools = body.get("data", {}).get("poolGetPools", []) + except Exception as e: + print(f"FAILED ({e})") + continue + + for p in pools: + tokens = [t["symbol"] for t in p.get("poolTokens", [])] + weights = [t.get("weight") for t in p.get("poolTokens", [])] + token_addresses = [t.get("address", "") for t in p.get("poolTokens", [])] + tvl = float(p.get("dynamicData", {}).get("totalLiquidity", 0)) + fee = float(p.get("dynamicData", {}).get("swapFee", 0)) + + all_pools.append({ + "pool_id": p["id"], + "chain": p["chain"], + "pool_type": p["type"], + "protocol_version": p.get("protocolVersion", 0), + "tokens": tokens, + "token_addresses": token_addresses, + "weights": weights, + "swap_fee": fee, + "create_time": p.get("createTime", 0), + "current_tvl": tvl, + }) + + print(f"{len(pools)} pools") + time.sleep(0.3) + + df = pd.DataFrame(all_pools) + print(f"\n Total: {len(df)} pools across {len(chains)} chains") + return df + + +def fetch_pool_snapshots(pool_id: str, chain: str, + base_url: str = BALANCER_API_URL) -> pd.DataFrame: + """Fetch ALL_TIME daily snapshots for a single pool.""" + query = { + "query": """ + query GetSnapshots($poolId: String!, $chain: GqlChain!, + $range: GqlPoolSnapshotDataRange!) { + poolGetSnapshots(id: $poolId, chain: $chain, range: $range) { + timestamp + volume24h + totalLiquidity + totalShares + } + } + """, + "variables": { + "poolId": pool_id, + "chain": chain, + "range": "ALL_TIME", + }, + } + + body = _graphql_request(query) + snapshots = body.get("data", {}).get("poolGetSnapshots", []) + + if not snapshots: + return pd.DataFrame(columns=["timestamp", "volume_usd", + "total_liquidity_usd", "total_shares"]) + + records = [] + for snap in snapshots: + records.append({ + "timestamp": int(snap["timestamp"]), + "volume_usd": float(snap["volume24h"]), + "total_liquidity_usd": float(snap["totalLiquidity"]), + "total_shares": float(snap.get("totalShares", 0)), + }) + + df = pd.DataFrame(records) + df["date"] = pd.to_datetime(df["timestamp"], unit="s").dt.date + df = df.sort_values("timestamp").drop_duplicates("date", keep="last") + return df + + +def fetch_all_snapshots(pools_df: pd.DataFrame, + cache_path: str = None) -> pd.DataFrame: + """Fetch daily snapshots for all pools, with caching.""" + cached = pd.DataFrame() + cached_pool_ids = set() + if cache_path and os.path.exists(cache_path): + cached = pd.read_parquet(cache_path) + cached_pool_ids = set(cached["pool_id"].unique()) + print(f" Cache has {len(cached_pool_ids)} pools, " + f"{len(cached)} pool-days") + + if len(pools_df) == 0: + print(" No pools to fetch.") + return cached if len(cached) > 0 else pd.DataFrame( + columns=["pool_id", "chain", "date", "volume_usd", + "total_liquidity_usd", "total_shares"] + ) + to_fetch = pools_df[~pools_df["pool_id"].isin(cached_pool_ids)] + print(f" Need to fetch {len(to_fetch)} new pools") + + new_records = [] + for i, (_, pool) in enumerate(to_fetch.iterrows()): + if (i + 1) % 10 == 0 or i == 0: + print(f" Fetching {i+1}/{len(to_fetch)}: {pool['pool_id'][:10]}... " + f"({pool['chain']})", flush=True) + try: + snap_df = fetch_pool_snapshots(pool["pool_id"], pool["chain"]) + if len(snap_df) > 0: + snap_df["pool_id"] = pool["pool_id"] + snap_df["chain"] = pool["chain"] + cols = ["pool_id", "chain", "date", "volume_usd", + "total_liquidity_usd"] + if "total_shares" in snap_df.columns: + cols.append("total_shares") + new_records.append(snap_df[cols]) + except Exception as e: + print(f" FAILED {pool['pool_id'][:10]}: {e}") + time.sleep(0.5) + + if new_records: + new_df = pd.concat(new_records, ignore_index=True) + combined = pd.concat([cached, new_df], ignore_index=True) + else: + combined = cached + + if cache_path and len(combined) > 0: + os.makedirs(os.path.dirname(cache_path), exist_ok=True) + combined.to_parquet(cache_path, index=False) + print(f" Saved cache: {len(combined)} pool-days -> {cache_path}") + + return combined + + +def fetch_token_prices(token_addresses_by_chain: dict, + cache_dir: str = None) -> dict: + """Fetch hourly token prices from Balancer API.""" + if cache_dir: + os.makedirs(cache_dir, exist_ok=True) + + prices = {} + + for chain, tokens in token_addresses_by_chain.items(): + uncached = {} + for symbol, address in tokens.items(): + cache_key = f"{chain}_{symbol}".replace("/", "_") + cp = os.path.join(cache_dir, f"{cache_key}.parquet") if cache_dir else None + + if cp and os.path.exists(cp): + prices[(chain, symbol)] = pd.read_parquet(cp) + else: + uncached[symbol] = address + + if not uncached: + continue + + addr_to_symbol = {addr: sym for sym, addr in uncached.items()} + addresses = list(uncached.values()) + + print(f" Fetching {len(addresses)} prices on {chain}...", flush=True) + + batch_size = 20 + for batch_start in range(0, len(addresses), batch_size): + batch_addrs = addresses[batch_start:batch_start + batch_size] + query = { + "query": """ + query GetPrices($chain: GqlChain!, $addresses: [String!]!, + $range: GqlTokenChartDataRange!) { + tokenGetHistoricalPrices( + addresses: $addresses, chain: $chain, range: $range + ) { + address + prices { + timestamp + price + } + } + } + """, + "variables": { + "chain": chain, + "addresses": batch_addrs, + "range": "ONE_YEAR", + }, + } + + try: + body = _graphql_request(query, timeout=60) + results = body.get("data", {}).get( + "tokenGetHistoricalPrices", []) + for result in results: + addr = result.get("address", "") + price_list = result.get("prices", []) + symbol = addr_to_symbol.get(addr) + if symbol and price_list: + pdf = pd.DataFrame(price_list) + pdf["timestamp"] = pdf["timestamp"].astype(int) + pdf["price"] = pdf["price"].astype(float) + prices[(chain, symbol)] = pdf + if cache_dir: + cache_key = f"{chain}_{symbol}".replace("/", "_") + cp = os.path.join(cache_dir, f"{cache_key}.parquet") + pdf.to_parquet(cp, index=False) + except Exception as e: + print(f" FAILED batch on {chain}: {e}") + + time.sleep(0.5) + + print(f" Got prices for {len(prices)} token-chain pairs") + return prices + + +def compute_pair_volatility( + snapshots_df: pd.DataFrame, + pool_row: pd.Series, + token_prices: dict, +) -> pd.Series: + """Compute daily annualised volatility for a pool's pair ratio.""" + tokens = pool_row["tokens"] + chain = pool_row["chain"] + + if len(tokens) < 2: + return pd.Series(dtype=float) + + def _get_price_df(symbol): + key = (chain, symbol) + if key in token_prices: + return token_prices[key] + for k, v in token_prices.items(): + if k[1] == symbol: + return v + return None + + p0_df = _get_price_df(tokens[0]) + p1_df = _get_price_df(tokens[1]) + + stables = {"USDC", "USDT", "DAI", "LUSD", "GHO", "crvUSD", "sDAI", + "WXDAI", "xDAI", "USDC.e", "USDbC"} + + if tokens[0] in stables and tokens[1] in stables: + dates = snapshots_df["date"].unique() + return pd.Series(0.01, index=dates) + + if p0_df is None and tokens[0] not in stables: + dates = snapshots_df["date"].unique() + return pd.Series(0.5, index=dates) + if p1_df is None and tokens[1] not in stables: + dates = snapshots_df["date"].unique() + return pd.Series(0.5, index=dates) + + if tokens[0] in stables: + if p1_df is None or len(p1_df) == 0: + dates = snapshots_df["date"].unique() + return pd.Series(0.5, index=dates) + ratio_df = p1_df.copy() + ratio_df["ratio"] = 1.0 / ratio_df["price"] + elif tokens[1] in stables: + if p0_df is None or len(p0_df) == 0: + dates = snapshots_df["date"].unique() + return pd.Series(0.5, index=dates) + ratio_df = p0_df.copy() + ratio_df["ratio"] = ratio_df["price"] + else: + if p0_df is None or p1_df is None or len(p0_df) == 0 or len(p1_df) == 0: + dates = snapshots_df["date"].unique() + return pd.Series(0.5, index=dates) + merged = pd.merge_asof( + p0_df.sort_values("timestamp"), + p1_df.sort_values("timestamp"), + on="timestamp", + suffixes=("_0", "_1"), + tolerance=7200, + ).dropna() + if len(merged) == 0: + dates = snapshots_df["date"].unique() + return pd.Series(0.5, index=dates) + ratio_df = merged.copy() + ratio_df["ratio"] = merged["price_0"] / merged["price_1"] + + ratio_df["datetime"] = pd.to_datetime(ratio_df["timestamp"], unit="s") + ratio_df["date"] = ratio_df["datetime"].dt.date + ratio_df = ratio_df.sort_values("timestamp") + + ratio_df["log_return"] = np.log( + ratio_df["ratio"] / ratio_df["ratio"].shift(1) + ) + ratio_df = ratio_df.dropna(subset=["log_return"]) + + daily_vol = ratio_df.groupby("date")["log_return"].std() + daily_vol_ann = daily_vol * np.sqrt(24 * 365) + + return daily_vol_ann + + +def assemble_panel( + pools_df: pd.DataFrame, + snapshots_df: pd.DataFrame, + token_prices: dict, +) -> pd.DataFrame: + """Assemble the full panel DataFrame with lagged TVL. + + Adds log_tvl_lag1 = per-pool shift(1) of log_tvl to break + the TVL-volume simultaneity bias. Drops the first observation + per pool (~1 obs per pool). + """ + records = [] + pool_ids = snapshots_df["pool_id"].unique() + n_pools = len(pool_ids) + + # Track volatility fallback rate + n_obs_total = 0 + n_obs_fallback = 0 + pools_all_fallback = [] # pools where every obs hit fallback + + for i, pool_id in enumerate(pool_ids): + if (i + 1) % 20 == 0 or i == 0: + print(f" Assembling {i+1}/{n_pools}...", flush=True) + + pool_snaps = snapshots_df[snapshots_df["pool_id"] == pool_id] + pool_meta = pools_df[pools_df["pool_id"] == pool_id] + if len(pool_meta) == 0: + continue + pool_row = pool_meta.iloc[0] + + tokens = pool_row["tokens"] + if len(tokens) < 2: + continue + + chain = pool_row["chain"] + swap_fee = pool_row["swap_fee"] + + tiers = sorted([classify_token_tier(t) for t in tokens]) + tier_a = tiers[0] + tier_b = tiers[1] if len(tiers) > 1 else tiers[0] + + vol_series = compute_pair_volatility(pool_snaps, pool_row, token_prices) + + pool_obs = 0 + pool_fallback = 0 + has_shares = "total_shares" in pool_snaps.columns + for _, snap in pool_snaps.iterrows(): + date = snap["date"] + volume = snap["volume_usd"] + tvl = snap["total_liquidity_usd"] + shares = float(snap["total_shares"]) if has_shares else 0.0 + + if tvl <= 0 or volume <= 0: + continue + + used_fallback = False + if isinstance(vol_series, pd.Series) and date in vol_series.index: + vol = vol_series[date] + else: + vol = 0.5 + used_fallback = True + + if not np.isfinite(vol) or vol <= 0: + vol = 0.5 + used_fallback = True + + n_obs_total += 1 + if used_fallback: + n_obs_fallback += 1 + pool_fallback += 1 + pool_obs += 1 + + if isinstance(date, datetime): + is_weekend = date.weekday() >= 5 + else: + is_weekend = pd.Timestamp(date).weekday() >= 5 + + # DOW harmonics (deterministic from date) + if isinstance(date, datetime): + dow = date.weekday() + else: + dow = pd.Timestamp(date).weekday() + dow_sin = np.sin(2.0 * np.pi * dow / 7.0) + dow_cos = np.cos(2.0 * np.pi * dow / 7.0) + + record = { + "pool_id": pool_id, + "chain": chain, + "date": date, + "log_volume": np.log(volume), + "log_tvl": np.log(tvl), + "volatility": vol, + "log_sigma": np.log(max(vol, 1e-6)), + "weekend": 1.0 if is_weekend else 0.0, + "log_fee": np.log(max(swap_fee, 1e-6)), + "dow_sin": dow_sin, + "dow_cos": dow_cos, + "tier_A": tier_a, + "tier_B": tier_b, + "tokens": ",".join(tokens[:2]), + "swap_fee": swap_fee, + } + if shares > 0: + record["total_shares"] = shares + records.append(record) + + if pool_obs > 0 and pool_fallback == pool_obs: + pools_all_fallback.append( + (pool_id[:16], chain, ",".join(tokens[:2])) + ) + + panel = pd.DataFrame(records) + + # Add lagged TVL to break simultaneity bias + panel = panel.sort_values(["pool_id", "date"]).reset_index(drop=True) + panel["log_tvl_lag1"] = panel.groupby("pool_id")["log_tvl"].shift(1) + n_before = len(panel) + panel = panel.dropna(subset=["log_tvl_lag1"]).reset_index(drop=True) + n_dropped = n_before - len(panel) + + # Interaction terms (use lagged TVL to break simultaneity) + panel["tvl_x_sigma"] = panel["log_tvl_lag1"] * panel["log_sigma"] + panel["tvl_x_fee"] = panel["log_tvl_lag1"] * panel["log_fee"] + panel["sigma_x_fee"] = panel["log_sigma"] * panel["log_fee"] + + print(f"\n Panel: {len(panel)} observations, " + f"{panel['pool_id'].nunique()} pools, " + f"{panel['chain'].nunique()} chains") + print(f" Dropped {n_dropped} first-day obs for lagged TVL") + + # Volatility coverage report + if n_obs_total > 0: + pct = 100 * n_obs_fallback / n_obs_total + print(f"\n Volatility coverage:") + print(f" {n_obs_fallback}/{n_obs_total} obs used fallback " + f"vol=0.5 ({pct:.1f}%)") + print(f" {len(pools_all_fallback)} pools had 100% fallback") + if pools_all_fallback: + for pid, ch, toks in pools_all_fallback[:10]: + print(f" {pid}... ({ch}) {toks}") + if len(pools_all_fallback) > 10: + print(f" ... and {len(pools_all_fallback) - 10} more") + + return panel diff --git a/quantammsim/noise_calibration/data_validation.py b/quantammsim/noise_calibration/data_validation.py new file mode 100644 index 0000000..8cb44e7 --- /dev/null +++ b/quantammsim/noise_calibration/data_validation.py @@ -0,0 +1,41 @@ +"""Data validation for noise calibration panels.""" + +import numpy as np +import pandas as pd + + +def validate_panel(panel: pd.DataFrame) -> pd.DataFrame: + """Run data validation checks. Prints warnings but does NOT drop rows.""" + print("\n Data validation:") + + # Pools with constant volume + vol_std = panel.groupby("pool_id")["log_volume"].std() + constant_vol = vol_std[vol_std < 0.01] + if len(constant_vol) > 0: + print(f" WARNING: {len(constant_vol)} pools have near-constant " + f"log(volume) (std < 0.01)") + for pid in constant_vol.index[:5]: + print(f" {pid[:16]}... std={constant_vol[pid]:.4f}") + if len(constant_vol) > 5: + print(f" ... and {len(constant_vol) - 5} more") + + # TVL jumps > 10x between consecutive days + panel_sorted = panel.sort_values(["pool_id", "date"]) + tvl_ratio = panel_sorted.groupby("pool_id")["log_tvl"].diff().abs() + big_jumps = tvl_ratio[tvl_ratio > np.log(10)] + if len(big_jumps) > 0: + affected_pools = panel_sorted.loc[big_jumps.index, "pool_id"].nunique() + print(f" WARNING: {len(big_jumps)} TVL jumps > 10x across " + f"{affected_pools} pools") + + # Days where volume > TVL + high_vol = panel[panel["log_volume"] > panel["log_tvl"]] + if len(high_vol) > 0: + affected_pools = high_vol["pool_id"].nunique() + print(f" WARNING: {len(high_vol)} days where volume > TVL across " + f"{affected_pools} pools (potential wash trading)") + + if len(constant_vol) == 0 and len(big_jumps) == 0 and len(high_vol) == 0: + print(" All checks passed.") + + return panel diff --git a/quantammsim/noise_calibration/formula_arb.py b/quantammsim/noise_calibration/formula_arb.py new file mode 100644 index 0000000..80fe335 --- /dev/null +++ b/quantammsim/noise_calibration/formula_arb.py @@ -0,0 +1,36 @@ +"""JAX-differentiable LVR formula for arb volume. + +Based on arXiv:2305.14604v2 §6 with gas costs and discrete-time correction. +Reference: scripts/plot_formula_arb_vs_real.py:formula_arb_volume_daily (line 58). +""" + +import jax.numpy as jnp + + +def formula_arb_volume_daily_jax(sigma_daily, tvl, fee, gas_usd, cadence_minutes): + """Analytical arb volume per day for a CPMM with gas costs. + + All inputs are JAX scalars or arrays (must be broadcastable). + + Parameters + ---------- + sigma_daily : float + Daily volatility of the log price ratio (NOT annualised). + tvl : float + Pool TVL in USD. + fee : float + Swap fee as fraction (e.g. 0.003 for 30bp). + gas_usd : float + All-in gas cost per arb tx in USD. + cadence_minutes : float + Effective arb cadence in minutes (= simulator's arb_frequency). + """ + block_time_s = cadence_minutes * 60.0 + delta = 2.0 * jnp.sqrt(2.0 * jnp.maximum(gas_usd, 0.0) / jnp.maximum(tvl, 1e-6)) + bLVR = sigma_daily**2 * tvl / 8.0 + sqrt_term = sigma_daily * jnp.sqrt(block_time_s / (2.0 * 86400.0)) + correction = jnp.maximum( + 1.0 - delta / (2.0 * fee) - sqrt_term / (fee + delta / 2.0), + 0.0, + ) + return bLVR * correction / fee diff --git a/quantammsim/noise_calibration/inference.py b/quantammsim/noise_calibration/inference.py new file mode 100644 index 0000000..e315a5b --- /dev/null +++ b/quantammsim/noise_calibration/inference.py @@ -0,0 +1,270 @@ +"""Inference runners: SVI, NUTS, SVI-initialized NUTS.""" + +import numpy as np + +from .constants import K_COEFF +from .model import noise_model + + +def _get_theta_samples(sample_dict: dict, X_pool: np.ndarray, + data: dict = None) -> np.ndarray: + """Get theta samples, reconstructing from non-centered params if needed. + + MCMC.get_samples() includes the deterministic "theta" site. + SVI's Predictive(guide, ...) does NOT — the guide only samples latent + variables. In that case, reconstruct theta manually: + mu = X_pool @ B^T + L_Sigma = diag(sigma_theta) @ L_Omega + theta = mu + eta @ L_Sigma^T + + For marginalized IBP (W present, z_logit absent): compute MAP feature + assignments from data, then theta = X_pool @ B.T + Z_MAP @ W. + Requires data dict for pool_idx, x_obs, y_obs. + + For legacy STE IBP (z_logit present): theta = X_pool @ B.T + Z_hard @ W. + """ + if "theta" in sample_dict: + return np.array(sample_dict["theta"]) + + # Marginalized IBP path: compute MAP assignments from data + if "W" in sample_dict and "z_logit" not in sample_dict: + B = np.array(sample_dict["B"]) # (S, K_coeff, K_cov) + W = np.array(sample_dict["W"]) # (S, K_features, K_coeff) + + # MAP assignments: (N_pools, K_features) binary + if "v" in sample_dict: + # Hybrid IBP+DP: joint MAP over (features, clusters) + from .postprocessing import assign_ibp_dp_joint + Z_map, _ = assign_ibp_dp_joint(sample_dict, data) + else: + from .postprocessing import assign_ibp_features + Z_map = assign_ibp_features(sample_dict, data) + + mu = np.einsum("pd,sjd->spj", X_pool, B) + # Z_map doesn't vary across samples — broadcast + feature_effect = np.einsum("pk,skj->spj", Z_map.astype(float), W) + return mu + feature_effect + + # Legacy STE IBP path: theta = X_pool @ B.T + Z_hard @ W + if "z_logit" in sample_dict: + B = np.array(sample_dict["B"]) # (S, K_coeff, K_cov) + W = np.array(sample_dict["W"]) # (S, K_features, K_coeff) + z_logit = np.array(sample_dict["z_logit"]) # (S, N_pools, K_features) + Z_hard = (z_logit > 0).astype(float) + + mu = np.einsum("pd,sjd->spj", X_pool, B) # (S, N_pools, K_coeff) + feature_effect = np.einsum("spk,skj->spj", Z_hard, W) + return mu + feature_effect + + B = np.array(sample_dict["B"]) # (S, K_coeff, K_cov) + sigma_theta = np.array(sample_dict["sigma_theta"]) # (S, K_coeff) + L_Omega = np.array(sample_dict["L_Omega"]) # (S, K_coeff, K_coeff) + eta = np.array(sample_dict["eta"]) # (S, N_pools, K_coeff) + + # mu[s, p, j] = sum_d X_pool[p, d] * B[s, j, d] -> (S, N_pools, K_coeff) + mu = np.einsum("pd,sjd->spj", X_pool, B) + + # L_Sigma = diag(sigma_theta) @ L_Omega -> (S, K_coeff, K_coeff) + L_Sigma = sigma_theta[:, :, None] * L_Omega + + # offset = eta @ L_Sigma^T -> (S, N_pools, K_coeff) + offset = np.einsum("spi,sji->spj", eta, L_Sigma) + + return mu + offset + + +def _build_model_kwargs(data: dict, model_fn=None) -> dict: + """Convert data dict to jnp arrays for the model. + + Uses inspect.signature on model_fn to decide which kwargs to include: + - tier_A_per_pool: only if model_fn accepts it + - K_clusters: only if model_fn accepts it and data has it + """ + import inspect + import jax.numpy as jnp + + if model_fn is None: + model_fn = noise_model + + params = set(inspect.signature(model_fn).parameters.keys()) + + kwargs = dict( + pool_idx=jnp.array(data["pool_idx"]), + X_pool=jnp.array(data["X_pool"]), + x_obs=jnp.array(data["x_obs"]), + y_obs=jnp.array(data["y_obs"]), + N_pools=data["N_pools"], + K_coeff=K_COEFF, + K_cov=data["K_cov"], + ) + + if "tier_A_per_pool" in params: + kwargs["tier_A_per_pool"] = jnp.array(data["tier_A_per_pool"]) + + if "K_clusters" in params and "K_clusters" in data: + kwargs["K_clusters"] = data["K_clusters"] + + if "K_features" in params and "K_features" in data: + kwargs["K_features"] = data["K_features"] + + # Structural model parameters + if "sigma_daily" in params and "sigma_daily" in data: + kwargs["sigma_daily"] = jnp.array(data["sigma_daily"]) + if "lag_log_tvl" in params and "lag_log_tvl" in data: + kwargs["lag_log_tvl"] = jnp.array(data["lag_log_tvl"]) + if "fee" in params and "fee" in data: + kwargs["fee"] = jnp.array(data["fee"]) + if "gas" in params and "gas" in data: + kwargs["gas"] = jnp.array(data["gas"]) + if "chain_idx" in params and "chain_idx" in data: + kwargs["chain_idx"] = jnp.array(data["chain_idx"]) + if "tier_idx" in params and "tier_idx" in data: + kwargs["tier_idx"] = jnp.array(data["tier_idx"]) + if "n_chains" in params and "n_chains" in data: + kwargs["n_chains"] = data["n_chains"] + if "n_tiers" in params and "n_tiers" in data: + kwargs["n_tiers"] = data["n_tiers"] + if "K_archetypes" in params and "K_archetypes" in data: + kwargs["K_archetypes"] = data["K_archetypes"] + + return kwargs + + +def run_svi(data, num_steps=20000, lr=1e-3, seed=0, + num_samples=1000, model_fn=None) -> tuple: + """Run SVI with AutoNormal guide. + + Returns (samples_dict, elbo_losses). + """ + import jax + import jax.numpy as jnp + import numpyro + from numpyro.infer import SVI, Trace_ELBO, Predictive + from numpyro.infer.autoguide import AutoNormal + + if model_fn is None: + model_fn = noise_model + + model_kwargs = _build_model_kwargs(data, model_fn=model_fn) + + print(f"\n Running SVI: {num_steps} steps, lr={lr}") + guide = AutoNormal(model_fn) + optimizer = numpyro.optim.Adam(lr) + svi = SVI(model_fn, guide, optimizer, loss=Trace_ELBO()) + + rng_key = jax.random.PRNGKey(seed) + svi_result = svi.run(rng_key, num_steps, **model_kwargs) + + elbo_losses = np.array(svi_result.losses) + print(f" SVI complete. Final ELBO: {elbo_losses[-1]:.2f}") + print(f" ELBO last 100 std: {np.std(elbo_losses[-100:]):.2f}") + + # Draw posterior samples + predictive = Predictive( + guide, params=svi_result.params, num_samples=num_samples, + ) + samples = predictive(jax.random.PRNGKey(seed + 1), **model_kwargs) + samples = {k: np.array(v) for k, v in samples.items()} + + print(f" Drew {num_samples} posterior samples.") + return samples, elbo_losses + + +def run_nuts(data, num_warmup=1000, num_samples=2000, num_chains=4, + target_accept=0.85, max_tree_depth=10, seed=42, + init_values=None, model_fn=None): + """Run NUTS MCMC. + + Uses init_to_value if init_values provided (for SVI-initialized NUTS). + Returns the MCMC object. + """ + import jax + import jax.numpy as jnp + import numpyro + from numpyro.infer import MCMC, NUTS, init_to_value + + if model_fn is None: + model_fn = noise_model + + # Note: set_host_device_count must be called before JAX init. + # We handle this in main(). Here we just verify device count. + n_devices = len(jax.devices("cpu")) + if n_devices < num_chains: + print(f" WARNING: Only {n_devices} CPU devices available for " + f"{num_chains} chains. Chains will run sequentially.") + + init_strategy = None + if init_values is not None: + init_strategy = init_to_value( + values={k: jnp.array(v) for k, v in init_values.items()} + ) + + kernel = NUTS( + model_fn, + target_accept_prob=target_accept, + max_tree_depth=max_tree_depth, + init_strategy=init_strategy, + ) + mcmc = MCMC( + kernel, + num_warmup=num_warmup, + num_samples=num_samples, + num_chains=num_chains, + progress_bar=True, + ) + + model_kwargs = _build_model_kwargs(data, model_fn=model_fn) + rng_key = jax.random.PRNGKey(seed) + + print(f"\n Running NUTS: {num_chains} chains x " + f"({num_warmup} warmup + {num_samples} samples)") + print(f" target_accept={target_accept}, max_tree_depth={max_tree_depth}") + if init_values is not None: + print(" Using SVI-initialized starting values.") + + mcmc.run(rng_key, **model_kwargs) + mcmc.print_summary(exclude_deterministic=True) + return mcmc + + +def run_svi_then_nuts(data, svi_steps=5000, svi_lr=1e-3, + num_warmup=500, num_samples=2000, num_chains=4, + target_accept=0.85, max_tree_depth=10, seed=42, + model_fn=None): + """Run SVI first, then use posterior means as NUTS init. + + Returns (MCMC, elbo_losses). + """ + if model_fn is None: + model_fn = noise_model + + # Phase 1: SVI + print(" Phase 1: SVI warm-start") + samples, elbo_losses = run_svi( + data, num_steps=svi_steps, lr=svi_lr, seed=seed, num_samples=100, + model_fn=model_fn, + ) + + # Extract posterior means for init + init_values = {} + skip_keys = {"y", "theta", "w"} + for k, v in samples.items(): + if k in skip_keys: + continue + init_values[k] = np.mean(v, axis=0) + + # Phase 2: NUTS from SVI init + print("\n Phase 2: NUTS from SVI-initialized values") + mcmc = run_nuts( + data, + num_warmup=num_warmup, + num_samples=num_samples, + num_chains=num_chains, + target_accept=target_accept, + max_tree_depth=max_tree_depth, + seed=seed, + init_values=init_values, + model_fn=model_fn, + ) + + return mcmc, elbo_losses diff --git a/quantammsim/noise_calibration/model.py b/quantammsim/noise_calibration/model.py new file mode 100644 index 0000000..4a1ba19 --- /dev/null +++ b/quantammsim/noise_calibration/model.py @@ -0,0 +1,465 @@ +"""NumPyro noise volume models.""" + +import jax +import jax.numpy as jnp + +from .formula_arb import formula_arb_volume_daily_jax + + +def _pad_with_ref(alpha): + """Prepend a zero for the reference category.""" + return jnp.concatenate([jnp.zeros(1), alpha]) + + +def stick_breaking_weights(v): + """Convert Beta stick-breaking fractions to K-simplex weights. + + v: array of shape (K-1,) with values in (0, 1). + Returns weights of shape (K,) summing to 1. + + w_1 = v_1 + w_k = v_k * prod_{j> jnp.arange(K_features)[None, :]) & 1 + ).astype(jnp.float32) # (n_configs, K_features) + + # Log-prior for each config from IBP stick-breaking + log_pi = jnp.log(pi + 1e-30) + log_1mpi = jnp.log(1.0 - pi + 1e-30) + log_prior = configs @ log_pi + (1.0 - configs) @ log_1mpi # (n_configs,) + + # Per-config feature effect + feature_effects = configs @ W # (n_configs, K_coeff) + + # Per-observation means + mu_pop_obs = jnp.sum(mu_pop[pool_idx] * x_obs, axis=1) # (N_obs,) + feature_mu = x_obs @ feature_effects.T # (N_obs, n_configs) + mu_obs = mu_pop_obs[:, None] + feature_mu # (N_obs, n_configs) + + # Log-likelihood per obs per config + log_lik = dist.StudentT(df, mu_obs, sigma_eps).log_prob( + y_obs[:, None] + ) # (N_obs, n_configs) + + # Sum log-likelihoods within each pool + pool_log_liks = jnp.zeros((N_pools, n_configs)) + pool_log_liks = pool_log_liks.at[pool_idx].add(log_lik) + + # Marginal: logsumexp over configs per pool + log_marginal = logsumexp( + log_prior[None, :] + pool_log_liks, axis=1 + ) # (N_pools,) + numpyro.factor("log_lik", log_marginal.sum()) + else: + # === Prior predictive: sample explicit assignments === + with numpyro.plate("pools", N_pools): + z_features = numpyro.sample( + "z_features", + dist.Bernoulli(probs=pi).expand([K_features]).to_event(1), + ) + + theta = mu_pop + z_features @ W # (N_pools, K_coeff) + numpyro.deterministic("theta", theta) + + theta_obs = theta[pool_idx] + mu_obs = jnp.sum(theta_obs * x_obs, axis=1) + + with numpyro.plate("obs", pool_idx.shape[0]): + numpyro.sample( + "y", dist.StudentT(df, mu_obs, sigma_eps), + ) + + +def noise_model_ibp_dp(pool_idx, X_pool, x_obs, y_obs=None, + N_pools=None, K_coeff=4, K_cov=None, + K_features=6, K_clusters=6): + """Hybrid IBP+DP noise model. + + IBP latent features for mean heterogeneity (theta = X_pool @ B.T + z @ W), + DP mixture for noise heterogeneity (per-cluster sigma_eps). Joint + marginalization over (2^K_features × K_clusters) configurations when + y_obs is provided. + """ + import numpyro + import numpyro.distributions as dist + from jax.scipy.special import logsumexp + + # --- Population effects --- + B = numpyro.sample( + "B", dist.Normal(0.0, 5.0).expand([K_coeff, K_cov]).to_event(2) + ) + + # Student-t degrees of freedom + df = numpyro.sample("df", dist.Gamma(2.0, 0.1)) + + # --- IBP prior on feature prevalences --- + alpha_ibp = numpyro.sample("alpha_ibp", dist.Gamma(2.0, 1.0)) + with numpyro.plate("features", K_features): + v_ibp = numpyro.sample("v_ibp", dist.Beta(alpha_ibp, 1.0)) + pi = jnp.cumprod(v_ibp) # decreasing prevalences + + # --- Feature effect matrix --- + sigma_w = numpyro.sample("sigma_w", dist.HalfNormal(2.0)) + W = numpyro.sample( + "W", dist.Normal(0.0, sigma_w).expand([K_features, K_coeff]).to_event(2) + ) + + # --- DP mixture on sigma_eps --- + alpha_dp = numpyro.sample("alpha_dp", dist.Gamma(1.0, 1.0)) + with numpyro.plate("sticks", K_clusters - 1): + v = numpyro.sample("v", dist.Beta(1.0, alpha_dp)) + w = numpyro.deterministic("w", stick_breaking_weights(v)) + + sigma_eps = numpyro.sample( + "sigma_eps", + dist.HalfNormal(2.0).expand([K_clusters]).to_event(1), + ) + + # --- Population mean per pool --- + mu_pop = X_pool @ B.T # (N_pools, K_coeff) + + if y_obs is not None: + # === Joint marginalization over IBP configs × DP clusters === + + # Enumerate all 2^K binary feature configurations + n_configs = 2 ** K_features + configs = ( + (jnp.arange(n_configs)[:, None] >> jnp.arange(K_features)[None, :]) & 1 + ).astype(jnp.float32) # (n_configs, K_features) + + # Log-prior for each IBP config + log_pi = jnp.log(pi + 1e-30) + log_1mpi = jnp.log(1.0 - pi + 1e-30) + log_ibp_prior = configs @ log_pi + (1.0 - configs) @ log_1mpi # (n_configs,) + + # Per-config feature effect + feature_effects = configs @ W # (n_configs, K_coeff) + + # Per-observation means for each IBP config + mu_pop_obs = jnp.sum(mu_pop[pool_idx] * x_obs, axis=1) # (N_obs,) + feature_mu = x_obs @ feature_effects.T # (N_obs, n_configs) + mu_obs = mu_pop_obs[:, None] + feature_mu # (N_obs, n_configs) + + # Log-likelihood per obs per IBP config per DP cluster + log_lik = dist.StudentT( + df, mu_obs[:, :, None], sigma_eps[None, None, :] + ).log_prob(y_obs[:, None, None]) # (N_obs, n_configs, K_clusters) + + # Sum log-likelihoods within each pool + pool_log_liks = jnp.zeros((N_pools, n_configs, K_clusters)) + pool_log_liks = pool_log_liks.at[pool_idx].add(log_lik) + + # Joint prior: IBP config prior × DP cluster weight + log_joint_prior = log_ibp_prior[:, None] + jnp.log(w + 1e-30)[None, :] # (n_configs, K_clusters) + + # Marginal log-likelihood per pool: logsumexp over (configs, clusters) + log_marginal = logsumexp( + log_joint_prior[None, :, :] + pool_log_liks, axis=(1, 2) + ) # (N_pools,) + numpyro.factor("log_lik", log_marginal.sum()) + else: + # === Prior predictive: sample explicit assignments === + with numpyro.plate("pools", N_pools): + z_features = numpyro.sample( + "z_features", + dist.Bernoulli(probs=pi).expand([K_features]).to_event(1), + ) + z_cluster = numpyro.sample("z_cluster", dist.Categorical(probs=w)) + + theta = mu_pop + z_features @ W # (N_pools, K_coeff) + numpyro.deterministic("theta", theta) + + theta_obs = theta[pool_idx] + mu_obs = jnp.sum(theta_obs * x_obs, axis=1) + sigma_obs = sigma_eps[z_cluster[pool_idx]] + + with numpyro.plate("obs", pool_idx.shape[0]): + numpyro.sample("y", dist.StudentT(df, mu_obs, sigma_obs)) + + +def structural_noise_model(pool_idx, X_pool, x_obs, y_obs=None, + sigma_daily=None, lag_log_tvl=None, + fee=None, gas=None, + chain_idx=None, tier_idx=None, + N_pools=None, K_obs_coeff=None, + K_cov=None, tier_A_per_pool=None, + n_chains=8, n_tiers=6, + **kwargs): + """Structural model: LVR arb + hierarchical per-pool noise. + + Decomposes observed total volume into arb (LVR formula with learnable + cadence) and noise (per-pool theta with hierarchical prior, same as + noise_model). All continuous — AutoNormal guide works directly. + """ + import numpyro + import numpyro.distributions as dist + + K_obs_coeff = K_obs_coeff or x_obs.shape[1] + K_cov = K_cov or X_pool.shape[1] + + # --- Arb cadence parameters --- + # Informative prior: exp(2.5) ≈ 12 min cadence (empirical median from + # formula-vs-real analysis on major pairs). sigma=0.5 allows range ~4-35 min. + alpha_0 = numpyro.sample("alpha_0", dist.Normal(2.5, 0.5)) + alpha_chain = numpyro.sample( + "alpha_chain", + dist.Normal(0, 0.5).expand([n_chains - 1]).to_event(1), + ) + alpha_tier = numpyro.sample( + "alpha_tier", + dist.Normal(0, 0.5).expand([n_tiers - 1]).to_event(1), + ) + alpha_tvl = numpyro.sample("alpha_tvl", dist.Normal(0, 0.3)) + + # Per-observation cadence (broadcast pool-level indices to obs) + log_cadence = ( + alpha_0 + + _pad_with_ref(alpha_chain)[chain_idx[pool_idx]] + + _pad_with_ref(alpha_tier)[tier_idx[pool_idx]] + + alpha_tvl * lag_log_tvl + ) + cadence = jnp.exp(jnp.clip(log_cadence, -2.0, 6.0)) # 0.1 to 400 min + + # V_arb per obs (deterministic given cadence + observables) + V_arb = formula_arb_volume_daily_jax( + sigma_daily, jnp.exp(lag_log_tvl), fee, gas, cadence, + ) + + # --- Hierarchical per-pool noise (same structure as noise_model) --- + B = numpyro.sample( + "B", dist.Normal(0.0, 5.0).expand([K_obs_coeff, K_cov]).to_event(2) + ) + sigma_theta = numpyro.sample( + "sigma_theta", dist.HalfNormal(2.0).expand([K_obs_coeff]).to_event(1) + ) + L_Omega = numpyro.sample( + "L_Omega", dist.LKJCholesky(K_obs_coeff, concentration=2.0) + ) + + # Non-centered pool effects + L_Sigma = jnp.diag(sigma_theta) @ L_Omega + + with numpyro.plate("pools", N_pools): + eta = numpyro.sample( + "eta", dist.Normal(0.0, 1.0).expand([K_obs_coeff]).to_event(1) + ) + + mu_pop = X_pool @ B.T # (N_pools, K_obs_coeff) + theta = mu_pop + eta @ L_Sigma.T # (N_pools, K_obs_coeff) + numpyro.deterministic("theta", theta) + + # Per-obs noise volume + theta_obs = theta[pool_idx] + log_V_noise = jnp.sum(theta_obs * x_obs, axis=1) + V_noise = jnp.exp(log_V_noise) + + # --- Observation model --- + df = numpyro.sample("df", dist.Gamma(2.0, 0.1)) + + # Per-tier sigma_eps (same as noise_model) + sigma_eps = numpyro.sample( + "sigma_eps", dist.HalfNormal(3.0).expand([3]).to_event(1) + ) + sigma_obs = sigma_eps[tier_A_per_pool[pool_idx]] + + mu = jnp.log(jnp.maximum(V_arb + V_noise, 1e-6)) + + if y_obs is not None: + with numpyro.plate("obs", pool_idx.shape[0]): + numpyro.sample("y", dist.StudentT(df, mu, sigma_obs), obs=y_obs) + else: + with numpyro.plate("obs", pool_idx.shape[0]): + numpyro.sample("y", dist.StudentT(df, mu, sigma_obs)) diff --git a/quantammsim/noise_calibration/output.py b/quantammsim/noise_calibration/output.py new file mode 100644 index 0000000..1d7b632 --- /dev/null +++ b/quantammsim/noise_calibration/output.py @@ -0,0 +1,314 @@ +"""JSON output and sample caching.""" + +import json +import os + +import numpy as np + +from .constants import K_COEFF, COEFF_NAMES, K_OBS_COEFF, OBS_COEFF_NAMES + + +def generate_output_json(pool_params, samples, data, convergence, + output_path, inference_config): + """Write structured JSON output. + + Dispatches format based on whether samples contain DP mixture parameters + (detected via "v" in sample_dict), or structural model parameters + (detected via "W_gate" in sample_dict). + """ + if hasattr(samples, "get_samples"): + sample_dict = samples.get_samples() + else: + sample_dict = samples + + # Structural model path: has arb cadence params + hierarchical noise + is_structural = "alpha_0" in sample_dict and "B" in sample_dict + if is_structural: + _generate_structural_output( + pool_params, sample_dict, data, convergence, + output_path, inference_config, + ) + return + + # Detection priority: check hybrid first, then pure IBP, then DP + is_ibp_dp = ("W" in sample_dict and "v" in sample_dict + and "z_logit" not in sample_dict) + is_ibp = ("W" in sample_dict and "v" not in sample_dict + and "z_logit" not in sample_dict) + is_ibp_ste = "z_logit" in sample_dict # legacy STE artifacts + is_dp = "v" in sample_dict and "W" not in sample_dict + + B_median = np.median(np.array(sample_dict["B"]), axis=0).tolist() + sigma_eps_median = np.median( + np.array(sample_dict["sigma_eps"]), axis=0 + ) + df_median = float(np.median(np.array(sample_dict["df"]))) + + if is_ibp_dp: + model_name = "hierarchical_student_t_ibp_dp" + sigma_eps_structure = "dp_mixture" + + W_median = np.median(np.array(sample_dict["W"]), axis=0).tolist() + v_ibp_median = np.median(np.array(sample_dict["v_ibp"]), axis=0) + pi = np.cumprod(v_ibp_median).tolist() + + from .model import stick_breaking_weights + import jax.numpy as jnp + v_median = np.median(np.array(sample_dict["v"]), axis=0) + cluster_weights = np.array( + stick_breaking_weights(jnp.array(v_median)) + ).tolist() + + population_effects = { + "B": B_median, + "sigma_eps": sigma_eps_median.tolist() if hasattr(sigma_eps_median, 'tolist') else sigma_eps_median, + "df": df_median, + "W": W_median, + "feature_prevalences": pi, + "alpha_ibp": float( + np.median(np.array(sample_dict["alpha_ibp"])) + ), + "cluster_weights": cluster_weights, + "alpha_dp": float( + np.median(np.array(sample_dict["alpha_dp"])) + ), + } + + # Joint MAP assignments + from .postprocessing import assign_ibp_dp_joint + feat_assignments, cluster_assignments = assign_ibp_dp_joint( + sample_dict, data + ) + + pool_entries = {} + for i, p in enumerate(pool_params): + entry = { + "chain": p["chain"], + "tokens": p["tokens"], + "theta_median": p["theta_median"], + "theta_std": p["theta_std"], + "noise_params": p["noise_params"], + "feature_assignments": feat_assignments[i].tolist(), + "cluster_assignment": int(cluster_assignments[i]), + } + pool_entries[p["pool_id"]] = entry + + elif is_ibp or is_ibp_ste: + model_name = "hierarchical_student_t_ibp" + sigma_eps_structure = "scalar" + + W_median = np.median(np.array(sample_dict["W"]), axis=0).tolist() + v_ibp_median = np.median(np.array(sample_dict["v_ibp"]), axis=0) + pi = np.cumprod(v_ibp_median).tolist() + + population_effects = { + "B": B_median, + "sigma_eps": float(sigma_eps_median), + "df": df_median, + "W": W_median, + "feature_prevalences": pi, + "alpha_ibp": float( + np.median(np.array(sample_dict["alpha_ibp"])) + ), + } + + # Per-pool feature assignments + if is_ibp_ste: + # Legacy STE path: threshold z_logit + z_logit = np.array(sample_dict["z_logit"]) + z_logit_median = np.median(z_logit, axis=0) + feature_assignments = (z_logit_median > 0).astype(int).tolist() + else: + # Marginalized path: MAP assignments from data + from .postprocessing import assign_ibp_features + feature_assignments = assign_ibp_features( + sample_dict, data + ).tolist() + + pool_entries = {} + for i, p in enumerate(pool_params): + entry = { + "chain": p["chain"], + "tokens": p["tokens"], + "theta_median": p["theta_median"], + "theta_std": p["theta_std"], + "noise_params": p["noise_params"], + "feature_assignments": feature_assignments[i], + } + pool_entries[p["pool_id"]] = entry + + elif is_dp: + model_name = "hierarchical_student_t_dp_sigma" + sigma_eps_structure = "dp_mixture" + else: + model_name = "unified_hierarchical_student_t" + sigma_eps_structure = "per_tier" + + if not is_ibp and not is_ibp_dp: + sigma_theta_median = np.median( + np.array(sample_dict["sigma_theta"]), axis=0 + ).tolist() + + # Correlation matrix + L_Omega = np.array(sample_dict["L_Omega"]) + Omega = np.einsum("sij,skj->sik", L_Omega, L_Omega) + Omega_median = np.median(Omega, axis=0).tolist() + + population_effects = { + "B": B_median, + "sigma_theta": sigma_theta_median, + "sigma_eps": sigma_eps_median.tolist() if hasattr(sigma_eps_median, 'tolist') else sigma_eps_median, + "df": df_median, + "correlation_matrix": Omega_median, + } + + if is_dp: + from .model import stick_breaking_weights + import jax.numpy as jnp + v_median = np.median(np.array(sample_dict["v"]), axis=0) + w = stick_breaking_weights(jnp.array(v_median)) + population_effects["cluster_weights"] = np.array(w).tolist() + population_effects["alpha_dp"] = float( + np.median(np.array(sample_dict["alpha_dp"])) + ) + + pool_entries = { + p["pool_id"]: { + "chain": p["chain"], + "tokens": p["tokens"], + "theta_median": p["theta_median"], + "theta_std": p["theta_std"], + "noise_params": p["noise_params"], + } + for p in pool_params + } + + output = { + "model": model_name, + "model_spec": { + "K_coeff": K_COEFF, + "K_cov": data["K_cov"], + "coeff_names": COEFF_NAMES, + "covariate_names": data["covariate_names"], + "likelihood": "StudentT", + "tvl_lag": "log_tvl_lag1", + "sigma_eps_structure": sigma_eps_structure, + }, + "inference": inference_config, + "population_effects": population_effects, + "convergence": convergence, + "n_pools": len(pool_params), + "n_obs": len(data["y_obs"]), + "pools": pool_entries, + } + + os.makedirs(os.path.dirname(output_path) or ".", exist_ok=True) + with open(output_path, "w") as f: + json.dump(output, f, indent=2, default=str) + print(f" Wrote {len(pool_params)} pool params -> {output_path}") + + +def _generate_structural_output(pool_params, sample_dict, data, convergence, + output_path, inference_config): + """Write structural model JSON output (LVR arb + hierarchical noise).""" + alpha_0 = float(np.median(np.array(sample_dict["alpha_0"]))) + alpha_chain = np.median(np.array(sample_dict["alpha_chain"]), axis=0).tolist() + alpha_tier = np.median(np.array(sample_dict["alpha_tier"]), axis=0).tolist() + alpha_tvl = float(np.median(np.array(sample_dict["alpha_tvl"]))) + + B_median = np.median(np.array(sample_dict["B"]), axis=0).tolist() + sigma_theta_median = np.median( + np.array(sample_dict["sigma_theta"]), axis=0 + ).tolist() + + # Correlation matrix + L_Omega = np.array(sample_dict["L_Omega"]) + Omega = np.einsum("sij,skj->sik", L_Omega, L_Omega) + Omega_median = np.median(Omega, axis=0).tolist() + + df_median = float(np.median(np.array(sample_dict["df"]))) + sigma_eps_median = np.median( + np.array(sample_dict["sigma_eps"]), axis=0 + ).tolist() + + population_effects = { + "alpha_0": alpha_0, + "alpha_chain": alpha_chain, + "alpha_tier": alpha_tier, + "alpha_tvl": alpha_tvl, + "B": B_median, + "sigma_theta": sigma_theta_median, + "correlation_matrix": Omega_median, + "df": df_median, + "sigma_eps": sigma_eps_median, + } + + pool_entries = {} + for p in pool_params: + pool_entries[p["pool_id"]] = { + "chain": p["chain"], + "tokens": p["tokens"], + "arb_frequency": p["arb_frequency"], + "noise_params": p["noise_params"], + } + + output = { + "model": "structural_mixture", + "model_spec": { + "K_obs_coeff": K_OBS_COEFF, + "obs_coeff_names": OBS_COEFF_NAMES, + "K_cov": data["K_cov"], + "covariate_names": data["covariate_names"], + "likelihood": "StudentT", + }, + "inference": inference_config, + "population_effects": population_effects, + "convergence": convergence, + "n_pools": len(pool_params), + "n_obs": len(data["y_obs"]), + "pools": pool_entries, + } + + os.makedirs(os.path.dirname(output_path) or ".", exist_ok=True) + with open(output_path, "w") as f: + json.dump(output, f, indent=2, default=str) + print(f" Wrote {len(pool_params)} pool params -> {output_path}") + + +def _save_sample_cache(samples, data, cache_dir): + """Cache posterior samples and data arrays for --predict reuse.""" + if hasattr(samples, "get_samples"): + sample_dict = samples.get_samples() + else: + sample_dict = samples + + os.makedirs(cache_dir, exist_ok=True) + + # Save only the samples needed for prediction and diagnostics. + # Skip "y" (S x N_obs, can be >1GB) and "theta" (S x N_pools x K, + # reconstructible from B, eta, sigma_theta, L_Omega). + skip_keys = {"y", "theta"} + sample_cache = os.path.join(cache_dir, "unified_samples.npz") + np.savez_compressed( + sample_cache, + **{k: np.array(v) for k, v in sample_dict.items() + if k not in skip_keys}, + ) + + # Data arrays for predict + data_cache = os.path.join(cache_dir, "unified_data.json") + cache_data = { + "pool_ids": data["pool_ids"], + "covariate_names": data["covariate_names"], + "K_cov": data["K_cov"], + "N_pools": data["N_pools"], + "ref_chain": data["ref_chain"], + "ref_tier_a": data["ref_tier_a"], + "ref_tier_b": data["ref_tier_b"], + "chains": data["chains"], + } + with open(data_cache, "w") as f: + json.dump(cache_data, f, indent=2) + + print(f" Cached samples -> {sample_cache}") + print(f" Cached data metadata -> {data_cache}") diff --git a/quantammsim/noise_calibration/plotting.py b/quantammsim/noise_calibration/plotting.py new file mode 100644 index 0000000..727443b --- /dev/null +++ b/quantammsim/noise_calibration/plotting.py @@ -0,0 +1,335 @@ +"""Diagnostic plots for noise calibration.""" + +import os + +import numpy as np +import pandas as pd + +from .constants import K_COEFF, COEFF_NAMES +from .inference import _get_theta_samples + + +def plot_diagnostics(samples, data, output_dir, elbo_losses=None, + mcmc=None, prior_samples=None): + """Generate up to 9 diagnostic plots.""" + import matplotlib + matplotlib.use("Agg") + import matplotlib.pyplot as plt + + os.makedirs(output_dir, exist_ok=True) + + if hasattr(samples, "get_samples"): + sample_dict = samples.get_samples() + else: + sample_dict = samples + + theta_samples = _get_theta_samples( + sample_dict, np.array(data["X_pool"]), data=data + ) # (S, N_pools, K_coeff) + theta_median = np.median(theta_samples, axis=0) + + pool_idx = data["pool_idx"] + x_obs = data["x_obs"] + y_obs = data["y_obs"] + pool_meta = data["pool_meta"] + pool_ids = data["pool_ids"] + + # --- 1. Prior predictive check --- + if prior_samples is not None: + y_prior = prior_samples.get("y", None) + if y_prior is not None: + fig, ax = plt.subplots(figsize=(10, 5)) + # Flatten a subsample of prior draws + y_prior_flat = y_prior.flatten() + # Clip for display + clip_lo, clip_hi = np.percentile(y_prior_flat, [0.5, 99.5]) + y_prior_clipped = y_prior_flat[ + (y_prior_flat >= clip_lo) & (y_prior_flat <= clip_hi) + ] + ax.hist(y_prior_clipped, bins=100, alpha=0.5, density=True, + color="steelblue", label="Prior predictive") + ax.hist(y_obs, bins=100, alpha=0.5, density=True, + color="coral", label="Observed") + ax.set_xlabel("log(volume)") + ax.set_ylabel("Density") + ax.set_title("Prior predictive check: log-volume") + ax.legend() + plt.tight_layout() + path = os.path.join(output_dir, "prior_predictive.png") + plt.savefig(path, dpi=150, bbox_inches="tight") + plt.close() + print(f" Saved: {path}") + + # --- 2. ELBO loss curve (SVI only) --- + if elbo_losses is not None: + fig, axes = plt.subplots(1, 2, figsize=(14, 5)) + + ax = axes[0] + ax.plot(elbo_losses, alpha=0.3, color="steelblue", linewidth=0.5) + # Smoothed + window = min(100, len(elbo_losses) // 10) + if window > 1: + smoothed = pd.Series(elbo_losses).rolling(window).mean().values + ax.plot(smoothed, color="red", linewidth=1.5, label=f"Rolling {window}") + ax.legend() + ax.set_xlabel("Step") + ax.set_ylabel("ELBO loss") + ax.set_title("ELBO convergence") + + ax = axes[1] + # Last 20% of training + start = len(elbo_losses) * 4 // 5 + ax.plot(range(start, len(elbo_losses)), elbo_losses[start:], + color="steelblue", linewidth=0.8) + ax.set_xlabel("Step") + ax.set_ylabel("ELBO loss") + ax.set_title("ELBO convergence (last 20%)") + + plt.tight_layout() + path = os.path.join(output_dir, "elbo_convergence.png") + plt.savefig(path, dpi=150, bbox_inches="tight") + plt.close() + print(f" Saved: {path}") + + # --- 3. Trace plots (NUTS only) --- + if mcmc is not None: + try: + import arviz as az + idata = az.from_numpyro(mcmc) + var_names = ["sigma_theta", "sigma_eps", "df"] + available = [v for v in var_names if v in idata.posterior] + if available: + axes = az.plot_trace(idata, var_names=available, compact=True) + fig = axes.ravel()[0].figure + fig.set_size_inches(14, 3 * len(available)) + path = os.path.join(output_dir, "trace_plots.png") + fig.savefig(path, dpi=150, bbox_inches="tight") + plt.close(fig) + print(f" Saved: {path}") + except Exception as e: + print(f" WARNING: Trace plots failed: {e}") + + # --- 4. Posterior predictive: predicted vs observed --- + # Compute y_pred and r2 here; r2 is reused in plot 9 (model summary). + theta_obs = theta_median[pool_idx] + y_pred = np.sum(theta_obs * x_obs, axis=1) + r2 = 1 - np.var(y_obs - y_pred) / np.var(y_obs) + + fig, axes = plt.subplots(1, 2, figsize=(14, 6)) + + ax = axes[0] + ax.scatter(y_obs, y_pred, alpha=0.1, s=4, color="steelblue") + lims = [min(y_obs.min(), y_pred.min()), max(y_obs.max(), y_pred.max())] + ax.plot(lims, lims, "r--", linewidth=1) + ax.set_xlabel("Observed log(volume)") + ax.set_ylabel("Predicted log(volume)") + ax.set_title("Posterior predictive check") + ax.text(0.05, 0.95, f"R² = {r2:.3f}", transform=ax.transAxes, + fontsize=11, verticalalignment="top") + + ax = axes[1] + residuals = y_obs - y_pred + ax.hist(residuals, bins=60, color="steelblue", edgecolor="white", alpha=0.8) + ax.axvline(0, color="red", linestyle="--") + ax.set_xlabel("Residual") + + sigma_eps_samples = np.array(sample_dict.get("sigma_eps", [0])) + if sigma_eps_samples.ndim > 1: + sigma_str = ", ".join(f"{np.median(sigma_eps_samples[:, i]):.2f}" + for i in range(sigma_eps_samples.shape[1])) + else: + sigma_str = f"{np.median(sigma_eps_samples):.2f}" + ax.set_title(f"Residuals (sigma_eps ~ [{sigma_str}])") + + plt.tight_layout() + path = os.path.join(output_dir, "posterior_predictive.png") + plt.savefig(path, dpi=150, bbox_inches="tight") + plt.close() + print(f" Saved: {path}") + + # --- 5. Per-pool b_c by chain/tier --- + b_tvl_all = theta_median[:, 1] + + fig, axes = plt.subplots(1, 2, figsize=(14, 6)) + + ax = axes[0] + chains_present = sorted(pool_meta["chain"].unique()) + chain_data = [] + chain_labels = [] + for c in chains_present: + mask = pool_meta["chain"].values == c + if mask.sum() > 0: + chain_data.append(b_tvl_all[mask]) + chain_labels.append(f"{c}\n(n={mask.sum()})") + if chain_data: + ax.boxplot(chain_data, tick_labels=chain_labels, vert=True) + ax.axhline(1.0, color="red", linestyle="--", linewidth=0.8, alpha=0.6) + ax.set_ylabel("Per-pool b_c (TVL elasticity)") + ax.set_title("TVL elasticity by chain") + + ax = axes[1] + tier_a_vals = pool_meta["tier_A"].values.astype(int) + tier_labels_map = {0: "Blue-chip", 1: "Mid-cap", 2: "Long-tail"} + tier_data = [] + tier_labels = [] + for t in [0, 1, 2]: + mask = tier_a_vals == t + if mask.sum() > 0: + tier_data.append(b_tvl_all[mask]) + tier_labels.append(f"{tier_labels_map[t]}\n(n={mask.sum()})") + if tier_data: + ax.boxplot(tier_data, tick_labels=tier_labels, vert=True) + ax.axhline(1.0, color="red", linestyle="--", linewidth=0.8, alpha=0.6) + ax.set_ylabel("Per-pool b_c (TVL elasticity)") + ax.set_title("TVL elasticity by token tier (best token)") + + plt.tight_layout() + path = os.path.join(output_dir, "per_pool_b_c.png") + plt.savefig(path, dpi=150, bbox_inches="tight") + plt.close() + print(f" Saved: {path}") + + # --- 6. Correlation matrix posterior --- + L_Omega_samples = np.array(sample_dict["L_Omega"]) # (S, K, K) + Omega_samples = np.einsum("sij,skj->sik", L_Omega_samples, L_Omega_samples) + Omega_median = np.median(Omega_samples, axis=0) + + fig, ax = plt.subplots(figsize=(7, 6)) + im = ax.imshow(Omega_median, vmin=-1, vmax=1, cmap="RdBu_r") + ax.set_xticks(range(K_COEFF)) + ax.set_yticks(range(K_COEFF)) + ax.set_xticklabels(COEFF_NAMES, rotation=45, ha="right") + ax.set_yticklabels(COEFF_NAMES) + for i in range(K_COEFF): + for j in range(K_COEFF): + ax.text(j, i, f"{Omega_median[i, j]:.2f}", ha="center", + va="center", fontsize=10, + color="white" if abs(Omega_median[i, j]) > 0.5 else "black") + plt.colorbar(im, ax=ax, shrink=0.8) + ax.set_title("Posterior median correlation matrix (Omega)") + plt.tight_layout() + path = os.path.join(output_dir, "correlation_matrix.png") + plt.savefig(path, dpi=150, bbox_inches="tight") + plt.close() + print(f" Saved: {path}") + + # --- 7. Shrinkage plot: OLS b_c vs hierarchical b_c --- + ols_b_c = np.zeros(len(pool_ids)) + for i, pid in enumerate(pool_ids): + mask = pool_idx == i + if mask.sum() < 5: + ols_b_c[i] = np.nan + continue + x_i = x_obs[mask] + y_i = y_obs[mask] + try: + beta, _, _, _ = np.linalg.lstsq(x_i, y_i, rcond=None) + ols_b_c[i] = beta[1] # TVL coefficient + except np.linalg.LinAlgError: + ols_b_c[i] = np.nan + + hier_b_c = theta_median[:, 1] + valid = np.isfinite(ols_b_c) + + if valid.sum() > 2: + fig, ax = plt.subplots(figsize=(8, 8)) + ax.scatter(ols_b_c[valid], hier_b_c[valid], alpha=0.6, s=20, + color="steelblue") + + pop_b_c = np.median(hier_b_c) + ax.axhline(pop_b_c, color="red", linestyle="--", linewidth=0.8, + label=f"Population median = {pop_b_c:.3f}") + + lims = [min(np.nanmin(ols_b_c[valid]), hier_b_c[valid].min()) - 0.2, + max(np.nanmax(ols_b_c[valid]), hier_b_c[valid].max()) + 0.2] + ax.plot(lims, lims, "k:", linewidth=0.8, alpha=0.5) + ax.set_xlabel("Per-pool OLS b_c (lagged TVL)") + ax.set_ylabel("Hierarchical posterior median b_c") + ax.set_title("Shrinkage: OLS vs hierarchical TVL elasticity") + ax.legend() + plt.tight_layout() + path = os.path.join(output_dir, "shrinkage_b_c.png") + plt.savefig(path, dpi=150, bbox_inches="tight") + plt.close() + print(f" Saved: {path}") + + # --- 8. beta_tvl vs beta_vol scatter colored by chain --- + fig, ax = plt.subplots(figsize=(10, 7)) + pool_id_to_chain = dict(zip(pool_meta["pool_id"], pool_meta["chain"])) + chain_colors = {} + cmap = plt.cm.tab10 + unique_chains = sorted(pool_meta["chain"].unique()) + for i, c in enumerate(unique_chains): + chain_colors[c] = cmap(i % 10) + + beta_tvl_arr = theta_median[:, 1] + beta_vol_arr = theta_median[:, 2] + for i, pid in enumerate(pool_ids): + c = pool_id_to_chain.get(pid, "?") + ax.scatter(beta_tvl_arr[i], beta_vol_arr[i], + color=chain_colors.get(c, "gray"), alpha=0.6, s=20, + edgecolors="white", linewidths=0.3) + + from matplotlib.lines import Line2D + handles = [Line2D([0], [0], marker="o", color="w", + markerfacecolor=chain_colors[c], markersize=8, + label=c) + for c in unique_chains if c in chain_colors] + ax.legend(handles=handles, fontsize=8, loc="best") + ax.set_xlabel("b_tvl (TVL elasticity)") + ax.set_ylabel("b_sigma (volatility sensitivity)") + ax.set_title("Pool-specific coefficients by chain") + ax.axhline(0, color="gray", linewidth=0.5, linestyle="--") + ax.axvline(0, color="gray", linewidth=0.5, linestyle="--") + plt.tight_layout() + path = os.path.join(output_dir, "beta_tvl_vs_beta_vol.png") + plt.savefig(path, dpi=150, bbox_inches="tight") + plt.close() + print(f" Saved: {path}") + + # --- 9. Model summary panel --- + B_samples = np.array(sample_dict["B"]) + B_median = np.median(B_samples, axis=0) # (K_coeff, K_cov) + sigma_theta_med = np.median(np.array(sample_dict["sigma_theta"]), axis=0) + df_med = np.median(np.array(sample_dict["df"])) + sigma_eps_med = np.median(np.array(sample_dict["sigma_eps"]), axis=0) + + col_names = data["covariate_names"] + + fig, ax = plt.subplots(figsize=(12, 8)) + ax.axis("off") + + summary = "Group-level regression B (posterior median):\n" + header = f" {'covariate':<20s}" + for cn in COEFF_NAMES: + header += f" {cn:>10s}" + summary += header + "\n" + summary += " " + "-" * (20 + 11 * K_COEFF) + "\n" + for j, name in enumerate(col_names): + line = f" {name:<20s}" + for k in range(K_COEFF): + line += f" {B_median[k, j]:>10.3f}" + summary += line + "\n" + + summary += f"\nsigma_theta: [{', '.join(f'{v:.3f}' for v in sigma_theta_med)}]\n" + summary += f"\nCorrelation matrix (Omega):\n" + for i in range(K_COEFF): + row = " [" + " ".join(f"{Omega_median[i, j]:>6.3f}" + for j in range(K_COEFF)) + "]\n" + summary += row + + tier_names = ["blue-chip", "mid-cap", "long-tail"] + sigma_eps_str = ", ".join(f"{tier_names[i]}={sigma_eps_med[i]:.3f}" + for i in range(len(sigma_eps_med))) + summary += f"\nsigma_eps: [{sigma_eps_str}]\n" + summary += f"df (Student-t): {df_med:.1f}\n" + summary += f"R^2: {r2:.3f}\n" + + ax.text(0.02, 0.98, summary, transform=ax.transAxes, + fontsize=7, verticalalignment="top", fontfamily="monospace") + ax.set_title("Model Summary") + plt.tight_layout() + path = os.path.join(output_dir, "model_summary.png") + plt.savefig(path, dpi=150, bbox_inches="tight") + plt.close() + print(f" Saved: {path}") diff --git a/quantammsim/noise_calibration/postprocessing.py b/quantammsim/noise_calibration/postprocessing.py new file mode 100644 index 0000000..190f07c --- /dev/null +++ b/quantammsim/noise_calibration/postprocessing.py @@ -0,0 +1,659 @@ +"""Post-processing: extract params, predict, convergence, prior predictive.""" + +import numpy as np + +from .constants import K_COEFF, COEFF_NAMES, K_OBS_COEFF, OBS_COEFF_NAMES +from .token_classification import classify_token_tier +from .covariate_encoding import _tier_pair_idx +from .inference import _get_theta_samples, _build_model_kwargs +from .model import noise_model + + +def extract_noise_params(samples, data, use_median=True) -> list: + """Extract per-pool noise params from posterior samples. + + Handles both MCMC.get_samples() and SVI samples dict. + Applies weekend absorption: b_0_eff = b_0_raw + b_weekend * (2/7). + """ + # Get theta samples + if hasattr(samples, "get_samples"): + # MCMC object + sample_dict = samples.get_samples() + else: + sample_dict = samples + + theta_samples = _get_theta_samples( + sample_dict, np.array(data["X_pool"]), data=data + ) # (S, N_pools, K_coeff) + + agg_fn = np.median if use_median else np.mean + theta_agg = agg_fn(theta_samples, axis=0) # (N_pools, K_coeff) + theta_std = np.std(theta_samples, axis=0) + + pool_ids = data["pool_ids"] + pool_meta = data["pool_meta"] + + results = [] + for i, pool_id in enumerate(pool_ids): + meta = pool_meta.iloc[i] + b_0_raw, b_tvl, b_sigma, b_weekend = theta_agg[i] + std_vals = theta_std[i] + + # Weekend absorption: simulator has no weekend indicator, + # so fold the expected weekend effect into the intercept. + b_0_effective = b_0_raw + b_weekend * (2.0 / 7.0) + + tokens = meta["tokens"] + if isinstance(tokens, str): + tokens = tokens.split(",") + + results.append({ + "pool_id": pool_id, + "chain": str(meta["chain"]), + "tokens": tokens, + "theta_median": [float(x) for x in theta_agg[i]], + "theta_std": [float(x) for x in std_vals], + "b_weekend": float(b_weekend), + "noise_params": { + "b_0": float(b_0_effective), + "b_sigma": float(b_sigma), + "b_c": float(b_tvl), + "b_weekend": float(b_weekend), + "base_fee": float(meta["swap_fee"]), + }, + }) + + return results + + +def predict_new_pool(samples, data, chain: str, tokens: list, + fee: float, feature_assignments=None) -> dict: + """Predict noise params for an unseen pool using population effects. + + Constructs z_new, computes mu_new = B @ z_new across all posterior samples, + returns point estimate + 90% credible intervals with weekend absorption. + """ + if hasattr(samples, "get_samples"): + sample_dict = samples.get_samples() + else: + sample_dict = samples + + # Build z_new using data-driven column names + col_names = data["covariate_names"] + z_new = np.zeros(len(col_names), dtype=np.float64) + + tiers = sorted([classify_token_tier(t) for t in tokens]) + tier_a = str(tiers[0]) + tier_b = str(tiers[1]) if len(tiers) > 1 else tier_a + + for i, name in enumerate(col_names): + if name == "intercept": + z_new[i] = 1.0 + elif name == "log_fee": + z_new[i] = np.log(max(fee, 1e-6)) + elif name == f"chain_{chain}": + z_new[i] = 1.0 + elif name == f"tier_A_{tier_a}": + z_new[i] = 1.0 + elif name == f"tier_B_{tier_b}": + z_new[i] = 1.0 + + # mu_new = B @ z_new across all posterior samples + B_samples = np.array(sample_dict["B"]) # (S, K_coeff, K_cov) + mu_samples = np.einsum("skd,d->sk", B_samples, z_new) # (S, K_coeff) + + # IBP path: add feature effects + is_ibp = "W" in sample_dict + if is_ibp: + W_samples = np.array(sample_dict["W"]) # (S, K_features, K_coeff) + if feature_assignments is not None: + # User-specified binary features + Z = np.array(feature_assignments) # (K_features,) + feature_effect = np.einsum("skj,k->sj", W_samples, Z) + prediction_source = "ibp_user_features" + else: + # Marginal: weight by prevalences pi = cumprod(v_ibp) + v_ibp = np.array(sample_dict["v_ibp"]) # (S, K_features) + pi = np.cumprod(v_ibp, axis=1) # (S, K_features) + feature_effect = np.einsum("skj,sk->sj", W_samples, pi) + prediction_source = "ibp_marginal" + mu_samples = mu_samples + feature_effect + else: + prediction_source = "population_level" + + mu_median = np.median(mu_samples, axis=0) + mu_q05 = np.percentile(mu_samples, 5, axis=0) + mu_q95 = np.percentile(mu_samples, 95, axis=0) + + # Weekend absorption + b_0_raw, b_tvl, b_sigma, b_weekend = mu_median + b_0_effective = b_0_raw + b_weekend * (2.0 / 7.0) + + result = { + "chain": chain, + "tokens": tokens, + "fee": fee, + "prediction_source": prediction_source, + "noise_params": { + "b_0": float(b_0_effective), + "b_sigma": float(b_sigma), + "b_c": float(b_tvl), + "b_weekend": float(b_weekend), + "base_fee": float(fee), + }, + "credible_intervals_90": { + name: { + "median": float(mu_median[k]), + "q05": float(mu_q05[k]), + "q95": float(mu_q95[k]), + } + for k, name in enumerate(COEFF_NAMES) + }, + } + + print(f"\n Predicted noise_params for {chain} {tokens} (fee={fee}):") + for name, ci in result["credible_intervals_90"].items(): + print(f" {name:12s}: {ci['median']:+.3f} " + f"[{ci['q05']:+.3f}, {ci['q95']:+.3f}]") + print(f"\n Effective b_0 (weekend-absorbed): {b_0_effective:.3f}") + + return result + + +def check_convergence(mcmc_or_losses, method="nuts") -> dict: + """Compute convergence diagnostics. + + For NUTS: R-hat, ESS, divergences. + For SVI: final ELBO, ELBO stability. + """ + if method == "svi": + losses = np.array(mcmc_or_losses) + return { + "method": "svi", + "final_elbo": float(losses[-1]), + "elbo_last_100_std": float(np.std(losses[-100:])), + "elbo_last_100_mean": float(np.mean(losses[-100:])), + } + + # NUTS diagnostics + import arviz as az + + mcmc = mcmc_or_losses + idata = az.from_numpyro(mcmc) + + n_chains = idata.posterior.sizes.get("chain", 1) + + rhat_max = float("nan") + if n_chains >= 2: + rhat = az.rhat(idata) + rhat_vals = [] + for var in rhat.data_vars: + if var == "theta": + continue + vals = rhat[var].values + rhat_vals.extend(vals.flatten()) + rhat_max = float(np.nanmax(rhat_vals)) if rhat_vals else float("nan") + + ess = az.ess(idata) + ess_vals = [] + for var in ess.data_vars: + if var == "theta": + continue + vals = ess[var].values + ess_vals.extend(vals.flatten()) + ess_min = float(np.nanmin(ess_vals)) if ess_vals else float("nan") + + divergences = int(idata.sample_stats["diverging"].sum().values) + + print(f"\n Convergence diagnostics:") + if n_chains >= 2: + print(f" R-hat max: {rhat_max:.4f} " + f"{'OK' if rhat_max < 1.05 else 'WARNING'}") + else: + print(f" R-hat max: N/A (need >= 2 chains)") + print(f" ESS min: {ess_min:.0f} " + f"{'OK' if ess_min > 400 else 'WARNING'}") + print(f" Divergences: {divergences} " + f"{'OK' if divergences == 0 else 'WARNING'}") + + return { + "method": "nuts", + "r_hat_max": rhat_max, + "ess_min": ess_min, + "divergences": divergences, + } + + +def assign_dp_clusters(samples, data) -> np.ndarray: + """Compute posterior MAP cluster assignments for DP mixture model. + + Uses median posterior samples for v->w, sigma_eps, df, theta to compute + per-pool-per-cluster log-likelihoods, then returns argmax assignments. + """ + from scipy.special import logsumexp + from .model import stick_breaking_weights + + if hasattr(samples, "get_samples"): + sample_dict = samples.get_samples() + else: + sample_dict = samples + + # Median posterior parameters + v_med = np.median(np.array(sample_dict["v"]), axis=0) + sigma_eps_med = np.median(np.array(sample_dict["sigma_eps"]), axis=0) + df_med = float(np.median(np.array(sample_dict["df"]))) + + # Compute w from v via stick-breaking (using numpy) + import jax.numpy as jnp + w = np.array(stick_breaking_weights(jnp.array(v_med))) + + # Reconstruct theta + theta_samples = _get_theta_samples( + sample_dict, np.array(data["X_pool"]), data=data + ) + theta_med = np.median(theta_samples, axis=0) # (N_pools, K_coeff) + + # Per-observation predicted means + pool_idx = np.array(data["pool_idx"]) + x_obs = np.array(data["x_obs"]) + y_obs = np.array(data["y_obs"]) + N_pools = data["N_pools"] + K_clusters = len(sigma_eps_med) + + theta_obs = theta_med[pool_idx] + mu_obs = np.sum(theta_obs * x_obs, axis=1) # (N_obs,) + + # Log-likelihood per observation per cluster + from scipy.stats import t as t_dist + log_lik_per_k = np.zeros((len(y_obs), K_clusters)) + for k in range(K_clusters): + log_lik_per_k[:, k] = t_dist.logpdf( + y_obs, df_med, loc=mu_obs, scale=sigma_eps_med[k] + ) + + # Sum within pools + pool_log_liks = np.zeros((N_pools, K_clusters)) + for i in range(len(y_obs)): + pool_log_liks[pool_idx[i]] += log_lik_per_k[i] + + # Posterior cluster probabilities: log p(z=k|data) = log w_k + sum log p(y|k) + log_posterior = np.log(w + 1e-30)[None, :] + pool_log_liks + # MAP assignment + assignments = np.argmax(log_posterior, axis=1).astype(np.int64) + return assignments + + +def assign_ibp_features(samples, data) -> np.ndarray: + """Compute MAP feature assignments for marginalized IBP model. + + Enumerates all 2^K binary feature configurations per pool, evaluates + per-pool log-posterior (log-prior + log-likelihood), returns argmax + config as (N_pools, K_features) binary ndarray. + + Uses median posterior parameters for B, W, v_ibp, sigma_eps, df. + """ + from scipy.stats import t as t_dist + + if hasattr(samples, "get_samples"): + sample_dict = samples.get_samples() + else: + sample_dict = samples + + B_med = np.median(np.array(sample_dict["B"]), axis=0) # (K_coeff, K_cov) + W_med = np.median(np.array(sample_dict["W"]), axis=0) # (K_features, K_coeff) + v_ibp_med = np.median(np.array(sample_dict["v_ibp"]), axis=0) # (K_features,) + sigma_eps_med = float(np.median(np.array(sample_dict["sigma_eps"]))) + df_med = float(np.median(np.array(sample_dict["df"]))) + + pi = np.cumprod(v_ibp_med) # (K_features,) + K_features = len(pi) + + pool_idx = np.array(data["pool_idx"]) + X_pool = np.array(data["X_pool"]) + x_obs = np.array(data["x_obs"]) + y_obs = np.array(data["y_obs"]) + N_pools = data["N_pools"] + + # Enumerate all 2^K configs + n_configs = 2 ** K_features + configs = ( + (np.arange(n_configs)[:, None] >> np.arange(K_features)[None, :]) & 1 + ).astype(float) # (n_configs, K_features) + + # Log-prior per config + log_pi = np.log(pi + 1e-30) + log_1mpi = np.log(1.0 - pi + 1e-30) + log_prior = configs @ log_pi + (1.0 - configs) @ log_1mpi # (n_configs,) + + # Population mean + mu_pop = X_pool @ B_med.T # (N_pools, K_coeff) + + # Feature effects per config + feature_effects = configs @ W_med # (n_configs, K_coeff) + + # Per-obs means: mu_pop_obs + feature_mu + mu_pop_obs = np.sum(mu_pop[pool_idx] * x_obs, axis=1) # (N_obs,) + feature_mu = x_obs @ feature_effects.T # (N_obs, n_configs) + mu_obs = mu_pop_obs[:, None] + feature_mu # (N_obs, n_configs) + + # Log-likelihood per obs per config + log_lik = t_dist.logpdf( + y_obs[:, None], df_med, loc=mu_obs, scale=sigma_eps_med + ) # (N_obs, n_configs) + + # Sum within pools + pool_log_liks = np.zeros((N_pools, n_configs)) + for i in range(len(y_obs)): + pool_log_liks[pool_idx[i]] += log_lik[i] + + # Posterior = log_prior + pool_log_liks; MAP config per pool + log_posterior = log_prior[None, :] + pool_log_liks # (N_pools, n_configs) + best_config_idx = np.argmax(log_posterior, axis=1) # (N_pools,) + + return configs[best_config_idx].astype(int) # (N_pools, K_features) + + +def assign_ibp_dp_joint(samples, data) -> tuple: + """Compute MAP joint (feature, cluster) assignments for hybrid IBP+DP model. + + Enumerates all (2^K_features × K_clusters) joint configurations per pool, + evaluates joint log-posterior, returns argmax assignments. + + Returns: + (feature_assignments, cluster_assignments): + feature_assignments: (N_pools, K_features) binary ndarray + cluster_assignments: (N_pools,) int ndarray + """ + from scipy.stats import t as t_dist + from .model import stick_breaking_weights + + if hasattr(samples, "get_samples"): + sample_dict = samples.get_samples() + else: + sample_dict = samples + + B_med = np.median(np.array(sample_dict["B"]), axis=0) # (K_coeff, K_cov) + W_med = np.median(np.array(sample_dict["W"]), axis=0) # (K_features, K_coeff) + v_ibp_med = np.median(np.array(sample_dict["v_ibp"]), axis=0) # (K_features,) + v_med = np.median(np.array(sample_dict["v"]), axis=0) # (K_clusters-1,) + sigma_eps_med = np.median(np.array(sample_dict["sigma_eps"]), axis=0) # (K_clusters,) + df_med = float(np.median(np.array(sample_dict["df"]))) + + pi = np.cumprod(v_ibp_med) # (K_features,) + K_features = len(pi) + K_clusters = len(sigma_eps_med) + + import jax.numpy as jnp + w = np.array(stick_breaking_weights(jnp.array(v_med))) + + pool_idx = np.array(data["pool_idx"]) + X_pool = np.array(data["X_pool"]) + x_obs = np.array(data["x_obs"]) + y_obs = np.array(data["y_obs"]) + N_pools = data["N_pools"] + + # Enumerate all 2^K configs + n_configs = 2 ** K_features + configs = ( + (np.arange(n_configs)[:, None] >> np.arange(K_features)[None, :]) & 1 + ).astype(float) # (n_configs, K_features) + + # IBP log-prior per config + log_pi = np.log(pi + 1e-30) + log_1mpi = np.log(1.0 - pi + 1e-30) + log_ibp_prior = configs @ log_pi + (1.0 - configs) @ log_1mpi # (n_configs,) + + # Joint log-prior: IBP config × DP cluster + log_joint_prior = log_ibp_prior[:, None] + np.log(w + 1e-30)[None, :] # (n_configs, K_clusters) + + # Population mean + mu_pop = X_pool @ B_med.T # (N_pools, K_coeff) + feature_effects = configs @ W_med # (n_configs, K_coeff) + + # Per-obs means + mu_pop_obs = np.sum(mu_pop[pool_idx] * x_obs, axis=1) # (N_obs,) + feature_mu = x_obs @ feature_effects.T # (N_obs, n_configs) + mu_obs = mu_pop_obs[:, None] + feature_mu # (N_obs, n_configs) + + # Log-likelihood per obs per config per cluster + log_lik = np.zeros((len(y_obs), n_configs, K_clusters)) + for k in range(K_clusters): + log_lik[:, :, k] = t_dist.logpdf( + y_obs[:, None], df_med, loc=mu_obs, scale=sigma_eps_med[k] + ) + + # Sum within pools + pool_log_liks = np.zeros((N_pools, n_configs, K_clusters)) + for i in range(len(y_obs)): + pool_log_liks[pool_idx[i]] += log_lik[i] + + # Joint posterior: log_joint_prior + pool_log_liks + log_posterior = log_joint_prior[None, :, :] + pool_log_liks # (N_pools, n_configs, K_clusters) + + # Flatten to (N_pools, n_configs * K_clusters), argmax, unravel + flat = log_posterior.reshape(N_pools, -1) + best_flat_idx = np.argmax(flat, axis=1) + best_config_idx = best_flat_idx // K_clusters + best_cluster_idx = best_flat_idx % K_clusters + + feature_assignments = configs[best_config_idx].astype(int) # (N_pools, K_features) + cluster_assignments = best_cluster_idx.astype(np.int64) # (N_pools,) + + return feature_assignments, cluster_assignments + + +def extract_structural_params(samples, data, use_median=True) -> list: + """Extract per-pool arb frequency and noise coefficients from structural model. + + Parameters + ---------- + samples : dict + Posterior samples from SVI/NUTS with structural_noise_model. + data : dict + Output of encode_covariates_structural(). + + Returns + ------- + list of dict + Per-pool dicts with: pool_id, chain, tokens, arb_frequency, noise_params. + """ + if hasattr(samples, "get_samples"): + sample_dict = samples.get_samples() + else: + sample_dict = samples + + agg_fn = np.median if use_median else np.mean + + # Cadence parameters + alpha_0 = agg_fn(np.array(sample_dict["alpha_0"])) + alpha_chain = agg_fn(np.array(sample_dict["alpha_chain"]), axis=0) + alpha_tier = agg_fn(np.array(sample_dict["alpha_tier"]), axis=0) + alpha_tvl = agg_fn(np.array(sample_dict["alpha_tvl"])) + + # Per-pool theta from hierarchical model + # theta = X_pool @ B.T + eta @ L_Sigma.T + B = agg_fn(np.array(sample_dict["B"]), axis=0) + eta = agg_fn(np.array(sample_dict["eta"]), axis=0) + sigma_theta = agg_fn(np.array(sample_dict["sigma_theta"]), axis=0) + L_Omega = agg_fn(np.array(sample_dict["L_Omega"]), axis=0) + + X_pool = np.array(data["X_pool"]) + L_Sigma = np.diag(sigma_theta) @ L_Omega + theta = X_pool @ B.T + eta @ L_Sigma.T # (N_pools, K_obs_coeff) + + chain_idx = np.array(data["chain_idx"]) + tier_idx = np.array(data["tier_idx"]) + pool_meta = data["pool_meta"] + pool_ids = data["pool_ids"] + + # Per-pool cadence + padded_chain = np.concatenate([[0.0], alpha_chain]) + padded_tier = np.concatenate([[0.0], alpha_tier]) + + # Per-pool log_tvl (median across observations) + pool_idx_arr = np.array(data["pool_idx"]) + lag_log_tvl = np.array(data["lag_log_tvl"]) + N_pools = data["N_pools"] + + pool_tvl_median = np.zeros(N_pools) + for p in range(N_pools): + mask = pool_idx_arr == p + if mask.any(): + pool_tvl_median[p] = np.median(lag_log_tvl[mask]) + + results = [] + for i, pool_id in enumerate(pool_ids): + meta = pool_meta.iloc[i] + log_cadence = ( + alpha_0 + + padded_chain[chain_idx[i]] + + padded_tier[tier_idx[i]] + + alpha_tvl * pool_tvl_median[i] + ) + cadence = np.exp(np.clip(log_cadence, -2.0, 6.0)) + arb_freq = int(np.clip(np.round(cadence), 1, 60)) + + noise_coeffs = { + name: float(theta[i, k]) + for k, name in enumerate(OBS_COEFF_NAMES) + } + + tokens = meta["tokens"] + if isinstance(tokens, str): + tokens = tokens.split(",") + + results.append({ + "pool_id": pool_id, + "chain": str(meta["chain"]), + "tokens": tokens, + "arb_frequency": arb_freq, + "noise_params": noise_coeffs, + }) + + return results + + +def predict_new_pool_structural( + samples, data, chain: str, tokens: list, fee: float, tvl_est: float, +) -> dict: + """Predict cadence and noise coefficients for a hypothetical pool. + + Uses the structural model's arb cadence parameters and hierarchical B + regression for noise (population mean, no pool-specific random effect). + + Parameters + ---------- + samples : dict + Posterior samples from structural_noise_model. + data : dict + Output of encode_covariates_structural(). + chain : str + Chain name. + tokens : list of str + Token symbols. + fee : float + Swap fee (fraction). + tvl_est : float + Estimated TVL in USD. + """ + if hasattr(samples, "get_samples"): + sample_dict = samples.get_samples() + else: + sample_dict = samples + + agg_fn = np.median + + # Cadence parameters + alpha_0 = agg_fn(np.array(sample_dict["alpha_0"])) + alpha_chain = agg_fn(np.array(sample_dict["alpha_chain"]), axis=0) + alpha_tier = agg_fn(np.array(sample_dict["alpha_tier"]), axis=0) + alpha_tvl = agg_fn(np.array(sample_dict["alpha_tvl"])) + + # Hierarchical B for noise population mean + B = agg_fn(np.array(sample_dict["B"]), axis=0) + + # Construct chain and tier indices for the new pool + chains = data["chains"] + chain_to_idx = {c: i for i, c in enumerate(chains)} + c_idx = chain_to_idx.get(chain, 0) # fallback to reference + + tiers = sorted([classify_token_tier(t) for t in tokens]) + tier_a = tiers[0] + tier_b = tiers[1] if len(tiers) > 1 else tier_a + t_idx = _tier_pair_idx(tier_a, tier_b) + + padded_chain = np.concatenate([[0.0], alpha_chain]) + padded_tier = np.concatenate([[0.0], alpha_tier]) + + log_tvl = np.log(max(tvl_est, 1.0)) + log_cadence = ( + alpha_0 + + padded_chain[c_idx] + + padded_tier[t_idx] + + alpha_tvl * log_tvl + ) + cadence = np.exp(np.clip(log_cadence, -2.0, 6.0)) + arb_freq = int(np.clip(np.round(cadence), 1, 60)) + + # Construct X_pool_new for hierarchical regression + col_names = data["covariate_names"] + z_new = np.zeros(len(col_names), dtype=np.float64) + tier_a_str = str(tier_a) + tier_b_str = str(tier_b) + + for i, name in enumerate(col_names): + if name == "intercept": + z_new[i] = 1.0 + elif name == "log_fee": + z_new[i] = np.log(max(fee, 1e-6)) + elif name == f"chain_{chain}": + z_new[i] = 1.0 + elif name == f"tier_A_{tier_a_str}": + z_new[i] = 1.0 + elif name == f"tier_B_{tier_b_str}": + z_new[i] = 1.0 + + # Population mean theta for new pool (no random effect) + theta_new = z_new @ B.T # (K_obs_coeff,) + + noise_coeffs = { + name: float(theta_new[k]) + for k, name in enumerate(OBS_COEFF_NAMES) + } + + return { + "chain": chain, + "tokens": tokens, + "fee": fee, + "tvl_est": tvl_est, + "arb_frequency": arb_freq, + "noise_params": noise_coeffs, + } + + +def run_prior_predictive(data, num_samples=500, model_fn=None) -> dict: + """Run prior predictive check (no observations).""" + import jax + from numpyro.infer import Predictive + + if model_fn is None: + model_fn = noise_model + + model_kwargs = _build_model_kwargs(data, model_fn=model_fn) + model_kwargs["y_obs"] = None # no observations + + predictive = Predictive(model_fn, num_samples=num_samples) + rng_key = jax.random.PRNGKey(99) + prior_samples = predictive(rng_key, **model_kwargs) + prior_samples = {k: np.array(v) for k, v in prior_samples.items()} + + print(f" Prior predictive: drew {num_samples} samples") + y_prior = prior_samples.get("y", None) + if y_prior is not None: + print(f" Prior log-volume range: " + f"[{np.percentile(y_prior, 1):.1f}, " + f"{np.percentile(y_prior, 99):.1f}]") + print(f" Observed log-volume range: " + f"[{data['y_obs'].min():.1f}, {data['y_obs'].max():.1f}]") + + return prior_samples diff --git a/quantammsim/noise_calibration/token_classification.py b/quantammsim/noise_calibration/token_classification.py new file mode 100644 index 0000000..aad9caa --- /dev/null +++ b/quantammsim/noise_calibration/token_classification.py @@ -0,0 +1,23 @@ +"""Token tier classification.""" + +from .constants import _TIER_0, _TIER_1 + + +def _normalise_symbol(symbol: str) -> str: + """Normalise wrapped/bridged variants to canonical form.""" + s = symbol.strip() + mapping = { + "WETH": "WETH", "WBTC": "WBTC", "cbBTC": "cbBTC", + "WMATIC": "WMATIC", "WAVAX": "WAVAX", "WXDAI": "WXDAI", "wS": "wS", + } + return mapping.get(s, s) + + +def classify_token_tier(symbol: str) -> int: + """Classify a token symbol into tier 0/1/2.""" + s = _normalise_symbol(symbol) + if s in _TIER_0: + return 0 + if s in _TIER_1: + return 1 + return 2 diff --git a/quantammsim/pools/ECLP/gyroscope.py b/quantammsim/pools/ECLP/gyroscope.py index f1fef2a..517a7ba 100644 --- a/quantammsim/pools/ECLP/gyroscope.py +++ b/quantammsim/pools/ECLP/gyroscope.py @@ -31,6 +31,7 @@ from typing import Dict, Any, Optional, Tuple import numpy as np +from quantammsim.core_simulator.dynamic_inputs import materialize_dynamic_inputs from quantammsim.pools.base_pool import AbstractPool from quantammsim.pools.ECLP.gyroscope_reserves import ( @@ -321,11 +322,7 @@ def calculate_reserves_with_dynamic_inputs( run_fingerprint: Dict[str, Any], prices: jnp.ndarray, start_index: jnp.ndarray, - fees_array: jnp.ndarray, - arb_thresh_array: jnp.ndarray, - arb_fees_array: jnp.ndarray, - trade_array: jnp.ndarray, - lp_supply_array: jnp.ndarray = None, + dynamic_inputs, additional_oracle_input: Optional[jnp.ndarray] = None, ) -> jnp.ndarray: # Gyroscope ECLP pools are only defined for 2 assets @@ -356,34 +353,27 @@ def calculate_reserves_with_dynamic_inputs( sin=jnp.sin(phi), cos=jnp.cos(phi), ) - # any of fees_array, arb_thresh_array, arb_fees_array, trade_array - # can be singletons, in which case we repeat them for the length of the bout - - # Determine the maximum leading dimension max_len = bout_length - 1 if run_fingerprint["arb_frequency"] != 1: max_len = max_len // run_fingerprint["arb_frequency"] + materialized_inputs = materialize_dynamic_inputs( + dynamic_inputs, + run_fingerprint.get("dynamic_input_flags"), + run_fingerprint, + scan_len=max_len, + do_trades=run_fingerprint["do_trades"], + dtype=arb_acted_upon_local_prices.dtype, + ) # Handle trade array reordering if needed if run_fingerprint["do_trades"]: - # if we are doing trades, the trades array must be of the same length as the other arrays - assert trade_array.shape[0] == max_len if needs_swap: # Swap trade indices (0->1, 1->0) but keep amounts unchanged - trade_array = trade_array.at[:, :2].set(1 - trade_array[:, :2]) - - # Broadcast input arrays to match the maximum leading dimension. - # If they are singletons, this will just repeat them for the length of the bout. - # If they are arrays of length bout_length, this will cause no change. - fees_array_broadcast = jnp.broadcast_to( - fees_array, (max_len,) + fees_array.shape[1:] - ) - arb_thresh_array_broadcast = jnp.broadcast_to( - arb_thresh_array, (max_len,) + arb_thresh_array.shape[1:] - ) - arb_fees_array_broadcast = jnp.broadcast_to( - arb_fees_array, (max_len,) + arb_fees_array.shape[1:] - ) + materialized_inputs = materialized_inputs._replace( + trades=materialized_inputs.trades.at[:, :2].set( + 1 - materialized_inputs.trades[:, :2] + ) + ) # Calculate reserves reserves = _jax_calc_gyroscope_reserves_with_dynamic_inputs( @@ -394,10 +384,10 @@ def calculate_reserves_with_dynamic_inputs( sin=jnp.sin(phi), cos=jnp.cos(phi), lam=lam, - fees=fees_array_broadcast, - arb_thresh=arb_thresh_array_broadcast, - arb_fees=arb_fees_array_broadcast, - trades=trade_array, + fees=materialized_inputs.fees, + arb_thresh=materialized_inputs.gas_cost, + arb_fees=materialized_inputs.arb_fees, + trades=materialized_inputs.trades, do_trades=run_fingerprint["do_trades"], ) # Restore original order if we swapped diff --git a/quantammsim/pools/ECLP/gyroscope_reserves.py b/quantammsim/pools/ECLP/gyroscope_reserves.py index 3dde3fb..4e7d969 100644 --- a/quantammsim/pools/ECLP/gyroscope_reserves.py +++ b/quantammsim/pools/ECLP/gyroscope_reserves.py @@ -605,7 +605,7 @@ def _jax_calc_gyroscope_reserves_with_dynamic_fees_and_trades_scan_function_usin gamma = input_list[1] arb_thresh = input_list[2] arb_fees = input_list[3] - trade = input_list[4] + trade = input_list[4] if do_trades else None @@ -727,6 +727,8 @@ def _jax_calc_gyroscope_reserves_with_dynamic_inputs( arb_fees = jnp.where( arb_fees.size == 1, jnp.full(prices.shape[0], arb_fees), arb_fees ) + if do_trades and trades is None: + raise ValueError("Trades must be provided when do_trades=True.") scan_fn = Partial( _jax_calc_gyroscope_reserves_with_dynamic_fees_and_trades_scan_function_using_precalcs, @@ -745,17 +747,15 @@ def _jax_calc_gyroscope_reserves_with_dynamic_inputs( initial_reserves, 0 ] - carry_list_end, reserves = scan( - scan_fn, - carry_list_init, - [ - prices, - gamma, - arb_thresh, - arb_fees, - trades, - ], - ) + scan_inputs = [ + prices, + gamma, + arb_thresh, + arb_fees, + ] + if do_trades: + scan_inputs.append(trades) + carry_list_end, reserves = scan(scan_fn, carry_list_init, scan_inputs) return reserves diff --git a/quantammsim/pools/FM_AMM/cow_pool.py b/quantammsim/pools/FM_AMM/cow_pool.py index 5fd29bb..13c171b 100644 --- a/quantammsim/pools/FM_AMM/cow_pool.py +++ b/quantammsim/pools/FM_AMM/cow_pool.py @@ -30,6 +30,7 @@ from functools import partial import numpy as np +from quantammsim.core_simulator.dynamic_inputs import materialize_dynamic_inputs from quantammsim.pools.base_pool import AbstractPool from quantammsim.pools.FM_AMM.cow_reserves import ( _jax_calc_cowamm_reserves_with_fees, @@ -56,10 +57,9 @@ class CowPool(AbstractPool): start_index, additional_oracle_input=None) -> jnp.ndarray: Calculates the reserves of the pool without considering fees. - calculate_reserves_with_dynamic_inputs(params, run_fingerprint, prices, - start_index, fees_array, arb_thresh_array, arb_fees_array, trade_array, - additional_oracle_input=None) -> jnp.ndarray: - Calculates the reserves of the pool with dynamic inputs for fees, + calculate_reserves_with_dynamic_inputs(params, run_fingerprint, prices, + start_index, dynamic_inputs, additional_oracle_input=None) -> jnp.ndarray: + Calculates the reserves of the pool with dynamic inputs for fees, arbitrage thresholds, arbitrage fees, and trades. init_base_parameters(initial_values_dict, run_fingerprint, n_assets, @@ -194,11 +194,7 @@ def calculate_reserves_with_dynamic_inputs( run_fingerprint: Dict[str, Any], prices: jnp.ndarray, start_index: jnp.ndarray, - fees_array: jnp.ndarray, - arb_thresh_array: jnp.ndarray, - arb_fees_array: jnp.ndarray, - trade_array: jnp.ndarray, - lp_supply_array: jnp.ndarray = None, + dynamic_inputs, additional_oracle_input: Optional[jnp.ndarray] = None, ) -> jnp.ndarray: bout_length = run_fingerprint["bout_length"] @@ -218,38 +214,27 @@ def calculate_reserves_with_dynamic_inputs( initial_value_per_token = weights * initial_pool_value initial_reserves = initial_value_per_token / arb_acted_upon_local_prices[0] - # any of fees_array, arb_thresh_array, arb_fees_array, trade_array - # can be singletons, in which case we repeat them for the length of the bout - - # Determine the maximum leading dimension max_len = bout_length - 1 if run_fingerprint["arb_frequency"] != 1: max_len = max_len // run_fingerprint["arb_frequency"] - # Broadcast input arrays to match the maximum leading dimension. - # If they are singletons, this will just repeat them for the length of the bout. - # If they are arrays of length bout_length, this will cause no change. - fees_array_broadcast = jnp.broadcast_to( - fees_array, (max_len,) + fees_array.shape[1:] - ) - arb_thresh_array_broadcast = jnp.broadcast_to( - arb_thresh_array, (max_len,) + arb_thresh_array.shape[1:] - ) - arb_fees_array_broadcast = jnp.broadcast_to( - arb_fees_array, (max_len,) + arb_fees_array.shape[1:] + materialized_inputs = materialize_dynamic_inputs( + dynamic_inputs, + run_fingerprint.get("dynamic_input_flags"), + run_fingerprint, + scan_len=max_len, + do_trades=run_fingerprint["do_trades"], + dtype=arb_acted_upon_local_prices.dtype, ) - # if we are doing trades, the trades array must be of the same length as the other arrays - if run_fingerprint["do_trades"]: - assert trade_array.shape[0] == max_len reserves = _jax_calc_cowamm_reserves_with_dynamic_inputs( initial_reserves, arb_acted_upon_local_prices, - fees_array_broadcast, - arb_thresh_array_broadcast, - arb_fees_array_broadcast, + materialized_inputs.fees, + materialized_inputs.gas_cost, + materialized_inputs.arb_fees, weights, run_fingerprint["arb_quality"], - trade_array, + materialized_inputs.trades, run_fingerprint["do_trades"], run_fingerprint["do_arb"], noise_trader_ratio=run_fingerprint["noise_trader_ratio"], diff --git a/quantammsim/pools/FM_AMM/cow_reserves.py b/quantammsim/pools/FM_AMM/cow_reserves.py index f876d51..ab340a0 100644 --- a/quantammsim/pools/FM_AMM/cow_reserves.py +++ b/quantammsim/pools/FM_AMM/cow_reserves.py @@ -729,7 +729,7 @@ def _jax_calc_cowamm_reserves_with_dynamic_fees_and_trades_scan_function( gamma = input_list[1] arb_thresh = input_list[2] arb_fees = input_list[3] - trade = input_list[4] + trade = input_list[4] if do_trades else None if do_arb: reserves_with_perfect_arb = _jax_calc_cowamm_reserves_with_fees_scan_function( @@ -827,6 +827,8 @@ def _jax_calc_cowamm_reserves_with_dynamic_inputs( initial_prices = prices[0] gamma = 1.0 - fees + if do_trades and trades is None: + raise ValueError("Trades must be provided when do_trades=True.") scan_fn = Partial( _jax_calc_cowamm_reserves_with_dynamic_fees_and_trades_scan_function, @@ -838,8 +840,9 @@ def _jax_calc_cowamm_reserves_with_dynamic_inputs( ) carry_list_init = [initial_prices, initial_reserves] - _, reserves = scan( - scan_fn, carry_list_init, [prices, gamma, arb_thresh, arb_fees, trades] - ) + scan_inputs = [prices, gamma, arb_thresh, arb_fees] + if do_trades: + scan_inputs.append(trades) + _, reserves = scan(scan_fn, carry_list_init, scan_inputs) return reserves diff --git a/quantammsim/pools/G3M/balancer/balancer.py b/quantammsim/pools/G3M/balancer/balancer.py index 4b6cfc2..9a81979 100644 --- a/quantammsim/pools/G3M/balancer/balancer.py +++ b/quantammsim/pools/G3M/balancer/balancer.py @@ -8,12 +8,14 @@ import jax.numpy as jnp from jax.lax import dynamic_slice +from quantammsim.core_simulator.dynamic_inputs import materialize_dynamic_inputs from quantammsim.pools.base_pool import AbstractPool from quantammsim.pools.G3M.balancer.balancer_reserves import ( _jax_calc_balancer_reserve_ratios, _jax_calc_balancer_reserves_with_fees_using_precalcs, _jax_calc_balancer_reserves_with_dynamic_inputs, ) +from quantammsim.pools.reCLAMM.reclamm import _prepare_dynamic_array DEFAULT_BACKEND = default_backend() CPU_DEVICE = devices("cpu")[0] @@ -152,6 +154,18 @@ def calculate_reserves_with_fees( initial_value_per_token = weights * initial_pool_value initial_reserves = initial_value_per_token / local_prices[0] + noise_model = run_fingerprint.get("noise_model", "ratio") + noise_params = run_fingerprint.get("reclamm_noise_params", None) + if noise_params is not None and type(noise_params) is not dict: + noise_params = dict(noise_params) + + arb_freq = run_fingerprint["arb_frequency"] + max_len = arb_acted_upon_local_prices.shape[0] + _na = self._prepare_noise_arrays( + prices, run_fingerprint, start_index, + bout_length, arb_freq, max_len, + ) + if run_fingerprint["do_arb"]: reserves = _jax_calc_balancer_reserves_with_fees_using_precalcs( initial_reserves, @@ -161,6 +175,17 @@ def calculate_reserves_with_fees( arb_thresh=run_fingerprint["gas_cost"], arb_fees=run_fingerprint["arb_fees"], all_sig_variations=jnp.array(run_fingerprint["all_sig_variations"]), + noise_model=noise_model, + noise_trader_ratio=run_fingerprint.get("noise_trader_ratio", 0.0), + protocol_fee_split=run_fingerprint.get("protocol_fee_split", 0.0), + seconds_per_step=float(arb_freq) * 60.0, + noise_params=noise_params, + volatility_array=_na.get("volatility"), + dow_sin_array=_na.get("dow_sin"), + dow_cos_array=_na.get("dow_cos"), + noise_base_array=_na.get("noise_base"), + noise_tvl_coeff_array=_na.get("noise_tvl_coeff"), + competitor_tvl_array=_na.get("competitor_tvl"), ) else: reserves = jnp.broadcast_to( @@ -255,11 +280,7 @@ def calculate_reserves_with_dynamic_inputs( run_fingerprint: Dict[str, Any], prices: jnp.ndarray, start_index: jnp.ndarray, - fees_array: jnp.ndarray, - arb_thresh_array: jnp.ndarray, - arb_fees_array: jnp.ndarray, - trade_array: jnp.ndarray, - lp_supply_array: jnp.ndarray = None, + dynamic_inputs, additional_oracle_input: Optional[jnp.ndarray] = None, ) -> jnp.ndarray: """ @@ -287,14 +308,8 @@ def calculate_reserves_with_dynamic_inputs( Price history array start_index : jnp.ndarray Starting index for the calculation window - fees_array : jnp.ndarray - Time-varying trading fees - arb_thresh_array : jnp.ndarray - Time-varying arbitrage thresholds - arb_fees_array : jnp.ndarray - Time-varying arbitrage fees - trade_array : jnp.ndarray - Custom trade sequence + dynamic_inputs : DynamicInputArrays + Fixed-structure bundle of dynamic inputs. Returns ------- @@ -318,42 +333,136 @@ def calculate_reserves_with_dynamic_inputs( initial_value_per_token = weights * initial_pool_value initial_reserves = initial_value_per_token / arb_acted_upon_local_prices[0] - # any of fees_array, arb_thresh_array, arb_fees_array, trade_array - # can be singletons, in which case we repeat them for the length of the bout - - # Determine the maximum leading dimension max_len = bout_length - 1 if run_fingerprint["arb_frequency"] != 1: max_len = max_len // run_fingerprint["arb_frequency"] - # Broadcast input arrays to match the maximum leading dimension. - # If they are singletons, this will just repeat them for the length of the bout. - # If they are arrays of length bout_length, this will cause no change. - fees_array_broadcast = jnp.broadcast_to( - fees_array, (max_len,) + fees_array.shape[1:] - ) - arb_thresh_array_broadcast = jnp.broadcast_to( - arb_thresh_array, (max_len,) + arb_thresh_array.shape[1:] + materialized_inputs = materialize_dynamic_inputs( + dynamic_inputs, + run_fingerprint.get("dynamic_input_flags"), + run_fingerprint, + scan_len=max_len, + do_trades=run_fingerprint["do_trades"], + dtype=arb_acted_upon_local_prices.dtype, ) - arb_fees_array_broadcast = jnp.broadcast_to( - arb_fees_array, (max_len,) + arb_fees_array.shape[1:] + noise_model = run_fingerprint.get("noise_model", "ratio") + noise_arrays = self._prepare_noise_arrays( + prices, run_fingerprint, start_index, + bout_length, run_fingerprint["arb_frequency"], max_len, ) - # if we are doing trades, the trades array must be of the same length as the other arrays - if run_fingerprint["do_trades"]: - assert trade_array.shape[0] == max_len + + noise_params = run_fingerprint.get("reclamm_noise_params", None) + if noise_params is not None and type(noise_params) is not dict: + noise_params = dict(noise_params) + reserves = _jax_calc_balancer_reserves_with_dynamic_inputs( initial_reserves, weights, arb_acted_upon_local_prices, - fees_array_broadcast, - arb_thresh_array_broadcast, - arb_fees_array_broadcast, + materialized_inputs.fees, + materialized_inputs.gas_cost, + materialized_inputs.arb_fees, jnp.array(run_fingerprint["all_sig_variations"]), - trade_array, + materialized_inputs.trades, run_fingerprint["do_trades"], run_fingerprint["do_arb"], + noise_model, + materialized_inputs.lp_supply, + protocol_fee_split=run_fingerprint.get("protocol_fee_split", 0.0), + noise_trader_ratio=run_fingerprint.get("noise_trader_ratio", 0.0), + seconds_per_step=float(run_fingerprint.get("arb_frequency", 1)) * 60.0, + noise_params=noise_params, + volatility_array=noise_arrays.get("volatility"), + dow_sin_array=noise_arrays.get("dow_sin"), + dow_cos_array=noise_arrays.get("dow_cos"), + noise_base_array=noise_arrays.get("noise_base"), + noise_tvl_coeff_array=noise_arrays.get("noise_tvl_coeff"), + competitor_tvl_array=noise_arrays.get("competitor_tvl"), ) return reserves + def _prepare_noise_arrays(self, prices, run_fingerprint, start_index, + bout_length, arb_freq, max_len): + """Prepare noise arrays — identical to ReClammPool._prepare_noise_arrays. + + The noise model describes market-level organic volume, not pool + mechanics, so the same implementation applies to any pool type. + """ + import numpy as np + noise_model = run_fingerprint.get("noise_model", "ratio") + result = {"volatility": None, "dow_sin": None, "dow_cos": None, + "noise_base": None, "noise_tvl_coeff": None, + "competitor_tvl": None} + + if noise_model == "mm_observed": + nb = run_fingerprint.get("noise_base_array") + ct = run_fingerprint.get("competitor_tvl_array") + if nb is None and "noise_arrays_path" in run_fingerprint: + path = run_fingerprint["noise_arrays_path"] + if not hasattr(self, "_mm_observed_cache") or self._mm_observed_cache[0] != path: + arrays = np.load(path) + self._mm_observed_cache = ( + path, arrays["noise_base"], arrays["competitor_tvl"]) + nb = self._mm_observed_cache[1] + ct = self._mm_observed_cache[2] + if nb is not None: + result["noise_base"] = _prepare_dynamic_array( + jnp.array(nb), start_index, bout_length, arb_freq, max_len) + if ct is not None: + result["competitor_tvl"] = _prepare_dynamic_array( + jnp.array(ct), start_index, bout_length, arb_freq, max_len) + return result + + if noise_model == "market_linear": + nb = run_fingerprint.get("noise_base_array") + ntc = run_fingerprint.get("noise_tvl_coeff_array") + if nb is None and "noise_arrays_path" in run_fingerprint: + path = run_fingerprint["noise_arrays_path"] + if not hasattr(self, "_market_linear_cache") or self._market_linear_cache[0] != path: + arrays = np.load(path) + self._market_linear_cache = (path, arrays["noise_base"], arrays["noise_tvl_coeff"]) + nb = self._market_linear_cache[1] + ntc = self._market_linear_cache[2] + if nb is not None: + result["noise_base"] = _prepare_dynamic_array( + jnp.array(nb), start_index, bout_length, arb_freq, max_len) + if ntc is not None: + result["noise_tvl_coeff"] = _prepare_dynamic_array( + jnp.array(ntc), start_index, bout_length, arb_freq, max_len) + return result + + needs_vol = noise_model in ( + "tsoukalas_sqrt", "tsoukalas_log", "loglinear", "calibrated", + ) + if not needs_vol: + return result + + volatility_array = self.calculate_volatility_array( + prices, run_fingerprint, + ) + result["volatility"] = _prepare_dynamic_array( + volatility_array, start_index, bout_length, arb_freq, max_len, + ) + + if noise_model != "calibrated": + return result + + # Day-of-week sin/cos arrays for the calibrated noise model. + import pandas as pd + start_dt = pd.Timestamp(run_fingerprint["startDateString"]) + n_minutes = prices.shape[0] + day_indices = np.arange(n_minutes) // 1440 + start_weekday = start_dt.weekday() + weekdays = ((start_weekday + day_indices) % 7).astype(np.float64) + dow_sin_full = jnp.array(np.sin(2.0 * np.pi * weekdays / 7.0)) + dow_cos_full = jnp.array(np.cos(2.0 * np.pi * weekdays / 7.0)) + result["dow_sin"] = _prepare_dynamic_array( + dow_sin_full, start_index, bout_length, arb_freq, max_len, + ) + result["dow_cos"] = _prepare_dynamic_array( + dow_cos_full, start_index, bout_length, arb_freq, max_len, + ) + return result + def init_base_parameters( self, initial_values_dict: Dict[str, Any], diff --git a/quantammsim/pools/G3M/balancer/balancer_reserves.py b/quantammsim/pools/G3M/balancer/balancer_reserves.py index c0b9d45..01003a3 100644 --- a/quantammsim/pools/G3M/balancer/balancer_reserves.py +++ b/quantammsim/pools/G3M/balancer/balancer_reserves.py @@ -15,6 +15,12 @@ parallelised_optimal_trade_sifter, ) from quantammsim.pools.G3M.G3M_trades import jitted_G3M_cond_trade +from quantammsim.pools.noise_trades import calculate_reserves_after_noise_trade +from quantammsim.pools.reCLAMM.reclamm_reserves import ( + reclamm_mm_observed_noise_volume, + reclamm_market_linear_noise_volume, + reclamm_calibrated_noise_volume, +) DEFAULT_BACKEND = default_backend() @@ -62,7 +68,7 @@ def _jax_calc_balancer_reserve_ratio(prev_prices, weights, prices): ) -@partial(jit, static_argnums=(5,)) +@partial(jit, static_argnums=(5, 6)) def _jax_calc_balancer_reserves_with_fees_scan_function_using_precalcs( carry_list, prices_and_precalcs, @@ -70,9 +76,14 @@ def _jax_calc_balancer_reserves_with_fees_scan_function_using_precalcs( tokens_to_drop, active_trade_directions, n, + noise_model="ratio", gamma=0.997, arb_thresh=0.0, arb_fees=0.0, + noise_trader_ratio=0.0, + protocol_fee_split=0.0, + seconds_per_step=60.0, + noise_params=None, ): """ Calculate changes in AMM reserves considering fees @@ -147,7 +158,7 @@ def _jax_calc_balancer_reserves_with_fees_scan_function_using_precalcs( tokens_to_drop, gamma, n, - 0, + -1e-15, ) optimal_arb_trade = jnp.where( @@ -174,6 +185,77 @@ def _jax_calc_balancer_reserves_with_fees_scan_function_using_precalcs( reserves = jnp.where(do_price_arb_trade, post_price_reserves, prev_reserves) + # --- Noise model dispatch (non-dynamic path) --- + if noise_model == "ratio": + if_noise = noise_trader_ratio > 0 + applied_trade = reserves - prev_reserves + noisy = calculate_reserves_after_noise_trade( + applied_trade, reserves, prices, noise_trader_ratio, gamma, + ) + reserves = jnp.where(if_noise, noisy, reserves) + elif noise_model in ("tsoukalas_sqrt", "tsoukalas_log", "loglinear"): + from quantammsim.pools.reCLAMM.reclamm_reserves import ( + reclamm_tsoukalas_sqrt_noise_volume, + reclamm_tsoukalas_log_noise_volume, + reclamm_loglinear_noise_volume, + ) + volatility = prices_and_precalcs[4] + arb_volume = 0.5 * jnp.sum(jnp.abs(reserves - prev_reserves) * prices) + effective_value = (reserves * prices).sum() + _np = noise_params if noise_params is not None else {} + if noise_model == "tsoukalas_sqrt": + noise_vol = reclamm_tsoukalas_sqrt_noise_volume( + effective_value, gamma, volatility, arb_volume, _np) + elif noise_model == "tsoukalas_log": + noise_vol = reclamm_tsoukalas_log_noise_volume( + effective_value, gamma, volatility, arb_volume, _np) + else: + noise_vol = reclamm_loglinear_noise_volume( + effective_value, gamma, volatility, arb_volume, _np) + minutes_per_step = seconds_per_step / 60.0 + noise_fee_total = (1.0 - gamma) * noise_vol * minutes_per_step + noise_fee_income = noise_fee_total * (1.0 - protocol_fee_split) + scale = 1.0 + noise_fee_income / jnp.maximum(effective_value, 1e-8) + reserves = reserves * scale + elif noise_model == "calibrated": + volatility = prices_and_precalcs[4] + dow_sin = prices_and_precalcs[5] + dow_cos = prices_and_precalcs[6] + arb_volume = 0.5 * jnp.sum(jnp.abs(reserves - prev_reserves) * prices) + effective_value = (reserves * prices).sum() + _np = noise_params if noise_params is not None else {} + noise_vol = reclamm_calibrated_noise_volume( + effective_value, gamma, volatility, arb_volume, dow_sin, dow_cos, _np) + minutes_per_step = seconds_per_step / 60.0 + noise_fee_total = (1.0 - gamma) * noise_vol * minutes_per_step + noise_fee_income = noise_fee_total * (1.0 - protocol_fee_split) + scale = 1.0 + noise_fee_income / jnp.maximum(effective_value, 1e-8) + reserves = reserves * scale + elif noise_model == "market_linear": + noise_base = prices_and_precalcs[4] + noise_tvl_coeff = prices_and_precalcs[5] + effective_value = (reserves * prices).sum() + _np = noise_params if noise_params is not None else {} + noise_vol = reclamm_market_linear_noise_volume( + effective_value, noise_base, noise_tvl_coeff, + tvl_mean=_np.get("tvl_mean", 0.0), tvl_std=_np.get("tvl_std", 1.0)) + minutes_per_step = seconds_per_step / 60.0 + noise_fee_total = (1.0 - gamma) * noise_vol * minutes_per_step + noise_fee_income = noise_fee_total * (1.0 - protocol_fee_split) + scale = 1.0 + noise_fee_income / jnp.maximum(effective_value, 1e-8) + reserves = reserves * scale + elif noise_model == "mm_observed": + noise_base = prices_and_precalcs[4] + competitor_tvl = prices_and_precalcs[5] + effective_value = (reserves * prices).sum() + noise_vol = reclamm_mm_observed_noise_volume( + effective_value, noise_base, competitor_tvl) + minutes_per_step = seconds_per_step / 60.0 + noise_fee_total = (1.0 - gamma) * noise_vol * minutes_per_step + noise_fee_income = noise_fee_total * (1.0 - protocol_fee_split) + scale = 1.0 + noise_fee_income / jnp.maximum(effective_value, 1e-8) + reserves = reserves * scale + counter += 1 return [ prices, @@ -182,7 +264,7 @@ def _jax_calc_balancer_reserves_with_fees_scan_function_using_precalcs( ], reserves -@jit +@partial(jit, static_argnums=(), static_argnames=("noise_model",)) def _jax_calc_balancer_reserves_with_fees_using_precalcs( initial_reserves, weights, @@ -191,6 +273,17 @@ def _jax_calc_balancer_reserves_with_fees_using_precalcs( arb_thresh=0.0, arb_fees=0.0, all_sig_variations=None, + noise_model="ratio", + noise_trader_ratio=0.0, + protocol_fee_split=0.0, + seconds_per_step=60.0, + noise_params=None, + volatility_array=None, + dow_sin_array=None, + dow_cos_array=None, + noise_base_array=None, + noise_tvl_coeff_array=None, + competitor_tvl_array=None, ): """ Calculate AMM reserves considering fees and arbitrage opportunities using signature variations, @@ -260,6 +353,11 @@ def _jax_calc_balancer_reserves_with_fees_using_precalcs( n=n_assets, tokens_to_drop=tokens_to_drop, active_trade_directions=active_trade_directions, + noise_model=noise_model, + noise_trader_ratio=noise_trader_ratio, + protocol_fee_split=protocol_fee_split, + seconds_per_step=seconds_per_step, + noise_params=noise_params, ) carry_list_init = [ @@ -267,21 +365,38 @@ def _jax_calc_balancer_reserves_with_fees_using_precalcs( initial_reserves, 0, ] + + scan_inputs = [ + prices, + active_initial_weights, + per_asset_ratios, + all_other_assets_ratios, + ] + + # Append noise arrays at position 4+ in prices_and_precalcs + if noise_model in ("tsoukalas_sqrt", "tsoukalas_log", "loglinear"): + scan_inputs.append(volatility_array) + elif noise_model == "calibrated": + scan_inputs.append(volatility_array) + scan_inputs.append(dow_sin_array) + scan_inputs.append(dow_cos_array) + elif noise_model == "market_linear": + scan_inputs.append(noise_base_array) + scan_inputs.append(noise_tvl_coeff_array) + elif noise_model == "mm_observed": + scan_inputs.append(noise_base_array) + scan_inputs.append(competitor_tvl_array) + _, reserves = scan( scan_fn, carry_list_init, - [ - prices, - active_initial_weights, - per_asset_ratios, - all_other_assets_ratios, - ], + scan_inputs, ) return reserves -@partial(jit, static_argnums=(6, 7, 8)) +@partial(jit, static_argnums=(6, 7, 8, 9)) def _jax_calc_balancer_reserves_with_dynamic_fees_and_trades_scan_function_using_precalcs( carry_list, input_list, @@ -291,7 +406,11 @@ def _jax_calc_balancer_reserves_with_dynamic_fees_and_trades_scan_function_using active_trade_directions, n, do_trades, - do_arb + do_arb, + noise_model="ratio", + protocol_fee_split=0.0, + seconds_per_step=60.0, + noise_params=None, ): """ Calculate changes in AMM reserves considering fees @@ -350,6 +469,7 @@ def _jax_calc_balancer_reserves_with_dynamic_fees_and_trades_scan_function_using prev_reserves = carry_list[1] counter = carry_list[2] + prev_lp_supply = carry_list[3] # input_list contains weights, prices, precalcs and fee/arb amounts prices = input_list[0] @@ -360,6 +480,15 @@ def _jax_calc_balancer_reserves_with_dynamic_fees_and_trades_scan_function_using arb_thresh = input_list[5] arb_fees = input_list[6] trade = input_list[7] + lp_supply = input_list[8] + + # Scale reserves for LP supply changes (proportional deposits/withdrawals) + lp_supply_change = lp_supply != prev_lp_supply + prev_reserves = jnp.where( + lp_supply_change, + prev_reserves * lp_supply / prev_lp_supply, + prev_reserves, + ) fees_are_being_charged = gamma != 1.0 @@ -385,7 +514,7 @@ def _jax_calc_balancer_reserves_with_dynamic_fees_and_trades_scan_function_using tokens_to_drop, gamma, n, - 0, + -1e-15, ) optimal_arb_trade = jnp.where( @@ -412,6 +541,81 @@ def _jax_calc_balancer_reserves_with_dynamic_fees_and_trades_scan_function_using reserves = jnp.where(do_price_arb_trade, post_price_reserves, prev_reserves) + # --- Noise model dispatch --- + # Same pattern as reclamm_reserves.py: input_list[9+] are noise arrays, + # protocol_fee_split / seconds_per_step / noise_params are passed via Partial. + noise_fee_income = jnp.asarray(0.0, dtype=prices.dtype) + if noise_model == "ratio": + noise_trader_ratio = input_list[9] + if_noise = noise_trader_ratio > 0 + applied_trade = reserves - prev_reserves + noisy = calculate_reserves_after_noise_trade( + applied_trade, reserves, prices, noise_trader_ratio, gamma, + ) + reserves = jnp.where(if_noise, noisy, reserves) + elif noise_model in ("tsoukalas_sqrt", "tsoukalas_log", "loglinear"): + from quantammsim.pools.reCLAMM.reclamm_reserves import ( + reclamm_tsoukalas_sqrt_noise_volume, + reclamm_tsoukalas_log_noise_volume, + reclamm_loglinear_noise_volume, + ) + volatility = input_list[9] + arb_volume = 0.5 * jnp.sum(jnp.abs(reserves - prev_reserves) * prices) + effective_value = (reserves * prices).sum() + _np = noise_params if noise_params is not None else {} + if noise_model == "tsoukalas_sqrt": + noise_vol = reclamm_tsoukalas_sqrt_noise_volume( + effective_value, gamma, volatility, arb_volume, _np) + elif noise_model == "tsoukalas_log": + noise_vol = reclamm_tsoukalas_log_noise_volume( + effective_value, gamma, volatility, arb_volume, _np) + else: + noise_vol = reclamm_loglinear_noise_volume( + effective_value, gamma, volatility, arb_volume, _np) + minutes_per_step = seconds_per_step / 60.0 + noise_fee_total = (1.0 - gamma) * noise_vol * minutes_per_step + noise_fee_income = noise_fee_total * (1.0 - protocol_fee_split) + scale = 1.0 + noise_fee_income / jnp.maximum(effective_value, 1e-8) + reserves = reserves * scale + elif noise_model == "calibrated": + volatility = input_list[9] + dow_sin = input_list[10] + dow_cos = input_list[11] + arb_volume = 0.5 * jnp.sum(jnp.abs(reserves - prev_reserves) * prices) + effective_value = (reserves * prices).sum() + _np = noise_params if noise_params is not None else {} + noise_vol = reclamm_calibrated_noise_volume( + effective_value, gamma, volatility, arb_volume, dow_sin, dow_cos, _np) + minutes_per_step = seconds_per_step / 60.0 + noise_fee_total = (1.0 - gamma) * noise_vol * minutes_per_step + noise_fee_income = noise_fee_total * (1.0 - protocol_fee_split) + scale = 1.0 + noise_fee_income / jnp.maximum(effective_value, 1e-8) + reserves = reserves * scale + elif noise_model == "market_linear": + noise_base = input_list[9] + noise_tvl_coeff = input_list[10] + effective_value = (reserves * prices).sum() + _np = noise_params if noise_params is not None else {} + noise_vol = reclamm_market_linear_noise_volume( + effective_value, noise_base, noise_tvl_coeff, + tvl_mean=_np.get("tvl_mean", 0.0), tvl_std=_np.get("tvl_std", 1.0)) + minutes_per_step = seconds_per_step / 60.0 + noise_fee_total = (1.0 - gamma) * noise_vol * minutes_per_step + noise_fee_income = noise_fee_total * (1.0 - protocol_fee_split) + scale = 1.0 + noise_fee_income / jnp.maximum(effective_value, 1e-8) + reserves = reserves * scale + elif noise_model == "mm_observed": + noise_base = input_list[9] + competitor_tvl = input_list[10] + effective_value = (reserves * prices).sum() + noise_vol = reclamm_mm_observed_noise_volume( + effective_value, noise_base, competitor_tvl) + minutes_per_step = seconds_per_step / 60.0 + noise_fee_total = (1.0 - gamma) * noise_vol * minutes_per_step + noise_fee_income = noise_fee_total * (1.0 - protocol_fee_split) + scale = 1.0 + noise_fee_income / jnp.maximum(effective_value, 1e-8) + reserves = reserves * scale + # apply trade if trade is present if do_trades: reserves += jitted_G3M_cond_trade(do_trades, reserves, weights, trade, gamma) @@ -421,10 +625,11 @@ def _jax_calc_balancer_reserves_with_dynamic_fees_and_trades_scan_function_using prices, reserves, counter, + lp_supply, ], reserves -@partial(jit, static_argnums=(8,9,)) +@partial(jit, static_argnums=(8, 9, 10)) def _jax_calc_balancer_reserves_with_dynamic_inputs( initial_reserves, weights, @@ -436,6 +641,18 @@ def _jax_calc_balancer_reserves_with_dynamic_inputs( trades=None, do_trades=False, do_arb=True, + noise_model="ratio", + lp_supply_array=None, + protocol_fee_split=0.0, + noise_trader_ratio=0.0, + seconds_per_step=60.0, + noise_params=None, + volatility_array=None, + dow_sin_array=None, + dow_cos_array=None, + noise_base_array=None, + noise_tvl_coeff_array=None, + competitor_tvl_array=None, ): """ Calculate AMM reserves considering fees and arbitrage opportunities using signature variations, @@ -494,6 +711,16 @@ def _jax_calc_balancer_reserves_with_dynamic_inputs( arb_fees = jnp.where( arb_fees.size == 1, jnp.full(prices.shape[0], arb_fees), arb_fees ) + if do_trades and trades is None: + raise ValueError("Trades must be provided when do_trades=True.") + + if lp_supply_array is None: + lp_supply_array = jnp.array(1.0) + lp_supply_array = jnp.where( + lp_supply_array.size == 1, + jnp.full(prices.shape[0], lp_supply_array), + lp_supply_array, + ) # pre-calculate some values that are repeatedly used in optimal arb calculations _, active_trade_directions, tokens_to_drop, leave_one_out_idxs = ( @@ -521,26 +748,53 @@ def _jax_calc_balancer_reserves_with_dynamic_inputs( active_trade_directions=active_trade_directions, do_trades=do_trades, do_arb=do_arb, + noise_model=noise_model, + protocol_fee_split=protocol_fee_split, + seconds_per_step=seconds_per_step, + noise_params=noise_params, ) carry_list_init = [ initial_prices, initial_reserves, 0, + lp_supply_array[0], ] + + scan_inputs = [ + prices, + active_initial_weights, + per_asset_ratios, + all_other_assets_ratios, + gamma, + arb_thresh, + arb_fees, + trades, + lp_supply_array, + ] + + # Append noise-model-specific arrays at position 9+ + # (same ordering as reclamm_reserves.py scan inputs) + if noise_model == "ratio": + ntr = jnp.broadcast_to(jnp.asarray(noise_trader_ratio), (prices.shape[0],)) + scan_inputs.append(ntr) + elif noise_model in ("tsoukalas_sqrt", "tsoukalas_log", "loglinear"): + scan_inputs.append(volatility_array) + elif noise_model == "calibrated": + scan_inputs.append(volatility_array) + scan_inputs.append(dow_sin_array) + scan_inputs.append(dow_cos_array) + elif noise_model == "market_linear": + scan_inputs.append(noise_base_array) + scan_inputs.append(noise_tvl_coeff_array) + elif noise_model == "mm_observed": + scan_inputs.append(noise_base_array) + scan_inputs.append(competitor_tvl_array) + _, reserves = scan( scan_fn, carry_list_init, - [ - prices, - active_initial_weights, - per_asset_ratios, - all_other_assets_ratios, - gamma, - arb_thresh, - arb_fees, - trades, - ], + scan_inputs, ) return reserves diff --git a/quantammsim/pools/G3M/optimal_n_pool_arb.py b/quantammsim/pools/G3M/optimal_n_pool_arb.py index 822e7ce..d9e3964 100644 --- a/quantammsim/pools/G3M/optimal_n_pool_arb.py +++ b/quantammsim/pools/G3M/optimal_n_pool_arb.py @@ -164,21 +164,18 @@ def construct_optimal_trade_jnp( valid_post_trade_reserves = ( jnp.sum(initial_reserves + active_overall_trade > 0) == n ) - valid_post_trade_constant = ( - jnp.prod( - ( - initial_reserves - + active_overall_trade - * (fee_gamma ** (trade_to_direction_jnp(active_overall_trade))) - ) - ** initial_weights + post_trade_constant = jnp.prod( + ( + initial_reserves + + active_overall_trade + * (fee_gamma ** (trade_to_direction_jnp(active_overall_trade))) ) - - initial_constant - >= slack + ** initial_weights ) + relative_diff = (post_trade_constant - initial_constant) / initial_constant + valid_post_trade_constant = relative_diff >= slack valid_trade = jnp.logical_and(valid_post_trade_reserves, valid_post_trade_constant) return jnp.where(valid_trade, active_overall_trade, 0) - # return active_overall_trade, valid_post_trade_reserves * valid_post_trade_constant construct_optimal_trade_jnp_vmapped = vmap( @@ -336,18 +333,16 @@ def calc_optimal_trade_for_one_signature( valid_post_trade_reserves = ( jnp.sum(initial_reserves + active_overall_trade > 0) == n ) - valid_post_trade_constant = ( - jnp.prod( - ( - initial_reserves - + active_overall_trade - * (fee_gamma ** (trade_to_direction_jnp(active_overall_trade))) - ) - ** initial_weights + post_trade_constant = jnp.prod( + ( + initial_reserves + + active_overall_trade + * (fee_gamma ** (trade_to_direction_jnp(active_overall_trade))) ) - - initial_constant - >= slack + ** initial_weights ) + relative_diff = (post_trade_constant - initial_constant) / initial_constant + valid_post_trade_constant = relative_diff >= slack valid_trade = jnp.logical_and(valid_post_trade_reserves, valid_post_trade_constant) return jnp.where(valid_trade, active_overall_trade, 0) # return { @@ -411,7 +406,7 @@ def parallelised_optimal_trade_sifter( tokens_to_drop, fee_gamma, n, - 0, + slack, ) profits = -(overall_trades * local_prices).sum(-1) @@ -457,7 +452,7 @@ def wrapped_parallelised_optimal_trade_sifter( tokens_to_drop, fee_gamma, n, - slack=0, + slack=slack, ) return trade diff --git a/quantammsim/pools/G3M/quantamm/TFMM_base_pool.py b/quantammsim/pools/G3M/quantamm/TFMM_base_pool.py index bb99518..b739abe 100644 --- a/quantammsim/pools/G3M/quantamm/TFMM_base_pool.py +++ b/quantammsim/pools/G3M/quantamm/TFMM_base_pool.py @@ -11,6 +11,7 @@ from jax.lax import stop_gradient, dynamic_slice, scan, fori_loop from jax.tree_util import Partial +from quantammsim.core_simulator.dynamic_inputs import materialize_dynamic_inputs from quantammsim.pools.base_pool import AbstractPool from quantammsim.pools.G3M.quantamm.quantamm_reserves import ( _jax_calc_quantAMM_reserve_ratios, @@ -381,11 +382,7 @@ def calculate_reserves_with_dynamic_inputs( run_fingerprint: Dict[str, Any], prices: jnp.ndarray, start_index: jnp.ndarray, - fees_array: jnp.ndarray, - arb_thresh_array: jnp.ndarray, - arb_fees_array: jnp.ndarray, - trade_array: jnp.ndarray, - lp_supply_array: jnp.ndarray = None, + dynamic_inputs, additional_oracle_input: Optional[jnp.ndarray] = None, ) -> jnp.ndarray: bout_length = run_fingerprint["bout_length"] @@ -409,49 +406,35 @@ def calculate_reserves_with_dynamic_inputs( initial_value_per_token = arb_acted_upon_weights[0] * initial_pool_value initial_reserves = initial_value_per_token / arb_acted_upon_local_prices[0] - # any of fees_array, arb_thresh_array, arb_fees_array, trade_array, and lp_supply_array - # can be singletons, in which case we repeat them for the length of the bout. - - # Determine the maximum leading dimension max_len = bout_length - 1 if run_fingerprint["arb_frequency"] != 1: max_len = max_len // run_fingerprint["arb_frequency"] - # Broadcast input arrays to match the maximum leading dimension. - # If they are singletons, this will just repeat them for the length of the bout. - # If they are arrays of length bout_length, this will cause no change. - fees_array_broadcast = jnp.broadcast_to( - fees_array, (max_len,) + fees_array.shape[1:] - ) - arb_thresh_array_broadcast = jnp.broadcast_to( - arb_thresh_array, (max_len,) + arb_thresh_array.shape[1:] - ) - arb_fees_array_broadcast = jnp.broadcast_to( - arb_fees_array, (max_len,) + arb_fees_array.shape[1:] - ) - # if lp_supply_array is not provided, we set it to a constant of 1.0 - if lp_supply_array is None: - lp_supply_array = jnp.array(1.0) - - lp_supply_array_broadcast = jnp.broadcast_to( - lp_supply_array, (max_len,) + lp_supply_array.shape[1:] + materialized_inputs = materialize_dynamic_inputs( + dynamic_inputs, + run_fingerprint.get("dynamic_input_flags"), + run_fingerprint, + scan_len=max_len, + do_trades=run_fingerprint["do_trades"], + dtype=arb_acted_upon_local_prices.dtype, ) + lp_supply_array_broadcast = materialized_inputs.lp_supply # if we are doing trades, the trades array must be of the same length as the other arrays if run_fingerprint["do_trades"]: - assert trade_array.shape[0] == max_len + assert materialized_inputs.trades.shape[0] == max_len protocol_fee_split = run_fingerprint.get("protocol_fee_split", 0.0) reserves = _jax_calc_quantAMM_reserves_with_dynamic_inputs( initial_reserves, arb_acted_upon_weights, arb_acted_upon_local_prices, - fees_array_broadcast, - arb_thresh_array_broadcast, - arb_fees_array_broadcast, + materialized_inputs.fees, + materialized_inputs.gas_cost, + materialized_inputs.arb_fees, jnp.array(run_fingerprint["all_sig_variations"]), - trade_array, + materialized_inputs.trades, run_fingerprint["do_trades"], run_fingerprint["do_arb"], run_fingerprint["noise_trader_ratio"], - lp_supply_array_broadcast, + materialized_inputs.lp_supply, protocol_fee_split=protocol_fee_split, ) return reserves @@ -1577,11 +1560,16 @@ def calculate_weights_direct( initial_weights, minimum_weight, params, + jnp.zeros_like(initial_weights), + jnp.ones_like(initial_weights), local_fingerprint["max_memory_days"], local_fingerprint["chunk_period"], local_fingerprint["weight_interpolation_period"], maximum_change, False, + False, + False, + False, ) return target_weights_cpu diff --git a/quantammsim/pools/G3M/quantamm/quantamm_reserves.py b/quantammsim/pools/G3M/quantamm/quantamm_reserves.py index b6bd126..e0091ba 100644 --- a/quantammsim/pools/G3M/quantamm/quantamm_reserves.py +++ b/quantammsim/pools/G3M/quantamm/quantamm_reserves.py @@ -540,9 +540,14 @@ def _jax_calc_quantAMM_reserves_with_dynamic_fees_and_trades_scan_function_using gamma = input_list[8] arb_thresh = input_list[9] arb_fees = input_list[10] - trade = input_list[11] - do_arb = input_list[12] - lp_supply = input_list[13] + if do_trades: + trade = input_list[11] + do_arb = input_list[12] + lp_supply = input_list[13] + else: + trade = None + do_arb = input_list[11] + lp_supply = input_list[12] fees_are_being_charged = gamma != 1.0 protocol_fee_amount_step = jnp.zeros_like(prev_reserves) @@ -826,6 +831,8 @@ def _jax_calc_quantAMM_reserves_with_dynamic_inputs( arb_fees = jnp.where( arb_fees.size == 1, jnp.full(weights.shape[0], arb_fees), arb_fees ) + if do_trades and trades is None: + raise ValueError("Trades must be provided when do_trades=True.") if lp_supply_array is None: lp_supply_array = jnp.array(1.0) @@ -899,26 +906,23 @@ def _jax_calc_quantAMM_reserves_with_dynamic_inputs( ] # carry_list_init = [initial_weights, initial_i] # nojit_scan = jax.disable_jit()(jax.lax.scan) - carry_list_end, reserves = scan( - scan_fn, - carry_list_init, - [ - weights, - prices, - active_initial_weights, - per_asset_ratios, - all_other_assets_ratios, - lagged_active_initial_weights, - lagged_per_asset_ratios, - lagged_all_other_assets_ratios, - gamma, - arb_thresh, - arb_fees, - trades, - do_arb, - lp_supply_array, - ], - ) + scan_inputs = [ + weights, + prices, + active_initial_weights, + per_asset_ratios, + all_other_assets_ratios, + lagged_active_initial_weights, + lagged_per_asset_ratios, + lagged_all_other_assets_ratios, + gamma, + arb_thresh, + arb_fees, + ] + if do_trades: + scan_inputs.append(trades) + scan_inputs.extend([do_arb, lp_supply_array]) + carry_list_end, reserves = scan(scan_fn, carry_list_init, scan_inputs) return reserves diff --git a/quantammsim/pools/base_pool.py b/quantammsim/pools/base_pool.py index 3aeb85c..706c939 100644 --- a/quantammsim/pools/base_pool.py +++ b/quantammsim/pools/base_pool.py @@ -1,11 +1,12 @@ from abc import ABC, abstractmethod -from typing import Dict, Any, Optional +from typing import Dict, Any, Optional, Tuple +from functools import partial import numpy as np import jax.numpy as jnp from jax.nn import softmax -from jax.lax import stop_gradient -from jax import tree_util +from jax.lax import stop_gradient, dynamic_slice +from jax import tree_util, jit, vmap from quantammsim.core_simulator.param_utils import make_vmap_in_axes_dict @@ -287,6 +288,99 @@ def add_noise( params[key] = jnp.array(params[key]) return params + @partial(jit, static_argnums=(2, 3)) + def calculate_volatility_array(self, prices, run_fingerprint, subsample_freq=5): + """Annualised daily realised volatility broadcast to minute-level array. + + Pure-JAX implementation (vmap + dynamic_slice) — JIT-compatible and + callable from within traced contexts (e.g. forward_pass). + + Parameters + ---------- + prices : jnp.ndarray, shape (T, 2) + Minute-level prices for two tokens. + run_fingerprint : dict + Must contain ``tokens`` and ``numeraire`` for ordering. + subsample_freq : int + Subsample within each day to reduce microstructure noise. + + Returns + ------- + jnp.ndarray, shape (T,) + Annualised volatility, constant within each day. + """ + ordered_prices, needs_swap = self._handle_numeraire_ordering( + prices, run_fingerprint, + ) + asset_prices = ordered_prices[:, 0] / ordered_prices[:, 1] + n_minutes = len(asset_prices) + + # Guard: need at least one full day for vmap + dynamic_slice + if n_minutes < 1440: + return jnp.full(n_minutes, 0.1) * jnp.sqrt(365.0) + + n_days = n_minutes // 1440 + + def calculate_daily_volatility(day_idx): + start_idx = day_idx * 1440 + window_prices = dynamic_slice(asset_prices, [start_idx], [1440]) + subsampled_prices = window_prices[::subsample_freq] + log_prices = jnp.log(jnp.maximum(subsampled_prices, 1e-8)) + returns = jnp.diff(log_prices) + num_nonzero_returns = jnp.sum(returns != 0) + total_returns = len(returns) + adjusted_variance = ( + num_nonzero_returns * jnp.var(returns) / total_returns + ) + dt = subsample_freq / 1440 + vol = jnp.sqrt(adjusted_variance) / jnp.sqrt(dt) + return vol + + daily_volatilities = vmap(calculate_daily_volatility)(jnp.arange(n_days)) + volatility_array = jnp.repeat(daily_volatilities, 1440) + + remaining_minutes = n_minutes - len(volatility_array) + if remaining_minutes > 0: + last_vol = ( + daily_volatilities[-1] if len(daily_volatilities) > 0 else 0.1 + ) + volatility_array = jnp.concatenate( + [volatility_array, jnp.full(remaining_minutes, last_vol)] + ) + + return volatility_array * jnp.sqrt(365.0) + + @partial(jit, static_argnums=(2,)) + def _handle_numeraire_ordering( + self, + prices: jnp.ndarray, + run_fingerprint: Dict[str, Any], + ) -> Tuple[jnp.ndarray, bool]: + """Reorder prices so numeraire token is in second position. + + Parameters + ---------- + prices : jnp.ndarray, shape (..., 2) + Price array with two tokens. + run_fingerprint : dict + Must contain ``tokens`` (sorted) and ``numeraire``. + + Returns + ------- + (ordered_prices, needs_swap) : (jnp.ndarray, bool) + """ + tokens = sorted(run_fingerprint["tokens"]) + numeraire = run_fingerprint["numeraire"] + if numeraire is None or numeraire not in tokens: + numeraire = tokens[-1] + needs_swap = tokens.index(numeraire) == 0 + + if needs_swap: + ordered_prices = prices[..., ::-1] + else: + ordered_prices = prices + return ordered_prices, needs_swap + def _tree_flatten(self): children = () aux_data = dict() # static values diff --git a/quantammsim/pools/hodl_pool.py b/quantammsim/pools/hodl_pool.py index d3171a3..c938ff9 100644 --- a/quantammsim/pools/hodl_pool.py +++ b/quantammsim/pools/hodl_pool.py @@ -37,9 +37,9 @@ class HODLPool(AbstractPool): additional_oracle_input=None): Calculates the reserves without fees, assuming no trading activity. - calculate_reserves_with_dynamic_inputs(params, run_fingerprint, prices, start_index, - fees_array, arb_thresh_array, arb_fees_array, trade_array, additional_oracle_input=None): - Calculates the reserves with dynamic inputs, which in this case is + calculate_reserves_with_dynamic_inputs(params, run_fingerprint, prices, start_index, + dynamic_inputs, additional_oracle_input=None): + Calculates the reserves with dynamic inputs, which in this case is the same as reserves without fees due to no activity. init_base_parameters(initial_values_dict, run_fingerprint, n_assets, @@ -124,11 +124,7 @@ def calculate_reserves_with_dynamic_inputs( run_fingerprint: Dict[str, Any], prices: jnp.ndarray, start_index: jnp.ndarray, - fees_array: jnp.ndarray, - arb_thresh_array: jnp.ndarray, - arb_fees_array: jnp.ndarray, - trade_array: jnp.ndarray, - lp_supply_array: jnp.ndarray = None, + dynamic_inputs, additional_oracle_input: Optional[jnp.ndarray] = None, ) -> jnp.ndarray: # hodl means no activity, so reserves are just the initial reserves diff --git a/quantammsim/pools/noise_trades.py b/quantammsim/pools/noise_trades.py index 82e7674..9c9f00f 100644 --- a/quantammsim/pools/noise_trades.py +++ b/quantammsim/pools/noise_trades.py @@ -108,3 +108,341 @@ def calculate_reserves_after_noise_trade( ) reserves = current_reserves * ratio_of_value_of_trade_to_reserves return reserves + + +@jit +def reclamm_tsoukalas_sqrt_noise_volume( + effective_value_usd, + gamma, + volatility, + arb_volume_this_period, + noise_params=None, +): + """reClAMM Tsoukalas sqrt model: effective TVL regressor. + + Predicts per-minute noise trader volume using: + V_daily = (a_0 - a_f*fee + a_sigma*sigma + + a_c*sqrt(c_eff/1e6)) * 1e6 + V_noise = max(0, V_daily/1440 - arb_volume_this_period) + + where c_eff = (Ra+Va)*pA + (Rb+Vb)*pB is the effective TVL (real + + virtual reserves valued in USD). For a concentrated liquidity pool, + effective reserves determine execution quality and routing decisions, + so they are the natural driver of noise volume. + + Parameters + ---------- + effective_value_usd : float + Effective TVL in USD: (Ra+Va)*pA + (Rb+Vb)*pB. + gamma : float + Fee parameter (1 - fee_rate). + volatility : float + Annualised daily realised volatility of the price ratio. + arb_volume_this_period : float + Arb volume already accounted for this time step (USD). + noise_params : dict, optional + Regression coefficients. Keys: a_0_base, a_f, a_sigma, + a_c, base_fee. + + Returns + ------- + float + Per-minute noise volume (USD), floored at zero. + """ + if noise_params is None: + noise_params = {} + a_0_base = noise_params.get("a_0_base", 0.5) + a_f = noise_params.get("a_f", 0.0) + a_sigma = noise_params.get("a_sigma", 2.0) + a_c = noise_params.get("a_c", 1.0) + base_fee = noise_params.get("base_fee", 0.003) + + fee = 1.0 - gamma + a_0 = a_0_base + base_fee * a_f + daily_vol = ( + a_0 - a_f * fee + + a_sigma * volatility + + a_c * jnp.sqrt(effective_value_usd / 1e6) + ) * 1e6 + return jnp.maximum(0.0, daily_vol / 1440.0 - arb_volume_this_period) + + +@jit +def reclamm_tsoukalas_log_noise_volume( + effective_value_usd, + gamma, + volatility, + arb_volume_this_period, + noise_params=None, +): + """reClAMM Tsoukalas log model: log(c_eff/1e6) instead of sqrt. + + Same specification as the sqrt variant but uses log regressor, + which may fit better for pools spanning a wide TVL range. + + Parameters + ---------- + effective_value_usd : float + Effective TVL in USD: (Ra+Va)*pA + (Rb+Vb)*pB. + gamma : float + Fee parameter (1 - fee_rate). + volatility : float + Annualised daily realised volatility of the price ratio. + arb_volume_this_period : float + Arb volume already accounted for this time step (USD). + noise_params : dict, optional + Regression coefficients (same keys as sqrt variant). + + Returns + ------- + float + Per-minute noise volume (USD), floored at zero. + """ + if noise_params is None: + noise_params = {} + a_0_base = noise_params.get("a_0_base", 0.5) + a_f = noise_params.get("a_f", 0.0) + a_sigma = noise_params.get("a_sigma", 2.0) + a_c = noise_params.get("a_c", 1.0) + base_fee = noise_params.get("base_fee", 0.003) + + fee = 1.0 - gamma + a_0 = a_0_base + base_fee * a_f + daily_vol = ( + a_0 - a_f * fee + + a_sigma * volatility + + a_c * jnp.log(jnp.maximum(effective_value_usd / 1e6, 1e-30)) + ) * 1e6 + return jnp.maximum(0.0, daily_vol / 1440.0 - arb_volume_this_period) + + +@jit +def reclamm_loglinear_noise_volume( + effective_value_usd, + gamma, + volatility, + arb_volume_this_period, + noise_params=None, +): + """Loglinear noise volume from hierarchical cross-pool calibration. + + Predicts per-minute noise volume using: + log(V_daily) = b_0 + b_sigma * volatility + b_c * log(TVL) + V_noise = max(0, exp(log_daily_vol) / 1440 - arb_volume) + + where b_0 is a pool-specific intercept (BLUP from the hierarchical + model, absorbing chain, token tier, and fee effects), and b_sigma, + b_c are shared fixed effects estimated from cross-pool variation. + + Note: ``gamma`` is accepted for interface compatibility with the + other noise volume functions but is not used; fee effects are + absorbed into ``b_0`` via the hierarchical model's BLUP. + + Parameters + ---------- + effective_value_usd : float + Effective TVL in USD: (Ra+Va)*pA + (Rb+Vb)*pB. + gamma : float + Fee parameter (1 - fee_rate). Unused — kept for uniform + calling convention across noise models. + volatility : float + Annualised daily realised volatility of the price ratio. + arb_volume_this_period : float + Arb volume already accounted for this time step (USD). + noise_params : dict, optional + Hierarchical model coefficients. Keys: b_0, b_sigma, b_c. + + Returns + ------- + float + Per-minute noise volume (USD), floored at zero. + """ + if noise_params is None: + noise_params = {} + b_0 = noise_params.get("b_0", -6.7) + b_sigma = noise_params.get("b_sigma", -0.0007) + b_c = noise_params.get("b_c", 1.04) + + log_daily_vol = ( + b_0 + + b_sigma * volatility + + b_c * jnp.log(jnp.maximum(effective_value_usd, 1.0)) + ) + daily_vol = jnp.exp(log_daily_vol) + return jnp.maximum(0.0, daily_vol / 1440.0 - arb_volume_this_period) + + +@jit +def reclamm_calibrated_noise_volume( + effective_value_usd, + gamma, + volatility, + arb_volume_this_period, + dow_sin, + dow_cos, + noise_params=None, +): + """8-covariate calibrated noise volume from cross-pool log-linear model. + + Predicts per-minute noise volume using:: + + log(V_daily) = c_0 + c_1*log(TVL) + c_2*log(sigma) + + c_3*log(TVL)*log(sigma) + c_4*log(TVL)*fee + + c_5*log(sigma)*fee + c_6*dow_sin + c_7*dow_cos + V_noise = max(0, exp(log_daily_vol) / 1440 - arb_volume) + + where sigma is annualised daily realised volatility, fee = 1 - gamma, + and dow_sin/dow_cos encode day-of-week seasonality. + + Parameters + ---------- + effective_value_usd : float + Effective TVL in USD: (Ra+Va)*pA + (Rb+Vb)*pB. + gamma : float + Fee parameter (1 - fee_rate). + volatility : float + Annualised daily realised volatility of the price ratio. + arb_volume_this_period : float + Arb volume already accounted for this time step (USD). + dow_sin : float + sin(2*pi*weekday/7) for the current day. + dow_cos : float + cos(2*pi*weekday/7) for the current day. + noise_params : dict, optional + Calibrated coefficients: c_0 .. c_7. + + Returns + ------- + float + Per-minute noise volume (USD), floored at zero. + """ + if noise_params is None: + noise_params = {} + c_0 = noise_params.get("c_0", 0.0) + c_1 = noise_params.get("c_1", 1.0) + c_2 = noise_params.get("c_2", 0.0) + c_3 = noise_params.get("c_3", 0.0) + c_4 = noise_params.get("c_4", 0.0) + c_5 = noise_params.get("c_5", 0.0) + c_6 = noise_params.get("c_6", 0.0) + c_7 = noise_params.get("c_7", 0.0) + + fee = 1.0 - gamma + log_tvl = jnp.log(jnp.maximum(effective_value_usd, 1.0)) + log_sigma = jnp.log(jnp.maximum(volatility, 1e-10)) + + log_daily_vol = ( + c_0 + + c_1 * log_tvl + + c_2 * log_sigma + + c_3 * log_tvl * log_sigma + + c_4 * log_tvl * fee + + c_5 * log_sigma * fee + + c_6 * dow_sin + + c_7 * dow_cos + ) + daily_vol = jnp.exp(log_daily_vol) + # noise_coeffs predict V_noise directly (not V_total), so no need to + # subtract arb volume — that would double-count the arb subtraction. + return jnp.maximum(0.0, daily_vol / 1440.0) + + +@jit +def reclamm_market_linear_noise_volume( + effective_value_usd, + noise_base, + noise_tvl_coeff, + tvl_mean=0.0, + tvl_std=1.0, +): + """Market-feature linear noise model with precomputed daily coefficients. + + The full model is:: + + log(V_daily_noise) = base_t + tvl_coeff_t * standardized_log_tvl + + where ``base_t`` absorbs all non-TVL terms (intercept, market regime, + token volatility, pair volatility, day-of-week, cross-pool volumes) + and ``tvl_coeff_t`` is the effective TVL coefficient including + interaction terms (tvl×btc_vol, tvl×tok_a_vol, tvl×pair_vol). + + The log(TVL) is standardized using the same mean/std from training + to ensure the coefficient scale matches. + + Both base_t and tvl_coeff_t are precomputed daily from the per-pool + calibrated noise model and passed in as dynamic input arrays. + + Under counterfactual (varying reClAMM concentration), only + ``effective_value_usd`` changes — all market/peer features are held + at observed values via the precomputed arrays. + + Parameters + ---------- + effective_value_usd : float + Effective TVL in USD: (Ra+Va)*pA + (Rb+Vb)*pB. + noise_base : float + Precomputed non-TVL component of log(V_daily_noise) for this step. + noise_tvl_coeff : float + Precomputed effective coefficient on log(TVL) for this step. + tvl_mean : float + Mean of log(TVL) from training data standardization. + tvl_std : float + Std of log(TVL) from training data standardization. + + Returns + ------- + float + Per-minute noise volume (USD), floored at zero. + """ + log_tvl = jnp.log(jnp.maximum(effective_value_usd, 1.0)) + # Clamp standardized TVL to training range [-3, +3] std to prevent + # extreme concentration from wireheading the noise model + standardized_log_tvl = jnp.clip( + (log_tvl - tvl_mean) / tvl_std, -3.0, 3.0) + log_daily_noise = noise_base + noise_tvl_coeff * standardized_log_tvl + daily_noise = jnp.exp(log_daily_noise) + return jnp.maximum(0.0, daily_noise / 1440.0) + + +@jit +def reclamm_mm_observed_noise_volume( + effective_value_usd, + noise_base, + competitor_tvl, +): + """Michaelis-Menten noise model with observed competitor TVL as K. + + Derived from optimal routing (Diamandis et al. 2023):: + + V_noise = exp(base_t) * TVL / (K_t + TVL) + + where K_t is observed total competitor liquidity (direct + multi-hop + network conductance) from DeFi Llama, and base_t absorbs per-pool + intercept + market feature effects. + + The MM form guarantees: + - Elasticity ≈ 1 at low TVL (TVL << K) + - Structural saturation at high TVL (V_noise → exp(base_t)) + - No wireheading: V_noise is bounded regardless of concentration + + Parameters + ---------- + effective_value_usd : float + Effective TVL in USD: (Ra+Va)*pA + (Rb+Vb)*pB. + noise_base : float + Precomputed log(V_max_daily) = alpha_i + gamma_i @ x_market_t. + competitor_tvl : float + Observed competitor TVL (K) for this step, from DeFi Llama + network conductance model. + + Returns + ------- + float + Per-minute noise volume (USD), floored at zero. + """ + tvl = jnp.maximum(effective_value_usd, 1.0) + K = jnp.maximum(competitor_tvl, 1.0) + daily_noise = jnp.exp(noise_base) * tvl / (K + tvl) + return jnp.maximum(0.0, daily_noise / 1440.0) + + diff --git a/quantammsim/pools/reCLAMM/reclamm.py b/quantammsim/pools/reCLAMM/reclamm.py index 152a264..6af9b32 100644 --- a/quantammsim/pools/reCLAMM/reclamm.py +++ b/quantammsim/pools/reCLAMM/reclamm.py @@ -16,6 +16,7 @@ from typing import Dict, Any, Optional, NamedTuple import numpy as np +from quantammsim.core_simulator.dynamic_inputs import materialize_dynamic_inputs from quantammsim.pools.base_pool import AbstractPool from quantammsim.pools.reCLAMM.reclamm_reserves import ( initialise_reclamm_reserves, @@ -33,6 +34,22 @@ SHIFT_EXPONENT_DIVISOR = 124649.0 +def _prepare_dynamic_array(arr, start_index, bout_length, arb_frequency, max_len): + """Slice and decimate a dynamic input array to match arb_prices shape.""" + arr = jnp.asarray(arr) + if arr.ndim == 0: + return jnp.full((max_len,), arr, dtype=arr.dtype) + if arr.shape[0] <= 1: + return jnp.broadcast_to(arr, (max_len,) + arr.shape[1:]) + + start = (start_index[0],) + (0,) * (arr.ndim - 1) + slice_sizes = (bout_length - 1,) + arr.shape[1:] + sliced = dynamic_slice(arr, start, slice_sizes) + if arb_frequency != 1: + sliced = sliced[::arb_frequency] + return sliced + + class _PoolState(NamedTuple): """Intermediate state produced by _init_pool_state. @@ -197,6 +214,147 @@ def _resolve_fees(params, run_fingerprint): return jnp.squeeze(params["fees"]) return run_fingerprint["fees"] + @staticmethod + def _resolve_ste_temperature(run_fingerprint): + """Resolve STE gate temperature for differentiable reCLAMM transitions.""" + return run_fingerprint.get("ste_temperature") + + def _resolve_noise_inputs( + self, + run_fingerprint: Dict[str, Any], + prices: jnp.ndarray, + start_index: jnp.ndarray, + arb_len: int, + lp_supply_array: Optional[jnp.ndarray] = None, + ): + """Prepare optional lp-supply and noise-model inputs for reserve scans.""" + bout_length = run_fingerprint["bout_length"] + arb_freq = run_fingerprint["arb_frequency"] + + lp_prepared = None + if lp_supply_array is not None: + lp_prepared = _prepare_dynamic_array( + lp_supply_array, + start_index=start_index, + bout_length=bout_length, + arb_frequency=arb_freq, + max_len=arb_len, + ) + + noise_model = run_fingerprint.get("noise_model", "ratio") + noise_params = run_fingerprint.get("reclamm_noise_params", None) + if noise_params is not None and type(noise_params) is not dict: + noise_params = dict(noise_params) + + noise_arrays = self._prepare_noise_arrays( + prices, + run_fingerprint, + start_index, + bout_length, + arb_freq, + arb_len, + ) + + return lp_prepared, noise_model, noise_params, noise_arrays + + def _prepare_noise_arrays(self, prices, run_fingerprint, start_index, + bout_length, arb_freq, max_len): + """Prepare dynamic input arrays for noise models. + + Returns dict with keys depending on noise_model: + - "ratio": {} + - "tsoukalas_*"/"loglinear": {"volatility": array} + - "calibrated": {"volatility": array, "dow_sin": array, "dow_cos": array} + - "market_linear": {"noise_base": array, "noise_tvl_coeff": array} + - "mm_observed": {"noise_base": array, "competitor_tvl": array} + """ + noise_model = run_fingerprint.get("noise_model", "ratio") + result = {"volatility": None, "dow_sin": None, "dow_cos": None, + "noise_base": None, "noise_tvl_coeff": None, + "competitor_tvl": None} + + if noise_model == "mm_observed": + # MM model with observed competitor TVL as K + nb = run_fingerprint.get("noise_base_array") + ct = run_fingerprint.get("competitor_tvl_array") + if nb is None and "noise_arrays_path" in run_fingerprint: + path = run_fingerprint["noise_arrays_path"] + if not hasattr(self, "_mm_observed_cache") or self._mm_observed_cache[0] != path: + arrays = np.load(path) + self._mm_observed_cache = ( + path, arrays["noise_base"], arrays["competitor_tvl"]) + nb = self._mm_observed_cache[1] + ct = self._mm_observed_cache[2] + if nb is not None: + result["noise_base"] = _prepare_dynamic_array( + jnp.array(nb), start_index, bout_length, arb_freq, max_len) + if ct is not None: + result["competitor_tvl"] = _prepare_dynamic_array( + jnp.array(ct), start_index, bout_length, arb_freq, max_len) + return result + + if noise_model == "market_linear": + # Load precomputed arrays from path (cached on instance) or direct. + nb = run_fingerprint.get("noise_base_array") + ntc = run_fingerprint.get("noise_tvl_coeff_array") + if nb is None and "noise_arrays_path" in run_fingerprint: + path = run_fingerprint["noise_arrays_path"] + if ( + not hasattr(self, "_market_linear_cache") + or self._market_linear_cache[0] != path + ): + arrays = np.load(path) + self._market_linear_cache = ( + path, + arrays["noise_base"], + arrays["noise_tvl_coeff"], + ) + nb = self._market_linear_cache[1] + ntc = self._market_linear_cache[2] + if nb is not None: + result["noise_base"] = _prepare_dynamic_array( + jnp.array(nb), start_index, bout_length, arb_freq, max_len, + ) + if ntc is not None: + result["noise_tvl_coeff"] = _prepare_dynamic_array( + jnp.array(ntc), start_index, bout_length, arb_freq, max_len, + ) + return result + + needs_vol = noise_model in ( + "tsoukalas_sqrt", "tsoukalas_log", "loglinear", "calibrated", + ) + if not needs_vol: + return result + + volatility_array = self.calculate_volatility_array( + prices, run_fingerprint, + ) + result["volatility"] = _prepare_dynamic_array( + volatility_array, start_index, bout_length, arb_freq, max_len, + ) + + if noise_model != "calibrated": + return result + + # Day-of-week sin/cos arrays for the calibrated noise model. + import pandas as pd + + start_dt = pd.Timestamp(run_fingerprint["startDateString"]) + n_minutes = prices.shape[0] + day_indices = np.arange(n_minutes) // 1440 + start_weekday = start_dt.weekday() + weekdays = ((start_weekday + day_indices) % 7).astype(np.float64) + dow_sin_full = jnp.array(np.sin(2.0 * np.pi * weekdays / 7.0)) + dow_cos_full = jnp.array(np.cos(2.0 * np.pi * weekdays / 7.0)) + result["dow_sin"] = _prepare_dynamic_array( + dow_sin_full, start_index, bout_length, arb_freq, max_len, + ) + result["dow_cos"] = _prepare_dynamic_array( + dow_cos_full, start_index, bout_length, arb_freq, max_len, + ) + return result + @partial(jit, static_argnums=(2,)) def calculate_reserves_with_fees( self, @@ -205,8 +363,19 @@ def calculate_reserves_with_fees( prices: jnp.ndarray, start_index: jnp.ndarray, additional_oracle_input: Optional[jnp.ndarray] = None, + lp_supply_array: Optional[jnp.ndarray] = None, ) -> jnp.ndarray: s = self._init_pool_state(params, run_fingerprint, prices, start_index) + ste_temperature = self._resolve_ste_temperature(run_fingerprint) + lp_prepared, noise_model, noise_params, noise_arrays = ( + self._resolve_noise_inputs( + run_fingerprint, + prices, + start_index, + s.arb_prices.shape[0], + lp_supply_array=lp_supply_array, + ) + ) if run_fingerprint["do_arb"]: return _jax_calc_reclamm_reserves_with_fees( @@ -224,6 +393,17 @@ def calculate_reserves_with_fees( arc_length_speed=s.arc_length_speed, centeredness_scaling=s.centeredness_scaling, protocol_fee_split=run_fingerprint.get("protocol_fee_split", 0.0), + ste_temperature=ste_temperature, + noise_trader_ratio=run_fingerprint.get("noise_trader_ratio", 0.0), + lp_supply_array=lp_prepared, + noise_model=noise_model, + noise_params=noise_params, + volatility_array=noise_arrays["volatility"], + dow_sin_array=noise_arrays["dow_sin"], + dow_cos_array=noise_arrays["dow_cos"], + noise_base_array=noise_arrays["noise_base"], + noise_tvl_coeff_array=noise_arrays["noise_tvl_coeff"], + competitor_tvl_array=noise_arrays["competitor_tvl"], ) return jnp.broadcast_to(s.initial_reserves, s.arb_prices.shape) @@ -235,6 +415,7 @@ def calculate_reserves_and_fee_revenue_with_fees( prices: jnp.ndarray, start_index: jnp.ndarray, additional_oracle_input: Optional[jnp.ndarray] = None, + lp_supply_array: Optional[jnp.ndarray] = None, ): """Calculate reserves and LP fee revenue with fees. @@ -245,6 +426,16 @@ def calculate_reserves_and_fee_revenue_with_fees( LP fee revenue per timestep in USD. """ s = self._init_pool_state(params, run_fingerprint, prices, start_index) + ste_temperature = self._resolve_ste_temperature(run_fingerprint) + lp_prepared, noise_model, noise_params, noise_arrays = ( + self._resolve_noise_inputs( + run_fingerprint, + prices, + start_index, + s.arb_prices.shape[0], + lp_supply_array=lp_supply_array, + ) + ) if run_fingerprint["do_arb"]: return _jax_calc_reclamm_reserves_and_fee_revenue_with_fees( @@ -262,6 +453,17 @@ def calculate_reserves_and_fee_revenue_with_fees( arc_length_speed=s.arc_length_speed, centeredness_scaling=s.centeredness_scaling, protocol_fee_split=run_fingerprint.get("protocol_fee_split", 0.0), + ste_temperature=ste_temperature, + noise_trader_ratio=run_fingerprint.get("noise_trader_ratio", 0.0), + lp_supply_array=lp_prepared, + noise_model=noise_model, + noise_params=noise_params, + volatility_array=noise_arrays["volatility"], + dow_sin_array=noise_arrays["dow_sin"], + dow_cos_array=noise_arrays["dow_cos"], + noise_base_array=noise_arrays["noise_base"], + noise_tvl_coeff_array=noise_arrays["noise_tvl_coeff"], + competitor_tvl_array=noise_arrays["competitor_tvl"], ) return ( jnp.broadcast_to(s.initial_reserves, s.arb_prices.shape), @@ -275,11 +477,7 @@ def calculate_reserves_and_fee_revenue_with_dynamic_inputs( run_fingerprint: Dict[str, Any], prices: jnp.ndarray, start_index: jnp.ndarray, - fees_array: jnp.ndarray, - arb_thresh_array: jnp.ndarray, - arb_fees_array: jnp.ndarray, - trade_array: jnp.ndarray, - lp_supply_array: jnp.ndarray = None, + dynamic_inputs, additional_oracle_input: Optional[jnp.ndarray] = None, ): """Calculate reserves and LP fee revenue with time-varying inputs. @@ -291,20 +489,22 @@ def calculate_reserves_and_fee_revenue_with_dynamic_inputs( LP fee revenue per timestep in USD. """ s = self._init_pool_state(params, run_fingerprint, prices, start_index) - - bout_length = run_fingerprint["bout_length"] - max_len = bout_length - 1 - if run_fingerprint["arb_frequency"] != 1: - max_len = max_len // run_fingerprint["arb_frequency"] - - fees_array_broadcast = jnp.broadcast_to( - fees_array, (max_len,) + fees_array.shape[1:] - ) - arb_thresh_array_broadcast = jnp.broadcast_to( - arb_thresh_array, (max_len,) + arb_thresh_array.shape[1:] + ste_temperature = self._resolve_ste_temperature(run_fingerprint) + max_len = s.arb_prices.shape[0] + materialized_inputs = materialize_dynamic_inputs( + dynamic_inputs, + run_fingerprint.get("dynamic_input_flags"), + run_fingerprint, + scan_len=max_len, + do_trades=False, + dtype=s.arb_prices.dtype, ) - arb_fees_array_broadcast = jnp.broadcast_to( - arb_fees_array, (max_len,) + arb_fees_array.shape[1:] + _, noise_model, noise_params, noise_arrays = self._resolve_noise_inputs( + run_fingerprint, + prices, + start_index, + max_len, + lp_supply_array=None, ) return _jax_calc_reclamm_reserves_and_fee_revenue_with_dynamic_inputs( @@ -313,15 +513,27 @@ def calculate_reserves_and_fee_revenue_with_dynamic_inputs( s.centeredness_margin, s.daily_price_shift_base, s.seconds_per_step, - fees=fees_array_broadcast, - arb_thresh=arb_thresh_array_broadcast, - arb_fees=arb_fees_array_broadcast, + fees=materialized_inputs.fees, + arb_thresh=materialized_inputs.gas_cost, + arb_fees=materialized_inputs.arb_fees, + price_ratio_updates=materialized_inputs.reclamm_price_ratio_updates, all_sig_variations=jnp.array( run_fingerprint["all_sig_variations"] ), arc_length_speed=s.arc_length_speed, centeredness_scaling=s.centeredness_scaling, protocol_fee_split=run_fingerprint.get("protocol_fee_split", 0.0), + ste_temperature=ste_temperature, + noise_trader_ratio=run_fingerprint.get("noise_trader_ratio", 0.0), + lp_supply_array=materialized_inputs.lp_supply, + noise_model=noise_model, + noise_params=noise_params, + volatility_array=noise_arrays["volatility"], + dow_sin_array=noise_arrays["dow_sin"], + dow_cos_array=noise_arrays["dow_cos"], + noise_base_array=noise_arrays["noise_base"], + noise_tvl_coeff_array=noise_arrays["noise_tvl_coeff"], + competitor_tvl_array=noise_arrays["competitor_tvl"], ) @partial(jit, static_argnums=(2,)) @@ -332,9 +544,20 @@ def _calculate_reserves_zero_fees( prices: jnp.ndarray, start_index: jnp.ndarray, additional_oracle_input: Optional[jnp.ndarray] = None, + lp_supply_array: Optional[jnp.ndarray] = None, ) -> jnp.ndarray: """Protected zero-fee implementation for hooks and weight calculation.""" s = self._init_pool_state(params, run_fingerprint, prices, start_index) + ste_temperature = self._resolve_ste_temperature(run_fingerprint) + lp_prepared = None + if lp_supply_array is not None: + lp_prepared = _prepare_dynamic_array( + lp_supply_array, + start_index=start_index, + bout_length=run_fingerprint["bout_length"], + arb_frequency=run_fingerprint["arb_frequency"], + max_len=s.arb_prices.shape[0], + ) if run_fingerprint["do_arb"]: return _jax_calc_reclamm_reserves_zero_fees( @@ -345,6 +568,8 @@ def _calculate_reserves_zero_fees( s.seconds_per_step, arc_length_speed=s.arc_length_speed, centeredness_scaling=s.centeredness_scaling, + ste_temperature=ste_temperature, + lp_supply_array=lp_prepared, ) return jnp.broadcast_to(s.initial_reserves, s.arb_prices.shape) @@ -355,9 +580,15 @@ def calculate_reserves_zero_fees( prices: jnp.ndarray, start_index: jnp.ndarray, additional_oracle_input: Optional[jnp.ndarray] = None, + lp_supply_array: Optional[jnp.ndarray] = None, ) -> jnp.ndarray: return self._calculate_reserves_zero_fees( - params, run_fingerprint, prices, start_index, additional_oracle_input + params, + run_fingerprint, + prices, + start_index, + additional_oracle_input, + lp_supply_array, ) @partial(jit, static_argnums=(2,)) @@ -367,28 +598,26 @@ def calculate_reserves_with_dynamic_inputs( run_fingerprint: Dict[str, Any], prices: jnp.ndarray, start_index: jnp.ndarray, - fees_array: jnp.ndarray, - arb_thresh_array: jnp.ndarray, - arb_fees_array: jnp.ndarray, - trade_array: jnp.ndarray, - lp_supply_array: jnp.ndarray = None, + dynamic_inputs, additional_oracle_input: Optional[jnp.ndarray] = None, ) -> jnp.ndarray: s = self._init_pool_state(params, run_fingerprint, prices, start_index) - - bout_length = run_fingerprint["bout_length"] - max_len = bout_length - 1 - if run_fingerprint["arb_frequency"] != 1: - max_len = max_len // run_fingerprint["arb_frequency"] - - fees_array_broadcast = jnp.broadcast_to( - fees_array, (max_len,) + fees_array.shape[1:] - ) - arb_thresh_array_broadcast = jnp.broadcast_to( - arb_thresh_array, (max_len,) + arb_thresh_array.shape[1:] + ste_temperature = self._resolve_ste_temperature(run_fingerprint) + max_len = s.arb_prices.shape[0] + materialized_inputs = materialize_dynamic_inputs( + dynamic_inputs, + run_fingerprint.get("dynamic_input_flags"), + run_fingerprint, + scan_len=max_len, + do_trades=False, + dtype=s.arb_prices.dtype, ) - arb_fees_array_broadcast = jnp.broadcast_to( - arb_fees_array, (max_len,) + arb_fees_array.shape[1:] + _, noise_model, noise_params, noise_arrays = self._resolve_noise_inputs( + run_fingerprint, + prices, + start_index, + max_len, + lp_supply_array=None, ) return _jax_calc_reclamm_reserves_with_dynamic_inputs( @@ -397,15 +626,26 @@ def calculate_reserves_with_dynamic_inputs( s.centeredness_margin, s.daily_price_shift_base, s.seconds_per_step, - fees=fees_array_broadcast, - arb_thresh=arb_thresh_array_broadcast, - arb_fees=arb_fees_array_broadcast, + fees=materialized_inputs.fees, + arb_thresh=materialized_inputs.gas_cost, + arb_fees=materialized_inputs.arb_fees, + price_ratio_updates=materialized_inputs.reclamm_price_ratio_updates, all_sig_variations=jnp.array( run_fingerprint["all_sig_variations"] ), arc_length_speed=s.arc_length_speed, centeredness_scaling=s.centeredness_scaling, protocol_fee_split=run_fingerprint.get("protocol_fee_split", 0.0), + ste_temperature=ste_temperature, + noise_trader_ratio=run_fingerprint.get("noise_trader_ratio", 0.0), + lp_supply_array=materialized_inputs.lp_supply, + noise_model=noise_model, + noise_params=noise_params, + volatility_array=noise_arrays["volatility"], + dow_sin_array=noise_arrays["dow_sin"], + dow_cos_array=noise_arrays["dow_cos"], + noise_base_array=noise_arrays["noise_base"], + noise_tvl_coeff_array=noise_arrays["noise_tvl_coeff"], ) def init_base_parameters( diff --git a/quantammsim/pools/reCLAMM/reclamm_reserves.py b/quantammsim/pools/reCLAMM/reclamm_reserves.py index 81ad48e..8d12ae9 100644 --- a/quantammsim/pools/reCLAMM/reclamm_reserves.py +++ b/quantammsim/pools/reCLAMM/reclamm_reserves.py @@ -17,7 +17,8 @@ import jax.numpy as jnp from jax import jit -from jax.lax import scan +from jax.lax import scan, cond, stop_gradient +from jax.nn import sigmoid from jax.tree_util import Partial from functools import partial @@ -30,10 +31,32 @@ from quantammsim.pools.G3M.G3M_trades import ( _jax_calc_G3M_trade_from_exact_in_given_out, ) +from quantammsim.pools.noise_trades import ( + calculate_reserves_after_noise_trade, + reclamm_tsoukalas_sqrt_noise_volume, + reclamm_tsoukalas_log_noise_volume, + reclamm_loglinear_noise_volume, + reclamm_calibrated_noise_volume, + reclamm_market_linear_noise_volume, + reclamm_mm_observed_noise_volume, +) # Reference balance for initialisation (matches Solidity _INITIALIZATION_MAX_BALANCE_A) _INITIALIZATION_MAX_BALANCE_A = 1e6 +# MONKEY PATCH — blessed-arb experiment: +# If True, arb trades execute zero-fee (internal gamma = 1) and the +# arbitrageur returns their LVR back to the pool (minus gas + external cost). +# Set via set_blessed_arb() before constructing scan closures. Cache must be +# cleared between toggled values (jax.clear_caches()) because the global is +# captured at Partial-construction time. +_BLESSED_ARB = False + + +def set_blessed_arb(enabled: bool) -> None: + global _BLESSED_ARB + _BLESSED_ARB = bool(enabled) + # Virtual balance decay is capped at 30 days to prevent overflow _MAX_DECAY_DURATION_SECONDS = 30 * 86400 @@ -48,6 +71,35 @@ # Pure math functions # --------------------------------------------------------------------------- +def _ste_gate(hard_bool, soft_value): + """Hard forward / soft backward gate.""" + hard_value = hard_bool.astype(soft_value.dtype) + return soft_value + stop_gradient(hard_value - soft_value) + + +def _ste_greater_than(x, threshold, temperature=10.0): + hard = x > threshold + soft = sigmoid(temperature * (x - threshold)) + return _ste_gate(hard, soft) + + +def _ste_less_than(x, threshold, temperature=10.0): + hard = x < threshold + soft = sigmoid(temperature * (threshold - x)) + return _ste_gate(hard, soft) + + +def _ste_greater_equal(x, threshold, temperature=10.0): + hard = x >= threshold + soft = sigmoid(temperature * (x - threshold)) + return _ste_gate(hard, soft) + + +def _ste_select(mask, when_true, when_false): + """Select between two values using a 0/1 gate that can carry STE gradients.""" + return mask * when_true + (1.0 - mask) * when_false + + def compute_invariant(Ra, Rb, Va, Vb): """Compute constant-product invariant L = (Ra + Va) * (Rb + Vb).""" return (Ra + Va) * (Rb + Vb) @@ -556,14 +608,55 @@ def initialise_reclamm_reserves(initial_pool_value, initial_prices, price_ratio) # Scan-based reserve calculations # --------------------------------------------------------------------------- +def apply_target_price_ratio_to_virtual_balances(Ra, Rb, Va, Vb, target_price_ratio): + """Retarget virtual balances to a desired price ratio while preserving centeredness. + + Uses the closed-form quadratic solution from ReClammMath.sol + ``computeVirtualBalancesUpdatingPriceRatio``: + + Vu = Ru * (1 + C + sqrt(1 + C*(C + 4*Q0 - 2))) / (2*(Q0 - 1)) + Vo = Vu * lastVo / lastVu + + where Q0 = sqrt(price_ratio), C = centeredness, Ru is the real balance of + the undervalued token. The overvalued virtual balance is then scaled + proportionally so that Va/Vb is preserved, which keeps centeredness constant. + """ + safe_ratio = jnp.maximum(target_price_ratio, 1.0 + 1e-12) + Q0 = jnp.sqrt(safe_ratio) # sqrt(price_ratio) + centeredness, is_above = compute_centeredness(Ra, Rb, Va, Vb) + C = centeredness + + # Closed-form quadratic solution for the undervalued virtual balance. + discriminant = jnp.maximum(1.0 + C * (C + 4.0 * Q0 - 2.0), 0.0) + numerator_factor = 1.0 + C + jnp.sqrt(discriminant) + denominator = 2.0 * jnp.maximum(Q0 - 1.0, 1e-30) + + # Above center: A is undervalued (Ra abundant), B is overvalued. + Vu_above = Ra * numerator_factor / denominator # new Va + Vo_above = Vu_above * Vb / jnp.maximum(Va, 1e-30) # new Vb, scaled + + # Below center: B is undervalued (Rb abundant), A is overvalued. + Vu_below = Rb * numerator_factor / denominator # new Vb + Vo_below = Vu_below * Va / jnp.maximum(Vb, 1e-30) # new Va, scaled + + Va_new = jnp.where(is_above, Vu_above, Vo_below) + Vb_new = jnp.where(is_above, Vo_above, Vu_below) + + # When centeredness is degenerate (e.g. both sides zero), preserve current virtuals. + invalid_centeredness = ~jnp.isfinite(centeredness) + Va_new = jnp.where(invalid_centeredness, Va, Va_new) + Vb_new = jnp.where(invalid_centeredness, Vb, Vb_new) + return Va_new, Vb_new + def _reclamm_scan_step_zero_fees( carry_list, - prices, + input_list, centeredness_margin, daily_price_shift_base, seconds_per_step, arc_length_speed=0.0, centeredness_scaling=False, + ste_temperature=10.0, ): """Single scan step for zero-fee reClAMM pool. @@ -571,11 +664,23 @@ def _reclamm_scan_step_zero_fees( 1. Update virtual balances (path-dependent) 2. Compute analytical constant-product arb (no fee friction) - Carry: [real_reserves (2,), Va (0-d), Vb (0-d)] + Carry: [real_reserves (2,), Va (0-d), Vb (0-d), prev_lp_supply (0-d)] + Input: [prices (2,), lp_supply (0-d)] """ prev_reserves = carry_list[0] Va = carry_list[1] Vb = carry_list[2] + prev_lp_supply = carry_list[3] + + prices = input_list[0] + lp_supply = input_list[1] + + # Scale both real and virtual reserves by LP supply ratio. + scale = lp_supply / prev_lp_supply + lp_supply_change = lp_supply != prev_lp_supply + prev_reserves = jnp.where(lp_supply_change, prev_reserves * scale, prev_reserves) + Va = jnp.where(lp_supply_change, Va * scale, Va) + Vb = jnp.where(lp_supply_change, Vb * scale, Vb) Ra = prev_reserves[0] Rb = prev_reserves[1] @@ -583,7 +688,6 @@ def _reclamm_scan_step_zero_fees( # Step 1: Update virtual balances if out of range centeredness, is_above = compute_centeredness(Ra, Rb, Va, Vb) sqrt_Q = jnp.sqrt(compute_price_ratio(Ra, Rb, Va, Vb)) - out_of_range = centeredness < centeredness_margin market_price = prices[0] / prices[1] # Centeredness-proportional scaling: margin/centeredness multiplier @@ -614,8 +718,11 @@ def _reclamm_scan_step_zero_fees( Va_updated = jnp.where(use_cal, Va_cal, Va_geo) Vb_updated = jnp.where(use_cal, Vb_cal, Vb_geo) - Va = jnp.where(out_of_range, Va_updated, Va) - Vb = jnp.where(out_of_range, Vb_updated, Vb) + out_of_range_gate = _ste_less_than( + centeredness, centeredness_margin, ste_temperature + ) + Va = _ste_select(out_of_range_gate, Va_updated, Va) + Vb = _ste_select(out_of_range_gate, Vb_updated, Vb) # Step 2: Analytical zero-fee arb on effective reserves L = compute_invariant(Ra, Rb, Va, Vb) @@ -650,23 +757,32 @@ def _reclamm_scan_step_zero_fees( Rb_new = jnp.where(clamp_a, Rb + edge_a[1], jnp.where(clamp_b, Rb + edge_b[1], Rb_new)) new_reserves = jnp.array([Ra_new, Rb_new]) - return [new_reserves, Va, Vb], new_reserves + return [new_reserves, Va, Vb, lp_supply], new_reserves +# --------------------------------------------------------------------------- +# Test-only diagnostic helpers (virtual-balance history) +# --------------------------------------------------------------------------- +# These helpers mirror production kernels but additionally return Va/Vb +# trajectories for assertions in tests. Production pool paths should use the +# reserve-only kernels above. + def _reclamm_scan_step_zero_fees_full_state( carry_list, - prices, + input_list, centeredness_margin, daily_price_shift_base, seconds_per_step, arc_length_speed=0.0, centeredness_scaling=False, + ste_temperature=10.0, ): - """Like _reclamm_scan_step_zero_fees but outputs (reserves, Va, Vb).""" + """TEST-ONLY: scan step that outputs (reserves, Va, Vb).""" new_carry, new_reserves = _reclamm_scan_step_zero_fees( - carry_list, prices, centeredness_margin, daily_price_shift_base, seconds_per_step, + carry_list, input_list, centeredness_margin, daily_price_shift_base, seconds_per_step, arc_length_speed=arc_length_speed, centeredness_scaling=centeredness_scaling, + ste_temperature=ste_temperature, ) return new_carry, (new_reserves, new_carry[1], new_carry[2]) @@ -684,14 +800,21 @@ def _reclamm_scan_step_with_fees_and_revenue( arc_length_speed=0.0, centeredness_scaling=False, protocol_fee_split=0.0, + ste_temperature=10.0, + noise_trader_ratio=0.0, + noise_model="ratio", + noise_params=None, ): """Single scan step for reClAMM pool with fees, returning LP fee revenue. Primary implementation — ``_reclamm_scan_step_with_fees`` wraps this. - Carry: [real_reserves (2,), Va (0-d), Vb (0-d)] + Carry: [real_reserves (2,), Va, Vb, step_idx, active_start_ratio, + active_target_ratio, active_start_step, active_end_step, active_enabled, + prev_lp_supply] Input: [prices, active_initial_weights, per_asset_ratios, - all_other_assets_ratios, gamma, arb_thresh, arb_fees] + all_other_assets_ratios, gamma, arb_thresh, arb_fees, price_ratio_update, + lp_supply] Returns ------- @@ -702,9 +825,13 @@ def _reclamm_scan_step_with_fees_and_revenue( prev_reserves = carry_list[0] Va = carry_list[1] Vb = carry_list[2] - - Ra = prev_reserves[0] - Rb = prev_reserves[1] + step_idx = carry_list[3] + active_start_ratio = carry_list[4] + active_target_ratio = carry_list[5] + active_start_step = carry_list[6] + active_end_step = carry_list[7] + active_enabled = carry_list[8] + prev_lp_supply = carry_list[9] prices = input_list[0] active_initial_weights = input_list[1] @@ -713,11 +840,115 @@ def _reclamm_scan_step_with_fees_and_revenue( gamma = input_list[4] arb_thresh = input_list[5] arb_fees = input_list[6] + price_ratio_update = input_list[7] + lp_supply = input_list[8] + + # Scale both real and virtual reserves by LP supply ratio so liquidity + # add/remove events preserve proportional pool state. + scale = lp_supply / prev_lp_supply + lp_supply_change = lp_supply != prev_lp_supply + prev_reserves = jnp.where(lp_supply_change, prev_reserves * scale, prev_reserves) + Va = jnp.where(lp_supply_change, Va * scale, Va) + Vb = jnp.where(lp_supply_change, Vb * scale, Vb) + + Ra = prev_reserves[0] + Rb = prev_reserves[1] + + event_has = price_ratio_update[0] > 0.5 + event_target_ratio = jnp.maximum( + jnp.where(jnp.isfinite(price_ratio_update[1]), price_ratio_update[1], 1.0), + 1.0 + 1e-12, + ) + event_end_step = jnp.where( + jnp.isfinite(price_ratio_update[2]), price_ratio_update[2], step_idx + ) + event_start_override = price_ratio_update[3] + + def _apply_schedule_state(_): + current_price_ratio = compute_price_ratio(Ra, Rb, Va, Vb) + start_ratio_from_event = jnp.where( + jnp.isfinite(event_start_override), + event_start_override, + current_price_ratio, + ) + next_active_start_ratio = jnp.where( + event_has, start_ratio_from_event, active_start_ratio + ) + next_active_target_ratio = jnp.where( + event_has, event_target_ratio, active_target_ratio + ) + next_active_start_step = jnp.where(event_has, step_idx, active_start_step) + next_active_end_step = jnp.where( + event_has, jnp.maximum(event_end_step, step_idx), active_end_step + ) + next_active_enabled = jnp.where(event_has, True, active_enabled) + next_active_enabled = jnp.logical_and( + next_active_enabled, step_idx <= next_active_end_step + ) + + schedule_duration = next_active_end_step - next_active_start_step + schedule_progress = jnp.where( + schedule_duration <= 0.0, + 1.0, + jnp.clip((step_idx - next_active_start_step) / schedule_duration, 0.0, 1.0), + ) + safe_start_ratio = jnp.maximum(next_active_start_ratio, 1.0 + 1e-12) + safe_target_ratio = jnp.maximum(next_active_target_ratio, 1.0 + 1e-12) + scheduled_price_ratio = safe_start_ratio * ( + safe_target_ratio / safe_start_ratio + ) ** schedule_progress + scheduled_price_ratio = jnp.where( + next_active_enabled, scheduled_price_ratio, current_price_ratio + ) + Va_scheduled, Vb_scheduled = apply_target_price_ratio_to_virtual_balances( + Ra, Rb, Va, Vb, scheduled_price_ratio + ) + next_Va = jnp.where(next_active_enabled, Va_scheduled, Va) + next_Vb = jnp.where(next_active_enabled, Vb_scheduled, Vb) + return ( + next_Va, + next_Vb, + next_active_start_ratio, + next_active_target_ratio, + next_active_start_step, + next_active_end_step, + next_active_enabled, + ) + + def _skip_schedule_state(_): + retained_active_enabled = jnp.logical_and( + active_enabled, step_idx <= active_end_step + ) + return ( + Va, + Vb, + active_start_ratio, + active_target_ratio, + active_start_step, + active_end_step, + retained_active_enabled, + ) + + active_not_expired = jnp.logical_and(active_enabled, step_idx <= active_end_step) + schedule_active = jnp.logical_or(event_has, active_not_expired) + ( + Va, + Vb, + active_start_ratio, + active_target_ratio, + active_start_step, + active_end_step, + active_enabled, + ) = cond( + schedule_active, + _apply_schedule_state, + _skip_schedule_state, + operand=None, + ) # Step 1: Update virtual balances if out of range centeredness, is_above = compute_centeredness(Ra, Rb, Va, Vb) sqrt_Q = jnp.sqrt(compute_price_ratio(Ra, Rb, Va, Vb)) - out_of_range = centeredness < centeredness_margin market_price = prices[0] / prices[1] # Centeredness-proportional scaling: margin/centeredness multiplier @@ -747,14 +978,15 @@ def _reclamm_scan_step_with_fees_and_revenue( Va_updated = jnp.where(use_cal, Va_cal, Va_geo) Vb_updated = jnp.where(use_cal, Vb_cal, Vb_geo) - Va = jnp.where(out_of_range, Va_updated, Va) - Vb = jnp.where(out_of_range, Vb_updated, Vb) + out_of_range_gate = _ste_less_than( + centeredness, centeredness_margin, ste_temperature + ) + Va = _ste_select(out_of_range_gate, Va_updated, Va) + Vb = _ste_select(out_of_range_gate, Vb_updated, Vb) # Step 2: Compute arb trade using G3M machinery on effective reserves effective_reserves = jnp.array([Ra + Va, Rb + Vb]) - fees_are_being_charged = gamma != 1.0 - # Zero-fee analytical arb L = compute_invariant(Ra, Rb, Va, Vb) market_price = prices[0] / prices[1] @@ -777,18 +1009,131 @@ def _reclamm_scan_step_with_fees_and_revenue( 0, ) - optimal_arb_trade = jnp.where(fees_are_being_charged, fee_trade, zero_fee_trade) + fees_are_being_charged = gamma != 1.0 + # Blessed-arb monkey patch: zero-fee trade regardless of pool fee setting. + if _BLESSED_ARB: + optimal_arb_trade = zero_fee_trade + else: + optimal_arb_trade = jnp.where(fees_are_being_charged, fee_trade, zero_fee_trade) # Check profitability for arb profit_to_arb = -(optimal_arb_trade * prices).sum() - arb_thresh arb_external_cost = 0.5 * arb_fees * (jnp.abs(optimal_arb_trade) * prices).sum() - do_trade = profit_to_arb >= arb_external_cost # Apply trade to REAL reserves only - applied_trade = jnp.where(do_trade, optimal_arb_trade, 0.0) + trade_gate = _ste_greater_equal( + profit_to_arb, arb_external_cost, ste_temperature + ) + applied_trade = _ste_select( + trade_gate, optimal_arb_trade, jnp.zeros_like(optimal_arb_trade) + ) Ra_new = Ra + applied_trade[0] Rb_new = Rb + applied_trade[1] + # --- Noise model dispatch --- + # noise_model is a concrete Python string (passed via Partial as static + # aux_data), so if/elif branches resolve at trace time. + noise_fee_income = jnp.asarray(0.0, dtype=prices.dtype) + if noise_model == "ratio": + noisy_reserves = calculate_reserves_after_noise_trade( + applied_trade, jnp.array([Ra_new, Rb_new]), prices, + noise_trader_ratio, gamma, + ) + Ra_new = jnp.where(noise_trader_ratio > 0, noisy_reserves[0], Ra_new) + Rb_new = jnp.where(noise_trader_ratio > 0, noisy_reserves[1], Rb_new) + elif noise_model in ("tsoukalas_sqrt", "tsoukalas_log", "loglinear"): + volatility = input_list[9] + arb_volume = 0.5 * jnp.sum(jnp.abs(applied_trade) * prices) + real_value = jnp.sum(jnp.array([Ra_new, Rb_new]) * prices) + effective_value = (Ra_new + Va) * prices[0] + (Rb_new + Vb) * prices[1] + + _np = noise_params if noise_params is not None else {} + if noise_model == "tsoukalas_sqrt": + noise_vol = reclamm_tsoukalas_sqrt_noise_volume( + effective_value, gamma, volatility, arb_volume, _np + ) + elif noise_model == "tsoukalas_log": + noise_vol = reclamm_tsoukalas_log_noise_volume( + effective_value, gamma, volatility, arb_volume, _np + ) + else: + noise_vol = reclamm_loglinear_noise_volume( + effective_value, gamma, volatility, arb_volume, _np + ) + + # Scale effective reserves uniformly to preserve quoted price. + # For a 2-CLP: price ∝ (Ra+Va)/(Rb+Vb), so we must scale + # effective reserves (Ra+Va, Rb+Vb) by the same factor, then + # subtract back the fixed virtual reserves. + minutes_per_step = seconds_per_step / 60.0 + noise_fee_total = (1.0 - gamma) * noise_vol * minutes_per_step + noise_fee_income = noise_fee_total * (1.0 - protocol_fee_split) + scale = 1.0 + noise_fee_income / jnp.maximum(effective_value, 1e-8) + Ra_new = (Ra_new + Va) * scale - Va + Rb_new = (Rb_new + Vb) * scale - Vb + elif noise_model == "calibrated": + volatility = input_list[9] + dow_sin = input_list[10] + dow_cos = input_list[11] + arb_volume = 0.5 * jnp.sum(jnp.abs(applied_trade) * prices) + effective_value = (Ra_new + Va) * prices[0] + (Rb_new + Vb) * prices[1] + + _np = noise_params if noise_params is not None else {} + noise_vol = reclamm_calibrated_noise_volume( + effective_value, gamma, volatility, + arb_volume, dow_sin, dow_cos, _np, + ) + + minutes_per_step = seconds_per_step / 60.0 + noise_fee_total = (1.0 - gamma) * noise_vol * minutes_per_step + noise_fee_income = noise_fee_total * (1.0 - protocol_fee_split) + scale = 1.0 + noise_fee_income / jnp.maximum(effective_value, 1e-8) + Ra_new = (Ra_new + Va) * scale - Va + Rb_new = (Rb_new + Vb) * scale - Vb + elif noise_model == "market_linear": + noise_base = input_list[9] + noise_tvl_coeff = input_list[10] + effective_value = (Ra_new + Va) * prices[0] + (Rb_new + Vb) * prices[1] + + _np = noise_params if noise_params is not None else {} + noise_vol = reclamm_market_linear_noise_volume( + effective_value, noise_base, noise_tvl_coeff, + tvl_mean=_np.get("tvl_mean", 0.0), + tvl_std=_np.get("tvl_std", 1.0), + ) + + minutes_per_step = seconds_per_step / 60.0 + noise_fee_total = (1.0 - gamma) * noise_vol * minutes_per_step + noise_fee_income = noise_fee_total * (1.0 - protocol_fee_split) + scale = 1.0 + noise_fee_income / jnp.maximum(effective_value, 1e-8) + Ra_new = (Ra_new + Va) * scale - Va + Rb_new = (Rb_new + Vb) * scale - Vb + elif noise_model == "mm_observed": + noise_base = input_list[9] + competitor_tvl = input_list[10] + effective_value = (Ra_new + Va) * prices[0] + (Rb_new + Vb) * prices[1] + + noise_vol = reclamm_mm_observed_noise_volume( + effective_value, noise_base, competitor_tvl, + ) + + # In-range gate: noise traders only route here if the pool is + # in range (post-arb, post-recentering state). Hard-indicator + # limit of the routing-with-misquote convex program. + centeredness_post, _ = compute_centeredness(Ra_new, Rb_new, Va, Vb) + in_range_gate = (centeredness_post >= centeredness_margin).astype( + noise_vol.dtype + ) + noise_vol = noise_vol * in_range_gate + + minutes_per_step = seconds_per_step / 60.0 + noise_fee_total = (1.0 - gamma) * noise_vol * minutes_per_step + noise_fee_income = noise_fee_total * (1.0 - protocol_fee_split) + scale = 1.0 + noise_fee_income / jnp.maximum(effective_value, 1e-8) + Ra_new = (Ra_new + Va) * scale - Va + Rb_new = (Rb_new + Vb) * scale - Vb + # else: "arb_only" — no noise trades + # Clamp-to-edge: if a real reserve would go negative, apply an # exact-in-given-out edge trade that drains that token to _DUST_USD # worth of reserves (preserving the AMM invariant). @@ -814,20 +1159,48 @@ def _reclamm_scan_step_with_fees_and_revenue( Rb_new = jnp.where(clamp_a, Rb + edge_a[1], jnp.where(clamp_b, Rb + edge_b[1], Rb_new)) # Protocol fee: divert protocol_fee_split of inbound swap fees from LP reserves. - # Computed on the final trade (normal arb or edge trade). + # Computed on the final trade (normal arb or edge trade). Blessed arb pays + # no swap fee, so fee_rate collapses to 0 in that mode. final_trade = jnp.array([Ra_new - Ra, Rb_new - Rb]) - fee_rate = 1.0 - gamma + fee_rate = 0.0 if _BLESSED_ARB else (1.0 - gamma) inbound = jnp.maximum(final_trade, 0.0) protocol_fee = inbound * fee_rate * protocol_fee_split Ra_new = Ra_new - protocol_fee[0] Rb_new = Rb_new - protocol_fee[1] - # LP fee revenue: total fee income minus protocol's share, in USD. - lp_fee_income = inbound * fee_rate * (1.0 - protocol_fee_split) - lp_fee_revenue_usd = (lp_fee_income * prices).sum() + # LP fee revenue: noise-trader fees only. + # Arb fee income is excluded — arb trades are net-negative for LPs + # (IL exceeds the fee collected), so reporting arb fees as "revenue" + # is misleading. + lp_fee_revenue_usd = noise_fee_income + + # Blessed-arb LVR return: arb returns gross profit minus gas + external cost + # to the pool, scaled across effective reserves to preserve quoted price. + if _BLESSED_ARB: + arb_profit_usd = -(applied_trade * prices).sum() + external_cost_applied = 0.5 * arb_fees * (jnp.abs(applied_trade) * prices).sum() + returned_profit = jnp.maximum( + arb_profit_usd - arb_thresh - external_cost_applied, 0.0 + ) + eff_val = (Ra_new + Va) * prices[0] + (Rb_new + Vb) * prices[1] + scale_lvr = 1.0 + returned_profit / jnp.maximum(eff_val, 1e-8) + Ra_new = (Ra_new + Va) * scale_lvr - Va + Rb_new = (Rb_new + Vb) * scale_lvr - Vb + lp_fee_revenue_usd = lp_fee_revenue_usd + returned_profit new_reserves = jnp.array([Ra_new, Rb_new]) - return [new_reserves, Va, Vb], (new_reserves, lp_fee_revenue_usd) + return [ + new_reserves, + Va, + Vb, + step_idx + 1.0, + active_start_ratio, + active_target_ratio, + active_start_step, + active_end_step, + active_enabled, + lp_supply, + ], (new_reserves, lp_fee_revenue_usd) def _reclamm_scan_step_with_fees( @@ -843,6 +1216,10 @@ def _reclamm_scan_step_with_fees( arc_length_speed=0.0, centeredness_scaling=False, protocol_fee_split=0.0, + ste_temperature=10.0, + noise_trader_ratio=0.0, + noise_model="ratio", + noise_params=None, ): """Single scan step for reClAMM pool with fees (reserves only). @@ -861,10 +1238,53 @@ def _reclamm_scan_step_with_fees( arc_length_speed=arc_length_speed, centeredness_scaling=centeredness_scaling, protocol_fee_split=protocol_fee_split, + ste_temperature=ste_temperature, + noise_trader_ratio=noise_trader_ratio, + noise_model=noise_model, + noise_params=noise_params, ) return new_carry, new_reserves +def _reclamm_scan_step_with_fees_full_state( + carry_list, + input_list, + weights, + tokens_to_drop, + active_trade_directions, + n, + centeredness_margin, + daily_price_shift_base, + seconds_per_step, + arc_length_speed=0.0, + centeredness_scaling=False, + protocol_fee_split=0.0, + ste_temperature=10.0, + noise_trader_ratio=0.0, + noise_model="ratio", + noise_params=None, +): + """TEST-ONLY: fee scan step that also outputs virtual balances.""" + new_carry, (new_reserves, _fee_rev) = _reclamm_scan_step_with_fees_and_revenue( + carry_list, input_list, + weights=weights, + tokens_to_drop=tokens_to_drop, + active_trade_directions=active_trade_directions, + n=n, + centeredness_margin=centeredness_margin, + daily_price_shift_base=daily_price_shift_base, + seconds_per_step=seconds_per_step, + arc_length_speed=arc_length_speed, + centeredness_scaling=centeredness_scaling, + protocol_fee_split=protocol_fee_split, + ste_temperature=ste_temperature, + noise_trader_ratio=noise_trader_ratio, + noise_model=noise_model, + noise_params=noise_params, + ) + return new_carry, (new_reserves, new_carry[1], new_carry[2]) + + @jit def _jax_calc_reclamm_reserves_zero_fees( initial_reserves, @@ -876,6 +1296,8 @@ def _jax_calc_reclamm_reserves_zero_fees( seconds_per_step, arc_length_speed=0.0, centeredness_scaling=False, + ste_temperature=10.0, + lp_supply_array=None, ): """Calculate reClAMM reserves over time with zero fees. @@ -897,12 +1319,22 @@ def _jax_calc_reclamm_reserves_zero_fees( If > 0, use constant-arc-length thermostat instead of geometric. centeredness_scaling : bool If True, scale speed by margin/centeredness (proportional controller). + lp_supply_array : jnp.ndarray, optional + LP token supply over time, shape (T,). Defaults to constant 1.0. Returns ------- reserves : jnp.ndarray, shape (T, 2) Real reserves over time. """ + if lp_supply_array is None: + lp_supply_array = jnp.array(1.0) + lp_supply_array = jnp.where( + lp_supply_array.size == 1, + jnp.full(prices.shape[0], lp_supply_array), + lp_supply_array, + ) + scan_fn = Partial( _reclamm_scan_step_zero_fees, centeredness_margin=centeredness_margin, @@ -910,10 +1342,11 @@ def _jax_calc_reclamm_reserves_zero_fees( seconds_per_step=seconds_per_step, arc_length_speed=arc_length_speed, centeredness_scaling=centeredness_scaling, + ste_temperature=ste_temperature, ) - carry_init = [initial_reserves, initial_Va, initial_Vb] - _, reserves = scan(scan_fn, carry_init, prices) + carry_init = [initial_reserves, initial_Va, initial_Vb, lp_supply_array[0]] + _, reserves = scan(scan_fn, carry_init, [prices, lp_supply_array]) return reserves @@ -928,8 +1361,10 @@ def _jax_calc_reclamm_reserves_zero_fees_full_state( seconds_per_step, arc_length_speed=0.0, centeredness_scaling=False, + ste_temperature=10.0, + lp_supply_array=None, ): - """Like _jax_calc_reclamm_reserves_zero_fees but also returns virtual balances. + """TEST-ONLY: Like _jax_calc_reclamm_reserves_zero_fees but returns Va/Vb. Returns ------- @@ -937,6 +1372,14 @@ def _jax_calc_reclamm_reserves_zero_fees_full_state( Va_history : jnp.ndarray, shape (T,) Vb_history : jnp.ndarray, shape (T,) """ + if lp_supply_array is None: + lp_supply_array = jnp.array(1.0) + lp_supply_array = jnp.where( + lp_supply_array.size == 1, + jnp.full(prices.shape[0], lp_supply_array), + lp_supply_array, + ) + scan_fn = Partial( _reclamm_scan_step_zero_fees_full_state, centeredness_margin=centeredness_margin, @@ -944,14 +1387,17 @@ def _jax_calc_reclamm_reserves_zero_fees_full_state( seconds_per_step=seconds_per_step, arc_length_speed=arc_length_speed, centeredness_scaling=centeredness_scaling, + ste_temperature=ste_temperature, ) - carry_init = [initial_reserves, initial_Va, initial_Vb] - _, (reserves, Va_history, Vb_history) = scan(scan_fn, carry_init, prices) + carry_init = [initial_reserves, initial_Va, initial_Vb, lp_supply_array[0]] + _, (reserves, Va_history, Vb_history) = scan( + scan_fn, carry_init, [prices, lp_supply_array] + ) return reserves, Va_history, Vb_history -@jit +@partial(jit, static_argnames=("noise_model",)) def _jax_calc_reclamm_reserves_with_fees( initial_reserves, initial_Va, @@ -967,12 +1413,31 @@ def _jax_calc_reclamm_reserves_with_fees( arc_length_speed=0.0, centeredness_scaling=False, protocol_fee_split=0.0, + ste_temperature=10.0, + noise_trader_ratio=0.0, + lp_supply_array=None, + noise_model="ratio", + noise_params=None, + volatility_array=None, + dow_sin_array=None, + dow_cos_array=None, + noise_base_array=None, + noise_tvl_coeff_array=None, + competitor_tvl_array=None, ): """Calculate reClAMM reserves over time with fees. Uses the G3M optimal arb machinery with constant weights [0.5, 0.5] applied to effective reserves (real + virtual). """ + if lp_supply_array is None: + lp_supply_array = jnp.array(1.0) + lp_supply_array = jnp.where( + lp_supply_array.size == 1, + jnp.full(prices.shape[0], lp_supply_array), + lp_supply_array, + ) + n_assets = 2 weights = jnp.array([0.5, 0.5]) gamma = 1.0 - fees @@ -992,6 +1457,8 @@ def _jax_calc_reclamm_reserves_with_fees( gamma_array = jnp.full(prices.shape[0], gamma) arb_thresh_array = jnp.full(prices.shape[0], arb_thresh) arb_fees_array = jnp.full(prices.shape[0], arb_fees) + price_ratio_updates = jnp.zeros((prices.shape[0], 4), dtype=prices.dtype) + price_ratio_updates = price_ratio_updates.at[:, 3].set(jnp.nan) scan_fn = Partial( _reclamm_scan_step_with_fees, @@ -1005,19 +1472,53 @@ def _jax_calc_reclamm_reserves_with_fees( arc_length_speed=arc_length_speed, centeredness_scaling=centeredness_scaling, protocol_fee_split=protocol_fee_split, + ste_temperature=ste_temperature, + noise_trader_ratio=noise_trader_ratio, + noise_model=noise_model, + noise_params=noise_params if noise_params is not None else {}, ) - carry_init = [initial_reserves, initial_Va, initial_Vb] - _, reserves = scan( - scan_fn, - carry_init, - [prices, active_initial_weights, per_asset_ratios, - all_other_assets_ratios, gamma_array, arb_thresh_array, arb_fees_array], - ) + carry_init = [ + initial_reserves, + initial_Va, + initial_Vb, + jnp.float64(0.0), # step_idx + jnp.float64(0.0), # active_start_ratio + jnp.float64(0.0), # active_target_ratio + jnp.float64(0.0), # active_start_step + jnp.float64(0.0), # active_end_step + jnp.array(False), # active_enabled + lp_supply_array[0], # prev_lp_supply + ] + scan_inputs = [ + prices, + active_initial_weights, + per_asset_ratios, + all_other_assets_ratios, + gamma_array, + arb_thresh_array, + arb_fees_array, + price_ratio_updates, + lp_supply_array, + ] + if noise_model in ("tsoukalas_sqrt", "tsoukalas_log", "loglinear"): + scan_inputs.append(volatility_array) + elif noise_model == "calibrated": + scan_inputs.append(volatility_array) + scan_inputs.append(dow_sin_array) + scan_inputs.append(dow_cos_array) + elif noise_model == "market_linear": + scan_inputs.append(noise_base_array) + scan_inputs.append(noise_tvl_coeff_array) + elif noise_model == "mm_observed": + scan_inputs.append(noise_base_array) + scan_inputs.append(competitor_tvl_array) + + _, reserves = scan(scan_fn, carry_init, scan_inputs) return reserves -@partial(jit, static_argnums=(10,)) +@partial(jit, static_argnums=(11,), static_argnames=("noise_model",)) def _jax_calc_reclamm_reserves_with_dynamic_inputs( initial_reserves, initial_Va, @@ -1029,14 +1530,34 @@ def _jax_calc_reclamm_reserves_with_dynamic_inputs( fees, arb_thresh, arb_fees, + price_ratio_updates=None, do_trades=False, trades=None, all_sig_variations=None, arc_length_speed=0.0, centeredness_scaling=False, protocol_fee_split=0.0, + ste_temperature=10.0, + noise_trader_ratio=0.0, + lp_supply_array=None, + noise_model="ratio", + noise_params=None, + volatility_array=None, + dow_sin_array=None, + dow_cos_array=None, + noise_base_array=None, + noise_tvl_coeff_array=None, + competitor_tvl_array=None, ): """Calculate reClAMM reserves with time-varying fees/arb arrays.""" + if lp_supply_array is None: + lp_supply_array = jnp.array(1.0) + lp_supply_array = jnp.where( + lp_supply_array.size == 1, + jnp.full(prices.shape[0], lp_supply_array), + lp_supply_array, + ) + n_assets = 2 weights = jnp.array([0.5, 0.5]) @@ -1048,6 +1569,18 @@ def _jax_calc_reclamm_reserves_with_dynamic_inputs( arb_fees = jnp.where( arb_fees.size == 1, jnp.full(prices.shape[0], arb_fees), arb_fees ) + if price_ratio_updates is None: + price_ratio_updates = jnp.zeros((prices.shape[0], 4), dtype=prices.dtype) + price_ratio_updates = price_ratio_updates.at[:, 3].set(jnp.nan) + else: + if price_ratio_updates.ndim == 1: + price_ratio_updates = jnp.broadcast_to( + price_ratio_updates, (prices.shape[0], price_ratio_updates.shape[0]) + ) + elif price_ratio_updates.shape[0] == 1 and prices.shape[0] != 1: + price_ratio_updates = jnp.broadcast_to( + price_ratio_updates, (prices.shape[0], price_ratio_updates.shape[1]) + ) _, active_trade_directions, tokens_to_drop, leave_one_out_idxs = ( precalc_shared_values_for_all_signatures(all_sig_variations, n_assets) @@ -1072,19 +1605,170 @@ def _jax_calc_reclamm_reserves_with_dynamic_inputs( arc_length_speed=arc_length_speed, centeredness_scaling=centeredness_scaling, protocol_fee_split=protocol_fee_split, + ste_temperature=ste_temperature, + noise_trader_ratio=noise_trader_ratio, + noise_model=noise_model, + noise_params=noise_params if noise_params is not None else {}, ) - carry_init = [initial_reserves, initial_Va, initial_Vb] - _, reserves = scan( - scan_fn, - carry_init, - [prices, active_initial_weights, per_asset_ratios, - all_other_assets_ratios, gamma, arb_thresh, arb_fees], - ) + carry_init = [ + initial_reserves, + initial_Va, + initial_Vb, + jnp.float64(0.0), # step_idx + jnp.float64(0.0), # active_start_ratio + jnp.float64(0.0), # active_target_ratio + jnp.float64(0.0), # active_start_step + jnp.float64(0.0), # active_end_step + jnp.array(False), # active_enabled + lp_supply_array[0], # prev_lp_supply + ] + scan_inputs = [ + prices, + active_initial_weights, + per_asset_ratios, + all_other_assets_ratios, + gamma, + arb_thresh, + arb_fees, + price_ratio_updates, + lp_supply_array, + ] + if noise_model in ("tsoukalas_sqrt", "tsoukalas_log", "loglinear"): + scan_inputs.append(volatility_array) + elif noise_model == "calibrated": + scan_inputs.append(volatility_array) + scan_inputs.append(dow_sin_array) + scan_inputs.append(dow_cos_array) + elif noise_model == "market_linear": + scan_inputs.append(noise_base_array) + scan_inputs.append(noise_tvl_coeff_array) + elif noise_model == "mm_observed": + scan_inputs.append(noise_base_array) + scan_inputs.append(competitor_tvl_array) + + _, reserves = scan(scan_fn, carry_init, scan_inputs) return reserves -@jit +@partial(jit, static_argnums=(11,), static_argnames=("noise_model",)) +def _jax_calc_reclamm_reserves_with_dynamic_inputs_full_state( + initial_reserves, + initial_Va, + initial_Vb, + prices, + centeredness_margin, + daily_price_shift_base, + seconds_per_step, + fees, + arb_thresh, + arb_fees, + price_ratio_updates=None, + do_trades=False, + trades=None, + all_sig_variations=None, + arc_length_speed=0.0, + centeredness_scaling=False, + protocol_fee_split=0.0, + ste_temperature=10.0, + noise_trader_ratio=0.0, + lp_supply_array=None, + noise_model="ratio", + noise_params=None, + volatility_array=None, +): + """TEST-ONLY: dynamic-input reserve path returning virtual-balance history.""" + if lp_supply_array is None: + lp_supply_array = jnp.array(1.0) + lp_supply_array = jnp.where( + lp_supply_array.size == 1, + jnp.full(prices.shape[0], lp_supply_array), + lp_supply_array, + ) + + n_assets = 2 + weights = jnp.array([0.5, 0.5]) + + gamma = jnp.where(fees.size == 1, jnp.full(prices.shape[0], 1.0 - fees), 1.0 - fees) + arb_thresh = jnp.where( + arb_thresh.size == 1, jnp.full(prices.shape[0], arb_thresh), arb_thresh + ) + arb_fees = jnp.where( + arb_fees.size == 1, jnp.full(prices.shape[0], arb_fees), arb_fees + ) + if price_ratio_updates is None: + price_ratio_updates = jnp.zeros((prices.shape[0], 4), dtype=prices.dtype) + price_ratio_updates = price_ratio_updates.at[:, 3].set(jnp.nan) + else: + if price_ratio_updates.ndim == 1: + price_ratio_updates = jnp.broadcast_to( + price_ratio_updates, (prices.shape[0], price_ratio_updates.shape[0]) + ) + elif price_ratio_updates.shape[0] == 1 and prices.shape[0] != 1: + price_ratio_updates = jnp.broadcast_to( + price_ratio_updates, (prices.shape[0], price_ratio_updates.shape[1]) + ) + + _, active_trade_directions, tokens_to_drop, leave_one_out_idxs = ( + precalc_shared_values_for_all_signatures(all_sig_variations, n_assets) + ) + + active_initial_weights, per_asset_ratios, all_other_assets_ratios = ( + precalc_components_of_optimal_trade_across_prices_and_dynamic_fees( + weights, prices, gamma, tokens_to_drop, + active_trade_directions, leave_one_out_idxs, + ) + ) + + scan_fn = Partial( + _reclamm_scan_step_with_fees_full_state, + weights=weights, + tokens_to_drop=tokens_to_drop, + active_trade_directions=active_trade_directions, + n=n_assets, + centeredness_margin=centeredness_margin, + daily_price_shift_base=daily_price_shift_base, + seconds_per_step=seconds_per_step, + arc_length_speed=arc_length_speed, + centeredness_scaling=centeredness_scaling, + protocol_fee_split=protocol_fee_split, + ste_temperature=ste_temperature, + noise_trader_ratio=noise_trader_ratio, + noise_model=noise_model, + noise_params=noise_params if noise_params is not None else {}, + ) + + carry_init = [ + initial_reserves, + initial_Va, + initial_Vb, + jnp.float64(0.0), # step_idx + jnp.float64(0.0), # active_start_ratio + jnp.float64(0.0), # active_target_ratio + jnp.float64(0.0), # active_start_step + jnp.float64(0.0), # active_end_step + jnp.array(False), # active_enabled + lp_supply_array[0], # prev_lp_supply + ] + scan_inputs = [ + prices, + active_initial_weights, + per_asset_ratios, + all_other_assets_ratios, + gamma, + arb_thresh, + arb_fees, + price_ratio_updates, + lp_supply_array, + ] + if noise_model in ("tsoukalas_sqrt", "tsoukalas_log", "loglinear"): + scan_inputs.append(volatility_array) + + _, (reserves, Va_history, Vb_history) = scan(scan_fn, carry_init, scan_inputs) + return reserves, Va_history, Vb_history + + +@partial(jit, static_argnames=("noise_model",)) def _jax_calc_reclamm_reserves_and_fee_revenue_with_fees( initial_reserves, initial_Va, @@ -1100,6 +1784,17 @@ def _jax_calc_reclamm_reserves_and_fee_revenue_with_fees( arc_length_speed=0.0, centeredness_scaling=False, protocol_fee_split=0.0, + ste_temperature=10.0, + noise_trader_ratio=0.0, + lp_supply_array=None, + noise_model="ratio", + noise_params=None, + volatility_array=None, + dow_sin_array=None, + dow_cos_array=None, + noise_base_array=None, + noise_tvl_coeff_array=None, + competitor_tvl_array=None, ): """Calculate reClAMM reserves and LP fee revenue over time with fees. @@ -1109,6 +1804,14 @@ def _jax_calc_reclamm_reserves_and_fee_revenue_with_fees( fee_revenue : jnp.ndarray, shape (T,) LP fee revenue per timestep in USD. """ + if lp_supply_array is None: + lp_supply_array = jnp.array(1.0) + lp_supply_array = jnp.where( + lp_supply_array.size == 1, + jnp.full(prices.shape[0], lp_supply_array), + lp_supply_array, + ) + n_assets = 2 weights = jnp.array([0.5, 0.5]) gamma = 1.0 - fees @@ -1127,6 +1830,8 @@ def _jax_calc_reclamm_reserves_and_fee_revenue_with_fees( gamma_array = jnp.full(prices.shape[0], gamma) arb_thresh_array = jnp.full(prices.shape[0], arb_thresh) arb_fees_array = jnp.full(prices.shape[0], arb_fees) + price_ratio_updates = jnp.zeros((prices.shape[0], 4), dtype=prices.dtype) + price_ratio_updates = price_ratio_updates.at[:, 3].set(jnp.nan) scan_fn = Partial( _reclamm_scan_step_with_fees_and_revenue, @@ -1140,19 +1845,53 @@ def _jax_calc_reclamm_reserves_and_fee_revenue_with_fees( arc_length_speed=arc_length_speed, centeredness_scaling=centeredness_scaling, protocol_fee_split=protocol_fee_split, + ste_temperature=ste_temperature, + noise_trader_ratio=noise_trader_ratio, + noise_model=noise_model, + noise_params=noise_params if noise_params is not None else {}, ) - carry_init = [initial_reserves, initial_Va, initial_Vb] - _, (reserves, fee_revenue) = scan( - scan_fn, - carry_init, - [prices, active_initial_weights, per_asset_ratios, - all_other_assets_ratios, gamma_array, arb_thresh_array, arb_fees_array], - ) + carry_init = [ + initial_reserves, + initial_Va, + initial_Vb, + jnp.float64(0.0), # step_idx + jnp.float64(0.0), # active_start_ratio + jnp.float64(0.0), # active_target_ratio + jnp.float64(0.0), # active_start_step + jnp.float64(0.0), # active_end_step + jnp.array(False), # active_enabled + lp_supply_array[0], # prev_lp_supply + ] + scan_inputs = [ + prices, + active_initial_weights, + per_asset_ratios, + all_other_assets_ratios, + gamma_array, + arb_thresh_array, + arb_fees_array, + price_ratio_updates, + lp_supply_array, + ] + if noise_model in ("tsoukalas_sqrt", "tsoukalas_log", "loglinear"): + scan_inputs.append(volatility_array) + elif noise_model == "calibrated": + scan_inputs.append(volatility_array) + scan_inputs.append(dow_sin_array) + scan_inputs.append(dow_cos_array) + elif noise_model == "market_linear": + scan_inputs.append(noise_base_array) + scan_inputs.append(noise_tvl_coeff_array) + elif noise_model == "mm_observed": + scan_inputs.append(noise_base_array) + scan_inputs.append(competitor_tvl_array) + + _, (reserves, fee_revenue) = scan(scan_fn, carry_init, scan_inputs) return reserves, fee_revenue -@partial(jit, static_argnums=(10,)) +@partial(jit, static_argnums=(11,), static_argnames=("noise_model",)) def _jax_calc_reclamm_reserves_and_fee_revenue_with_dynamic_inputs( initial_reserves, initial_Va, @@ -1164,12 +1903,24 @@ def _jax_calc_reclamm_reserves_and_fee_revenue_with_dynamic_inputs( fees, arb_thresh, arb_fees, + price_ratio_updates=None, do_trades=False, trades=None, all_sig_variations=None, arc_length_speed=0.0, centeredness_scaling=False, protocol_fee_split=0.0, + ste_temperature=10.0, + noise_trader_ratio=0.0, + lp_supply_array=None, + noise_model="ratio", + noise_params=None, + volatility_array=None, + dow_sin_array=None, + dow_cos_array=None, + noise_base_array=None, + noise_tvl_coeff_array=None, + competitor_tvl_array=None, ): """Calculate reClAMM reserves and LP fee revenue with time-varying fees/arb arrays. @@ -1179,6 +1930,14 @@ def _jax_calc_reclamm_reserves_and_fee_revenue_with_dynamic_inputs( fee_revenue : jnp.ndarray, shape (T,) LP fee revenue per timestep in USD. """ + if lp_supply_array is None: + lp_supply_array = jnp.array(1.0) + lp_supply_array = jnp.where( + lp_supply_array.size == 1, + jnp.full(prices.shape[0], lp_supply_array), + lp_supply_array, + ) + n_assets = 2 weights = jnp.array([0.5, 0.5]) @@ -1189,6 +1948,18 @@ def _jax_calc_reclamm_reserves_and_fee_revenue_with_dynamic_inputs( arb_fees = jnp.where( arb_fees.size == 1, jnp.full(prices.shape[0], arb_fees), arb_fees ) + if price_ratio_updates is None: + price_ratio_updates = jnp.zeros((prices.shape[0], 4), dtype=prices.dtype) + price_ratio_updates = price_ratio_updates.at[:, 3].set(jnp.nan) + else: + if price_ratio_updates.ndim == 1: + price_ratio_updates = jnp.broadcast_to( + price_ratio_updates, (prices.shape[0], price_ratio_updates.shape[0]) + ) + elif price_ratio_updates.shape[0] == 1 and prices.shape[0] != 1: + price_ratio_updates = jnp.broadcast_to( + price_ratio_updates, (prices.shape[0], price_ratio_updates.shape[1]) + ) _, active_trade_directions, tokens_to_drop, leave_one_out_idxs = ( precalc_shared_values_for_all_signatures(all_sig_variations, n_assets) @@ -1213,13 +1984,47 @@ def _jax_calc_reclamm_reserves_and_fee_revenue_with_dynamic_inputs( arc_length_speed=arc_length_speed, centeredness_scaling=centeredness_scaling, protocol_fee_split=protocol_fee_split, + ste_temperature=ste_temperature, + noise_trader_ratio=noise_trader_ratio, + noise_model=noise_model, + noise_params=noise_params if noise_params is not None else {}, ) - carry_init = [initial_reserves, initial_Va, initial_Vb] - _, (reserves, fee_revenue) = scan( - scan_fn, - carry_init, - [prices, active_initial_weights, per_asset_ratios, - all_other_assets_ratios, gamma, arb_thresh, arb_fees], - ) + carry_init = [ + initial_reserves, + initial_Va, + initial_Vb, + jnp.float64(0.0), # step_idx + jnp.float64(0.0), # active_start_ratio + jnp.float64(0.0), # active_target_ratio + jnp.float64(0.0), # active_start_step + jnp.float64(0.0), # active_end_step + jnp.array(False), # active_enabled + lp_supply_array[0], # prev_lp_supply + ] + scan_inputs = [ + prices, + active_initial_weights, + per_asset_ratios, + all_other_assets_ratios, + gamma, + arb_thresh, + arb_fees, + price_ratio_updates, + lp_supply_array, + ] + if noise_model in ("tsoukalas_sqrt", "tsoukalas_log", "loglinear"): + scan_inputs.append(volatility_array) + elif noise_model == "calibrated": + scan_inputs.append(volatility_array) + scan_inputs.append(dow_sin_array) + scan_inputs.append(dow_cos_array) + elif noise_model == "market_linear": + scan_inputs.append(noise_base_array) + scan_inputs.append(noise_tvl_coeff_array) + elif noise_model == "mm_observed": + scan_inputs.append(noise_base_array) + scan_inputs.append(competitor_tvl_array) + + _, (reserves, fee_revenue) = scan(scan_fn, carry_init, scan_inputs) return reserves, fee_revenue diff --git a/quantammsim/runners/__init__.py b/quantammsim/runners/__init__.py index 987a800..511800b 100644 --- a/quantammsim/runners/__init__.py +++ b/quantammsim/runners/__init__.py @@ -28,7 +28,7 @@ from .jax_runner_utils import ( nan_rollback, Hashabledict, - get_trades_and_fees, + prepare_dynamic_inputs, get_unique_tokens, OptunaManager, generate_evaluation_points, @@ -80,7 +80,7 @@ # Utilities "nan_rollback", "Hashabledict", - "get_trades_and_fees", + "prepare_dynamic_inputs", "get_unique_tokens", "OptunaManager", "generate_evaluation_points", diff --git a/quantammsim/runners/default_run_fingerprint.py b/quantammsim/runners/default_run_fingerprint.py index 45b9094..f9c002b 100644 --- a/quantammsim/runners/default_run_fingerprint.py +++ b/quantammsim/runners/default_run_fingerprint.py @@ -84,7 +84,7 @@ "return_val": "daily_log_sharpe", "initial_pool_value": 1000000.0, "fees": 0.0, - "protocol_fee_split": 0.0, # fraction of swap fees diverted from LP reserves to protocol treasury + "protocol_fee_split": 0.25, # fraction of swap fees diverted from LP reserves to protocol treasury "arb_fees": 0.0, "gas_cost": 0.0, "use_alt_lamb": False, @@ -98,6 +98,7 @@ "reclamm_interpolation_method": "geometric", # "geometric" or "constant_arc_length" "reclamm_arc_length_speed": None, # auto-calibrate from geometric onset if None "reclamm_centeredness_scaling": False, # scale speed by margin/centeredness + "ste_temperature": 10.0, # STE gate sharpness; higher is closer to hard threshold "reclamm_learn_arc_length_speed": False, # include arc_length_speed in trainable params "reclamm_use_shift_exponent": False, # parametrise shift rate as shift_exponent (log-friendly) "reclamm_learn_fees": False, # include fees in trainable params (Optuna search over fee level) diff --git a/quantammsim/runners/jax_runner_utils.py b/quantammsim/runners/jax_runner_utils.py index 77bcadb..7b85754 100644 --- a/quantammsim/runners/jax_runner_utils.py +++ b/quantammsim/runners/jax_runner_utils.py @@ -14,6 +14,12 @@ raw_fee_like_amounts_to_fee_like_array, raw_trades_to_trade_array, ) +from quantammsim.core_simulator.dynamic_inputs import ( + DynamicInputArrays, + DynamicInputFrames, + dynamic_input_flags_from_frames, + empty_dynamic_input_arrays, +) from quantammsim.apis.rest_apis.simulator_dtos.simulation_run_dto import ( LiquidityPoolCoinDto, @@ -652,7 +658,7 @@ def __eq__(self, other): # These are excluded when creating static_dict from run_fingerprint _TRAINING_ONLY_FIELDS = frozenset({ "optimisation_settings", # Contains lr, optimizer, etc. - "startDateString", # Data loading dates + # startDateString kept in static dict — needed by calibrated noise model "endDateString", "endTestDateString", "subsidary_pools", # Handled separately @@ -669,6 +675,10 @@ def __eq__(self, other): "initial_raw_width", "initial_raw_exponents", "initial_pre_exp_scaling", + # Noise model arrays — loaded from path at runtime, not hashable + "noise_base_array", + "noise_tvl_coeff_array", + "competitor_tvl_array", }) @@ -1053,8 +1063,9 @@ def get_unique_tokens(run_fingerprint): >>> get_unique_tokens(fingerprint) ['BTC', 'DAI', 'ETH'] """ + subsidary_pools = run_fingerprint.get("subsidary_pools", []) all_tokens = [run_fingerprint["tokens"]] + [ - cprd["tokens"] for cprd in run_fingerprint["subsidary_pools"] + cprd["tokens"] for cprd in subsidary_pools ] all_tokens = [item for sublist in all_tokens for item in sublist] unique_tokens = list(set(all_tokens)) @@ -1214,39 +1225,189 @@ def unpermute_list_of_params(list_of_params): return list_of_params_to_return -def get_trades_and_fees( - run_fingerprint, raw_trades, fees_df, gas_cost_df, arb_fees_df, lp_supply_df, do_test_period=False -): - """ - Process trade and fee data for a simulation run. +def _to_dynamic_input_arrays( + trades_array, + fees_array, + gas_cost_array, + arb_fees_array, + lp_supply_array, + reclamm_price_ratio_updates_array, +) -> DynamicInputArrays: + """Normalize optional numpy arrays into the hot-path container.""" + empty = empty_dynamic_input_arrays() + return DynamicInputArrays( + trades=None if trades_array is None else jnp.asarray(trades_array, dtype=jnp.float64), + fees=empty.fees if fees_array is None else jnp.asarray(fees_array, dtype=jnp.float64), + gas_cost=empty.gas_cost if gas_cost_array is None else jnp.asarray(gas_cost_array, dtype=jnp.float64), + arb_fees=empty.arb_fees if arb_fees_array is None else jnp.asarray(arb_fees_array, dtype=jnp.float64), + lp_supply=empty.lp_supply if lp_supply_array is None else jnp.asarray(lp_supply_array, dtype=jnp.float64), + reclamm_price_ratio_updates=( + empty.reclamm_price_ratio_updates + if reclamm_price_ratio_updates_array is None + else jnp.asarray(reclamm_price_ratio_updates_array, dtype=jnp.float64) + ), + ) - Takes raw trades, fees, gas costs and arbitrage fees and converts them into arrays - suitable for simulation. Handles both training and test periods if specified. - Parameters - ---------- - run_fingerprint : dict - Dictionary containing run configuration including start/end dates and tokens - raw_trades : pd.DataFrame, optional - DataFrame containing raw trade data - fees_df : pd.DataFrame, optional - DataFrame containing fee data - gas_cost_df : pd.DataFrame, optional - DataFrame containing gas cost data - arb_fees_df : pd.DataFrame, optional - DataFrame containing arbitrage fee data - lp_supply_df : pd.DataFrame, optional - DataFrame containing LP supply data - do_test_period : bool, optional - Whether to process data for a test period after training period (default False) +def _coerce_reclamm_price_ratio_updates_to_frame(raw_updates) -> pd.DataFrame: + """Accept DataFrame / CSV path / list[dict] / dict payload and return a DataFrame.""" + if raw_updates is None: + return pd.DataFrame( + columns=["unix", "end_unix", "price_ratio", "start_price_ratio"] + ) + if isinstance(raw_updates, pd.DataFrame): + return raw_updates.copy() + if isinstance(raw_updates, (str, Path)): + return pd.read_csv(raw_updates) + if isinstance(raw_updates, list): + return pd.DataFrame(raw_updates) + if isinstance(raw_updates, dict): + for key in ( + "updates", + "rows", + "reclamm_price_ratio_updates", + "price_ratio_updates", + ): + value = raw_updates.get(key) + if isinstance(value, list): + return pd.DataFrame(value) + if isinstance(value, (str, Path)): + return pd.read_csv(value) + return pd.DataFrame(raw_updates) + raise TypeError( + "reclamm_price_ratio_updates must be a DataFrame, CSV path, list of dicts, or dict payload" + ) - Returns - ------- - dict - Contains processed arrays for trades, fees, gas costs and arb fees for both - training and test periods as applicable + +def _ceil_div_nonnegative(delta: int, denom: int) -> int: + """Ceiling division for non-negative integers.""" + if delta <= 0: + return 0 + return (delta + denom - 1) // denom + + +def _normalize_reclamm_price_ratio_updates_for_window( + raw_updates, + start_date_string: str, + end_date_string: str, + arb_frequency: int, +) -> np.ndarray: + """Normalize manual reCLAMM price-ratio updates into per-step event rows. + + Output columns per step: + 0. has_event (0/1) + 1. target_price_ratio + 2. end_step + 3. start_price_ratio_override (NaN when not supplied) """ - # Process raw trades if provided + start_unix = pd.to_datetime(start_date_string, format="%Y-%m-%d %H:%M:%S").value // 10**6 + end_unix = pd.to_datetime(end_date_string, format="%Y-%m-%d %H:%M:%S").value // 10**6 + step_ms = int(arb_frequency) * 60 * 1000 + if step_ms <= 0: + raise ValueError("arb_frequency must be >= 1 for reCLAMM price-ratio updates") + scan_len = int(max((end_unix - start_unix) // step_ms, 0)) + default_matrix = np.zeros((scan_len, 4), dtype=np.float64) + if scan_len > 0: + default_matrix[:, 3] = np.nan + + updates_df = _coerce_reclamm_price_ratio_updates_to_frame(raw_updates) + if updates_df.empty: + return default_matrix + + required = {"unix", "end_unix", "price_ratio"} + missing = sorted(required.difference(updates_df.columns)) + if missing: + raise ValueError( + "reclamm_price_ratio_updates missing required columns: " + + ", ".join(missing) + ) + + updates = updates_df.copy() + updates["unix"] = pd.to_numeric(updates["unix"], errors="coerce") + updates["end_unix"] = pd.to_numeric(updates["end_unix"], errors="coerce") + updates["price_ratio"] = pd.to_numeric(updates["price_ratio"], errors="coerce") + if "start_price_ratio" in updates.columns: + updates["start_price_ratio"] = pd.to_numeric( + updates["start_price_ratio"], errors="coerce" + ) + else: + updates["start_price_ratio"] = np.nan + + invalid_required = updates["unix"].isna() | updates["end_unix"].isna() | updates["price_ratio"].isna() + if invalid_required.any(): + raise ValueError( + "reclamm_price_ratio_updates contains non-numeric unix/end_unix/price_ratio values" + ) + if (updates["price_ratio"] <= 1.0).any(): + raise ValueError("reclamm price_ratio values must be > 1.0") + if (updates["end_unix"] < updates["unix"]).any(): + raise ValueError("reclamm end_unix must be >= unix for every update") + + updates = updates.sort_values("unix", kind="stable") + + for _, row in updates.iterrows(): + event_start_unix = int(row["unix"]) + event_end_unix = int(row["end_unix"]) + target_price_ratio = float(row["price_ratio"]) + start_price_ratio_override = row["start_price_ratio"] + + # Event completes before window start - no effect. + if event_end_unix <= start_unix: + continue + + in_progress_pre_window = event_start_unix < start_unix and event_end_unix > start_unix + if in_progress_pre_window and pd.isna(start_price_ratio_override): + raise ValueError( + "reclamm pre-window in-progress event requires start_price_ratio" + ) + + effective_start_unix = max(event_start_unix, start_unix) + start_step = _ceil_div_nonnegative(effective_start_unix - start_unix, step_ms) + end_step = _ceil_div_nonnegative(event_end_unix - start_unix, step_ms) + + # Starts after current window. + if start_step >= scan_len: + continue + + end_step = min(max(end_step, start_step), scan_len - 1) + default_matrix[start_step, 0] = 1.0 + default_matrix[start_step, 1] = target_price_ratio + default_matrix[start_step, 2] = float(end_step) + default_matrix[start_step, 3] = ( + float(start_price_ratio_override) + if not pd.isna(start_price_ratio_override) + else np.nan + ) + + return default_matrix + + +def _has_reclamm_schedule_events(schedule_array: Optional[np.ndarray]) -> bool: + """Return True when a normalized schedule contains at least one event row.""" + if schedule_array is None: + return False + if schedule_array.size == 0: + return False + return bool(np.any(np.asarray(schedule_array)[:, 0] > 0.5)) + + +def prepare_dynamic_inputs( + run_fingerprint, + dynamic_input_frames: Optional[DynamicInputFrames] = None, + do_test_period: bool = False, +): + """Convert optional pandas inputs into dynamic input bundles.""" + if dynamic_input_frames is None: + dynamic_input_frames = DynamicInputFrames() + + raw_trades = dynamic_input_frames.trades + fees_df = dynamic_input_frames.fees + gas_cost_df = dynamic_input_frames.gas_cost + arb_fees_df = dynamic_input_frames.arb_fees + lp_supply_df = dynamic_input_frames.lp_supply + reclamm_price_ratio_updates = dynamic_input_frames.reclamm_price_ratio_updates + dynamic_input_flags = dynamic_input_flags_from_frames(dynamic_input_frames) + if raw_trades is not None: train_period_trades = raw_trades_to_trade_array( raw_trades, @@ -1264,7 +1425,7 @@ def get_trades_and_fees( else: train_period_trades = None test_period_trades = None - # Process fees, gas costs, and arb fees if provided + fees_array = ( raw_fee_like_amounts_to_fee_like_array( fees_df, @@ -1280,8 +1441,8 @@ def get_trades_and_fees( test_fees_array = ( raw_fee_like_amounts_to_fee_like_array( fees_df, - run_fingerprint["startDateString"], run_fingerprint["endDateString"], + run_fingerprint["endTestDateString"], names=["fees"], fill_method="ffill", ) @@ -1359,26 +1520,117 @@ def get_trades_and_fees( if lp_supply_df is not None else None ) + + # Subsample minute-resolution dynamic inputs to match arb_frequency so + # materialize_dynamic_inputs sees the same scan_len the pool's scan loop + # uses (scan_len = (bout_length - 1) // arb_frequency). + arb_freq = run_fingerprint.get("arb_frequency", 1) + if arb_freq > 1: + if fees_array is not None: + fees_array = fees_array[::arb_freq] + if gas_cost_array is not None: + gas_cost_array = gas_cost_array[::arb_freq] + if arb_fees_array is not None: + arb_fees_array = arb_fees_array[::arb_freq] + if lp_supply_array is not None: + lp_supply_array = lp_supply_array[::arb_freq] + if do_test_period: + if test_fees_array is not None: + test_fees_array = test_fees_array[::arb_freq] + if test_gas_cost_array is not None: + test_gas_cost_array = test_gas_cost_array[::arb_freq] + if test_arb_fees_array is not None: + test_arb_fees_array = test_arb_fees_array[::arb_freq] + if test_lp_supply_array is not None: + test_lp_supply_array = test_lp_supply_array[::arb_freq] + + reclamm_price_ratio_updates_array = ( + _normalize_reclamm_price_ratio_updates_for_window( + reclamm_price_ratio_updates, + run_fingerprint["startDateString"], + run_fingerprint["endDateString"], + run_fingerprint["arb_frequency"], + ) + if reclamm_price_ratio_updates is not None + else None + ) + if do_test_period: + test_reclamm_price_ratio_updates_array = ( + _normalize_reclamm_price_ratio_updates_for_window( + reclamm_price_ratio_updates, + run_fingerprint["endDateString"], + run_fingerprint["endTestDateString"], + run_fingerprint["arb_frequency"], + ) + if reclamm_price_ratio_updates is not None + else None + ) + + train_has_reclamm_schedule = _has_reclamm_schedule_events( + reclamm_price_ratio_updates_array + ) + test_has_reclamm_schedule = False + if do_test_period: + test_has_reclamm_schedule = _has_reclamm_schedule_events( + test_reclamm_price_ratio_updates_array + ) + + if not train_has_reclamm_schedule: + reclamm_price_ratio_updates_array = None + if do_test_period and not test_has_reclamm_schedule: + test_reclamm_price_ratio_updates_array = None + if not train_has_reclamm_schedule and ( + not do_test_period or not test_has_reclamm_schedule + ): + dynamic_input_flags["has_reclamm_price_ratio_updates"] = False + dynamic_input_flags["use_dynamic_inputs"] = any( + value + for key, value in dynamic_input_flags.items() + if key != "use_dynamic_inputs" + ) + + # Unit LP supply is the neutral case; keep it on the static hot path. + if lp_supply_array is not None and np.allclose(lp_supply_array, 1.0): + lp_supply_array = None + if not do_test_period or test_lp_supply_array is None or np.allclose(test_lp_supply_array, 1.0): + dynamic_input_flags["has_lp_supply"] = False + dynamic_input_flags["use_dynamic_inputs"] = any( + value for key, value in dynamic_input_flags.items() if key != "use_dynamic_inputs" + ) + + if do_test_period and test_lp_supply_array is not None and np.allclose(test_lp_supply_array, 1.0): + test_lp_supply_array = None + if do_test_period: return { - "train_period_trades": train_period_trades, - "test_period_trades": test_period_trades, - "fees_array": fees_array, - "gas_cost_array": gas_cost_array, - "arb_fees_array": arb_fees_array, - "lp_supply_array": lp_supply_array, - "test_fees_array": test_fees_array, - "test_gas_cost_array": test_gas_cost_array, - "test_arb_fees_array": test_arb_fees_array, - "test_lp_supply_array": test_lp_supply_array, - } - else: - return { - "train_period_trades": train_period_trades, - "fees_array": fees_array, - "gas_cost_array": gas_cost_array, - "arb_fees_array": arb_fees_array, - "lp_supply_array": lp_supply_array, + "train_dynamic_inputs": _to_dynamic_input_arrays( + train_period_trades, + fees_array, + gas_cost_array, + arb_fees_array, + lp_supply_array, + reclamm_price_ratio_updates_array, + ), + "test_dynamic_inputs": _to_dynamic_input_arrays( + test_period_trades, + test_fees_array, + test_gas_cost_array, + test_arb_fees_array, + test_lp_supply_array, + test_reclamm_price_ratio_updates_array, + ), + "dynamic_input_flags": dynamic_input_flags, } + return { + "train_dynamic_inputs": _to_dynamic_input_arrays( + train_period_trades, + fees_array, + gas_cost_array, + arb_fees_array, + lp_supply_array, + reclamm_price_ratio_updates_array, + ), + "dynamic_input_flags": dynamic_input_flags, + } def create_daily_unix_array(start_date_str, end_date_str): @@ -1621,12 +1873,22 @@ def try_forward_pass(n_sets: int) -> bool: "n_assets": n_tokens, "training_data_kind": probe_fingerprint["optimisation_settings"]["training_data_kind"], "do_trades": False, + "dynamic_input_flags": { + "use_dynamic_inputs": False, + "has_trades": False, + "has_dynamic_fees": False, + "has_dynamic_gas_cost": False, + "has_dynamic_arb_fees": False, + "has_lp_supply": False, + "has_reclamm_price_ratio_updates": False, + }, }, ) # Create vmapped forward pass partial_forward = Partial( forward_pass_nograd, + dynamic_inputs=None, prices=data_dict["prices"], static_dict=static_dict, pool=pool, diff --git a/quantammsim/runners/jax_runners.py b/quantammsim/runners/jax_runners.py index 62d5ad4..d7671f9 100644 --- a/quantammsim/runners/jax_runners.py +++ b/quantammsim/runners/jax_runners.py @@ -53,6 +53,10 @@ forward_pass_nograd, _calculate_return_value, ) +from quantammsim.core_simulator.dynamic_inputs import ( + DynamicInputFrames, + materialize_dynamic_inputs, +) from quantammsim.core_simulator.windowing_utils import get_indices, filter_coarse_weights_by_data_indices import hashlib @@ -80,7 +84,7 @@ from quantammsim.runners.jax_runner_utils import ( Hashabledict, - get_trades_and_fees, + prepare_dynamic_inputs, get_unique_tokens, OptunaManager, generate_evaluation_points, @@ -159,7 +163,10 @@ def _build_scan_infrastructure( run_scan_chunk : callable ``@jit`` wrapped ``lax.scan(scan_body, carry, None, length=chunk_size)``. scan_body : callable - The raw scan body (for partial-chunk Python fallback). + The raw scan body. + run_scan_step : callable + ``@jit`` wrapped single-step execution used for remainder iterations so + partial chunks follow the same numerics as the full scan path. """ # Local aliases for closed-over constants _start_idx = start_idx @@ -309,7 +316,11 @@ def scan_body(carry, _): def _run_scan_chunk(carry): return lax.scan(scan_body, carry, None, length=chunk_size) - return _run_scan_chunk, scan_body + @jit + def _run_scan_step(carry): + return scan_body(carry, None) + + return _run_scan_chunk, scan_body, _run_scan_step def train_on_historic_data( @@ -673,6 +684,15 @@ def _train_on_historic_data_impl( "n_assets": n_assets, "training_data_kind": run_fingerprint["optimisation_settings"]["training_data_kind"], "do_trades": False, + "dynamic_input_flags": { + "use_dynamic_inputs": False, + "has_trades": False, + "has_dynamic_fees": False, + "has_dynamic_gas_cost": False, + "has_dynamic_arb_fees": False, + "has_lp_supply": False, + "has_reclamm_price_ratio_updates": False, + }, }, ) @@ -694,6 +714,7 @@ def _train_on_historic_data_impl( continuous_static_dict["bout_length"] = original_bout_length + data_dict["bout_length_test"] partial_forward_pass_nograd_batch_continuous = Partial( forward_pass_nograd, + dynamic_inputs=None, static_dict=Hashabledict(continuous_static_dict), pool=pool, ) @@ -834,11 +855,12 @@ def init_optimizer(params): ) if config_key in _scan_infra_cache: - _run_scan_chunk, scan_body = _scan_infra_cache[config_key] + _run_scan_chunk, scan_body, _run_scan_step = _scan_infra_cache[config_key] else: # Build scan-compatible update (prices as explicit arg, not closure) partial_step_no_prices = Partial( forward_pass, + dynamic_inputs=None, static_dict=Hashabledict(base_static_dict), pool=pool, ) @@ -852,7 +874,7 @@ def init_optimizer(params): partial_step_no_prices, params_in_axes_dict, ) - _run_scan_chunk, scan_body = _build_scan_infrastructure( + _run_scan_chunk, scan_body, _run_scan_step = _build_scan_infrastructure( chunk_size, partial_step_no_prices=partial_step_no_prices, forward_nograd_continuous=partial_forward_pass_nograd_continuous, @@ -878,7 +900,7 @@ def init_optimizer(params): swa_freq=swa_freq, n_parameter_sets=n_parameter_sets, ) - _scan_infra_cache[config_key] = (_run_scan_chunk, scan_body) + _scan_infra_cache[config_key] = (_run_scan_chunk, scan_body, _run_scan_step) # ── Initialize carry (prices & nan_bank in carry, not closures) ── carry = { @@ -933,7 +955,7 @@ def init_optimizer(params): "params": {k: [] for k in carry["params"]}, } for _ in range(actual): - carry, step_out = scan_body(carry, None) + carry, step_out = _run_scan_step(carry) all_per_steps["objective"].append(step_out["objective"]) all_per_steps["train_metrics"].append(step_out["train_metrics"]) all_per_steps["test_metrics"].append(step_out["test_metrics"]) @@ -1276,7 +1298,9 @@ def _extract_params_at(params_tree, j): return selected_params elif run_fingerprint["optimisation_settings"]["method"] == "optuna": - n_evaluation_points = 20 + n_evaluation_points = run_fingerprint["optimisation_settings"].get( + "optuna_settings", {} + ).get("n_evaluation_points", 20) min_spacing = data_dict["bout_length"] // 2 # E run_fingerprint["optimisation_settings"]["n_parameter_sets"] = 1 @@ -1395,7 +1419,14 @@ def objective(trial): ) train_objectives.append(train_value) - mean_train_value = jnp.sum(jnp.array(train_objectives)) / len(train_objectives) + _train_arr = jnp.array(train_objectives) + _robust_temp = run_fingerprint.get("optimisation_settings", {}).get( + "robust_temperature", None) + if _robust_temp is not None: + _weights = jax.nn.softmax(-_train_arr / _robust_temp) + mean_train_value = jnp.sum(_weights * _train_arr) + else: + mean_train_value = jnp.mean(_train_arr) train_value = _calculate_return_value( run_fingerprint["return_val"], train_outputs["reserves"], @@ -1432,6 +1463,17 @@ def objective(trial): initial_reserves=train_outputs["reserves"][0], ) + # Reject catastrophic in-sample configurations + min_train_ret_over_hodl = run_fingerprint["optimisation_settings"][ + "optuna_settings"].get("min_train_returns_over_hodl", None) + if min_train_ret_over_hodl is not None: + if float(train_returns_over_hodl) < min_train_ret_over_hodl: + optuna_manager.logger.info( + f"Training {trial.number}, REJECTED:" + f" ret_over_hodl={train_returns_over_hodl:.4f}" + f" < {min_train_ret_over_hodl}") + return float("-inf") + # Test period evaluation using continuous forward pass # This ensures test metrics reflect continuous simulation from training continuous_outputs = partial_forward_pass_continuous_optuna( @@ -1457,6 +1499,20 @@ def objective(trial): continuous_prices, ) + # Full train-period metric dict, parallel to the BFGS/CMA-ES + # save_multi_params path. Persisted on the trial so + # save_optuna_results_sgd_format can write the same + # list-of-dict schema other methods use. + train_dict_for_metrics = { + "value": train_outputs["value"], + "reserves": train_outputs["reserves"], + } + if "fee_revenue" in train_outputs: + train_dict_for_metrics["fee_revenue"] = train_outputs["fee_revenue"] + train_metrics_dict = calculate_period_metrics( + train_dict_for_metrics, train_outputs["prices"], + ) + # Calculate validation metrics train_length = data_dict["bout_length"] if val_fraction > 0: @@ -1566,6 +1622,14 @@ def objective(trial): trial.set_user_attr("continuous_test_return", continuous_test_metrics["return"]) trial.set_user_attr("continuous_test_returns_over_hodl", continuous_test_metrics["returns_over_hodl"]) trial.set_user_attr("continuous_test_returns_over_uniform_hodl", continuous_test_metrics["returns_over_uniform_hodl"]) + # Full metric dicts for save_optuna_results_sgd_format — + # match the list-of-dict schema produced by save_multi_params + # (BFGS / CMA-ES). Plain floats so optuna can persist them. + # `.item()` handles 0-d and (1,) JAX arrays alike. + def _scalarise(d): + return {k: float(np.asarray(v).reshape(-1)[0]) for k, v in d.items()} + trial.set_user_attr("train_metrics_dict", _scalarise(train_metrics_dict)) + trial.set_user_attr("continuous_test_metrics_dict", _scalarise(continuous_test_metrics)) if run_fingerprint["optimisation_settings"]["optuna_settings"][ "multi_objective" @@ -2166,6 +2230,9 @@ def solve_single(flat_x0): tol = cma_settings["tol"] n_eval_points = cma_settings["n_evaluation_points"] population_size_override = cma_settings.get("population_size") + overfitting_penalty = float(cma_settings.get("overfitting_penalty", 0.0)) + # Penalty only meaningful when there's a held-out validation period. + apply_penalty = overfitting_penalty > 0.0 and val_fraction > 0 # Generate fixed evaluation points (same as BFGS/optuna) min_spacing = data_dict["bout_length"] // 2 @@ -2177,6 +2244,23 @@ def solve_single(flat_x0): min_spacing, run_fingerprint["optimisation_settings"]["initial_random_key"], ) + n_train_eval = len(evaluation_starts) + if apply_penalty: + # Sample evaluation points from the validation period and append + # them to the fixed start indexes. The split point n_train_eval + # separates train-period objectives from val-period objectives. + val_eval_starts = generate_evaluation_points( + val_start_idx, + data_dict["end_idx"], + bout_length_window, + n_eval_points, + min_spacing, + run_fingerprint["optimisation_settings"]["initial_random_key"] + 1, + ) + evaluation_starts = list(evaluation_starts) + list(val_eval_starts) + if verbose: + print(f"[CMA-ES] Overfitting penalty {overfitting_penalty} active: " + f"{n_train_eval} train + {len(val_eval_starts)} val eval points") fixed_start_indexes = jnp.array( [(s, 0) for s in evaluation_starts], dtype=jnp.int32 ) @@ -2239,19 +2323,65 @@ def solve_single(flat_x0): # Build eval function: population (lam, n_flat) -> fitness (lam,) # Each individual is evaluated as -objective (we minimise, objective is maximised) - def eval_single(flat_x): - p = unravel_fn(flat_x) - return -batched_obj(p, fixed_start_indexes) + if apply_penalty: + # Mirror the optuna penalty: penalised = mean_train - α·max(0, mean_train - mean_val) + _alpha = jnp.asarray(overfitting_penalty, dtype=flat_x0_template.dtype) + _split = n_train_eval + def eval_single(flat_x): + p = unravel_fn(flat_x) + per_pt = batched_pts(p, fixed_start_indexes) + mean_train = jnp.mean(per_pt[:_split]) + mean_val = jnp.mean(per_pt[_split:]) + gap = mean_train - mean_val + penalised = mean_train - _alpha * jnp.maximum(0.0, gap) + return -penalised + else: + def eval_single(flat_x): + p = unravel_fn(flat_x) + return -batched_obj(p, fixed_start_indexes) # Un-jitted vmap for fusion into lax.while_loop's XLA program eval_fn_raw = vmap(eval_single) # Standalone jitted version kept for any verbose/diagnostic use eval_population = jit(eval_fn_raw) + # Build box constraints from parameter_config (if available) + param_config = run_fingerprint.get("optimisation_settings", {}).get( + "optuna_settings", {} + ).get("parameter_config", {}) + if param_config: + # Construct lower/upper bound pytrees matching params_single structure + lb_dict = {} + ub_dict = {} + for k, v in params_single.items(): + if k == "subsidary_params": + continue + cfg = param_config.get(k) + if cfg is not None: + lo = jnp.full_like(jnp.asarray(v, dtype=flat_x0_template.dtype), cfg["low"]) + hi = jnp.full_like(jnp.asarray(v, dtype=flat_x0_template.dtype), cfg["high"]) + else: + lo = jnp.full_like(jnp.asarray(v, dtype=flat_x0_template.dtype), -1e30) + hi = jnp.full_like(jnp.asarray(v, dtype=flat_x0_template.dtype), 1e30) + lb_dict[k] = lo + ub_dict[k] = hi + lb_dict["subsidary_params"] = params_single.get("subsidary_params", []) + ub_dict["subsidary_params"] = params_single.get("subsidary_params", []) + flat_lb, _ = ravel_pytree(lb_dict) + flat_ub, _ = ravel_pytree(ub_dict) + if verbose: + print(f"[CMA-ES] Box constraints: {n_flat} dims bounded") + else: + flat_lb = None + flat_ub = None + @jit def _run_one_restart(flat_x0, rng_key): state = init_cmaes(flat_x0, sigma0) - return run_cmaes(state, rng_key, eval_fn_raw, cma_params, n_generations, tol) + return run_cmaes( + state, rng_key, eval_fn_raw, cma_params, n_generations, tol, + lower_bounds=flat_lb, upper_bounds=flat_ub, + ) # Keep initial params for saving initial_params = deepcopy(params) @@ -2478,14 +2608,10 @@ def do_run_on_historic_data( root=None, price_data=None, verbose=False, - raw_trades=None, fees=None, gas_cost=None, arb_fees=None, - fees_df=None, - gas_cost_df=None, - arb_fees_df=None, - lp_supply_df=None, + dynamic_input_frames: DynamicInputFrames = None, do_test_period=False, low_data_mode=False, preslice_burnin=True, @@ -2511,23 +2637,14 @@ def do_run_on_historic_data( Pre-loaded price data. When None, loaded from parquet files. verbose : bool, optional Print progress information (default False). - raw_trades : DataFrame, optional - Real trade data to inject. Columns: unix timestamp (minute), - token_in, token_out, amount_in. fees : float, optional Swap fee override (e.g. 0.003 for 30 bps). gas_cost : float, optional Gas cost override per transaction. arb_fees : float, optional Arbitrageur fee override. - fees_df : DataFrame, optional - Time-varying swap fees (columns: unix, fee). - gas_cost_df : DataFrame, optional - Time-varying gas costs (columns: unix, gas_cost). - arb_fees_df : DataFrame, optional - Time-varying arb fees (columns: unix, arb_fee). - lp_supply_df : DataFrame, optional - Time-varying LP supply changes. + dynamic_input_frames : DynamicInputFrames, optional + Optional container of trades / fee / gas / arb / LP supply DataFrames. do_test_period : bool, optional If True, also run the OOS test period defined by ``endDateString`` to ``endTestDateString`` (default False). @@ -2572,15 +2689,21 @@ def do_run_on_historic_data( np.random.seed(0) - dynamic_inputs_dict = get_trades_and_fees( + dynamic_inputs_dict = prepare_dynamic_inputs( run_fingerprint, - raw_trades, - fees_df, - gas_cost_df, - arb_fees_df, - lp_supply_df, + dynamic_input_frames=dynamic_input_frames, do_test_period=do_test_period, ) + train_dynamic_inputs = ( + dynamic_inputs_dict["train_dynamic_inputs"] + if dynamic_inputs_dict["dynamic_input_flags"]["use_dynamic_inputs"] + else None + ) + test_dynamic_inputs = ( + dynamic_inputs_dict.get("test_dynamic_inputs") + if dynamic_inputs_dict["dynamic_input_flags"]["use_dynamic_inputs"] + else None + ) # Load price data if not provided if price_data is None: @@ -2624,7 +2747,8 @@ def do_run_on_historic_data( "fees": fees if fees is not None else run_fingerprint["fees"], "arb_fees": arb_fees if arb_fees is not None else run_fingerprint["arb_fees"], "gas_cost": gas_cost if gas_cost is not None else run_fingerprint["gas_cost"], - "do_trades": False if raw_trades is None else run_fingerprint["do_trades"], + "do_trades": dynamic_inputs_dict["dynamic_input_flags"]["has_trades"], + "dynamic_input_flags": dynamic_inputs_dict["dynamic_input_flags"], # Include date strings for run-time use "startDateString": run_fingerprint["startDateString"], "endDateString": run_fingerprint["endDateString"], @@ -2680,10 +2804,7 @@ def do_run_on_historic_data( param, (data_dict["start_idx"], 0), data_dict["prices"], - dynamic_inputs_dict["train_period_trades"], - dynamic_inputs_dict["fees_array"], - dynamic_inputs_dict["gas_cost_array"], - dynamic_inputs_dict["arb_fees_array"], + train_dynamic_inputs, ) if low_data_mode: output_dict["final_prices"] = output_dict["prices"][-1] @@ -2699,10 +2820,7 @@ def do_run_on_historic_data( param, (data_dict["start_idx_test"], 0), data_dict["prices"], - dynamic_inputs_dict["test_period_trades"], - dynamic_inputs_dict["test_fees_array"], - dynamic_inputs_dict["test_gas_cost_array"], - dynamic_inputs_dict["test_arb_fees_array"], + test_dynamic_inputs, ) if low_data_mode: output_dict_test["final_prices"] = output_dict_test["prices"][-1] @@ -2741,14 +2859,10 @@ def do_run_on_historic_data_with_provided_coarse_weights( root=None, price_data=None, verbose=False, - raw_trades=None, fees=None, gas_cost=None, arb_fees=None, - fees_df=None, - gas_cost_df=None, - arb_fees_df=None, - lp_supply_df=None, + dynamic_input_frames: DynamicInputFrames = None, do_test_period=False, low_data_mode=False, ): @@ -2778,22 +2892,14 @@ def do_run_on_historic_data_with_provided_coarse_weights( Pre-loaded price data. verbose : bool, optional Print progress (default False). - raw_trades : DataFrame, optional - Real trade data to inject. fees : float, optional Swap fee override. gas_cost : float, optional Gas cost override. arb_fees : float, optional Arbitrageur fee override. - fees_df : DataFrame, optional - Time-varying swap fees. - gas_cost_df : DataFrame, optional - Time-varying gas costs. - arb_fees_df : DataFrame, optional - Time-varying arb fees. - lp_supply_df : DataFrame, optional - Time-varying LP supply changes. + dynamic_input_frames : DynamicInputFrames, optional + Optional container of trades / fee / gas / arb / LP supply DataFrames. do_test_period : bool, optional Run OOS test period (default False). low_data_mode : bool, optional @@ -2832,13 +2938,9 @@ def do_run_on_historic_data_with_provided_coarse_weights( np.random.seed(0) - dynamic_inputs_dict = get_trades_and_fees( + dynamic_inputs_dict = prepare_dynamic_inputs( run_fingerprint, - raw_trades, - fees_df, - gas_cost_df, - arb_fees_df, - lp_supply_df, + dynamic_input_frames=dynamic_input_frames, do_test_period=do_test_period, ) @@ -2881,7 +2983,8 @@ def do_run_on_historic_data_with_provided_coarse_weights( "fees": fees if fees is not None else run_fingerprint["fees"], "arb_fees": arb_fees if arb_fees is not None else run_fingerprint["arb_fees"], "gas_cost": gas_cost if gas_cost is not None else run_fingerprint["gas_cost"], - "do_trades": False if raw_trades is None else run_fingerprint["do_trades"], + "do_trades": dynamic_inputs_dict["dynamic_input_flags"]["has_trades"], + "dynamic_input_flags": dynamic_inputs_dict["dynamic_input_flags"], # Include date strings for run-time use "startDateString": run_fingerprint["startDateString"], "endDateString": run_fingerprint["endDateString"], @@ -2912,11 +3015,16 @@ def do_run_on_historic_data_with_provided_coarse_weights( initial_weights, minimum_weight, params, + jnp.zeros_like(initial_weights), + jnp.ones_like(initial_weights), run_fingerprint["max_memory_days"], chunk_period, chunk_period, 1.0, False, + False, + False, + False, ) weights = _jax_fine_weights_from_actual_starts_and_diffs( @@ -2948,78 +3056,35 @@ def do_run_on_historic_data_with_provided_coarse_weights( # weights=HashableArrayWrapper(weights), # initial_reserves=HashableArrayWrapper(params["initial_reserves"]), # ) - fees_array = dynamic_inputs_dict.get("fees_array") - arb_thresh_array = dynamic_inputs_dict.get("gas_cost_array") - arb_fees_array = dynamic_inputs_dict.get("arb_fees_array") - trade_array = dynamic_inputs_dict.get("trades") - lp_supply_array = dynamic_inputs_dict.get("lp_supply_array") - - if fees_array is None: - fees_array = jnp.array([static_dict["fees"]]) - if arb_thresh_array is None: - arb_thresh_array = jnp.array([static_dict["gas_cost"]]) - if arb_fees_array is None: - arb_fees_array = jnp.array([static_dict["arb_fees"]]) - - # initial_pool_value = run_fingerprint["initial_pool_value"] - # initial_value_per_token = arb_acted_upon_weights[0] * initial_pool_value - # initial_reserves = initial_value_per_token / arb_acted_upon_local_prices[0] - + dynamic_input_flags = dynamic_inputs_dict["dynamic_input_flags"] + dynamic_inputs = dynamic_inputs_dict["train_dynamic_inputs"] initial_reserves = params["initial_reserves"] - - # any of fees_array, arb_thresh_array, arb_fees_array, trade_array, and lp_supply_array - # can be singletons, in which case we repeat them for the length of the bout. - - # Determine the maximum leading dimension max_len = bout_length - 1 if run_fingerprint["arb_frequency"] != 1: max_len = max_len // run_fingerprint["arb_frequency"] - - fees_array = fees_array[:max_len] - arb_thresh_array = arb_thresh_array[:max_len] - arb_thresh_array = arb_thresh_array * 0.0 - arb_fees_array = arb_fees_array[:max_len] - if lp_supply_array is not None: - lp_supply_array = lp_supply_array[:max_len] - if trade_array is not None: - trade_array = trade_array[:max_len] - # Broadcast input arrays to match the maximum leading dimension. - # If they are singletons, this will just repeat them for the length of the bout. - # If they are arrays of length bout_length, this will cause no change. - fees_array_broadcast = jnp.broadcast_to( - fees_array, (max_len,) + fees_array.shape[1:] - ) - arb_thresh_array_broadcast = jnp.broadcast_to( - arb_thresh_array, (max_len,) + arb_thresh_array.shape[1:] - ) - arb_fees_array_broadcast = jnp.broadcast_to( - arb_fees_array, (max_len,) + arb_fees_array.shape[1:] - ) - # if lp_supply_array is not provided, we set it to a constant of 1.0 - if lp_supply_array is None: - lp_supply_array = jnp.array(1.0) - - lp_supply_array_broadcast = jnp.broadcast_to( - lp_supply_array, (max_len,) + lp_supply_array.shape[1:] + materialized_inputs = materialize_dynamic_inputs( + dynamic_inputs, + dynamic_input_flags, + static_dict, + scan_len=max_len, + do_trades=run_fingerprint["do_trades"], + dtype=local_prices.dtype, ) - # if we are doing trades, the trades array must be of the same length as the other arrays - if run_fingerprint["do_trades"]: - assert trade_array.shape[0] == max_len protocol_fee_split = run_fingerprint.get("protocol_fee_split", 0.0) reserves = _jax_calc_quantAMM_reserves_with_dynamic_inputs( initial_reserves, weights, local_prices, - fees_array_broadcast, - arb_thresh_array_broadcast, - arb_fees_array_broadcast, + materialized_inputs.fees, + materialized_inputs.gas_cost, + materialized_inputs.arb_fees, jnp.array(static_dict["all_sig_variations"]), - None, + materialized_inputs.trades, run_fingerprint["do_trades"], run_fingerprint["do_arb"], run_fingerprint["noise_trader_ratio"], - lp_supply_array_broadcast, + materialized_inputs.lp_supply, protocol_fee_split=protocol_fee_split, ) diff --git a/quantammsim/runners/multi_period_sgd.py b/quantammsim/runners/multi_period_sgd.py index 7802968..90d5f4a 100644 --- a/quantammsim/runners/multi_period_sgd.py +++ b/quantammsim/runners/multi_period_sgd.py @@ -430,6 +430,7 @@ def multi_period_sgd_training( # Create base forward pass base_forward_pass = Partial( forward_pass, + dynamic_inputs=None, prices=data_dict["prices"], static_dict=Hashabledict(static_dict), pool=pool, @@ -465,11 +466,14 @@ def multi_period_sgd_training( opt_state = optimizer.init(params) # Use existing factory - it handles batching, gradients, optimizer application + robust_temp = run_fingerprint["optimisation_settings"].get( + "robust_temperature", None) update_fn = update_from_partial_training_step_factory_with_optax( partial_training_step, optimizer, run_fingerprint["optimisation_settings"]["train_on_hessian_trace"], Partial(partial_training_step, start_index=(data_dict["start_idx"], 0)), + robust_temperature=robust_temp, ) # Training loop @@ -506,6 +510,7 @@ def multi_period_sgd_training( partial_nograd = jit(Partial( forward_pass_nograd, + dynamic_inputs=None, prices=data_dict["prices"], static_dict=Hashabledict(static_dict), pool=pool, diff --git a/quantammsim/runners/training_evaluator.py b/quantammsim/runners/training_evaluator.py index e79a071..011b3f7 100644 --- a/quantammsim/runners/training_evaluator.py +++ b/quantammsim/runners/training_evaluator.py @@ -742,6 +742,7 @@ def _compute_metrics( eval_fn = jit(Partial( forward_pass_nograd, + dynamic_inputs=None, prices=data_dict["prices"], static_dict=Hashabledict(static_dict), pool=pool, diff --git a/quantammsim/simulator_analysis_tools/finance/param_financial_calculator.py b/quantammsim/simulator_analysis_tools/finance/param_financial_calculator.py index f92f7da..cc000b0 100644 --- a/quantammsim/simulator_analysis_tools/finance/param_financial_calculator.py +++ b/quantammsim/simulator_analysis_tools/finance/param_financial_calculator.py @@ -20,6 +20,7 @@ from quantammsim.runners.jax_runners import do_run_on_historic_data from quantammsim.runners.jax_runner_utils import optimized_output_conversion +from quantammsim.core_simulator.dynamic_inputs import DynamicInputFrames import quantammsim.simulator_analysis_tools.finance.financial_analysis_calculator as fac import quantammsim.simulator_analysis_tools.finance.financial_analysis_functions as faf import quantammsim.simulator_analysis_tools.finance.financial_analysis_utils as fau @@ -238,6 +239,15 @@ def run_pool_simulation(simulationRunDto): run_fingerprint["fees"] = static_fee fee_steps_df = None + dynamic_input_frames = DynamicInputFrames( + trades=raw_trades, + fees=fee_steps_df, + gas_cost=gas_cost_df, + reclamm_price_ratio_updates=run_fingerprint.get( + "reclamm_price_ratio_updates" + ), + ) + print("run fingerprint-------------------", run_fingerprint) print("update rule parameter dict converted-------------------", update_rule_parameter_dict_converted) outputDict = do_run_on_historic_data( @@ -247,9 +257,7 @@ def run_pool_simulation(simulationRunDto): price_data=price_data_local, verbose=True, do_test_period=False, - raw_trades=raw_trades, - gas_cost_df=gas_cost_df, - fees_df=fee_steps_df + dynamic_input_frames=dynamic_input_frames, ) print("outputDict: ", outputDict.keys()) resultTimeSteps = optimized_output_conversion(simulationRunDto, outputDict, tokens) @@ -293,9 +301,7 @@ def run_pool_simulation(simulationRunDto): price_data=price_data_local, verbose=False, do_test_period=False, - raw_trades=raw_trades, - gas_cost_df=gas_cost_df, - fees_df=fee_steps_df, + dynamic_input_frames=dynamic_input_frames, ) # Extract final weights from the result. diff --git a/quantammsim/training/backpropagation.py b/quantammsim/training/backpropagation.py index 07d6311..bc34f66 100644 --- a/quantammsim/training/backpropagation.py +++ b/quantammsim/training/backpropagation.py @@ -49,6 +49,7 @@ # jax.set_cpu_device_count(n) # print(devices("cpu")) +import jax import jax.numpy as jnp from jax import grad, value_and_grad, jit, vmap from jax.tree_util import tree_map @@ -170,6 +171,45 @@ def batched_objective(params, start_indexes): return batched_objective +def batched_robust_objective_factory(batched_partial_training_step, temperature=1.0): + """Creates an objective with distributionally robust aggregation. + + Instead of ``mean(outputs)``, uses a softmin-weighted average that + up-weights bad windows and down-weights good ones:: + + weights = softmax(-outputs / temperature) + objective = sum(weights * outputs) + + At ``temperature → ∞``: recovers the mean (standard behavior). + At ``temperature → 0``: recovers the min (pure worst-case). + + This encourages the optimizer to spend gradient budget on surviving + crashes rather than squeezing marginal gains in calm periods. + + Parameters + ---------- + batched_partial_training_step : callable + A vectorized function that processes batches of inputs. + temperature : float + Controls robustness. Lower = more robust / pessimistic. + Recommended range: 0.1 (very robust) to 10.0 (near mean). + Default 1.0 is a moderate robustness level. + + Returns + ------- + callable + JIT-compiled robust objective function. + """ + + @jit + def batched_robust_objective(params, start_indexes): + output = batched_partial_training_step(params, start_indexes) + weights = jax.nn.softmax(-output / temperature) + return jnp.sum(weights * output) + + return batched_robust_objective + + def batched_objective_with_hessian_factory( batched_partial_training_step, partial_fixed_training_step ): @@ -326,6 +366,7 @@ def update_from_partial_training_step_factory( partial_training_step, train_on_hessian_trace=False, partial_fixed_training_step=None, + robust_temperature=None, ): """Creates a complete update function from a partial training step. @@ -342,6 +383,11 @@ def update_from_partial_training_step_factory( partial_fixed_training_step : callable, optional The function used to compute Hessian trace when train_on_hessian_trace is True. Required if train_on_hessian_trace is True. + robust_temperature : float, optional + If set, uses distributionally robust aggregation (softmin-weighted + average) instead of mean over training windows. Lower values are + more robust / pessimistic. Recommended range: 0.1–10.0. + None (default) uses standard mean aggregation. Returns ------- @@ -358,6 +404,10 @@ def update_from_partial_training_step_factory( batched_partial_training_step, partial_fixed_training_step ) update = update_with_hessian_factory(batched_objective_with_hessian) + elif robust_temperature is not None: + batched_objective = batched_robust_objective_factory( + batched_partial_training_step, temperature=robust_temperature) + update = update_factory(batched_objective) else: batched_objective = batched_objective_factory(batched_partial_training_step) update = update_factory(batched_objective) @@ -522,6 +572,7 @@ def update_from_partial_training_step_factory_with_optax( optimizer, train_on_hessian_trace=False, partial_fixed_training_step=None, + robust_temperature=None, ): """Creates a complete update function from a partial training step using optax optimizer. @@ -539,6 +590,9 @@ def update_from_partial_training_step_factory_with_optax( partial_fixed_training_step : callable, optional The function used to compute Hessian trace when train_on_hessian_trace is True. Required if train_on_hessian_trace is True. + robust_temperature : float, optional + If set, uses distributionally robust aggregation (softmin-weighted + average) instead of mean. See ``batched_robust_objective_factory``. Returns ------- @@ -555,6 +609,10 @@ def update_from_partial_training_step_factory_with_optax( batched_partial_training_step, partial_fixed_training_step ) update = update_with_hessian_factory_with_optax(batched_objective_with_hessian, optimizer) + elif robust_temperature is not None: + batched_objective = batched_robust_objective_factory( + batched_partial_training_step, temperature=robust_temperature) + update = update_factory_with_optax(batched_objective, optimizer) else: batched_objective = batched_objective_factory(batched_partial_training_step) update = update_factory_with_optax(batched_objective, optimizer) diff --git a/quantammsim/training/cma_es.py b/quantammsim/training/cma_es.py index 53e9284..f31c432 100644 --- a/quantammsim/training/cma_es.py +++ b/quantammsim/training/cma_es.py @@ -288,6 +288,8 @@ def run_cmaes( params: dict, n_generations: int, tol: float = 1e-8, + lower_bounds: jnp.ndarray = None, + upper_bounds: jnp.ndarray = None, ) -> CMAESState: """Run CMA-ES via ``lax.while_loop``. JIT-compatible. @@ -308,6 +310,10 @@ def run_cmaes( Maximum number of generations. tol : float Convergence tolerance passed to :func:`_should_stop_jax`. + lower_bounds : jax.Array, optional + Per-dimension lower bounds, shape ``(n,)``. None = unbounded. + upper_bounds : jax.Array, optional + Per-dimension upper bounds, shape ``(n,)``. None = unbounded. Returns ------- @@ -315,6 +321,7 @@ def run_cmaes( Final state after convergence or ``n_generations``. """ lam = params["lam"] + use_bounds = lower_bounds is not None and upper_bounds is not None def cond_fn(carry): state, _key = carry @@ -324,6 +331,8 @@ def body_fn(carry): state, key = carry key, subkey = random.split(key) pop = ask(state, subkey, lam) + if use_bounds: + pop = jnp.clip(pop, lower_bounds, upper_bounds) fitness = eval_fn(pop) state = tell(state, pop, fitness, params) return (state, key) diff --git a/quantammsim/utils/data_processing/amalgamated_data_utils.py b/quantammsim/utils/data_processing/amalgamated_data_utils.py index 61cd65f..76ab9c6 100644 --- a/quantammsim/utils/data_processing/amalgamated_data_utils.py +++ b/quantammsim/utils/data_processing/amalgamated_data_utils.py @@ -43,7 +43,9 @@ def forward_fill_ohlcv_data(df, token): end=pd.to_datetime(df.index.max(), unit="ms"), freq="1min", ) - full_index = full_index.astype(np.int64) // 10**6 + # int64 cast of a DatetimeIndex is unit-dependent on pandas>=3 (this index + # is datetime64[ms] there, not [ns]), so convert to ms explicitly. + full_index = (full_index - pd.Timestamp(0)) // pd.Timedelta(milliseconds=1) # Reindex with the complete minute-level index df = df.reindex(full_index) diff --git a/quantammsim/utils/data_processing/historic_data_utils.py b/quantammsim/utils/data_processing/historic_data_utils.py index 31fb76c..e1a1b32 100644 --- a/quantammsim/utils/data_processing/historic_data_utils.py +++ b/quantammsim/utils/data_processing/historic_data_utils.py @@ -83,9 +83,6 @@ def start_and_end_calcs( if oracle_values is not None: oracle_values = oracle_values[remainder_idx:] - print("start_date: ", start_date) - print("end_date: ", end_date) - print("unix_values: ", unix_values) start_idx = np.where(unix_values == start_date)[0][0] end_idx = np.where(unix_values == end_date)[0][0] + 1 else: @@ -644,7 +641,13 @@ def get_binance_vision_data(token, numeraire, root): data_type="klines", data_frequency="1m" ) - + + # The dumper validates tickers against the live exchangeInfo endpoint, which + # is geo-switched to binance.us for US IPs and so hides pairs (e.g. RPLUSDT) + # that exist in the vision archive but aren't listed on Binance.US. Skip the + # check; a pair with no archive simply downloads nothing. + data_dumper.get_list_all_trading_pairs = lambda: [f"{token}{numeraire}"] + # Download all available data data_dumper.dump_data( tickers=[f"{token}{numeraire}"], @@ -729,6 +732,71 @@ def get_binance_vision_data(token, numeraire, root): return result_df +def get_quote_currency_candidates(token): + """Return quote currencies to try when bootstrapping minute data.""" + if token == "USDT": + return ["USD", "USDC"] + return ["USDT"] + + +def get_coinbase_live_data(token, numeraire): + """Download minute OHLCV data from Coinbase and standardize the schema.""" + market = f"{token}-{numeraire}" + start_time = "2016-01-01-00-00" + end_time = datetime.utcnow().strftime("%Y-%m-%d-%H-%M") + + try: + retrieved_data = HistoricalData( + market, 60, start_time, end_time + ).retrieve_data() + except Exception as exc: + print(f"No Coinbase live data found for {market}: {exc}") + return None + + if retrieved_data is None or retrieved_data.empty: + print(f"No Coinbase live data returned for {market}") + return None + + standardized_df = retrieved_data.reset_index() + date_column = standardized_df.columns[0] + standardized_df = standardized_df.rename( + columns={ + date_column: "date", + "volume": f"Volume {token}", + } + ) + standardized_df["date"] = pd.to_datetime( + standardized_df["date"], utc=True + ).dt.tz_localize(None) + standardized_df["unix"] = pddatetime_to_unixtimestamp(standardized_df["date"]) + standardized_df["symbol"] = f"{token}/{numeraire}" + standardized_df["Volume USD"] = ( + standardized_df[f"Volume {token}"] * standardized_df["close"] + ) + + standardized_df = standardized_df[ + [ + "unix", + "date", + "symbol", + "open", + "high", + "low", + "close", + "Volume USD", + f"Volume {token}", + ] + ] + standardized_df = standardized_df.sort_values("unix") + standardized_df = standardized_df.drop_duplicates( + subset="unix", keep="last" + ).reset_index(drop=True) + standardized_df["date"] = standardized_df["date"].dt.strftime( + "%Y-%m-%d %H:%M:%S" + ) + return standardized_df + + def update_historic_data(token, root): """Update historic data for a given token, handling reruns gracefully. @@ -746,18 +814,42 @@ def update_historic_data(token, root): os.makedirs(outputPath, exist_ok=True) os.makedirs(root + "concat_binance_data/", exist_ok=True) - # Try binance.vision data first - print(f"Attempting to get Binance vision data for {token}") filled_timestamps = {} - concated_df = get_binance_vision_data(token, "USDT", root) - if concated_df is not None: - print(f"Binance vision data available for {token}") + quote_currency_candidates = get_quote_currency_candidates(token) + primary_numeraire = quote_currency_candidates[0] + concated_df = None + filled_timestamps["Binance Vision"] = [] + + # Try binance.vision data first, using a USD quote for assets like USDT. + for numeraire in quote_currency_candidates: + print(f"Attempting to get Binance vision data for {token} quoted in {numeraire}") + candidate_df = get_binance_vision_data(token, numeraire, root) + if candidate_df is None: + continue + concated_df = candidate_df + primary_numeraire = numeraire + print(f"Binance vision data available for {token} quoted in {numeraire}") if concated_df.index.name != "unix": concated_df.set_index("unix", inplace=True) - filled_binance_vision_unix_values = concated_df.index.tolist() - filled_timestamps["Binance Vision"] = filled_binance_vision_unix_values - else: - print(f"No Binance vision data available for {token}") + filled_timestamps["Binance Vision"] = concated_df.index.tolist() + break + + if concated_df is None: + for numeraire in quote_currency_candidates: + print(f"Attempting to get Coinbase live data for {token} quoted in {numeraire}") + candidate_df = get_coinbase_live_data(token, numeraire) + if candidate_df is None: + continue + concated_df = candidate_df + primary_numeraire = numeraire + print(f"Coinbase live data available for {token} quoted in {numeraire}") + if concated_df.index.name != "unix": + concated_df.set_index("unix", inplace=True) + filled_timestamps["Coinbase Live"] = concated_df.index.tolist() + break + + if concated_df is None: + print(f"No live market data available for {token}") concated_df = pd.DataFrame( columns=[ "date", @@ -770,11 +862,13 @@ def update_historic_data(token, root): f"Volume {token}", ] ) - filled_timestamps["Binance Vision"] = [] + concated_df.index.name = "unix" # Fill gaps with cryptodatadownload Binance data - print("Filling gaps with cryptodatadownload Binance data") + print( + f"Filling gaps with cryptodatadownload Binance data quoted in {primary_numeraire}" + ) concated_df, filled_binance_unix_values = fill_in_missing_rows_with_exchange_data( - concated_df, token, "USDT", root, "raw_binance_data/", "Binance_" + concated_df, token, primary_numeraire, root, "raw_binance_data/", "Binance_" ) filled_timestamps["Binance CDD"] = filled_binance_unix_values if concated_df is None: @@ -900,6 +994,10 @@ def update_historic_data(token, root): filled_timestamps["Aerodrome"] = filled_aerodrome_unix_values print("Filled aerodrome data") print(len(filled_aerodrome_unix_values)) + if concated_df.empty: + raise FileNotFoundError( + f"No market data found for {token} using quote currencies {quote_currency_candidates}" + ) # Ensure data is properly sorted and has no duplicates concated_df = concated_df.sort_index() concated_df = concated_df[~concated_df.index.duplicated(keep="first")] @@ -987,7 +1085,7 @@ def update_historic_data(token, root): agg_dict = {k: v for k, v in agg_dict.items() if k in concated_df_hourly.columns} # Perform resampling - hourly_data = concated_df_hourly.resample("1H").agg(agg_dict).reset_index() + hourly_data = concated_df_hourly.resample("1h").agg(agg_dict).reset_index() # Save hourly data hourly_data.to_csv(hourlyPath, index=False) diff --git a/results/competitor_tvl/competitor_tvl.npz b/results/competitor_tvl/competitor_tvl.npz new file mode 100644 index 0000000..8a96f48 Binary files /dev/null and b/results/competitor_tvl/competitor_tvl.npz differ diff --git a/results/full_sweep/daily_log_sharpe_excess_penalty1.0_aave_1m.json b/results/full_sweep/daily_log_sharpe_excess_penalty1.0_aave_1m.json new file mode 100644 index 0000000..3b0da19 --- /dev/null +++ b/results/full_sweep/daily_log_sharpe_excess_penalty1.0_aave_1m.json @@ -0,0 +1,9 @@ +{ + "daily_log_sharpe_excess": { + "price_ratio": 1.012011484075616, + "centeredness_margin": 0.026628408428241428, + "shift_exponent": 0.08827350039359076, + "subsidary_params": [], + "initial_weights_logits": "[0. 0.]" + } +} \ No newline at end of file diff --git a/results/full_sweep/daily_log_sharpe_excess_penalty1.0_cow_500k.json b/results/full_sweep/daily_log_sharpe_excess_penalty1.0_cow_500k.json new file mode 100644 index 0000000..b45c179 --- /dev/null +++ b/results/full_sweep/daily_log_sharpe_excess_penalty1.0_cow_500k.json @@ -0,0 +1,9 @@ +{ + "daily_log_sharpe_excess": { + "price_ratio": 5.992515918267629, + "centeredness_margin": 0.4084847505974458, + "shift_exponent": 0.00014153838858700288, + "subsidary_params": [], + "initial_weights_logits": "[0. 0.]" + } +} \ No newline at end of file diff --git a/results/full_sweep/returns_over_hodl_boldusdc_1m.json b/results/full_sweep/returns_over_hodl_boldusdc_1m.json new file mode 100644 index 0000000..6308f84 --- /dev/null +++ b/results/full_sweep/returns_over_hodl_boldusdc_1m.json @@ -0,0 +1,9 @@ +{ + "returns_over_hodl": { + "price_ratio": 1.0102394435218087, + "centeredness_margin": 0.03713493968302037, + "shift_exponent": 2.2665545348679854e-05, + "subsidary_params": [], + "initial_weights_logits": "[0. 0.]" + } +} \ No newline at end of file diff --git a/results/full_sweep/returns_over_hodl_btceth_5m.json b/results/full_sweep/returns_over_hodl_btceth_5m.json new file mode 100644 index 0000000..497ac07 --- /dev/null +++ b/results/full_sweep/returns_over_hodl_btceth_5m.json @@ -0,0 +1,9 @@ +{ + "returns_over_hodl": { + "price_ratio": 1.0135446536102861, + "centeredness_margin": 0.26645620813345267, + "shift_exponent": 1.0439828724530492e-05, + "subsidary_params": [], + "initial_weights_logits": "[0. 0.]" + } +} \ No newline at end of file diff --git a/results/full_sweep/returns_over_hodl_penalty1.0_cmaes_aave_20m.json b/results/full_sweep/returns_over_hodl_penalty1.0_cmaes_aave_20m.json new file mode 100644 index 0000000..43b73df --- /dev/null +++ b/results/full_sweep/returns_over_hodl_penalty1.0_cmaes_aave_20m.json @@ -0,0 +1,8 @@ +{ + "returns_over_hodl": { + "centeredness_margin": "[0.01026839]", + "price_ratio": "[1.3478746]", + "shift_exponent": "[0.54106706]", + "subsidary_params": [] + } +} \ No newline at end of file diff --git a/results/full_sweep/returns_over_hodl_penalty1.0_cmaes_aave_5m.json b/results/full_sweep/returns_over_hodl_penalty1.0_cmaes_aave_5m.json new file mode 100644 index 0000000..edd7992 --- /dev/null +++ b/results/full_sweep/returns_over_hodl_penalty1.0_cmaes_aave_5m.json @@ -0,0 +1,8 @@ +{ + "returns_over_hodl": { + "centeredness_margin": "[0.0100561]", + "price_ratio": "[1.2961863]", + "shift_exponent": "[0.06752973]", + "subsidary_params": [] + } +} \ No newline at end of file diff --git a/results/full_sweep/returns_over_hodl_penalty1.0_cow_20m.json b/results/full_sweep/returns_over_hodl_penalty1.0_cow_20m.json new file mode 100644 index 0000000..e08407b --- /dev/null +++ b/results/full_sweep/returns_over_hodl_penalty1.0_cow_20m.json @@ -0,0 +1,9 @@ +{ + "returns_over_hodl": { + "price_ratio": 4.05509593366626, + "centeredness_margin": 0.3192831580248914, + "shift_exponent": 0.0013440567877262095, + "subsidary_params": [], + "initial_weights_logits": "[0. 0.]" + } +} \ No newline at end of file diff --git a/results/full_sweep/returns_over_hodl_penalty1.0_cow_2m.json b/results/full_sweep/returns_over_hodl_penalty1.0_cow_2m.json new file mode 100644 index 0000000..a2c913a --- /dev/null +++ b/results/full_sweep/returns_over_hodl_penalty1.0_cow_2m.json @@ -0,0 +1,9 @@ +{ + "returns_over_hodl": { + "price_ratio": 5.832161383230471, + "centeredness_margin": 0.7765835409120687, + "shift_exponent": 0.00018668202918720232, + "subsidary_params": [], + "initial_weights_logits": "[0. 0.]" + } +} \ No newline at end of file diff --git a/results/linear_market_noise/_sim_arrays/0x9d1fcf346ea1b0_2024-06-01_2026-03-01.npz b/results/linear_market_noise/_sim_arrays/0x9d1fcf346ea1b0_2024-06-01_2026-03-01.npz new file mode 100644 index 0000000..368146c Binary files /dev/null and b/results/linear_market_noise/_sim_arrays/0x9d1fcf346ea1b0_2024-06-01_2026-03-01.npz differ diff --git a/results/linear_market_noise/_sim_arrays/0x9d1fcf346ea1b0_2025-08-03_2026-02-18.npz b/results/linear_market_noise/_sim_arrays/0x9d1fcf346ea1b0_2025-08-03_2026-02-18.npz new file mode 100644 index 0000000..03413c9 Binary files /dev/null and b/results/linear_market_noise/_sim_arrays/0x9d1fcf346ea1b0_2025-08-03_2026-02-18.npz differ diff --git a/results/linear_market_noise/meta.json b/results/linear_market_noise/meta.json new file mode 100644 index 0000000..35249de --- /dev/null +++ b/results/linear_market_noise/meta.json @@ -0,0 +1,77 @@ +{ + "feat_names": [ + "xobs_0", + "xobs_1", + "xobs_2", + "xobs_3", + "btc_log_price", + "btc_log_return", + "btc_realized_vol_7d", + "btc_trend_7d", + "btc_volume_zscore", + "pair_realized_vol_7d", + "tok_a_log_return", + "tok_a_realized_vol_7d", + "tok_a_trend_7d", + "tok_a_volume_zscore", + "tok_b_log_return", + "tok_b_realized_vol_7d", + "tok_b_trend_7d", + "tok_b_volume_zscore", + "xobs_1\u00d7btc_realized_vol_7d", + "xobs_1\u00d7tok_a_realized_vol_7d", + "xobs_1\u00d7pair_realized_vol_7d", + "tok_a_realized_vol_7d\u00d7tok_b_realized_vol_7d" + ], + "pool_ids": [ + "0x072f14b85add63", + "0x0b09dea16768f0", + "0x10f21c9bd8128a", + "0x1535d7ca00323a", + "0x21d4c792ea7e38", + "0x25ca5451cd5a50", + "0x260dbd54d87a10", + "0x272d6be442e30d", + "0x32df62dc3aed2c", + "0x36be1e97ea98ab", + "0x3de27efa2f1aa6", + "0x3e5fa9518ea95c", + "0x4683e340a80492", + "0x4cdabe9e07ca39", + "0x4fbb7870dbe7a7", + "0x571bea0e99e139", + "0x5c6ee304399dbd", + "0x5f1f4e50ba51d7", + "0x711af51a937e01", + "0x713fb5036dc700", + "0x9232a548dd9e81", + "0x92762b42a06dcd", + "0x96646936b91d6b", + "0x9d1fcf346ea1b0", + "0xa6f548df93de92", + "0xa83b8d30f61d75", + "0xb460daa847c45f", + "0xbc2acf5e821c5c", + "0xbda917a67c7d9a", + "0xcc65a812ce382a", + "0xcf354603a9aebd", + "0xcf7b51ce575551", + "0xd1d7fa8871d84d", + "0xdaba3d8ccf79ef", + "0xe99481dc77691d", + "0xf16aee6a71af1a" + ], + "n_pools": 36, + "n_feat": 22, + "hparams": { + "epochs": 2000, + "lr": 0.0003, + "l2_alpha": 0.001, + "huber_delta": 1.0, + "trend_windows": [ + 7 + ], + "per_pool": true, + "pool_intercepts": false + } +} \ No newline at end of file diff --git a/results/linear_market_noise/model.npz b/results/linear_market_noise/model.npz new file mode 100644 index 0000000..f440f04 Binary files /dev/null and b/results/linear_market_noise/model.npz differ diff --git a/results/mm_noise/meta.json b/results/mm_noise/meta.json new file mode 100644 index 0000000..1c37a0d --- /dev/null +++ b/results/mm_noise/meta.json @@ -0,0 +1,227 @@ +{ + "model": "michaelis_menten", + "pool_ids": [ + "0x072f14b85add63", + "0x0b09dea16768f0", + "0x10f21c9bd8128a", + "0x1535d7ca00323a", + "0x21d4c792ea7e38", + "0x25ca5451cd5a50", + "0x260dbd54d87a10", + "0x272d6be442e30d", + "0x32df62dc3aed2c", + "0x36be1e97ea98ab", + "0x3de27efa2f1aa6", + "0x3e5fa9518ea95c", + "0x4683e340a80492", + "0x4cdabe9e07ca39", + "0x4fbb7870dbe7a7", + "0x571bea0e99e139", + "0x5c6ee304399dbd", + "0x5f1f4e50ba51d7", + "0x711af51a937e01", + "0x713fb5036dc700", + "0x9232a548dd9e81", + "0x92762b42a06dcd", + "0x96646936b91d6b", + "0x9d1fcf346ea1b0", + "0xa6f548df93de92", + "0xa83b8d30f61d75", + "0xb460daa847c45f", + "0xbc2acf5e821c5c", + "0xbda917a67c7d9a", + "0xcc65a812ce382a", + "0xcf354603a9aebd", + "0xcf7b51ce575551", + "0xd1d7fa8871d84d", + "0xd321300ef77067", + "0xdaba3d8ccf79ef", + "0xe99481dc77691d", + "0xf16aee6a71af1a", + "0xff028c1ec4559d" + ], + "pool_tokens": [ + [ + "SNX", + "WETH" + ], + [ + "DAI", + "WETH" + ], + [ + "USDC", + "WETH" + ], + [ + "WETH", + "ALCX" + ], + [ + "COW", + "GNO" + ], + [ + "scUSD", + "stS" + ], + [ + "wstETH", + "JitoSOL" + ], + [ + "waGnowstETH", + "waGnoGNO" + ], + [ + "RDNT", + "WETH" + ], + [ + "ACX", + "wstETH" + ], + [ + "wstETH", + "AAVE" + ], + [ + "WETH", + "USDT" + ], + [ + "wstETH", + "GNO" + ], + [ + "COW", + "wstETH" + ], + [ + "waBasUSDC", + "waBasWETH" + ], + [ + "WBTC", + "QNT" + ], + [ + "BAL", + "WETH" + ], + [ + "LDO", + "wstETH" + ], + [ + "QNT", + "waEthLidowstETH" + ], + [ + "USDC", + "stS" + ], + [ + "WETH", + "LIT" + ], + [ + "GNO", + "COW" + ], + [ + "USDC", + "WETH" + ], + [ + "AAVE", + "WETH" + ], + [ + "WBTC", + "WETH" + ], + [ + "WETH", + "ARB" + ], + [ + "WBTC", + "BADGER" + ], + [ + "wstETH", + "sDAI" + ], + [ + "WETH", + "EIGEN" + ], + [ + "BAL", + "WETH" + ], + [ + "WBTC", + "WETH" + ], + [ + "RDNT", + "WETH" + ], + [ + "waGnoGNO", + "sDAI" + ], + [ + "WETH", + "COW" + ], + [ + "waEthLidoWETH", + "TREE" + ], + [ + "LINK", + "WETH" + ], + [ + "WETH", + "ALCX" + ], + [ + "WETH", + "COW" + ] + ], + "market_names": [ + "xobs_0", + "xobs_2", + "xobs_3", + "btc_log_price", + "btc_log_return", + "btc_realized_vol_7d", + "btc_trend_7d", + "btc_volume_zscore", + "pair_realized_vol_7d", + "tok_a_log_return", + "tok_a_realized_vol_7d", + "tok_a_trend_7d", + "tok_a_volume_zscore", + "tok_b_log_return", + "tok_b_realized_vol_7d", + "tok_b_trend_7d", + "tok_b_volume_zscore", + "tok_a_realized_vol_7d\u00d7tok_b_realized_vol_7d" + ], + "n_pools": 38, + "n_market_feat": 18, + "per_pool_gamma": true, + "hparams": { + "epochs": 5000, + "lr": 0.0001, + "l2_alpha": 0.001, + "huber_delta": 0.5, + "init_log_K": 17.0 + } +} \ No newline at end of file diff --git a/results/mm_noise/model.npz b/results/mm_noise/model.npz new file mode 100644 index 0000000..9e9e80b Binary files /dev/null and b/results/mm_noise/model.npz differ diff --git a/results/run_0bd5282d0820f9de3f3a83bea60d7757eb7b8267493a30b2f12a3dbd83e85922.json b/results/run_0bd5282d0820f9de3f3a83bea60d7757eb7b8267493a30b2f12a3dbd83e85922.json new file mode 100644 index 0000000..f9f997a --- /dev/null +++ b/results/run_0bd5282d0820f9de3f3a83bea60d7757eb7b8267493a30b2f12a3dbd83e85922.json @@ -0,0 +1 @@ +"[{\"alphabetic\": true, \"arb_fees\": 0.0, \"arb_frequency\": 3, \"arb_quality\": 1.0, \"bout_offset\": 10080, \"checkpoint_fused\": \"scan\", \"chunk_period\": 1440, \"do_arb\": true, \"do_trades\": false, \"endDateString\": \"2025-10-05 00:00:00\", \"endTestDateString\": \"2026-03-01 00:00:00\", \"ensemble_init_method\": \"gaussian\", \"ensemble_init_scale\": 0.5, \"ensemble_init_seed\": 42, \"evaluation_starts\": [77760, 82489, 84681, 87218, 91947, 94849, 96677, 96743, 99003, 101406, 106135, 109990, 110864, 115594, 117272, 119753, 120323, 120373, 121327, 123651, 125052, 126360, 129781, 130380, 132786, 134511, 139240, 143969, 146028, 148698, 153428, 157946, 158157, 158231, 158726, 160217, 162886, 164053, 167448, 167616], \"fees\": 0.003, \"freq\": \"minute\", \"gas_cost\": 3.0, \"initial_arc_length_speed\": 0.0001, \"initial_centeredness_margin\": 0.2, \"initial_daily_price_shift_base\": 0.999991935483871, \"initial_k_per_day\": 20, \"initial_log_amplitude\": 0.0, \"initial_memory_length\": 10.0, \"initial_memory_length_delta\": 0.0, \"initial_pool_value\": 500000.0, \"initial_pre_exp_scaling\": 0.5, \"initial_price_ratio\": 4.0, \"initial_raw_exponents\": 0.0, \"initial_raw_width\": 0.0, \"initial_shift_exponent\": 1.0, \"initial_weights_logits\": 1.0, \"learnable_bounds_settings\": {\"freeze_bounds\": false, \"max_weights_per_asset\": null, \"min_weights_per_asset\": null}, \"max_memory_days\": 365, \"maximum_change\": 0.0003, \"minimum_weight\": null, \"n_ensemble_members\": 1, \"noise_arrays_path\": \"results/mm_noise/_sim_arrays/0xd321300ef77067_2025-01-01_2026-03-01_mm.npz\", \"noise_model\": \"mm_observed\", \"noise_trader_ratio\": 0.0, \"numeraire\": null, \"optimisation_settings\": {\"base_lr\": 0.1, \"batch_size\": 8, \"bfgs_settings\": {\"compute_dtype\": \"float32\", \"maxiter\": 100, \"n_evaluation_points\": 20, \"tol\": 1e-06}, \"checkpoint_interval\": 10, \"clip_norm\": 10.0, \"cma_es_settings\": {\"compute_dtype\": \"float32\", \"memory_budget\": null, \"n_evaluation_points\": 20, \"n_generations\": 300, \"population_size\": null, \"sigma0\": 0.5, \"tol\": 1e-08}, \"decay_lr_plateau\": 100, \"decay_lr_ratio\": 0.8, \"early_stopping\": true, \"early_stopping_metric\": \"daily_log_sharpe\", \"early_stopping_patience\": 200, \"force_scalar\": false, \"include_flipped_training_data\": false, \"initial_random_key\": 0, \"lr_decay_ratio\": 1000, \"lr_schedule_type\": \"constant\", \"max_mc_version\": 9, \"method\": \"optuna\", \"min_lr\": 1e-06, \"n_cycles\": 5, \"n_iterations\": 1000, \"n_parameter_sets\": 1, \"noise_scale\": 0.1, \"optimiser\": \"adamw\", \"optuna_settings\": {\"early_stopping\": {\"enabled\": false, \"min_improvement\": 0.001, \"patience\": 100}, \"expand_around\": false, \"make_scalar\": true, \"min_train_returns_over_hodl\": -0.5, \"multi_objective\": false, \"n_jobs\": 4, \"n_startup_trials\": 10, \"n_trials\": 300, \"overfitting_penalty\": 1.0, \"parameter_config\": {\"centeredness_margin\": {\"high\": 0.99, \"low\": 0.01, \"scalar\": true}, \"k_per_day\": {\"high\": 1000, \"log_scale\": true, \"low\": 0.1, \"scalar\": true}, \"log_amplitude\": {\"high\": 10, \"log_scale\": false, \"low\": -10, \"scalar\": true}, \"log_k\": {\"high\": 10.0, \"log_scale\": false, \"low\": -10.0, \"scalar\": true}, \"logit_lamb\": {\"high\": 4.602848117654388, \"log_scale\": false, \"low\": -5.955817303419269, \"scalar\": true}, \"memory_days_1\": {\"high\": 200, \"log_scale\": true, \"low\": 0.5, \"scalar\": true}, \"memory_days_2\": {\"high\": 200, \"log_scale\": true, \"low\": 0.5, \"scalar\": true}, \"memory_length\": {\"high\": 200, \"log_scale\": true, \"low\": 1, \"scalar\": true}, \"memory_length_delta\": {\"high\": 100, \"log_scale\": true, \"low\": 0.1, \"scalar\": true}, \"price_ratio\": {\"high\": 200.0, \"log_scale\": true, \"low\": 1.01, \"scalar\": true}, \"raw_exponents\": {\"high\": 10, \"log_scale\": false, \"low\": 0, \"scalar\": true}, \"raw_pre_exp_scaling\": {\"high\": 10, \"log_scale\": false, \"low\": -10, \"scalar\": true}, \"raw_width\": {\"high\": 10, \"log_scale\": false, \"low\": -10, \"scalar\": true}, \"shift_exponent\": {\"high\": 125.0, \"log_scale\": true, \"low\": 1e-05, \"scalar\": true}, \"weights_logits\": {\"high\": 10, \"log_scale\": false, \"low\": -10, \"scalar\": true}}, \"storage\": {\"type\": \"sqlite\", \"url\": null}, \"study_name\": null, \"timeout\": 7200}, \"parameter_init_method\": \"gaussian\", \"sample_method\": \"uniform\", \"swa_freq\": 10, \"swa_start_frac\": 0.75, \"track_checkpoints\": false, \"train_on_hessian_trace\": false, \"training_data_kind\": \"historic\", \"use_gradient_clipping\": true, \"use_plateau_decay\": false, \"use_swa\": false, \"val_fraction\": 0.2, \"warmup_steps\": 100, \"weight_decay\": 0.01}, \"price_noise_sigma\": 0.0, \"protocol_fee_split\": 0.25, \"reclamm_arc_length_speed\": null, \"reclamm_centeredness_scaling\": false, \"reclamm_interpolation_method\": \"geometric\", \"reclamm_learn_arc_length_speed\": false, \"reclamm_learn_fees\": false, \"reclamm_use_shift_exponent\": true, \"return_val\": \"daily_log_sharpe_excess\", \"rule\": \"reclamm\", \"startDateString\": \"2025-01-01 00:00:00\", \"ste_max_change\": false, \"ste_min_max_weight\": false, \"ste_temperature\": 10.0, \"subsidary_pools\": [], \"tokens\": [\"COW\", \"ETH\"], \"training_method\": \"optuna\", \"turnover_penalty\": 0.0, \"use_alt_lamb\": false, \"use_fused_reserves\": true, \"use_pre_exp_scaling\": true, \"weight_calculation_method\": \"auto\", \"weight_interpolation_method\": \"linear\", \"weight_interpolation_period\": 1440}, {\"centeredness_margin\": 0.46471962432526753, \"continuous_test_metrics\": [{\"annualised_returns\": -0.6404701233884285, \"annualised_returns_over_hodl\": 0.12809742720753414, \"annualised_returns_over_uniform_hodl\": 0.2689811188147073, \"calmar\": -1.1464517294726957, \"daily_log_sharpe\": -1.3176314219276837, \"daily_returns\": 0.02057864893155599, \"fee_revenue_over_value\": 0.08482398533046036, \"jax_sharpe\": -0.7590361455706187, \"return\": -0.33766504593139546, \"returns_over_hodl\": 0.049740515102964844, \"returns_over_uniform_hodl\": 0.10069072290619174, \"sharpe\": -0.9283971926659313, \"sterling\": -1.922472174788748, \"ulcer\": -0.14721891698483452}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -2.5624721690711363, \"optuna_trial_number\": 0, \"price_ratio\": 10.674918060781419, \"shift_exponent\": 0.14195425846677004, \"step\": 0, \"test_objective\": [{\"annualised_returns\": -0.6404701233884285, \"annualised_returns_over_hodl\": 0.12809742720753414, \"annualised_returns_over_uniform_hodl\": 0.2689811188147073, \"calmar\": -1.1464517294726957, \"daily_log_sharpe\": -1.3176314219276837, \"daily_returns\": 0.02057864893155599, \"fee_revenue_over_value\": 0.08482398533046036, \"jax_sharpe\": -0.7590361455706187, \"return\": -0.33766504593139546, \"returns_over_hodl\": 0.049740515102964844, \"returns_over_uniform_hodl\": 0.10069072290619174, \"sharpe\": -0.9283971926659313, \"sterling\": -1.922472174788748, \"ulcer\": -0.14721891698483452}], \"train_objective\": [{\"annualised_returns\": -0.3206387480222357, \"annualised_returns_over_hodl\": -0.1444544407581989, \"annualised_returns_over_uniform_hodl\": -0.1444544407581989, \"calmar\": -0.4375190811687271, \"daily_log_sharpe\": -0.31762018956714283, \"daily_returns\": 0.008120051931859449, \"fee_revenue_over_value\": 0.037613680021894025, \"jax_sharpe\": 0.17869502455314287, \"return\": -0.20920340867222087, \"returns_over_hodl\": -0.09037295948028357, \"returns_over_uniform_hodl\": -0.09037295948028357, \"sharpe\": 0.18517454991118598, \"sterling\": -1.042014583296152, \"ulcer\": -0.16557462417149582}], \"train_return\": -0.20920340867222087, \"train_returns_over_hodl\": -0.09037295948028357, \"train_sharpe\": 0.17869502455314287, \"validation_return\": -0.18954144660270555, \"validation_returns_over_hodl\": -0.02596767117310772, \"validation_sharpe\": -1.4876445334580528}, {\"centeredness_margin\": 0.655162982676203, \"continuous_test_metrics\": [{\"annualised_returns\": -0.48450648009953234, \"annualised_returns_over_hodl\": 3.8193892493154635e-11, \"annualised_returns_over_uniform_hodl\": 0.8194636556776564, \"calmar\": -0.8358050112565429, \"daily_log_sharpe\": -0.6924636646388644, \"daily_returns\": 0.031016041469442607, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": 0.005019202924369023, \"return\": -0.23422461544060902, \"returns_over_hodl\": 1.5382140006181544e-11, \"returns_over_uniform_hodl\": 0.27259154365441707, \"sharpe\": -0.20210860913602083, \"sterling\": -1.2473844014651312, \"ulcer\": -0.17641031386350667}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -0.1633533176402896, \"optuna_trial_number\": 1, \"price_ratio\": 2.041024073154013, \"shift_exponent\": 3.968219027841517e-05, \"step\": 1, \"test_objective\": [{\"annualised_returns\": -0.48450648009953234, \"annualised_returns_over_hodl\": 3.8193892493154635e-11, \"annualised_returns_over_uniform_hodl\": 0.8194636556776564, \"calmar\": -0.8358050112565429, \"daily_log_sharpe\": -0.6924636646388644, \"daily_returns\": 0.031016041469442607, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": 0.005019202924369023, \"return\": -0.23422461544060902, \"returns_over_hodl\": 1.5382140006181544e-11, \"returns_over_uniform_hodl\": 0.27259154365441707, \"sharpe\": -0.20210860913602083, \"sterling\": -1.2473844014651312, \"ulcer\": -0.17641031386350667}], \"train_objective\": [{\"annualised_returns\": -0.6324753870056516, \"annualised_returns_over_hodl\": -0.5371622245984855, \"annualised_returns_over_uniform_hodl\": -0.5371622245984854, \"calmar\": -0.8072934587382454, \"daily_log_sharpe\": -0.6811432751398421, \"daily_returns\": 0.00858276332280686, \"fee_revenue_over_value\": 0.0009230347693268367, \"jax_sharpe\": 0.07986406597484998, \"return\": -0.45540193412063623, \"returns_over_hodl\": -0.37356694202886953, \"returns_over_uniform_hodl\": -0.3735669420288694, \"sharpe\": -0.0119329484185243, \"sterling\": -1.70594339595672, \"ulcer\": -0.2067844993380864}], \"train_return\": -0.45540193412063623, \"train_returns_over_hodl\": -0.37356694202886953, \"train_sharpe\": 0.07986406597484998, \"validation_return\": -0.3391427112655351, \"validation_returns_over_hodl\": -4.842126033466343e-09, \"validation_sharpe\": -2.2438531835322433}, {\"centeredness_margin\": 0.8235484695852557, \"continuous_test_metrics\": [{\"annualised_returns\": -0.6721300433036967, \"annualised_returns_over_hodl\": 0.1419901903369194, \"annualised_returns_over_uniform_hodl\": 0.1572356333649223, \"calmar\": -1.1730244798062601, \"daily_log_sharpe\": -1.4513025556561954, \"daily_returns\": 0.018417611156516994, \"fee_revenue_over_value\": 0.05980824933518147, \"jax_sharpe\": -0.9188556372976855, \"return\": -0.36180304772149985, \"returns_over_hodl\": 0.054927997981996546, \"returns_over_uniform_hodl\": 0.06057737168313504, \"sharpe\": -1.0682028019834668, \"sterling\": -2.080341899921111, \"ulcer\": -0.14654129507215818}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -2.5356394845866372, \"optuna_trial_number\": 2, \"price_ratio\": 99.66516128598917, \"shift_exponent\": 0.14182584870948664, \"step\": 2, \"test_objective\": [{\"annualised_returns\": -0.6721300433036967, \"annualised_returns_over_hodl\": 0.1419901903369194, \"annualised_returns_over_uniform_hodl\": 0.1572356333649223, \"calmar\": -1.1730244798062601, \"daily_log_sharpe\": -1.4513025556561954, \"daily_returns\": 0.018417611156516994, \"fee_revenue_over_value\": 0.05980824933518147, \"jax_sharpe\": -0.9188556372976855, \"return\": -0.36180304772149985, \"returns_over_hodl\": 0.054927997981996546, \"returns_over_uniform_hodl\": 0.06057737168313504, \"sharpe\": -1.0682028019834668, \"sterling\": -2.080341899921111, \"ulcer\": -0.14654129507215818}], \"train_objective\": [{\"annualised_returns\": -0.298120441728026, \"annualised_returns_over_hodl\": -0.11609627800521316, \"annualised_returns_over_uniform_hodl\": -0.11609627800521294, \"calmar\": -0.40936502825310156, \"daily_log_sharpe\": -0.29898444162861915, \"daily_returns\": 0.008019133814410863, \"fee_revenue_over_value\": 0.025462037074439354, \"jax_sharpe\": 0.1501067855566077, \"return\": -0.1933916134538355, \"returns_over_hodl\": -0.07218517687280646, \"returns_over_uniform_hodl\": -0.07218517687280634, \"sharpe\": 0.1897412896225608, \"sterling\": -0.9889828176218105, \"ulcer\": -0.16279548928751747}], \"train_return\": -0.1933916134538355, \"train_returns_over_hodl\": -0.07218517687280646, \"train_sharpe\": 0.1501067855566077, \"validation_return\": -0.15891929561617046, \"validation_returns_over_hodl\": -0.022906539752410193, \"validation_sharpe\": -1.2517826240737908}, {\"centeredness_margin\": 0.3381357881613315, \"continuous_test_metrics\": [{\"annualised_returns\": -0.6396572565942849, \"annualised_returns_over_hodl\": 0.11222295317007913, \"annualised_returns_over_uniform_hodl\": 0.2718501783309877, \"calmar\": -1.1300639720759869, \"daily_log_sharpe\": -1.296920180538615, \"daily_returns\": 0.020821463264837414, \"fee_revenue_over_value\": 0.07011765280912065, \"jax_sharpe\": -0.7420717224649779, \"return\": -0.3370623593561778, \"returns_over_hodl\": 0.043766145722275995, \"returns_over_uniform_hodl\": 0.10169228792716356, \"sharpe\": -0.9034749851697665, \"sterling\": -1.9104675412228247, \"ulcer\": -0.14998733941945425}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -2.5652590052229236, \"optuna_trial_number\": 3, \"price_ratio\": 35.81736374675731, \"shift_exponent\": 1.1959173015299123, \"step\": 3, \"test_objective\": [{\"annualised_returns\": -0.6396572565942849, \"annualised_returns_over_hodl\": 0.11222295317007913, \"annualised_returns_over_uniform_hodl\": 0.2718501783309877, \"calmar\": -1.1300639720759869, \"daily_log_sharpe\": -1.296920180538615, \"daily_returns\": 0.020821463264837414, \"fee_revenue_over_value\": 0.07011765280912065, \"jax_sharpe\": -0.7420717224649779, \"return\": -0.3370623593561778, \"returns_over_hodl\": 0.043766145722275995, \"returns_over_uniform_hodl\": 0.10169228792716356, \"sharpe\": -0.9034749851697665, \"sterling\": -1.9104675412228247, \"ulcer\": -0.14998733941945425}], \"train_objective\": [{\"annualised_returns\": -0.33241939406874277, \"annualised_returns_over_hodl\": -0.15929025805090857, \"annualised_returns_over_uniform_hodl\": -0.15929025805090857, \"calmar\": -0.45398088547232934, \"daily_log_sharpe\": -0.32949421933547096, \"daily_returns\": 0.008043860947320382, \"fee_revenue_over_value\": 0.031022203638769754, \"jax_sharpe\": 0.14826498492388263, \"return\": -0.21755746731108205, \"returns_over_hodl\": -0.09998235552388102, \"returns_over_uniform_hodl\": -0.09998235552388102, \"sharpe\": 0.17968150752449222, \"sterling\": -1.0747170716765198, \"ulcer\": -0.1666469347636634}], \"train_return\": -0.21755746731108205, \"train_returns_over_hodl\": -0.09998235552388102, \"train_sharpe\": 0.14826498492388263, \"validation_return\": -0.19406172280933387, \"validation_returns_over_hodl\": -0.02475762557069583, \"validation_sharpe\": -1.5144795773234603}, {\"centeredness_margin\": 0.5181916005497886, \"continuous_test_metrics\": [{\"annualised_returns\": -0.6576385341294058, \"annualised_returns_over_hodl\": -0.12161515437147596, \"annualised_returns_over_uniform_hodl\": 0.20838423803338113, \"calmar\": -1.1138220919851656, \"daily_log_sharpe\": -1.2568421732439974, \"daily_returns\": 0.02448508241295901, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": -0.6275355769893219, \"return\": -0.35058928104664633, \"returns_over_hodl\": -0.05088299883667069, \"returns_over_uniform_hodl\": 0.07921279001947013, \"sharpe\": -0.8349596589884809, \"sterling\": -1.8289487939493405, \"ulcer\": -0.15981294507818977}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -3.003362989165306, \"optuna_trial_number\": 4, \"price_ratio\": 56.28002900011108, \"shift_exponent\": 9.266285989370293e-05, \"step\": 4, \"test_objective\": [{\"annualised_returns\": -0.6576385341294058, \"annualised_returns_over_hodl\": -0.12161515437147596, \"annualised_returns_over_uniform_hodl\": 0.20838423803338113, \"calmar\": -1.1138220919851656, \"daily_log_sharpe\": -1.2568421732439974, \"daily_returns\": 0.02448508241295901, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": -0.6275355769893219, \"return\": -0.35058928104664633, \"returns_over_hodl\": -0.05088299883667069, \"returns_over_uniform_hodl\": 0.07921279001947013, \"sharpe\": -0.8349596589884809, \"sterling\": -1.8289487939493405, \"ulcer\": -0.15981294507818977}], \"train_objective\": [{\"annualised_returns\": -0.3674205043232103, \"annualised_returns_over_hodl\": -0.20336849236227972, \"annualised_returns_over_uniform_hodl\": -0.20336849236227972, \"calmar\": -0.49948555724118676, \"daily_log_sharpe\": -0.37409423823081706, \"daily_returns\": 0.00802268407053471, \"fee_revenue_over_value\": 0.011971712660006685, \"jax_sharpe\": 0.1137824989070795, \"return\": -0.24272655038148783, \"returns_over_hodl\": -0.12893351540114772, \"returns_over_uniform_hodl\": -0.12893351540114772, \"sharpe\": 0.14116265547029103, \"sterling\": -1.1776909216209719, \"ulcer\": -0.16836165799134564}], \"train_return\": -0.24272655038148783, \"train_returns_over_hodl\": -0.12893351540114772, \"train_sharpe\": 0.1137824989070795, \"validation_return\": -0.232100724724433, \"validation_returns_over_hodl\": -0.046642186918052, \"validation_sharpe\": -1.738484943488827}, {\"centeredness_margin\": 0.4883117561863167, \"continuous_test_metrics\": [{\"annualised_returns\": -0.6690415169408899, \"annualised_returns_over_hodl\": 0.03271179853487838, \"annualised_returns_over_uniform_hodl\": 0.16813676257371357, \"calmar\": -1.1542427133141004, \"daily_log_sharpe\": -1.4015758382727845, \"daily_returns\": 0.020371572034616366, \"fee_revenue_over_value\": 0.04172022857157689, \"jax_sharpe\": -0.8497213838843736, \"return\": -0.35938864613922994, \"returns_over_hodl\": 0.013047777094425506, \"returns_over_uniform_hodl\": 0.06458970623780402, \"sharpe\": -1.009083606395454, \"sterling\": -2.001367675500016, \"ulcer\": -0.15111375686381495}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -3.0474789614578492, \"optuna_trial_number\": 5, \"price_ratio\": 158.68256685599886, \"shift_exponent\": 0.0029931328328113285, \"step\": 5, \"test_objective\": [{\"annualised_returns\": -0.6690415169408899, \"annualised_returns_over_hodl\": 0.03271179853487838, \"annualised_returns_over_uniform_hodl\": 0.16813676257371357, \"calmar\": -1.1542427133141004, \"daily_log_sharpe\": -1.4015758382727845, \"daily_returns\": 0.020371572034616366, \"fee_revenue_over_value\": 0.04172022857157689, \"jax_sharpe\": -0.8497213838843736, \"return\": -0.35938864613922994, \"returns_over_hodl\": 0.013047777094425506, \"returns_over_uniform_hodl\": 0.06458970623780402, \"sharpe\": -1.009083606395454, \"sterling\": -2.001367675500016, \"ulcer\": -0.15111375686381495}], \"train_objective\": [{\"annualised_returns\": -0.33291965509290977, \"annualised_returns_over_hodl\": -0.15992025585611458, \"annualised_returns_over_uniform_hodl\": -0.15992025585611436, \"calmar\": -0.4543563118533098, \"daily_log_sharpe\": -0.3370451974259816, \"daily_returns\": 0.007981932496925092, \"fee_revenue_over_value\": 0.015598159649821485, \"jax_sharpe\": 0.13617723569359635, \"return\": -0.21791349590982256, \"returns_over_hodl\": -0.10039188339004068, \"returns_over_uniform_hodl\": -0.10039188339004057, \"sharpe\": 0.1655019713638176, \"sterling\": -1.0842634051315951, \"ulcer\": -0.16568285806276936}], \"train_return\": -0.21791349590982256, \"train_returns_over_hodl\": -0.10039188339004068, \"train_sharpe\": 0.13617723569359633, \"validation_return\": -0.18846900791691445, \"validation_returns_over_hodl\": -0.03251089888371139, \"validation_sharpe\": -1.4799427033335304}, {\"centeredness_margin\": 0.9471377615893438, \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -Infinity, \"optuna_trial_number\": 6, \"price_ratio\": 1.161706459267995, \"shift_exponent\": 0.2498790638011502, \"step\": 6, \"test_objective\": -Infinity, \"train_objective\": -Infinity, \"train_return\": -Infinity, \"train_returns_over_hodl\": -Infinity, \"train_sharpe\": -Infinity, \"validation_return\": -Infinity, \"validation_returns_over_hodl\": -Infinity, \"validation_sharpe\": -Infinity}, {\"centeredness_margin\": 0.3715959828031041, \"continuous_test_metrics\": [{\"annualised_returns\": -0.6668380876774018, \"annualised_returns_over_hodl\": -0.15090220111147457, \"annualised_returns_over_uniform_hodl\": 0.17591389130182433, \"calmar\": -1.127605255496241, \"daily_log_sharpe\": -1.2914388571682698, \"daily_returns\": 0.024679018750181823, \"fee_revenue_over_value\": 0.0005451369796337714, \"jax_sharpe\": -0.6627082406869704, \"return\": -0.3576743676540589, \"returns_over_hodl\": -0.0637569975025446, \"returns_over_uniform_hodl\": 0.06743855245000296, \"sharpe\": -0.8713858097746185, \"sterling\": -1.8558338137743544, \"ulcer\": -0.1594314965590225}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -2.9831008254443594, \"optuna_trial_number\": 7, \"price_ratio\": 37.257907317277564, \"shift_exponent\": 0.0009982876023968785, \"step\": 7, \"test_objective\": [{\"annualised_returns\": -0.6668380876774018, \"annualised_returns_over_hodl\": -0.15090220111147457, \"annualised_returns_over_uniform_hodl\": 0.17591389130182433, \"calmar\": -1.127605255496241, \"daily_log_sharpe\": -1.2914388571682698, \"daily_returns\": 0.024679018750181823, \"fee_revenue_over_value\": 0.0005451369796337714, \"jax_sharpe\": -0.6627082406869704, \"return\": -0.3576743676540589, \"returns_over_hodl\": -0.0637569975025446, \"returns_over_uniform_hodl\": 0.06743855245000296, \"sharpe\": -0.8713858097746185, \"sterling\": -1.8558338137743544, \"ulcer\": -0.1594314965590225}], \"train_objective\": [{\"annualised_returns\": -0.35802457844887026, \"annualised_returns_over_hodl\": -0.19153584421910907, \"annualised_returns_over_uniform_hodl\": -0.1915358442191093, \"calmar\": -0.48774014832992835, \"daily_log_sharpe\": -0.35683838981430344, \"daily_returns\": 0.008051653769263407, \"fee_revenue_over_value\": 0.020690404068827918, \"jax_sharpe\": 0.16107306399583357, \"return\": -0.2359174083901573, \"returns_over_hodl\": -0.1211011856390114, \"returns_over_uniform_hodl\": -0.12110118563901151, \"sharpe\": 0.16181754473030024, \"sterling\": -1.1444944664725105, \"ulcer\": -0.1685283260531518}], \"train_return\": -0.2359174083901573, \"train_returns_over_hodl\": -0.1211011856390114, \"train_sharpe\": 0.1610730639958336, \"validation_return\": -0.23501921376695645, \"validation_returns_over_hodl\": -0.04540732737344977, \"validation_sharpe\": -1.7584248007101093}, {\"centeredness_margin\": 0.4516079943045338, \"continuous_test_metrics\": [{\"annualised_returns\": -0.6540073254758583, \"annualised_returns_over_hodl\": 0.11712889093979717, \"annualised_returns_over_uniform_hodl\": 0.22120079520870317, \"calmar\": -1.1500488055441758, \"daily_log_sharpe\": -1.362863689394875, \"daily_returns\": 0.019860453164211193, \"fee_revenue_over_value\": 0.06586891958462657, \"jax_sharpe\": -0.8162262688929137, \"return\": -0.3478240070126357, \"returns_over_hodl\": 0.0456179057305659, \"returns_over_uniform_hodl\": 0.08380821633183988, \"sharpe\": -0.9739214664885126, \"sterling\": -1.9826945874873934, \"ulcer\": -0.1482856128315258}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -2.5663885008193175, \"optuna_trial_number\": 8, \"price_ratio\": 54.28874719166965, \"shift_exponent\": 0.7301981887541154, \"step\": 8, \"test_objective\": [{\"annualised_returns\": -0.6540073254758583, \"annualised_returns_over_hodl\": 0.11712889093979717, \"annualised_returns_over_uniform_hodl\": 0.22120079520870317, \"calmar\": -1.1500488055441758, \"daily_log_sharpe\": -1.362863689394875, \"daily_returns\": 0.019860453164211193, \"fee_revenue_over_value\": 0.06586891958462657, \"jax_sharpe\": -0.8162262688929137, \"return\": -0.3478240070126357, \"returns_over_hodl\": 0.0456179057305659, \"returns_over_uniform_hodl\": 0.08380821633183988, \"sharpe\": -0.9739214664885126, \"sterling\": -1.9826945874873934, \"ulcer\": -0.1482856128315258}], \"train_objective\": [{\"annualised_returns\": -0.3173117444965675, \"annualised_returns_over_hodl\": -0.14026461820991432, \"annualised_returns_over_uniform_hodl\": -0.14026461820991432, \"calmar\": -0.4340834409769612, \"daily_log_sharpe\": -0.3154251677329145, \"daily_returns\": 0.008024878721838455, \"fee_revenue_over_value\": 0.029142762859802634, \"jax_sharpe\": 0.15046361774706424, \"return\": -0.20685444793480767, \"returns_over_hodl\": -0.08767102800093485, \"returns_over_uniform_hodl\": -0.08767102800093485, \"sharpe\": 0.18518581265519987, \"sterling\": -1.0365208663486256, \"ulcer\": -0.16503683293893814}], \"train_return\": -0.20685444793480767, \"train_returns_over_hodl\": -0.08767102800093485, \"train_sharpe\": 0.1504636177470642, \"validation_return\": -0.1801231919758961, \"validation_returns_over_hodl\": -0.024307826563857104, \"validation_sharpe\": -1.4141996399992642}, {\"centeredness_margin\": 0.8514365691086092, \"continuous_test_metrics\": [{\"annualised_returns\": -0.6778726305716357, \"annualised_returns_over_hodl\": 0.11724276939731149, \"annualised_returns_over_uniform_hodl\": 0.13696684545544557, \"calmar\": -1.180261565191513, \"daily_log_sharpe\": -1.432226862777219, \"daily_returns\": 0.018667209078811364, \"fee_revenue_over_value\": 0.11957489170946776, \"jax_sharpe\": -0.9187267797522083, \"return\": -0.366328584362542, \"returns_over_hodl\": 0.04566083173859692, \"returns_over_uniform_hodl\": 0.053056681183005994, \"sharpe\": -1.0500398014441619, \"sterling\": -2.0588430048755844, \"ulcer\": -0.154707132808202}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -3.4704942789430593, \"optuna_trial_number\": 9, \"price_ratio\": 3.2308066909949145, \"shift_exponent\": 13.122775564247547, \"step\": 9, \"test_objective\": [{\"annualised_returns\": -0.6778726305716357, \"annualised_returns_over_hodl\": 0.11724276939731149, \"annualised_returns_over_uniform_hodl\": 0.13696684545544557, \"calmar\": -1.180261565191513, \"daily_log_sharpe\": -1.432226862777219, \"daily_returns\": 0.018667209078811364, \"fee_revenue_over_value\": 0.11957489170946776, \"jax_sharpe\": -0.9187267797522083, \"return\": -0.366328584362542, \"returns_over_hodl\": 0.04566083173859692, \"returns_over_uniform_hodl\": 0.053056681183005994, \"sharpe\": -1.0500398014441619, \"sterling\": -2.0588430048755844, \"ulcer\": -0.154707132808202}], \"train_objective\": [{\"annualised_returns\": -0.5143038326521316, \"annualised_returns_over_hodl\": -0.38834427500019963, \"annualised_returns_over_uniform_hodl\": -0.38834427500019963, \"calmar\": -0.6735993215846658, \"daily_log_sharpe\": -0.6900031163490119, \"daily_returns\": 0.00832572376997785, \"fee_revenue_over_value\": 0.05733753710529108, \"jax_sharpe\": -0.18385402545906, \"return\": -0.35496201822326545, \"returns_over_hodl\": -0.25803424443039746, \"returns_over_uniform_hodl\": -0.25803424443039746, \"sharpe\": -0.21406059308888165, \"sterling\": -1.6564638250185026, \"ulcer\": -0.16835772810729766}], \"train_return\": -0.35496201822326545, \"train_returns_over_hodl\": -0.25803424443039746, \"train_sharpe\": -0.18385402545906, \"validation_return\": -0.17154325193107622, \"validation_returns_over_hodl\": -0.028427713632161455, \"validation_sharpe\": -1.3966527369160917}, {\"centeredness_margin\": 0.822081802323659, \"continuous_test_metrics\": [{\"annualised_returns\": -0.6508843227697834, \"annualised_returns_over_hodl\": 0.20506603081195585, \"annualised_returns_over_uniform_hodl\": 0.2322236106291249, \"calmar\": -1.158491081636268, \"daily_log_sharpe\": -1.3665471952720387, \"daily_returns\": 0.018727103684996957, \"fee_revenue_over_value\": 0.11672680021712882, \"jax_sharpe\": -0.8090864460724437, \"return\": -0.3454595791572709, \"returns_over_hodl\": 0.07801829465849863, \"returns_over_uniform_hodl\": 0.08773750284977822, \"sharpe\": -0.9845063063619411, \"sterling\": -1.9693061204740094, \"ulcer\": -0.14947620951036164}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -2.7502184609202356, \"optuna_trial_number\": 10, \"price_ratio\": 3.453439768111635, \"shift_exponent\": 2.644386683336268, \"step\": 10, \"test_objective\": [{\"annualised_returns\": -0.6508843227697834, \"annualised_returns_over_hodl\": 0.20506603081195585, \"annualised_returns_over_uniform_hodl\": 0.2322236106291249, \"calmar\": -1.158491081636268, \"daily_log_sharpe\": -1.3665471952720387, \"daily_returns\": 0.018727103684996957, \"fee_revenue_over_value\": 0.11672680021712882, \"jax_sharpe\": -0.8090864460724437, \"return\": -0.3454595791572709, \"returns_over_hodl\": 0.07801829465849863, \"returns_over_uniform_hodl\": 0.08773750284977822, \"sharpe\": -0.9845063063619411, \"sterling\": -1.9693061204740094, \"ulcer\": -0.14947620951036164}], \"train_objective\": [{\"annualised_returns\": -0.4438643268343113, \"annualised_returns_over_hodl\": -0.29963711629460477, \"annualised_returns_over_uniform_hodl\": -0.299637116294605, \"calmar\": -0.5888568563919538, \"daily_log_sharpe\": -0.5435488626474493, \"daily_returns\": 0.008343641460319042, \"fee_revenue_over_value\": 0.05639904168950563, \"jax_sharpe\": -0.11429358254294177, \"return\": -0.2996844232790643, \"returns_over_hodl\": -0.19445026386250475, \"returns_over_uniform_hodl\": -0.19445026386250486, \"sharpe\": -0.06336002449231976, \"sterling\": -1.44200773096214, \"ulcer\": -0.16614832411944302}], \"train_return\": -0.2996844232790643, \"train_returns_over_hodl\": -0.19445026386250475, \"train_sharpe\": -0.11429358254294177, \"validation_return\": -0.1699699760333121, \"validation_returns_over_hodl\": -0.024766128407895383, \"validation_sharpe\": -1.3730650016171386}, {\"centeredness_margin\": 0.047584843027494585, \"continuous_test_metrics\": [{\"annualised_returns\": -0.48450648230773297, \"annualised_returns_over_hodl\": 4.1250558524552616e-11, \"annualised_returns_over_uniform_hodl\": 0.819463647883687, \"calmar\": -0.8358050147945487, \"daily_log_sharpe\": -0.6924636701835181, \"daily_returns\": 0.031016041364514316, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": 0.005019196286315608, \"return\": -0.23422461676171946, \"returns_over_hodl\": 1.6613155295885917e-11, \"returns_over_uniform_hodl\": 0.2725915414589508, \"sharpe\": -0.2021086156396561, \"sterling\": -1.2473844088650672, \"ulcer\": -0.17641031364658877}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -0.19538131030986766, \"optuna_trial_number\": 11, \"price_ratio\": 1.174502338378968, \"shift_exponent\": 2.650169688107134e-05, \"step\": 11, \"test_objective\": [{\"annualised_returns\": -0.48450648230773297, \"annualised_returns_over_hodl\": 4.1250558524552616e-11, \"annualised_returns_over_uniform_hodl\": 0.819463647883687, \"calmar\": -0.8358050147945487, \"daily_log_sharpe\": -0.6924636701835181, \"daily_returns\": 0.031016041364514316, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": 0.005019196286315608, \"return\": -0.23422461676171946, \"returns_over_hodl\": 1.6613155295885917e-11, \"returns_over_uniform_hodl\": 0.2725915414589508, \"sharpe\": -0.2021086156396561, \"sterling\": -1.2473844088650672, \"ulcer\": -0.17641031364658877}], \"train_objective\": [{\"annualised_returns\": -0.6770853865616113, \"annualised_returns_over_hodl\": -0.5933413000267216, \"annualised_returns_over_uniform_hodl\": -0.593341300026722, \"calmar\": -0.8466547724459513, \"daily_log_sharpe\": -0.7671775422565718, \"daily_returns\": 0.010764829168933109, \"fee_revenue_over_value\": 0.0018061984562553182, \"jax_sharpe\": 0.03224857721574503, \"return\": -0.4965496535076652, \"returns_over_hodl\": -0.42089779628472, \"returns_over_uniform_hodl\": -0.42089779628472046, \"sharpe\": -0.08591081026859863, \"sterling\": -1.7970577571701432, \"ulcer\": -0.21159159829161064}], \"train_return\": -0.4965496535076652, \"train_returns_over_hodl\": -0.42089779628472, \"train_sharpe\": 0.03224857721574503, \"validation_return\": -0.3391427107825513, \"validation_returns_over_hodl\": -5.822155979551269e-09, \"validation_sharpe\": -2.2438531859876143}, {\"centeredness_margin\": 0.6733518417795908, \"continuous_test_metrics\": [{\"annualised_returns\": -0.6225236073356177, \"annualised_returns_over_hodl\": -0.26773785307556874, \"annualised_returns_over_uniform_hodl\": 0.33232436648623764, \"calmar\": -1.07457138278806, \"daily_log_sharpe\": -1.0598906536579573, \"daily_returns\": 0.03101604153888227, \"fee_revenue_over_value\": 0.000733994524891811, \"jax_sharpe\": -0.3860425432263199, \"return\": -0.32454329455894626, \"returns_over_hodl\": -0.11794408869858564, \"returns_over_uniform_hodl\": 0.12249689501777294, \"sharpe\": -0.5956721338094445, \"sterling\": -1.6037164009292124, \"ulcer\": -0.17617193467603265}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -0.1115161658667363, \"optuna_trial_number\": 12, \"price_ratio\": 1.4618154918727937, \"shift_exponent\": 0.0036579799259836886, \"step\": 12, \"test_objective\": [{\"annualised_returns\": -0.6225236073356177, \"annualised_returns_over_hodl\": -0.26773785307556874, \"annualised_returns_over_uniform_hodl\": 0.33232436648623764, \"calmar\": -1.07457138278806, \"daily_log_sharpe\": -1.0598906536579573, \"daily_returns\": 0.03101604153888227, \"fee_revenue_over_value\": 0.000733994524891811, \"jax_sharpe\": -0.3860425432263199, \"return\": -0.32454329455894626, \"returns_over_hodl\": -0.11794408869858564, \"returns_over_uniform_hodl\": 0.12249689501777294, \"sharpe\": -0.5956721338094445, \"sterling\": -1.6037164009292124, \"ulcer\": -0.17617193467603265}], \"train_objective\": [{\"annualised_returns\": -0.5993228026362359, \"annualised_returns_over_hodl\": -0.495411909501664, \"annualised_returns_over_uniform_hodl\": -0.495411909501664, \"calmar\": -0.759140151589114, \"daily_log_sharpe\": -0.6217951306224263, \"daily_returns\": 0.009389805859774737, \"fee_revenue_over_value\": 0.0013297454916523762, \"jax_sharpe\": 0.12472319330360261, \"return\": -0.42608427549636607, \"returns_over_hodl\": -0.33984381355080484, \"returns_over_uniform_hodl\": -0.33984381355080484, \"sharpe\": 0.03987067069867342, \"sterling\": -1.6374859670856199, \"ulcer\": -0.20052158730957084}], \"train_return\": -0.42608427549636607, \"train_returns_over_hodl\": -0.33984381355080484, \"train_sharpe\": 0.12472319330360261, \"validation_return\": -0.33914271103058913, \"validation_returns_over_hodl\": -3.2860124621336695e-09, \"validation_sharpe\": -2.2438531763471645}, {\"centeredness_margin\": 0.7682956045894073, \"continuous_test_metrics\": [{\"annualised_returns\": -0.4845064804654926, \"annualised_returns_over_hodl\": 3.871836184998756e-11, \"annualised_returns_over_uniform_hodl\": 0.8194636543859788, \"calmar\": -0.8358050118428924, \"daily_log_sharpe\": -0.6924636655577769, \"daily_returns\": 0.031016041452055717, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": 0.00501920182424641, \"return\": -0.23422461565955377, \"returns_over_hodl\": 1.559330442546525e-11, \"returns_over_uniform_hodl\": 0.27259154329056723, \"sharpe\": -0.2021086102138662, \"sterling\": -1.2473844026915106, \"ulcer\": -0.17641031382755692}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -0.16850401382303287, \"optuna_trial_number\": 13, \"price_ratio\": 1.9077098834738853, \"shift_exponent\": 3.367980994881573e-05, \"step\": 13, \"test_objective\": [{\"annualised_returns\": -0.4845064804654926, \"annualised_returns_over_hodl\": 3.871836184998756e-11, \"annualised_returns_over_uniform_hodl\": 0.8194636543859788, \"calmar\": -0.8358050118428924, \"daily_log_sharpe\": -0.6924636655577769, \"daily_returns\": 0.031016041452055717, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": 0.00501920182424641, \"return\": -0.23422461565955377, \"returns_over_hodl\": 1.559330442546525e-11, \"returns_over_uniform_hodl\": 0.27259154329056723, \"sharpe\": -0.2021086102138662, \"sterling\": -1.2473844026915106, \"ulcer\": -0.17641031382755692}], \"train_objective\": [{\"annualised_returns\": -0.6406673380715542, \"annualised_returns_over_hodl\": -0.547478661303634, \"annualised_returns_over_uniform_hodl\": -0.547478661303634, \"calmar\": -0.8155348542890052, \"daily_log_sharpe\": -0.6975766423139129, \"daily_returns\": 0.008821702610719151, \"fee_revenue_over_value\": 0.0008023037705626827, \"jax_sharpe\": 0.07465445332840298, \"return\": -0.4628042877511609, \"returns_over_hodl\": -0.38208162342691243, \"returns_over_uniform_hodl\": -0.38208162342691243, \"sharpe\": -0.02764469456032239, \"sterling\": -1.7240465354733583, \"ulcer\": -0.20734690359113633}], \"train_return\": -0.4628042877511609, \"train_returns_over_hodl\": -0.38208162342691243, \"train_sharpe\": 0.07465445332840298, \"validation_return\": -0.3391427111765667, \"validation_returns_over_hodl\": -4.998411351486709e-09, \"validation_sharpe\": -2.24385318387774}, {\"centeredness_margin\": 0.11027079919698404, \"continuous_test_metrics\": [{\"annualised_returns\": -0.4845064804556317, \"annualised_returns_over_hodl\": 3.968980699653457e-11, \"annualised_returns_over_uniform_hodl\": 0.8194636544207832, \"calmar\": -0.8358050118272342, \"daily_log_sharpe\": -0.6924636655333013, \"daily_returns\": 0.031016041452497853, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": 0.005019201853215672, \"return\": -0.23422461565365427, \"returns_over_hodl\": 1.5984547019343154e-11, \"returns_over_uniform_hodl\": 0.27259154330037116, \"sharpe\": -0.2021086101853019, \"sterling\": -1.2473844026589105, \"ulcer\": -0.1764103138284578}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -0.16942900749638345, \"optuna_trial_number\": 14, \"price_ratio\": 1.7368587178231059, \"shift_exponent\": 4.3383000038978194e-05, \"step\": 14, \"test_objective\": [{\"annualised_returns\": -0.4845064804556317, \"annualised_returns_over_hodl\": 3.968980699653457e-11, \"annualised_returns_over_uniform_hodl\": 0.8194636544207832, \"calmar\": -0.8358050118272342, \"daily_log_sharpe\": -0.6924636655333013, \"daily_returns\": 0.031016041452497853, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": 0.005019201853215672, \"return\": -0.23422461565365427, \"returns_over_hodl\": 1.5984547019343154e-11, \"returns_over_uniform_hodl\": 0.27259154330037116, \"sharpe\": -0.2021086101853019, \"sterling\": -1.2473844026589105, \"ulcer\": -0.1764103138284578}], \"train_objective\": [{\"annualised_returns\": -0.6239241193061452, \"annualised_returns_over_hodl\": -0.5263932867397216, \"annualised_returns_over_uniform_hodl\": -0.5263932867397214, \"calmar\": -0.800425588312126, \"daily_log_sharpe\": -0.6616197743821787, \"daily_returns\": 0.00901424132161826, \"fee_revenue_over_value\": 0.0338689886494184, \"jax_sharpe\": 0.1086703326212499, \"return\": -0.4477437216436736, \"returns_over_hodl\": -0.36475795470205974, \"returns_over_uniform_hodl\": -0.36475795470205963, \"sharpe\": 0.00875187593303115, \"sterling\": -1.6895167119388088, \"ulcer\": -0.20511458037351643}], \"train_return\": -0.4477437216436736, \"train_returns_over_hodl\": -0.36475795470205974, \"train_sharpe\": 0.1086703326212499, \"validation_return\": -0.33914271122799633, \"validation_returns_over_hodl\": -5.0271233842380525e-09, \"validation_sharpe\": -2.2438531843622207}, {\"centeredness_margin\": 0.051326558821043575, \"continuous_test_metrics\": [{\"annualised_returns\": -0.48450648079816394, \"annualised_returns_over_hodl\": 3.9160896747603147e-11, \"annualised_returns_over_uniform_hodl\": 0.8194636532117976, \"calmar\": -0.8358050123758988, \"daily_log_sharpe\": -0.6924636663930867, \"daily_returns\": 0.031016041436245624, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": 0.005019200824217415, \"return\": -0.23422461585858256, \"returns_over_hodl\": 1.577160624322005e-11, \"returns_over_uniform_hodl\": 0.2725915429598147, \"sharpe\": -0.20210861119364462, \"sterling\": -1.2473844038063266, \"ulcer\": -0.1764103137948781}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -0.1741494330467881, \"optuna_trial_number\": 15, \"price_ratio\": 1.5974526121297734, \"shift_exponent\": 9.795640778242455e-05, \"step\": 15, \"test_objective\": [{\"annualised_returns\": -0.48450648079816394, \"annualised_returns_over_hodl\": 3.9160896747603147e-11, \"annualised_returns_over_uniform_hodl\": 0.8194636532117976, \"calmar\": -0.8358050123758988, \"daily_log_sharpe\": -0.6924636663930867, \"daily_returns\": 0.031016041436245624, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": 0.005019200824217415, \"return\": -0.23422461585858256, \"returns_over_hodl\": 1.577160624322005e-11, \"returns_over_uniform_hodl\": 0.2725915429598147, \"sharpe\": -0.20210861119364462, \"sterling\": -1.2473844038063266, \"ulcer\": -0.1764103137948781}], \"train_objective\": [{\"annualised_returns\": -0.6301190161047125, \"annualised_returns_over_hodl\": -0.5341947567684374, \"annualised_returns_over_uniform_hodl\": -0.5341947567684375, \"calmar\": -0.806139929628165, \"daily_log_sharpe\": -0.6720716641903903, \"daily_returns\": 0.008963320742088983, \"fee_revenue_over_value\": 0.03588007302364913, \"jax_sharpe\": 0.10436544641109295, \"return\": -0.453284727962621, \"returns_over_hodl\": -0.37113159014090535, \"returns_over_uniform_hodl\": -0.37113159014090547, \"sharpe\": 0.0004992001758221952, \"sterling\": -1.7028311638805755, \"ulcer\": -0.20560023849530698}], \"train_return\": -0.453284727962621, \"train_returns_over_hodl\": -0.37113159014090535, \"train_sharpe\": 0.10436544641109294, \"validation_return\": -0.3391427111437755, \"validation_returns_over_hodl\": -5.171041372875607e-09, \"validation_sharpe\": -2.2438531846384344}, {\"centeredness_margin\": 0.619097154529434, \"continuous_test_metrics\": [{\"annualised_returns\": -0.48450647961554616, \"annualised_returns_over_hodl\": 3.7523983920095816e-11, \"annualised_returns_over_uniform_hodl\": 0.8194636573859131, \"calmar\": -0.8358050104810941, \"daily_log_sharpe\": -0.6924636634236074, \"daily_returns\": 0.03101604149244049, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": 0.005019204379274893, \"return\": -0.2342246151510523, \"returns_over_hodl\": 1.511235581119763e-11, \"returns_over_uniform_hodl\": 0.27259154413561215, \"sharpe\": -0.20210860771057537, \"sterling\": -1.2473843998432375, \"ulcer\": -0.17641031391105003}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -0.15218188760374607, \"optuna_trial_number\": 16, \"price_ratio\": 2.154010001878652, \"shift_exponent\": 0.0003054881258680994, \"step\": 16, \"test_objective\": [{\"annualised_returns\": -0.48450647961554616, \"annualised_returns_over_hodl\": 3.7523983920095816e-11, \"annualised_returns_over_uniform_hodl\": 0.8194636573859131, \"calmar\": -0.8358050104810941, \"daily_log_sharpe\": -0.6924636634236074, \"daily_returns\": 0.03101604149244049, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": 0.005019204379274893, \"return\": -0.2342246151510523, \"returns_over_hodl\": 1.511235581119763e-11, \"returns_over_uniform_hodl\": 0.27259154413561215, \"sharpe\": -0.20210860771057537, \"sterling\": -1.2473843998432375, \"ulcer\": -0.17641031391105003}], \"train_objective\": [{\"annualised_returns\": -0.6224525241019625, \"annualised_returns_over_hodl\": -0.5245400507209246, \"annualised_returns_over_uniform_hodl\": -0.5245400507209248, \"calmar\": -0.796717554657417, \"daily_log_sharpe\": -0.6615925266066681, \"daily_returns\": 0.008629982683983399, \"fee_revenue_over_value\": 0.001229463666140466, \"jax_sharpe\": 0.09341567687712166, \"return\": -0.44643274268248423, \"returns_over_hodl\": -0.3632499791673547, \"returns_over_uniform_hodl\": -0.3632499791673548, \"sharpe\": 0.006453413451340615, \"sterling\": -1.6855594846210915, \"ulcer\": -0.20591525232925526}], \"train_return\": -0.44643274268248423, \"train_returns_over_hodl\": -0.3632499791673547, \"train_sharpe\": 0.09341567687712164, \"validation_return\": -0.3391427113231432, \"validation_returns_over_hodl\": -4.504152051332255e-09, \"validation_sharpe\": -2.2438531825199726}, {\"centeredness_margin\": 0.4993449197692139, \"continuous_test_metrics\": [{\"annualised_returns\": -0.48450648102256055, \"annualised_returns_over_hodl\": 3.9471981239103116e-11, \"annualised_returns_over_uniform_hodl\": 0.8194636524197751, \"calmar\": -0.8358050127354298, \"daily_log_sharpe\": -0.6924636669565297, \"daily_returns\": 0.031016041425582754, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": 0.005019200149661261, \"return\": -0.2342246159928334, \"returns_over_hodl\": 1.5896839400397766e-11, \"returns_over_uniform_hodl\": 0.2725915427367116, \"sharpe\": -0.2021086118545417, \"sterling\": -1.247384404558306, \"ulcer\": -0.17641031377283495}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -0.15918912047130468, \"optuna_trial_number\": 17, \"price_ratio\": 1.5417827969797353, \"shift_exponent\": 0.0014886448537767844, \"step\": 17, \"test_objective\": [{\"annualised_returns\": -0.48450648102256055, \"annualised_returns_over_hodl\": 3.9471981239103116e-11, \"annualised_returns_over_uniform_hodl\": 0.8194636524197751, \"calmar\": -0.8358050127354298, \"daily_log_sharpe\": -0.6924636669565297, \"daily_returns\": 0.031016041425582754, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": 0.005019200149661261, \"return\": -0.2342246159928334, \"returns_over_hodl\": 1.5896839400397766e-11, \"returns_over_uniform_hodl\": 0.2725915427367116, \"sharpe\": -0.2021086118545417, \"sterling\": -1.247384404558306, \"ulcer\": -0.17641031377283495}], \"train_objective\": [{\"annualised_returns\": -0.6529728523647655, \"annualised_returns_over_hodl\": -0.5629754652162717, \"annualised_returns_over_uniform_hodl\": -0.5629754652162717, \"calmar\": -0.8260986639872567, \"daily_log_sharpe\": -0.7189221620492546, \"daily_returns\": 0.009212271049797447, \"fee_revenue_over_value\": 0.0013321334829440236, \"jax_sharpe\": 0.06349603632614469, \"return\": -0.4740495615420077, \"returns_over_hodl\": -0.3950166881836118, \"returns_over_uniform_hodl\": -0.3950166881836118, \"sharpe\": -0.04410356946335166, \"sterling\": -1.75061329092838, \"ulcer\": -0.20845622163917507}], \"train_return\": -0.4740495615420077, \"train_returns_over_hodl\": -0.3950166881836118, \"train_sharpe\": 0.06349603632614469, \"validation_return\": -0.3391427107264423, \"validation_returns_over_hodl\": -4.717578216961726e-09, \"validation_sharpe\": -2.2438531811860254}, {\"centeredness_margin\": 0.8287087068073223, \"continuous_test_metrics\": [{\"annualised_returns\": -0.5806265120295897, \"annualised_returns_over_hodl\": -0.1864621629314056, \"annualised_returns_over_uniform_hodl\": 0.48020254389281236, \"calmar\": -1.0001627824623547, \"daily_log_sharpe\": -0.9372048502657127, \"daily_returns\": 0.031150904608584707, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": -0.2595304211455035, \"return\": -0.2952953507024625, \"returns_over_hodl\": -0.07975019792982829, \"returns_over_uniform_hodl\": 0.17110212152613924, \"sharpe\": -0.469779847061233, \"sterling\": -1.5147172082262637, \"ulcer\": -0.17213556526784957}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -2.9684415781954296, \"optuna_trial_number\": 18, \"price_ratio\": 10.561249488711791, \"shift_exponent\": 0.0007280565784300721, \"step\": 18, \"test_objective\": [{\"annualised_returns\": -0.5806265120295897, \"annualised_returns_over_hodl\": -0.1864621629314056, \"annualised_returns_over_uniform_hodl\": 0.48020254389281236, \"calmar\": -1.0001627824623547, \"daily_log_sharpe\": -0.9372048502657127, \"daily_returns\": 0.031150904608584707, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": -0.2595304211455035, \"return\": -0.2952953507024625, \"returns_over_hodl\": -0.07975019792982829, \"returns_over_uniform_hodl\": 0.17110212152613924, \"sharpe\": -0.469779847061233, \"sterling\": -1.5147172082262637, \"ulcer\": -0.17213556526784957}], \"train_objective\": [{\"annualised_returns\": -0.42597993439139503, \"annualised_returns_over_hodl\": -0.27711461815428495, \"annualised_returns_over_uniform_hodl\": -0.27711461815428484, \"calmar\": -0.5716291154575417, \"daily_log_sharpe\": -0.4346806869308049, \"daily_returns\": 0.008136008828090414, \"fee_revenue_over_value\": 0.000674658965158186, \"jax_sharpe\": 0.0913796725109698, \"return\": -0.28609656557983454, \"returns_over_hodl\": -0.17882060268098576, \"returns_over_uniform_hodl\": -0.17882060268098565, \"sharpe\": 0.11036722141670491, \"sterling\": -1.3161300641703377, \"ulcer\": -0.17491696384653646}], \"train_return\": -0.28609656557983454, \"train_returns_over_hodl\": -0.17882060268098576, \"train_sharpe\": 0.09137967251096979, \"validation_return\": -0.29761876594179115, \"validation_returns_over_hodl\": -0.06740181339213691, \"validation_sharpe\": -2.03210732828531}, {\"centeredness_margin\": 0.59139905769927, \"continuous_test_metrics\": [{\"annualised_returns\": -0.5002106078473354, \"annualised_returns_over_hodl\": -0.03046426880289066, \"annualised_returns_over_uniform_hodl\": 0.764035044883939, \"calmar\": -0.8628956969384957, \"daily_log_sharpe\": -0.733881996087974, \"daily_returns\": 0.0310160417352677, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": -0.05262698191464579, \"return\": -0.243706886784685, \"returns_over_hodl\": -0.012382579580699882, \"returns_over_uniform_hodl\": 0.2568335830690809, \"sharpe\": -0.2492769629595213, \"sterling\": -1.2878154752871014, \"ulcer\": -0.17641031441304378}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -4.017173955465776, \"optuna_trial_number\": 19, \"price_ratio\": 6.595581885018263, \"shift_exponent\": 0.00041227924517541007, \"step\": 19, \"test_objective\": [{\"annualised_returns\": -0.5002106078473354, \"annualised_returns_over_hodl\": -0.03046426880289066, \"annualised_returns_over_uniform_hodl\": 0.764035044883939, \"calmar\": -0.8628956969384957, \"daily_log_sharpe\": -0.733881996087974, \"daily_returns\": 0.0310160417352677, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": -0.05262698191464579, \"return\": -0.243706886784685, \"returns_over_hodl\": -0.012382579580699882, \"returns_over_uniform_hodl\": 0.2568335830690809, \"sharpe\": -0.2492769629595213, \"sterling\": -1.2878154752871014, \"ulcer\": -0.17641031441304378}], \"train_objective\": [{\"annualised_returns\": -0.4584360819773067, \"annualised_returns_over_hodl\": -0.3179878838231547, \"annualised_returns_over_uniform_hodl\": -0.31798788382315457, \"calmar\": -0.6144269362577761, \"daily_log_sharpe\": -0.4548532582683416, \"daily_returns\": 0.008165951396233578, \"fee_revenue_over_value\": 0.014640022488313054, \"jax_sharpe\": 0.10437341809510747, \"return\": -0.31088286312525937, \"returns_over_hodl\": -0.20733145708898182, \"returns_over_uniform_hodl\": -0.2073314570889817, \"sharpe\": 0.1181126870347613, \"sterling\": -1.3808344941365225, \"ulcer\": -0.1795080214159336}], \"train_return\": -0.31088286312525937, \"train_returns_over_hodl\": -0.20733145708898182, \"train_sharpe\": 0.10437341809510746, \"validation_return\": -0.3363912953028616, \"validation_returns_over_hodl\": -0.039009088388164415, \"validation_sharpe\": -2.2195910287970095}, {\"centeredness_margin\": 0.6711266657075208, \"continuous_test_metrics\": [{\"annualised_returns\": -0.6496173673767044, \"annualised_returns_over_hodl\": 0.10699248033117148, \"annualised_returns_over_uniform_hodl\": 0.23669540164507574, \"calmar\": -1.1875605865945917, \"daily_log_sharpe\": -1.3767498964402172, \"daily_returns\": 0.020323602565744815, \"fee_revenue_over_value\": 0.09816723055151862, \"jax_sharpe\": -0.7985377426641642, \"return\": -0.34450396879213274, \"returns_over_hodl\": 0.04178650791946237, \"returns_over_uniform_hodl\": 0.08932556861191143, \"sharpe\": -0.9960161980948806, \"sterling\": -1.9564580149844766, \"ulcer\": -0.1441761349428617}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -3.09618805942241, \"optuna_trial_number\": 20, \"price_ratio\": 3.408949946983975, \"shift_exponent\": 0.013313040793229794, \"step\": 20, \"test_objective\": [{\"annualised_returns\": -0.6496173673767044, \"annualised_returns_over_hodl\": 0.10699248033117148, \"annualised_returns_over_uniform_hodl\": 0.23669540164507574, \"calmar\": -1.1875605865945917, \"daily_log_sharpe\": -1.3767498964402172, \"daily_returns\": 0.020323602565744815, \"fee_revenue_over_value\": 0.09816723055151862, \"jax_sharpe\": -0.7985377426641642, \"return\": -0.34450396879213274, \"returns_over_hodl\": 0.04178650791946237, \"returns_over_uniform_hodl\": 0.08932556861191143, \"sharpe\": -0.9960161980948806, \"sterling\": -1.9564580149844766, \"ulcer\": -0.1441761349428617}], \"train_objective\": [{\"annualised_returns\": -0.36161685222935125, \"annualised_returns_over_hodl\": -0.19605973172597557, \"annualised_returns_over_uniform_hodl\": -0.19605973172597546, \"calmar\": -0.4880595309870576, \"daily_log_sharpe\": -0.3851969640831576, \"daily_returns\": 0.008374337374930839, \"fee_revenue_over_value\": 0.020947120440232114, \"jax_sharpe\": 0.12027526878037192, \"return\": -0.23851604303027296, \"returns_over_hodl\": -0.12409030871187599, \"returns_over_uniform_hodl\": -0.12409030871187587, \"sharpe\": 0.11726035842422533, \"sterling\": -1.1617219627604425, \"ulcer\": -0.16874645309020542}], \"train_return\": -0.23851604303027296, \"train_returns_over_hodl\": -0.12409030871187599, \"train_sharpe\": 0.12027526878037191, \"validation_return\": -0.18071530227698418, \"validation_returns_over_hodl\": -0.04005246127234774, \"validation_sharpe\": -1.4519672661435568}, {\"centeredness_margin\": 0.8830590935737885, \"continuous_test_metrics\": [{\"annualised_returns\": -0.5248947640942274, \"annualised_returns_over_hodl\": -0.07834877877772017, \"annualised_returns_over_uniform_hodl\": 0.6769109134865934, \"calmar\": -0.9057548349342196, \"daily_log_sharpe\": -0.7914611502267107, \"daily_returns\": 0.031016041594841903, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": -0.11056094562616518, \"return\": -0.2589781153418348, \"returns_over_hodl\": -0.03232475574497584, \"returns_over_uniform_hodl\": 0.2314553367648795, \"sharpe\": -0.31125896283131493, \"sterling\": -1.3522033825798416, \"ulcer\": -0.1761736033119485}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -3.8645123342149277, \"optuna_trial_number\": 21, \"price_ratio\": 5.968262589581211, \"shift_exponent\": 0.0008741824895079894, \"step\": 21, \"test_objective\": [{\"annualised_returns\": -0.5248947640942274, \"annualised_returns_over_hodl\": -0.07834877877772017, \"annualised_returns_over_uniform_hodl\": 0.6769109134865934, \"calmar\": -0.9057548349342196, \"daily_log_sharpe\": -0.7914611502267107, \"daily_returns\": 0.031016041594841903, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": -0.11056094562616518, \"return\": -0.2589781153418348, \"returns_over_hodl\": -0.03232475574497584, \"returns_over_uniform_hodl\": 0.2314553367648795, \"sharpe\": -0.31125896283131493, \"sterling\": -1.3522033825798416, \"ulcer\": -0.1761736033119485}], \"train_objective\": [{\"annualised_returns\": -0.4645651811167313, \"annualised_returns_over_hodl\": -0.32570649234788573, \"annualised_returns_over_uniform_hodl\": -0.32570649234788585, \"calmar\": -0.6190717386260176, \"daily_log_sharpe\": -0.4678623209684097, \"daily_returns\": 0.008187253406869075, \"fee_revenue_over_value\": 0.0004852295482233526, \"jax_sharpe\": 0.14421861444324235, \"return\": -0.3156283966589586, \"returns_over_hodl\": -0.21279008661683296, \"returns_over_uniform_hodl\": -0.21279008661683307, \"sharpe\": 0.1043413884966376, \"sterling\": -1.3964626704791752, \"ulcer\": -0.1800858116821985}], \"train_return\": -0.3156283966589586, \"train_returns_over_hodl\": -0.21279008661683296, \"train_sharpe\": 0.14421861444324235, \"validation_return\": -0.3330762709615347, \"validation_returns_over_hodl\": -0.055162229052321154, \"validation_sharpe\": -2.1909578447118694}, {\"centeredness_margin\": 0.36686962143028967, \"continuous_test_metrics\": [{\"annualised_returns\": -0.6528552469564431, \"annualised_returns_over_hodl\": 0.008385404003219854, \"annualised_returns_over_uniform_hodl\": 0.2252671217746851, \"calmar\": -1.186927991463391, \"daily_log_sharpe\": -1.3511402163576294, \"daily_returns\": 0.022273219133434146, \"fee_revenue_over_value\": 0.1268838004482437, \"jax_sharpe\": -0.7685615581062795, \"return\": -0.34695028937989414, \"returns_over_hodl\": 0.0033687001209286027, \"returns_over_uniform_hodl\": 0.08526018996978646, \"sharpe\": -0.9675069266853169, \"sterling\": -1.9278260032220658, \"ulcer\": -0.14847029316960203}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -2.5605187115732715, \"optuna_trial_number\": 22, \"price_ratio\": 2.919610002872762, \"shift_exponent\": 3.005185811633982, \"step\": 22, \"test_objective\": [{\"annualised_returns\": -0.6528552469564431, \"annualised_returns_over_hodl\": 0.008385404003219854, \"annualised_returns_over_uniform_hodl\": 0.2252671217746851, \"calmar\": -1.186927991463391, \"daily_log_sharpe\": -1.3511402163576294, \"daily_returns\": 0.022273219133434146, \"fee_revenue_over_value\": 0.1268838004482437, \"jax_sharpe\": -0.7685615581062795, \"return\": -0.34695028937989414, \"returns_over_hodl\": 0.0033687001209286027, \"returns_over_uniform_hodl\": 0.08526018996978646, \"sharpe\": -0.9675069266853169, \"sterling\": -1.9278260032220658, \"ulcer\": -0.14847029316960203}], \"train_objective\": [{\"annualised_returns\": -0.3568631168120474, \"annualised_returns_over_hodl\": -0.19007317124104228, \"annualised_returns_over_uniform_hodl\": -0.19007317124104228, \"calmar\": -0.48011809808469236, \"daily_log_sharpe\": -0.3672113382956054, \"daily_returns\": 0.00836827183472043, \"fee_revenue_over_value\": 0.06353356545952848, \"jax_sharpe\": 0.07861036487299729, \"return\": -0.2350784352463572, \"returns_over_hodl\": -0.12013614271110873, \"returns_over_uniform_hodl\": -0.12013614271110873, \"sharpe\": 0.13907375403983316, \"sterling\": -1.1430819850620408, \"ulcer\": -0.16801807315394965}], \"train_return\": -0.2350784352463572, \"train_returns_over_hodl\": -0.12013614271110873, \"train_sharpe\": 0.07861036487299729, \"validation_return\": -0.20978810825726024, \"validation_returns_over_hodl\": -0.032271558134628786, \"validation_sharpe\": -1.6384371821379744}, {\"centeredness_margin\": 0.5610665219591892, \"continuous_test_metrics\": [{\"annualised_returns\": -0.6442538951058212, \"annualised_returns_over_hodl\": 0.054753604639973696, \"annualised_returns_over_uniform_hodl\": 0.2556260816408036, \"calmar\": -1.2128587160511595, \"daily_log_sharpe\": -1.348153296353299, \"daily_returns\": 0.021832662895800648, \"fee_revenue_over_value\": 0.141355415125614, \"jax_sharpe\": -0.7207734738223132, \"return\": -0.34048122649878165, \"returns_over_hodl\": 0.021700934729727894, \"returns_over_uniform_hodl\": 0.09601069838761611, \"sharpe\": -0.9669164234756521, \"sterling\": -1.8903250815075137, \"ulcer\": -0.1411427305987714}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -3.0440571232700084, \"optuna_trial_number\": 23, \"price_ratio\": 1.490856219583378, \"shift_exponent\": 0.021613277746029225, \"step\": 23, \"test_objective\": [{\"annualised_returns\": -0.6442538951058212, \"annualised_returns_over_hodl\": 0.054753604639973696, \"annualised_returns_over_uniform_hodl\": 0.2556260816408036, \"calmar\": -1.2128587160511595, \"daily_log_sharpe\": -1.348153296353299, \"daily_returns\": 0.021832662895800648, \"fee_revenue_over_value\": 0.141355415125614, \"jax_sharpe\": -0.7207734738223132, \"return\": -0.34048122649878165, \"returns_over_hodl\": 0.021700934729727894, \"returns_over_uniform_hodl\": 0.09601069838761611, \"sharpe\": -0.9669164234756521, \"sterling\": -1.8903250815075137, \"ulcer\": -0.1411427305987714}], \"train_objective\": [{\"annualised_returns\": -0.45407861163914964, \"annualised_returns_over_hodl\": -0.3125003550798172, \"annualised_returns_over_uniform_hodl\": -0.31250035507981666, \"calmar\": -0.6007075303056214, \"daily_log_sharpe\": -0.5474065547851055, \"daily_returns\": 0.009145545276699906, \"fee_revenue_over_value\": 0.04487850623459214, \"jax_sharpe\": -0.12974690762883456, \"return\": -0.30752186028258444, \"returns_over_hodl\": -0.20346540720651174, \"returns_over_uniform_hodl\": -0.2034654072065114, \"sharpe\": -0.04830069481051337, \"sterling\": -1.4282377548081469, \"ulcer\": -0.1742337541211422}], \"train_return\": -0.30752186028258444, \"train_returns_over_hodl\": -0.20346540720651174, \"train_sharpe\": -0.12974690762883453, \"validation_return\": -0.1916828701211024, \"validation_returns_over_hodl\": -0.057261670655790464, \"validation_sharpe\": -1.5702125597484018}, {\"centeredness_margin\": 0.41671902389445764, \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -Infinity, \"optuna_trial_number\": 24, \"price_ratio\": 1.0499235359407775, \"shift_exponent\": 0.7074892602929548, \"step\": 24, \"test_objective\": -Infinity, \"train_objective\": -Infinity, \"train_return\": -Infinity, \"train_returns_over_hodl\": -Infinity, \"train_sharpe\": -Infinity, \"validation_return\": -Infinity, \"validation_returns_over_hodl\": -Infinity, \"validation_sharpe\": -Infinity}, {\"centeredness_margin\": 0.5351616581811076, \"continuous_test_metrics\": [{\"annualised_returns\": -0.6032917425318195, \"annualised_returns_over_hodl\": 0.23970500051110322, \"annualised_returns_over_uniform_hodl\": 0.40020432557510044, \"calmar\": -1.1465838874077479, \"daily_log_sharpe\": -1.2019203097173086, \"daily_returns\": 0.021264667512838592, \"fee_revenue_over_value\": 0.17479572291615697, \"jax_sharpe\": -0.5879338923197224, \"return\": -0.31088900179297374, \"returns_over_hodl\": 0.09039243653609796, \"returns_over_uniform_hodl\": 0.1451880625049029, \"sharpe\": -0.8155364170636148, \"sterling\": -1.7687930414151172, \"ulcer\": -0.14003326164860447}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -2.809669701765351, \"optuna_trial_number\": 25, \"price_ratio\": 1.3180101401456639, \"shift_exponent\": 0.05146389581264966, \"step\": 25, \"test_objective\": [{\"annualised_returns\": -0.6032917425318195, \"annualised_returns_over_hodl\": 0.23970500051110322, \"annualised_returns_over_uniform_hodl\": 0.40020432557510044, \"calmar\": -1.1465838874077479, \"daily_log_sharpe\": -1.2019203097173086, \"daily_returns\": 0.021264667512838592, \"fee_revenue_over_value\": 0.17479572291615697, \"jax_sharpe\": -0.5879338923197224, \"return\": -0.31088900179297374, \"returns_over_hodl\": 0.09039243653609796, \"returns_over_uniform_hodl\": 0.1451880625049029, \"sharpe\": -0.8155364170636148, \"sterling\": -1.7687930414151172, \"ulcer\": -0.14003326164860447}], \"train_objective\": [{\"annualised_returns\": -0.515377713244715, \"annualised_returns_over_hodl\": -0.3896966538258476, \"annualised_returns_over_uniform_hodl\": -0.38969665382584806, \"calmar\": -0.6742361353557893, \"daily_log_sharpe\": -0.6805288784841687, \"daily_returns\": 0.009782708795354901, \"fee_revenue_over_value\": 0.08052026363049712, \"jax_sharpe\": -0.29488680473219, \"return\": -0.35582826357226005, \"returns_over_hodl\": -0.25903065766966893, \"returns_over_uniform_hodl\": -0.25903065766966926, \"sharpe\": -0.19392933300817608, \"sterling\": -1.6163527161471531, \"ulcer\": -0.1730184369532823}], \"train_return\": -0.35582826357226005, \"train_returns_over_hodl\": -0.25903065766966893, \"train_sharpe\": -0.2948868047321899, \"validation_return\": -0.19062168767203969, \"validation_returns_over_hodl\": -0.04933753121870321, \"validation_sharpe\": -1.5751726934562758}, {\"centeredness_margin\": 0.46891553418341514, \"continuous_test_metrics\": [{\"annualised_returns\": -0.5981222765499117, \"annualised_returns_over_hodl\": 0.2103619961459522, \"annualised_returns_over_uniform_hodl\": 0.4184502493554014, \"calmar\": -1.1309008992542042, \"daily_log_sharpe\": -1.1927382440312768, \"daily_returns\": 0.02163131261902313, \"fee_revenue_over_value\": 0.15168037532042924, \"jax_sharpe\": -0.5875651223107582, \"return\": -0.3072864984773157, \"returns_over_hodl\": 0.07992381603067211, \"returns_over_uniform_hodl\": 0.1511748248740421, \"sharpe\": -0.8070892365994674, \"sterling\": -1.76483633311676, \"ulcer\": -0.14247350768303324}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -2.725617091108537, \"optuna_trial_number\": 26, \"price_ratio\": 1.9697280919257323, \"shift_exponent\": 0.06065040869631065, \"step\": 26, \"test_objective\": [{\"annualised_returns\": -0.5981222765499117, \"annualised_returns_over_hodl\": 0.2103619961459522, \"annualised_returns_over_uniform_hodl\": 0.4184502493554014, \"calmar\": -1.1309008992542042, \"daily_log_sharpe\": -1.1927382440312768, \"daily_returns\": 0.02163131261902313, \"fee_revenue_over_value\": 0.15168037532042924, \"jax_sharpe\": -0.5875651223107582, \"return\": -0.3072864984773157, \"returns_over_hodl\": 0.07992381603067211, \"returns_over_uniform_hodl\": 0.1511748248740421, \"sharpe\": -0.8070892365994674, \"sterling\": -1.76483633311676, \"ulcer\": -0.14247350768303324}], \"train_objective\": [{\"annualised_returns\": -0.36349278136533214, \"annualised_returns_over_hodl\": -0.1984221609005401, \"annualised_returns_over_uniform_hodl\": -0.19842216090054, \"calmar\": -0.49184989034413223, \"daily_log_sharpe\": -0.3883035464252666, \"daily_returns\": 0.008663666249628936, \"fee_revenue_over_value\": 0.0673122819765104, \"jax_sharpe\": 0.02008324762514072, \"return\": -0.23987536479221017, \"returns_over_hodl\": -0.12565389136383076, \"returns_over_uniform_hodl\": -0.12565389136383065, \"sharpe\": 0.11062976477215229, \"sterling\": -1.16625021172047, \"ulcer\": -0.1693848794527353}], \"train_return\": -0.23987536479221017, \"train_returns_over_hodl\": -0.12565389136383076, \"train_sharpe\": 0.020083247625140715, \"validation_return\": -0.19710032722395554, \"validation_returns_over_hodl\": -0.039677400570753085, \"validation_sharpe\": -1.5706631977151333}, {\"centeredness_margin\": 0.5633667554146873, \"continuous_test_metrics\": [{\"annualised_returns\": -0.7838870109410131, \"annualised_returns_over_hodl\": -0.5807648779006547, \"annualised_returns_over_uniform_hodl\": -0.23721693109040176, \"calmar\": -1.3997050013010492, \"daily_log_sharpe\": -1.6371695603490934, \"daily_returns\": 0.031016041504593393, \"fee_revenue_over_value\": 0.009212213842515516, \"jax_sharpe\": -0.9371985957727408, \"return\": -0.4604262395190374, \"returns_over_hodl\": -0.29538899910231664, \"returns_over_uniform_hodl\": -0.10331799818693166, \"sharpe\": -1.182490332000148, \"sterling\": -2.0686230213643646, \"ulcer\": -0.16742747807769054}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -0.12286213432049775, \"optuna_trial_number\": 27, \"price_ratio\": 1.0107581934097132, \"shift_exponent\": 0.004807014458404444, \"step\": 27, \"test_objective\": [{\"annualised_returns\": -0.7838870109410131, \"annualised_returns_over_hodl\": -0.5807648779006547, \"annualised_returns_over_uniform_hodl\": -0.23721693109040176, \"calmar\": -1.3997050013010492, \"daily_log_sharpe\": -1.6371695603490934, \"daily_returns\": 0.031016041504593393, \"fee_revenue_over_value\": 0.009212213842515516, \"jax_sharpe\": -0.9371985957727408, \"return\": -0.4604262395190374, \"returns_over_hodl\": -0.29538899910231664, \"returns_over_uniform_hodl\": -0.10331799818693166, \"sharpe\": -1.182490332000148, \"sterling\": -2.0686230213643646, \"ulcer\": -0.16742747807769054}], \"train_objective\": [{\"annualised_returns\": -0.6181158663033349, \"annualised_returns_over_hodl\": -0.5190787320031334, \"annualised_returns_over_uniform_hodl\": -0.5190787320031351, \"calmar\": -0.7698601784180152, \"daily_log_sharpe\": -0.6501560500640043, \"daily_returns\": 0.014689320926142386, \"fee_revenue_over_value\": 0.000261485098893507, \"jax_sharpe\": 0.12901725843467127, \"return\": -0.44258103189703624, \"returns_over_hodl\": -0.35881948424473087, \"returns_over_uniform_hodl\": -0.3588194842447322, \"sharpe\": 0.02101896299943928, \"sterling\": -1.6564564974615088, \"ulcer\": -0.20720924669762283}], \"train_return\": -0.44258103189703624, \"train_returns_over_hodl\": -0.35881948424473087, \"train_sharpe\": 0.12901725843467124, \"validation_return\": -0.33914271087614334, \"validation_returns_over_hodl\": -3.6248410939521136e-09, \"validation_sharpe\": -2.2438531772010064}, {\"centeredness_margin\": 0.546941958621945, \"continuous_test_metrics\": [{\"annualised_returns\": -0.48450648136971064, \"annualised_returns_over_hodl\": 3.995204167495103e-11, \"annualised_returns_over_uniform_hodl\": 0.8194636511944899, \"calmar\": -0.8358050132916377, \"daily_log_sharpe\": -0.6924636678282039, \"daily_returns\": 0.031016041409086974, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": 0.005019199106096017, \"return\": -0.23422461620052448, \"returns_over_hodl\": 1.609024025128747e-11, \"returns_over_uniform_hodl\": 0.2725915423915637, \"sharpe\": -0.20210861287697518, \"sterling\": -1.2473844057216459, \"ulcer\": -0.1764103137387334}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -0.17237992624614706, \"optuna_trial_number\": 28, \"price_ratio\": 1.4825501667586876, \"shift_exponent\": 0.0009809841080548728, \"step\": 28, \"test_objective\": [{\"annualised_returns\": -0.48450648136971064, \"annualised_returns_over_hodl\": 3.995204167495103e-11, \"annualised_returns_over_uniform_hodl\": 0.8194636511944899, \"calmar\": -0.8358050132916377, \"daily_log_sharpe\": -0.6924636678282039, \"daily_returns\": 0.031016041409086974, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": 0.005019199106096017, \"return\": -0.23422461620052448, \"returns_over_hodl\": 1.609024025128747e-11, \"returns_over_uniform_hodl\": 0.2725915423915637, \"sharpe\": -0.20210861287697518, \"sterling\": -1.2473844057216459, \"ulcer\": -0.1764103137387334}], \"train_objective\": [{\"annualised_returns\": -0.6592305763720234, \"annualised_returns_over_hodl\": -0.570856055947321, \"annualised_returns_over_uniform_hodl\": -0.5708560559473213, \"calmar\": -0.8320222092020139, \"daily_log_sharpe\": -0.7314559464460665, \"daily_returns\": 0.00931909583014928, \"fee_revenue_over_value\": 0.0011600259935780185, \"jax_sharpe\": 0.05615857706511265, \"return\": -0.4798281524478022, \"returns_over_hodl\": -0.4016636092776774, \"returns_over_uniform_hodl\": -0.40166360927767764, \"sharpe\": -0.05532286885678368, \"sterling\": -1.7624060462943962, \"ulcer\": -0.20910322361950867}], \"train_return\": -0.4798281524478022, \"train_returns_over_hodl\": -0.4016636092776774, \"train_sharpe\": 0.056158577065112646, \"validation_return\": -0.3391427107802144, \"validation_returns_over_hodl\": -5.117416823630094e-09, \"validation_sharpe\": -2.2438531828918893}, {\"centeredness_margin\": 0.03622594537094864, \"continuous_test_metrics\": [{\"annualised_returns\": -0.48783261865600847, \"annualised_returns_over_hodl\": 0.10895073281109302, \"annualised_returns_over_uniform_hodl\": 0.8077238607362485, \"calmar\": -0.8921521941627363, \"daily_log_sharpe\": -0.7824267177282299, \"daily_returns\": 0.02874171668246537, \"fee_revenue_over_value\": 0.11082934770002234, \"jax_sharpe\": -0.15708346229806489, \"return\": -0.23621840745245715, \"returns_over_hodl\": 0.0425283233974334, \"returns_over_uniform_hodl\": 0.26927819236989725, \"sharpe\": -0.3413049871657034, \"sterling\": -1.3233217041195646, \"ulcer\": -0.16039525145754477}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -2.8062902702558636, \"optuna_trial_number\": 29, \"price_ratio\": 6.306909114812609, \"shift_exponent\": 0.07665030506437354, \"step\": 29, \"test_objective\": [{\"annualised_returns\": -0.48783261865600847, \"annualised_returns_over_hodl\": 0.10895073281109302, \"annualised_returns_over_uniform_hodl\": 0.8077238607362485, \"calmar\": -0.8921521941627363, \"daily_log_sharpe\": -0.7824267177282299, \"daily_returns\": 0.02874171668246537, \"fee_revenue_over_value\": 0.11082934770002234, \"jax_sharpe\": -0.15708346229806489, \"return\": -0.23621840745245715, \"returns_over_hodl\": 0.0425283233974334, \"returns_over_uniform_hodl\": 0.26927819236989725, \"sharpe\": -0.3413049871657034, \"sterling\": -1.3233217041195646, \"ulcer\": -0.16039525145754477}], \"train_objective\": [{\"annualised_returns\": -0.41900906692598416, \"annualised_returns_over_hodl\": -0.2683359386421206, \"annualised_returns_over_uniform_hodl\": -0.2683359386421206, \"calmar\": -0.5681533433872417, \"daily_log_sharpe\": -0.394155953131155, \"daily_returns\": 0.008173714246885674, \"fee_revenue_over_value\": 0.04696437500780176, \"jax_sharpe\": 0.21370410124768294, \"return\": -0.2808455500969156, \"returns_over_hodl\": -0.17278053406431282, \"returns_over_uniform_hodl\": -0.17278053406431282, \"sharpe\": 0.18211080389839596, \"sterling\": -1.2628921351655669, \"ulcer\": -0.17884421406393453}], \"train_return\": -0.2808455500969156, \"train_returns_over_hodl\": -0.17278053406431282, \"train_sharpe\": 0.21370410124768296, \"validation_return\": -0.3017571473008174, \"validation_returns_over_hodl\": -0.023652335597211138, \"validation_sharpe\": -2.133843541219203}, {\"centeredness_margin\": 0.44963915031466284, \"continuous_test_metrics\": [{\"annualised_returns\": -0.48450648222343606, \"annualised_returns_over_hodl\": 4.11342071515719e-11, \"annualised_returns_over_uniform_hodl\": 0.8194636481812168, \"calmar\": -0.8358050146594874, \"daily_log_sharpe\": -0.6924636699718529, \"daily_returns\": 0.031016041368519858, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": 0.005019196539719268, \"return\": -0.2342246167112868, \"returns_over_hodl\": 1.6566303884246736e-11, \"returns_over_uniform_hodl\": 0.27259154154276133, \"sharpe\": -0.20210861539138295, \"sterling\": -1.2473844085825787, \"ulcer\": -0.17641031365486948}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -0.19053111248312887, \"optuna_trial_number\": 30, \"price_ratio\": 1.1133753736934335, \"shift_exponent\": 0.0008711434638680357, \"step\": 30, \"test_objective\": [{\"annualised_returns\": -0.48450648222343606, \"annualised_returns_over_hodl\": 4.11342071515719e-11, \"annualised_returns_over_uniform_hodl\": 0.8194636481812168, \"calmar\": -0.8358050146594874, \"daily_log_sharpe\": -0.6924636699718529, \"daily_returns\": 0.031016041368519858, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": 0.005019196539719268, \"return\": -0.2342246167112868, \"returns_over_hodl\": 1.6566303884246736e-11, \"returns_over_uniform_hodl\": 0.27259154154276133, \"sharpe\": -0.20210861539138295, \"sterling\": -1.2473844085825787, \"ulcer\": -0.17641031365486948}], \"train_objective\": [{\"annualised_returns\": -0.6749059766045237, \"annualised_returns_over_hodl\": -0.5905966858687526, \"annualised_returns_over_uniform_hodl\": -0.5905966858687529, \"calmar\": -0.8426178813255194, \"daily_log_sharpe\": -0.7618572317962512, \"daily_returns\": 0.012387743039981167, \"fee_revenue_over_value\": 0.0011966613297377192, \"jax_sharpe\": 0.03692024202231753, \"return\": -0.49448945516390763, \"returns_over_hodl\": -0.41852801859110444, \"returns_over_uniform_hodl\": -0.4185280185911048, \"sharpe\": -0.08048691777343543, \"sterling\": -1.7890502936959503, \"ulcer\": -0.21204142658061242}], \"train_return\": -0.49448945516390763, \"train_returns_over_hodl\": -0.41852801859110444, \"train_sharpe\": 0.03692024202231753, \"validation_return\": -0.339142710698094, \"validation_returns_over_hodl\": -5.671784819583081e-09, \"validation_sharpe\": -2.243853184897093}, {\"centeredness_margin\": 0.7285521536164183, \"continuous_test_metrics\": [{\"annualised_returns\": -0.7622503774274736, \"annualised_returns_over_hodl\": -0.3563799281843244, \"annualised_returns_over_uniform_hodl\": -0.16084920426290794, \"calmar\": -1.3110536200831266, \"daily_log_sharpe\": -1.7946967904223545, \"daily_returns\": 0.023493206707916707, \"fee_revenue_over_value\": 0.04840520896952192, \"jax_sharpe\": -1.1744377692881192, \"return\": -0.43928798703770433, \"returns_over_hodl\": -0.16260976271320982, \"returns_over_uniform_hodl\": -0.06818973225180469, \"sharpe\": -1.4109013195695346, \"sterling\": -2.215121307165161, \"ulcer\": -0.143982152629916}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -3.2794096888754503, \"optuna_trial_number\": 31, \"price_ratio\": 1.6108516220611575, \"shift_exponent\": 0.008652706700815036, \"step\": 31, \"test_objective\": [{\"annualised_returns\": -0.7622503774274736, \"annualised_returns_over_hodl\": -0.3563799281843244, \"annualised_returns_over_uniform_hodl\": -0.16084920426290794, \"calmar\": -1.3110536200831266, \"daily_log_sharpe\": -1.7946967904223545, \"daily_returns\": 0.023493206707916707, \"fee_revenue_over_value\": 0.04840520896952192, \"jax_sharpe\": -1.1744377692881192, \"return\": -0.43928798703770433, \"returns_over_hodl\": -0.16260976271320982, \"returns_over_uniform_hodl\": -0.06818973225180469, \"sharpe\": -1.4109013195695346, \"sterling\": -2.215121307165161, \"ulcer\": -0.143982152629916}], \"train_objective\": [{\"annualised_returns\": -0.47100066677326324, \"annualised_returns_over_hodl\": -0.33381094510992015, \"annualised_returns_over_uniform_hodl\": -0.33381094510992015, \"calmar\": -0.6178294681096719, \"daily_log_sharpe\": -0.4972261733841254, \"daily_returns\": 0.008974759370878662, \"fee_revenue_over_value\": 0.010941982492568458, \"jax_sharpe\": 0.15028105155641105, \"return\": -0.3206341812397695, \"returns_over_hodl\": -0.21854807427593126, \"returns_over_uniform_hodl\": -0.21854807427593126, \"sharpe\": 0.08471291742616917, \"sterling\": -1.401787492269078, \"ulcer\": -0.18207932026664037}], \"train_return\": -0.3206341812397695, \"train_returns_over_hodl\": -0.21854807427593126, \"train_sharpe\": 0.15028105155641108, \"validation_return\": -0.21439869725488692, \"validation_returns_over_hodl\": -0.05661251816365198, \"validation_sharpe\": -1.759973216603608}, {\"centeredness_margin\": 0.770560883302617, \"continuous_test_metrics\": [{\"annualised_returns\": -0.48450648178418876, \"annualised_returns_over_hodl\": 4.052602697868224e-11, \"annualised_returns_over_uniform_hodl\": 0.8194636497315666, \"calmar\": -0.8358050139557197, \"daily_log_sharpe\": -0.6924636688689288, \"daily_returns\": 0.03101604138939186, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": 0.0050191978601375, \"return\": -0.23422461644849624, \"returns_over_hodl\": 1.6321388685014426e-11, \"returns_over_uniform_hodl\": 0.272591541979476, \"sharpe\": -0.20210861409770342, \"sterling\": -1.2473844071106097, \"ulcer\": -0.17641031369801802}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -0.17657753122221032, \"optuna_trial_number\": 32, \"price_ratio\": 1.0367190747440165, \"shift_exponent\": 0.0018239674403245673, \"step\": 32, \"test_objective\": [{\"annualised_returns\": -0.48450648178418876, \"annualised_returns_over_hodl\": 4.052602697868224e-11, \"annualised_returns_over_uniform_hodl\": 0.8194636497315666, \"calmar\": -0.8358050139557197, \"daily_log_sharpe\": -0.6924636688689288, \"daily_returns\": 0.03101604138939186, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": 0.0050191978601375, \"return\": -0.23422461644849624, \"returns_over_hodl\": 1.6321388685014426e-11, \"returns_over_uniform_hodl\": 0.272591541979476, \"sharpe\": -0.20210861409770342, \"sterling\": -1.2473844071106097, \"ulcer\": -0.17641031369801802}], \"train_objective\": [{\"annualised_returns\": -0.6666053825894961, \"annualised_returns_over_hodl\": -0.5801434309503235, \"annualised_returns_over_uniform_hodl\": -0.5801434309503251, \"calmar\": -0.830447933521378, \"daily_log_sharpe\": -0.7426801547904242, \"daily_returns\": 0.017260058474048608, \"fee_revenue_over_value\": 0.00018049791644172918, \"jax_sharpe\": 0.054126594904421545, \"return\": -0.48669209234133537, \"returns_over_hodl\": -0.40955897124576035, \"returns_over_uniform_hodl\": -0.40955897124576157, \"sharpe\": -0.06148990841535686, \"sterling\": -1.7639826086567063, \"ulcer\": -0.21271482013372442}], \"train_return\": -0.48669209234133537, \"train_returns_over_hodl\": -0.40955897124576035, \"train_sharpe\": 0.054126594904421545, \"validation_return\": -0.3391427106762771, \"validation_returns_over_hodl\": -5.245960332800337e-09, \"validation_sharpe\": -2.2438531832073605}, {\"centeredness_margin\": 0.035924936481540604, \"continuous_test_metrics\": [{\"annualised_returns\": -0.5657516296983051, \"annualised_returns_over_hodl\": -0.03243092329847208, \"annualised_returns_over_uniform_hodl\": 0.5327042858923627, \"calmar\": -1.0161007499978334, \"daily_log_sharpe\": -0.9740213051327619, \"daily_returns\": 0.028063183020661363, \"fee_revenue_over_value\": 0.1013756478230423, \"jax_sharpe\": -0.37149212736771825, \"return\": -0.28533342590658606, \"returns_over_hodl\": -0.013189886533033435, \"returns_over_uniform_hodl\": 0.18765718650912766, \"sharpe\": -0.546643923852742, \"sterling\": -1.5538461913270643, \"ulcer\": -0.1630454024394824}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -2.8882781464279605, \"optuna_trial_number\": 33, \"price_ratio\": 7.232872660919461, \"shift_exponent\": 14.719805899282015, \"step\": 33, \"test_objective\": [{\"annualised_returns\": -0.5657516296983051, \"annualised_returns_over_hodl\": -0.03243092329847208, \"annualised_returns_over_uniform_hodl\": 0.5327042858923627, \"calmar\": -1.0161007499978334, \"daily_log_sharpe\": -0.9740213051327619, \"daily_returns\": 0.028063183020661363, \"fee_revenue_over_value\": 0.1013756478230423, \"jax_sharpe\": -0.37149212736771825, \"return\": -0.28533342590658606, \"returns_over_hodl\": -0.013189886533033435, \"returns_over_uniform_hodl\": 0.18765718650912766, \"sharpe\": -0.546643923852742, \"sterling\": -1.5538461913270643, \"ulcer\": -0.1630454024394824}], \"train_objective\": [{\"annualised_returns\": -0.4087290056895644, \"annualised_returns_over_hodl\": -0.255389865085602, \"annualised_returns_over_uniform_hodl\": -0.255389865085602, \"calmar\": -0.5549671527300669, \"daily_log_sharpe\": -0.3843076869648661, \"daily_returns\": 0.008186719961240835, \"fee_revenue_over_value\": 0.04500052110675413, \"jax_sharpe\": 0.1672834562309392, \"return\": -0.27314672925521966, \"returns_over_hodl\": -0.16392483628498256, \"returns_over_uniform_hodl\": -0.16392483628498256, \"sharpe\": 0.18386120878650966, \"sterling\": -1.2426780942451814, \"ulcer\": -0.17717374062212057}], \"train_return\": -0.27314672925521966, \"train_returns_over_hodl\": -0.16392483628498256, \"train_sharpe\": 0.1672834562309392, \"validation_return\": -0.2972700408508243, \"validation_returns_over_hodl\": -0.02805182816494811, \"validation_sharpe\": -2.1201261812835686}, {\"centeredness_margin\": 0.648119081533195, \"continuous_test_metrics\": [{\"annualised_returns\": -0.4845064818300392, \"annualised_returns_over_hodl\": 4.05895317356908e-11, \"annualised_returns_over_uniform_hodl\": 0.8194636495697343, \"calmar\": -0.835805014029182, \"daily_log_sharpe\": -0.6924636689840591, \"daily_returns\": 0.03101604138721316, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": 0.005019197722306923, \"return\": -0.2342246164759274, \"returns_over_hodl\": 1.6346923814580805e-11, \"returns_over_uniform_hodl\": 0.2725915419338898, \"sharpe\": -0.20210861423274273, \"sterling\": -1.2473844072642601, \"ulcer\": -0.17641031369351398}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -0.18023948421108937, \"optuna_trial_number\": 34, \"price_ratio\": 1.042751495391484, \"shift_exponent\": 0.001527401211509358, \"step\": 34, \"test_objective\": [{\"annualised_returns\": -0.4845064818300392, \"annualised_returns_over_hodl\": 4.05895317356908e-11, \"annualised_returns_over_uniform_hodl\": 0.8194636495697343, \"calmar\": -0.835805014029182, \"daily_log_sharpe\": -0.6924636689840591, \"daily_returns\": 0.03101604138721316, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": 0.005019197722306923, \"return\": -0.2342246164759274, \"returns_over_hodl\": 1.6346923814580805e-11, \"returns_over_uniform_hodl\": 0.2725915419338898, \"sharpe\": -0.20210861423274273, \"sterling\": -1.2473844072642601, \"ulcer\": -0.17641031369351398}], \"train_objective\": [{\"annualised_returns\": -0.6676007063835474, \"annualised_returns_over_hodl\": -0.5813968802006755, \"annualised_returns_over_uniform_hodl\": -0.581396880200675, \"calmar\": -0.8316865323505233, \"daily_log_sharpe\": -0.7448543201080605, \"daily_returns\": 0.01639101899479461, \"fee_revenue_over_value\": 0.00034460591487221835, \"jax_sharpe\": 0.05217673076686483, \"return\": -0.4876230164132287, \"returns_over_hodl\": -0.4106297822706845, \"returns_over_uniform_hodl\": -0.41062978227068414, \"sharpe\": -0.06355953619606877, \"sterling\": -1.7666141414858185, \"ulcer\": -0.21271591547564017}], \"train_return\": -0.4876230164132287, \"train_returns_over_hodl\": -0.4106297822706845, \"train_sharpe\": 0.05217673076686484, \"validation_return\": -0.3391427107260103, \"validation_returns_over_hodl\": -5.3575942571271185e-09, \"validation_sharpe\": -2.243853183883851}, {\"centeredness_margin\": 0.4364515413201686, \"continuous_test_metrics\": [{\"annualised_returns\": -0.4845064772332913, \"annualised_returns_over_hodl\": 3.4407365845368076e-11, \"annualised_returns_over_uniform_hodl\": 0.8194636657942176, \"calmar\": -0.8358050066642406, \"daily_log_sharpe\": -0.6924636574419729, \"daily_returns\": 0.031016041605637517, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": 0.005019211540428036, \"return\": -0.2342246137258096, \"returns_over_hodl\": 1.3857137659556429e-11, \"returns_over_uniform_hodl\": 0.2725915465041291, \"sharpe\": -0.20210860069440112, \"sterling\": -1.2473843918601033, \"ulcer\": -0.1764103141450538}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -0.1215302244621338, \"optuna_trial_number\": 35, \"price_ratio\": 3.339314335342653, \"shift_exponent\": 4.370811401892988e-05, \"step\": 35, \"test_objective\": [{\"annualised_returns\": -0.4845064772332913, \"annualised_returns_over_hodl\": 3.4407365845368076e-11, \"annualised_returns_over_uniform_hodl\": 0.8194636657942176, \"calmar\": -0.8358050066642406, \"daily_log_sharpe\": -0.6924636574419729, \"daily_returns\": 0.031016041605637517, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": 0.005019211540428036, \"return\": -0.2342246137258096, \"returns_over_hodl\": 1.3857137659556429e-11, \"returns_over_uniform_hodl\": 0.2725915465041291, \"sharpe\": -0.20210860069440112, \"sterling\": -1.2473843918601033, \"ulcer\": -0.1764103141450538}], \"train_objective\": [{\"annualised_returns\": -0.5466166795952307, \"annualised_returns_over_hodl\": -0.42903707669907165, \"annualised_returns_over_uniform_hodl\": -0.42903707669907165, \"calmar\": -0.71927612321663, \"daily_log_sharpe\": -0.5454650003764866, \"daily_returns\": 0.008363963551262137, \"fee_revenue_over_value\": 0.02065261616050522, \"jax_sharpe\": 0.12079553787058982, \"return\": -0.3813672862800259, \"returns_over_hodl\": -0.2884073468185503, \"returns_over_uniform_hodl\": -0.2884073468185503, \"sharpe\": 0.0918212472172416, \"sterling\": -1.5311464163511743, \"ulcer\": -0.19540917801207927}], \"train_return\": -0.3813672862800259, \"train_returns_over_hodl\": -0.2884073468185503, \"train_sharpe\": 0.12079553787058982, \"validation_return\": -0.339142711962484, \"validation_returns_over_hodl\": -3.5815506116421147e-09, \"validation_sharpe\": -2.243853181076843}, {\"centeredness_margin\": 0.44324855874063585, \"continuous_test_metrics\": [{\"annualised_returns\": -0.48450648227310444, \"annualised_returns_over_hodl\": 4.120259688988881e-11, \"annualised_returns_over_uniform_hodl\": 0.8194636480059105, \"calmar\": -0.8358050147390665, \"daily_log_sharpe\": -0.6924636700965625, \"daily_returns\": 0.031016041366159795, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": 0.005019196390411127, \"return\": -0.23422461674100215, \"returns_over_hodl\": 1.659383741525744e-11, \"returns_over_uniform_hodl\": 0.2725915414933797, \"sharpe\": -0.2021086155376676, \"sterling\": -1.2473844087490233, \"ulcer\": -0.1764103136499904}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -0.19663206298126507, \"optuna_trial_number\": 36, \"price_ratio\": 1.1282780250374753, \"shift_exponent\": 1.3627116959293374e-05, \"step\": 36, \"test_objective\": [{\"annualised_returns\": -0.48450648227310444, \"annualised_returns_over_hodl\": 4.120259688988881e-11, \"annualised_returns_over_uniform_hodl\": 0.8194636480059105, \"calmar\": -0.8358050147390665, \"daily_log_sharpe\": -0.6924636700965625, \"daily_returns\": 0.031016041366159795, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": 0.005019196390411127, \"return\": -0.23422461674100215, \"returns_over_hodl\": 1.659383741525744e-11, \"returns_over_uniform_hodl\": 0.2725915414933797, \"sharpe\": -0.2021086155376676, \"sterling\": -1.2473844087490233, \"ulcer\": -0.1764103136499904}], \"train_objective\": [{\"annualised_returns\": -0.6761236461723947, \"annualised_returns_over_hodl\": -0.592130143640127, \"annualised_returns_over_uniform_hodl\": -0.5921301436401274, \"calmar\": -0.8442323876163166, \"daily_log_sharpe\": -0.7647792412032884, \"daily_returns\": 0.011794602250454572, \"fee_revenue_over_value\": 0.0012363170245950589, \"jax_sharpe\": 0.03476889959527259, \"return\": -0.49563984808770756, \"returns_over_hodl\": -0.41985127734333805, \"returns_over_uniform_hodl\": -0.4198512773433385, \"sharpe\": -0.08344099594675583, \"sterling\": -1.7924378957444278, \"ulcer\": -0.21197719010370303}], \"train_return\": -0.49563984808770756, \"train_returns_over_hodl\": -0.41985127734333805, \"train_sharpe\": 0.03476889959527259, \"validation_return\": -0.33914271082549097, \"validation_returns_over_hodl\": -5.859656981854755e-09, \"validation_sharpe\": -2.243853186314535}, {\"centeredness_margin\": 0.4326641140336924, \"continuous_test_metrics\": [{\"annualised_returns\": -0.47686684792067313, \"annualised_returns_over_hodl\": 0.014820029210077523, \"annualised_returns_over_uniform_hodl\": 0.8464281713419148, \"calmar\": -0.8226261296407479, \"daily_log_sharpe\": -0.6811399924724327, \"daily_returns\": 0.03101604161806471, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": -0.0009521001735557664, \"return\": -0.22967408719022675, \"returns_over_hodl\": 0.005942377452062386, \"returns_over_uniform_hodl\": 0.2801537660597886, \"sharpe\": -0.1913801456528958, \"sterling\": -1.2277157782758894, \"ulcer\": -0.17641031417075045}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -0.10516610015478256, \"optuna_trial_number\": 37, \"price_ratio\": 2.9315379150753205, \"shift_exponent\": 0.0013341013778179762, \"step\": 37, \"test_objective\": [{\"annualised_returns\": -0.47686684792067313, \"annualised_returns_over_hodl\": 0.014820029210077523, \"annualised_returns_over_uniform_hodl\": 0.8464281713419148, \"calmar\": -0.8226261296407479, \"daily_log_sharpe\": -0.6811399924724327, \"daily_returns\": 0.03101604161806471, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": -0.0009521001735557664, \"return\": -0.22967408719022675, \"returns_over_hodl\": 0.005942377452062386, \"returns_over_uniform_hodl\": 0.2801537660597886, \"sharpe\": -0.1913801456528958, \"sterling\": -1.2277157782758894, \"ulcer\": -0.17641031417075045}], \"train_objective\": [{\"annualised_returns\": -0.5409958442617784, \"annualised_returns_over_hodl\": -0.4219585441881799, \"annualised_returns_over_uniform_hodl\": -0.42195854418818, \"calmar\": -0.7088160388284329, \"daily_log_sharpe\": -0.539348597146783, \"daily_returns\": 0.008427023048395085, \"fee_revenue_over_value\": 0.017581910994072008, \"jax_sharpe\": 0.15532860844066698, \"return\": -0.3767222315047657, \"returns_over_hodl\": -0.2830642946675823, \"returns_over_uniform_hodl\": -0.28306429466758243, \"sharpe\": 0.09389206427310204, \"sterling\": -1.5285786626703715, \"ulcer\": -0.1928660694826435}], \"train_return\": -0.3767222315047657, \"train_returns_over_hodl\": -0.2830642946675823, \"train_sharpe\": 0.15532860844066698, \"validation_return\": -0.339142711777906, \"validation_returns_over_hodl\": -3.0947370222023096e-09, \"validation_sharpe\": -2.2438531783472326}, {\"centeredness_margin\": 0.43153466606935376, \"continuous_test_metrics\": [{\"annualised_returns\": -0.48450648148756403, \"annualised_returns_over_hodl\": 4.011524445957093e-11, \"annualised_returns_over_uniform_hodl\": 0.8194636507785193, \"calmar\": -0.8358050134804641, \"daily_log_sharpe\": -0.6924636681241297, \"daily_returns\": 0.031016041403486825, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": 0.005019198751817152, \"return\": -0.2342246162710332, \"returns_over_hodl\": 1.6155965454345278e-11, \"returns_over_uniform_hodl\": 0.27259154227438986, \"sharpe\": -0.20210861322407855, \"sterling\": -1.2473844061165862, \"ulcer\": -0.1764103137271563}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -0.1824014503066499, \"optuna_trial_number\": 38, \"price_ratio\": 1.4678443913565418, \"shift_exponent\": 2.980325720748101e-05, \"step\": 38, \"test_objective\": [{\"annualised_returns\": -0.48450648148756403, \"annualised_returns_over_hodl\": 4.011524445957093e-11, \"annualised_returns_over_uniform_hodl\": 0.8194636507785193, \"calmar\": -0.8358050134804641, \"daily_log_sharpe\": -0.6924636681241297, \"daily_returns\": 0.031016041403486825, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": 0.005019198751817152, \"return\": -0.2342246162710332, \"returns_over_hodl\": 1.6155965454345278e-11, \"returns_over_uniform_hodl\": 0.27259154227438986, \"sharpe\": -0.20210861322407855, \"sterling\": -1.2473844061165862, \"ulcer\": -0.1764103137271563}], \"train_objective\": [{\"annualised_returns\": -0.6608689754769557, \"annualised_returns_over_hodl\": -0.5729193544860733, \"annualised_returns_over_uniform_hodl\": -0.5729193544860733, \"calmar\": -0.8334785169928521, \"daily_log_sharpe\": -0.7345268492655305, \"daily_returns\": 0.009378939450109005, \"fee_revenue_over_value\": 0.001319889297302012, \"jax_sharpe\": 0.05461454498198891, \"return\": -0.48134797315751654, \"returns_over_hodl\": -0.4034118085358166, \"returns_over_uniform_hodl\": -0.4034118085358166, \"sharpe\": -0.05783746348007236, \"sterling\": -1.7659270252045602, \"ulcer\": -0.20925472044951932}], \"train_return\": -0.48134797315751654, \"train_returns_over_hodl\": -0.4034118085358166, \"train_sharpe\": 0.05461454498198891, \"validation_return\": -0.3391427109489349, \"validation_returns_over_hodl\": -5.4231912294255835e-09, \"validation_sharpe\": -2.2438531849549057}, {\"centeredness_margin\": 0.4084847505974458, \"continuous_test_metrics\": [{\"annualised_returns\": -0.48139551166583594, \"annualised_returns_over_hodl\": 0.006034922417176514, \"annualised_returns_over_uniform_hodl\": 0.8304439954502376, \"calmar\": -0.8304383808903137, \"daily_log_sharpe\": -0.6897330980836178, \"daily_returns\": 0.031016041707999847, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": -0.0055960562376894665, \"return\": -0.23236674542854407, \"returns_over_hodl\": 0.0024261252689663504, \"returns_over_uniform_hodl\": 0.2756790151431008, \"sharpe\": -0.20050699934975852, \"sterling\": -1.2393750512947432, \"ulcer\": -0.1764103143566811}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": 0.6714537457794364, \"optuna_trial_number\": 39, \"price_ratio\": 5.992515918267629, \"shift_exponent\": 0.00014153838858700288, \"step\": 39, \"test_objective\": [{\"annualised_returns\": -0.48139551166583594, \"annualised_returns_over_hodl\": 0.006034922417176514, \"annualised_returns_over_uniform_hodl\": 0.8304439954502376, \"calmar\": -0.8304383808903137, \"daily_log_sharpe\": -0.6897330980836178, \"daily_returns\": 0.031016041707999847, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": -0.0055960562376894665, \"return\": -0.23236674542854407, \"returns_over_hodl\": 0.0024261252689663504, \"returns_over_uniform_hodl\": 0.2756790151431008, \"sharpe\": -0.20050699934975852, \"sterling\": -1.2393750512947432, \"ulcer\": -0.1764103143566811}], \"train_objective\": [{\"annualised_returns\": -0.47556357906483104, \"annualised_returns_over_hodl\": -0.3395571947479379, \"annualised_returns_over_uniform_hodl\": -0.339557194747938, \"calmar\": -0.6367947390905202, \"daily_log_sharpe\": -0.4693339983261094, \"daily_returns\": 0.008218420938685425, \"fee_revenue_over_value\": 0.017621180983720237, \"jax_sharpe\": 0.10251059844470106, \"return\": -0.3241979081888643, \"returns_over_hodl\": -0.22264731096141377, \"returns_over_uniform_hodl\": -0.22264731096141388, \"sharpe\": 0.11472391797645144, \"sterling\": -1.4167764812998844, \"ulcer\": -0.18174942040568762}], \"train_return\": -0.3241979081888643, \"train_returns_over_hodl\": -0.22264731096141377, \"train_sharpe\": 0.10251059844470106, \"validation_return\": -0.3391248167688018, \"validation_returns_over_hodl\": 2.7076189463848266e-05, \"validation_sharpe\": -2.2437072192510237}, {\"centeredness_margin\": 0.33402083019406614, \"continuous_test_metrics\": [{\"annualised_returns\": -0.5374120711366173, \"annualised_returns_over_hodl\": -0.10263096254191695, \"annualised_returns_over_uniform_hodl\": 0.6327303673664717, \"calmar\": -0.9293040215413498, \"daily_log_sharpe\": -0.8215525759732561, \"daily_returns\": 0.031016041713378163, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": -0.13963456706754795, \"return\": -0.2669036125471246, \"returns_over_hodl\": -0.042674393845684055, \"returns_over_uniform_hodl\": 0.21828447631927794, \"sharpe\": -0.3439730424561141, \"sterling\": -1.38867023198596, \"ulcer\": -0.17496546507717634}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -3.0936720071697983, \"optuna_trial_number\": 40, \"price_ratio\": 12.348000337188124, \"shift_exponent\": 3.62698693921719e-05, \"step\": 40, \"test_objective\": [{\"annualised_returns\": -0.5374120711366173, \"annualised_returns_over_hodl\": -0.10263096254191695, \"annualised_returns_over_uniform_hodl\": 0.6327303673664717, \"calmar\": -0.9293040215413498, \"daily_log_sharpe\": -0.8215525759732561, \"daily_returns\": 0.031016041713378163, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": -0.13963456706754795, \"return\": -0.2669036125471246, \"returns_over_hodl\": -0.042674393845684055, \"returns_over_uniform_hodl\": 0.21828447631927794, \"sharpe\": -0.3439730424561141, \"sterling\": -1.38867023198596, \"ulcer\": -0.17496546507717634}], \"train_objective\": [{\"annualised_returns\": -0.4139661700256402, \"annualised_returns_over_hodl\": -0.261985226062851, \"annualised_returns_over_uniform_hodl\": -0.261985226062851, \"calmar\": -0.5591240238576991, \"daily_log_sharpe\": -0.41170370759194336, \"daily_returns\": 0.008135745474768101, \"fee_revenue_over_value\": 0.018040175825350783, \"jax_sharpe\": 0.11286970632781505, \"return\": -0.27706225444104593, \"returns_over_hodl\": -0.16842873479178233, \"returns_over_uniform_hodl\": -0.16842873479178233, \"sharpe\": 0.1348170377743075, \"sterling\": -1.2808504789066715, \"ulcer\": -0.1744245884681085}], \"train_return\": -0.27706225444104593, \"train_returns_over_hodl\": -0.16842873479178233, \"train_sharpe\": 0.11286970632781507, \"validation_return\": -0.3120722013226941, \"validation_returns_over_hodl\": -0.06724058003492384, \"validation_sharpe\": -2.080577294625204}, {\"centeredness_margin\": 0.2942618014365657, \"continuous_test_metrics\": [{\"annualised_returns\": -0.5487397747650892, \"annualised_returns_over_hodl\": -0.1246054420494801, \"annualised_returns_over_uniform_hodl\": 0.5927485940586872, \"calmar\": -0.948350293796565, \"daily_log_sharpe\": -0.8521164721539175, \"daily_returns\": 0.031016041576353015, \"fee_revenue_over_value\": 0.0010244562051998133, \"jax_sharpe\": -0.17177336017996325, \"return\": -0.2741870634005442, \"returns_over_hodl\": -0.05218560282085838, \"returns_over_uniform_hodl\": 0.20618059030834623, \"sharpe\": -0.3763801868765848, \"sterling\": -1.416523946798903, \"ulcer\": -0.17541053487301014}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -3.9508362181572245, \"optuna_trial_number\": 41, \"price_ratio\": 4.701396708698761, \"shift_exponent\": 0.0017795476623403837, \"step\": 41, \"test_objective\": [{\"annualised_returns\": -0.5487397747650892, \"annualised_returns_over_hodl\": -0.1246054420494801, \"annualised_returns_over_uniform_hodl\": 0.5927485940586872, \"calmar\": -0.948350293796565, \"daily_log_sharpe\": -0.8521164721539175, \"daily_returns\": 0.031016041576353015, \"fee_revenue_over_value\": 0.0010244562051998133, \"jax_sharpe\": -0.17177336017996325, \"return\": -0.2741870634005442, \"returns_over_hodl\": -0.05218560282085838, \"returns_over_uniform_hodl\": 0.20618059030834623, \"sharpe\": -0.3763801868765848, \"sterling\": -1.416523946798903, \"ulcer\": -0.17541053487301014}], \"train_objective\": [{\"annualised_returns\": -0.4562664747383349, \"annualised_returns_over_hodl\": -0.3152556146023281, \"annualised_returns_over_uniform_hodl\": -0.3152556146023283, \"calmar\": -0.6090147821714051, \"daily_log_sharpe\": -0.44409986809854335, \"daily_returns\": 0.008211377444540252, \"fee_revenue_over_value\": 0.023205150036711503, \"jax_sharpe\": 0.13266669737574444, \"return\": -0.3092080795137736, \"returns_over_hodl\": -0.20540500915441395, \"returns_over_uniform_hodl\": -0.20540500915441406, \"sharpe\": 0.1402652301650866, \"sterling\": -1.3624620579042537, \"ulcer\": -0.18081664459557506}], \"train_return\": -0.3092080795137736, \"train_returns_over_hodl\": -0.20540500915441395, \"train_sharpe\": 0.13266669737574444, \"validation_return\": -0.33511714872984866, \"validation_returns_over_hodl\": -0.04719452861931317, \"validation_sharpe\": -2.2089216139443772}, {\"centeredness_margin\": 0.47157441222781143, \"continuous_test_metrics\": [{\"annualised_returns\": -0.513389977430641, \"annualised_returns_over_hodl\": -0.05603077576423854, \"annualised_returns_over_uniform_hodl\": 0.7175177114241544, \"calmar\": -0.8858186138442626, \"daily_log_sharpe\": -0.7635041619256789, \"daily_returns\": 0.031016041639692245, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": -0.08065958624721335, \"return\": -0.2518029753036114, \"returns_over_hodl\": -0.022954985232010117, \"returns_over_uniform_hodl\": 0.24337922818433677, \"sharpe\": -0.2810205177542021, \"sterling\": -1.3222490944110592, \"ulcer\": -0.17626491130556754}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -3.539427780980658, \"optuna_trial_number\": 42, \"price_ratio\": 9.852055974887367, \"shift_exponent\": 2.1620432045574877e-05, \"step\": 42, \"test_objective\": [{\"annualised_returns\": -0.513389977430641, \"annualised_returns_over_hodl\": -0.05603077576423854, \"annualised_returns_over_uniform_hodl\": 0.7175177114241544, \"calmar\": -0.8858186138442626, \"daily_log_sharpe\": -0.7635041619256789, \"daily_returns\": 0.031016041639692245, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": -0.08065958624721335, \"return\": -0.2518029753036114, \"returns_over_hodl\": -0.022954985232010117, \"returns_over_uniform_hodl\": 0.24337922818433677, \"sharpe\": -0.2810205177542021, \"sterling\": -1.3222490944110592, \"ulcer\": -0.17626491130556754}], \"train_objective\": [{\"annualised_returns\": -0.43549363180239353, \"annualised_returns_over_hodl\": -0.2890955804894998, \"annualised_returns_over_uniform_hodl\": -0.2890955804894998, \"calmar\": -0.5867112105728448, \"daily_log_sharpe\": -0.4351688827971953, \"daily_returns\": 0.00814276237582544, \"fee_revenue_over_value\": 0.01518532756710765, \"jax_sharpe\": 0.10103941236640905, \"return\": -0.29330365436485695, \"returns_over_hodl\": -0.18711067741597598, \"returns_over_uniform_hodl\": -0.18711067741597598, \"sharpe\": 0.12078340250775205, \"sterling\": -1.3340765511754324, \"ulcer\": -0.17635148007653975}], \"train_return\": -0.29330365436485695, \"train_returns_over_hodl\": -0.18711067741597598, \"train_sharpe\": 0.10103941236640905, \"validation_return\": -0.32661181263774397, \"validation_returns_over_hodl\": -0.06154156078338757, \"validation_sharpe\": -2.1419211097740587}, {\"centeredness_margin\": 0.32092659573392757, \"continuous_test_metrics\": [{\"annualised_returns\": -0.47763917908344955, \"annualised_returns_over_hodl\": 0.013321790424618563, \"annualised_returns_over_uniform_hodl\": 0.8437021846387447, \"calmar\": -0.8239584532164795, \"daily_log_sharpe\": -0.6800299820924164, \"daily_returns\": 0.031016041675561323, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": 0.006688796222076179, \"return\": -0.23013231329194916, \"returns_over_hodl\": 0.005343994638795868, \"returns_over_uniform_hodl\": 0.27939227036027714, \"sharpe\": -0.18933555250510015, \"sterling\": -1.229704183062386, \"ulcer\": -0.1764103142896158}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -0.09097057431438293, \"optuna_trial_number\": 43, \"price_ratio\": 4.621780473054278, \"shift_exponent\": 0.00037027080346239434, \"step\": 43, \"test_objective\": [{\"annualised_returns\": -0.47763917908344955, \"annualised_returns_over_hodl\": 0.013321790424618563, \"annualised_returns_over_uniform_hodl\": 0.8437021846387447, \"calmar\": -0.8239584532164795, \"daily_log_sharpe\": -0.6800299820924164, \"daily_returns\": 0.031016041675561323, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": 0.006688796222076179, \"return\": -0.23013231329194916, \"returns_over_hodl\": 0.005343994638795868, \"returns_over_uniform_hodl\": 0.27939227036027714, \"sharpe\": -0.18933555250510015, \"sterling\": -1.229704183062386, \"ulcer\": -0.1764103142896158}], \"train_objective\": [{\"annualised_returns\": -0.4998527455494418, \"annualised_returns_over_hodl\": -0.3701454693413109, \"annualised_returns_over_uniform_hodl\": -0.3701454693413109, \"calmar\": -0.6660096186410964, \"daily_log_sharpe\": -0.4925092446776767, \"daily_returns\": 0.008281832450779613, \"fee_revenue_over_value\": 0.019729893532360255, \"jax_sharpe\": 0.16726711077956918, \"return\": -0.3433772983757152, \"returns_over_hodl\": -0.24470872615459938, \"returns_over_uniform_hodl\": -0.24470872615459938, \"sharpe\": 0.1108147874761446, \"sterling\": -1.4608279703298122, \"ulcer\": -0.18553496942320274}], \"train_return\": -0.3433772983757152, \"train_returns_over_hodl\": -0.24470872615459938, \"train_sharpe\": 0.16726711077956916, \"validation_return\": -0.33914271213368496, \"validation_returns_over_hodl\": -2.6730205826552833e-09, \"validation_sharpe\": -2.243853177911891}, {\"centeredness_margin\": 0.3180277866917001, \"continuous_test_metrics\": [{\"annualised_returns\": -0.5605793490433374, \"annualised_returns_over_hodl\": -0.14757289888213865, \"annualised_returns_over_uniform_hodl\": 0.5509601442210867, \"calmar\": -0.9694103951438311, \"daily_log_sharpe\": -0.8818594189763549, \"daily_returns\": 0.031016041658882353, \"fee_revenue_over_value\": 0.00025767286489259544, \"jax_sharpe\": -0.20287020273463532, \"return\": -0.28191731463955416, \"returns_over_hodl\": -0.062280275505276816, \"returns_over_uniform_hodl\": 0.1933341962410462, \"sharpe\": -0.4091431741547625, \"sterling\": -1.4520104960253937, \"ulcer\": -0.174187182238003}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -3.396166191784746, \"optuna_trial_number\": 44, \"price_ratio\": 6.705418911599108, \"shift_exponent\": 0.001358468564645277, \"step\": 44, \"test_objective\": [{\"annualised_returns\": -0.5605793490433374, \"annualised_returns_over_hodl\": -0.14757289888213865, \"annualised_returns_over_uniform_hodl\": 0.5509601442210867, \"calmar\": -0.9694103951438311, \"daily_log_sharpe\": -0.8818594189763549, \"daily_returns\": 0.031016041658882353, \"fee_revenue_over_value\": 0.00025767286489259544, \"jax_sharpe\": -0.20287020273463532, \"return\": -0.28191731463955416, \"returns_over_hodl\": -0.062280275505276816, \"returns_over_uniform_hodl\": 0.1933341962410462, \"sharpe\": -0.4091431741547625, \"sterling\": -1.4520104960253937, \"ulcer\": -0.174187182238003}], \"train_objective\": [{\"annualised_returns\": -0.4313753956508868, \"annualised_returns_over_hodl\": -0.283909328490179, \"annualised_returns_over_uniform_hodl\": -0.28390932849017914, \"calmar\": -0.5791872297176636, \"daily_log_sharpe\": -0.4245431328929734, \"daily_returns\": 0.008202818062856897, \"fee_revenue_over_value\": 0.020825936596895307, \"jax_sharpe\": 0.12459311814703199, \"return\": -0.2901780784357837, \"returns_over_hodl\": -0.1835154312888917, \"returns_over_uniform_hodl\": -0.1835154312888918, \"sharpe\": 0.13901494192460934, \"sterling\": -1.3134000391938778, \"ulcer\": -0.17719840334001016}], \"train_return\": -0.2901780784357837, \"train_returns_over_hodl\": -0.1835154312888917, \"train_sharpe\": 0.12459311814703199, \"validation_return\": -0.3226511821791591, \"validation_returns_over_hodl\": -0.0657077204078177, \"validation_sharpe\": -2.129628075775312}, {\"centeredness_margin\": 0.4815556566997596, \"continuous_test_metrics\": [{\"annualised_returns\": -0.509266818117458, \"annualised_returns_over_hodl\": -0.04803230747751741, \"annualised_returns_over_uniform_hodl\": 0.7320706363927432, \"calmar\": -0.8785182754116863, \"daily_log_sharpe\": -0.754981935162513, \"daily_returns\": 0.031016041731379628, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": -0.07432968380627326, \"return\": -0.24925619146163247, \"returns_over_hodl\": -0.0196292275098503, \"returns_over_uniform_hodl\": 0.2476115600745592, \"sharpe\": -0.27215550287919754, \"sterling\": -1.3111311534905195, \"ulcer\": -0.17641031440499952}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -4.04822277577512, \"optuna_trial_number\": 45, \"price_ratio\": 5.642396964476567, \"shift_exponent\": 0.0007928307132809494, \"step\": 45, \"test_objective\": [{\"annualised_returns\": -0.509266818117458, \"annualised_returns_over_hodl\": -0.04803230747751741, \"annualised_returns_over_uniform_hodl\": 0.7320706363927432, \"calmar\": -0.8785182754116863, \"daily_log_sharpe\": -0.754981935162513, \"daily_returns\": 0.031016041731379628, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": -0.07432968380627326, \"return\": -0.24925619146163247, \"returns_over_hodl\": -0.0196292275098503, \"returns_over_uniform_hodl\": 0.2476115600745592, \"sharpe\": -0.27215550287919754, \"sterling\": -1.3111311534905195, \"ulcer\": -0.17641031440499952}], \"train_objective\": [{\"annualised_returns\": -0.45963024244790884, \"annualised_returns_over_hodl\": -0.3194917356908783, \"annualised_returns_over_uniform_hodl\": -0.3194917356908783, \"calmar\": -0.6152376440539549, \"daily_log_sharpe\": -0.45249376457291635, \"daily_returns\": 0.008182080404496606, \"fee_revenue_over_value\": 0.017687329713224207, \"jax_sharpe\": 0.11502929154227119, \"return\": -0.31180579536732744, \"returns_over_hodl\": -0.20839307537762797, \"returns_over_uniform_hodl\": -0.20839307537762797, \"sharpe\": 0.1259853235566296, \"sterling\": -1.3788035201318511, \"ulcer\": -0.180108150897034}], \"train_return\": -0.31180579536732744, \"train_returns_over_hodl\": -0.20839307537762797, \"train_sharpe\": 0.11502929154227119, \"validation_return\": -0.33675395623562265, \"validation_returns_over_hodl\": -0.037278504962201864, \"validation_sharpe\": -2.222703524582466}, {\"centeredness_margin\": 0.37422539924484133, \"continuous_test_metrics\": [{\"annualised_returns\": -0.616764506802685, \"annualised_returns_over_hodl\": 0.0870769646488565, \"annualised_returns_over_uniform_hodl\": 0.3526514389023725, \"calmar\": -1.1142112359613867, \"daily_log_sharpe\": -1.218868030747041, \"daily_returns\": 0.022395381096004415, \"fee_revenue_over_value\": 0.08846406041297114, \"jax_sharpe\": -0.6399023523323419, \"return\": -0.3204117014222051, \"returns_over_hodl\": 0.03419728068724437, \"returns_over_uniform_hodl\": 0.12936291682214685, \"sharpe\": -0.8225533428105847, \"sterling\": -1.8073009456357576, \"ulcer\": -0.14955970948347674}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -3.123219939809399, \"optuna_trial_number\": 46, \"price_ratio\": 7.425420148379546, \"shift_exponent\": 0.009285698620620387, \"step\": 46, \"test_objective\": [{\"annualised_returns\": -0.616764506802685, \"annualised_returns_over_hodl\": 0.0870769646488565, \"annualised_returns_over_uniform_hodl\": 0.3526514389023725, \"calmar\": -1.1142112359613867, \"daily_log_sharpe\": -1.218868030747041, \"daily_returns\": 0.022395381096004415, \"fee_revenue_over_value\": 0.08846406041297114, \"jax_sharpe\": -0.6399023523323419, \"return\": -0.3204117014222051, \"returns_over_hodl\": 0.03419728068724437, \"returns_over_uniform_hodl\": 0.12936291682214685, \"sharpe\": -0.8225533428105847, \"sterling\": -1.8073009456357576, \"ulcer\": -0.14955970948347674}], \"train_objective\": [{\"annualised_returns\": -0.38249360648086084, \"annualised_returns_over_hodl\": -0.22235062532531513, \"annualised_returns_over_uniform_hodl\": -0.22235062532531513, \"calmar\": -0.515348322737585, \"daily_log_sharpe\": -0.3903442174085726, \"daily_returns\": 0.008179031137701537, \"fee_revenue_over_value\": 0.026161163816943996, \"jax_sharpe\": 0.11242261567794673, \"return\": -0.2537334900208045, \"returns_over_hodl\": -0.1415944323032805, \"returns_over_uniform_hodl\": -0.1415944323032805, \"sharpe\": 0.13755398085775541, \"sterling\": -1.2034004603061086, \"ulcer\": -0.17126600870185185}], \"train_return\": -0.2537334900208045, \"train_returns_over_hodl\": -0.1415944323032805, \"train_sharpe\": 0.11242261567794673, \"validation_return\": -0.21542535597466206, \"validation_returns_over_hodl\": -0.03520427469780818, \"validation_sharpe\": -1.6850686842941993}, {\"centeredness_margin\": 0.28470552459555903, \"continuous_test_metrics\": [{\"annualised_returns\": -0.48047543880699684, \"annualised_returns_over_hodl\": 0.00781976381946059, \"annualised_returns_over_uniform_hodl\": 0.8336914448605741, \"calmar\": -0.8288511928413158, \"daily_log_sharpe\": -0.6844059516911835, \"daily_returns\": 0.0310160416454811, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": 0.00791933627905974, \"return\": -0.23181855403880602, \"returns_over_hodl\": 0.0031419907680223513, \"returns_over_uniform_hodl\": 0.2765900182139114, \"sharpe\": -0.19368998647457225, \"sterling\": -1.2370062780362376, \"ulcer\": -0.17641031422742692}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -0.10265298594495265, \"optuna_trial_number\": 47, \"price_ratio\": 3.842389166648869, \"shift_exponent\": 0.0004816110552157442, \"step\": 47, \"test_objective\": [{\"annualised_returns\": -0.48047543880699684, \"annualised_returns_over_hodl\": 0.00781976381946059, \"annualised_returns_over_uniform_hodl\": 0.8336914448605741, \"calmar\": -0.8288511928413158, \"daily_log_sharpe\": -0.6844059516911835, \"daily_returns\": 0.0310160416454811, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": 0.00791933627905974, \"return\": -0.23181855403880602, \"returns_over_hodl\": 0.0031419907680223513, \"returns_over_uniform_hodl\": 0.2765900182139114, \"sharpe\": -0.19368998647457225, \"sterling\": -1.2370062780362376, \"ulcer\": -0.17641031422742692}], \"train_objective\": [{\"annualised_returns\": -0.5213656482763147, \"annualised_returns_over_hodl\": -0.39723748900065325, \"annualised_returns_over_uniform_hodl\": -0.39723748900065325, \"calmar\": -0.6910870295359941, \"daily_log_sharpe\": -0.5162726959949531, \"daily_returns\": 0.0083106180798691, \"fee_revenue_over_value\": 0.02133195895536005, \"jax_sharpe\": 0.1631494020143251, \"return\": -0.3606723268200559, \"returns_over_hodl\": -0.26460262265346435, \"returns_over_uniform_hodl\": -0.26460262265346435, \"sharpe\": 0.10312948481159098, \"sterling\": -1.4976934539569084, \"ulcer\": -0.18932597243515287}], \"train_return\": -0.3606723268200559, \"train_returns_over_hodl\": -0.26460262265346435, \"train_sharpe\": 0.1631494020143251, \"validation_return\": -0.3391427120305882, \"validation_returns_over_hodl\": -3.0193166855596587e-09, \"validation_sharpe\": -2.2438531789797493}, {\"centeredness_margin\": 0.4179982173697212, \"continuous_test_metrics\": [{\"annualised_returns\": -0.5519734391481768, \"annualised_returns_over_hodl\": -0.13087839091880127, \"annualised_returns_over_uniform_hodl\": 0.5813351919642762, \"calmar\": -0.9539328674946498, \"daily_log_sharpe\": -0.8587817540278397, \"daily_returns\": 0.031016041585842122, \"fee_revenue_over_value\": 0.000143797919875779, \"jax_sharpe\": -0.17981440332018592, \"return\": -0.2762862301533783, \"returns_over_hodl\": -0.05492683331169512, \"returns_over_uniform_hodl\": 0.20269212370019818, \"sharpe\": -0.38344653068148515, \"sterling\": -1.4251800470076201, \"ulcer\": -0.1753309553538549}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -3.899309909893332, \"optuna_trial_number\": 48, \"price_ratio\": 4.514612510909671, \"shift_exponent\": 0.001682245869819342, \"step\": 48, \"test_objective\": [{\"annualised_returns\": -0.5519734391481768, \"annualised_returns_over_hodl\": -0.13087839091880127, \"annualised_returns_over_uniform_hodl\": 0.5813351919642762, \"calmar\": -0.9539328674946498, \"daily_log_sharpe\": -0.8587817540278397, \"daily_returns\": 0.031016041585842122, \"fee_revenue_over_value\": 0.000143797919875779, \"jax_sharpe\": -0.17981440332018592, \"return\": -0.2762862301533783, \"returns_over_hodl\": -0.05492683331169512, \"returns_over_uniform_hodl\": 0.20269212370019818, \"sharpe\": -0.38344653068148515, \"sterling\": -1.4251800470076201, \"ulcer\": -0.1753309553538549}], \"train_objective\": [{\"annualised_returns\": -0.45564976887602215, \"annualised_returns_over_hodl\": -0.3144789733671619, \"annualised_returns_over_uniform_hodl\": -0.314478973367162, \"calmar\": -0.6077276369574118, \"daily_log_sharpe\": -0.44445973733412486, \"daily_returns\": 0.008275838957959298, \"fee_revenue_over_value\": 0.020093648855226194, \"jax_sharpe\": 0.19055292733333157, \"return\": -0.3087325056010817, \"returns_over_hodl\": -0.20485797228615432, \"returns_over_uniform_hodl\": -0.20485797228615443, \"sharpe\": 0.13969573356949716, \"sterling\": -1.3623185870211159, \"ulcer\": -0.18038033732212672}], \"train_return\": -0.3087325056010817, \"train_returns_over_hodl\": -0.20485797228615432, \"train_sharpe\": 0.1905529273333316, \"validation_return\": -0.3339493966894995, \"validation_returns_over_hodl\": -0.05256486216890466, \"validation_sharpe\": -2.1991817216511484}, {\"centeredness_margin\": 0.4029183117425496, \"continuous_test_metrics\": [{\"annualised_returns\": -0.48379966159682997, \"annualised_returns_over_hodl\": 0.0013711391685855556, \"annualised_returns_over_uniform_hodl\": 0.8219584117263379, \"calmar\": -0.8345857021480982, \"daily_log_sharpe\": -0.6956336425589477, \"daily_returns\": 0.031016041714412714, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": -0.012625141842565956, \"return\": -0.2338019168654759, \"returns_over_hodl\": 0.0005519836589229499, \"returns_over_uniform_hodl\": 0.2732939985035485, \"sharpe\": -0.2071990502493021, \"sterling\": -1.2455646572997812, \"ulcer\": -0.17641031436993232}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -4.2851391798648155, \"optuna_trial_number\": 49, \"price_ratio\": 6.131515138265132, \"shift_exponent\": 0.00018657818531221305, \"step\": 49, \"test_objective\": [{\"annualised_returns\": -0.48379966159682997, \"annualised_returns_over_hodl\": 0.0013711391685855556, \"annualised_returns_over_uniform_hodl\": 0.8219584117263379, \"calmar\": -0.8345857021480982, \"daily_log_sharpe\": -0.6956336425589477, \"daily_returns\": 0.031016041714412714, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": -0.012625141842565956, \"return\": -0.2338019168654759, \"returns_over_hodl\": 0.0005519836589229499, \"returns_over_uniform_hodl\": 0.2732939985035485, \"sharpe\": -0.2071990502493021, \"sterling\": -1.2455646572997812, \"ulcer\": -0.17641031436993232}], \"train_objective\": [{\"annualised_returns\": -0.47213680093541344, \"annualised_returns_over_hodl\": -0.3352417222322547, \"annualised_returns_over_uniform_hodl\": -0.3352417222322548, \"calmar\": -0.6323826182468236, \"daily_log_sharpe\": -0.4663955917056467, \"daily_returns\": 0.008167243313743515, \"fee_revenue_over_value\": 0.017548384840008754, \"jax_sharpe\": 0.15171976542411994, \"return\": -0.32152039213947803, \"returns_over_hodl\": -0.21956745322472593, \"returns_over_uniform_hodl\": -0.21956745322472604, \"sharpe\": 0.11537007958107773, \"sterling\": -1.409903561958995, \"ulcer\": -0.18123295599300043}], \"train_return\": -0.32152039213947803, \"train_returns_over_hodl\": -0.21956745322472593, \"train_sharpe\": 0.15171976542411994, \"validation_return\": -0.3390019475996938, \"validation_returns_over_hodl\": -0.009808300028074424, \"validation_sharpe\": -2.2426410300112596}, {\"centeredness_margin\": 0.3108523272398995, \"continuous_test_metrics\": [{\"annualised_returns\": -0.4814066665122292, \"annualised_returns_over_hodl\": 0.006013285005765923, \"annualised_returns_over_uniform_hodl\": 0.8304046237863585, \"calmar\": -0.8304576236454543, \"daily_log_sharpe\": -0.6862153970127354, \"daily_returns\": 0.031016041665478666, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": 0.007126457920875319, \"return\": -0.23237339519376066, \"returns_over_hodl\": 0.002417442268368042, \"returns_over_uniform_hodl\": 0.27566796433740426, \"sharpe\": -0.19554196340501995, \"sterling\": -1.2394037707035717, \"ulcer\": -0.1764103142687656}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -0.09715170274772011, \"optuna_trial_number\": 50, \"price_ratio\": 4.622398519796724, \"shift_exponent\": 9.389076745531383e-05, \"step\": 50, \"test_objective\": [{\"annualised_returns\": -0.4814066665122292, \"annualised_returns_over_hodl\": 0.006013285005765923, \"annualised_returns_over_uniform_hodl\": 0.8304046237863585, \"calmar\": -0.8304576236454543, \"daily_log_sharpe\": -0.6862153970127354, \"daily_returns\": 0.031016041665478666, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": 0.007126457920875319, \"return\": -0.23237339519376066, \"returns_over_hodl\": 0.002417442268368042, \"returns_over_uniform_hodl\": 0.27566796433740426, \"sharpe\": -0.19554196340501995, \"sterling\": -1.2394037707035717, \"ulcer\": -0.1764103142687656}], \"train_objective\": [{\"annualised_returns\": -0.5075588089659674, \"annualised_returns_over_hodl\": -0.37985000918083067, \"annualised_returns_over_uniform_hodl\": -0.37985000918083045, \"calmar\": -0.6760655231338658, \"daily_log_sharpe\": -0.5024784615032355, \"daily_returns\": 0.008272523052682569, \"fee_revenue_over_value\": 0.019736168392584224, \"jax_sharpe\": 0.10667009473079507, \"return\": -0.34953825357489154, \"returns_over_hodl\": -0.25179546818922527, \"returns_over_uniform_hodl\": -0.25179546818922516, \"sharpe\": 0.10462887386547123, \"sterling\": -1.4764490046509413, \"ulcer\": -0.1867056133928932}], \"train_return\": -0.34953825357489154, \"train_returns_over_hodl\": -0.25179546818922527, \"train_sharpe\": 0.10667009473079506, \"validation_return\": -0.33914271214325176, \"validation_returns_over_hodl\": -2.8558548859081156e-09, \"validation_sharpe\": -2.243853178713449}, {\"centeredness_margin\": 0.22200713085406998, \"continuous_test_metrics\": [{\"annualised_returns\": -0.4812418310986647, \"annualised_returns_over_hodl\": 0.006333045572158191, \"annualised_returns_over_uniform_hodl\": 0.8309864197402768, \"calmar\": -0.8301732715218783, \"daily_log_sharpe\": -0.6891666046196262, \"daily_returns\": 0.03101604170852712, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": -0.005186169999984833, \"return\": -0.2322751401595694, \"returns_over_hodl\": 0.00254574946140651, \"returns_over_uniform_hodl\": 0.27583124789045965, \"sharpe\": -0.1997805609745401, \"sterling\": -1.238979392864857, \"ulcer\": -0.17641031435776533}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -0.09515308853485416, \"optuna_trial_number\": 51, \"price_ratio\": 5.698354535872558, \"shift_exponent\": 0.00022065983178175903, \"step\": 51, \"test_objective\": [{\"annualised_returns\": -0.4812418310986647, \"annualised_returns_over_hodl\": 0.006333045572158191, \"annualised_returns_over_uniform_hodl\": 0.8309864197402768, \"calmar\": -0.8301732715218783, \"daily_log_sharpe\": -0.6891666046196262, \"daily_returns\": 0.03101604170852712, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": -0.005186169999984833, \"return\": -0.2322751401595694, \"returns_over_hodl\": 0.00254574946140651, \"returns_over_uniform_hodl\": 0.27583124789045965, \"sharpe\": -0.1997805609745401, \"sterling\": -1.238979392864857, \"ulcer\": -0.17641031435776533}], \"train_objective\": [{\"annualised_returns\": -0.4715295359328431, \"annualised_returns_over_hodl\": -0.33447697023216716, \"annualised_returns_over_uniform_hodl\": -0.3344769702321674, \"calmar\": -0.6314192410797034, \"daily_log_sharpe\": -0.4594982294057498, \"daily_returns\": 0.008225682920128045, \"fee_revenue_over_value\": 0.0227848394149314, \"jax_sharpe\": 0.16701243999685075, \"return\": -0.3210466182816557, \"returns_over_hodl\": -0.21902248689976633, \"returns_over_uniform_hodl\": -0.21902248689976644, \"sharpe\": 0.12760574439387937, \"sterling\": -1.400699334462425, \"ulcer\": -0.18221366062701877}], \"train_return\": -0.3210466182816557, \"train_returns_over_hodl\": -0.21902248689976633, \"train_sharpe\": 0.16701243999685075, \"validation_return\": -0.3391427122719245, \"validation_returns_over_hodl\": -2.8037330235264335e-09, \"validation_sharpe\": -2.2438531770172183}, {\"centeredness_margin\": 0.16868744215789822, \"continuous_test_metrics\": [{\"annualised_returns\": -0.667938855006994, \"annualised_returns_over_hodl\": -0.06849768601280626, \"annualised_returns_over_uniform_hodl\": 0.17202867049451487, \"calmar\": -1.1245863370878968, \"daily_log_sharpe\": -1.3269568561760876, \"daily_returns\": 0.02259063533821623, \"fee_revenue_over_value\": 0.00992089625032429, \"jax_sharpe\": -0.7363421107399516, \"return\": -0.35852992063508926, \"returns_over_hodl\": -0.028172450597734655, \"returns_over_uniform_hodl\": 0.06601676544723323, \"sharpe\": -0.9162907730701293, \"sterling\": -1.91545323010432, \"ulcer\": -0.15644292859423486}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -2.7951232411886866, \"optuna_trial_number\": 52, \"price_ratio\": 120.36638669047649, \"shift_exponent\": 1.9291308386921233e-05, \"step\": 52, \"test_objective\": [{\"annualised_returns\": -0.667938855006994, \"annualised_returns_over_hodl\": -0.06849768601280626, \"annualised_returns_over_uniform_hodl\": 0.17202867049451487, \"calmar\": -1.1245863370878968, \"daily_log_sharpe\": -1.3269568561760876, \"daily_returns\": 0.02259063533821623, \"fee_revenue_over_value\": 0.00992089625032429, \"jax_sharpe\": -0.7363421107399516, \"return\": -0.35852992063508926, \"returns_over_hodl\": -0.028172450597734655, \"returns_over_uniform_hodl\": 0.06601676544723323, \"sharpe\": -0.9162907730701293, \"sterling\": -1.91545323010432, \"ulcer\": -0.15644292859423486}], \"train_objective\": [{\"annualised_returns\": -0.32173854710060956, \"annualised_returns_over_hodl\": -0.14583945972244172, \"annualised_returns_over_uniform_hodl\": -0.14583945972244194, \"calmar\": -0.44114289881522323, \"daily_log_sharpe\": -0.31340413842619647, \"daily_returns\": 0.008022249320125435, \"fee_revenue_over_value\": 0.027073469272442856, \"jax_sharpe\": 0.16328259320952093, \"return\": -0.20998089200885894, \"returns_over_hodl\": -0.09126727272633839, \"returns_over_uniform_hodl\": -0.0912672727263385, \"sharpe\": 0.19457411820494333, \"sterling\": -1.0447975391038784, \"ulcer\": -0.16583401329189687}], \"train_return\": -0.20998089200885894, \"train_returns_over_hodl\": -0.09126727272633839, \"train_sharpe\": 0.1632825932095209, \"validation_return\": -0.20956993651139066, \"validation_returns_over_hodl\": -0.03796508945202437, \"validation_sharpe\": -1.597383422925916}, {\"centeredness_margin\": 0.010078152993953315, \"continuous_test_metrics\": [{\"annualised_returns\": -0.5817226070869559, \"annualised_returns_over_hodl\": 0.15591203446475022, \"annualised_returns_over_uniform_hodl\": 0.47633381413568454, \"calmar\": -1.0420997543138675, \"daily_log_sharpe\": -1.0689525020328818, \"daily_returns\": 0.023138200494098055, \"fee_revenue_over_value\": 0.0691080740716206, \"jax_sharpe\": -0.47637885688507775, \"return\": -0.29603771335890816, \"returns_over_hodl\": 0.060088665561917054, \"returns_over_uniform_hodl\": 0.16986843804927854, \"sharpe\": -0.6562159359360442, \"sterling\": -1.6698608805379538, \"ulcer\": -0.15470635786306985}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -2.5369317782061636, \"optuna_trial_number\": 53, \"price_ratio\": 99.5827962948424, \"shift_exponent\": 37.834647910578454, \"step\": 53, \"test_objective\": [{\"annualised_returns\": -0.5817226070869559, \"annualised_returns_over_hodl\": 0.15591203446475022, \"annualised_returns_over_uniform_hodl\": 0.47633381413568454, \"calmar\": -1.0420997543138675, \"daily_log_sharpe\": -1.0689525020328818, \"daily_returns\": 0.023138200494098055, \"fee_revenue_over_value\": 0.0691080740716206, \"jax_sharpe\": -0.47637885688507775, \"return\": -0.29603771335890816, \"returns_over_hodl\": 0.060088665561917054, \"returns_over_uniform_hodl\": 0.16986843804927854, \"sharpe\": -0.6562159359360442, \"sterling\": -1.6698608805379538, \"ulcer\": -0.15470635786306985}], \"train_objective\": [{\"annualised_returns\": -0.3240760698376526, \"annualised_returns_over_hodl\": -0.1487831913401667, \"annualised_returns_over_uniform_hodl\": -0.1487831913401665, \"calmar\": -0.4442529348864526, \"daily_log_sharpe\": -0.3152463645578518, \"daily_returns\": 0.008016917734347227, \"fee_revenue_over_value\": 0.027598172852859745, \"jax_sharpe\": 0.16302263755234328, \"return\": -0.2116350089980058, \"returns_over_hodl\": -0.09316994853199012, \"returns_over_uniform_hodl\": -0.09316994853199001, \"sharpe\": 0.19423636211709147, \"sterling\": -1.0505129202555161, \"ulcer\": -0.16611128254071458}], \"train_return\": -0.2116350089980058, \"train_returns_over_hodl\": -0.09316994853199012, \"train_sharpe\": 0.16302263755234328, \"validation_return\": -0.21093416008879118, \"validation_returns_over_hodl\": -0.03456526593188136, \"validation_sharpe\": -1.594272218858379}, {\"centeredness_margin\": 0.08837041984945501, \"continuous_test_metrics\": [{\"annualised_returns\": -0.5782119988824657, \"annualised_returns_over_hodl\": -0.1451875236497271, \"annualised_returns_over_uniform_hodl\": 0.4887247051766175, \"calmar\": -0.9949341618599397, \"daily_log_sharpe\": -0.9379829350120834, \"daily_returns\": 0.02994638421149258, \"fee_revenue_over_value\": 0.003041850900029861, \"jax_sharpe\": -0.26363047904560327, \"return\": -0.293664128601985, \"returns_over_hodl\": -0.061224352167472684, \"returns_over_uniform_hodl\": 0.17381294181724094, \"sharpe\": -0.4735353129159722, \"sterling\": -1.5160896730056308, \"ulcer\": -0.17055917712518098}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -2.909786891475764, \"optuna_trial_number\": 54, \"price_ratio\": 16.317834553302145, \"shift_exponent\": 0.00020307500031310042, \"step\": 54, \"test_objective\": [{\"annualised_returns\": -0.5782119988824657, \"annualised_returns_over_hodl\": -0.1451875236497271, \"annualised_returns_over_uniform_hodl\": 0.4887247051766175, \"calmar\": -0.9949341618599397, \"daily_log_sharpe\": -0.9379829350120834, \"daily_returns\": 0.02994638421149258, \"fee_revenue_over_value\": 0.003041850900029861, \"jax_sharpe\": -0.26363047904560327, \"return\": -0.293664128601985, \"returns_over_hodl\": -0.061224352167472684, \"returns_over_uniform_hodl\": 0.17381294181724094, \"sharpe\": -0.4735353129159722, \"sterling\": -1.5160896730056308, \"ulcer\": -0.17055917712518098}], \"train_objective\": [{\"annualised_returns\": -0.36290384381183083, \"annualised_returns_over_hodl\": -0.19768048935671567, \"annualised_returns_over_uniform_hodl\": -0.19768048935671556, \"calmar\": -0.49530555933665904, \"daily_log_sharpe\": -0.34451256671950026, \"daily_returns\": 0.008126523272606193, \"fee_revenue_over_value\": 0.0362628292646073, \"jax_sharpe\": 0.164804400021953, \"return\": -0.23944844396400877, \"returns_over_hodl\": -0.12516281852189926, \"returns_over_uniform_hodl\": -0.12516281852189914, \"sharpe\": 0.19201552185703666, \"sterling\": -1.140737950389387, \"ulcer\": -0.17106980672236724}], \"train_return\": -0.23944844396400877, \"train_returns_over_hodl\": -0.12516281852189926, \"train_sharpe\": 0.164804400021953, \"validation_return\": -0.2848935982739018, \"validation_returns_over_hodl\": -0.060858790299747834, \"validation_sharpe\": -1.9765466178520268}, {\"centeredness_margin\": 0.1926700827060015, \"continuous_test_metrics\": [{\"annualised_returns\": -0.5313901724791932, \"annualised_returns_over_hodl\": -0.09094915031644724, \"annualised_returns_over_uniform_hodl\": 0.6539850006884829, \"calmar\": -0.9181051148471441, \"daily_log_sharpe\": -0.8063234108334025, \"daily_returns\": 0.031016041689995645, \"fee_revenue_over_value\": 2.2836934143807984e-06, \"jax_sharpe\": -0.12387080190719767, \"return\": -0.26307498897382897, \"returns_over_hodl\": -0.037674724781277424, \"returns_over_uniform_hodl\": 0.2246470130127438, \"sharpe\": -0.32709952186482605, \"sterling\": -1.3710675121635025, \"ulcer\": -0.17557771094933186}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -3.35968565803866, \"optuna_trial_number\": 55, \"price_ratio\": 9.345554353998262, \"shift_exponent\": 0.0004218331297392401, \"step\": 55, \"test_objective\": [{\"annualised_returns\": -0.5313901724791932, \"annualised_returns_over_hodl\": -0.09094915031644724, \"annualised_returns_over_uniform_hodl\": 0.6539850006884829, \"calmar\": -0.9181051148471441, \"daily_log_sharpe\": -0.8063234108334025, \"daily_returns\": 0.031016041689995645, \"fee_revenue_over_value\": 2.2836934143807984e-06, \"jax_sharpe\": -0.12387080190719767, \"return\": -0.26307498897382897, \"returns_over_hodl\": -0.037674724781277424, \"returns_over_uniform_hodl\": 0.2246470130127438, \"sharpe\": -0.32709952186482605, \"sterling\": -1.3710675121635025, \"ulcer\": -0.17557771094933186}], \"train_objective\": [{\"annualised_returns\": -0.4113627302812545, \"annualised_returns_over_hodl\": -0.25870661500639414, \"annualised_returns_over_uniform_hodl\": -0.258706615006394, \"calmar\": -0.5570668520084101, \"daily_log_sharpe\": -0.39787214044235103, \"daily_returns\": 0.008163560745298644, \"fee_revenue_over_value\": 0.029105085798265623, \"jax_sharpe\": 0.17983725634441192, \"return\": -0.2751141022078286, \"returns_over_hodl\": -0.16618784001578457, \"returns_over_uniform_hodl\": -0.16618784001578446, \"sharpe\": 0.1580619021541131, \"sterling\": -1.2630899455327431, \"ulcer\": -0.175505025333844}], \"train_return\": -0.2751141022078286, \"train_returns_over_hodl\": -0.16618784001578457, \"train_sharpe\": 0.17983725634441192, \"validation_return\": -0.32218545501149065, \"validation_returns_over_hodl\": -0.06371125416889434, \"validation_sharpe\": -2.1211603607233704}, {\"centeredness_margin\": 0.2656445084020547, \"continuous_test_metrics\": [{\"annualised_returns\": -0.48135357897186415, \"annualised_returns_over_hodl\": 0.006116269965251719, \"annualised_returns_over_uniform_hodl\": 0.830591999275156, \"calmar\": -0.8303660439832191, \"daily_log_sharpe\": -0.686079315425417, \"daily_returns\": 0.031016041640025502, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": 0.00803604747777697, \"return\": -0.2323417487556929, \"returns_over_hodl\": 0.002458768693966107, \"returns_over_uniform_hodl\": 0.27572055546305774, \"sharpe\": -0.1953760438366523, \"sterling\": -1.239267094549151, \"ulcer\": -0.17641031421615225}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -0.10553296444004434, \"optuna_trial_number\": 56, \"price_ratio\": 3.7438073330252117, \"shift_exponent\": 0.0004335260555226446, \"step\": 56, \"test_objective\": [{\"annualised_returns\": -0.48135357897186415, \"annualised_returns_over_hodl\": 0.006116269965251719, \"annualised_returns_over_uniform_hodl\": 0.830591999275156, \"calmar\": -0.8303660439832191, \"daily_log_sharpe\": -0.686079315425417, \"daily_returns\": 0.031016041640025502, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": 0.00803604747777697, \"return\": -0.2323417487556929, \"returns_over_hodl\": 0.002458768693966107, \"returns_over_uniform_hodl\": 0.27572055546305774, \"sharpe\": -0.1953760438366523, \"sterling\": -1.239267094549151, \"ulcer\": -0.17641031421615225}], \"train_objective\": [{\"annualised_returns\": -0.5232866908655718, \"annualised_returns_over_hodl\": -0.3996567312691348, \"annualised_returns_over_uniform_hodl\": -0.3996567312691348, \"calmar\": -0.6933321609019218, \"daily_log_sharpe\": -0.5172414385585543, \"daily_returns\": 0.008308508697361822, \"fee_revenue_over_value\": 0.021699611744878045, \"jax_sharpe\": 0.16467658511118266, \"return\": -0.36223143078471365, \"returns_over_hodl\": -0.2663960081343665, \"returns_over_uniform_hodl\": -0.2663960081343665, \"sharpe\": 0.10501567124046372, \"sterling\": -1.4981842059828236, \"ulcer\": -0.1900995552461667}], \"train_return\": -0.36223143078471365, \"train_returns_over_hodl\": -0.2663960081343665, \"train_sharpe\": 0.16467658511118266, \"validation_return\": -0.33914271202694357, \"validation_returns_over_hodl\": -3.104859147562422e-09, \"validation_sharpe\": -2.2438531793165386}, {\"centeredness_margin\": 0.10625404321240031, \"continuous_test_metrics\": [{\"annualised_returns\": -0.5018954209949659, \"annualised_returns_over_hodl\": -0.03373261871499278, \"annualised_returns_over_uniform_hodl\": 0.7580884011912827, \"calmar\": -0.865802114137362, \"daily_log_sharpe\": -0.7372427424570989, \"daily_returns\": 0.03101604167120787, \"fee_revenue_over_value\": 0.0002424676048470156, \"jax_sharpe\": -0.054597875988664744, \"return\": -0.2447347020006383, \"returns_over_hodl\": -0.01372476852486093, \"returns_over_uniform_hodl\": 0.2551255248334221, \"sharpe\": -0.2528063634535611, \"sterling\": -1.2921531123363328, \"ulcer\": -0.17641031450991115}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -3.8014929330860125, \"optuna_trial_number\": 57, \"price_ratio\": 7.948657723881354, \"shift_exponent\": 2.4349256636003874e-05, \"step\": 57, \"test_objective\": [{\"annualised_returns\": -0.5018954209949659, \"annualised_returns_over_hodl\": -0.03373261871499278, \"annualised_returns_over_uniform_hodl\": 0.7580884011912827, \"calmar\": -0.865802114137362, \"daily_log_sharpe\": -0.7372427424570989, \"daily_returns\": 0.03101604167120787, \"fee_revenue_over_value\": 0.0002424676048470156, \"jax_sharpe\": -0.054597875988664744, \"return\": -0.2447347020006383, \"returns_over_hodl\": -0.01372476852486093, \"returns_over_uniform_hodl\": 0.2551255248334221, \"sharpe\": -0.2528063634535611, \"sterling\": -1.2921531123363328, \"ulcer\": -0.17641031450991115}], \"train_objective\": [{\"annualised_returns\": -0.41024006597167373, \"annualised_returns_over_hodl\": -0.25729280098361174, \"annualised_returns_over_uniform_hodl\": -0.25729280098361196, \"calmar\": -0.557258362442257, \"daily_log_sharpe\": -0.3869187166651316, \"daily_returns\": 0.008173696854745404, \"fee_revenue_over_value\": 0.03829759213340564, \"jax_sharpe\": 0.15594966352052128, \"return\": -0.27427505819072173, \"returns_over_hodl\": -0.16522271556467216, \"returns_over_uniform_hodl\": -0.16522271556467227, \"sharpe\": 0.17714720542914844, \"sterling\": -1.25287947320896, \"ulcer\": -0.1764090385583106}], \"train_return\": -0.27427505819072173, \"train_returns_over_hodl\": -0.16522271556467216, \"train_sharpe\": 0.1559496635205213, \"validation_return\": -0.3324994304523329, \"validation_returns_over_hodl\": -0.052976984143466854, \"validation_sharpe\": -2.185461924223911}, {\"centeredness_margin\": 0.23571842596941667, \"continuous_test_metrics\": [{\"annualised_returns\": -0.4845064781914272, \"annualised_returns_over_hodl\": 3.562417028035725e-11, \"annualised_returns_over_uniform_hodl\": 0.8194636624124219, \"calmar\": -0.8358050081993634, \"daily_log_sharpe\": -0.6924636598477574, \"daily_returns\": 0.031016041560109522, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": 0.0050192086602594286, \"return\": -0.23422461429903807, \"returns_over_hodl\": 1.4347190102625973e-11, \"returns_over_uniform_hodl\": 0.27259154555151843, \"sharpe\": -0.2021086035162671, \"sterling\": -1.2473843950708845, \"ulcer\": -0.17641031405093915}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -0.13158327497302214, \"optuna_trial_number\": 58, \"price_ratio\": 2.6282114545281074, \"shift_exponent\": 0.000539721235445762, \"step\": 58, \"test_objective\": [{\"annualised_returns\": -0.4845064781914272, \"annualised_returns_over_hodl\": 3.562417028035725e-11, \"annualised_returns_over_uniform_hodl\": 0.8194636624124219, \"calmar\": -0.8358050081993634, \"daily_log_sharpe\": -0.6924636598477574, \"daily_returns\": 0.031016041560109522, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": 0.0050192086602594286, \"return\": -0.23422461429903807, \"returns_over_hodl\": 1.4347190102625973e-11, \"returns_over_uniform_hodl\": 0.27259154555151843, \"sharpe\": -0.2021086035162671, \"sterling\": -1.2473843950708845, \"ulcer\": -0.17641031405093915}], \"train_objective\": [{\"annualised_returns\": -0.5710216724127979, \"annualised_returns_over_hodl\": -0.45977121581520863, \"annualised_returns_over_uniform_hodl\": -0.45977121581520863, \"calmar\": -0.7438465911336937, \"daily_log_sharpe\": -0.5753177897393136, \"daily_returns\": 0.008489676338643622, \"fee_revenue_over_value\": 0.02571936024211628, \"jax_sharpe\": 0.14837705469457438, \"return\": -0.4018038113394421, \"returns_over_hodl\": -0.31191480248055703, \"returns_over_uniform_hodl\": -0.31191480248055703, \"sharpe\": 0.07848678069858754, \"sterling\": -1.5747320095232638, \"ulcer\": -0.19970458471850006}], \"train_return\": -0.4018038113394421, \"train_returns_over_hodl\": -0.31191480248055703, \"train_sharpe\": 0.14837705469457438, \"validation_return\": -0.3391427116595397, \"validation_returns_over_hodl\": -3.8832939086574925e-09, \"validation_sharpe\": -2.2438531811970486}, {\"centeredness_margin\": 0.267758544347435, \"continuous_test_metrics\": [{\"annualised_returns\": -0.4834703769809917, \"annualised_returns_over_hodl\": 0.002009917288201546, \"annualised_returns_over_uniform_hodl\": 0.823120640479488, \"calmar\": -0.8340176637410834, \"daily_log_sharpe\": -0.6903095408019847, \"daily_returns\": 0.031016041649551667, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": 0.00586593310500043, \"return\": -0.23360511286407204, \"returns_over_hodl\": 0.0008089843702221433, \"returns_over_uniform_hodl\": 0.27362105407753745, \"sharpe\": -0.19979572051777442, \"sterling\": -1.2447168983809858, \"ulcer\": -0.17641031423584308}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -0.10470614324232663, \"optuna_trial_number\": 59, \"price_ratio\": 4.223962285460809, \"shift_exponent\": 3.589097504412288e-05, \"step\": 59, \"test_objective\": [{\"annualised_returns\": -0.4834703769809917, \"annualised_returns_over_hodl\": 0.002009917288201546, \"annualised_returns_over_uniform_hodl\": 0.823120640479488, \"calmar\": -0.8340176637410834, \"daily_log_sharpe\": -0.6903095408019847, \"daily_returns\": 0.031016041649551667, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": 0.00586593310500043, \"return\": -0.23360511286407204, \"returns_over_hodl\": 0.0008089843702221433, \"returns_over_uniform_hodl\": 0.27362105407753745, \"sharpe\": -0.19979572051777442, \"sterling\": -1.2447168983809858, \"ulcer\": -0.17641031423584308}], \"train_objective\": [{\"annualised_returns\": -0.5195410553080053, \"annualised_returns_over_hodl\": -0.3949397094217967, \"annualised_returns_over_uniform_hodl\": -0.3949397094217967, \"calmar\": -0.690436556971449, \"daily_log_sharpe\": -0.5157500391822903, \"daily_returns\": 0.008250651440953114, \"fee_revenue_over_value\": 0.020669692335081855, \"jax_sharpe\": 0.10986824065839054, \"return\": -0.35919377550965637, \"returns_over_hodl\": -0.2629018942139586, \"returns_over_uniform_hodl\": -0.2629018942139586, \"sharpe\": 0.10029371930248625, \"sterling\": -1.4964453037002081, \"ulcer\": -0.1889763222235071}], \"train_return\": -0.35919377550965637, \"train_returns_over_hodl\": -0.2629018942139586, \"train_sharpe\": 0.10986824065839056, \"validation_return\": -0.33914271211564007, \"validation_returns_over_hodl\": -3.0800393346908095e-09, \"validation_sharpe\": -2.2438531795507877}, {\"centeredness_margin\": 0.6300697501394983, \"continuous_test_metrics\": [{\"annualised_returns\": -0.6883185439434634, \"annualised_returns_over_hodl\": 0.10015504367664274, \"annualised_returns_over_uniform_hodl\": 0.10009740093931008, \"calmar\": -1.1896634572148406, \"daily_log_sharpe\": -1.5166990418615058, \"daily_returns\": 0.018171005248093088, \"fee_revenue_over_value\": 0.08366496345130646, \"jax_sharpe\": -0.9835296270753778, \"return\": -0.374685850928423, \"returns_over_hodl\": 0.0391902205539989, \"returns_over_uniform_hodl\": 0.039168291748909345, \"sharpe\": -1.1387488028516266, \"sterling\": -2.122141085511769, \"ulcer\": -0.15060944797562648}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -2.5186041294902175, \"optuna_trial_number\": 60, \"price_ratio\": 10.793611098567078, \"shift_exponent\": 105.35832011015675, \"step\": 60, \"test_objective\": [{\"annualised_returns\": -0.6883185439434634, \"annualised_returns_over_hodl\": 0.10015504367664274, \"annualised_returns_over_uniform_hodl\": 0.10009740093931008, \"calmar\": -1.1896634572148406, \"daily_log_sharpe\": -1.5166990418615058, \"daily_returns\": 0.018171005248093088, \"fee_revenue_over_value\": 0.08366496345130646, \"jax_sharpe\": -0.9835296270753778, \"return\": -0.374685850928423, \"returns_over_hodl\": 0.0391902205539989, \"returns_over_uniform_hodl\": 0.039168291748909345, \"sharpe\": -1.1387488028516266, \"sterling\": -2.122141085511769, \"ulcer\": -0.15060944797562648}], \"train_objective\": [{\"annualised_returns\": -0.4728858684303704, \"annualised_returns_over_hodl\": -0.3361850515243173, \"annualised_returns_over_uniform_hodl\": -0.3361850515243173, \"calmar\": -0.6248495226276348, \"daily_log_sharpe\": -0.5987117508140299, \"daily_returns\": 0.008129796104198363, \"fee_revenue_over_value\": 0.03713710310029866, \"jax_sharpe\": -0.14671616362643786, \"return\": -0.32210509204309834, \"returns_over_hodl\": -0.22024001409405058, \"returns_over_uniform_hodl\": -0.22024001409405058, \"sharpe\": -0.11664157839651208, \"sterling\": -1.5269487038710026, \"ulcer\": -0.1679921025723317}], \"train_return\": -0.32210509204309834, \"train_returns_over_hodl\": -0.22024001409405058, \"train_sharpe\": -0.14671616362643786, \"validation_return\": -0.16800300459464557, \"validation_returns_over_hodl\": -0.02716524414018684, \"validation_sharpe\": -1.3406219873389764}, {\"centeredness_margin\": 0.8665305887936725, \"continuous_test_metrics\": [{\"annualised_returns\": -0.6734505758682833, \"annualised_returns_over_hodl\": -0.11208112685391891, \"annualised_returns_over_uniform_hodl\": 0.15257473867924776, \"calmar\": -1.1361985974047746, \"daily_log_sharpe\": -1.3342179397583376, \"daily_returns\": 0.023260188719543263, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": -0.7276266115848721, \"return\": -0.3628394963967173, \"returns_over_hodl\": -0.04674746238253735, \"returns_over_uniform_hodl\": 0.05885496607163532, \"sharpe\": -0.9207855538327953, \"sterling\": -1.9092017319914474, \"ulcer\": -0.15764305818612814}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -3.0161656327065236, \"optuna_trial_number\": 61, \"price_ratio\": 93.7589489152387, \"shift_exponent\": 5.626553703644291e-05, \"step\": 61, \"test_objective\": [{\"annualised_returns\": -0.6734505758682833, \"annualised_returns_over_hodl\": -0.11208112685391891, \"annualised_returns_over_uniform_hodl\": 0.15257473867924776, \"calmar\": -1.1361985974047746, \"daily_log_sharpe\": -1.3342179397583376, \"daily_returns\": 0.023260188719543263, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": -0.7276266115848721, \"return\": -0.3628394963967173, \"returns_over_hodl\": -0.04674746238253735, \"returns_over_uniform_hodl\": 0.05885496607163532, \"sharpe\": -0.9207855538327953, \"sterling\": -1.9092017319914474, \"ulcer\": -0.15764305818612814}], \"train_objective\": [{\"annualised_returns\": -0.37088403724780583, \"annualised_returns_over_hodl\": -0.20773025159464564, \"annualised_returns_over_uniform_hodl\": -0.20773025159464564, \"calmar\": -0.5028052103360962, \"daily_log_sharpe\": -0.38464556454979903, \"daily_returns\": 0.00799676355208315, \"fee_revenue_over_value\": 0.000667885689676034, \"jax_sharpe\": 0.09834808627927445, \"return\": -0.24524655024652597, \"returns_over_hodl\": -0.13183218750530545, \"returns_over_uniform_hodl\": -0.13183218750530545, \"sharpe\": 0.12602327074505623, \"sterling\": -1.1919365233765222, \"ulcer\": -0.16826926160682273}], \"train_return\": -0.24524655024652597, \"train_returns_over_hodl\": -0.13183218750530545, \"train_sharpe\": 0.09834808627927445, \"validation_return\": -0.21937451968751964, \"validation_returns_over_hodl\": -0.04287977954275857, \"validation_sharpe\": -1.6681126527875707}, {\"centeredness_margin\": 0.1910770839392888, \"continuous_test_metrics\": [{\"annualised_returns\": -0.482195088944416, \"annualised_returns_over_hodl\": 0.004483833608132981, \"annualised_returns_over_uniform_hodl\": 0.8276218420339094, \"calmar\": -0.8318177056404122, \"daily_log_sharpe\": -0.6877738701890543, \"daily_returns\": 0.031016041658791665, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": 0.006772359557454721, \"return\": -0.2328436159216113, \"returns_over_hodl\": 0.0018033971430739815, \"returns_over_uniform_hodl\": 0.274886535560795, \"sharpe\": -0.19716017300441743, \"sterling\": -1.2414336043961096, \"ulcer\": -0.1764103142549524}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -0.10117867813604686, \"optuna_trial_number\": 62, \"price_ratio\": 4.281986893781591, \"shift_exponent\": 0.00015482419530303722, \"step\": 62, \"test_objective\": [{\"annualised_returns\": -0.482195088944416, \"annualised_returns_over_hodl\": 0.004483833608132981, \"annualised_returns_over_uniform_hodl\": 0.8276218420339094, \"calmar\": -0.8318177056404122, \"daily_log_sharpe\": -0.6877738701890543, \"daily_returns\": 0.031016041658791665, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": 0.006772359557454721, \"return\": -0.2328436159216113, \"returns_over_hodl\": 0.0018033971430739815, \"returns_over_uniform_hodl\": 0.274886535560795, \"sharpe\": -0.19716017300441743, \"sterling\": -1.2414336043961096, \"ulcer\": -0.1764103142549524}], \"train_objective\": [{\"annualised_returns\": -0.5096638056070288, \"annualised_returns_over_hodl\": -0.3825009118091981, \"annualised_returns_over_uniform_hodl\": -0.3825009118091981, \"calmar\": -0.6779322654603734, \"daily_log_sharpe\": -0.5009326712289252, \"daily_returns\": 0.008224663122303012, \"fee_revenue_over_value\": 0.023889543183496002, \"jax_sharpe\": 0.11880625147661371, \"return\": -0.35122775894191216, \"returns_over_hodl\": -0.253738850069975, \"returns_over_uniform_hodl\": -0.253738850069975, \"sharpe\": 0.11250939203851174, \"sterling\": -1.4730275549730445, \"ulcer\": -0.1880912634313492}], \"train_return\": -0.35122775894191216, \"train_returns_over_hodl\": -0.253738850069975, \"train_sharpe\": 0.1188062514766137, \"validation_return\": -0.3391427121483692, \"validation_returns_over_hodl\": -2.975222845869041e-09, \"validation_sharpe\": -2.243853179231364}, {\"centeredness_margin\": 0.7115278306916851, \"continuous_test_metrics\": [{\"annualised_returns\": -0.6885871053746123, \"annualised_returns_over_hodl\": -0.1019772485979179, \"annualised_returns_over_uniform_hodl\": 0.09914949811526164, \"calmar\": -1.1573507433178492, \"daily_log_sharpe\": -1.4137419096328339, \"daily_returns\": 0.02205406787641536, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": -0.8270278020671638, \"return\": -0.3749029036025745, \"returns_over_hodl\": -0.042393589186598635, \"returns_over_uniform_hodl\": 0.03880758624280101, \"sharpe\": -1.0083129014086647, \"sterling\": -1.9904380378450859, \"ulcer\": -0.1554628561114101}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -3.014266788176723, \"optuna_trial_number\": 63, \"price_ratio\": 181.64600392538915, \"shift_exponent\": 3.671014373041449e-05, \"step\": 63, \"test_objective\": [{\"annualised_returns\": -0.6885871053746123, \"annualised_returns_over_hodl\": -0.1019772485979179, \"annualised_returns_over_uniform_hodl\": 0.09914949811526164, \"calmar\": -1.1573507433178492, \"daily_log_sharpe\": -1.4137419096328339, \"daily_returns\": 0.02205406787641536, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": -0.8270278020671638, \"return\": -0.3749029036025745, \"returns_over_hodl\": -0.042393589186598635, \"returns_over_uniform_hodl\": 0.03880758624280101, \"sharpe\": -1.0083129014086647, \"sterling\": -1.9904380378450859, \"ulcer\": -0.1554628561114101}], \"train_objective\": [{\"annualised_returns\": -0.35034270930677747, \"annualised_returns_over_hodl\": -0.18186177315299967, \"annualised_returns_over_uniform_hodl\": -0.18186177315299967, \"calmar\": -0.47733231042872803, \"daily_log_sharpe\": -0.35883973168621647, \"daily_returns\": 0.007998688883340243, \"fee_revenue_over_value\": 0.009577740533868277, \"jax_sharpe\": 0.11749263277191742, \"return\": -0.23037947237109346, \"returns_over_hodl\": -0.11473108186409453, \"returns_over_uniform_hodl\": -0.11473108186409453, \"sharpe\": 0.14657319628911578, \"sterling\": -1.1365126155771006, \"ulcer\": -0.16645985576522235}], \"train_return\": -0.23037947237109346, \"train_returns_over_hodl\": -0.11473108186409453, \"train_sharpe\": 0.11749263277191742, \"validation_return\": -0.20623358560873473, \"validation_returns_over_hodl\": -0.039004028397924606, \"validation_sharpe\": -1.589066622860633}, {\"centeredness_margin\": 0.26632954505349865, \"continuous_test_metrics\": [{\"annualised_returns\": -0.4845064775715857, \"annualised_returns_over_hodl\": 3.479283527951793e-11, \"annualised_returns_over_uniform_hodl\": 0.8194636646001878, \"calmar\": -0.8358050072062503, \"daily_log_sharpe\": -0.6924636582913836, \"daily_returns\": 0.031016041589563042, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": 0.0050192105235402126, \"return\": -0.2342246139282026, \"returns_over_hodl\": 1.4012346838399026e-11, \"returns_over_uniform_hodl\": 0.27259154616778547, \"sharpe\": -0.20210860169071412, \"sterling\": -1.2473843929937345, \"ulcer\": -0.17641031411182756}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -0.12828215707660984, \"optuna_trial_number\": 64, \"price_ratio\": 3.03312559719746, \"shift_exponent\": 2.491644467689685e-05, \"step\": 64, \"test_objective\": [{\"annualised_returns\": -0.4845064775715857, \"annualised_returns_over_hodl\": 3.479283527951793e-11, \"annualised_returns_over_uniform_hodl\": 0.8194636646001878, \"calmar\": -0.8358050072062503, \"daily_log_sharpe\": -0.6924636582913836, \"daily_returns\": 0.031016041589563042, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": 0.0050192105235402126, \"return\": -0.2342246139282026, \"returns_over_hodl\": 1.4012346838399026e-11, \"returns_over_uniform_hodl\": 0.27259154616778547, \"sharpe\": -0.20210860169071412, \"sterling\": -1.2473843929937345, \"ulcer\": -0.17641031411182756}], \"train_objective\": [{\"annualised_returns\": -0.5551365834029796, \"annualised_returns_over_hodl\": -0.43976651681162804, \"annualised_returns_over_uniform_hodl\": -0.43976651681162804, \"calmar\": -0.7280932988472761, \"daily_log_sharpe\": -0.5532679310064424, \"daily_returns\": 0.008409132991487857, \"fee_revenue_over_value\": 0.023675212657893355, \"jax_sharpe\": 0.1298576278247259, \"return\": -0.38845150378359206, \"returns_over_hodl\": -0.2965560868015428, \"returns_over_uniform_hodl\": -0.2965560868015428, \"sharpe\": 0.09265046070124018, \"sterling\": -1.541857063812107, \"ulcer\": -0.1976335635213762}], \"train_return\": -0.38845150378359206, \"train_returns_over_hodl\": -0.2965560868015428, \"train_sharpe\": 0.1298576278247259, \"validation_return\": -0.3391427119186914, \"validation_returns_over_hodl\": -3.7836580535355324e-09, \"validation_sharpe\": -2.2438531817450618}, {\"centeredness_margin\": 0.3609476030620956, \"continuous_test_metrics\": [{\"annualised_returns\": -0.48201379561425006, \"annualised_returns_over_hodl\": 0.004835522406822612, \"annualised_returns_over_uniform_hodl\": 0.8282617271391917, \"calmar\": -0.831504962389006, \"daily_log_sharpe\": -0.6874096003029839, \"daily_returns\": 0.03101604166030528, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": 0.007481835130778495, \"return\": -0.23273545345532243, \"returns_over_hodl\": 0.0019446427994769255, \"returns_over_uniform_hodl\": 0.2750662836210167, \"sharpe\": -0.19677733824674373, \"sterling\": -1.2409668554864757, \"ulcer\": -0.17641031425808273}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -0.10084904423231578, \"optuna_trial_number\": 65, \"price_ratio\": 4.5345300494733, \"shift_exponent\": 5.876976943699182e-05, \"step\": 65, \"test_objective\": [{\"annualised_returns\": -0.48201379561425006, \"annualised_returns_over_hodl\": 0.004835522406822612, \"annualised_returns_over_uniform_hodl\": 0.8282617271391917, \"calmar\": -0.831504962389006, \"daily_log_sharpe\": -0.6874096003029839, \"daily_returns\": 0.03101604166030528, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": 0.007481835130778495, \"return\": -0.23273545345532243, \"returns_over_hodl\": 0.0019446427994769255, \"returns_over_uniform_hodl\": 0.2750662836210167, \"sharpe\": -0.19677733824674373, \"sterling\": -1.2409668554864757, \"ulcer\": -0.17641031425808273}], \"train_objective\": [{\"annualised_returns\": -0.5111042582252715, \"annualised_returns_over_hodl\": -0.38431492878065243, \"annualised_returns_over_uniform_hodl\": -0.3843149287806521, \"calmar\": -0.6804261889666048, \"daily_log_sharpe\": -0.5066998329477528, \"daily_returns\": 0.008263992467852385, \"fee_revenue_over_value\": 0.019573552217783254, \"jax_sharpe\": 0.17095394664186803, \"return\": -0.3523855327127019, \"returns_over_hodl\": -0.25507059876523275, \"returns_over_uniform_hodl\": -0.2550705987652325, \"sharpe\": 0.10266961003174503, \"sterling\": -1.4831974515410062, \"ulcer\": -0.1872763213337625}], \"train_return\": -0.3523855327127019, \"train_returns_over_hodl\": -0.25507059876523275, \"train_sharpe\": 0.17095394664186803, \"validation_return\": -0.3391427121356564, \"validation_returns_over_hodl\": -2.9658732136894628e-09, \"validation_sharpe\": -2.2438531789977665}, {\"centeredness_margin\": 0.27357052324701936, \"continuous_test_metrics\": [{\"annualised_returns\": -0.4838095436731792, \"annualised_returns_over_hodl\": 0.001351971969646426, \"annualised_returns_over_uniform_hodl\": 0.821923532376615, \"calmar\": -0.8346027492135698, \"daily_log_sharpe\": -0.6910126763704838, \"daily_returns\": 0.031016041642837753, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": 0.005718658217761613, \"return\": -0.23380782425872904, \"returns_over_hodl\": 0.000544270573546557, \"returns_over_uniform_hodl\": 0.27328418139679544, \"sharpe\": -0.20054934051556025, \"sterling\": -1.245590100375597, \"ulcer\": -0.17641031422196818}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -0.10885364406847087, \"optuna_trial_number\": 66, \"price_ratio\": 4.1198963228361, \"shift_exponent\": 2.301718085909097e-05, \"step\": 66, \"test_objective\": [{\"annualised_returns\": -0.4838095436731792, \"annualised_returns_over_hodl\": 0.001351971969646426, \"annualised_returns_over_uniform_hodl\": 0.821923532376615, \"calmar\": -0.8346027492135698, \"daily_log_sharpe\": -0.6910126763704838, \"daily_returns\": 0.031016041642837753, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": 0.005718658217761613, \"return\": -0.23380782425872904, \"returns_over_hodl\": 0.000544270573546557, \"returns_over_uniform_hodl\": 0.27328418139679544, \"sharpe\": -0.20054934051556025, \"sterling\": -1.245590100375597, \"ulcer\": -0.17641031422196818}], \"train_objective\": [{\"annualised_returns\": -0.5233885262934739, \"annualised_returns_over_hodl\": -0.39978497651106126, \"annualised_returns_over_uniform_hodl\": -0.39978497651106126, \"calmar\": -0.6949107279967314, \"daily_log_sharpe\": -0.5204537759292509, \"daily_returns\": 0.008279766990742819, \"fee_revenue_over_value\": 0.020774495431039812, \"jax_sharpe\": 0.16012375404905776, \"return\": -0.3623141484912449, \"returns_over_hodl\": -0.26649115556344727, \"returns_over_uniform_hodl\": -0.26649115556344727, \"sharpe\": 0.09796188415495105, \"sterling\": -1.5022741409708464, \"ulcer\": -0.189744342122153}], \"train_return\": -0.3623141484912449, \"train_returns_over_hodl\": -0.26649115556344727, \"train_sharpe\": 0.16012375404905776, \"validation_return\": -0.3391427120959032, \"validation_returns_over_hodl\": -3.2037190678124716e-09, \"validation_sharpe\": -2.2438531798197157}, {\"centeredness_margin\": 0.17128160108175566, \"continuous_test_metrics\": [{\"annualised_returns\": -0.48450647713690986, \"annualised_returns_over_hodl\": 3.426725569966038e-11, \"annualised_returns_over_uniform_hodl\": 0.819463666134401, \"calmar\": -0.8358050065098126, \"daily_log_sharpe\": -0.6924636571999633, \"daily_returns\": 0.031016041610217916, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": 0.005019211830163102, \"return\": -0.23422461366814706, \"returns_over_hodl\": 1.3800738329905471e-11, \"returns_over_uniform_hodl\": 0.27259154659995466, \"sharpe\": -0.2021086004105336, \"sterling\": -1.2473843915371183, \"ulcer\": -0.17641031415452202}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -0.12169075495802306, \"optuna_trial_number\": 67, \"price_ratio\": 3.283079172673024, \"shift_exponent\": 4.381821048451919e-05, \"step\": 67, \"test_objective\": [{\"annualised_returns\": -0.48450647713690986, \"annualised_returns_over_hodl\": 3.426725569966038e-11, \"annualised_returns_over_uniform_hodl\": 0.819463666134401, \"calmar\": -0.8358050065098126, \"daily_log_sharpe\": -0.6924636571999633, \"daily_returns\": 0.031016041610217916, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": 0.005019211830163102, \"return\": -0.23422461366814706, \"returns_over_hodl\": 1.3800738329905471e-11, \"returns_over_uniform_hodl\": 0.27259154659995466, \"sharpe\": -0.2021086004105336, \"sterling\": -1.2473843915371183, \"ulcer\": -0.17641031415452202}], \"train_objective\": [{\"annualised_returns\": -0.5447380606308698, \"annualised_returns_over_hodl\": -0.42667126012094403, \"annualised_returns_over_uniform_hodl\": -0.42667126012094425, \"calmar\": -0.7168473253301163, \"daily_log_sharpe\": -0.5410755685388426, \"daily_returns\": 0.0083871274660848, \"fee_revenue_over_value\": 0.024044340563194242, \"jax_sharpe\": 0.12818477833311903, \"return\": -0.3798122926511758, \"returns_over_hodl\": -0.2866186893203395, \"returns_over_uniform_hodl\": -0.2866186893203396, \"sharpe\": 0.09744817071890673, \"sterling\": -1.5244717550549838, \"ulcer\": -0.19562137994070394}], \"train_return\": -0.3798122926511758, \"train_returns_over_hodl\": -0.2866186893203395, \"train_sharpe\": 0.12818477833311903, \"validation_return\": -0.33914271201609514, \"validation_returns_over_hodl\": -3.5862013358922695e-09, \"validation_sharpe\": -2.2438531812807962}, {\"centeredness_margin\": 0.031194964771086242, \"continuous_test_metrics\": [{\"annualised_returns\": -0.5051700434975996, \"annualised_returns_over_hodl\": -0.040085022544421034, \"annualised_returns_over_uniform_hodl\": 0.7465304350877375, \"calmar\": -0.8714510605616108, \"daily_log_sharpe\": -0.7484270900371405, \"daily_returns\": 0.031016041765626282, \"fee_revenue_over_value\": 0.002270899235686589, \"jax_sharpe\": -0.06531706799682997, \"return\": -0.24673833161183767, \"returns_over_hodl\": -0.01634124063369491, \"returns_over_uniform_hodl\": 0.25179582509216547, \"sharpe\": -0.2654274552286578, \"sterling\": -1.300583796137309, \"ulcer\": -0.17641031447580408}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -0.08926499769946283, \"optuna_trial_number\": 68, \"price_ratio\": 4.945977351250102, \"shift_exponent\": 0.0016918635608756448, \"step\": 68, \"test_objective\": [{\"annualised_returns\": -0.5051700434975996, \"annualised_returns_over_hodl\": -0.040085022544421034, \"annualised_returns_over_uniform_hodl\": 0.7465304350877375, \"calmar\": -0.8714510605616108, \"daily_log_sharpe\": -0.7484270900371405, \"daily_returns\": 0.031016041765626282, \"fee_revenue_over_value\": 0.002270899235686589, \"jax_sharpe\": -0.06531706799682997, \"return\": -0.24673833161183767, \"returns_over_hodl\": -0.01634124063369491, \"returns_over_uniform_hodl\": 0.25179582509216547, \"sharpe\": -0.2654274552286578, \"sterling\": -1.300583796137309, \"ulcer\": -0.17641031447580408}], \"train_objective\": [{\"annualised_returns\": -0.4519389657702849, \"annualised_returns_over_hodl\": -0.30980581735614166, \"annualised_returns_over_uniform_hodl\": -0.30980581735614143, \"calmar\": -0.6102612484266907, \"daily_log_sharpe\": -0.4207957245876338, \"daily_returns\": 0.008239984427069729, \"fee_revenue_over_value\": 0.045822324656036306, \"jax_sharpe\": 0.1636194479658585, \"return\": -0.3058753687580581, \"returns_over_hodl\": -0.2015715027194157, \"returns_over_uniform_hodl\": -0.2015715027194156, \"sharpe\": 0.17486597150800562, \"sterling\": -1.3373922109515048, \"ulcer\": -0.1824996268316073}], \"train_return\": -0.3058753687580581, \"train_returns_over_hodl\": -0.2015715027194157, \"train_sharpe\": 0.1636194479658585, \"validation_return\": -0.33914271283372965, \"validation_returns_over_hodl\": -2.626357464841078e-09, \"validation_sharpe\": -2.2438531786847924}, {\"centeredness_margin\": 0.09873011080647182, \"continuous_test_metrics\": [{\"annualised_returns\": -0.4807023539563354, \"annualised_returns_over_hodl\": 0.007379571389799056, \"annualised_returns_over_uniform_hodl\": 0.8328905349534552, \"calmar\": -0.8683809543753, \"daily_log_sharpe\": -0.7337480026442527, \"daily_returns\": 0.03101604165405883, \"fee_revenue_over_value\": 0.07476254805737152, \"jax_sharpe\": -0.08100360496814644, \"return\": -0.23195369913617592, \"returns_over_hodl\": 0.0029655084562743017, \"returns_over_uniform_hodl\": 0.27636542950089327, \"sharpe\": -0.2708812243023653, \"sterling\": -1.258293732542849, \"ulcer\": -0.17114448669658072}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -3.744303345934075, \"optuna_trial_number\": 69, \"price_ratio\": 5.288185072635788, \"shift_exponent\": 0.0037797786538374865, \"step\": 69, \"test_objective\": [{\"annualised_returns\": -0.4807023539563354, \"annualised_returns_over_hodl\": 0.007379571389799056, \"annualised_returns_over_uniform_hodl\": 0.8328905349534552, \"calmar\": -0.8683809543753, \"daily_log_sharpe\": -0.7337480026442527, \"daily_returns\": 0.03101604165405883, \"fee_revenue_over_value\": 0.07476254805737152, \"jax_sharpe\": -0.08100360496814644, \"return\": -0.23195369913617592, \"returns_over_hodl\": 0.0029655084562743017, \"returns_over_uniform_hodl\": 0.27636542950089327, \"sharpe\": -0.2708812243023653, \"sterling\": -1.258293732542849, \"ulcer\": -0.17114448669658072}], \"train_objective\": [{\"annualised_returns\": -0.4499407195091304, \"annualised_returns_over_hodl\": -0.307289349556388, \"annualised_returns_over_uniform_hodl\": -0.3072893495563881, \"calmar\": -0.6048220900293871, \"daily_log_sharpe\": -0.4333200133816774, \"daily_returns\": 0.008258659517172218, \"fee_revenue_over_value\": 0.03571710981948347, \"jax_sharpe\": 0.1403385258929348, \"return\": -0.3043399655912994, \"returns_over_hodl\": -0.19980537947874977, \"returns_over_uniform_hodl\": -0.19980537947874988, \"sharpe\": 0.15055903655044997, \"sterling\": -1.3426720237100378, \"ulcer\": -0.18099472122971721}], \"train_return\": -0.3043399655912994, \"train_returns_over_hodl\": -0.19980537947874977, \"train_sharpe\": 0.14033852589293483, \"validation_return\": -0.3334804509985303, \"validation_returns_over_hodl\": -0.045203662682516854, \"validation_sharpe\": -2.2074929228897897}, {\"centeredness_margin\": 0.25385380139167213, \"continuous_test_metrics\": [{\"annualised_returns\": -0.5032779889255597, \"annualised_returns_over_hodl\": -0.036414646932341754, \"annualised_returns_over_uniform_hodl\": 0.7532085491580169, \"calmar\": -0.8681871377534633, \"daily_log_sharpe\": -0.7405957392757336, \"daily_returns\": 0.031016041751118762, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": -0.05858940905005134, \"return\": -0.2455796855682053, \"returns_over_hodl\": -0.01482820398946505, \"returns_over_uniform_hodl\": 0.25372130244093594, \"sharpe\": -0.25647607223181834, \"sterling\": -1.295712604807214, \"ulcer\": -0.1764103144458115}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -3.924927400639611, \"optuna_trial_number\": 70, \"price_ratio\": 7.4297879249699275, \"shift_exponent\": 0.00032230750379443437, \"step\": 70, \"test_objective\": [{\"annualised_returns\": -0.5032779889255597, \"annualised_returns_over_hodl\": -0.036414646932341754, \"annualised_returns_over_uniform_hodl\": 0.7532085491580169, \"calmar\": -0.8681871377534633, \"daily_log_sharpe\": -0.7405957392757336, \"daily_returns\": 0.031016041751118762, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": -0.05858940905005134, \"return\": -0.2455796855682053, \"returns_over_hodl\": -0.01482820398946505, \"returns_over_uniform_hodl\": 0.25372130244093594, \"sharpe\": -0.25647607223181834, \"sterling\": -1.295712604807214, \"ulcer\": -0.1764103144458115}], \"train_objective\": [{\"annualised_returns\": -0.44173013348705337, \"annualised_returns_over_hodl\": -0.296949445139539, \"annualised_returns_over_uniform_hodl\": -0.29694944513953925, \"calmar\": -0.594030305859009, \"daily_log_sharpe\": -0.4330676777287735, \"daily_returns\": 0.008190212293988608, \"fee_revenue_over_value\": 0.02202256083586799, \"jax_sharpe\": 0.16127974759080238, \"return\": -0.29805401867263714, \"returns_over_hodl\": -0.1925748636227238, \"returns_over_uniform_hodl\": -0.1925748636227239, \"sharpe\": 0.13466355700663954, \"sterling\": -1.337809005227039, \"ulcer\": -0.17838571473145018}], \"train_return\": -0.29805401867263714, \"train_returns_over_hodl\": -0.1925748636227238, \"train_sharpe\": 0.16127974759080238, \"validation_return\": -0.3348426839858136, \"validation_returns_over_hodl\": -0.04624686311771309, \"validation_sharpe\": -2.205945215181302}, {\"centeredness_margin\": 0.8760970488031538, \"continuous_test_metrics\": [{\"annualised_returns\": -0.6852187402775538, \"annualised_returns_over_hodl\": 0.11262310840518341, \"annualised_returns_over_uniform_hodl\": 0.11103833402989083, \"calmar\": -1.1864611136914671, \"daily_log_sharpe\": -1.492668667639689, \"daily_returns\": 0.018141299244058395, \"fee_revenue_over_value\": 0.0634286150082305, \"jax_sharpe\": -0.9716501248132086, \"return\": -0.3721886187357605, \"returns_over_hodl\": 0.04391736794594636, \"returns_over_uniform_hodl\": 0.04331827702527402, \"sharpe\": -1.1102953272246823, \"sterling\": -2.12703283203177, \"ulcer\": -0.14866369376317123}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -3.172360469189875, \"optuna_trial_number\": 71, \"price_ratio\": 64.85303530491584, \"shift_exponent\": 55.04829903518503, \"step\": 71, \"test_objective\": [{\"annualised_returns\": -0.6852187402775538, \"annualised_returns_over_hodl\": 0.11262310840518341, \"annualised_returns_over_uniform_hodl\": 0.11103833402989083, \"calmar\": -1.1864611136914671, \"daily_log_sharpe\": -1.492668667639689, \"daily_returns\": 0.018141299244058395, \"fee_revenue_over_value\": 0.0634286150082305, \"jax_sharpe\": -0.9716501248132086, \"return\": -0.3721886187357605, \"returns_over_hodl\": 0.04391736794594636, \"returns_over_uniform_hodl\": 0.04331827702527402, \"sharpe\": -1.1102953272246823, \"sterling\": -2.12703283203177, \"ulcer\": -0.14866369376317123}], \"train_objective\": [{\"annualised_returns\": -0.35174975989797375, \"annualised_returns_over_hodl\": -0.1836337256766709, \"annualised_returns_over_uniform_hodl\": -0.1836337256766709, \"calmar\": -0.4776515472386664, \"daily_log_sharpe\": -0.3835787777176731, \"daily_returns\": 0.008043129265622574, \"fee_revenue_over_value\": 0.0282797606590074, \"jax_sharpe\": 0.06959366506143559, \"return\": -0.2313918965052979, \"returns_over_hodl\": -0.1158956397024139, \"returns_over_uniform_hodl\": -0.1158956397024139, \"sharpe\": 0.1012540992552355, \"sterling\": -1.1663125836276975, \"ulcer\": -0.16359508252641106}], \"train_return\": -0.2313918965052979, \"train_returns_over_hodl\": -0.1158956397024139, \"train_sharpe\": 0.06959366506143559, \"validation_return\": -0.1608879396859505, \"validation_returns_over_hodl\": -0.02593367054351725, \"validation_sharpe\": -1.2817611075321345}, {\"centeredness_margin\": 0.06179643733372603, \"continuous_test_metrics\": [{\"annualised_returns\": -0.48139027887016417, \"annualised_returns_over_hodl\": 0.0060450730183270185, \"annualised_returns_over_uniform_hodl\": 0.8304624648997552, \"calmar\": -0.8304293539914267, \"daily_log_sharpe\": -0.6920531609241954, \"daily_returns\": 0.031016041718691413, \"fee_revenue_over_value\": 0.0007801899404488603, \"jax_sharpe\": -0.009983939746914184, \"return\": -0.23236362601900196, \"returns_over_hodl\": 0.002430198623330604, \"returns_over_uniform_hodl\": 0.2756841990838288, \"sharpe\": -0.20350955223921197, \"sterling\": -1.2393615790230663, \"ulcer\": -0.17641031437878024}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -0.08797197517391986, \"optuna_trial_number\": 72, \"price_ratio\": 4.307859531297535, \"shift_exponent\": 0.0010604227558231182, \"step\": 72, \"test_objective\": [{\"annualised_returns\": -0.48139027887016417, \"annualised_returns_over_hodl\": 0.0060450730183270185, \"annualised_returns_over_uniform_hodl\": 0.8304624648997552, \"calmar\": -0.8304293539914267, \"daily_log_sharpe\": -0.6920531609241954, \"daily_returns\": 0.031016041718691413, \"fee_revenue_over_value\": 0.0007801899404488603, \"jax_sharpe\": -0.009983939746914184, \"return\": -0.23236362601900196, \"returns_over_hodl\": 0.002430198623330604, \"returns_over_uniform_hodl\": 0.2756841990838288, \"sharpe\": -0.20350955223921197, \"sterling\": -1.2393615790230663, \"ulcer\": -0.17641031437878024}], \"train_objective\": [{\"annualised_returns\": -0.48363553224949307, \"annualised_returns_over_hodl\": -0.34972251354031925, \"annualised_returns_over_uniform_hodl\": -0.34972251354031925, \"calmar\": -0.6476168832034249, \"daily_log_sharpe\": -0.4609991323880958, \"daily_returns\": 0.008274926889857943, \"fee_revenue_over_value\": 0.03777440960731941, \"jax_sharpe\": 0.2084916811658885, \"return\": -0.33053224883946297, \"returns_over_hodl\": -0.2299334925191433, \"returns_over_uniform_hodl\": -0.2299334925191433, \"sharpe\": 0.14814152366527, \"sterling\": -1.4074851843222838, \"ulcer\": -0.18608200571686198}], \"train_return\": -0.33053224883946297, \"train_returns_over_hodl\": -0.2299334925191433, \"train_sharpe\": 0.2084916811658885, \"validation_return\": -0.3391427125502098, \"validation_returns_over_hodl\": -2.583132485689532e-09, \"validation_sharpe\": -2.2438531790963716}, {\"centeredness_margin\": 0.028844487565883536, \"continuous_test_metrics\": [{\"annualised_returns\": -0.49698897411125764, \"annualised_returns_over_hodl\": -0.024214661551096972, \"annualised_returns_over_uniform_hodl\": 0.7754059841264533, \"calmar\": -0.8573381602301606, \"daily_log_sharpe\": -0.7286913686302536, \"daily_returns\": 0.031016041816095675, \"fee_revenue_over_value\": 0.0019923991835890794, \"jax_sharpe\": -0.04471388092185678, \"return\": -0.24174728535570156, \"returns_over_hodl\": -0.009823604127100594, \"returns_over_uniform_hodl\": 0.26009011528171144, \"sharpe\": -0.243822396773333, \"sterling\": -1.2795212130163438, \"ulcer\": -0.1764103145801382}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -3.8952649210516705, \"optuna_trial_number\": 73, \"price_ratio\": 7.466296381314341, \"shift_exponent\": 0.00014742560736965388, \"step\": 73, \"test_objective\": [{\"annualised_returns\": -0.49698897411125764, \"annualised_returns_over_hodl\": -0.024214661551096972, \"annualised_returns_over_uniform_hodl\": 0.7754059841264533, \"calmar\": -0.8573381602301606, \"daily_log_sharpe\": -0.7286913686302536, \"daily_returns\": 0.031016041816095675, \"fee_revenue_over_value\": 0.0019923991835890794, \"jax_sharpe\": -0.04471388092185678, \"return\": -0.24174728535570156, \"returns_over_hodl\": -0.009823604127100594, \"returns_over_uniform_hodl\": 0.26009011528171144, \"sharpe\": -0.243822396773333, \"sterling\": -1.2795212130163438, \"ulcer\": -0.1764103145801382}], \"train_objective\": [{\"annualised_returns\": -0.4041577781148946, \"annualised_returns_over_hodl\": -0.2496331436941378, \"annualised_returns_over_uniform_hodl\": -0.2496331436941378, \"calmar\": -0.5490141730550299, \"daily_log_sharpe\": -0.37474234011323443, \"daily_returns\": 0.008147492852337715, \"fee_revenue_over_value\": 0.044312284476225774, \"jax_sharpe\": 0.1708672210992975, \"return\": -0.26974021006594084, \"returns_over_hodl\": -0.16000643046154006, \"returns_over_uniform_hodl\": -0.16000643046154006, \"sharpe\": 0.19251454791475625, \"sterling\": -1.2310865641272455, \"ulcer\": -0.17684264135523334}], \"train_return\": -0.26974021006594084, \"train_returns_over_hodl\": -0.16000643046154006, \"train_sharpe\": 0.1708672210992975, \"validation_return\": -0.3342871086563526, \"validation_returns_over_hodl\": -0.048237802856241685, \"validation_sharpe\": -2.200880495871054}, {\"centeredness_margin\": 0.049755445601936024, \"continuous_test_metrics\": [{\"annualised_returns\": -0.5213718397636684, \"annualised_returns_over_hodl\": -0.07151470263209658, \"annualised_returns_over_uniform_hodl\": 0.6893452749939737, \"calmar\": -0.8996392387687514, \"daily_log_sharpe\": -0.7857444107316912, \"daily_returns\": 0.03101604178337578, \"fee_revenue_over_value\": 0.002796523727515512, \"jax_sharpe\": -0.10391751174738442, \"return\": -0.2567700700025858, \"returns_over_hodl\": -0.02944134643672769, \"returns_over_uniform_hodl\": 0.2351247415059976, \"sharpe\": -0.3054388944915651, \"sterling\": -1.3429425832518764, \"ulcer\": -0.17622625043797832}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -4.227025971384258, \"optuna_trial_number\": 74, \"price_ratio\": 5.669395237343768, \"shift_exponent\": 0.0017774806827154355, \"step\": 74, \"test_objective\": [{\"annualised_returns\": -0.5213718397636684, \"annualised_returns_over_hodl\": -0.07151470263209658, \"annualised_returns_over_uniform_hodl\": 0.6893452749939737, \"calmar\": -0.8996392387687514, \"daily_log_sharpe\": -0.7857444107316912, \"daily_returns\": 0.03101604178337578, \"fee_revenue_over_value\": 0.002796523727515512, \"jax_sharpe\": -0.10391751174738442, \"return\": -0.2567700700025858, \"returns_over_hodl\": -0.02944134643672769, \"returns_over_uniform_hodl\": 0.2351247415059976, \"sharpe\": -0.3054388944915651, \"sterling\": -1.3429425832518764, \"ulcer\": -0.17622625043797832}], \"train_objective\": [{\"annualised_returns\": -0.4358978729651307, \"annualised_returns_over_hodl\": -0.28960465681764735, \"annualised_returns_over_uniform_hodl\": -0.28960465681764735, \"calmar\": -0.5897735512073207, \"daily_log_sharpe\": -0.40697992619560647, \"daily_returns\": 0.008233592320978507, \"fee_revenue_over_value\": 0.044013359279413376, \"jax_sharpe\": 0.16231018420842916, \"return\": -0.293610939231978, \"returns_over_hodl\": -0.18746413698742714, \"returns_over_uniform_hodl\": -0.18746413698742714, \"sharpe\": 0.1773308011505235, \"sterling\": -1.304713263739224, \"ulcer\": -0.1802228955371793}], \"train_return\": -0.293610939231978, \"train_returns_over_hodl\": -0.18746413698742714, \"train_sharpe\": 0.16231018420842916, \"validation_return\": -0.33863621738249383, \"validation_returns_over_hodl\": -0.0187030082097851, \"validation_sharpe\": -2.2392771877341593}, {\"centeredness_margin\": 0.040636488155607324, \"continuous_test_metrics\": [{\"annualised_returns\": -0.5365215364920132, \"annualised_returns_over_hodl\": -0.08173374756184715, \"annualised_returns_over_uniform_hodl\": 0.6358735599720586, \"calmar\": -0.9771817228607544, \"daily_log_sharpe\": -0.9410605223545911, \"daily_returns\": 0.030942421721306308, \"fee_revenue_over_value\": 0.21855027219896594, \"jax_sharpe\": -0.3342061885297955, \"return\": -0.266335557359466, \"returns_over_hodl\": -0.03375765981268308, \"returns_over_uniform_hodl\": 0.21922848972414788, \"sharpe\": -0.5210023856015122, \"sterling\": -1.5352660501082684, \"ulcer\": -0.14617532068238523}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -3.3048645532232883, \"optuna_trial_number\": 75, \"price_ratio\": 1.4573658076343314, \"shift_exponent\": 0.023486476572529818, \"step\": 75, \"test_objective\": [{\"annualised_returns\": -0.5365215364920132, \"annualised_returns_over_hodl\": -0.08173374756184715, \"annualised_returns_over_uniform_hodl\": 0.6358735599720586, \"calmar\": -0.9771817228607544, \"daily_log_sharpe\": -0.9410605223545911, \"daily_returns\": 0.030942421721306308, \"fee_revenue_over_value\": 0.21855027219896594, \"jax_sharpe\": -0.3342061885297955, \"return\": -0.266335557359466, \"returns_over_hodl\": -0.03375765981268308, \"returns_over_uniform_hodl\": 0.21922848972414788, \"sharpe\": -0.5210023856015122, \"sterling\": -1.5352660501082684, \"ulcer\": -0.14617532068238523}], \"train_objective\": [{\"annualised_returns\": -0.4588061940083029, \"annualised_returns_over_hodl\": -0.318453980032821, \"annualised_returns_over_uniform_hodl\": -0.3184539800328209, \"calmar\": -0.5975578398171366, \"daily_log_sharpe\": -0.4692548517046605, \"daily_returns\": 0.009346300452998833, \"fee_revenue_over_value\": 0.06497226587394626, \"jax_sharpe\": 0.026396238981057623, \"return\": -0.3111688264778716, \"returns_over_hodl\": -0.20766039123081603, \"returns_over_uniform_hodl\": -0.20766039123081592, \"sharpe\": 0.10065458233675345, \"sterling\": -1.3507701766194882, \"ulcer\": -0.182867703515496}], \"train_return\": -0.3111688264778716, \"train_returns_over_hodl\": -0.20766039123081603, \"train_sharpe\": 0.026396238981057623, \"validation_return\": -0.3071712472645661, \"validation_returns_over_hodl\": -0.06384466193401206, \"validation_sharpe\": -2.2647874815471445}, {\"centeredness_margin\": 0.021353775708220002, \"continuous_test_metrics\": [{\"annualised_returns\": -0.40087635999648497, \"annualised_returns_over_hodl\": 0.1622331039168654, \"annualised_returns_over_uniform_hodl\": 1.1146409143109586, \"calmar\": -0.7241764022156906, \"daily_log_sharpe\": -0.57805775554336, \"daily_returns\": 0.031016041728607932, \"fee_revenue_over_value\": 0.10284785775061545, \"jax_sharpe\": 0.06277596022215831, \"return\": -0.1864252492751276, \"returns_over_hodl\": 0.06241956019035966, \"returns_over_uniform_hodl\": 0.3520261538557259, \"sharpe\": -0.1086488964214422, \"sterling\": -1.043739896057546, \"ulcer\": -0.1724685384259891}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -0.08068019611576338, \"optuna_trial_number\": 76, \"price_ratio\": 3.6976493183094985, \"shift_exponent\": 0.004901114157698789, \"step\": 76, \"test_objective\": [{\"annualised_returns\": -0.40087635999648497, \"annualised_returns_over_hodl\": 0.1622331039168654, \"annualised_returns_over_uniform_hodl\": 1.1146409143109586, \"calmar\": -0.7241764022156906, \"daily_log_sharpe\": -0.57805775554336, \"daily_returns\": 0.031016041728607932, \"fee_revenue_over_value\": 0.10284785775061545, \"jax_sharpe\": 0.06277596022215831, \"return\": -0.1864252492751276, \"returns_over_hodl\": 0.06241956019035966, \"returns_over_uniform_hodl\": 0.3520261538557259, \"sharpe\": -0.1086488964214422, \"sterling\": -1.043739896057546, \"ulcer\": -0.1724685384259891}], \"train_objective\": [{\"annualised_returns\": -0.49227773479870407, \"annualised_returns_over_hodl\": -0.36060596912675547, \"annualised_returns_over_uniform_hodl\": -0.36060596912675535, \"calmar\": -0.6566050548086946, \"daily_log_sharpe\": -0.4698794862120579, \"daily_returns\": 0.008322760264365578, \"fee_revenue_over_value\": 0.04187311070560428, \"jax_sharpe\": 0.14963164438368462, \"return\": -0.3373573635652447, \"returns_over_hodl\": -0.2377841951869386, \"returns_over_uniform_hodl\": -0.2377841951869385, \"sharpe\": 0.1463166817432487, \"sterling\": -1.4213308948946446, \"ulcer\": -0.18769970899737265}], \"train_return\": -0.3373573635652447, \"train_returns_over_hodl\": -0.2377841951869386, \"train_sharpe\": 0.14963164438368462, \"validation_return\": -0.3391427124754891, \"validation_returns_over_hodl\": -2.3692302564271017e-09, \"validation_sharpe\": -2.243853177658603}, {\"centeredness_margin\": 0.03147949518921389, \"continuous_test_metrics\": [{\"annualised_returns\": -0.4773716411000595, \"annualised_returns_over_hodl\": 0.01384078625739682, \"annualised_returns_over_uniform_hodl\": 0.8446464751458025, \"calmar\": -0.823496932000748, \"daily_log_sharpe\": -0.6786540958975562, \"daily_returns\": 0.031016041628375256, \"fee_revenue_over_value\": 0.0004470967435757261, \"jax_sharpe\": 0.010115386212620115, \"return\": -0.22997353652616748, \"returns_over_hodl\": 0.005551336590866374, \"returns_over_uniform_hodl\": 0.27965613098251296, \"sharpe\": -0.18779939052576605, \"sterling\": -1.2290153937266468, \"ulcer\": -0.17641031419206799}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -0.11399503954417234, \"optuna_trial_number\": 77, \"price_ratio\": 3.1082990141490017, \"shift_exponent\": 0.001271590785560862, \"step\": 77, \"test_objective\": [{\"annualised_returns\": -0.4773716411000595, \"annualised_returns_over_hodl\": 0.01384078625739682, \"annualised_returns_over_uniform_hodl\": 0.8446464751458025, \"calmar\": -0.823496932000748, \"daily_log_sharpe\": -0.6786540958975562, \"daily_returns\": 0.031016041628375256, \"fee_revenue_over_value\": 0.0004470967435757261, \"jax_sharpe\": 0.010115386212620115, \"return\": -0.22997353652616748, \"returns_over_hodl\": 0.005551336590866374, \"returns_over_uniform_hodl\": 0.27965613098251296, \"sharpe\": -0.18779939052576605, \"sterling\": -1.2290153937266468, \"ulcer\": -0.17641031419206799}], \"train_objective\": [{\"annualised_returns\": -0.5280461262824045, \"annualised_returns_over_hodl\": -0.40565046998107257, \"annualised_returns_over_uniform_hodl\": -0.40565046998107246, \"calmar\": -0.6965015372056456, \"daily_log_sharpe\": -0.5134790921157543, \"daily_returns\": 0.008402530043841233, \"fee_revenue_over_value\": 0.037368270504078226, \"jax_sharpe\": 0.15114608198487595, \"return\": -0.3661048248389335, \"returns_over_hodl\": -0.2708514446005138, \"returns_over_uniform_hodl\": -0.2708514446005137, \"sharpe\": 0.1233294785931918, \"sterling\": -1.4825400814766212, \"ulcer\": -0.1944010815932401}], \"train_return\": -0.3661048248389335, \"train_returns_over_hodl\": -0.2708514446005138, \"train_sharpe\": 0.15114608198487592, \"validation_return\": -0.3391427120409929, \"validation_returns_over_hodl\": -3.3568120505478305e-09, \"validation_sharpe\": -2.243853180279541}, {\"centeredness_margin\": 0.11766898849015323, \"continuous_test_metrics\": [{\"annualised_returns\": -0.6336223835474721, \"annualised_returns_over_hodl\": -0.28926823910543276, \"annualised_returns_over_uniform_hodl\": 0.2931506054972195, \"calmar\": -1.0930402303423974, \"daily_log_sharpe\": -1.092621518807195, \"daily_returns\": 0.031016041425209785, \"fee_revenue_over_value\": 0.009778603396079125, \"jax_sharpe\": -0.4083115834355911, \"return\": -0.332613084850265, \"returns_over_hodl\": -0.12848215142131758, \"returns_over_uniform_hodl\": 0.10908624342088791, \"sharpe\": -0.6270481901969895, \"sterling\": -1.6312911697309382, \"ulcer\": -0.17641031377206387}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -0.134439503817407, \"optuna_trial_number\": 78, \"price_ratio\": 1.2284884830622822, \"shift_exponent\": 0.0038693594259594805, \"step\": 78, \"test_objective\": [{\"annualised_returns\": -0.6336223835474721, \"annualised_returns_over_hodl\": -0.28926823910543276, \"annualised_returns_over_uniform_hodl\": 0.2931506054972195, \"calmar\": -1.0930402303423974, \"daily_log_sharpe\": -1.092621518807195, \"daily_returns\": 0.031016041425209785, \"fee_revenue_over_value\": 0.009778603396079125, \"jax_sharpe\": -0.4083115834355911, \"return\": -0.332613084850265, \"returns_over_hodl\": -0.12848215142131758, \"returns_over_uniform_hodl\": 0.10908624342088791, \"sharpe\": -0.6270481901969895, \"sterling\": -1.6312911697309382, \"ulcer\": -0.17641031377206387}], \"train_objective\": [{\"annualised_returns\": -0.651702491093914, \"annualised_returns_over_hodl\": -0.5613756507718269, \"annualised_returns_over_uniform_hodl\": -0.5613756507718275, \"calmar\": -0.8176664121160266, \"daily_log_sharpe\": -0.7129170169062001, \"daily_returns\": 0.010463631409476568, \"fee_revenue_over_value\": 0.0042347187040300055, \"jax_sharpe\": 0.07052615693439025, \"return\": -0.4728814824213562, \"returns_over_hodl\": -0.39367308558685876, \"returns_over_uniform_hodl\": -0.3936730855868592, \"sharpe\": -0.03466311821965784, \"sterling\": -1.7469833985885044, \"ulcer\": -0.2077826479616977}], \"train_return\": -0.4728814824213562, \"train_returns_over_hodl\": -0.39367308558685876, \"train_sharpe\": 0.07052615693439025, \"validation_return\": -0.3391427102304585, \"validation_returns_over_hodl\": -3.9732904744127495e-09, \"validation_sharpe\": -2.2438531762328577}, {\"centeredness_margin\": 0.13383526067993673, \"continuous_test_metrics\": [{\"annualised_returns\": -0.45384104550051296, \"annualised_returns_over_hodl\": 0.399813824100806, \"annualised_returns_over_uniform_hodl\": 0.9276990487224592, \"calmar\": -0.8427105837955398, \"daily_log_sharpe\": -0.7864375143772052, \"daily_returns\": 0.025884406317610874, \"fee_revenue_over_value\": 0.34302474654541676, \"jax_sharpe\": -0.21298494527545442, \"return\": -0.2161942423911739, \"returns_over_hodl\": 0.14505942527840854, \"returns_over_uniform_hodl\": 0.30255502999035855, \"sharpe\": -0.38469713198039457, \"sterling\": -1.3427332003593746, \"ulcer\": -0.13829331431242728}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -2.559907268363154, \"optuna_trial_number\": 79, \"price_ratio\": 1.1100462971588725, \"shift_exponent\": 0.1565394470655814, \"step\": 79, \"test_objective\": [{\"annualised_returns\": -0.45384104550051296, \"annualised_returns_over_hodl\": 0.399813824100806, \"annualised_returns_over_uniform_hodl\": 0.9276990487224592, \"calmar\": -0.8427105837955398, \"daily_log_sharpe\": -0.7864375143772052, \"daily_returns\": 0.025884406317610874, \"fee_revenue_over_value\": 0.34302474654541676, \"jax_sharpe\": -0.21298494527545442, \"return\": -0.2161942423911739, \"returns_over_hodl\": 0.14505942527840854, \"returns_over_uniform_hodl\": 0.30255502999035855, \"sharpe\": -0.38469713198039457, \"sterling\": -1.3427332003593746, \"ulcer\": -0.13829331431242728}], \"train_objective\": [{\"annualised_returns\": -0.7061478501821219, \"annualised_returns_over_hodl\": -0.6299407699240247, \"annualised_returns_over_uniform_hodl\": -0.6299407699240249, \"calmar\": -0.8678146403108151, \"daily_log_sharpe\": -1.1401202973436733, \"daily_returns\": 0.012498201773596286, \"fee_revenue_over_value\": 0.14263275320720759, \"jax_sharpe\": -0.7620961461712987, \"return\": -0.5245666263696509, \"returns_over_hodl\": -0.453124789152733, \"returns_over_uniform_hodl\": -0.45312478915273335, \"sharpe\": -0.640895556606412, \"sterling\": -2.0440622526064733, \"ulcer\": -0.191370138349924}], \"train_return\": -0.5245666263696509, \"train_returns_over_hodl\": -0.453124789152733, \"train_sharpe\": -0.7620961461712986, \"validation_return\": -0.24361144147569003, \"validation_returns_over_hodl\": -0.0636196939325766, \"validation_sharpe\": -1.9519572873831486}, {\"centeredness_margin\": 0.04777631345674749, \"continuous_test_metrics\": [{\"annualised_returns\": -0.3834559186430647, \"annualised_returns_over_hodl\": 0.19602682525331505, \"annualised_returns_over_uniform_hodl\": 1.1761273514528505, \"calmar\": -0.6927066305661641, \"daily_log_sharpe\": -0.5499251875130341, \"daily_returns\": 0.031016041601856937, \"fee_revenue_over_value\": 0.11439032148429207, \"jax_sharpe\": 0.08710301454016016, \"return\": -0.17697957727841784, \"returns_over_hodl\": 0.07475434381415536, \"returns_over_uniform_hodl\": 0.3677232924025109, \"sharpe\": -0.07927251644174038, \"sterling\": -0.9931902701982911, \"ulcer\": -0.17423316411063922}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -0.0985314288200998, \"optuna_trial_number\": 80, \"price_ratio\": 2.412012453462943, \"shift_exponent\": 0.004182253939712921, \"step\": 80, \"test_objective\": [{\"annualised_returns\": -0.3834559186430647, \"annualised_returns_over_hodl\": 0.19602682525331505, \"annualised_returns_over_uniform_hodl\": 1.1761273514528505, \"calmar\": -0.6927066305661641, \"daily_log_sharpe\": -0.5499251875130341, \"daily_returns\": 0.031016041601856937, \"fee_revenue_over_value\": 0.11439032148429207, \"jax_sharpe\": 0.08710301454016016, \"return\": -0.17697957727841784, \"returns_over_hodl\": 0.07475434381415536, \"returns_over_uniform_hodl\": 0.3677232924025109, \"sharpe\": -0.07927251644174038, \"sterling\": -0.9931902701982911, \"ulcer\": -0.17423316411063922}], \"train_objective\": [{\"annualised_returns\": -0.5448265829090256, \"annualised_returns_over_hodl\": -0.4267827396051651, \"annualised_returns_over_uniform_hodl\": -0.4267827396051651, \"calmar\": -0.7102075900036274, \"daily_log_sharpe\": -0.5401506645547518, \"daily_returns\": 0.008545608179390153, \"fee_revenue_over_value\": 0.03772013695191922, \"jax_sharpe\": 0.16034807142193302, \"return\": -0.3798855087377726, \"returns_over_hodl\": -0.2867029073517523, \"returns_over_uniform_hodl\": -0.2867029073517523, \"sharpe\": 0.10002567358472937, \"sterling\": -1.525265191955459, \"ulcer\": -0.19447384730104572}], \"train_return\": -0.3798855087377726, \"train_returns_over_hodl\": -0.2867029073517523, \"train_sharpe\": 0.16034807142193305, \"validation_return\": -0.33914271146974695, \"validation_returns_over_hodl\": -2.8990332356926274e-09, \"validation_sharpe\": -2.2438531763816143}, {\"centeredness_margin\": 0.4324116898486964, \"continuous_test_metrics\": [{\"annualised_returns\": -0.6679548523317909, \"annualised_returns_over_hodl\": 0.09650317006934661, \"annualised_returns_over_uniform_hodl\": 0.1719722070280847, \"calmar\": -1.1631181425551196, \"daily_log_sharpe\": -1.4166541246219853, \"daily_returns\": 0.019432247379301024, \"fee_revenue_over_value\": 0.05828202796688627, \"jax_sharpe\": -0.886148390847413, \"return\": -0.35854236676621765, \"returns_over_hodl\": 0.03779959447359893, \"returns_over_uniform_hodl\": 0.06599608204379082, \"sharpe\": -1.0297280592342861, \"sterling\": -2.0411699216597445, \"ulcer\": -0.14842789740872808}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -2.576359005436441, \"optuna_trial_number\": 81, \"price_ratio\": 198.04019948584045, \"shift_exponent\": 3.5380177159006267, \"step\": 81, \"test_objective\": [{\"annualised_returns\": -0.6679548523317909, \"annualised_returns_over_hodl\": 0.09650317006934661, \"annualised_returns_over_uniform_hodl\": 0.1719722070280847, \"calmar\": -1.1631181425551196, \"daily_log_sharpe\": -1.4166541246219853, \"daily_returns\": 0.019432247379301024, \"fee_revenue_over_value\": 0.05828202796688627, \"jax_sharpe\": -0.886148390847413, \"return\": -0.35854236676621765, \"returns_over_hodl\": 0.03779959447359893, \"returns_over_uniform_hodl\": 0.06599608204379082, \"sharpe\": -1.0297280592342861, \"sterling\": -2.0411699216597445, \"ulcer\": -0.14842789740872808}], \"train_objective\": [{\"annualised_returns\": -0.3104890849141947, \"annualised_returns_over_hodl\": -0.1316725825426983, \"annualised_returns_over_uniform_hodl\": -0.1316725825426983, \"calmar\": -0.4256099970534433, \"daily_log_sharpe\": -0.3075647186175322, \"daily_returns\": 0.00798871641164979, \"fee_revenue_over_value\": 0.025715404662967074, \"jax_sharpe\": 0.15780144530832443, \"return\": -0.20205147433284476, \"returns_over_hodl\": -0.0821463270713163, \"returns_over_uniform_hodl\": -0.0821463270713163, \"sharpe\": 0.19088652303035447, \"sterling\": -1.0184960606691886, \"ulcer\": -0.1643556822536155}], \"train_return\": -0.20205147433284476, \"train_returns_over_hodl\": -0.0821463270713163, \"train_sharpe\": 0.1578014453083244, \"validation_return\": -0.17475791650104877, \"validation_returns_over_hodl\": -0.023450365501774817, \"validation_sharpe\": -1.3730574232930943}, {\"centeredness_margin\": 0.12338951147008453, \"continuous_test_metrics\": [{\"annualised_returns\": -0.536173253939488, \"annualised_returns_over_hodl\": 0.11992618473817496, \"annualised_returns_over_uniform_hodl\": 0.6371028430217232, \"calmar\": -0.9988663207211926, \"daily_log_sharpe\": -0.9378044115264536, \"daily_returns\": 0.026290792181250522, \"fee_revenue_over_value\": 0.12477378578317787, \"jax_sharpe\": -0.32402571749372766, \"return\": -0.26611357185343487, \"returns_over_hodl\": 0.04667157857312776, \"returns_over_uniform_hodl\": 0.2195973927778172, \"sharpe\": -0.5190001376313405, \"sterling\": -1.4952643668326344, \"ulcer\": -0.15378786211752432}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -2.7408801053164265, \"optuna_trial_number\": 82, \"price_ratio\": 3.661966894117942, \"shift_exponent\": 0.07888860924146675, \"step\": 82, \"test_objective\": [{\"annualised_returns\": -0.536173253939488, \"annualised_returns_over_hodl\": 0.11992618473817496, \"annualised_returns_over_uniform_hodl\": 0.6371028430217232, \"calmar\": -0.9988663207211926, \"daily_log_sharpe\": -0.9378044115264536, \"daily_returns\": 0.026290792181250522, \"fee_revenue_over_value\": 0.12477378578317787, \"jax_sharpe\": -0.32402571749372766, \"return\": -0.26611357185343487, \"returns_over_hodl\": 0.04667157857312776, \"returns_over_uniform_hodl\": 0.2195973927778172, \"sharpe\": -0.5190001376313405, \"sterling\": -1.4952643668326344, \"ulcer\": -0.15378786211752432}], \"train_objective\": [{\"annualised_returns\": -0.42325649605710813, \"annualised_returns_over_hodl\": -0.27368488829261706, \"annualised_returns_over_uniform_hodl\": -0.2736848882926173, \"calmar\": -0.5657824201297948, \"daily_log_sharpe\": -0.4253632429544299, \"daily_returns\": 0.008326468585261981, \"fee_revenue_over_value\": 0.051811872800092446, \"jax_sharpe\": 0.15474442349818823, \"return\": -0.2840420879820412, \"returns_over_hodl\": -0.17645740537135002, \"returns_over_uniform_hodl\": -0.17645740537135013, \"sharpe\": 0.12644389736607958, \"sterling\": -1.2911858408672747, \"ulcer\": -0.17652852851663348}], \"train_return\": -0.2840420879820412, \"train_returns_over_hodl\": -0.17645740537135002, \"train_sharpe\": 0.15474442349818823, \"validation_return\": -0.26817049254596903, \"validation_returns_over_hodl\": -0.02942050934485896, \"validation_sharpe\": -1.9809997995275734}, {\"centeredness_margin\": 0.13862051227915345, \"continuous_test_metrics\": [{\"annualised_returns\": -0.5832062203393436, \"annualised_returns_over_hodl\": 0.09098279663748299, \"annualised_returns_over_uniform_hodl\": 0.47109731690033163, \"calmar\": -1.0472265175786641, \"daily_log_sharpe\": -1.0719282747836987, \"daily_returns\": 0.024072656155279686, \"fee_revenue_over_value\": 0.07413108298412802, \"jax_sharpe\": -0.47237345053830104, \"return\": -0.2970443871660128, \"returns_over_hodl\": 0.035692186151829564, \"returns_over_uniform_hodl\": 0.16819551332491312, \"sharpe\": -0.6591369876623664, \"sterling\": -1.659808450399594, \"ulcer\": -0.15482832388337694}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -3.0920093099699133, \"optuna_trial_number\": 83, \"price_ratio\": 25.96546868474812, \"shift_exponent\": 0.011645711802039252, \"step\": 83, \"test_objective\": [{\"annualised_returns\": -0.5832062203393436, \"annualised_returns_over_hodl\": 0.09098279663748299, \"annualised_returns_over_uniform_hodl\": 0.47109731690033163, \"calmar\": -1.0472265175786641, \"daily_log_sharpe\": -1.0719282747836987, \"daily_returns\": 0.024072656155279686, \"fee_revenue_over_value\": 0.07413108298412802, \"jax_sharpe\": -0.47237345053830104, \"return\": -0.2970443871660128, \"returns_over_hodl\": 0.035692186151829564, \"returns_over_uniform_hodl\": 0.16819551332491312, \"sharpe\": -0.6591369876623664, \"sterling\": -1.659808450399594, \"ulcer\": -0.15482832388337694}], \"train_objective\": [{\"annualised_returns\": -0.3485839870808024, \"annualised_returns_over_hodl\": -0.1796469471145815, \"annualised_returns_over_uniform_hodl\": -0.1796469471145815, \"calmar\": -0.47654853284504556, \"daily_log_sharpe\": -0.334176517973921, \"daily_returns\": 0.008066132920679106, \"fee_revenue_over_value\": 0.033085625788735896, \"jax_sharpe\": 0.16337902090026105, \"return\": -0.229115218234325, \"returns_over_hodl\": -0.11327685234223661, \"returns_over_uniform_hodl\": -0.11327685234223661, \"sharpe\": 0.19204339578527277, \"sterling\": -1.108278440089125, \"ulcer\": -0.1691355819043681}], \"train_return\": -0.229115218234325, \"train_returns_over_hodl\": -0.11327685234223661, \"train_sharpe\": 0.16337902090026105, \"validation_return\": -0.24129405284256733, \"validation_returns_over_hodl\": -0.03229994160039207, \"validation_sharpe\": -1.8287587795786189}, {\"centeredness_margin\": 0.011554818265196519, \"continuous_test_metrics\": [{\"annualised_returns\": -0.49520591916340495, \"annualised_returns_over_hodl\": 0.1765861637832815, \"annualised_returns_over_uniform_hodl\": 0.7816993778325876, \"calmar\": -0.912105875528201, \"daily_log_sharpe\": -0.8009264534501624, \"daily_returns\": 0.026952155625204244, \"fee_revenue_over_value\": 0.08615729483523149, \"jax_sharpe\": -0.1675680416035119, \"return\": -0.24066593978988682, \"returns_over_hodl\": 0.0676842840374654, \"returns_over_uniform_hodl\": 0.26188713207093084, \"sharpe\": -0.36132596082365015, \"sterling\": -1.3452534407381538, \"ulcer\": -0.16125034458042103}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -2.546704878298128, \"optuna_trial_number\": 84, \"price_ratio\": 28.694880686975104, \"shift_exponent\": 0.16814868229173746, \"step\": 84, \"test_objective\": [{\"annualised_returns\": -0.49520591916340495, \"annualised_returns_over_hodl\": 0.1765861637832815, \"annualised_returns_over_uniform_hodl\": 0.7816993778325876, \"calmar\": -0.912105875528201, \"daily_log_sharpe\": -0.8009264534501624, \"daily_returns\": 0.026952155625204244, \"fee_revenue_over_value\": 0.08615729483523149, \"jax_sharpe\": -0.1675680416035119, \"return\": -0.24066593978988682, \"returns_over_hodl\": 0.0676842840374654, \"returns_over_uniform_hodl\": 0.26188713207093084, \"sharpe\": -0.36132596082365015, \"sterling\": -1.3452534407381538, \"ulcer\": -0.16125034458042103}], \"train_objective\": [{\"annualised_returns\": -0.34563940494319656, \"annualised_returns_over_hodl\": -0.17593872241922592, \"annualised_returns_over_uniform_hodl\": -0.17593872241922592, \"calmar\": -0.4726588795749604, \"daily_log_sharpe\": -0.33154600812624513, \"daily_returns\": 0.00806398732091169, \"fee_revenue_over_value\": 0.032564121466035066, \"jax_sharpe\": 0.16370652757973564, \"return\": -0.22700150899257998, \"returns_over_hodl\": -0.11084552284085425, \"returns_over_uniform_hodl\": -0.11084552284085425, \"sharpe\": 0.19290577965667255, \"sterling\": -1.1011053272634455, \"ulcer\": -0.1688105597484512}], \"train_return\": -0.22700150899257998, \"train_returns_over_hodl\": -0.11084552284085425, \"train_sharpe\": 0.1637065275797356, \"validation_return\": -0.2486330527146331, \"validation_returns_over_hodl\": -0.04510041147145538, \"validation_sharpe\": -1.7893444041671278}, {\"centeredness_margin\": 0.07543422689680936, \"continuous_test_metrics\": [{\"annualised_returns\": -0.5135761707424177, \"annualised_returns_over_hodl\": 0.03027743023963736, \"annualised_returns_over_uniform_hodl\": 0.716860531555485, \"calmar\": -0.9373704399690692, \"daily_log_sharpe\": -0.8388129284653755, \"daily_returns\": 0.028922000938701058, \"fee_revenue_over_value\": 0.08911215765120926, \"jax_sharpe\": -0.20667997501979163, \"return\": -0.2519182863876406, \"returns_over_hodl\": 0.012085355211174598, \"returns_over_uniform_hodl\": 0.24318760030835107, \"sharpe\": -0.39870062126149935, \"sterling\": -1.3912906964515221, \"ulcer\": -0.16192878314569742}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -3.205637755311758, \"optuna_trial_number\": 85, \"price_ratio\": 9.016510716909263, \"shift_exponent\": 0.007346879934466835, \"step\": 85, \"test_objective\": [{\"annualised_returns\": -0.5135761707424177, \"annualised_returns_over_hodl\": 0.03027743023963736, \"annualised_returns_over_uniform_hodl\": 0.716860531555485, \"calmar\": -0.9373704399690692, \"daily_log_sharpe\": -0.8388129284653755, \"daily_returns\": 0.028922000938701058, \"fee_revenue_over_value\": 0.08911215765120926, \"jax_sharpe\": -0.20667997501979163, \"return\": -0.2519182863876406, \"returns_over_hodl\": 0.012085355211174598, \"returns_over_uniform_hodl\": 0.24318760030835107, \"sharpe\": -0.39870062126149935, \"sterling\": -1.3912906964515221, \"ulcer\": -0.16192878314569742}], \"train_objective\": [{\"annualised_returns\": -0.3932990130047619, \"annualised_returns_over_hodl\": -0.2359582862574231, \"annualised_returns_over_uniform_hodl\": -0.2359582862574231, \"calmar\": -0.534998991759933, \"daily_log_sharpe\": -0.37105960753430905, \"daily_returns\": 0.00814408884218817, \"fee_revenue_over_value\": 0.04131842634649457, \"jax_sharpe\": 0.16528680714488128, \"return\": -0.26168906598255737, \"returns_over_hodl\": -0.1507454669651377, \"returns_over_uniform_hodl\": -0.1507454669651377, \"sharpe\": 0.1859198790950103, \"sterling\": -1.2095150243757624, \"ulcer\": -0.17504709781709285}], \"train_return\": -0.26168906598255737, \"train_returns_over_hodl\": -0.1507454669651377, \"train_sharpe\": 0.16528680714488125, \"validation_return\": -0.29640305130493383, \"validation_returns_over_hodl\": -0.037135166893668914, \"validation_sharpe\": -2.1047219638081804}, {\"centeredness_margin\": 0.03221643309614838, \"continuous_test_metrics\": [{\"annualised_returns\": -0.5054055149790626, \"annualised_returns_over_hodl\": -0.040541807292527654, \"annualised_returns_over_uniform_hodl\": 0.745699325120432, \"calmar\": -0.8724314779063052, \"daily_log_sharpe\": -0.7565567593729772, \"daily_returns\": 0.031016041671547895, \"fee_revenue_over_value\": 0.02447158916354016, \"jax_sharpe\": -0.08858675109131646, \"return\": -0.24688271337696233, \"returns_over_hodl\": -0.016529782341303423, \"returns_over_uniform_hodl\": 0.2515558865709484, \"sharpe\": -0.2768874013178327, \"sterling\": -1.302373153281648, \"ulcer\": -0.1760632023041218}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -0.09159336084435876, \"optuna_trial_number\": 86, \"price_ratio\": 3.3006544773742523, \"shift_exponent\": 0.0032797333161368883, \"step\": 86, \"test_objective\": [{\"annualised_returns\": -0.5054055149790626, \"annualised_returns_over_hodl\": -0.040541807292527654, \"annualised_returns_over_uniform_hodl\": 0.745699325120432, \"calmar\": -0.8724314779063052, \"daily_log_sharpe\": -0.7565567593729772, \"daily_returns\": 0.031016041671547895, \"fee_revenue_over_value\": 0.02447158916354016, \"jax_sharpe\": -0.08858675109131646, \"return\": -0.24688271337696233, \"returns_over_hodl\": -0.016529782341303423, \"returns_over_uniform_hodl\": 0.2515558865709484, \"sharpe\": -0.2768874013178327, \"sterling\": -1.302373153281648, \"ulcer\": -0.1760632023041218}], \"train_objective\": [{\"annualised_returns\": -0.5062828607296961, \"annualised_returns_over_hodl\": -0.3782431588575508, \"annualised_returns_over_uniform_hodl\": -0.3782431588575508, \"calmar\": -0.6720463928940771, \"daily_log_sharpe\": -0.48604547933882025, \"daily_returns\": 0.008369230186833047, \"fee_revenue_over_value\": 0.04157175587407347, \"jax_sharpe\": 0.15233892824169085, \"return\": -0.3485155373796567, \"returns_over_hodl\": -0.2506190717351118, \"returns_over_uniform_hodl\": -0.2506190717351118, \"sharpe\": 0.13911103621598114, \"sterling\": -1.4464360944766084, \"ulcer\": -0.1901688448749396}], \"train_return\": -0.3485155373796567, \"train_returns_over_hodl\": -0.2506190717351118, \"train_sharpe\": 0.15233892824169087, \"validation_return\": -0.3391427121016396, \"validation_returns_over_hodl\": -2.6915846218500405e-09, \"validation_sharpe\": -2.24385317788275}, {\"centeredness_margin\": 0.01582044207377524, \"continuous_test_metrics\": [{\"annualised_returns\": -0.4245409501389966, \"annualised_returns_over_hodl\": 0.11632643561561573, \"annualised_returns_over_uniform_hodl\": 1.0311153993847562, \"calmar\": -0.7669261457556058, \"daily_log_sharpe\": -0.6164311923717729, \"daily_returns\": 0.031016041685890033, \"fee_revenue_over_value\": 0.08401233792356214, \"jax_sharpe\": 0.033374014211754864, \"return\": -0.19952323282447093, \"returns_over_hodl\": 0.04531534943306692, \"returns_over_uniform_hodl\": 0.33025947991985705, \"sharpe\": -0.14602082182449933, \"sterling\": -1.105231625853066, \"ulcer\": -0.17263411072288426}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -3.7840717908177193, \"optuna_trial_number\": 87, \"price_ratio\": 7.153215583678692, \"shift_exponent\": 0.0036094282568483634, \"step\": 87, \"test_objective\": [{\"annualised_returns\": -0.4245409501389966, \"annualised_returns_over_hodl\": 0.11632643561561573, \"annualised_returns_over_uniform_hodl\": 1.0311153993847562, \"calmar\": -0.7669261457556058, \"daily_log_sharpe\": -0.6164311923717729, \"daily_returns\": 0.031016041685890033, \"fee_revenue_over_value\": 0.08401233792356214, \"jax_sharpe\": 0.033374014211754864, \"return\": -0.19952323282447093, \"returns_over_hodl\": 0.04531534943306692, \"returns_over_uniform_hodl\": 0.33025947991985705, \"sharpe\": -0.14602082182449933, \"sterling\": -1.105231625853066, \"ulcer\": -0.17263411072288426}], \"train_objective\": [{\"annualised_returns\": -0.4073210404114196, \"annualised_returns_over_hodl\": -0.25361676066173844, \"annualised_returns_over_uniform_hodl\": -0.25361676066173844, \"calmar\": -0.5531586514366645, \"daily_log_sharpe\": -0.3772615937766392, \"daily_returns\": 0.008194355219338144, \"fee_revenue_over_value\": 0.04502509347471151, \"jax_sharpe\": 0.17166116911577814, \"return\": -0.2720964019010147, \"returns_over_hodl\": -0.16271667963223557, \"returns_over_uniform_hodl\": -0.16271667963223557, \"sharpe\": 0.19240697640027785, \"sterling\": -1.2377807043012303, \"ulcer\": -0.17724937775226232}], \"train_return\": -0.2720964019010147, \"train_returns_over_hodl\": -0.16271667963223557, \"train_sharpe\": 0.17166116911577814, \"validation_return\": -0.33421729954114443, \"validation_returns_over_hodl\": -0.042598149898622095, \"validation_sharpe\": -2.2087521868048623}, {\"centeredness_margin\": 0.03596347843205831, \"continuous_test_metrics\": [{\"annualised_returns\": -0.4823829269874088, \"annualised_returns_over_hodl\": 0.08529052223731215, \"annualised_returns_over_uniform_hodl\": 0.826958856993002, \"calmar\": -0.8795228808258136, \"daily_log_sharpe\": -0.7675256835095015, \"daily_returns\": 0.029305749949427702, \"fee_revenue_over_value\": 0.11386432753591626, \"jax_sharpe\": -0.14575532398512997, \"return\": -0.23295570692734902, \"returns_over_hodl\": 0.03351247429783499, \"returns_over_uniform_hodl\": 0.2747002589202845, \"sharpe\": -0.3242689588121964, \"sterling\": -1.307010227244403, \"ulcer\": -0.16063833610366873}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -3.0316940261921155, \"optuna_trial_number\": 88, \"price_ratio\": 5.559054207593046, \"shift_exponent\": 0.027565634244931636, \"step\": 88, \"test_objective\": [{\"annualised_returns\": -0.4823829269874088, \"annualised_returns_over_hodl\": 0.08529052223731215, \"annualised_returns_over_uniform_hodl\": 0.826958856993002, \"calmar\": -0.8795228808258136, \"daily_log_sharpe\": -0.7675256835095015, \"daily_returns\": 0.029305749949427702, \"fee_revenue_over_value\": 0.11386432753591626, \"jax_sharpe\": -0.14575532398512997, \"return\": -0.23295570692734902, \"returns_over_hodl\": 0.03351247429783499, \"returns_over_uniform_hodl\": 0.2747002589202845, \"sharpe\": -0.3242689588121964, \"sterling\": -1.307010227244403, \"ulcer\": -0.16063833610366873}], \"train_objective\": [{\"annualised_returns\": -0.42957055740337824, \"annualised_returns_over_hodl\": -0.28163642678536405, \"annualised_returns_over_uniform_hodl\": -0.28163642678536394, \"calmar\": -0.5810388440754617, \"daily_log_sharpe\": -0.40685021596212034, \"daily_returns\": 0.008186902262085434, \"fee_revenue_over_value\": 0.04819129723301975, \"jax_sharpe\": 0.21172556525727065, \"return\": -0.28881108287249135, \"returns_over_hodl\": -0.18194302171824184, \"returns_over_uniform_hodl\": -0.18194302171824173, \"sharpe\": 0.17387980120952706, \"sterling\": -1.287884908506193, \"ulcer\": -0.17986085941784397}], \"train_return\": -0.28881108287249135, \"train_returns_over_hodl\": -0.18194302171824184, \"train_sharpe\": 0.21172556525727065, \"validation_return\": -0.3069243859431917, \"validation_returns_over_hodl\": -0.02826206401392828, \"validation_sharpe\": -2.165713404346728}, {\"centeredness_margin\": 0.04784416371676352, \"continuous_test_metrics\": [{\"annualised_returns\": -0.48418682830596627, \"annualised_returns_over_hodl\": 0.0006200828578530881, \"annualised_returns_over_uniform_hodl\": 0.8205918848375073, \"calmar\": -0.8352535906661669, \"daily_log_sharpe\": -0.6917949993912721, \"daily_returns\": 0.03101604161741581, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": 0.005434065894422416, \"return\": -0.23403341127334532, \"returns_over_hodl\": 0.00024968459162622025, \"returns_over_uniform_hodl\": 0.2729092932338337, \"sharpe\": -0.20138739853275764, \"sterling\": -1.2465614392543078, \"ulcer\": -0.1764103141694149}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -0.11832379211087818, \"optuna_trial_number\": 89, \"price_ratio\": 3.160488495241876, \"shift_exponent\": 0.0004889803146298093, \"step\": 89, \"test_objective\": [{\"annualised_returns\": -0.48418682830596627, \"annualised_returns_over_hodl\": 0.0006200828578530881, \"annualised_returns_over_uniform_hodl\": 0.8205918848375073, \"calmar\": -0.8352535906661669, \"daily_log_sharpe\": -0.6917949993912721, \"daily_returns\": 0.03101604161741581, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": 0.005434065894422416, \"return\": -0.23403341127334532, \"returns_over_hodl\": 0.00024968459162622025, \"returns_over_uniform_hodl\": 0.2729092932338337, \"sharpe\": -0.20138739853275764, \"sterling\": -1.2465614392543078, \"ulcer\": -0.1764103141694149}], \"train_objective\": [{\"annualised_returns\": -0.5332231927585038, \"annualised_returns_over_hodl\": -0.41217014742901625, \"annualised_returns_over_uniform_hodl\": -0.41217014742901614, \"calmar\": -0.7025697212090138, \"daily_log_sharpe\": -0.5210446607199168, \"daily_returns\": 0.008385729156284041, \"fee_revenue_over_value\": 0.03347610527947947, \"jax_sharpe\": 0.14674425045871714, \"return\": -0.37033556909721665, \"returns_over_hodl\": -0.27571792913150583, \"returns_over_uniform_hodl\": -0.2757179291315057, \"sharpe\": 0.11740932695516797, \"sterling\": -1.493065988425364, \"ulcer\": -0.19522861123784052}], \"train_return\": -0.37033556909721665, \"train_returns_over_hodl\": -0.27571792913150583, \"train_sharpe\": 0.14674425045871714, \"validation_return\": -0.3391427120291062, \"validation_returns_over_hodl\": -3.4856432185037534e-09, \"validation_sharpe\": -2.243853180920614}, {\"centeredness_margin\": 0.01998812350236722, \"continuous_test_metrics\": [{\"annualised_returns\": -0.41402326243739884, \"annualised_returns_over_hodl\": 0.13672958548841896, \"annualised_returns_over_uniform_hodl\": 1.0682381754742, \"calmar\": -0.7479260945712245, \"daily_log_sharpe\": -0.6184605432068766, \"daily_returns\": 0.031016041501743745, \"fee_revenue_over_value\": 0.13482692987165318, \"jax_sharpe\": 0.009741195029651574, \"return\": -0.19366289021934435, \"returns_over_hodl\": 0.05296817412641164, \"returns_over_uniform_hodl\": 0.3399983963078428, \"sharpe\": -0.16065393668354405, \"sterling\": -1.0985713311515486, \"ulcer\": -0.16799186753464335}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": 0.6217044074671498, \"optuna_trial_number\": 90, \"price_ratio\": 2.6039658211213697, \"shift_exponent\": 0.006778711235718485, \"step\": 90, \"test_objective\": [{\"annualised_returns\": -0.41402326243739884, \"annualised_returns_over_hodl\": 0.13672958548841896, \"annualised_returns_over_uniform_hodl\": 1.0682381754742, \"calmar\": -0.7479260945712245, \"daily_log_sharpe\": -0.6184605432068766, \"daily_returns\": 0.031016041501743745, \"fee_revenue_over_value\": 0.13482692987165318, \"jax_sharpe\": 0.009741195029651574, \"return\": -0.19366289021934435, \"returns_over_hodl\": 0.05296817412641164, \"returns_over_uniform_hodl\": 0.3399983963078428, \"sharpe\": -0.16065393668354405, \"sterling\": -1.0985713311515486, \"ulcer\": -0.16799186753464335}], \"train_objective\": [{\"annualised_returns\": -0.5291216725919521, \"annualised_returns_over_hodl\": -0.4070049465076825, \"annualised_returns_over_uniform_hodl\": -0.4070049465076824, \"calmar\": -0.6934707526564199, \"daily_log_sharpe\": -0.5205113027400106, \"daily_returns\": 0.008504029168856191, \"fee_revenue_over_value\": 0.041028786623466665, \"jax_sharpe\": 0.12874543265361987, \"return\": -0.36698226421344504, \"returns_over_hodl\": -0.2718607339553556, \"returns_over_uniform_hodl\": -0.2718607339553555, \"sharpe\": 0.11052981455998245, \"sterling\": -1.4942811114167027, \"ulcer\": -0.1926062897135507}], \"train_return\": -0.36698226421344504, \"train_returns_over_hodl\": -0.2718607339553556, \"train_sharpe\": 0.1287454326536199, \"validation_return\": -0.3388865034120332, \"validation_returns_over_hodl\": 0.00038768845319725465, \"validation_sharpe\": -2.243055359070456}, {\"centeredness_margin\": 0.030298904746931952, \"continuous_test_metrics\": [{\"annualised_returns\": -0.5200996263962381, \"annualised_returns_over_hodl\": -0.06904674572468472, \"annualised_returns_over_uniform_hodl\": 0.6938356243290218, \"calmar\": -0.8973567048378754, \"daily_log_sharpe\": -0.787178931619343, \"daily_returns\": 0.03101604165882255, \"fee_revenue_over_value\": 0.010908732476287982, \"jax_sharpe\": -0.11375467388697194, \"return\": -0.2559750776780588, \"returns_over_hodl\": -0.028403191043861242, \"returns_over_uniform_hodl\": 0.23644588675284473, \"sharpe\": -0.3082231111124831, \"sterling\": -1.3393539331464464, \"ulcer\": -0.1763151514266748}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -0.09468316842743374, \"optuna_trial_number\": 91, \"price_ratio\": 3.203239037916974, \"shift_exponent\": 0.0031284557873022733, \"step\": 91, \"test_objective\": [{\"annualised_returns\": -0.5200996263962381, \"annualised_returns_over_hodl\": -0.06904674572468472, \"annualised_returns_over_uniform_hodl\": 0.6938356243290218, \"calmar\": -0.8973567048378754, \"daily_log_sharpe\": -0.787178931619343, \"daily_returns\": 0.03101604165882255, \"fee_revenue_over_value\": 0.010908732476287982, \"jax_sharpe\": -0.11375467388697194, \"return\": -0.2559750776780588, \"returns_over_hodl\": -0.028403191043861242, \"returns_over_uniform_hodl\": 0.23644588675284473, \"sharpe\": -0.3082231111124831, \"sterling\": -1.3393539331464464, \"ulcer\": -0.1763151514266748}], \"train_objective\": [{\"annualised_returns\": -0.513126579390506, \"annualised_returns_over_hodl\": -0.3868617150261734, \"annualised_returns_over_uniform_hodl\": -0.3868617150261733, \"calmar\": -0.6795379170357662, \"daily_log_sharpe\": -0.4954382764530413, \"daily_returns\": 0.008377919327542128, \"fee_revenue_over_value\": 0.040377464786536976, \"jax_sharpe\": 0.14909604772622284, \"return\": -0.3540132515016432, \"returns_over_hodl\": -0.2569429095984104, \"returns_over_uniform_hodl\": -0.25694290959841026, \"sharpe\": 0.13234282714090315, \"sterling\": -1.4606983035225118, \"ulcer\": -0.19104235046923035}], \"train_return\": -0.3540132515016432, \"train_returns_over_hodl\": -0.2569429095984104, \"train_sharpe\": 0.14909604772622284, \"validation_return\": -0.33914271202180113, \"validation_returns_over_hodl\": -2.7832390836479703e-09, \"validation_sharpe\": -2.2438531779602813}, {\"centeredness_margin\": 0.049215335826156614, \"continuous_test_metrics\": [{\"annualised_returns\": -0.4845064789797403, \"annualised_returns_over_hodl\": 3.664402115077792e-11, \"annualised_returns_over_uniform_hodl\": 0.8194636596300258, \"calmar\": -0.8358050094623986, \"daily_log_sharpe\": -0.6924636618271424, \"daily_returns\": 0.03101604152265254, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": 0.005019206290565611, \"return\": -0.2342246147706658, \"returns_over_hodl\": 1.475797262173728e-11, \"returns_over_uniform_hodl\": 0.2725915447677514, \"sharpe\": -0.20210860583798812, \"sterling\": -1.2473843977125783, \"ulcer\": -0.1764103139735071}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -0.14482554096348754, \"optuna_trial_number\": 92, \"price_ratio\": 2.25953804035452, \"shift_exponent\": 0.00045098837194525496, \"step\": 92, \"test_objective\": [{\"annualised_returns\": -0.4845064789797403, \"annualised_returns_over_hodl\": 3.664402115077792e-11, \"annualised_returns_over_uniform_hodl\": 0.8194636596300258, \"calmar\": -0.8358050094623986, \"daily_log_sharpe\": -0.6924636618271424, \"daily_returns\": 0.03101604152265254, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": 0.005019206290565611, \"return\": -0.2342246147706658, \"returns_over_hodl\": 1.475797262173728e-11, \"returns_over_uniform_hodl\": 0.2725915447677514, \"sharpe\": -0.20210860583798812, \"sterling\": -1.2473843977125783, \"ulcer\": -0.1764103139735071}], \"train_objective\": [{\"annualised_returns\": -0.5923718425489223, \"annualised_returns_over_hodl\": -0.486658300110702, \"annualised_returns_over_uniform_hodl\": -0.4866583001107021, \"calmar\": -0.7666233825527167, \"daily_log_sharpe\": -0.6055505971743946, \"daily_returns\": 0.008559371840970779, \"fee_revenue_over_value\": 0.03015551361662131, \"jax_sharpe\": 0.12421593204750447, \"return\": -0.4200600235137929, \"returns_over_hodl\": -0.33291431668353544, \"returns_over_uniform_hodl\": -0.33291431668353555, \"sharpe\": 0.05876557317506698, \"sterling\": -1.620643411729961, \"ulcer\": -0.20274766854926987}], \"train_return\": -0.4200600235137929, \"train_returns_over_hodl\": -0.33291431668353544, \"train_sharpe\": 0.12421593204750445, \"validation_return\": -0.3391427115095318, \"validation_returns_over_hodl\": -4.281734744537857e-09, \"validation_sharpe\": -2.2438531822951835}, {\"centeredness_margin\": 0.042009899114397266, \"continuous_test_metrics\": [{\"annualised_returns\": -0.4845064798046479, \"annualised_returns_over_hodl\": 3.781575053096731e-11, \"annualised_returns_over_uniform_hodl\": 0.8194636567184679, \"calmar\": -0.8358050107840762, \"daily_log_sharpe\": -0.6924636638984464, \"daily_returns\": 0.031016041483454732, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": 0.00501920381079903, \"return\": -0.23422461526418714, \"returns_over_hodl\": 1.5229817407202972e-11, \"returns_over_uniform_hodl\": 0.272591543947601, \"sharpe\": -0.20210860826754487, \"sterling\": -1.2473844004769659, \"ulcer\": -0.17641031389247025}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -0.15217702690015966, \"optuna_trial_number\": 93, \"price_ratio\": 1.91687609195212, \"shift_exponent\": 0.0010039204355813114, \"step\": 93, \"test_objective\": [{\"annualised_returns\": -0.4845064798046479, \"annualised_returns_over_hodl\": 3.781575053096731e-11, \"annualised_returns_over_uniform_hodl\": 0.8194636567184679, \"calmar\": -0.8358050107840762, \"daily_log_sharpe\": -0.6924636638984464, \"daily_returns\": 0.031016041483454732, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": 0.00501920381079903, \"return\": -0.23422461526418714, \"returns_over_hodl\": 1.5229817407202972e-11, \"returns_over_uniform_hodl\": 0.272591543947601, \"sharpe\": -0.20210860826754487, \"sterling\": -1.2473844004769659, \"ulcer\": -0.17641031389247025}], \"train_objective\": [{\"annualised_returns\": -0.6097668392669665, \"annualised_returns_over_hodl\": -0.5085644835320013, \"annualised_returns_over_uniform_hodl\": -0.5085644835320016, \"calmar\": -0.7848808015815673, \"daily_log_sharpe\": -0.6351496954873921, \"daily_returns\": 0.008711443700412373, \"fee_revenue_over_value\": 0.03274556912568422, \"jax_sharpe\": 0.12187854996992156, \"return\": -0.4352136850807675, \"returns_over_hodl\": -0.35034507002183746, \"returns_over_uniform_hodl\": -0.35034507002183757, \"sharpe\": 0.033435434558621316, \"sterling\": -1.6600833488068714, \"ulcer\": -0.20397512388188727}], \"train_return\": -0.4352136850807675, \"train_returns_over_hodl\": -0.35034507002183746, \"train_sharpe\": 0.12187854996992158, \"validation_return\": -0.33914271119701644, \"validation_returns_over_hodl\": -4.504051243081619e-09, \"validation_sharpe\": -2.2438531818796617}, {\"centeredness_margin\": 0.6352851288432839, \"continuous_test_metrics\": [{\"annualised_returns\": -0.6603800443480828, \"annualised_returns_over_hodl\": 0.13210847750960708, \"annualised_returns_over_uniform_hodl\": 0.1987079220139003, \"calmar\": -1.1670188874988279, \"daily_log_sharpe\": -1.4021547136478947, \"daily_returns\": 0.019305527213169034, \"fee_revenue_over_value\": 0.07227342421304024, \"jax_sharpe\": -0.8607219106807048, \"return\": -0.35268864920669973, \"returns_over_hodl\": 0.051242118930358904, \"returns_over_uniform_hodl\": 0.07572398870596442, \"sharpe\": -1.017369837739088, \"sterling\": -2.0205687586637127, \"ulcer\": -0.14622198301213243}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -2.5683001634203326, \"optuna_trial_number\": 94, \"price_ratio\": 23.537633916969412, \"shift_exponent\": 0.0843331403941723, \"step\": 94, \"test_objective\": [{\"annualised_returns\": -0.6603800443480828, \"annualised_returns_over_hodl\": 0.13210847750960708, \"annualised_returns_over_uniform_hodl\": 0.1987079220139003, \"calmar\": -1.1670188874988279, \"daily_log_sharpe\": -1.4021547136478947, \"daily_returns\": 0.019305527213169034, \"fee_revenue_over_value\": 0.07227342421304024, \"jax_sharpe\": -0.8607219106807048, \"return\": -0.35268864920669973, \"returns_over_hodl\": 0.051242118930358904, \"returns_over_uniform_hodl\": 0.07572398870596442, \"sharpe\": -1.017369837739088, \"sterling\": -2.0205687586637127, \"ulcer\": -0.14622198301213243}], \"train_objective\": [{\"annualised_returns\": -0.2998538716924446, \"annualised_returns_over_hodl\": -0.11827925253312066, \"annualised_returns_over_uniform_hodl\": -0.11827925253312066, \"calmar\": -0.41095471772146064, \"daily_log_sharpe\": -0.29678031289945594, \"daily_returns\": 0.008065610423904085, \"fee_revenue_over_value\": 0.031132539211747984, \"jax_sharpe\": 0.15360997689978345, \"return\": -0.19460163431039423, \"returns_over_hodl\": -0.07357702365463248, \"returns_over_uniform_hodl\": -0.07357702365463248, \"sharpe\": 0.19632376527720607, \"sterling\": -0.9879697303143233, \"ulcer\": -0.1635415093192367}], \"train_return\": -0.19460163431039423, \"train_returns_over_hodl\": -0.07357702365463248, \"train_sharpe\": 0.15360997689978342, \"validation_return\": -0.17124507226400443, \"validation_returns_over_hodl\": -0.024510610326812787, \"validation_sharpe\": -1.3512280173697202}, {\"centeredness_margin\": 0.01531087334507199, \"continuous_test_metrics\": [{\"annualised_returns\": -0.39237591980257747, \"annualised_returns_over_hodl\": 0.17872301665043988, \"annualised_returns_over_uniform_hodl\": 1.1446437007534787, \"calmar\": -0.7088204821551346, \"daily_log_sharpe\": -0.5812386591657039, \"daily_returns\": 0.03101604160559451, \"fee_revenue_over_value\": 0.14877477517341448, \"jax_sharpe\": 0.04260618483006345, \"return\": -0.18179595647112623, \"returns_over_hodl\": 0.06846479817228746, \"returns_over_uniform_hodl\": 0.35971926987154323, \"sharpe\": -0.12292829219784679, \"sterling\": -1.03932126705305, \"ulcer\": -0.1682785576329237}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -0.09151194196400925, \"optuna_trial_number\": 95, \"price_ratio\": 2.231815657671463, \"shift_exponent\": 0.006302202291635156, \"step\": 95, \"test_objective\": [{\"annualised_returns\": -0.39237591980257747, \"annualised_returns_over_hodl\": 0.17872301665043988, \"annualised_returns_over_uniform_hodl\": 1.1446437007534787, \"calmar\": -0.7088204821551346, \"daily_log_sharpe\": -0.5812386591657039, \"daily_returns\": 0.03101604160559451, \"fee_revenue_over_value\": 0.14877477517341448, \"jax_sharpe\": 0.04260618483006345, \"return\": -0.18179595647112623, \"returns_over_hodl\": 0.06846479817228746, \"returns_over_uniform_hodl\": 0.35971926987154323, \"sharpe\": -0.12292829219784679, \"sterling\": -1.03932126705305, \"ulcer\": -0.1682785576329237}], \"train_objective\": [{\"annualised_returns\": -0.5434877254405176, \"annualised_returns_over_hodl\": -0.42509666528416823, \"annualised_returns_over_uniform_hodl\": -0.42509666528416845, \"calmar\": -0.7076218767654262, \"daily_log_sharpe\": -0.5373672798289368, \"daily_returns\": 0.008525890694462414, \"fee_revenue_over_value\": 0.042487165107514595, \"jax_sharpe\": 0.1602413241371137, \"return\": -0.3787787468497329, \"returns_over_hodl\": -0.28542983593007676, \"returns_over_uniform_hodl\": -0.285429835930077, \"sharpe\": 0.10324067018936268, \"sterling\": -1.518373574730072, \"ulcer\": -0.19448458495522108}], \"train_return\": -0.3787787468497329, \"train_returns_over_hodl\": -0.28542983593007676, \"train_sharpe\": 0.16024132413711367, \"validation_return\": -0.3391427113739517, \"validation_returns_over_hodl\": -2.6916695539114244e-09, \"validation_sharpe\": -2.2438531751610014}, {\"centeredness_margin\": 0.0270518995811901, \"continuous_test_metrics\": [{\"annualised_returns\": -0.389347086355625, \"annualised_returns_over_hodl\": 0.18459861456746496, \"annualised_returns_over_uniform_hodl\": 1.1553341404255333, \"calmar\": -0.7033489382424102, \"daily_log_sharpe\": -0.5535708038774406, \"daily_returns\": 0.031016041636799475, \"fee_revenue_over_value\": 0.09416201308855748, \"jax_sharpe\": 0.08942814115967125, \"return\": -0.18015582296792731, \"returns_over_hodl\": 0.07060659211305365, \"returns_over_uniform_hodl\": 0.36244489943436364, \"sharpe\": -0.07913762693262444, \"sterling\": -1.005169479474787, \"ulcer\": -0.1753705765626434}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -0.09534742555059728, \"optuna_trial_number\": 96, \"price_ratio\": 2.9386357763735558, \"shift_exponent\": 0.0037899159719116974, \"step\": 96, \"test_objective\": [{\"annualised_returns\": -0.389347086355625, \"annualised_returns_over_hodl\": 0.18459861456746496, \"annualised_returns_over_uniform_hodl\": 1.1553341404255333, \"calmar\": -0.7033489382424102, \"daily_log_sharpe\": -0.5535708038774406, \"daily_returns\": 0.031016041636799475, \"fee_revenue_over_value\": 0.09416201308855748, \"jax_sharpe\": 0.08942814115967125, \"return\": -0.18015582296792731, \"returns_over_hodl\": 0.07060659211305365, \"returns_over_uniform_hodl\": 0.36244489943436364, \"sharpe\": -0.07913762693262444, \"sterling\": -1.005169479474787, \"ulcer\": -0.1753705765626434}], \"train_objective\": [{\"annualised_returns\": -0.5234680674604327, \"annualised_returns_over_hodl\": -0.3998851457390976, \"annualised_returns_over_uniform_hodl\": -0.3998851457390976, \"calmar\": -0.68964450858986, \"daily_log_sharpe\": -0.5095649381393202, \"daily_returns\": 0.008439609745793584, \"fee_revenue_over_value\": 0.03880951562482357, \"jax_sharpe\": 0.1433528540562371, \"return\": -0.36237876210268116, \"returns_over_hodl\": -0.2665654784535464, \"returns_over_uniform_hodl\": -0.2665654784535464, \"sharpe\": 0.12250336130267239, \"sterling\": -1.4807744018101416, \"ulcer\": -0.19250532877336818}], \"train_return\": -0.36237876210268116, \"train_returns_over_hodl\": -0.2665654784535464, \"train_sharpe\": 0.1433528540562371, \"validation_return\": -0.33914271179228883, \"validation_returns_over_hodl\": -2.8036745147730358e-09, \"validation_sharpe\": -2.243853177191421}, {\"centeredness_margin\": 0.2278946231038127, \"continuous_test_metrics\": [{\"annualised_returns\": -0.5711316909349144, \"annualised_returns_over_hodl\": 0.11216395282401548, \"annualised_returns_over_uniform_hodl\": 0.5137150541999418, \"calmar\": -1.066153603315377, \"daily_log_sharpe\": -1.058385942344466, \"daily_returns\": 0.02460113601212666, \"fee_revenue_over_value\": 0.12309225710980458, \"jax_sharpe\": -0.4461957436171257, \"return\": -0.28891265432490876, \"returns_over_hodl\": 0.043743846217894955, \"returns_over_uniform_hodl\": 0.18170910315491273, \"sharpe\": -0.6519224868297345, \"sterling\": -1.6241322385473858, \"ulcer\": -0.15011705385269372}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -2.9587877650707544, \"optuna_trial_number\": 97, \"price_ratio\": 3.4594855092299404, \"shift_exponent\": 0.031866492979270335, \"step\": 97, \"test_objective\": [{\"annualised_returns\": -0.5711316909349144, \"annualised_returns_over_hodl\": 0.11216395282401548, \"annualised_returns_over_uniform_hodl\": 0.5137150541999418, \"calmar\": -1.066153603315377, \"daily_log_sharpe\": -1.058385942344466, \"daily_returns\": 0.02460113601212666, \"fee_revenue_over_value\": 0.12309225710980458, \"jax_sharpe\": -0.4461957436171257, \"return\": -0.28891265432490876, \"returns_over_hodl\": 0.043743846217894955, \"returns_over_uniform_hodl\": 0.18170910315491273, \"sharpe\": -0.6519224868297345, \"sterling\": -1.6241322385473858, \"ulcer\": -0.15011705385269372}], \"train_objective\": [{\"annualised_returns\": -0.40512502342740375, \"annualised_returns_over_hodl\": -0.2508512326407851, \"annualised_returns_over_uniform_hodl\": -0.2508512326407849, \"calmar\": -0.5411456879092479, \"daily_log_sharpe\": -0.4170836945543493, \"daily_returns\": 0.008389064293890627, \"fee_revenue_over_value\": 0.045210018316040615, \"jax_sharpe\": 0.13353144715743895, \"return\": -0.27046015097155984, \"returns_over_hodl\": -0.16083455456135143, \"returns_over_uniform_hodl\": -0.16083455456135132, \"sharpe\": 0.11559915028981614, \"sterling\": -1.257128765635116, \"ulcer\": -0.17328541299768097}], \"train_return\": -0.27046015097155984, \"train_returns_over_hodl\": -0.16083455456135143, \"train_sharpe\": 0.13353144715743895, \"validation_return\": -0.24299225581144324, \"validation_returns_over_hodl\": -0.03652461134808327, \"validation_sharpe\": -1.8523298169521027}, {\"centeredness_margin\": 0.03435311651256861, \"continuous_test_metrics\": [{\"annualised_returns\": -0.46581665401018024, \"annualised_returns_over_hodl\": 0.03625617671611736, \"annualised_returns_over_uniform_hodl\": 0.8854304583390715, \"calmar\": -0.8414901386019686, \"daily_log_sharpe\": -0.7594517133518935, \"daily_returns\": 0.031016041535719883, \"fee_revenue_over_value\": 0.20466300039721325, \"jax_sharpe\": -0.15199608270867315, \"return\": -0.22316174886700735, \"returns_over_hodl\": 0.014446619286653695, \"returns_over_uniform_hodl\": 0.29097619107716377, \"sharpe\": -0.3252189123744915, \"sterling\": -1.2989597304378537, \"ulcer\": -0.15488559575959346}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": 0.65412497890096, \"optuna_trial_number\": 98, \"price_ratio\": 1.670353652164293, \"shift_exponent\": 0.009883731577338645, \"step\": 98, \"test_objective\": [{\"annualised_returns\": -0.46581665401018024, \"annualised_returns_over_hodl\": 0.03625617671611736, \"annualised_returns_over_uniform_hodl\": 0.8854304583390715, \"calmar\": -0.8414901386019686, \"daily_log_sharpe\": -0.7594517133518935, \"daily_returns\": 0.031016041535719883, \"fee_revenue_over_value\": 0.20466300039721325, \"jax_sharpe\": -0.15199608270867315, \"return\": -0.22316174886700735, \"returns_over_hodl\": 0.014446619286653695, \"returns_over_uniform_hodl\": 0.29097619107716377, \"sharpe\": -0.3252189123744915, \"sterling\": -1.2989597304378537, \"ulcer\": -0.15488559575959346}], \"train_objective\": [{\"annualised_returns\": -0.5253168826253569, \"annualised_returns_over_hodl\": -0.40221342925483383, \"annualised_returns_over_uniform_hodl\": -0.4022134292548336, \"calmar\": -0.6812710250369588, \"daily_log_sharpe\": -0.5156969392803793, \"daily_returns\": 0.009043848792816367, \"fee_revenue_over_value\": 0.0540074902938887, \"jax_sharpe\": 0.13080274123430516, \"return\": -0.3638818045718266, \"returns_over_hodl\": -0.2682943782591065, \"returns_over_uniform_hodl\": -0.2682943782591064, \"sharpe\": 0.1181604531476015, \"sterling\": -1.474032201692117, \"ulcer\": -0.19134657917845124}], \"train_return\": -0.3638818045718266, \"train_returns_over_hodl\": -0.2682943782591065, \"train_sharpe\": 0.13080274123430516, \"validation_return\": -0.33328275365251103, \"validation_returns_over_hodl\": 0.0058351973458601325, \"validation_sharpe\": -2.3210235875554597}, {\"centeredness_margin\": 0.16203587239273134, \"continuous_test_metrics\": [{\"annualised_returns\": -0.5492709169108358, \"annualised_returns_over_hodl\": 0.09991152067136322, \"annualised_returns_over_uniform_hodl\": 0.5908738976893295, \"calmar\": -1.0221549079936203, \"daily_log_sharpe\": -0.9771745145279337, \"daily_returns\": 0.025805367269967187, \"fee_revenue_over_value\": 0.11492582109340095, \"jax_sharpe\": -0.3640113101684435, \"return\": -0.27453124175371457, \"returns_over_hodl\": 0.03909757335776387, \"returns_over_uniform_hodl\": 0.20560862303101524, \"sharpe\": -0.5613956399914809, \"sterling\": -1.5382249348325934, \"ulcer\": -0.15341296258935733}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -3.1320476434157953, \"optuna_trial_number\": 99, \"price_ratio\": 4.33813983428152, \"shift_exponent\": 0.019220320856538044, \"step\": 99, \"test_objective\": [{\"annualised_returns\": -0.5492709169108358, \"annualised_returns_over_hodl\": 0.09991152067136322, \"annualised_returns_over_uniform_hodl\": 0.5908738976893295, \"calmar\": -1.0221549079936203, \"daily_log_sharpe\": -0.9771745145279337, \"daily_returns\": 0.025805367269967187, \"fee_revenue_over_value\": 0.11492582109340095, \"jax_sharpe\": -0.3640113101684435, \"return\": -0.27453124175371457, \"returns_over_hodl\": 0.03909757335776387, \"returns_over_uniform_hodl\": 0.20560862303101524, \"sharpe\": -0.5613956399914809, \"sterling\": -1.5382249348325934, \"ulcer\": -0.15341296258935733}], \"train_objective\": [{\"annualised_returns\": -0.4316711081303254, \"annualised_returns_over_hodl\": -0.28428173050436456, \"annualised_returns_over_uniform_hodl\": -0.28428173050436467, \"calmar\": -0.5765700086863352, \"daily_log_sharpe\": -0.4377226367705822, \"daily_returns\": 0.008275564387550916, \"fee_revenue_over_value\": 0.039993448413450504, \"jax_sharpe\": 0.15309430847444286, \"return\": -0.2904022153137914, \"returns_over_hodl\": -0.18377324849150656, \"returns_over_uniform_hodl\": -0.18377324849150667, \"sharpe\": 0.11862168782136938, \"sterling\": -1.3127479562825048, \"ulcer\": -0.17723097404357185}], \"train_return\": -0.2904022153137914, \"train_returns_over_hodl\": -0.18377324849150656, \"train_sharpe\": 0.15309430847444283, \"validation_return\": -0.2602242446976998, \"validation_returns_over_hodl\": -0.03695170417362559, \"validation_sharpe\": -1.952005875961627}, {\"centeredness_margin\": 0.19684416523105353, \"continuous_test_metrics\": [{\"annualised_returns\": -0.5573892541404413, \"annualised_returns_over_hodl\": 0.0320626849359269, \"annualised_returns_over_uniform_hodl\": 0.5622197653606553, \"calmar\": -1.0360245910336832, \"daily_log_sharpe\": -1.0014674886813772, \"daily_returns\": 0.026688926824587532, \"fee_revenue_over_value\": 0.11743288454801823, \"jax_sharpe\": -0.38502647312699867, \"return\": -0.27982232888745173, \"returns_over_hodl\": 0.012791284131447478, \"returns_over_uniform_hodl\": 0.19681571472017034, \"sharpe\": -0.5874035548656248, \"sterling\": -1.5630323952746075, \"ulcer\": -0.15344616904786684}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -3.2935085827361936, \"optuna_trial_number\": 100, \"price_ratio\": 3.499685511935329, \"shift_exponent\": 0.009397625873716486, \"step\": 100, \"test_objective\": [{\"annualised_returns\": -0.5573892541404413, \"annualised_returns_over_hodl\": 0.0320626849359269, \"annualised_returns_over_uniform_hodl\": 0.5622197653606553, \"calmar\": -1.0360245910336832, \"daily_log_sharpe\": -1.0014674886813772, \"daily_returns\": 0.026688926824587532, \"fee_revenue_over_value\": 0.11743288454801823, \"jax_sharpe\": -0.38502647312699867, \"return\": -0.27982232888745173, \"returns_over_hodl\": 0.012791284131447478, \"returns_over_uniform_hodl\": 0.19681571472017034, \"sharpe\": -0.5874035548656248, \"sterling\": -1.5630323952746075, \"ulcer\": -0.15344616904786684}], \"train_objective\": [{\"annualised_returns\": -0.45562853251659174, \"annualised_returns_over_hodl\": -0.31445222960903274, \"annualised_returns_over_uniform_hodl\": -0.31445222960903274, \"calmar\": -0.6046508600829398, \"daily_log_sharpe\": -0.4607215943265229, \"daily_returns\": 0.008351717339493707, \"fee_revenue_over_value\": 0.03400873673355299, \"jax_sharpe\": 0.1711600386262041, \"return\": -0.30871613291279654, \"returns_over_hodl\": -0.2048391393268908, \"returns_over_uniform_hodl\": -0.2048391393268908, \"sharpe\": 0.11406638354196152, \"sterling\": -1.3636344204259727, \"ulcer\": -0.18033998063414092}], \"train_return\": -0.30871613291279654, \"train_returns_over_hodl\": -0.2048391393268908, \"train_sharpe\": 0.1711600386262041, \"validation_return\": -0.27041452943744637, \"validation_returns_over_hodl\": -0.0394540019438393, \"validation_sharpe\": -2.022212882836996}, {\"centeredness_margin\": 0.1065534104289797, \"continuous_test_metrics\": [{\"annualised_returns\": -0.5204620403024001, \"annualised_returns_over_hodl\": 0.02655328757450226, \"annualised_returns_over_uniform_hodl\": 0.6925564638641131, \"calmar\": -0.9524627288553525, \"daily_log_sharpe\": -0.8785387081570747, \"daily_returns\": 0.02867262642310391, \"fee_revenue_over_value\": 0.11724418505439065, \"jax_sharpe\": -0.263761455297455, \"return\": -0.2562014178485461, \"returns_over_hodl\": 0.010610392171761518, \"returns_over_uniform_hodl\": 0.2360697469698756, \"sharpe\": -0.4498480075954922, \"sterling\": -1.4329169969871316, \"ulcer\": -0.157351195521052}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -3.310403047155035, \"optuna_trial_number\": 101, \"price_ratio\": 3.729964734912868, \"shift_exponent\": 0.010473271829050392, \"step\": 101, \"test_objective\": [{\"annualised_returns\": -0.5204620403024001, \"annualised_returns_over_hodl\": 0.02655328757450226, \"annualised_returns_over_uniform_hodl\": 0.6925564638641131, \"calmar\": -0.9524627288553525, \"daily_log_sharpe\": -0.8785387081570747, \"daily_returns\": 0.02867262642310391, \"fee_revenue_over_value\": 0.11724418505439065, \"jax_sharpe\": -0.263761455297455, \"return\": -0.2562014178485461, \"returns_over_hodl\": 0.010610392171761518, \"returns_over_uniform_hodl\": 0.2360697469698756, \"sharpe\": -0.4498480075954922, \"sterling\": -1.4329169969871316, \"ulcer\": -0.157351195521052}], \"train_objective\": [{\"annualised_returns\": -0.4640962274058277, \"annualised_returns_over_hodl\": -0.3251159210374296, \"annualised_returns_over_uniform_hodl\": -0.3251159210374296, \"calmar\": -0.6173801100866578, \"daily_log_sharpe\": -0.46197357728417304, \"daily_returns\": 0.008316896999844864, \"fee_revenue_over_value\": 0.03985489356273698, \"jax_sharpe\": 0.17839167610176518, \"return\": -0.3152645518501095, \"returns_over_hodl\": -0.2123715680239212, \"returns_over_uniform_hodl\": -0.2123715680239212, \"sharpe\": 0.12299793800704759, \"sterling\": -1.376470926570548, \"ulcer\": -0.1822098814884657}], \"train_return\": -0.3152645518501095, \"train_returns_over_hodl\": -0.2123715680239212, \"train_sharpe\": 0.17839167610176518, \"validation_return\": -0.29434877062039044, \"validation_returns_over_hodl\": -0.03868834764579365, \"validation_sharpe\": -2.127976620800408}, {\"centeredness_margin\": 0.03793967174014521, \"continuous_test_metrics\": [{\"annualised_returns\": -0.4285031221600335, \"annualised_returns_over_hodl\": 0.10864027123119602, \"annualised_returns_over_uniform_hodl\": 1.0171306882069864, \"calmar\": -0.7740837564637978, \"daily_log_sharpe\": -0.662509290742776, \"daily_returns\": 0.03101604152386393, \"fee_revenue_over_value\": 0.16463963344348356, \"jax_sharpe\": -0.050710832478124336, \"return\": -0.20174749199715925, \"returns_over_hodl\": 0.04241076809983357, \"returns_over_uniform_hodl\": 0.3265631304796257, \"sharpe\": -0.21595325197973558, \"sterling\": -1.1642482161743504, \"ulcer\": -0.16151949674844437}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": 0.629599598416239, \"optuna_trial_number\": 102, \"price_ratio\": 2.158927631873895, \"shift_exponent\": 0.008617683255323529, \"step\": 102, \"test_objective\": [{\"annualised_returns\": -0.4285031221600335, \"annualised_returns_over_hodl\": 0.10864027123119602, \"annualised_returns_over_uniform_hodl\": 1.0171306882069864, \"calmar\": -0.7740837564637978, \"daily_log_sharpe\": -0.662509290742776, \"daily_returns\": 0.03101604152386393, \"fee_revenue_over_value\": 0.16463963344348356, \"jax_sharpe\": -0.050710832478124336, \"return\": -0.20174749199715925, \"returns_over_hodl\": 0.04241076809983357, \"returns_over_uniform_hodl\": 0.3265631304796257, \"sharpe\": -0.21595325197973558, \"sterling\": -1.1642482161743504, \"ulcer\": -0.16151949674844437}], \"train_objective\": [{\"annualised_returns\": -0.5331952459641189, \"annualised_returns_over_hodl\": -0.41213495296397806, \"annualised_returns_over_uniform_hodl\": -0.4121349529639782, \"calmar\": -0.6948382737472567, \"daily_log_sharpe\": -0.5308638821358939, \"daily_returns\": 0.008542600119651677, \"fee_revenue_over_value\": 0.04414453198024047, \"jax_sharpe\": 0.15311423820084846, \"return\": -0.3703126813886636, \"returns_over_hodl\": -0.2756916021609247, \"returns_over_uniform_hodl\": -0.2756916021609248, \"sharpe\": 0.098944673258305, \"sterling\": -1.5025007022358923, \"ulcer\": -0.19201688404548}], \"train_return\": -0.3703126813886636, \"train_returns_over_hodl\": -0.2756916021609247, \"train_sharpe\": 0.1531142382008485, \"validation_return\": -0.3359755348228456, \"validation_returns_over_hodl\": 0.00479252464760016, \"validation_sharpe\": -2.2681590213205034}, {\"centeredness_margin\": 0.5933877341741166, \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -Infinity, \"optuna_trial_number\": 103, \"price_ratio\": 1.2732307093892516, \"shift_exponent\": 43.32978363774432, \"step\": 103, \"test_objective\": -Infinity, \"train_objective\": -Infinity, \"train_return\": -Infinity, \"train_returns_over_hodl\": -Infinity, \"train_sharpe\": -Infinity, \"validation_return\": -Infinity, \"validation_returns_over_hodl\": -Infinity, \"validation_sharpe\": -Infinity}, {\"centeredness_margin\": 0.042432598059307824, \"continuous_test_metrics\": [{\"annualised_returns\": -0.5353895709483202, \"annualised_returns_over_hodl\": 0.0029289508396246333, \"annualised_returns_over_uniform_hodl\": 0.6398688966479225, \"calmar\": -0.9784814485559299, \"daily_log_sharpe\": -0.9411023934972672, \"daily_returns\": 0.029227479380277775, \"fee_revenue_over_value\": 0.21064799500648773, \"jax_sharpe\": -0.3386736853682008, \"return\": -0.2656144381242437, \"returns_over_hodl\": 0.0011785691251100516, \"returns_over_uniform_hodl\": 0.22042687016208573, \"sharpe\": -0.523411041244567, \"sterling\": -1.5266284934504801, \"ulcer\": -0.14656349506842298}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -2.877495321021389, \"optuna_trial_number\": 104, \"price_ratio\": 1.5102327678604335, \"shift_exponent\": 0.08097717604885783, \"step\": 104, \"test_objective\": [{\"annualised_returns\": -0.5353895709483202, \"annualised_returns_over_hodl\": 0.0029289508396246333, \"annualised_returns_over_uniform_hodl\": 0.6398688966479225, \"calmar\": -0.9784814485559299, \"daily_log_sharpe\": -0.9411023934972672, \"daily_returns\": 0.029227479380277775, \"fee_revenue_over_value\": 0.21064799500648773, \"jax_sharpe\": -0.3386736853682008, \"return\": -0.2656144381242437, \"returns_over_hodl\": 0.0011785691251100516, \"returns_over_uniform_hodl\": 0.22042687016208573, \"sharpe\": -0.523411041244567, \"sterling\": -1.5266284934504801, \"ulcer\": -0.14656349506842298}], \"train_objective\": [{\"annualised_returns\": -0.42462264308508446, \"annualised_returns_over_hodl\": -0.27540532939763185, \"annualised_returns_over_uniform_hodl\": -0.2754053293976316, \"calmar\": -0.5594992890382348, \"daily_log_sharpe\": -0.43577467002045295, \"daily_returns\": 0.009136379526585643, \"fee_revenue_over_value\": 0.09355001868232739, \"jax_sharpe\": 0.013549650362705452, \"return\": -0.2850721894613303, \"returns_over_hodl\": -0.1776422968722955, \"returns_over_uniform_hodl\": -0.17764229687229538, \"sharpe\": 0.10426012854406193, \"sterling\": -1.2900413280709526, \"ulcer\": -0.17765152099700107}], \"train_return\": -0.2850721894613303, \"train_returns_over_hodl\": -0.1776422968722955, \"train_sharpe\": 0.01354965036270545, \"validation_return\": -0.29691334385927104, \"validation_returns_over_hodl\": -0.04647916059935131, \"validation_sharpe\": -2.180955875594704}, {\"centeredness_margin\": 0.028489425868607397, \"continuous_test_metrics\": [{\"annualised_returns\": -0.4719160465095009, \"annualised_returns_over_hodl\": 0.07044429311954792, \"annualised_returns_over_uniform_hodl\": 0.8639023061008642, \"calmar\": -0.8586489329234724, \"daily_log_sharpe\": -0.7656400557969895, \"daily_returns\": 0.0303306428810673, \"fee_revenue_over_value\": 0.17188786206962262, \"jax_sharpe\": -0.16377675381496867, \"return\": -0.22674631537793588, \"returns_over_hodl\": 0.027795163434036985, \"returns_over_uniform_hodl\": 0.28501923669934826, \"sharpe\": -0.3309819522998504, \"sterling\": -1.3041437154431197, \"ulcer\": -0.1548759945661559}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -3.149417517259403, \"optuna_trial_number\": 105, \"price_ratio\": 2.0851969324149096, \"shift_exponent\": 0.0318519028819689, \"step\": 105, \"test_objective\": [{\"annualised_returns\": -0.4719160465095009, \"annualised_returns_over_hodl\": 0.07044429311954792, \"annualised_returns_over_uniform_hodl\": 0.8639023061008642, \"calmar\": -0.8586489329234724, \"daily_log_sharpe\": -0.7656400557969895, \"daily_returns\": 0.0303306428810673, \"fee_revenue_over_value\": 0.17188786206962262, \"jax_sharpe\": -0.16377675381496867, \"return\": -0.22674631537793588, \"returns_over_hodl\": 0.027795163434036985, \"returns_over_uniform_hodl\": 0.28501923669934826, \"sharpe\": -0.3309819522998504, \"sterling\": -1.3041437154431197, \"ulcer\": -0.1548759945661559}], \"train_objective\": [{\"annualised_returns\": -0.49076570299257327, \"annualised_returns_over_hodl\": -0.35870180975145727, \"annualised_returns_over_uniform_hodl\": -0.3587018097514575, \"calmar\": -0.6414729103574072, \"daily_log_sharpe\": -0.5068149876663437, \"daily_returns\": 0.00861368961708996, \"fee_revenue_over_value\": 0.0607730296292728, \"jax_sharpe\": 0.036794189458470294, \"return\": -0.33615997299203026, \"returns_over_hodl\": -0.23640687660033388, \"returns_over_uniform_hodl\": -0.236406876600334, \"sharpe\": 0.07439430227169823, \"sterling\": -1.4390400406597736, \"ulcer\": -0.1833322748231006}], \"train_return\": -0.33615997299203026, \"train_returns_over_hodl\": -0.23640687660033388, \"train_sharpe\": 0.03679418945847029, \"validation_return\": -0.31303682065771987, \"validation_returns_over_hodl\": -0.043494124107855026, \"validation_sharpe\": -2.2320721371735748}, {\"centeredness_margin\": 0.042328100886336546, \"continuous_test_metrics\": [{\"annualised_returns\": -0.48625724119677216, \"annualised_returns_over_hodl\": 0.10342225630928992, \"annualised_returns_over_uniform_hodl\": 0.8132842449513729, \"calmar\": -0.8880962492139366, \"daily_log_sharpe\": -0.7962214960477739, \"daily_returns\": 0.02898339822423173, \"fee_revenue_over_value\": 0.14975524706362275, \"jax_sharpe\": -0.18870997568457443, \"return\": -0.23527311571685539, \"returns_over_hodl\": 0.04043203369479009, \"returns_over_uniform_hodl\": 0.27084911028299397, \"sharpe\": -0.36310078535109974, \"sterling\": -1.3384398122693548, \"ulcer\": -0.15605831658478186}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -2.724578782997912, \"optuna_trial_number\": 106, \"price_ratio\": 2.6286002730467075, \"shift_exponent\": 0.11952180460196954, \"step\": 106, \"test_objective\": [{\"annualised_returns\": -0.48625724119677216, \"annualised_returns_over_hodl\": 0.10342225630928992, \"annualised_returns_over_uniform_hodl\": 0.8132842449513729, \"calmar\": -0.8880962492139366, \"daily_log_sharpe\": -0.7962214960477739, \"daily_returns\": 0.02898339822423173, \"fee_revenue_over_value\": 0.14975524706362275, \"jax_sharpe\": -0.18870997568457443, \"return\": -0.23527311571685539, \"returns_over_hodl\": 0.04043203369479009, \"returns_over_uniform_hodl\": 0.27084911028299397, \"sharpe\": -0.36310078535109974, \"sterling\": -1.3384398122693548, \"ulcer\": -0.15605831658478186}], \"train_objective\": [{\"annualised_returns\": -0.4554344213818702, \"annualised_returns_over_hodl\": -0.3142077780468824, \"annualised_returns_over_uniform_hodl\": -0.3142077780468824, \"calmar\": -0.6034264733355048, \"daily_log_sharpe\": -0.4573653179002464, \"daily_returns\": 0.008407503256767085, \"fee_revenue_over_value\": 0.06315299201399029, \"jax_sharpe\": 0.15465315774809113, \"return\": -0.30856648983575763, \"returns_over_hodl\": -0.20466700986807185, \"returns_over_uniform_hodl\": -0.20466700986807185, \"sharpe\": 0.11279818798599157, \"sterling\": -1.358644697513249, \"ulcer\": -0.1803428033447181}], \"train_return\": -0.30856648983575763, \"train_returns_over_hodl\": -0.20466700986807185, \"train_sharpe\": 0.15465315774809113, \"validation_return\": -0.30028834315517083, \"validation_returns_over_hodl\": -0.029954068803728306, \"validation_sharpe\": -2.1397455365507034}, {\"centeredness_margin\": 0.09366339325141038, \"continuous_test_metrics\": [{\"annualised_returns\": -0.5215264737166787, \"annualised_returns_over_hodl\": -0.07181466534259151, \"annualised_returns_over_uniform_hodl\": 0.6887994856744672, \"calmar\": -0.8996670164937393, \"daily_log_sharpe\": -0.79714788219839, \"daily_returns\": 0.031016041521570288, \"fee_revenue_over_value\": 0.004469743226790464, \"jax_sharpe\": -0.13063080930438464, \"return\": -0.256866785067295, \"returns_over_hodl\": -0.029567639165768322, \"returns_over_uniform_hodl\": 0.23496401712653237, \"sharpe\": -0.3199786002462608, \"sterling\": -1.3426942729768359, \"ulcer\": -0.17641031397128193}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -0.12012136778843985, \"optuna_trial_number\": 107, \"price_ratio\": 1.7495559831868701, \"shift_exponent\": 0.0032461173604425055, \"step\": 107, \"test_objective\": [{\"annualised_returns\": -0.5215264737166787, \"annualised_returns_over_hodl\": -0.07181466534259151, \"annualised_returns_over_uniform_hodl\": 0.6887994856744672, \"calmar\": -0.8996670164937393, \"daily_log_sharpe\": -0.79714788219839, \"daily_returns\": 0.031016041521570288, \"fee_revenue_over_value\": 0.004469743226790464, \"jax_sharpe\": -0.13063080930438464, \"return\": -0.256866785067295, \"returns_over_hodl\": -0.029567639165768322, \"returns_over_uniform_hodl\": 0.23496401712653237, \"sharpe\": -0.3199786002462608, \"sterling\": -1.3426942729768359, \"ulcer\": -0.17641031397128193}], \"train_objective\": [{\"annualised_returns\": -0.5869196031969139, \"annualised_returns_over_hodl\": -0.479792086955397, \"annualised_returns_over_uniform_hodl\": -0.479792086955397, \"calmar\": -0.7545159366336808, \"daily_log_sharpe\": -0.5977185346678304, \"daily_returns\": 0.00893761039319118, \"fee_revenue_over_value\": 0.03779368430968977, \"jax_sharpe\": 0.13714310384038772, \"return\": -0.4153628807303915, \"returns_over_hodl\": -0.327511349427708, \"returns_over_uniform_hodl\": -0.327511349427708, \"sharpe\": 0.06399030188473462, \"sterling\": -1.6102245303855698, \"ulcer\": -0.1999149764585698}], \"train_return\": -0.4153628807303915, \"train_returns_over_hodl\": -0.327511349427708, \"train_sharpe\": 0.13714310384038772, \"validation_return\": -0.33914271100909676, \"validation_returns_over_hodl\": -3.5424739808220806e-09, \"validation_sharpe\": -2.2438531773480044}, {\"centeredness_margin\": 0.08212959279448537, \"continuous_test_metrics\": [{\"annualised_returns\": -0.46819204693030414, \"annualised_returns_over_hodl\": 0.03164818328365171, \"annualised_returns_over_uniform_hodl\": 0.8770463741182755, \"calmar\": -0.807661512162573, \"daily_log_sharpe\": -0.6622745388171857, \"daily_returns\": 0.03101604145978592, \"fee_revenue_over_value\": 0.0008705527442600428, \"jax_sharpe\": 0.017194017169053673, \"return\": -0.22455482816487082, \"returns_over_hodl\": 0.012627446123038455, \"returns_over_uniform_hodl\": 0.2886611245839761, \"sharpe\": -0.17082843726142885, \"sterling\": -1.20538206701857, \"ulcer\": -0.17641031384354344}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -0.1427477054353865, \"optuna_trial_number\": 108, \"price_ratio\": 1.5936430801470403, \"shift_exponent\": 0.0023245249791017863, \"step\": 108, \"test_objective\": [{\"annualised_returns\": -0.46819204693030414, \"annualised_returns_over_hodl\": 0.03164818328365171, \"annualised_returns_over_uniform_hodl\": 0.8770463741182755, \"calmar\": -0.807661512162573, \"daily_log_sharpe\": -0.6622745388171857, \"daily_returns\": 0.03101604145978592, \"fee_revenue_over_value\": 0.0008705527442600428, \"jax_sharpe\": 0.017194017169053673, \"return\": -0.22455482816487082, \"returns_over_hodl\": 0.012627446123038455, \"returns_over_uniform_hodl\": 0.2886611245839761, \"sharpe\": -0.17082843726142885, \"sterling\": -1.20538206701857, \"ulcer\": -0.17641031384354344}], \"train_objective\": [{\"annualised_returns\": -0.6197427061455657, \"annualised_returns_over_hodl\": -0.5211274735210922, \"annualised_returns_over_uniform_hodl\": -0.5211274735210922, \"calmar\": -0.7929348773915249, \"daily_log_sharpe\": -0.6510304096689239, \"daily_returns\": 0.009056505332852452, \"fee_revenue_over_value\": 0.03393847828169881, \"jax_sharpe\": 0.11566500583629806, \"return\": -0.44402392600850116, \"returns_over_hodl\": -0.3604791973932062, \"returns_over_uniform_hodl\": -0.36047919739320633, \"sharpe\": 0.021468827251810194, \"sterling\": -1.6819630554505531, \"ulcer\": -0.20463812106117377}], \"train_return\": -0.44402392600850116, \"train_returns_over_hodl\": -0.3604791973932062, \"train_sharpe\": 0.11566500583629805, \"validation_return\": -0.3391427107757997, \"validation_returns_over_hodl\": -4.221161531425821e-09, \"validation_sharpe\": -2.243853179320349}, {\"centeredness_margin\": 0.020710018983348365, \"continuous_test_metrics\": [{\"annualised_returns\": -0.45648589113090154, \"annualised_returns_over_hodl\": 0.054356812711848335, \"annualised_returns_over_uniform_hodl\": 0.9183639158573573, \"calmar\": -0.8278025128829859, \"daily_log_sharpe\": -0.7122684712740861, \"daily_returns\": 0.031202734786176835, \"fee_revenue_over_value\": 0.13377794700717993, \"jax_sharpe\": -0.09381634892472406, \"return\": -0.21772512315543135, \"returns_over_hodl\": 0.021546121947047636, \"returns_over_uniform_hodl\": 0.30001096033988506, \"sharpe\": -0.26610219417546843, \"sterling\": -1.2356704395937346, \"ulcer\": -0.16116951713436029}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -3.4494822824939497, \"optuna_trial_number\": 109, \"price_ratio\": 3.3575572855676583, \"shift_exponent\": 0.016124624802980714, \"step\": 109, \"test_objective\": [{\"annualised_returns\": -0.45648589113090154, \"annualised_returns_over_hodl\": 0.054356812711848335, \"annualised_returns_over_uniform_hodl\": 0.9183639158573573, \"calmar\": -0.8278025128829859, \"daily_log_sharpe\": -0.7122684712740861, \"daily_returns\": 0.031202734786176835, \"fee_revenue_over_value\": 0.13377794700717993, \"jax_sharpe\": -0.09381634892472406, \"return\": -0.21772512315543135, \"returns_over_hodl\": 0.021546121947047636, \"returns_over_uniform_hodl\": 0.30001096033988506, \"sharpe\": -0.26610219417546843, \"sterling\": -1.2356704395937346, \"ulcer\": -0.16116951713436029}], \"train_objective\": [{\"annualised_returns\": -0.48753639607443555, \"annualised_returns_over_hodl\": -0.35463502027060567, \"annualised_returns_over_uniform_hodl\": -0.35463502027060567, \"calmar\": -0.6471164790663333, \"daily_log_sharpe\": -0.4791894597358852, \"daily_returns\": 0.008381583585491115, \"fee_revenue_over_value\": 0.04582763975677636, \"jax_sharpe\": 0.12472393312304231, \"return\": -0.333607325260854, \"returns_over_hodl\": -0.2334706507406593, \"returns_over_uniform_hodl\": -0.2334706507406593, \"sharpe\": 0.12566231609837655, \"sterling\": -1.4173861578829723, \"ulcer\": -0.18604707176090665}], \"train_return\": -0.333607325260854, \"train_returns_over_hodl\": -0.2334706507406593, \"train_sharpe\": 0.1247239331230423, \"validation_return\": -0.3245789457007684, \"validation_returns_over_hodl\": -0.03735754860973595, \"validation_sharpe\": -2.2670197656858964}, {\"centeredness_margin\": 0.029595612284730323, \"continuous_test_metrics\": [{\"annualised_returns\": -0.4519370517766892, \"annualised_returns_over_hodl\": 0.06318106515571231, \"annualised_returns_over_uniform_hodl\": 0.9344193027070375, \"calmar\": -0.7796205516226569, \"daily_log_sharpe\": -0.6462451893172072, \"daily_returns\": 0.03101604141029229, \"fee_revenue_over_value\": 0.005057690666887075, \"jax_sharpe\": 0.011639627604492593, \"return\": -0.21509491818191429, \"returns_over_hodl\": 0.024980821296132882, \"returns_over_uniform_hodl\": 0.30438192429989974, \"sharpe\": -0.1570316263774824, \"sterling\": -1.1635327654057688, \"ulcer\": -0.1764103137412254}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -0.14692528878283562, \"optuna_trial_number\": 110, \"price_ratio\": 1.3569952533430878, \"shift_exponent\": 0.0029191168372997567, \"step\": 110, \"test_objective\": [{\"annualised_returns\": -0.4519370517766892, \"annualised_returns_over_hodl\": 0.06318106515571231, \"annualised_returns_over_uniform_hodl\": 0.9344193027070375, \"calmar\": -0.7796205516226569, \"daily_log_sharpe\": -0.6462451893172072, \"daily_returns\": 0.03101604141029229, \"fee_revenue_over_value\": 0.005057690666887075, \"jax_sharpe\": 0.011639627604492593, \"return\": -0.21509491818191429, \"returns_over_hodl\": 0.024980821296132882, \"returns_over_uniform_hodl\": 0.30438192429989974, \"sharpe\": -0.1570316263774824, \"sterling\": -1.1635327654057688, \"ulcer\": -0.1764103137412254}], \"train_objective\": [{\"annualised_returns\": -0.655450551325983, \"annualised_returns_over_hodl\": -0.5660957261043287, \"annualised_returns_over_uniform_hodl\": -0.5660957261043287, \"calmar\": -0.8267047414580707, \"daily_log_sharpe\": -0.7209495202684145, \"daily_returns\": 0.009569505171309537, \"fee_revenue_over_value\": 0.011478963469462256, \"jax_sharpe\": 0.06524934353062195, \"return\": -0.47633261537185, \"returns_over_hodl\": -0.39764280913729444, \"returns_over_uniform_hodl\": -0.39764280913729444, \"sharpe\": -0.04280335413732018, \"sterling\": -1.7591379376420466, \"ulcer\": -0.2085745772332892}], \"train_return\": -0.47633261537185, \"train_returns_over_hodl\": -0.39764280913729444, \"train_sharpe\": 0.06524934353062196, \"validation_return\": -0.33914271031370147, \"validation_returns_over_hodl\": -4.34833069551388e-09, \"validation_sharpe\": -2.2438531781157653}, {\"centeredness_margin\": 0.03890375848379443, \"continuous_test_metrics\": [{\"annualised_returns\": -0.49284113483581793, \"annualised_returns_over_hodl\": -0.016168302753350572, \"annualised_returns_over_uniform_hodl\": 0.7900460184235134, \"calmar\": -0.8501828552763898, \"daily_log_sharpe\": -0.7315357278281321, \"daily_returns\": 0.031016041497446606, \"fee_revenue_over_value\": 0.00457076261066803, \"jax_sharpe\": -0.06816889235911806, \"return\": -0.23923531383064245, \"returns_over_hodl\": -0.006543300855439305, \"returns_over_uniform_hodl\": 0.26426459488127474, \"sharpe\": -0.24983875658423313, \"sterling\": -1.2688423942200773, \"ulcer\": -0.17641031392139933}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -0.12829241197572716, \"optuna_trial_number\": 111, \"price_ratio\": 1.6459605565907411, \"shift_exponent\": 0.003088896640297918, \"step\": 111, \"test_objective\": [{\"annualised_returns\": -0.49284113483581793, \"annualised_returns_over_hodl\": -0.016168302753350572, \"annualised_returns_over_uniform_hodl\": 0.7900460184235134, \"calmar\": -0.8501828552763898, \"daily_log_sharpe\": -0.7315357278281321, \"daily_returns\": 0.031016041497446606, \"fee_revenue_over_value\": 0.00457076261066803, \"jax_sharpe\": -0.06816889235911806, \"return\": -0.23923531383064245, \"returns_over_hodl\": -0.006543300855439305, \"returns_over_uniform_hodl\": 0.26426459488127474, \"sharpe\": -0.24983875658423313, \"sterling\": -1.2688423942200773, \"ulcer\": -0.17641031392139933}], \"train_objective\": [{\"annualised_returns\": -0.5974301685517005, \"annualised_returns_over_hodl\": -0.49302844314767325, \"annualised_returns_over_uniform_hodl\": -0.49302844314767325, \"calmar\": -0.7666267989161075, \"daily_log_sharpe\": -0.6117212214544623, \"daily_returns\": 0.00906837644795069, \"fee_revenue_over_value\": 0.03995596168189838, \"jax_sharpe\": 0.13972638766127188, \"return\": -0.4244399287693613, \"returns_over_hodl\": -0.33795237615302554, \"returns_over_uniform_hodl\": -0.33795237615302554, \"sharpe\": 0.056174903495756705, \"sterling\": -1.628651668197854, \"ulcer\": -0.20216290948837265}], \"train_return\": -0.4244399287693613, \"train_returns_over_hodl\": -0.33795237615302554, \"train_sharpe\": 0.13972638766127188, \"validation_return\": -0.3391427109044173, \"validation_returns_over_hodl\": -3.786951974227293e-09, \"validation_sharpe\": -2.2438531779846684}, {\"centeredness_margin\": 0.04500974981886252, \"continuous_test_metrics\": [{\"annualised_returns\": -0.3645146277421307, \"annualised_returns_over_hodl\": 0.2327708208751358, \"annualised_returns_over_uniform_hodl\": 1.2429817134485575, \"calmar\": -0.6584894696399775, \"daily_log_sharpe\": -0.518959515299786, \"daily_returns\": 0.031016041569537772, \"fee_revenue_over_value\": 0.12536845582309641, \"jax_sharpe\": 0.11043806645102991, \"return\": -0.16688843911463935, \"returns_over_hodl\": 0.08793201808843265, \"returns_over_uniform_hodl\": 0.3844930885490163, \"sharpe\": -0.04716267823960185, \"sterling\": -0.942623468462146, \"ulcer\": -0.17476347658515085}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -0.10490711331537361, \"optuna_trial_number\": 112, \"price_ratio\": 2.0732232870731435, \"shift_exponent\": 0.0041875725408440905, \"step\": 112, \"test_objective\": [{\"annualised_returns\": -0.3645146277421307, \"annualised_returns_over_hodl\": 0.2327708208751358, \"annualised_returns_over_uniform_hodl\": 1.2429817134485575, \"calmar\": -0.6584894696399775, \"daily_log_sharpe\": -0.518959515299786, \"daily_returns\": 0.031016041569537772, \"fee_revenue_over_value\": 0.12536845582309641, \"jax_sharpe\": 0.11043806645102991, \"return\": -0.16688843911463935, \"returns_over_hodl\": 0.08793201808843265, \"returns_over_uniform_hodl\": 0.3844930885490163, \"sharpe\": -0.04716267823960185, \"sterling\": -0.942623468462146, \"ulcer\": -0.17476347658515085}], \"train_objective\": [{\"annualised_returns\": -0.5605715368376467, \"annualised_returns_over_hodl\": -0.4466109611513349, \"annualised_returns_over_uniform_hodl\": -0.44661096115133503, \"calmar\": -0.7252273723077628, \"daily_log_sharpe\": -0.5620255142385041, \"daily_returns\": 0.008623725882473005, \"fee_revenue_over_value\": 0.0394446627597471, \"jax_sharpe\": 0.132700195993592, \"return\": -0.392998478447687, \"returns_over_hodl\": -0.3017863206598781, \"returns_over_uniform_hodl\": -0.3017863206598782, \"sharpe\": 0.0855351589128294, \"sterling\": -1.5578445719737897, \"ulcer\": -0.19605437082639604}], \"train_return\": -0.392998478447687, \"train_returns_over_hodl\": -0.3017863206598781, \"train_sharpe\": 0.13270019599359198, \"validation_return\": -0.3391427112385691, \"validation_returns_over_hodl\": -3.088885147661813e-09, \"validation_sharpe\": -2.2438531763069887}, {\"centeredness_margin\": 0.025771332497651213, \"continuous_test_metrics\": [{\"annualised_returns\": -0.4746825407505123, \"annualised_returns_over_hodl\": 0.019057344440606583, \"annualised_returns_over_uniform_hodl\": 0.8541378075555988, \"calmar\": -0.818858052102215, \"daily_log_sharpe\": -0.6739936786366372, \"daily_returns\": 0.031016041553475284, \"fee_revenue_over_value\": 0.0006666536712370536, \"jax_sharpe\": 0.01200601006053907, \"return\": -0.22838031524182323, \"returns_over_hodl\": 0.007631871253164002, \"returns_over_uniform_hodl\": 0.2823038002266651, \"sharpe\": -0.1829916013963292, \"sterling\": -1.222092169895875, \"ulcer\": -0.1764103140372269}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -0.12554274374020147, \"optuna_trial_number\": 113, \"price_ratio\": 2.347442469167729, \"shift_exponent\": 0.0017586663115273593, \"step\": 113, \"test_objective\": [{\"annualised_returns\": -0.4746825407505123, \"annualised_returns_over_hodl\": 0.019057344440606583, \"annualised_returns_over_uniform_hodl\": 0.8541378075555988, \"calmar\": -0.818858052102215, \"daily_log_sharpe\": -0.6739936786366372, \"daily_returns\": 0.031016041553475284, \"fee_revenue_over_value\": 0.0006666536712370536, \"jax_sharpe\": 0.01200601006053907, \"return\": -0.22838031524182323, \"returns_over_hodl\": 0.007631871253164002, \"returns_over_uniform_hodl\": 0.2823038002266651, \"sharpe\": -0.1829916013963292, \"sterling\": -1.222092169895875, \"ulcer\": -0.1764103140372269}], \"train_objective\": [{\"annualised_returns\": -0.5702710492765259, \"annualised_returns_over_hodl\": -0.45882592744465145, \"annualised_returns_over_uniform_hodl\": -0.45882592744465145, \"calmar\": -0.7396635801327364, \"daily_log_sharpe\": -0.5723828887085671, \"daily_returns\": 0.008548172662680573, \"fee_revenue_over_value\": 0.03456220011909914, \"jax_sharpe\": 0.1523191937469335, \"return\": -0.4011685439188907, \"returns_over_hodl\": -0.3111840754768851, \"returns_over_uniform_hodl\": -0.3111840754768851, \"sharpe\": 0.08315654243100969, \"sterling\": -1.5686349749458286, \"ulcer\": -0.19996163030131944}], \"train_return\": -0.4011685439188907, \"train_returns_over_hodl\": -0.3111840754768851, \"train_sharpe\": 0.15231919374693353, \"validation_return\": -0.33914271146720054, \"validation_returns_over_hodl\": -3.7030227773016122e-09, \"validation_sharpe\": -2.2438531797177217}, {\"centeredness_margin\": 0.05089441243076884, \"continuous_test_metrics\": [{\"annualised_returns\": -0.5169201880929261, \"annualised_returns_over_hodl\": -0.06287898873870779, \"annualised_returns_over_uniform_hodl\": 0.705057632395123, \"calmar\": -0.8917208597959939, \"daily_log_sharpe\": -0.7790998903646418, \"daily_returns\": 0.031016041634512242, \"fee_revenue_over_value\": 0.0055077328226624525, \"jax_sharpe\": -0.10248112055394058, \"return\": -0.2539937690065517, \"returns_over_hodl\": -0.025815867131794623, \"returns_over_uniform_hodl\": 0.239738492798389, \"sharpe\": -0.2992885827469045, \"sterling\": -1.3308351505631573, \"ulcer\": -0.17641031420475592}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -0.09885966715832195, \"optuna_trial_number\": 114, \"price_ratio\": 3.0540138377217207, \"shift_exponent\": 0.0028919831547401624, \"step\": 114, \"test_objective\": [{\"annualised_returns\": -0.5169201880929261, \"annualised_returns_over_hodl\": -0.06287898873870779, \"annualised_returns_over_uniform_hodl\": 0.705057632395123, \"calmar\": -0.8917208597959939, \"daily_log_sharpe\": -0.7790998903646418, \"daily_returns\": 0.031016041634512242, \"fee_revenue_over_value\": 0.0055077328226624525, \"jax_sharpe\": -0.10248112055394058, \"return\": -0.2539937690065517, \"returns_over_hodl\": -0.025815867131794623, \"returns_over_uniform_hodl\": 0.239738492798389, \"sharpe\": -0.2992885827469045, \"sterling\": -1.3308351505631573, \"ulcer\": -0.17641031420475592}], \"train_objective\": [{\"annualised_returns\": -0.5228924223340706, \"annualised_returns_over_hodl\": -0.39916021385620815, \"annualised_returns_over_uniform_hodl\": -0.39916021385620815, \"calmar\": -0.6895366367825195, \"daily_log_sharpe\": -0.5094737711242131, \"daily_returns\": 0.008415332904861897, \"fee_revenue_over_value\": 0.0364302694197184, \"jax_sharpe\": 0.14201383428007866, \"return\": -0.3619112443380933, \"returns_over_hodl\": -0.26602770830474176, \"returns_over_uniform_hodl\": -0.26602770830474176, \"sharpe\": 0.12152004449678021, \"sterling\": -1.4812256739242369, \"ulcer\": -0.19219422824460075}], \"train_return\": -0.3619112443380933, \"train_returns_over_hodl\": -0.26602770830474176, \"train_sharpe\": 0.1420138342800787, \"validation_return\": -0.33914271183568334, \"validation_returns_over_hodl\": -2.907505347593542e-09, \"validation_sharpe\": -2.2438531777812916}, {\"centeredness_margin\": 0.07554182426655436, \"continuous_test_metrics\": [{\"annualised_returns\": -0.4583730175932863, \"annualised_returns_over_hodl\": 0.0506959960925355, \"annualised_returns_over_uniform_hodl\": 0.9117031958299953, \"calmar\": -0.828043307688578, \"daily_log_sharpe\": -0.6872688370316207, \"daily_returns\": 0.031016041647030684, \"fee_revenue_over_value\": 0.0837477604361238, \"jax_sharpe\": -0.038433037773642494, \"return\": -0.2188201452745313, \"returns_over_hodl\": 0.020116169247102578, \"returns_over_uniform_hodl\": 0.2981912153898918, \"sharpe\": -0.22232772008997154, \"sterling\": -1.198523670606436, \"ulcer\": -0.17137296209820302}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -3.852117548760368, \"optuna_trial_number\": 115, \"price_ratio\": 5.096682722419627, \"shift_exponent\": 0.004094873275918892, \"step\": 115, \"test_objective\": [{\"annualised_returns\": -0.4583730175932863, \"annualised_returns_over_hodl\": 0.0506959960925355, \"annualised_returns_over_uniform_hodl\": 0.9117031958299953, \"calmar\": -0.828043307688578, \"daily_log_sharpe\": -0.6872688370316207, \"daily_returns\": 0.031016041647030684, \"fee_revenue_over_value\": 0.0837477604361238, \"jax_sharpe\": -0.038433037773642494, \"return\": -0.2188201452745313, \"returns_over_hodl\": 0.020116169247102578, \"returns_over_uniform_hodl\": 0.2981912153898918, \"sharpe\": -0.22232772008997154, \"sterling\": -1.198523670606436, \"ulcer\": -0.17137296209820302}], \"train_objective\": [{\"annualised_returns\": -0.4537474687546178, \"annualised_returns_over_hodl\": -0.3120833342039411, \"annualised_returns_over_uniform_hodl\": -0.3120833342039413, \"calmar\": -0.6098644581641257, \"daily_log_sharpe\": -0.43498752499038534, \"daily_returns\": 0.008212082146519297, \"fee_revenue_over_value\": 0.03728653443009579, \"jax_sharpe\": 0.1429757502588051, \"return\": -0.3072668749616674, \"returns_over_hodl\": -0.20317210606512393, \"returns_over_uniform_hodl\": -0.20317210606512404, \"sharpe\": 0.15286322701066327, \"sterling\": -1.3490861175992108, \"ulcer\": -0.18166063284138081}], \"train_return\": -0.3072668749616674, \"train_returns_over_hodl\": -0.20317210606512393, \"train_sharpe\": 0.1429757502588051, \"validation_return\": -0.3354193413634675, \"validation_returns_over_hodl\": -0.03811407353989893, \"validation_sharpe\": -2.2191194519697}, {\"centeredness_margin\": 0.04295737882750683, \"continuous_test_metrics\": [{\"annualised_returns\": -0.5261240733776554, \"annualised_returns_over_hodl\": -0.08073350581268046, \"annualised_returns_over_uniform_hodl\": 0.6725719965527464, \"calmar\": -0.9093696027957059, \"daily_log_sharpe\": -0.8018892749044929, \"daily_returns\": 0.031016041733451922, \"fee_revenue_over_value\": 0.01790657181553273, \"jax_sharpe\": -0.12993403628014655, \"return\": -0.25975090394128497, \"returns_over_hodl\": -0.03333391575230926, \"returns_over_uniform_hodl\": 0.23017109042251538, \"sharpe\": -0.3254686570069397, \"sterling\": -1.3629118871062078, \"ulcer\": -0.17417006322122278}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -3.3441906506669885, \"optuna_trial_number\": 116, \"price_ratio\": 9.262046235032985, \"shift_exponent\": 0.0013934773582265035, \"step\": 116, \"test_objective\": [{\"annualised_returns\": -0.5261240733776554, \"annualised_returns_over_hodl\": -0.08073350581268046, \"annualised_returns_over_uniform_hodl\": 0.6725719965527464, \"calmar\": -0.9093696027957059, \"daily_log_sharpe\": -0.8018892749044929, \"daily_returns\": 0.031016041733451922, \"fee_revenue_over_value\": 0.01790657181553273, \"jax_sharpe\": -0.12993403628014655, \"return\": -0.25975090394128497, \"returns_over_hodl\": -0.03333391575230926, \"returns_over_uniform_hodl\": 0.23017109042251538, \"sharpe\": -0.3254686570069397, \"sterling\": -1.3629118871062078, \"ulcer\": -0.17417006322122278}], \"train_objective\": [{\"annualised_returns\": -0.3892922664238596, \"annualised_returns_over_hodl\": -0.23091243733048095, \"annualised_returns_over_uniform_hodl\": -0.23091243733048084, \"calmar\": -0.5297093627227932, \"daily_log_sharpe\": -0.3637171101493765, \"daily_returns\": 0.008164616954975208, \"fee_revenue_over_value\": 0.04169067437234312, \"jax_sharpe\": 0.16890497697221926, \"return\": -0.2587326200254947, \"returns_over_hodl\": -0.1473447654246013, \"returns_over_uniform_hodl\": -0.14734476542460118, \"sharpe\": 0.19248399948415795, \"sterling\": -1.198811036531649, \"ulcer\": -0.17478986364058868}], \"train_return\": -0.2587326200254947, \"train_returns_over_hodl\": -0.1473447654246013, \"train_sharpe\": 0.1689049769722193, \"validation_return\": -0.32264425671263053, \"validation_returns_over_hodl\": -0.061440279017928257, \"validation_sharpe\": -2.132739634658483}, {\"centeredness_margin\": 0.035290921127417635, \"continuous_test_metrics\": [{\"annualised_returns\": -0.48274890154742955, \"annualised_returns_over_hodl\": 0.003409495694154163, \"annualised_returns_over_uniform_hodl\": 0.8256671289980715, \"calmar\": -0.8327730700369709, \"daily_log_sharpe\": -0.6972771868310501, \"daily_returns\": 0.031016041736122664, \"fee_revenue_over_value\": 0.0019793230890627236, \"jax_sharpe\": -0.013352161883878551, \"return\": -0.23317416942252467, \"returns_over_hodl\": 0.001371737308868859, \"returns_over_uniform_hodl\": 0.27433721052571336, \"sharpe\": -0.20963384940529292, \"sterling\": -1.2428594218120808, \"ulcer\": -0.17641031441481364}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -0.08640897066498465, \"optuna_trial_number\": 117, \"price_ratio\": 4.242773367855104, \"shift_exponent\": 0.001255047979381432, \"step\": 117, \"test_objective\": [{\"annualised_returns\": -0.48274890154742955, \"annualised_returns_over_hodl\": 0.003409495694154163, \"annualised_returns_over_uniform_hodl\": 0.8256671289980715, \"calmar\": -0.8327730700369709, \"daily_log_sharpe\": -0.6972771868310501, \"daily_returns\": 0.031016041736122664, \"fee_revenue_over_value\": 0.0019793230890627236, \"jax_sharpe\": -0.013352161883878551, \"return\": -0.23317416942252467, \"returns_over_hodl\": 0.001371737308868859, \"returns_over_uniform_hodl\": 0.27433721052571336, \"sharpe\": -0.20963384940529292, \"sterling\": -1.2428594218120808, \"ulcer\": -0.17641031441481364}], \"train_objective\": [{\"annualised_returns\": -0.4837035870556565, \"annualised_returns_over_hodl\": -0.34980821755571423, \"annualised_returns_over_uniform_hodl\": -0.349808217555714, \"calmar\": -0.6480092531607193, \"daily_log_sharpe\": -0.45979341250654854, \"daily_returns\": 0.008293117715085866, \"fee_revenue_over_value\": 0.03971224586509617, \"jax_sharpe\": 0.15165191586414678, \"return\": -0.33058581849922886, \"returns_over_hodl\": -0.22999511191863098, \"returns_over_uniform_hodl\": -0.22999511191863087, \"sharpe\": 0.1510746543697793, \"sterling\": -1.405618282258923, \"ulcer\": -0.18636905068905243}], \"train_return\": -0.33058581849922886, \"train_returns_over_hodl\": -0.22999511191863098, \"train_sharpe\": 0.15165191586414678, \"validation_return\": -0.3391427127117269, \"validation_returns_over_hodl\": -2.5365042288783e-09, \"validation_sharpe\": -2.243853179500146}, {\"centeredness_margin\": 0.011841252327506335, \"continuous_test_metrics\": [{\"annualised_returns\": -0.42413027677869797, \"annualised_returns_over_hodl\": 0.11712309702268886, \"annualised_returns_over_uniform_hodl\": 1.0325648943339125, \"calmar\": -0.766184270435102, \"daily_log_sharpe\": -0.6250063350210507, \"daily_returns\": 0.031016041683068873, \"fee_revenue_over_value\": 0.10375714591848867, \"jax_sharpe\": 0.013316700089592824, \"return\": -0.19929321540456757, \"returns_over_hodl\": 0.04561572166789052, \"returns_over_uniform_hodl\": 0.33064173068080405, \"sharpe\": -0.16149972033622068, \"sterling\": -1.1219480019644317, \"ulcer\": -0.16881224048071702}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -3.675411373818361, \"optuna_trial_number\": 118, \"price_ratio\": 6.05314538936366, \"shift_exponent\": 0.006738985179923467, \"step\": 118, \"test_objective\": [{\"annualised_returns\": -0.42413027677869797, \"annualised_returns_over_hodl\": 0.11712309702268886, \"annualised_returns_over_uniform_hodl\": 1.0325648943339125, \"calmar\": -0.766184270435102, \"daily_log_sharpe\": -0.6250063350210507, \"daily_returns\": 0.031016041683068873, \"fee_revenue_over_value\": 0.10375714591848867, \"jax_sharpe\": 0.013316700089592824, \"return\": -0.19929321540456757, \"returns_over_hodl\": 0.04561572166789052, \"returns_over_uniform_hodl\": 0.33064173068080405, \"sharpe\": -0.16149972033622068, \"sterling\": -1.1219480019644317, \"ulcer\": -0.16881224048071702}], \"train_objective\": [{\"annualised_returns\": -0.4237312118721328, \"annualised_returns_over_hodl\": -0.2742827160407614, \"annualised_returns_over_uniform_hodl\": -0.2742827160407614, \"calmar\": -0.5743015048401986, \"daily_log_sharpe\": -0.3927180484190818, \"daily_returns\": 0.008214781867258202, \"fee_revenue_over_value\": 0.04718146359788997, \"jax_sharpe\": 0.1716198099272037, \"return\": -0.28439992416602344, \"returns_over_hodl\": -0.17686901244274644, \"returns_over_uniform_hodl\": -0.17686901244274644, \"sharpe\": 0.1878191289772785, \"sterling\": -1.2734891430178834, \"ulcer\": -0.17938092644743645}], \"train_return\": -0.28439992416602344, \"train_returns_over_hodl\": -0.17686901244274644, \"train_sharpe\": 0.1716198099272037, \"validation_return\": -0.3357486248160165, \"validation_returns_over_hodl\": -0.027558249751749364, \"validation_sharpe\": -2.2454490217601943}, {\"centeredness_margin\": 0.3211904955085244, \"continuous_test_metrics\": [{\"annualised_returns\": -0.6658393804085811, \"annualised_returns_over_hodl\": -0.20837595982052592, \"annualised_returns_over_uniform_hodl\": 0.17943888532819163, \"calmar\": -1.1265163982615505, \"daily_log_sharpe\": -1.2697158742501031, \"daily_returns\": 0.026365963357634133, \"fee_revenue_over_value\": 0.004149394306466898, \"jax_sharpe\": -0.6207417515469975, \"return\": -0.35689959871092614, \"returns_over_hodl\": -0.08981483512072397, \"returns_over_uniform_hodl\": 0.06872608979476125, \"sharpe\": -0.8439010167779193, \"sterling\": -1.8201518576845632, \"ulcer\": -0.16074275537388277}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -2.9851814943607344, \"optuna_trial_number\": 119, \"price_ratio\": 15.867105220788863, \"shift_exponent\": 0.0022927561717300464, \"step\": 119, \"test_objective\": [{\"annualised_returns\": -0.6658393804085811, \"annualised_returns_over_hodl\": -0.20837595982052592, \"annualised_returns_over_uniform_hodl\": 0.17943888532819163, \"calmar\": -1.1265163982615505, \"daily_log_sharpe\": -1.2697158742501031, \"daily_returns\": 0.026365963357634133, \"fee_revenue_over_value\": 0.004149394306466898, \"jax_sharpe\": -0.6207417515469975, \"return\": -0.35689959871092614, \"returns_over_hodl\": -0.08981483512072397, \"returns_over_uniform_hodl\": 0.06872608979476125, \"sharpe\": -0.8439010167779193, \"sterling\": -1.8201518576845632, \"ulcer\": -0.16074275537388277}], \"train_objective\": [{\"annualised_returns\": -0.37863497540800606, \"annualised_returns_over_hodl\": -0.21749130391196858, \"annualised_returns_over_uniform_hodl\": -0.21749130391196858, \"calmar\": -0.5134959711801019, \"daily_log_sharpe\": -0.37655282744839347, \"daily_returns\": 0.008112375015110357, \"fee_revenue_over_value\": 0.022372567873295217, \"jax_sharpe\": 0.13120100371829954, \"return\": -0.25090581560711567, \"returns_over_hodl\": -0.13834185238995733, \"returns_over_uniform_hodl\": -0.13834185238995733, \"sharpe\": 0.15462845964148383, \"sterling\": -1.1923040299771643, \"ulcer\": -0.17108939154911193}], \"train_return\": -0.25090581560711567, \"train_returns_over_hodl\": -0.13834185238995733, \"train_sharpe\": 0.1312010037182995, \"validation_return\": -0.25298130436122435, \"validation_returns_over_hodl\": -0.049056880781733536, \"validation_sharpe\": -1.8544456876612818}, {\"centeredness_margin\": 0.022804053022095566, \"continuous_test_metrics\": [{\"annualised_returns\": -0.43462434102212333, \"annualised_returns_over_hodl\": 0.09676578266595293, \"annualised_returns_over_uniform_hodl\": 0.9955254985817685, \"calmar\": -0.7851416505698373, \"daily_log_sharpe\": -0.6496060651896404, \"daily_returns\": 0.031016041662071, \"fee_revenue_over_value\": 0.10860807914623818, \"jax_sharpe\": -0.013893148927402498, \"return\": -0.2052019635642356, \"returns_over_hodl\": 0.03789968904371199, \"returns_over_uniform_hodl\": 0.3208223722981842, \"sharpe\": -0.1897811162281606, \"sterling\": -1.1551513167248257, \"ulcer\": -0.16762731909265563}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -3.504103884239927, \"optuna_trial_number\": 120, \"price_ratio\": 4.95169726553448, \"shift_exponent\": 0.007352082972652339, \"step\": 120, \"test_objective\": [{\"annualised_returns\": -0.43462434102212333, \"annualised_returns_over_hodl\": 0.09676578266595293, \"annualised_returns_over_uniform_hodl\": 0.9955254985817685, \"calmar\": -0.7851416505698373, \"daily_log_sharpe\": -0.6496060651896404, \"daily_returns\": 0.031016041662071, \"fee_revenue_over_value\": 0.10860807914623818, \"jax_sharpe\": -0.013893148927402498, \"return\": -0.2052019635642356, \"returns_over_hodl\": 0.03789968904371199, \"returns_over_uniform_hodl\": 0.3208223722981842, \"sharpe\": -0.1897811162281606, \"sterling\": -1.1551513167248257, \"ulcer\": -0.16762731909265563}], \"train_objective\": [{\"annualised_returns\": -0.44483471990408685, \"annualised_returns_over_hodl\": -0.30085916933214363, \"annualised_returns_over_uniform_hodl\": -0.30085916933214363, \"calmar\": -0.6005050020090074, \"daily_log_sharpe\": -0.4161979688955777, \"daily_returns\": 0.008205378996783702, \"fee_revenue_over_value\": 0.04821031424968282, \"jax_sharpe\": 0.21594438204719765, \"return\": -0.3004265621944312, \"returns_over_hodl\": -0.19530392159528065, \"returns_over_uniform_hodl\": -0.19530392159528065, \"sharpe\": 0.1761366835772999, \"sterling\": -1.3199255691625114, \"ulcer\": -0.18190662386456713}], \"train_return\": -0.3004265621944312, \"train_returns_over_hodl\": -0.19530392159528065, \"train_sharpe\": 0.21594438204719765, \"validation_return\": -0.33618202061855007, \"validation_returns_over_hodl\": -0.020036361473006914, \"validation_sharpe\": -2.2550994156731208}, {\"centeredness_margin\": 0.9875627644227661, \"continuous_test_metrics\": [{\"annualised_returns\": -0.7186200739411153, \"annualised_returns_over_hodl\": -0.17738419861385613, \"annualised_returns_over_uniform_hodl\": -0.006853570147191679, \"calmar\": -1.2017529534490248, \"daily_log_sharpe\": -1.5570839143679134, \"daily_returns\": 0.02170716055359086, \"fee_revenue_over_value\": 3.415243773708968e-05, \"jax_sharpe\": -0.9870754129744244, \"return\": -0.3999195111591457, \"returns_over_hodl\": -0.07562829453270803, \"returns_over_uniform_hodl\": -0.002765861884852394, \"sharpe\": -1.1606549970691036, \"sterling\": -2.099197532517697, \"ulcer\": -0.15315628806430254}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -3.0241236200238926, \"optuna_trial_number\": 121, \"price_ratio\": 35.03197682083335, \"shift_exponent\": 0.0019798037119742136, \"step\": 121, \"test_objective\": [{\"annualised_returns\": -0.7186200739411153, \"annualised_returns_over_hodl\": -0.17738419861385613, \"annualised_returns_over_uniform_hodl\": -0.006853570147191679, \"calmar\": -1.2017529534490248, \"daily_log_sharpe\": -1.5570839143679134, \"daily_returns\": 0.02170716055359086, \"fee_revenue_over_value\": 3.415243773708968e-05, \"jax_sharpe\": -0.9870754129744244, \"return\": -0.3999195111591457, \"returns_over_hodl\": -0.07562829453270803, \"returns_over_uniform_hodl\": -0.002765861884852394, \"sharpe\": -1.1606549970691036, \"sterling\": -2.099197532517697, \"ulcer\": -0.15315628806430254}], \"train_objective\": [{\"annualised_returns\": -0.35728181404313963, \"annualised_returns_over_hodl\": -0.19060045264793013, \"annualised_returns_over_uniform_hodl\": -0.19060045264793013, \"calmar\": -0.48370390162461263, \"daily_log_sharpe\": -0.36884642486449176, \"daily_returns\": 0.007966693972931514, \"fee_revenue_over_value\": 0.00013119560592683875, \"jax_sharpe\": 0.1417328353586599, \"return\": -0.2353808093969061, \"returns_over_hodl\": -0.1204839536484722, \"returns_over_uniform_hodl\": -0.1204839536484722, \"sharpe\": 0.13981447582986453, \"sterling\": -1.152671874228591, \"ulcer\": -0.16732699698455}], \"train_return\": -0.2353808093969061, \"train_returns_over_hodl\": -0.1204839536484722, \"train_sharpe\": 0.1417328353586599, \"validation_return\": -0.20018981169032368, \"validation_returns_over_hodl\": -0.039949783478093304, \"validation_sharpe\": -1.5533828729136747}, {\"centeredness_margin\": 0.23050088636988506, \"continuous_test_metrics\": [{\"annualised_returns\": -0.5173204017014812, \"annualised_returns_over_hodl\": -0.06365535977853387, \"annualised_returns_over_uniform_hodl\": 0.7036450557338019, \"calmar\": -0.9345309852663597, \"daily_log_sharpe\": -0.8281464118027553, \"daily_returns\": 0.03101604163850026, \"fee_revenue_over_value\": 0.09031876411317592, \"jax_sharpe\": -0.18491164213358452, \"return\": -0.2542427380310128, \"returns_over_hodl\": -0.02614098781787899, \"returns_over_uniform_hodl\": 0.23932474761729594, \"sharpe\": -0.37548042368962575, \"sterling\": -1.3733998389850357, \"ulcer\": -0.16733613919250762}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -3.3587652712653457, \"optuna_trial_number\": 122, \"price_ratio\": 3.730967769051276, \"shift_exponent\": 0.004268985781176623, \"step\": 122, \"test_objective\": [{\"annualised_returns\": -0.5173204017014812, \"annualised_returns_over_hodl\": -0.06365535977853387, \"annualised_returns_over_uniform_hodl\": 0.7036450557338019, \"calmar\": -0.9345309852663597, \"daily_log_sharpe\": -0.8281464118027553, \"daily_returns\": 0.03101604163850026, \"fee_revenue_over_value\": 0.09031876411317592, \"jax_sharpe\": -0.18491164213358452, \"return\": -0.2542427380310128, \"returns_over_hodl\": -0.02614098781787899, \"returns_over_uniform_hodl\": 0.23932474761729594, \"sharpe\": -0.37548042368962575, \"sterling\": -1.3733998389850357, \"ulcer\": -0.16733613919250762}], \"train_objective\": [{\"annualised_returns\": -0.45811780235465727, \"annualised_returns_over_hodl\": -0.3175870621440231, \"annualised_returns_over_uniform_hodl\": -0.317587062144023, \"calmar\": -0.6090582101931502, \"daily_log_sharpe\": -0.44900632473207963, \"daily_returns\": 0.008319177802896609, \"fee_revenue_over_value\": 0.02911420636831149, \"jax_sharpe\": 0.13570439549787072, \"return\": -0.31063700896265145, \"returns_over_hodl\": -0.20704865921556548, \"returns_over_uniform_hodl\": -0.20704865921556537, \"sharpe\": 0.1389333987265708, \"sterling\": -1.361884835672956, \"ulcer\": -0.18140887296125385}], \"train_return\": -0.31063700896265145, \"train_returns_over_hodl\": -0.20704865921556548, \"train_sharpe\": 0.13570439549787072, \"validation_return\": -0.3227826381184046, \"validation_returns_over_hodl\": -0.06456831544065, \"validation_sharpe\": -2.1642518103438486}, {\"centeredness_margin\": 0.01072910845773083, \"continuous_test_metrics\": [{\"annualised_returns\": -0.47548671820648825, \"annualised_returns_over_hodl\": 0.017497321702534263, \"annualised_returns_over_uniform_hodl\": 0.851299417551886, \"calmar\": -0.8202453130035191, \"daily_log_sharpe\": -0.6781148730501914, \"daily_returns\": 0.031016041747570673, \"fee_revenue_over_value\": 0.0014619557969666511, \"jax_sharpe\": 0.010781331177424526, \"return\": -0.22885625768227635, \"returns_over_hodl\": 0.007010351010996674, \"returns_over_uniform_hodl\": 0.28151286291371447, \"sharpe\": -0.18742341124306444, \"sterling\": -1.2241625623525216, \"ulcer\": -0.17641031443847552}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -0.08763139106193152, \"optuna_trial_number\": 123, \"price_ratio\": 4.386867014096508, \"shift_exponent\": 0.0005325483777798759, \"step\": 123, \"test_objective\": [{\"annualised_returns\": -0.47548671820648825, \"annualised_returns_over_hodl\": 0.017497321702534263, \"annualised_returns_over_uniform_hodl\": 0.851299417551886, \"calmar\": -0.8202453130035191, \"daily_log_sharpe\": -0.6781148730501914, \"daily_returns\": 0.031016041747570673, \"fee_revenue_over_value\": 0.0014619557969666511, \"jax_sharpe\": 0.010781331177424526, \"return\": -0.22885625768227635, \"returns_over_hodl\": 0.007010351010996674, \"returns_over_uniform_hodl\": 0.28151286291371447, \"sharpe\": -0.18742341124306444, \"sterling\": -1.2241625623525216, \"ulcer\": -0.17641031443847552}], \"train_objective\": [{\"annualised_returns\": -0.46809290923607216, \"annualised_returns_over_hodl\": -0.33014909503964684, \"annualised_returns_over_uniform_hodl\": -0.3301490950396466, \"calmar\": -0.630530284320201, \"daily_log_sharpe\": -0.4369496200065877, \"daily_returns\": 0.00826661900425145, \"fee_revenue_over_value\": 0.047343150816587305, \"jax_sharpe\": 0.168412294741068, \"return\": -0.3183694624112755, \"returns_over_hodl\": -0.21594304346502025, \"returns_over_uniform_hodl\": -0.21594304346502013, \"sharpe\": 0.1710082103813401, \"sterling\": -1.3678962223222184, \"ulcer\": -0.18514214827615727}], \"train_return\": -0.3183694624112755, \"train_returns_over_hodl\": -0.21594304346502025, \"train_sharpe\": 0.168412294741068, \"validation_return\": -0.3391427128615768, \"validation_returns_over_hodl\": -2.572119628396763e-09, \"validation_sharpe\": -2.243853180219095}, {\"centeredness_margin\": 0.21858190374549444, \"continuous_test_metrics\": [{\"annualised_returns\": -0.5279519197237776, \"annualised_returns_over_hodl\": -0.0842793194086855, \"annualised_returns_over_uniform_hodl\": 0.6661205090625155, \"calmar\": -0.9537366900071541, \"daily_log_sharpe\": -0.8832174186121605, \"daily_returns\": 0.031016041636621843, \"fee_revenue_over_value\": 0.13070703818056265, \"jax_sharpe\": -0.2637567941189371, \"return\": -0.2609021716985518, \"returns_over_hodl\": -0.03483731513850985, \"returns_over_uniform_hodl\": 0.22825787456063074, \"sharpe\": -0.4470692819994666, \"sterling\": -1.4445507288623076, \"ulcer\": -0.1608104539708425}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -3.3631029037555646, \"optuna_trial_number\": 124, \"price_ratio\": 2.5769077098775752, \"shift_exponent\": 0.006657282830684741, \"step\": 124, \"test_objective\": [{\"annualised_returns\": -0.5279519197237776, \"annualised_returns_over_hodl\": -0.0842793194086855, \"annualised_returns_over_uniform_hodl\": 0.6661205090625155, \"calmar\": -0.9537366900071541, \"daily_log_sharpe\": -0.8832174186121605, \"daily_returns\": 0.031016041636621843, \"fee_revenue_over_value\": 0.13070703818056265, \"jax_sharpe\": -0.2637567941189371, \"return\": -0.2609021716985518, \"returns_over_hodl\": -0.03483731513850985, \"returns_over_uniform_hodl\": 0.22825787456063074, \"sharpe\": -0.4470692819994666, \"sterling\": -1.4445507288623076, \"ulcer\": -0.1608104539708425}], \"train_objective\": [{\"annualised_returns\": -0.47734734761692554, \"annualised_returns_over_hodl\": -0.3418035625809823, \"annualised_returns_over_uniform_hodl\": -0.3418035625809823, \"calmar\": -0.6281778226412762, \"daily_log_sharpe\": -0.4714539814233929, \"daily_returns\": 0.008523312521133924, \"fee_revenue_over_value\": 0.03619492534702099, \"jax_sharpe\": 0.1368794400695896, \"return\": -0.32559437705089234, \"returns_over_hodl\": -0.22425362268819005, \"returns_over_uniform_hodl\": -0.22425362268819005, \"sharpe\": 0.12960354609576707, \"sterling\": -1.3953768331782745, \"ulcer\": -0.18408389271402767}], \"train_return\": -0.32559437705089234, \"train_returns_over_hodl\": -0.22425362268819005, \"train_sharpe\": 0.1368794400695896, \"validation_return\": -0.3184231413400034, \"validation_returns_over_hodl\": -0.06489001029827979, \"validation_sharpe\": -2.198558424601976}, {\"centeredness_margin\": 0.9608631512168265, \"continuous_test_metrics\": [{\"annualised_returns\": -0.6662039625441065, \"annualised_returns_over_hodl\": 0.17608584123825843, \"annualised_returns_over_uniform_hodl\": 0.17815207197460015, \"calmar\": -1.1666199521265816, \"daily_log_sharpe\": -1.4284927327393215, \"daily_returns\": 0.0181278471500987, \"fee_revenue_over_value\": 0.06833384850446571, \"jax_sharpe\": -0.8864377412956571, \"return\": -0.3571822707660104, \"returns_over_hodl\": 0.06750141222727613, \"returns_over_uniform_hodl\": 0.06825633577265378, \"sharpe\": -1.0447437107933826, \"sterling\": -2.0483052411425957, \"ulcer\": -0.14644929178054045}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -2.6001452001044916, \"optuna_trial_number\": 125, \"price_ratio\": 11.848343715716304, \"shift_exponent\": 0.11581713736702266, \"step\": 125, \"test_objective\": [{\"annualised_returns\": -0.6662039625441065, \"annualised_returns_over_hodl\": 0.17608584123825843, \"annualised_returns_over_uniform_hodl\": 0.17815207197460015, \"calmar\": -1.1666199521265816, \"daily_log_sharpe\": -1.4284927327393215, \"daily_returns\": 0.0181278471500987, \"fee_revenue_over_value\": 0.06833384850446571, \"jax_sharpe\": -0.8864377412956571, \"return\": -0.3571822707660104, \"returns_over_hodl\": 0.06750141222727613, \"returns_over_uniform_hodl\": 0.06825633577265378, \"sharpe\": -1.0447437107933826, \"sterling\": -2.0483052411425957, \"ulcer\": -0.14644929178054045}], \"train_objective\": [{\"annualised_returns\": -0.33571187972152916, \"annualised_returns_over_hodl\": -0.16343661092415296, \"annualised_returns_over_uniform_hodl\": -0.16343661092415296, \"calmar\": -0.45800308697093084, \"daily_log_sharpe\": -0.35832390471096714, \"daily_returns\": 0.008062249131859092, \"fee_revenue_over_value\": 0.028976266176790767, \"jax_sharpe\": 0.11070786336692497, \"return\": -0.21990261220595708, \"returns_over_hodl\": -0.1026798977663601, \"returns_over_uniform_hodl\": -0.1026798977663601, \"sharpe\": 0.12638192344107899, \"sterling\": -1.1144352807326212, \"ulcer\": -0.16290851845267793}], \"train_return\": -0.21990261220595708, \"train_returns_over_hodl\": -0.1026798977663601, \"train_sharpe\": 0.11070786336692498, \"validation_return\": -0.15868284347227812, \"validation_returns_over_hodl\": -0.023554593889872133, \"validation_sharpe\": -1.2621868860859087}, {\"centeredness_margin\": 0.1308968356364485, \"continuous_test_metrics\": [{\"annualised_returns\": -0.48278020248263454, \"annualised_returns_over_hodl\": 0.003348778782816364, \"annualised_returns_over_uniform_hodl\": 0.8255566505695453, \"calmar\": -0.8328270660527682, \"daily_log_sharpe\": -0.6889125008562446, \"daily_returns\": 0.031016041652805706, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": 0.006820828389897479, \"return\": -0.23319285828605363, \"returns_over_hodl\": 0.0013473335311433576, \"returns_over_uniform_hodl\": 0.274306152737533, \"sharpe\": -0.1983213327788759, \"sterling\": -1.2429400090164833, \"ulcer\": -0.17641031424257098}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -0.10411694417121609, \"optuna_trial_number\": 126, \"price_ratio\": 4.030359332069249, \"shift_exponent\": 0.0002432665269735209, \"step\": 126, \"test_objective\": [{\"annualised_returns\": -0.48278020248263454, \"annualised_returns_over_hodl\": 0.003348778782816364, \"annualised_returns_over_uniform_hodl\": 0.8255566505695453, \"calmar\": -0.8328270660527682, \"daily_log_sharpe\": -0.6889125008562446, \"daily_returns\": 0.031016041652805706, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": 0.006820828389897479, \"return\": -0.23319285828605363, \"returns_over_hodl\": 0.0013473335311433576, \"returns_over_uniform_hodl\": 0.274306152737533, \"sharpe\": -0.1983213327788759, \"sterling\": -1.2429400090164833, \"ulcer\": -0.17641031424257098}], \"train_objective\": [{\"annualised_returns\": -0.5107273655253333, \"annualised_returns_over_hodl\": -0.3838402934157352, \"annualised_returns_over_uniform_hodl\": -0.3838402934157352, \"calmar\": -0.6787842388195807, \"daily_log_sharpe\": -0.49911554448074735, \"daily_returns\": 0.008248187983322074, \"fee_revenue_over_value\": 0.027412522062637928, \"jax_sharpe\": 0.17684702396882832, \"return\": -0.35208247328811515, \"returns_over_hodl\": -0.25472199957991537, \"returns_over_uniform_hodl\": -0.25472199957991537, \"sharpe\": 0.11885583893470127, \"sterling\": -1.468706124342389, \"ulcer\": -0.1891588020968776}], \"train_return\": -0.35208247328811515, \"train_returns_over_hodl\": -0.25472199957991537, \"train_sharpe\": 0.17684702396882832, \"validation_return\": -0.3391427121398696, \"validation_returns_over_hodl\": -3.062347264659593e-09, \"validation_sharpe\": -2.2438531795649745}, {\"centeredness_margin\": 0.03666816571413914, \"continuous_test_metrics\": [{\"annualised_returns\": -0.4790188227652733, \"annualised_returns_over_hodl\": 0.010645432405792077, \"annualised_returns_over_uniform_hodl\": 0.8388326539075963, \"calmar\": -0.8263384319759969, \"daily_log_sharpe\": -0.6819875610540356, \"daily_returns\": 0.031016041748171384, \"fee_revenue_over_value\": 0.0001332117715507852, \"jax_sharpe\": 0.007413038085588697, \"return\": -0.23095186760740127, \"returns_over_hodl\": 0.004273765367990068, \"returns_over_uniform_hodl\": 0.27803030716266175, \"sharpe\": -0.19124457520675567, \"sterling\": -1.2332561444869867, \"ulcer\": -0.17641031443971367}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -0.08617257165477665, \"optuna_trial_number\": 127, \"price_ratio\": 4.597194830141803, \"shift_exponent\": 0.0002211378948443446, \"step\": 127, \"test_objective\": [{\"annualised_returns\": -0.4790188227652733, \"annualised_returns_over_hodl\": 0.010645432405792077, \"annualised_returns_over_uniform_hodl\": 0.8388326539075963, \"calmar\": -0.8263384319759969, \"daily_log_sharpe\": -0.6819875610540356, \"daily_returns\": 0.031016041748171384, \"fee_revenue_over_value\": 0.0001332117715507852, \"jax_sharpe\": 0.007413038085588697, \"return\": -0.23095186760740127, \"returns_over_hodl\": 0.004273765367990068, \"returns_over_uniform_hodl\": 0.27803030716266175, \"sharpe\": -0.19124457520675567, \"sterling\": -1.2332561444869867, \"ulcer\": -0.17641031443971367}], \"train_objective\": [{\"annualised_returns\": -0.46940934516423594, \"annualised_returns_over_hodl\": -0.33180693305894504, \"annualised_returns_over_uniform_hodl\": -0.33180693305894504, \"calmar\": -0.6318948959515132, \"daily_log_sharpe\": -0.4414910026283523, \"daily_returns\": 0.00819061739860305, \"fee_revenue_over_value\": 0.04250900579489872, \"jax_sharpe\": 0.21259954660452302, \"return\": -0.31939416975998214, \"returns_over_hodl\": -0.21712173027680548, \"returns_over_uniform_hodl\": -0.21712173027680548, \"sharpe\": 0.16309075999756165, \"sterling\": -1.3750598758878596, \"ulcer\": -0.1846557961039671}], \"train_return\": -0.31939416975998214, \"train_returns_over_hodl\": -0.21712173027680548, \"train_sharpe\": 0.212599546604523, \"validation_return\": -0.33914271283971376, \"validation_returns_over_hodl\": -2.529026210673635e-09, \"validation_sharpe\": -2.2438531799559858}, {\"centeredness_margin\": 0.04671576985016236, \"continuous_test_metrics\": [{\"annualised_returns\": -0.48181788565667094, \"annualised_returns_over_hodl\": 0.005215563498816023, \"annualised_returns_over_uniform_hodl\": 0.8289532024610715, \"calmar\": -0.8311670046136383, \"daily_log_sharpe\": -0.6870260380270132, \"daily_returns\": 0.031016041718685418, \"fee_revenue_over_value\": 1.0952158107117487e-06, \"jax_sharpe\": 0.006988586527083794, \"return\": -0.23261859589317946, \"returns_over_hodl\": 0.0020972421478702152, \"returns_over_uniform_hodl\": 0.2752604814868578, \"sharpe\": -0.19638202456500872, \"sterling\": -1.240462474404052, \"ulcer\": -0.1764103143787639}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -0.09452773893757527, \"optuna_trial_number\": 128, \"price_ratio\": 4.215540421342608, \"shift_exponent\": 0.000171795821530596, \"step\": 128, \"test_objective\": [{\"annualised_returns\": -0.48181788565667094, \"annualised_returns_over_hodl\": 0.005215563498816023, \"annualised_returns_over_uniform_hodl\": 0.8289532024610715, \"calmar\": -0.8311670046136383, \"daily_log_sharpe\": -0.6870260380270132, \"daily_returns\": 0.031016041718685418, \"fee_revenue_over_value\": 1.0952158107117487e-06, \"jax_sharpe\": 0.006988586527083794, \"return\": -0.23261859589317946, \"returns_over_hodl\": 0.0020972421478702152, \"returns_over_uniform_hodl\": 0.2752604814868578, \"sharpe\": -0.19638202456500872, \"sterling\": -1.240462474404052, \"ulcer\": -0.1764103143787639}], \"train_objective\": [{\"annualised_returns\": -0.4872088572725609, \"annualised_returns_over_hodl\": -0.35422253815360616, \"annualised_returns_over_uniform_hodl\": -0.35422253815360616, \"calmar\": -0.6525915533748209, \"daily_log_sharpe\": -0.46370804569062196, \"daily_returns\": 0.008208181507700717, \"fee_revenue_over_value\": 0.03915133846070152, \"jax_sharpe\": 0.15309210518948418, \"return\": -0.333348771439833, \"returns_over_hodl\": -0.23317324487218494, \"returns_over_uniform_hodl\": -0.23317324487218494, \"sharpe\": 0.1495307752720706, \"sterling\": -1.4122307628820665, \"ulcer\": -0.18700188713860422}], \"train_return\": -0.333348771439833, \"train_returns_over_hodl\": -0.23317324487218494, \"train_sharpe\": 0.15309210518948418, \"validation_return\": -0.33914271267863194, \"validation_returns_over_hodl\": -2.7765153509662355e-09, \"validation_sharpe\": -2.243853180382353}, {\"centeredness_margin\": 0.0825462429835203, \"continuous_test_metrics\": [{\"annualised_returns\": -0.48450647726130336, \"annualised_returns_over_hodl\": 3.419664551529422e-11, \"annualised_returns_over_uniform_hodl\": 0.8194636656953462, \"calmar\": -0.8358050067090862, \"daily_log_sharpe\": -0.6924636575122382, \"daily_returns\": 0.031016041604311255, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": 0.005019211456391046, \"return\": -0.23422461374256853, \"returns_over_hodl\": 1.3772316620475067e-11, \"returns_over_uniform_hodl\": 0.27259154647627826, \"sharpe\": -0.20210860077678475, \"sterling\": -1.2473843919538636, \"ulcer\": -0.17641031414231925}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -0.12453277690151443, \"optuna_trial_number\": 129, \"price_ratio\": 3.1027629622035136, \"shift_exponent\": 9.163736177494884e-05, \"step\": 129, \"test_objective\": [{\"annualised_returns\": -0.48450647726130336, \"annualised_returns_over_hodl\": 3.419664551529422e-11, \"annualised_returns_over_uniform_hodl\": 0.8194636656953462, \"calmar\": -0.8358050067090862, \"daily_log_sharpe\": -0.6924636575122382, \"daily_returns\": 0.031016041604311255, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": 0.005019211456391046, \"return\": -0.23422461374256853, \"returns_over_hodl\": 1.3772316620475067e-11, \"returns_over_uniform_hodl\": 0.27259154647627826, \"sharpe\": -0.20210860077678475, \"sterling\": -1.2473843919538636, \"ulcer\": -0.17641031414231925}], \"train_objective\": [{\"annualised_returns\": -0.5433483714577764, \"annualised_returns_over_hodl\": -0.4249211714938621, \"annualised_returns_over_uniform_hodl\": -0.4249211714938622, \"calmar\": -0.7138960294856731, \"daily_log_sharpe\": -0.5350181585835919, \"daily_returns\": 0.00841669178551376, \"fee_revenue_over_value\": 0.02918481639890477, \"jax_sharpe\": 0.14051500755740204, \"return\": -0.3786636236842338, \"returns_over_hodl\": -0.2852974135783919, \"returns_over_uniform_hodl\": -0.285297413578392, \"sharpe\": 0.10779970285574084, \"sterling\": -1.5135310259176584, \"ulcer\": -0.1967399821559791}], \"train_return\": -0.3786636236842338, \"train_returns_over_hodl\": -0.2852974135783919, \"train_sharpe\": 0.14051500755740207, \"validation_return\": -0.3391427120071854, \"validation_returns_over_hodl\": -3.6713154738521325e-09, \"validation_sharpe\": -2.2438531816073386}, {\"centeredness_margin\": 0.0327670582911752, \"continuous_test_metrics\": [{\"annualised_returns\": -0.48450647673433744, \"annualised_returns_over_hodl\": 3.360733913382319e-11, \"annualised_returns_over_uniform_hodl\": 0.8194636675553029, \"calmar\": -0.8358050058647996, \"daily_log_sharpe\": -0.6924636561891014, \"daily_returns\": 0.031016041629347398, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": 0.005019213040404038, \"return\": -0.23422461342729817, \"returns_over_hodl\": 1.3534950937810208e-11, \"returns_over_uniform_hodl\": 0.2725915470002054, \"sharpe\": -0.20210859922481933, \"sterling\": -1.2473843901880073, \"ulcer\": -0.1764103141940741}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -0.11814324164667167, \"optuna_trial_number\": 130, \"price_ratio\": 3.2606594351127014, \"shift_exponent\": 0.000221007076950093, \"step\": 130, \"test_objective\": [{\"annualised_returns\": -0.48450647673433744, \"annualised_returns_over_hodl\": 3.360733913382319e-11, \"annualised_returns_over_uniform_hodl\": 0.8194636675553029, \"calmar\": -0.8358050058647996, \"daily_log_sharpe\": -0.6924636561891014, \"daily_returns\": 0.031016041629347398, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": 0.005019213040404038, \"return\": -0.23422461342729817, \"returns_over_hodl\": 1.3534950937810208e-11, \"returns_over_uniform_hodl\": 0.2725915470002054, \"sharpe\": -0.20210859922481933, \"sterling\": -1.2473843901880073, \"ulcer\": -0.1764103141940741}], \"train_objective\": [{\"annualised_returns\": -0.5236726577704165, \"annualised_returns_over_hodl\": -0.4001427941266982, \"annualised_returns_over_uniform_hodl\": -0.4001427941266982, \"calmar\": -0.6925181327801647, \"daily_log_sharpe\": -0.5061169261660384, \"daily_returns\": 0.008387445123181623, \"fee_revenue_over_value\": 0.038063479531558655, \"jax_sharpe\": 0.156867236741487, \"return\": -0.3625449762515609, \"returns_over_hodl\": -0.26675666906564055, \"returns_over_uniform_hodl\": -0.26675666906564055, \"sharpe\": 0.13051340113250376, \"sterling\": -1.4700040152896758, \"ulcer\": -0.19446096416526262}], \"train_return\": -0.3625449762515609, \"train_returns_over_hodl\": -0.26675666906564055, \"train_sharpe\": 0.15686723674148703, \"validation_return\": -0.33914271215691993, \"validation_returns_over_hodl\": -3.4798463000029756e-09, \"validation_sharpe\": -2.2438531813723523}, {\"centeredness_margin\": 0.08190484935622022, \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -Infinity, \"optuna_trial_number\": 131, \"price_ratio\": 1.480037790460145, \"shift_exponent\": 60.98437655636361, \"step\": 131, \"test_objective\": -Infinity, \"train_objective\": -Infinity, \"train_return\": -Infinity, \"train_returns_over_hodl\": -Infinity, \"train_sharpe\": -Infinity, \"validation_return\": -Infinity, \"validation_returns_over_hodl\": -Infinity, \"validation_sharpe\": -Infinity}, {\"centeredness_margin\": 0.02410837631289648, \"continuous_test_metrics\": [{\"annualised_returns\": -0.5093794722324875, \"annualised_returns_over_hodl\": -0.04825084625482423, \"annualised_returns_over_uniform_hodl\": 0.7316730172956147, \"calmar\": -0.8816354469442431, \"daily_log_sharpe\": -0.7627915935719296, \"daily_returns\": 0.031016041762631848, \"fee_revenue_over_value\": 0.018605184202259217, \"jax_sharpe\": -0.09006759919624512, \"return\": -0.24932560522468405, \"returns_over_hodl\": -0.019719873552533573, \"returns_over_uniform_hodl\": 0.24749620592547883, \"sharpe\": -0.28355727138478215, \"sterling\": -1.3168788849735404, \"ulcer\": -0.17480337382752026}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -2.9725088004147113, \"optuna_trial_number\": 132, \"price_ratio\": 10.610639831662185, \"shift_exponent\": 0.00042260605194938654, \"step\": 132, \"test_objective\": [{\"annualised_returns\": -0.5093794722324875, \"annualised_returns_over_hodl\": -0.04825084625482423, \"annualised_returns_over_uniform_hodl\": 0.7316730172956147, \"calmar\": -0.8816354469442431, \"daily_log_sharpe\": -0.7627915935719296, \"daily_returns\": 0.031016041762631848, \"fee_revenue_over_value\": 0.018605184202259217, \"jax_sharpe\": -0.09006759919624512, \"return\": -0.24932560522468405, \"returns_over_hodl\": -0.019719873552533573, \"returns_over_uniform_hodl\": 0.24749620592547883, \"sharpe\": -0.28355727138478215, \"sterling\": -1.3168788849735404, \"ulcer\": -0.17480337382752026}], \"train_objective\": [{\"annualised_returns\": -0.3820034041005912, \"annualised_returns_over_hodl\": -0.2217332947543581, \"annualised_returns_over_uniform_hodl\": -0.2217332947543582, \"calmar\": -0.5202008545018444, \"daily_log_sharpe\": -0.3587304247790586, \"daily_returns\": 0.00814545931511892, \"fee_revenue_over_value\": 0.040200363620818046, \"jax_sharpe\": 0.16722512148657034, \"return\": -0.2533738767292627, \"returns_over_hodl\": -0.14118078108411236, \"returns_over_uniform_hodl\": -0.14118078108411247, \"sharpe\": 0.19176631776229436, \"sterling\": -1.183140462054524, \"ulcer\": -0.17373525975684925}], \"train_return\": -0.2533738767292627, \"train_returns_over_hodl\": -0.14118078108411236, \"train_sharpe\": 0.16722512148657032, \"validation_return\": -0.31280371989533895, \"validation_returns_over_hodl\": -0.06193421053652426, \"validation_sharpe\": -2.0702045214299933}, {\"centeredness_margin\": 0.14391695035124788, \"continuous_test_metrics\": [{\"annualised_returns\": -0.49125028455162667, \"annualised_returns_over_hodl\": -0.013082240821809754, \"annualised_returns_over_uniform_hodl\": 0.7956610148530892, \"calmar\": -0.8474385313735698, \"daily_log_sharpe\": -0.713060208564345, \"daily_returns\": 0.031016041755079476, \"fee_revenue_over_value\": 9.013546252820436e-07, \"jax_sharpe\": -0.030974853205666057, \"return\": -0.23827513590381233, \"returns_over_hodl\": -0.005289441411953644, \"returns_over_uniform_hodl\": 0.2658602511725654, \"sharpe\": -0.2265270443286656, \"sterling\": -1.2647466647235959, \"ulcer\": -0.17641031445399963}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -4.157583095239243, \"optuna_trial_number\": 133, \"price_ratio\": 6.487096060840618, \"shift_exponent\": 0.0003203233188400897, \"step\": 133, \"test_objective\": [{\"annualised_returns\": -0.49125028455162667, \"annualised_returns_over_hodl\": -0.013082240821809754, \"annualised_returns_over_uniform_hodl\": 0.7956610148530892, \"calmar\": -0.8474385313735698, \"daily_log_sharpe\": -0.713060208564345, \"daily_returns\": 0.031016041755079476, \"fee_revenue_over_value\": 9.013546252820436e-07, \"jax_sharpe\": -0.030974853205666057, \"return\": -0.23827513590381233, \"returns_over_hodl\": -0.005289441411953644, \"returns_over_uniform_hodl\": 0.2658602511725654, \"sharpe\": -0.2265270443286656, \"sterling\": -1.2647466647235959, \"ulcer\": -0.17641031445399963}], \"train_objective\": [{\"annualised_returns\": -0.440691189153548, \"annualised_returns_over_hodl\": -0.2956410628786401, \"annualised_returns_over_uniform_hodl\": -0.2956410628786399, \"calmar\": -0.5944188100501042, \"daily_log_sharpe\": -0.4212598670228166, \"daily_returns\": 0.008199025020077893, \"fee_revenue_over_value\": 0.031898097637588926, \"jax_sharpe\": 0.1387685513817892, \"return\": -0.297261209519592, \"returns_over_hodl\": -0.19166292159933895, \"returns_over_uniform_hodl\": -0.19166292159933884, \"sharpe\": 0.15553247714418636, \"sterling\": -1.3253076337311835, \"ulcer\": -0.17947480220840822}], \"train_return\": -0.297261209519592, \"train_returns_over_hodl\": -0.19166292159933895, \"train_sharpe\": 0.13876855138178917, \"validation_return\": -0.3381251350627359, \"validation_returns_over_hodl\": -0.024799497774849688, \"validation_sharpe\": -2.2346275911451388}, {\"centeredness_margin\": 0.06976865717113706, \"continuous_test_metrics\": [{\"annualised_returns\": -0.4921425644096661, \"annualised_returns_over_hodl\": -0.01481316638322161, \"annualised_returns_over_uniform_hodl\": 0.7925116624175637, \"calmar\": -0.8489777747438333, \"daily_log_sharpe\": -0.7152341246138035, \"daily_returns\": 0.03101604180629814, \"fee_revenue_over_value\": 0.0003949128425808077, \"jax_sharpe\": -0.03217970005221269, \"return\": -0.23881346156150707, \"returns_over_hodl\": -0.00599242339269157, \"returns_over_uniform_hodl\": 0.2649656433103542, \"sharpe\": -0.22898216725952017, \"sterling\": -1.2670438838799511, \"ulcer\": -0.1764103145598839}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -3.993271183176665, \"optuna_trial_number\": 134, \"price_ratio\": 7.067236453756142, \"shift_exponent\": 2.778903432279709e-05, \"step\": 134, \"test_objective\": [{\"annualised_returns\": -0.4921425644096661, \"annualised_returns_over_hodl\": -0.01481316638322161, \"annualised_returns_over_uniform_hodl\": 0.7925116624175637, \"calmar\": -0.8489777747438333, \"daily_log_sharpe\": -0.7152341246138035, \"daily_returns\": 0.03101604180629814, \"fee_revenue_over_value\": 0.0003949128425808077, \"jax_sharpe\": -0.03217970005221269, \"return\": -0.23881346156150707, \"returns_over_hodl\": -0.00599242339269157, \"returns_over_uniform_hodl\": 0.2649656433103542, \"sharpe\": -0.22898216725952017, \"sterling\": -1.2670438838799511, \"ulcer\": -0.1764103145598839}], \"train_objective\": [{\"annualised_returns\": -0.41393407630106605, \"annualised_returns_over_hodl\": -0.2619448092103165, \"annualised_returns_over_uniform_hodl\": -0.2619448092103164, \"calmar\": -0.5619017362245401, \"daily_log_sharpe\": -0.38583120633234347, \"daily_returns\": 0.008155610870328577, \"fee_revenue_over_value\": 0.042368789932680485, \"jax_sharpe\": 0.16455772062974813, \"return\": -0.2770382180537563, \"returns_over_hodl\": -0.1684010865342218, \"returns_over_uniform_hodl\": -0.1684010865342217, \"sharpe\": 0.18473521984799995, \"sterling\": -1.2566304574962237, \"ulcer\": -0.17746474356819483}], \"train_return\": -0.2770382180537563, \"train_returns_over_hodl\": -0.1684010865342218, \"train_sharpe\": 0.16455772062974813, \"validation_return\": -0.33610877165335773, \"validation_returns_over_hodl\": -0.0400604943980154, \"validation_sharpe\": -2.2171617420251897}, {\"centeredness_margin\": 0.039146017770938714, \"continuous_test_metrics\": [{\"annualised_returns\": -0.4787556331133983, \"annualised_returns_over_hodl\": 0.01115598981792898, \"annualised_returns_over_uniform_hodl\": 0.8397615967316088, \"calmar\": -0.8258844121136937, \"daily_log_sharpe\": -0.6831408358716431, \"daily_returns\": 0.031016041776879978, \"fee_revenue_over_value\": 0.00036583677286076554, \"jax_sharpe\": 0.0023900719687147635, \"return\": -0.2307954242199255, \"returns_over_hodl\": 0.004478058985347255, \"returns_over_uniform_hodl\": 0.278290290097575, \"sharpe\": -0.19304861790834413, \"sterling\": -1.232578548919817, \"ulcer\": -0.17641031449906738}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -0.08874907400162768, \"optuna_trial_number\": 135, \"price_ratio\": 5.52275720911931, \"shift_exponent\": 1.0898074891223681e-05, \"step\": 135, \"test_objective\": [{\"annualised_returns\": -0.4787556331133983, \"annualised_returns_over_hodl\": 0.01115598981792898, \"annualised_returns_over_uniform_hodl\": 0.8397615967316088, \"calmar\": -0.8258844121136937, \"daily_log_sharpe\": -0.6831408358716431, \"daily_returns\": 0.031016041776879978, \"fee_revenue_over_value\": 0.00036583677286076554, \"jax_sharpe\": 0.0023900719687147635, \"return\": -0.2307954242199255, \"returns_over_hodl\": 0.004478058985347255, \"returns_over_uniform_hodl\": 0.278290290097575, \"sharpe\": -0.19304861790834413, \"sterling\": -1.232578548919817, \"ulcer\": -0.17641031449906738}], \"train_objective\": [{\"annualised_returns\": -0.4394521920664649, \"annualised_returns_over_hodl\": -0.29408074654814376, \"annualised_returns_over_uniform_hodl\": -0.29408074654814376, \"calmar\": -0.5949065459743487, \"daily_log_sharpe\": -0.40719445566533585, \"daily_returns\": 0.0082373036274122, \"fee_revenue_over_value\": 0.04513661987159511, \"jax_sharpe\": 0.16549174588157053, \"return\": -0.29631649791914516, \"returns_over_hodl\": -0.19057625123848654, \"returns_over_uniform_hodl\": -0.19057625123848654, \"sharpe\": 0.18070622620172594, \"sterling\": -1.311233560152055, \"ulcer\": -0.1808306198240988}], \"train_return\": -0.29631649791914516, \"train_returns_over_hodl\": -0.19057625123848654, \"train_sharpe\": 0.16549174588157053, \"validation_return\": -0.339142712927462, \"validation_returns_over_hodl\": -2.6113025075602536e-09, \"validation_sharpe\": -2.2438531788386804}, {\"centeredness_margin\": 0.08539836082750257, \"continuous_test_metrics\": [{\"annualised_returns\": -0.4801611152191517, \"annualised_returns_over_hodl\": 0.008429514535703175, \"annualised_returns_over_uniform_hodl\": 0.8348008678157197, \"calmar\": -0.8283089635294761, \"daily_log_sharpe\": -0.683807396598195, \"daily_returns\": 0.031016041694571835, \"fee_revenue_over_value\": 5.379183339714206e-07, \"jax_sharpe\": 0.007622927517388545, \"return\": -0.23163140884790123, \"returns_over_hodl\": 0.0033863764893267145, \"returns_over_uniform_hodl\": 0.2769010224485513, \"sharpe\": -0.19308740881947453, \"sterling\": -1.2361970352151406, \"ulcer\": -0.1764103143289128}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -0.09287694675639668, \"optuna_trial_number\": 136, \"price_ratio\": 4.613498413460795, \"shift_exponent\": 0.00024002207012637148, \"step\": 136, \"test_objective\": [{\"annualised_returns\": -0.4801611152191517, \"annualised_returns_over_hodl\": 0.008429514535703175, \"annualised_returns_over_uniform_hodl\": 0.8348008678157197, \"calmar\": -0.8283089635294761, \"daily_log_sharpe\": -0.683807396598195, \"daily_returns\": 0.031016041694571835, \"fee_revenue_over_value\": 5.379183339714206e-07, \"jax_sharpe\": 0.007622927517388545, \"return\": -0.23163140884790123, \"returns_over_hodl\": 0.0033863764893267145, \"returns_over_uniform_hodl\": 0.2769010224485513, \"sharpe\": -0.19308740881947453, \"sterling\": -1.2361970352151406, \"ulcer\": -0.1764103143289128}], \"train_objective\": [{\"annualised_returns\": -0.4798246104877164, \"annualised_returns_over_hodl\": -0.34492327428371716, \"annualised_returns_over_uniform_hodl\": -0.3449232742837173, \"calmar\": -0.6433850321305677, \"daily_log_sharpe\": -0.4580303966109516, \"daily_returns\": 0.008242833051504419, \"fee_revenue_over_value\": 0.03589138369664954, \"jax_sharpe\": 0.14393942787425523, \"return\": -0.3275368756517705, \"returns_over_hodl\": -0.22648801427878051, \"returns_over_uniform_hodl\": -0.22648801427878062, \"sharpe\": 0.14681401166816313, \"sterling\": -1.4020781355041347, \"ulcer\": -0.18526610904271634}], \"train_return\": -0.3275368756517705, \"train_returns_over_hodl\": -0.22648801427878051, \"train_sharpe\": 0.14393942787425523, \"validation_return\": -0.3391427123802008, \"validation_returns_over_hodl\": -2.72861644390332e-09, \"validation_sharpe\": -2.2438531790756904}, {\"centeredness_margin\": 0.0830305852897564, \"continuous_test_metrics\": [{\"annualised_returns\": -0.48910845342658704, \"annualised_returns_over_hodl\": -0.00892732874377411, \"annualised_returns_over_uniform_hodl\": 0.8032207294531117, \"calmar\": -0.8437437276630172, \"daily_log_sharpe\": -0.7080827272314895, \"daily_returns\": 0.031016041795714266, \"fee_revenue_over_value\": 0.0002763167149625582, \"jax_sharpe\": -0.02592474084682123, \"return\": -0.23698523464802224, \"returns_over_hodl\": -0.0036050039573052306, \"returns_over_uniform_hodl\": 0.2680038528909885, \"sharpe\": -0.2210753409130207, \"sterling\": -1.2592324106520987, \"ulcer\": -0.17641031453800776}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -4.079504868712497, \"optuna_trial_number\": 137, \"price_ratio\": 6.673725154855541, \"shift_exponent\": 7.690799969691686e-05, \"step\": 137, \"test_objective\": [{\"annualised_returns\": -0.48910845342658704, \"annualised_returns_over_hodl\": -0.00892732874377411, \"annualised_returns_over_uniform_hodl\": 0.8032207294531117, \"calmar\": -0.8437437276630172, \"daily_log_sharpe\": -0.7080827272314895, \"daily_returns\": 0.031016041795714266, \"fee_revenue_over_value\": 0.0002763167149625582, \"jax_sharpe\": -0.02592474084682123, \"return\": -0.23698523464802224, \"returns_over_hodl\": -0.0036050039573052306, \"returns_over_uniform_hodl\": 0.2680038528909885, \"sharpe\": -0.2210753409130207, \"sterling\": -1.2592324106520987, \"ulcer\": -0.17641031453800776}], \"train_objective\": [{\"annualised_returns\": -0.4247123902856226, \"annualised_returns_over_hodl\": -0.2755183514733699, \"annualised_returns_over_uniform_hodl\": -0.2755183514733701, \"calmar\": -0.575889044283067, \"daily_log_sharpe\": -0.3981430095307851, \"daily_returns\": 0.00819573570239758, \"fee_revenue_over_value\": 0.040303307977900035, \"jax_sharpe\": 0.1572558278814469, \"return\": -0.2851398942167367, \"returns_over_hodl\": -0.17772017540258334, \"returns_over_uniform_hodl\": -0.17772017540258345, \"sharpe\": 0.17635645440180855, \"sterling\": -1.2838799571291186, \"ulcer\": -0.1783460120457618}], \"train_return\": -0.2851398942167367, \"train_returns_over_hodl\": -0.17772017540258334, \"train_sharpe\": 0.15725582788144687, \"validation_return\": -0.33736651292144804, \"validation_returns_over_hodl\": -0.031237025904241045, \"validation_sharpe\": -2.2279894536012694}, {\"centeredness_margin\": 0.03965262312803014, \"continuous_test_metrics\": [{\"annualised_returns\": -0.4773596189994235, \"annualised_returns_over_hodl\": 0.013864103551577411, \"annualised_returns_over_uniform_hodl\": 0.8446889078327859, \"calmar\": -0.8234761933258473, \"daily_log_sharpe\": -0.6791986943676556, \"daily_returns\": 0.031016041730778716, \"fee_revenue_over_value\": 0.0003141921823139079, \"jax_sharpe\": 0.008739124707620836, \"return\": -0.22996640286506664, \"returns_over_hodl\": 0.005560650501718056, \"returns_over_uniform_hodl\": 0.27966798594282527, \"sharpe\": -0.18840760141520208, \"sterling\": -1.2289844405705048, \"ulcer\": -0.1764103144037671}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -0.09043117712762738, \"optuna_trial_number\": 138, \"price_ratio\": 4.265590114543135, \"shift_exponent\": 0.0005352896202090403, \"step\": 138, \"test_objective\": [{\"annualised_returns\": -0.4773596189994235, \"annualised_returns_over_hodl\": 0.013864103551577411, \"annualised_returns_over_uniform_hodl\": 0.8446889078327859, \"calmar\": -0.8234761933258473, \"daily_log_sharpe\": -0.6791986943676556, \"daily_returns\": 0.031016041730778716, \"fee_revenue_over_value\": 0.0003141921823139079, \"jax_sharpe\": 0.008739124707620836, \"return\": -0.22996640286506664, \"returns_over_hodl\": 0.005560650501718056, \"returns_over_uniform_hodl\": 0.27966798594282527, \"sharpe\": -0.18840760141520208, \"sterling\": -1.2289844405705048, \"ulcer\": -0.1764103144037671}], \"train_objective\": [{\"annualised_returns\": -0.48518319746700034, \"annualised_returns_over_hodl\": -0.3516715474308697, \"annualised_returns_over_uniform_hodl\": -0.3516715474308697, \"calmar\": -0.6500278840157077, \"daily_log_sharpe\": -0.4616626327455839, \"daily_returns\": 0.008232775206195598, \"fee_revenue_over_value\": 0.039490899049904785, \"jax_sharpe\": 0.1528292875036146, \"return\": -0.33175118748767407, \"returns_over_hodl\": -0.23133559714036556, \"returns_over_uniform_hodl\": -0.23133559714036556, \"sharpe\": 0.14991067968836655, \"sterling\": -1.4087722381897942, \"ulcer\": -0.18658870308493128}], \"train_return\": -0.33175118748767407, \"train_returns_over_hodl\": -0.23133559714036556, \"train_sharpe\": 0.1528292875036146, \"validation_return\": -0.3391427127312189, \"validation_returns_over_hodl\": -2.6552308129978996e-09, \"validation_sharpe\": -2.243853180080743}, {\"centeredness_margin\": 0.02906450189650559, \"continuous_test_metrics\": [{\"annualised_returns\": -0.4845064787766422, \"annualised_returns_over_hodl\": 3.63635788147576e-11, \"annualised_returns_over_uniform_hodl\": 0.8194636603468723, \"calmar\": -0.8358050091369924, \"daily_log_sharpe\": -0.692463661317175, \"daily_returns\": 0.0310160415323032, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": 0.00501920690109751, \"return\": -0.23422461464915734, \"returns_over_hodl\": 1.464495191783044e-11, \"returns_over_uniform_hodl\": 0.2725915449696783, \"sharpe\": -0.20210860523981972, \"sterling\": -1.2473843970319731, \"ulcer\": -0.17641031399345802}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -0.14657120405694812, \"optuna_trial_number\": 139, \"price_ratio\": 2.3806918216463777, \"shift_exponent\": 1.1226105536393093e-05, \"step\": 139, \"test_objective\": [{\"annualised_returns\": -0.4845064787766422, \"annualised_returns_over_hodl\": 3.63635788147576e-11, \"annualised_returns_over_uniform_hodl\": 0.8194636603468723, \"calmar\": -0.8358050091369924, \"daily_log_sharpe\": -0.692463661317175, \"daily_returns\": 0.0310160415323032, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": 0.00501920690109751, \"return\": -0.23422461464915734, \"returns_over_hodl\": 1.464495191783044e-11, \"returns_over_uniform_hodl\": 0.2725915449696783, \"sharpe\": -0.20210860523981972, \"sterling\": -1.2473843970319731, \"ulcer\": -0.17641031399345802}], \"train_objective\": [{\"annualised_returns\": -0.5874896789314021, \"annualised_returns_over_hodl\": -0.48051000509048825, \"annualised_returns_over_uniform_hodl\": -0.48051000509048825, \"calmar\": -0.7618330574670735, \"daily_log_sharpe\": -0.5970166864937382, \"daily_returns\": 0.008497886718781766, \"fee_revenue_over_value\": 0.030411942016579464, \"jax_sharpe\": 0.12813996110025785, \"return\": -0.41585285997318044, \"returns_over_hodl\": -0.3280749562684835, \"returns_over_uniform_hodl\": -0.3280749562684835, \"sharpe\": 0.06652594849095506, \"sterling\": -1.609807256470633, \"ulcer\": -0.20249666197393362}], \"train_return\": -0.41585285997318044, \"train_returns_over_hodl\": -0.3280749562684835, \"train_sharpe\": 0.12813996110025785, \"validation_return\": -0.33914271165057586, \"validation_returns_over_hodl\": -4.334019920726462e-09, \"validation_sharpe\": -2.243853183045163}, {\"centeredness_margin\": 0.5431379750818347, \"continuous_test_metrics\": [{\"annualised_returns\": -0.4839988251665448, \"annualised_returns_over_hodl\": 0.000984783918468235, \"annualised_returns_over_uniform_hodl\": 0.8212554525956364, \"calmar\": -0.8349292727881552, \"daily_log_sharpe\": -0.6960490176619125, \"daily_returns\": 0.03101604171726362, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": -0.013159661717680578, \"return\": -0.23392098763031444, \"returns_over_hodl\": 0.0003964931551956319, \"returns_over_uniform_hodl\": 0.2730961226622961, \"sharpe\": -0.2076569861177189, \"sterling\": -1.2460774139854986, \"ulcer\": -0.17641031437582938}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -4.254166170385008, \"optuna_trial_number\": 140, \"price_ratio\": 6.490846653758195, \"shift_exponent\": 0.00010140330977957809, \"step\": 140, \"test_objective\": [{\"annualised_returns\": -0.4839988251665448, \"annualised_returns_over_hodl\": 0.000984783918468235, \"annualised_returns_over_uniform_hodl\": 0.8212554525956364, \"calmar\": -0.8349292727881552, \"daily_log_sharpe\": -0.6960490176619125, \"daily_returns\": 0.03101604171726362, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": -0.013159661717680578, \"return\": -0.23392098763031444, \"returns_over_hodl\": 0.0003964931551956319, \"returns_over_uniform_hodl\": 0.2730961226622961, \"sharpe\": -0.2076569861177189, \"sterling\": -1.2460774139854986, \"ulcer\": -0.17641031437582938}], \"train_objective\": [{\"annualised_returns\": -0.46869987961371207, \"annualised_returns_over_hodl\": -0.330913476007312, \"annualised_returns_over_uniform_hodl\": -0.330913476007312, \"calmar\": -0.6282507223211156, \"daily_log_sharpe\": -0.46380749610357785, \"daily_returns\": 0.008205584433689706, \"fee_revenue_over_value\": 0.016456289440138962, \"jax_sharpe\": 0.10106775543545449, \"return\": -0.3188418012730547, \"returns_over_hodl\": -0.21648635916171688, \"returns_over_uniform_hodl\": -0.21648635916171688, \"sharpe\": 0.1149900349346627, \"sterling\": -1.4034543145225058, \"ulcer\": -0.18071560184568627}], \"train_return\": -0.3188418012730547, \"train_returns_over_hodl\": -0.21648635916171688, \"train_sharpe\": 0.10106775543545447, \"validation_return\": -0.3388331968965177, \"validation_returns_over_hodl\": -0.01462229573963525, \"validation_sharpe\": -2.2411068554402576}, {\"centeredness_margin\": 0.43159225058918715, \"continuous_test_metrics\": [{\"annualised_returns\": -0.6888337208658766, \"annualised_returns_over_hodl\": 0.04055080136885736, \"annualised_returns_over_uniform_hodl\": 0.09827905473244436, \"calmar\": -1.1922947438551348, \"daily_log_sharpe\": -1.4979807548190163, \"daily_returns\": 0.01919046818390737, \"fee_revenue_over_value\": 0.06968976218448343, \"jax_sharpe\": -0.9895462061638607, \"return\": -0.37510231794983206, \"returns_over_hodl\": 0.016137733933780485, \"returns_over_uniform_hodl\": 0.03847619270741154, \"sharpe\": -1.1162397035257112, \"sterling\": -2.1203817619330683, \"ulcer\": -0.1493538702756114}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -2.53366682565281, \"optuna_trial_number\": 141, \"price_ratio\": 29.042995845440572, \"shift_exponent\": 120.84145115540673, \"step\": 141, \"test_objective\": [{\"annualised_returns\": -0.6888337208658766, \"annualised_returns_over_hodl\": 0.04055080136885736, \"annualised_returns_over_uniform_hodl\": 0.09827905473244436, \"calmar\": -1.1922947438551348, \"daily_log_sharpe\": -1.4979807548190163, \"daily_returns\": 0.01919046818390737, \"fee_revenue_over_value\": 0.06968976218448343, \"jax_sharpe\": -0.9895462061638607, \"return\": -0.37510231794983206, \"returns_over_hodl\": 0.016137733933780485, \"returns_over_uniform_hodl\": 0.03847619270741154, \"sharpe\": -1.1162397035257112, \"sterling\": -2.1203817619330683, \"ulcer\": -0.1493538702756114}], \"train_objective\": [{\"annualised_returns\": -0.3127469078797772, \"annualised_returns_over_hodl\": -0.13451594519568266, \"annualised_returns_over_uniform_hodl\": -0.13451594519568266, \"calmar\": -0.42677778415374196, \"daily_log_sharpe\": -0.3124469575025961, \"daily_returns\": 0.008062061975875563, \"fee_revenue_over_value\": 0.031885918809979735, \"jax_sharpe\": 0.16207314881736812, \"return\": -0.20363884679399713, \"returns_over_hodl\": -0.08397222886436284, \"returns_over_uniform_hodl\": -0.08397222886436284, \"sharpe\": 0.1816982206414253, \"sterling\": -1.0270735724329247, \"ulcer\": -0.16439484498073478}], \"train_return\": -0.20363884679399713, \"train_returns_over_hodl\": -0.08397222886436284, \"train_sharpe\": 0.16207314881736812, \"validation_return\": -0.18317737928896238, \"validation_returns_over_hodl\": -0.0251836373727935, \"validation_sharpe\": -1.4530498630727045}, {\"centeredness_margin\": 0.4329506165017902, \"continuous_test_metrics\": [{\"annualised_returns\": -0.5122742047758273, \"annualised_returns_over_hodl\": -0.05386630087085953, \"annualised_returns_over_uniform_hodl\": 0.7214558943790521, \"calmar\": -0.8838184273297586, \"daily_log_sharpe\": -0.7609626869062243, \"daily_returns\": 0.03101604162787257, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": -0.07815916346316584, \"return\": -0.2511125194137235, \"returns_over_hodl\": -0.022053342288181454, \"returns_over_uniform_hodl\": 0.24452665123351625, \"sharpe\": -0.27829047956800873, \"sterling\": -1.3191644572734442, \"ulcer\": -0.17632601676622273}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -3.6162058702625326, \"optuna_trial_number\": 142, \"price_ratio\": 9.151821072561347, \"shift_exponent\": 0.00010952086625209128, \"step\": 142, \"test_objective\": [{\"annualised_returns\": -0.5122742047758273, \"annualised_returns_over_hodl\": -0.05386630087085953, \"annualised_returns_over_uniform_hodl\": 0.7214558943790521, \"calmar\": -0.8838184273297586, \"daily_log_sharpe\": -0.7609626869062243, \"daily_returns\": 0.03101604162787257, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": -0.07815916346316584, \"return\": -0.2511125194137235, \"returns_over_hodl\": -0.022053342288181454, \"returns_over_uniform_hodl\": 0.24452665123351625, \"sharpe\": -0.27829047956800873, \"sterling\": -1.3191644572734442, \"ulcer\": -0.17632601676622273}], \"train_objective\": [{\"annualised_returns\": -0.4379100944577602, \"annualised_returns_over_hodl\": -0.29213872415990194, \"annualised_returns_over_uniform_hodl\": -0.29213872415990194, \"calmar\": -0.589612587916872, \"daily_log_sharpe\": -0.43675965570572267, \"daily_returns\": 0.00815802513744742, \"fee_revenue_over_value\": 0.01567269885059702, \"jax_sharpe\": 0.10313552089618157, \"return\": -0.2951418231476941, \"returns_over_hodl\": -0.1892250618837953, \"returns_over_uniform_hodl\": -0.1892250618837953, \"sharpe\": 0.12165416327808921, \"sterling\": -1.3383486881139908, \"ulcer\": -0.17675943771276617}], \"train_return\": -0.2951418231476941, \"train_returns_over_hodl\": -0.1892250618837953, \"train_sharpe\": 0.10313552089618157, \"validation_return\": -0.32853248555430725, \"validation_returns_over_hodl\": -0.059566905194930686, \"validation_sharpe\": -2.1546749948722983}, {\"centeredness_margin\": 0.03136944975166441, \"continuous_test_metrics\": [{\"annualised_returns\": -0.49661155544222446, \"annualised_returns_over_hodl\": -0.023482511322511, \"annualised_returns_over_uniform_hodl\": 0.7767381047541053, \"calmar\": -0.8566870874081722, \"daily_log_sharpe\": -0.7278907893429118, \"daily_returns\": 0.031016041814404056, \"fee_revenue_over_value\": 0.00197021808440972, \"jax_sharpe\": -0.04447162956713673, \"return\": -0.2415182065118232, \"returns_over_hodl\": -0.00952445783921041, \"returns_over_uniform_hodl\": 0.2604708062851413, \"sharpe\": -0.24297843594117136, \"sterling\": -1.2785495293848523, \"ulcer\": -0.17641031457664902}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -3.9188895652089064, \"optuna_trial_number\": 143, \"price_ratio\": 7.350117796029405, \"shift_exponent\": 0.00018709567744456748, \"step\": 143, \"test_objective\": [{\"annualised_returns\": -0.49661155544222446, \"annualised_returns_over_hodl\": -0.023482511322511, \"annualised_returns_over_uniform_hodl\": 0.7767381047541053, \"calmar\": -0.8566870874081722, \"daily_log_sharpe\": -0.7278907893429118, \"daily_returns\": 0.031016041814404056, \"fee_revenue_over_value\": 0.00197021808440972, \"jax_sharpe\": -0.04447162956713673, \"return\": -0.2415182065118232, \"returns_over_hodl\": -0.00952445783921041, \"returns_over_uniform_hodl\": 0.2604708062851413, \"sharpe\": -0.24297843594117136, \"sterling\": -1.2785495293848523, \"ulcer\": -0.17641031457664902}], \"train_objective\": [{\"annualised_returns\": -0.40522989985137325, \"annualised_returns_over_hodl\": -0.25098330752514897, \"annualised_returns_over_uniform_hodl\": -0.2509833075251491, \"calmar\": -0.55040796668596, \"daily_log_sharpe\": -0.37551798409054965, \"daily_returns\": 0.008189418470615106, \"fee_revenue_over_value\": 0.04424189106988231, \"jax_sharpe\": 0.1711408507781109, \"return\": -0.2705382403131328, \"returns_over_hodl\": -0.1609243781360229, \"returns_over_uniform_hodl\": -0.160924378136023, \"sharpe\": 0.19256904287321622, \"sterling\": -1.2334034048496676, \"ulcer\": -0.17696270953047188}], \"train_return\": -0.2705382403131328, \"train_returns_over_hodl\": -0.1609243781360229, \"train_sharpe\": 0.1711408507781109, \"validation_return\": -0.33474267386863554, \"validation_returns_over_hodl\": -0.0466081968736165, \"validation_sharpe\": -2.2049578786314936}, {\"centeredness_margin\": 0.09719487896882623, \"continuous_test_metrics\": [{\"annualised_returns\": -0.6026695271113323, \"annualised_returns_over_hodl\": 0.1489183590520129, \"annualised_returns_over_uniform_hodl\": 0.40240047024011405, \"calmar\": -1.0717462898485484, \"daily_log_sharpe\": -1.1444020847382097, \"daily_returns\": 0.022162404864731904, \"fee_revenue_over_value\": 0.06443489704165269, \"jax_sharpe\": -0.5677119875497593, \"return\": -0.3104539130629619, \"returns_over_hodl\": 0.057500858968277724, \"returns_over_uniform_hodl\": 0.14591110773424387, \"sharpe\": -0.7377855340484365, \"sterling\": -1.7545891928060662, \"ulcer\": -0.15317296353637563}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -2.577607900704269, \"optuna_trial_number\": 144, \"price_ratio\": 170.187166047835, \"shift_exponent\": 0.002132096947013982, \"step\": 144, \"test_objective\": [{\"annualised_returns\": -0.6026695271113323, \"annualised_returns_over_hodl\": 0.1489183590520129, \"annualised_returns_over_uniform_hodl\": 0.40240047024011405, \"calmar\": -1.0717462898485484, \"daily_log_sharpe\": -1.1444020847382097, \"daily_returns\": 0.022162404864731904, \"fee_revenue_over_value\": 0.06443489704165269, \"jax_sharpe\": -0.5677119875497593, \"return\": -0.3104539130629619, \"returns_over_hodl\": 0.057500858968277724, \"returns_over_uniform_hodl\": 0.14591110773424387, \"sharpe\": -0.7377855340484365, \"sterling\": -1.7545891928060662, \"ulcer\": -0.15317296353637563}], \"train_objective\": [{\"annualised_returns\": -0.3177402419361096, \"annualised_returns_over_hodl\": -0.14080424139342618, \"annualised_returns_over_uniform_hodl\": -0.1408042413934264, \"calmar\": -0.4358317588824158, \"daily_log_sharpe\": -0.3100494438332109, \"daily_returns\": 0.007986732421405894, \"fee_revenue_over_value\": 0.02622471144344188, \"jax_sharpe\": 0.16378783314654052, \"return\": -0.20715672679988062, \"returns_over_hodl\": -0.08801872933458166, \"returns_over_uniform_hodl\": -0.08801872933458177, \"sharpe\": 0.19551419874787196, \"sterling\": -1.0348111719374367, \"ulcer\": -0.16537862652506638}], \"train_return\": -0.20715672679988062, \"train_returns_over_hodl\": -0.08801872933458166, \"train_sharpe\": 0.1637878331465405, \"validation_return\": -0.20098684980084502, \"validation_returns_over_hodl\": -0.033090240669702564, \"validation_sharpe\": -1.535167377945217}, {\"centeredness_margin\": 0.016685044980917812, \"continuous_test_metrics\": [{\"annualised_returns\": -0.5074510834708448, \"annualised_returns_over_hodl\": -0.04450998486720792, \"annualised_returns_over_uniform_hodl\": 0.7384793749516778, \"calmar\": -0.8754772406142383, \"daily_log_sharpe\": -0.7532976491144107, \"daily_returns\": 0.031016041671071037, \"fee_revenue_over_value\": 0.0043121274757022335, \"jax_sharpe\": -0.07093730764254169, \"return\": -0.24813870585103914, \"returns_over_hodl\": -0.018169941225415043, \"returns_over_uniform_hodl\": 0.2494686356176894, \"sharpe\": -0.27068798645618813, \"sterling\": -1.306722708972618, \"ulcer\": -0.17633235231843292}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -3.8497133472161504, \"optuna_trial_number\": 145, \"price_ratio\": 7.476559815204743, \"shift_exponent\": 0.0007347892446286765, \"step\": 145, \"test_objective\": [{\"annualised_returns\": -0.5074510834708448, \"annualised_returns_over_hodl\": -0.04450998486720792, \"annualised_returns_over_uniform_hodl\": 0.7384793749516778, \"calmar\": -0.8754772406142383, \"daily_log_sharpe\": -0.7532976491144107, \"daily_returns\": 0.031016041671071037, \"fee_revenue_over_value\": 0.0043121274757022335, \"jax_sharpe\": -0.07093730764254169, \"return\": -0.24813870585103914, \"returns_over_hodl\": -0.018169941225415043, \"returns_over_uniform_hodl\": 0.2494686356176894, \"sharpe\": -0.27068798645618813, \"sterling\": -1.306722708972618, \"ulcer\": -0.17633235231843292}], \"train_objective\": [{\"annualised_returns\": -0.40414457000580184, \"annualised_returns_over_hodl\": -0.24961651021813736, \"annualised_returns_over_uniform_hodl\": -0.24961651021813747, \"calmar\": -0.5489928285211996, \"daily_log_sharpe\": -0.37472853060223843, \"daily_returns\": 0.008183360690914809, \"fee_revenue_over_value\": 0.04448321852921537, \"jax_sharpe\": 0.21060057499672144, \"return\": -0.2697303821733761, \"returns_over_hodl\": -0.15999512576330743, \"returns_over_uniform_hodl\": -0.15999512576330754, \"sharpe\": 0.19241980096242695, \"sterling\": -1.2311367143996776, \"ulcer\": -0.17683262525829643}], \"train_return\": -0.2697303821733761, \"train_returns_over_hodl\": -0.15999512576330743, \"train_sharpe\": 0.2106005749967214, \"validation_return\": -0.33400808932035697, \"validation_returns_over_hodl\": -0.04701983660536846, \"validation_sharpe\": -2.1981835153387848}, {\"centeredness_margin\": 0.04291147733711436, \"continuous_test_metrics\": [{\"annualised_returns\": -0.48429970081221896, \"annualised_returns_over_hodl\": 0.00040112117801505143, \"annualised_returns_over_uniform_hodl\": 0.8201934949161522, \"calmar\": -0.8354483034775118, \"daily_log_sharpe\": -0.6920310664127558, \"daily_returns\": 0.031016041656407534, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": 0.005310882842589692, \"return\": -0.23410091944838163, \"returns_over_hodl\": 0.00016152732306906614, \"returns_over_uniform_hodl\": 0.2727971058556402, \"sharpe\": -0.20164202603802542, \"sterling\": -1.2468520346224805, \"ulcer\": -0.1764103142500182}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -0.11023730311501867, \"optuna_trial_number\": 146, \"price_ratio\": 3.667871461491271, \"shift_exponent\": 8.178120271766013e-05, \"step\": 146, \"test_objective\": [{\"annualised_returns\": -0.48429970081221896, \"annualised_returns_over_hodl\": 0.00040112117801505143, \"annualised_returns_over_uniform_hodl\": 0.8201934949161522, \"calmar\": -0.8354483034775118, \"daily_log_sharpe\": -0.6920310664127558, \"daily_returns\": 0.031016041656407534, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": 0.005310882842589692, \"return\": -0.23410091944838163, \"returns_over_hodl\": 0.00016152732306906614, \"returns_over_uniform_hodl\": 0.2727971058556402, \"sharpe\": -0.20164202603802542, \"sterling\": -1.2468520346224805, \"ulcer\": -0.1764103142500182}], \"train_objective\": [{\"annualised_returns\": -0.5047609694527863, \"annualised_returns_over_hodl\": -0.3763265830743152, \"annualised_returns_over_uniform_hodl\": -0.3763265830743153, \"calmar\": -0.6726637301451706, \"daily_log_sharpe\": -0.482820286565194, \"daily_returns\": 0.0083339653905083, \"fee_revenue_over_value\": 0.03930484567866176, \"jax_sharpe\": 0.15822759621792631, \"return\": -0.34729704635057035, \"returns_over_hodl\": -0.24921748199529414, \"returns_over_uniform_hodl\": -0.24921748199529425, \"sharpe\": 0.14315643509015014, \"sterling\": -1.4382833925069753, \"ulcer\": -0.19085291578946373}], \"train_return\": -0.34729704635057035, \"train_returns_over_hodl\": -0.24921748199529414, \"train_sharpe\": 0.1582275962179263, \"validation_return\": -0.33914271229958703, \"validation_returns_over_hodl\": -3.243875945635466e-09, \"validation_sharpe\": -2.2438531809263838}, {\"centeredness_margin\": 0.01671257210799693, \"continuous_test_metrics\": [{\"annualised_returns\": -0.4793006902659882, \"annualised_returns_over_hodl\": 0.010098643671891727, \"annualised_returns_over_uniform_hodl\": 0.837837786555303, \"calmar\": -0.8268246722901461, \"daily_log_sharpe\": -0.6903303621608876, \"daily_returns\": 0.031016041680078526, \"fee_revenue_over_value\": 0.002261503373169532, \"jax_sharpe\": -0.007070506324768311, \"return\": -0.23111946570915576, \"returns_over_hodl\": 0.004054905839477874, \"returns_over_uniform_hodl\": 0.27775178694468927, \"sharpe\": -0.20215235874174817, \"sterling\": -1.2339818277619974, \"ulcer\": -0.17641031429895537}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -0.0998406977983406, \"optuna_trial_number\": 147, \"price_ratio\": 3.4479225852509994, \"shift_exponent\": 0.0017374505711678393, \"step\": 147, \"test_objective\": [{\"annualised_returns\": -0.4793006902659882, \"annualised_returns_over_hodl\": 0.010098643671891727, \"annualised_returns_over_uniform_hodl\": 0.837837786555303, \"calmar\": -0.8268246722901461, \"daily_log_sharpe\": -0.6903303621608876, \"daily_returns\": 0.031016041680078526, \"fee_revenue_over_value\": 0.002261503373169532, \"jax_sharpe\": -0.007070506324768311, \"return\": -0.23111946570915576, \"returns_over_hodl\": 0.004054905839477874, \"returns_over_uniform_hodl\": 0.27775178694468927, \"sharpe\": -0.20215235874174817, \"sterling\": -1.2339818277619974, \"ulcer\": -0.17641031429895537}], \"train_objective\": [{\"annualised_returns\": -0.5055100451146899, \"annualised_returns_over_hodl\": -0.3772699226513254, \"annualised_returns_over_uniform_hodl\": -0.3772699226513254, \"calmar\": -0.6724826053323956, \"daily_log_sharpe\": -0.4831588877178361, \"daily_returns\": 0.008352598541471067, \"fee_revenue_over_value\": 0.04158731193517659, \"jax_sharpe\": 0.15962278689543213, \"return\": -0.34789660412912227, \"returns_over_hodl\": -0.2499071333844195, \"returns_over_uniform_hodl\": -0.2499071333844195, \"sharpe\": 0.1440134867642061, \"sterling\": -1.4407354974600641, \"ulcer\": -0.19079269954373357}], \"train_return\": -0.34789660412912227, \"train_returns_over_hodl\": -0.2499071333844195, \"train_sharpe\": 0.15962278689543213, \"validation_return\": -0.3391427123566193, \"validation_returns_over_hodl\": -2.9349245256327094e-09, \"validation_sharpe\": -2.243853179840343}, {\"centeredness_margin\": 0.019439408554018014, \"continuous_test_metrics\": [{\"annualised_returns\": -0.484506477146339, \"annualised_returns_over_hodl\": 3.4030334106205373e-11, \"annualised_returns_over_uniform_hodl\": 0.8194636661011196, \"calmar\": -0.8358050065248905, \"daily_log_sharpe\": -0.6924636572235667, \"daily_returns\": 0.031016041609775235, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": 0.0050192118019901, \"return\": -0.23422461367378822, \"returns_over_hodl\": 1.3705259149787707e-11, \"returns_over_uniform_hodl\": 0.2725915465905797, \"sharpe\": -0.20210860043818044, \"sterling\": -1.2473843915686054, \"ulcer\": -0.1764103141536127}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -0.12534736171397687, \"optuna_trial_number\": 148, \"price_ratio\": 3.0197006780995608, \"shift_exponent\": 7.170116420330015e-05, \"step\": 148, \"test_objective\": [{\"annualised_returns\": -0.484506477146339, \"annualised_returns_over_hodl\": 3.4030334106205373e-11, \"annualised_returns_over_uniform_hodl\": 0.8194636661011196, \"calmar\": -0.8358050065248905, \"daily_log_sharpe\": -0.6924636572235667, \"daily_returns\": 0.031016041609775235, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": 0.0050192118019901, \"return\": -0.23422461367378822, \"returns_over_hodl\": 1.3705259149787707e-11, \"returns_over_uniform_hodl\": 0.2725915465905797, \"sharpe\": -0.20210860043818044, \"sterling\": -1.2473843915686054, \"ulcer\": -0.1764103141536127}], \"train_objective\": [{\"annualised_returns\": -0.5356288111264269, \"annualised_returns_over_hodl\": -0.4151996344742599, \"annualised_returns_over_uniform_hodl\": -0.4151996344742599, \"calmar\": -0.7052138740236222, \"daily_log_sharpe\": -0.5201786621384263, \"daily_returns\": 0.008407143310201206, \"fee_revenue_over_value\": 0.03637464410916269, \"jax_sharpe\": 0.18530512470625018, \"return\": -0.37230773142655593, \"returns_over_hodl\": -0.27798644192320776, \"returns_over_uniform_hodl\": -0.27798644192320776, \"sharpe\": 0.1243383662223636, \"sterling\": -1.490321893383371, \"ulcer\": -0.19683304320396533}], \"train_return\": -0.37230773142655593, \"train_returns_over_hodl\": -0.27798644192320776, \"train_sharpe\": 0.18530512470625016, \"validation_return\": -0.33914271208332425, \"validation_returns_over_hodl\": -3.695310279994146e-09, \"validation_sharpe\": -2.2438531819974763}, {\"centeredness_margin\": 0.014654005789667343, \"continuous_test_metrics\": [{\"annualised_returns\": -0.41741359480119955, \"annualised_returns_over_hodl\": 0.130152714822656, \"annualised_returns_over_uniform_hodl\": 1.0562718048439868, \"calmar\": -0.7540506805413557, \"daily_log_sharpe\": -0.6172266582578552, \"daily_returns\": 0.031016041578990756, \"fee_revenue_over_value\": 0.11634153977964291, \"jax_sharpe\": 0.0171383186558994, \"return\": -0.19554503494434594, \"returns_over_hodl\": 0.050510344070918034, \"returns_over_uniform_hodl\": 0.3368705842767097, \"sharpe\": -0.1555991682157889, \"sterling\": -1.1022853636704393, \"ulcer\": -0.16924889221534406}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": 0.6504489993342819, \"optuna_trial_number\": 149, \"price_ratio\": 3.5832321872643162, \"shift_exponent\": 0.006679383839193062, \"step\": 149, \"test_objective\": [{\"annualised_returns\": -0.41741359480119955, \"annualised_returns_over_hodl\": 0.130152714822656, \"annualised_returns_over_uniform_hodl\": 1.0562718048439868, \"calmar\": -0.7540506805413557, \"daily_log_sharpe\": -0.6172266582578552, \"daily_returns\": 0.031016041578990756, \"fee_revenue_over_value\": 0.11634153977964291, \"jax_sharpe\": 0.0171383186558994, \"return\": -0.19554503494434594, \"returns_over_hodl\": 0.050510344070918034, \"returns_over_uniform_hodl\": 0.3368705842767097, \"sharpe\": -0.1555991682157889, \"sterling\": -1.1022853636704393, \"ulcer\": -0.16924889221534406}], \"train_objective\": [{\"annualised_returns\": -0.49318886529134376, \"annualised_returns_over_hodl\": -0.36175339053855726, \"annualised_returns_over_uniform_hodl\": -0.36175339053855704, \"calmar\": -0.6569895088119402, \"daily_log_sharpe\": -0.4721229074384387, \"daily_returns\": 0.00835561721450607, \"fee_revenue_over_value\": 0.04292003242140509, \"jax_sharpe\": 0.14727894081073645, \"return\": -0.33807957186821436, \"returns_over_hodl\": -0.23861492739857604, \"returns_over_uniform_hodl\": -0.23861492739857593, \"sharpe\": 0.1442351025972721, \"sterling\": -1.4231185045601789, \"ulcer\": -0.18777217013010342}], \"train_return\": -0.33807957186821436, \"train_returns_over_hodl\": -0.23861492739857604, \"train_sharpe\": 0.14727894081073648, \"validation_return\": -0.33844838731411675, \"validation_returns_over_hodl\": 0.0010506403838157485, \"validation_sharpe\": -2.242993699425237}, {\"centeredness_margin\": 0.024540133816183016, \"continuous_test_metrics\": [{\"annualised_returns\": -0.48343650395964066, \"annualised_returns_over_hodl\": 0.0020756285667471452, \"annualised_returns_over_uniform_hodl\": 0.8232401972321481, \"calmar\": -0.8408106419123106, \"daily_log_sharpe\": -0.7143353581606727, \"daily_returns\": 0.031016041615567747, \"fee_revenue_over_value\": 0.04338128073145724, \"jax_sharpe\": -0.06006853924252664, \"return\": -0.2335848721786581, \"returns_over_hodl\": 0.0008354165516384349, \"returns_over_uniform_hodl\": 0.27365469073603443, \"sharpe\": -0.23362289467157685, \"sterling\": -1.2462024974118149, \"ulcer\": -0.1759283600440361}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -0.10051877561690226, \"optuna_trial_number\": 150, \"price_ratio\": 2.7479546127890986, \"shift_exponent\": 0.0037017132434872787, \"step\": 150, \"test_objective\": [{\"annualised_returns\": -0.48343650395964066, \"annualised_returns_over_hodl\": 0.0020756285667471452, \"annualised_returns_over_uniform_hodl\": 0.8232401972321481, \"calmar\": -0.8408106419123106, \"daily_log_sharpe\": -0.7143353581606727, \"daily_returns\": 0.031016041615567747, \"fee_revenue_over_value\": 0.04338128073145724, \"jax_sharpe\": -0.06006853924252664, \"return\": -0.2335848721786581, \"returns_over_hodl\": 0.0008354165516384349, \"returns_over_uniform_hodl\": 0.27365469073603443, \"sharpe\": -0.23362289467157685, \"sterling\": -1.2462024974118149, \"ulcer\": -0.1759283600440361}], \"train_objective\": [{\"annualised_returns\": -0.5352534767960644, \"annualised_returns_over_hodl\": -0.4147269616236404, \"annualised_returns_over_uniform_hodl\": -0.4147269616236404, \"calmar\": -0.7022837820962478, \"daily_log_sharpe\": -0.5255937537530497, \"daily_returns\": 0.008377036163408331, \"fee_revenue_over_value\": 0.03845976079250395, \"jax_sharpe\": 0.14007277624789077, \"return\": -0.3719997628554582, \"returns_over_hodl\": -0.2776321959097918, \"returns_over_uniform_hodl\": -0.2776321959097918, \"sharpe\": 0.11170695903260545, \"sterling\": -1.5044642194678817, \"ulcer\": -0.1940142348332222}], \"train_return\": -0.3719997628554582, \"train_returns_over_hodl\": -0.2776321959097918, \"train_sharpe\": 0.14007277624789077, \"validation_return\": -0.33914271165952226, \"validation_returns_over_hodl\": -2.9572789772558394e-09, \"validation_sharpe\": -2.2438531773251627}, {\"centeredness_margin\": 0.16150511851017163, \"continuous_test_metrics\": [{\"annualised_returns\": -0.5073075313456019, \"annualised_returns_over_hodl\": -0.044231506517263486, \"annualised_returns_over_uniform_hodl\": 0.7389860503306922, \"calmar\": -0.8751383721922723, \"daily_log_sharpe\": -0.7553449918511009, \"daily_returns\": 0.031016041656134613, \"fee_revenue_over_value\": 0.0020718324847307096, \"jax_sharpe\": -0.07706444335729182, \"return\": -0.24805046237440165, \"returns_over_hodl\": -0.018054705741125332, \"returns_over_uniform_hodl\": 0.2496152816243593, \"sharpe\": -0.2732902087837407, \"sterling\": -1.3060868702551864, \"ulcer\": -0.17641031424945652}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -0.09022728629654586, \"optuna_trial_number\": 151, \"price_ratio\": 3.3668474624462865, \"shift_exponent\": 0.00198615036422502, \"step\": 151, \"test_objective\": [{\"annualised_returns\": -0.5073075313456019, \"annualised_returns_over_hodl\": -0.044231506517263486, \"annualised_returns_over_uniform_hodl\": 0.7389860503306922, \"calmar\": -0.8751383721922723, \"daily_log_sharpe\": -0.7553449918511009, \"daily_returns\": 0.031016041656134613, \"fee_revenue_over_value\": 0.0020718324847307096, \"jax_sharpe\": -0.07706444335729182, \"return\": -0.24805046237440165, \"returns_over_hodl\": -0.018054705741125332, \"returns_over_uniform_hodl\": 0.2496152816243593, \"sharpe\": -0.2732902087837407, \"sterling\": -1.3060868702551864, \"ulcer\": -0.17641031424945652}], \"train_objective\": [{\"annualised_returns\": -0.508046209987945, \"annualised_returns_over_hodl\": -0.3804638118943484, \"annualised_returns_over_uniform_hodl\": -0.3804638118943484, \"calmar\": -0.6715700858993191, \"daily_log_sharpe\": -0.4938673431160656, \"daily_returns\": 0.008297855184684962, \"fee_revenue_over_value\": 0.02817024443435048, \"jax_sharpe\": 0.17897292138938373, \"return\": -0.34992919691923463, \"returns_over_hodl\": -0.25224515732702046, \"returns_over_uniform_hodl\": -0.25224515732702046, \"sharpe\": 0.12540342120425305, \"sterling\": -1.4608355096087282, \"ulcer\": -0.18848079474779608}], \"train_return\": -0.34992919691923463, \"train_returns_over_hodl\": -0.25224515732702046, \"train_sharpe\": 0.17897292138938373, \"validation_return\": -0.33914271190532974, \"validation_returns_over_hodl\": -2.6518337525871516e-09, \"validation_sharpe\": -2.2438531769710424}, {\"centeredness_margin\": 0.2252120948732589, \"continuous_test_metrics\": [{\"annualised_returns\": -0.5761829192571746, \"annualised_returns_over_hodl\": -0.17784208328879003, \"annualised_returns_over_uniform_hodl\": 0.4958864569546144, \"calmar\": -0.9963608997205629, \"daily_log_sharpe\": -0.929758181258261, \"daily_returns\": 0.031016041605716475, \"fee_revenue_over_value\": 0.006941216431470553, \"jax_sharpe\": -0.2580811374818622, \"return\": -0.29229760925814396, \"returns_over_hodl\": -0.07583554729543251, \"returns_over_uniform_hodl\": 0.17608387007672377, \"sharpe\": -0.45979124931297305, \"sterling\": -1.487706310016926, \"ulcer\": -0.17531740849606817}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -0.09344630759761519, \"optuna_trial_number\": 152, \"price_ratio\": 2.2404387839888518, \"shift_exponent\": 0.003359525505413378, \"step\": 152, \"test_objective\": [{\"annualised_returns\": -0.5761829192571746, \"annualised_returns_over_hodl\": -0.17784208328879003, \"annualised_returns_over_uniform_hodl\": 0.4958864569546144, \"calmar\": -0.9963608997205629, \"daily_log_sharpe\": -0.929758181258261, \"daily_returns\": 0.031016041605716475, \"fee_revenue_over_value\": 0.006941216431470553, \"jax_sharpe\": -0.2580811374818622, \"return\": -0.29229760925814396, \"returns_over_hodl\": -0.07583554729543251, \"returns_over_uniform_hodl\": 0.17608387007672377, \"sharpe\": -0.45979124931297305, \"sterling\": -1.487706310016926, \"ulcer\": -0.17531740849606817}], \"train_objective\": [{\"annualised_returns\": -0.5387357936876744, \"annualised_returns_over_hodl\": -0.4191123762227458, \"annualised_returns_over_uniform_hodl\": -0.4191123762227458, \"calmar\": -0.6998090551143418, \"daily_log_sharpe\": -0.5319844433471578, \"daily_returns\": 0.008564832153454476, \"fee_revenue_over_value\": 0.03156150586644603, \"jax_sharpe\": 0.142024782296397, \"return\": -0.3748608328340022, \"returns_over_hodl\": -0.2809231896316199, \"returns_over_uniform_hodl\": -0.2809231896316199, \"sharpe\": 0.10556082045979465, \"sterling\": -1.516384244469449, \"ulcer\": -0.19255629835067992}], \"train_return\": -0.3748608328340022, \"train_returns_over_hodl\": -0.2809231896316199, \"train_sharpe\": 0.142024782296397, \"validation_return\": -0.3391427114129666, \"validation_returns_over_hodl\": -2.748670846486334e-09, \"validation_sharpe\": -2.243853175540241}, {\"centeredness_margin\": 0.2000882917485027, \"continuous_test_metrics\": [{\"annualised_returns\": -0.5751917392265372, \"annualised_returns_over_hodl\": -0.03921974109633364, \"annualised_returns_over_uniform_hodl\": 0.49938488316630836, \"calmar\": -1.0601930684359548, \"daily_log_sharpe\": -1.0693536489509987, \"daily_returns\": 0.027603979274791857, \"fee_revenue_over_value\": 0.19431887636775483, \"jax_sharpe\": -0.4652402861179124, \"return\": -0.2916315020611231, \"returns_over_hodl\": -0.015984235353790566, \"returns_over_uniform_hodl\": 0.17719082964109245, \"sharpe\": -0.6644944566981598, \"sterling\": -1.6728730231025861, \"ulcer\": -0.1425208099347085}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -3.1478777385217187, \"optuna_trial_number\": 153, \"price_ratio\": 1.5844294745468708, \"shift_exponent\": 0.015927833953119162, \"step\": 153, \"test_objective\": [{\"annualised_returns\": -0.5751917392265372, \"annualised_returns_over_hodl\": -0.03921974109633364, \"annualised_returns_over_uniform_hodl\": 0.49938488316630836, \"calmar\": -1.0601930684359548, \"daily_log_sharpe\": -1.0693536489509987, \"daily_returns\": 0.027603979274791857, \"fee_revenue_over_value\": 0.19431887636775483, \"jax_sharpe\": -0.4652402861179124, \"return\": -0.2916315020611231, \"returns_over_hodl\": -0.015984235353790566, \"returns_over_uniform_hodl\": 0.17719082964109245, \"sharpe\": -0.6644944566981598, \"sterling\": -1.6728730231025861, \"ulcer\": -0.1425208099347085}], \"train_objective\": [{\"annualised_returns\": -0.46908173366421446, \"annualised_returns_over_hodl\": -0.3313943593903942, \"annualised_returns_over_uniform_hodl\": -0.33139435939039397, \"calmar\": -0.6101319665896994, \"daily_log_sharpe\": -0.4914198085043101, \"daily_returns\": 0.009142699699524371, \"fee_revenue_over_value\": 0.040630272345209, \"jax_sharpe\": 0.03219593379290083, \"return\": -0.31913906528716895, \"returns_over_hodl\": -0.2168282920789515, \"returns_over_uniform_hodl\": -0.21682829207895138, \"sharpe\": 0.08216062879683558, \"sterling\": -1.382959189115342, \"ulcer\": -0.18364785135317252}], \"train_return\": -0.31913906528716895, \"train_returns_over_hodl\": -0.2168282920789515, \"train_sharpe\": 0.032195933792900824, \"validation_return\": -0.264425555894066, \"validation_returns_over_hodl\": -0.06421690306629602, \"validation_sharpe\": -2.016846650644237}, {\"centeredness_margin\": 0.20641983879696207, \"continuous_test_metrics\": [{\"annualised_returns\": -0.6280725167885741, \"annualised_returns_over_hodl\": -0.12272065984715363, \"annualised_returns_over_uniform_hodl\": 0.3127391754245741, \"calmar\": -1.0708420963988932, \"daily_log_sharpe\": -1.1285065854651246, \"daily_returns\": 0.026440258580762595, \"fee_revenue_over_value\": 0.0007450113611227531, \"jax_sharpe\": -0.46846649594220563, \"return\": -0.3285598623969236, \"returns_over_hodl\": -0.05136426080395118, \"returns_over_uniform_hodl\": 0.11582202616172421, \"sharpe\": -0.6914558020432637, \"sterling\": -1.6974410003307139, \"ulcer\": -0.16362706104996388}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -2.9589947482415857, \"optuna_trial_number\": 154, \"price_ratio\": 31.04935447897678, \"shift_exponent\": 1.2474319611704334e-05, \"step\": 154, \"test_objective\": [{\"annualised_returns\": -0.6280725167885741, \"annualised_returns_over_hodl\": -0.12272065984715363, \"annualised_returns_over_uniform_hodl\": 0.3127391754245741, \"calmar\": -1.0708420963988932, \"daily_log_sharpe\": -1.1285065854651246, \"daily_returns\": 0.026440258580762595, \"fee_revenue_over_value\": 0.0007450113611227531, \"jax_sharpe\": -0.46846649594220563, \"return\": -0.3285598623969236, \"returns_over_hodl\": -0.05136426080395118, \"returns_over_uniform_hodl\": 0.11582202616172421, \"sharpe\": -0.6914558020432637, \"sterling\": -1.6974410003307139, \"ulcer\": -0.16362706104996388}], \"train_objective\": [{\"annualised_returns\": -0.3464589674822701, \"annualised_returns_over_hodl\": -0.17697082881149373, \"annualised_returns_over_uniform_hodl\": -0.17697082881149395, \"calmar\": -0.47375971799972055, \"daily_log_sharpe\": -0.3340204342204605, \"daily_returns\": 0.008021826851919404, \"fee_revenue_over_value\": 0.031168416319346227, \"jax_sharpe\": 0.1861103113244193, \"return\": -0.22758943955553868, \"returns_over_hodl\": -0.11152179983025967, \"returns_over_uniform_hodl\": -0.11152179983025978, \"sharpe\": 0.18908895305893966, \"sterling\": -1.1052417374905437, \"ulcer\": -0.16861063758932968}], \"train_return\": -0.22758943955553868, \"train_returns_over_hodl\": -0.11152179983025967, \"train_sharpe\": 0.1861103113244193, \"validation_return\": -0.251490275769782, \"validation_returns_over_hodl\": -0.05190223968078578, \"validation_sharpe\": -1.8344198202789204}, {\"centeredness_margin\": 0.24296955062749154, \"continuous_test_metrics\": [{\"annualised_returns\": -0.5784489120993108, \"annualised_returns_over_hodl\": -0.09606148793116076, \"annualised_returns_over_uniform_hodl\": 0.4878885065223981, \"calmar\": -1.05817685197772, \"daily_log_sharpe\": -1.070492555172483, \"daily_returns\": 0.028827843486797098, \"fee_revenue_over_value\": 0.17578437144345938, \"jax_sharpe\": -0.47345665229762407, \"return\": -0.2938239377415034, \"returns_over_hodl\": -0.03985799058148176, \"returns_over_uniform_hodl\": 0.17354736556126493, \"sharpe\": -0.6631094761111154, \"sterling\": -1.6694227353396498, \"ulcer\": -0.14593638732111555}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -3.1976140363247887, \"optuna_trial_number\": 155, \"price_ratio\": 1.7882370932385736, \"shift_exponent\": 0.009541735417463712, \"step\": 155, \"test_objective\": [{\"annualised_returns\": -0.5784489120993108, \"annualised_returns_over_hodl\": -0.09606148793116076, \"annualised_returns_over_uniform_hodl\": 0.4878885065223981, \"calmar\": -1.05817685197772, \"daily_log_sharpe\": -1.070492555172483, \"daily_returns\": 0.028827843486797098, \"fee_revenue_over_value\": 0.17578437144345938, \"jax_sharpe\": -0.47345665229762407, \"return\": -0.2938239377415034, \"returns_over_hodl\": -0.03985799058148176, \"returns_over_uniform_hodl\": 0.17354736556126493, \"sharpe\": -0.6631094761111154, \"sterling\": -1.6694227353396498, \"ulcer\": -0.14593638732111555}], \"train_objective\": [{\"annualised_returns\": -0.4843984880179344, \"annualised_returns_over_hodl\": -0.3506833328653658, \"annualised_returns_over_uniform_hodl\": -0.35068333286536557, \"calmar\": -0.6298863250805955, \"daily_log_sharpe\": -0.48774639387626145, \"daily_returns\": 0.00889686432301856, \"fee_revenue_over_value\": 0.03431255696007182, \"jax_sharpe\": 0.11923021677755594, \"return\": -0.3311329719294174, \"returns_over_hodl\": -0.23062448432725124, \"returns_over_uniform_hodl\": -0.23062448432725113, \"sharpe\": 0.11505386180235962, \"sterling\": -1.401295380570477, \"ulcer\": -0.18605079199745525}], \"train_return\": -0.3311329719294174, \"train_returns_over_hodl\": -0.23062448432725124, \"train_sharpe\": 0.11923021677755594, \"validation_return\": -0.28488274535730485, \"validation_returns_over_hodl\": -0.05261179866357013, \"validation_sharpe\": -2.129430533378561}, {\"centeredness_margin\": 0.19557620759734332, \"continuous_test_metrics\": [{\"annualised_returns\": -0.561835225389646, \"annualised_returns_over_hodl\": -0.15000915644163126, \"annualised_returns_over_uniform_hodl\": 0.5465274573299399, \"calmar\": -0.9717817018277058, \"daily_log_sharpe\": -0.8873835740847731, \"daily_returns\": 0.031016041559045332, \"fee_revenue_over_value\": 0.0022288793496834593, \"jax_sharpe\": -0.20778928072753175, \"return\": -0.28274456005009885, \"returns_over_hodl\": -0.06336054595304641, \"returns_over_uniform_hodl\": 0.19195945172037732, \"sharpe\": -0.4145857073584555, \"sterling\": -1.4529492114620222, \"ulcer\": -0.17476069641479022}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -4.098277963120994, \"optuna_trial_number\": 156, \"price_ratio\": 4.2647170996158135, \"shift_exponent\": 0.002494959805293081, \"step\": 156, \"test_objective\": [{\"annualised_returns\": -0.561835225389646, \"annualised_returns_over_hodl\": -0.15000915644163126, \"annualised_returns_over_uniform_hodl\": 0.5465274573299399, \"calmar\": -0.9717817018277058, \"daily_log_sharpe\": -0.8873835740847731, \"daily_returns\": 0.031016041559045332, \"fee_revenue_over_value\": 0.0022288793496834593, \"jax_sharpe\": -0.20778928072753175, \"return\": -0.28274456005009885, \"returns_over_hodl\": -0.06336054595304641, \"returns_over_uniform_hodl\": 0.19195945172037732, \"sharpe\": -0.4145857073584555, \"sterling\": -1.4529492114620222, \"ulcer\": -0.17476069641479022}], \"train_objective\": [{\"annualised_returns\": -0.47055599461371167, \"annualised_returns_over_hodl\": -0.33325095248403, \"annualised_returns_over_uniform_hodl\": -0.33325095248403, \"calmar\": -0.6266761931464463, \"daily_log_sharpe\": -0.457725032827576, \"daily_returns\": 0.008279623739648061, \"fee_revenue_over_value\": 0.027910032971650938, \"jax_sharpe\": 0.19017581061502656, \"return\": -0.32028753032792745, \"returns_over_hodl\": -0.21814933325138663, \"returns_over_uniform_hodl\": -0.21814933325138663, \"sharpe\": 0.13623978241189752, \"sterling\": -1.3900970495559481, \"ulcer\": -0.18297552865677574}], \"train_return\": -0.32028753032792745, \"train_returns_over_hodl\": -0.21814933325138663, \"train_sharpe\": 0.19017581061502653, \"validation_return\": -0.33744345319288194, \"validation_returns_over_hodl\": -0.03213062980700521, \"validation_sharpe\": -2.2286068666424343}, {\"centeredness_margin\": 0.1410963852280985, \"continuous_test_metrics\": [{\"annualised_returns\": -0.4944732928697745, \"annualised_returns_over_hodl\": -0.019334514404356073, \"annualised_returns_over_uniform_hodl\": 0.7842852239450893, \"calmar\": -0.8529984390197072, \"daily_log_sharpe\": -0.722229589419686, \"daily_returns\": 0.03101604166965661, \"fee_revenue_over_value\": 0.0005033506091210462, \"jax_sharpe\": -0.04384150144919311, \"return\": -0.24022229496282033, \"returns_over_hodl\": -0.007832168719835608, \"returns_over_uniform_hodl\": 0.2626243961129655, \"sharpe\": -0.23705900563942864, \"sterling\": -1.2730444655478192, \"ulcer\": -0.17641031427740567}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -0.09091253305799407, \"optuna_trial_number\": 157, \"price_ratio\": 3.8768208344510664, \"shift_exponent\": 0.0015308885590882865, \"step\": 157, \"test_objective\": [{\"annualised_returns\": -0.4944732928697745, \"annualised_returns_over_hodl\": -0.019334514404356073, \"annualised_returns_over_uniform_hodl\": 0.7842852239450893, \"calmar\": -0.8529984390197072, \"daily_log_sharpe\": -0.722229589419686, \"daily_returns\": 0.03101604166965661, \"fee_revenue_over_value\": 0.0005033506091210462, \"jax_sharpe\": -0.04384150144919311, \"return\": -0.24022229496282033, \"returns_over_hodl\": -0.007832168719835608, \"returns_over_uniform_hodl\": 0.2626243961129655, \"sharpe\": -0.23705900563942864, \"sterling\": -1.2730444655478192, \"ulcer\": -0.17641031427740567}], \"train_objective\": [{\"annualised_returns\": -0.49959750281644666, \"annualised_returns_over_hodl\": -0.36982403242375517, \"annualised_returns_over_uniform_hodl\": -0.36982403242375494, \"calmar\": -0.6635418462728749, \"daily_log_sharpe\": -0.48490585895064353, \"daily_returns\": 0.008302659211220828, \"fee_revenue_over_value\": 0.028910886679734377, \"jax_sharpe\": 0.1317303261778708, \"return\": -0.34317387381281905, \"returns_over_hodl\": -0.24447473363977912, \"returns_over_uniform_hodl\": -0.244474733639779, \"sharpe\": 0.1275499476570143, \"sterling\": -1.4464306674541727, \"ulcer\": -0.18723229070984237}], \"train_return\": -0.34317387381281905, \"train_returns_over_hodl\": -0.24447473363977912, \"train_sharpe\": 0.1317303261778708, \"validation_return\": -0.33914271204485436, \"validation_returns_over_hodl\": -2.6720849977124317e-09, \"validation_sharpe\": -2.2438531774372197}, {\"centeredness_margin\": 0.25499465645814834, \"continuous_test_metrics\": [{\"annualised_returns\": -0.5916359656621939, \"annualised_returns_over_hodl\": 0.06502865946418912, \"annualised_returns_over_uniform_hodl\": 0.44134405202028715, \"calmar\": -1.0692892509121672, \"daily_log_sharpe\": -1.1090016141712966, \"daily_returns\": 0.024226960551181762, \"fee_revenue_over_value\": 0.08383740490045781, \"jax_sharpe\": -0.5055683997633789, \"return\": -0.3028052454666076, \"returns_over_hodl\": 0.025697809991715115, \"returns_over_uniform_hodl\": 0.1586219233331867, \"sharpe\": -0.7012656270448017, \"sterling\": -1.6858469840010162, \"ulcer\": -0.1534091648769674}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -3.1726205751801198, \"optuna_trial_number\": 158, \"price_ratio\": 8.722236105052133, \"shift_exponent\": 0.007018354119808416, \"step\": 158, \"test_objective\": [{\"annualised_returns\": -0.5916359656621939, \"annualised_returns_over_hodl\": 0.06502865946418912, \"annualised_returns_over_uniform_hodl\": 0.44134405202028715, \"calmar\": -1.0692892509121672, \"daily_log_sharpe\": -1.1090016141712966, \"daily_returns\": 0.024226960551181762, \"fee_revenue_over_value\": 0.08383740490045781, \"jax_sharpe\": -0.5055683997633789, \"return\": -0.3028052454666076, \"returns_over_hodl\": 0.025697809991715115, \"returns_over_uniform_hodl\": 0.1586219233331867, \"sharpe\": -0.7012656270448017, \"sterling\": -1.6858469840010162, \"ulcer\": -0.1534091648769674}], \"train_objective\": [{\"annualised_returns\": -0.3932617349801325, \"annualised_returns_over_hodl\": -0.23591134061791819, \"annualised_returns_over_uniform_hodl\": -0.2359113406179183, \"calmar\": -0.5312697648054503, \"daily_log_sharpe\": -0.39247410593791626, \"daily_returns\": 0.008167955373515414, \"fee_revenue_over_value\": 0.029310424893864376, \"jax_sharpe\": 0.12736691080632456, \"return\": -0.2616615244705861, \"returns_over_hodl\": -0.1507137868791325, \"returns_over_uniform_hodl\": -0.15071378687913262, \"sharpe\": 0.1478440271759205, \"sterling\": -1.2241299817930438, \"ulcer\": -0.1730453520938795}], \"train_return\": -0.2616615244705861, \"train_returns_over_hodl\": -0.1507137868791325, \"train_sharpe\": 0.12736691080632453, \"validation_return\": -0.2410662055442354, \"validation_returns_over_hodl\": -0.034283877441833144, \"validation_sharpe\": -1.8387630515814197}, {\"centeredness_margin\": 0.1519064079620883, \"continuous_test_metrics\": [{\"annualised_returns\": -0.5482775123056614, \"annualised_returns_over_hodl\": -0.05679340311310754, \"annualised_returns_over_uniform_hodl\": 0.5943801756631961, \"calmar\": -0.9907252251565889, \"daily_log_sharpe\": -0.9197947946492018, \"daily_returns\": 0.029280176744074717, \"fee_revenue_over_value\": 0.07843987342967115, \"jax_sharpe\": -0.28430252811955203, \"return\": -0.27388771572565884, \"returns_over_hodl\": -0.023272962697449717, \"returns_over_uniform_hodl\": 0.20667805644182735, \"sharpe\": -0.4807121126473595, \"sterling\": -1.483246866403692, \"ulcer\": -0.16275273259309467}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -3.0531730993336277, \"optuna_trial_number\": 159, \"price_ratio\": 7.4456565482055055, \"shift_exponent\": 0.005069619846533553, \"step\": 159, \"test_objective\": [{\"annualised_returns\": -0.5482775123056614, \"annualised_returns_over_hodl\": -0.05679340311310754, \"annualised_returns_over_uniform_hodl\": 0.5943801756631961, \"calmar\": -0.9907252251565889, \"daily_log_sharpe\": -0.9197947946492018, \"daily_returns\": 0.029280176744074717, \"fee_revenue_over_value\": 0.07843987342967115, \"jax_sharpe\": -0.28430252811955203, \"return\": -0.27388771572565884, \"returns_over_hodl\": -0.023272962697449717, \"returns_over_uniform_hodl\": 0.20667805644182735, \"sharpe\": -0.4807121126473595, \"sterling\": -1.483246866403692, \"ulcer\": -0.16275273259309467}], \"train_objective\": [{\"annualised_returns\": -0.4150738209612037, \"annualised_returns_over_hodl\": -0.26338013317059783, \"annualised_returns_over_uniform_hodl\": -0.26338013317059783, \"calmar\": -0.5606820959657213, \"daily_log_sharpe\": -0.40503364453489876, \"daily_returns\": 0.008189558292075176, \"fee_revenue_over_value\": 0.0326773504383998, \"jax_sharpe\": 0.13798104849133588, \"return\": -0.27789213964502335, \"returns_over_hodl\": -0.16938332416450297, \"returns_over_uniform_hodl\": -0.16938332416450297, \"sharpe\": 0.15412725355314263, \"sterling\": -1.2690920952365587, \"ulcer\": -0.1762960523119526}], \"train_return\": -0.27789213964502335, \"train_returns_over_hodl\": -0.16938332416450297, \"train_sharpe\": 0.13798104849133588, \"validation_return\": -0.2901992086319045, \"validation_returns_over_hodl\": -0.0480167923907564, \"validation_sharpe\": -2.047260285176119}, {\"centeredness_margin\": 0.05243153801901702, \"continuous_test_metrics\": [{\"annualised_returns\": -0.4837994592779332, \"annualised_returns_over_hodl\": 0.017427416600144907, \"annualised_returns_over_uniform_hodl\": 0.8219591258223733, \"calmar\": -0.8777389843720923, \"daily_log_sharpe\": -0.7792242420563594, \"daily_returns\": 0.030669050940057042, \"fee_revenue_over_value\": 0.12933974624996047, \"jax_sharpe\": -0.1662632803014166, \"return\": -0.23380179592223782, \"returns_over_hodl\": 0.006982487177562335, \"returns_over_uniform_hodl\": 0.2732941994911291, \"sharpe\": -0.34002676968713036, \"sterling\": -1.3195080645176667, \"ulcer\": -0.159466239047281}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -3.401665292045143, \"optuna_trial_number\": 160, \"price_ratio\": 3.208286930562113, \"shift_exponent\": 0.012629249098386695, \"step\": 160, \"test_objective\": [{\"annualised_returns\": -0.4837994592779332, \"annualised_returns_over_hodl\": 0.017427416600144907, \"annualised_returns_over_uniform_hodl\": 0.8219591258223733, \"calmar\": -0.8777389843720923, \"daily_log_sharpe\": -0.7792242420563594, \"daily_returns\": 0.030669050940057042, \"fee_revenue_over_value\": 0.12933974624996047, \"jax_sharpe\": -0.1662632803014166, \"return\": -0.23380179592223782, \"returns_over_hodl\": 0.006982487177562335, \"returns_over_uniform_hodl\": 0.2732941994911291, \"sharpe\": -0.34002676968713036, \"sterling\": -1.3195080645176667, \"ulcer\": -0.159466239047281}], \"train_objective\": [{\"annualised_returns\": -0.4837861088515826, \"annualised_returns_over_hodl\": -0.34991214040359475, \"annualised_returns_over_uniform_hodl\": -0.34991214040359475, \"calmar\": -0.6411341598089206, \"daily_log_sharpe\": -0.4781816839385293, \"daily_returns\": 0.00838890683237347, \"fee_revenue_over_value\": 0.044511534815508284, \"jax_sharpe\": 0.12085573139423275, \"return\": -0.33065077963944256, \"returns_over_hodl\": -0.23006983455953434, \"returns_over_uniform_hodl\": -0.23006983455953434, \"sharpe\": 0.12274299098945533, \"sterling\": -1.4112389091726887, \"ulcer\": -0.1854166709112881}], \"train_return\": -0.33065077963944256, \"train_returns_over_hodl\": -0.23006983455953434, \"train_sharpe\": 0.12085573139423274, \"validation_return\": -0.31670957909067377, \"validation_returns_over_hodl\": -0.04037312818264138, \"validation_sharpe\": -2.235823739914105}, {\"centeredness_margin\": 0.1752688473884897, \"continuous_test_metrics\": [{\"annualised_returns\": -0.5095080676434838, \"annualised_returns_over_hodl\": -0.04850030478578593, \"annualised_returns_over_uniform_hodl\": 0.7312191324889923, \"calmar\": -0.8789344472737369, \"daily_log_sharpe\": -0.7555915534250313, \"daily_returns\": 0.031016041734187236, \"fee_revenue_over_value\": 0.00023933404250980452, \"jax_sharpe\": -0.07481264692766659, \"return\": -0.2494048531267219, \"returns_over_hodl\": -0.019823359760821502, \"returns_over_uniform_hodl\": 0.24736450907021368, \"sharpe\": -0.27284158949600074, \"sterling\": -1.3117522627564198, \"ulcer\": -0.17641031441080973}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -4.15780568266012, \"optuna_trial_number\": 161, \"price_ratio\": 5.765752242996048, \"shift_exponent\": 0.0010688247918773852, \"step\": 161, \"test_objective\": [{\"annualised_returns\": -0.5095080676434838, \"annualised_returns_over_hodl\": -0.04850030478578593, \"annualised_returns_over_uniform_hodl\": 0.7312191324889923, \"calmar\": -0.8789344472737369, \"daily_log_sharpe\": -0.7555915534250313, \"daily_returns\": 0.031016041734187236, \"fee_revenue_over_value\": 0.00023933404250980452, \"jax_sharpe\": -0.07481264692766659, \"return\": -0.2494048531267219, \"returns_over_hodl\": -0.019823359760821502, \"returns_over_uniform_hodl\": 0.24736450907021368, \"sharpe\": -0.27284158949600074, \"sterling\": -1.3117522627564198, \"ulcer\": -0.17641031441080973}], \"train_objective\": [{\"annualised_returns\": -0.4514294078883545, \"annualised_returns_over_hodl\": -0.3091641116630466, \"annualised_returns_over_uniform_hodl\": -0.3091641116630466, \"calmar\": -0.6064326039654035, \"daily_log_sharpe\": -0.4353973409777612, \"daily_returns\": 0.00823338205988749, \"fee_revenue_over_value\": 0.02896357787923799, \"jax_sharpe\": 0.13268619726150224, \"return\": -0.3054836284214614, \"returns_over_hodl\": -0.20112089682791534, \"returns_over_uniform_hodl\": -0.20112089682791534, \"sharpe\": 0.1458543648706194, \"sterling\": -1.3499214605578922, \"ulcer\": -0.18069621213170978}], \"train_return\": -0.3054836284214614, \"train_returns_over_hodl\": -0.20112089682791534, \"train_sharpe\": 0.13268619726150224, \"validation_return\": -0.3380956842970039, \"validation_returns_over_hodl\": -0.025472260891636123, \"validation_sharpe\": -2.234331776938459}, {\"centeredness_margin\": 0.029483091435975543, \"continuous_test_metrics\": [{\"annualised_returns\": -0.4872456703528443, \"annualised_returns_over_hodl\": -0.00531373640277677, \"annualised_returns_over_uniform_hodl\": 0.8097955281076927, \"calmar\": -0.8405303006537657, \"daily_log_sharpe\": -0.7070845771286314, \"daily_returns\": 0.031016041777270003, \"fee_revenue_over_value\": 0.0018544630441714265, \"jax_sharpe\": -0.024217613812310766, \"return\": -0.23586601144721386, \"returns_over_hodl\": -0.0021434481939940353, \"returns_over_uniform_hodl\": 0.26986381602055576, \"sharpe\": -0.2203764728902171, \"sterling\": -1.2544365810767109, \"ulcer\": -0.176410314499879}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -0.08765869976593621, \"optuna_trial_number\": 162, \"price_ratio\": 5.4129294846913885, \"shift_exponent\": 0.0008147974984247179, \"step\": 162, \"test_objective\": [{\"annualised_returns\": -0.4872456703528443, \"annualised_returns_over_hodl\": -0.00531373640277677, \"annualised_returns_over_uniform_hodl\": 0.8097955281076927, \"calmar\": -0.8405303006537657, \"daily_log_sharpe\": -0.7070845771286314, \"daily_returns\": 0.031016041777270003, \"fee_revenue_over_value\": 0.0018544630441714265, \"jax_sharpe\": -0.024217613812310766, \"return\": -0.23586601144721386, \"returns_over_hodl\": -0.0021434481939940353, \"returns_over_uniform_hodl\": 0.26986381602055576, \"sharpe\": -0.2203764728902171, \"sterling\": -1.2544365810767109, \"ulcer\": -0.176410314499879}], \"train_objective\": [{\"annualised_returns\": -0.43975785536924905, \"annualised_returns_over_hodl\": -0.29446568001404094, \"annualised_returns_over_uniform_hodl\": -0.29446568001404094, \"calmar\": -0.5950611581505334, \"daily_log_sharpe\": -0.40689002204064645, \"daily_returns\": 0.00822384137015278, \"fee_revenue_over_value\": 0.04657712799695704, \"jax_sharpe\": 0.16744078332681545, \"return\": -0.2965494840516507, \"returns_over_hodl\": -0.19084424744448591, \"returns_over_uniform_hodl\": -0.19084424744448591, \"sharpe\": 0.18223045751000383, \"sterling\": -1.3105305777303475, \"ulcer\": -0.18109342682914434}], \"train_return\": -0.2965494840516507, \"train_returns_over_hodl\": -0.19084424744448591, \"train_sharpe\": 0.16744078332681545, \"validation_return\": -0.339142712910657, \"validation_returns_over_hodl\": -2.5791918600859276e-09, \"validation_sharpe\": -2.2438531786429428}, {\"centeredness_margin\": 0.13201868908858963, \"continuous_test_metrics\": [{\"annualised_returns\": -0.499570313000554, \"annualised_returns_over_hodl\": -0.029222166457248067, \"annualised_returns_over_uniform_hodl\": 0.7662950019108696, \"calmar\": -0.8617911447289931, \"daily_log_sharpe\": -0.734513770899415, \"daily_returns\": 0.03101604169269298, \"fee_revenue_over_value\": 0.0009404753302393182, \"jax_sharpe\": -0.053083849127621686, \"return\": -0.24331681955699191, \"returns_over_hodl\": -0.011873203379936359, \"returns_over_uniform_hodl\": 0.2574818100367111, \"sharpe\": -0.2503548921335212, \"sterling\": -1.2861670041352564, \"ulcer\": -0.17641031432503596}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -0.08358528583085967, \"optuna_trial_number\": 163, \"price_ratio\": 4.383714026141979, \"shift_exponent\": 0.001498708923272476, \"step\": 163, \"test_objective\": [{\"annualised_returns\": -0.499570313000554, \"annualised_returns_over_hodl\": -0.029222166457248067, \"annualised_returns_over_uniform_hodl\": 0.7662950019108696, \"calmar\": -0.8617911447289931, \"daily_log_sharpe\": -0.734513770899415, \"daily_returns\": 0.03101604169269298, \"fee_revenue_over_value\": 0.0009404753302393182, \"jax_sharpe\": -0.053083849127621686, \"return\": -0.24331681955699191, \"returns_over_hodl\": -0.011873203379936359, \"returns_over_uniform_hodl\": 0.2574818100367111, \"sharpe\": -0.2503548921335212, \"sterling\": -1.2861670041352564, \"ulcer\": -0.17641031432503596}], \"train_objective\": [{\"annualised_returns\": -0.4831521777690344, \"annualised_returns_over_hodl\": -0.3491138067910131, \"annualised_returns_over_uniform_hodl\": -0.3491138067910131, \"calmar\": -0.6449956739769968, \"daily_log_sharpe\": -0.4651748332749839, \"daily_returns\": 0.008289586704530899, \"fee_revenue_over_value\": 0.03130222604918166, \"jax_sharpe\": 0.13371856866263265, \"return\": -0.33015185387466384, \"returns_over_hodl\": -0.22949593683181058, \"returns_over_uniform_hodl\": -0.22949593683181058, \"sharpe\": 0.13801594234933176, \"sterling\": -1.4128727481610122, \"ulcer\": -0.18508700964373362}], \"train_return\": -0.33015185387466384, \"train_returns_over_hodl\": -0.22949593683181058, \"train_sharpe\": 0.13371856866263265, \"validation_return\": -0.3391427121785556, \"validation_returns_over_hodl\": -2.4548539867552677e-09, \"validation_sharpe\": -2.243853177170386}, {\"centeredness_margin\": 0.2154918464729846, \"continuous_test_metrics\": [{\"annualised_returns\": -0.5059860267004024, \"annualised_returns_over_hodl\": -0.041667936742203926, \"annualised_returns_over_uniform_hodl\": 0.7436503760300994, \"calmar\": -0.8728586869216353, \"daily_log_sharpe\": -0.7478112604702596, \"daily_returns\": 0.031016041707088243, \"fee_revenue_over_value\": 2.5178115684224524e-05, \"jax_sharpe\": -0.06801978528252495, \"return\": -0.247238835579217, \"returns_over_hodl\": -0.016994830772544267, \"returns_over_uniform_hodl\": 0.25096407059953574, \"sharpe\": -0.26453827227760374, \"sterling\": -1.3026845877869664, \"ulcer\": -0.17641031435478963}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -4.28562042140942, \"optuna_trial_number\": 164, \"price_ratio\": 4.834926392484535, \"shift_exponent\": 0.0011823256786772974, \"step\": 164, \"test_objective\": [{\"annualised_returns\": -0.5059860267004024, \"annualised_returns_over_hodl\": -0.041667936742203926, \"annualised_returns_over_uniform_hodl\": 0.7436503760300994, \"calmar\": -0.8728586869216353, \"daily_log_sharpe\": -0.7478112604702596, \"daily_returns\": 0.031016041707088243, \"fee_revenue_over_value\": 2.5178115684224524e-05, \"jax_sharpe\": -0.06801978528252495, \"return\": -0.247238835579217, \"returns_over_hodl\": -0.016994830772544267, \"returns_over_uniform_hodl\": 0.25096407059953574, \"sharpe\": -0.26453827227760374, \"sterling\": -1.3026845877869664, \"ulcer\": -0.17641031435478963}], \"train_objective\": [{\"annualised_returns\": -0.47142234639654956, \"annualised_returns_over_hodl\": -0.3343419823571504, \"annualised_returns_over_uniform_hodl\": -0.3343419823571505, \"calmar\": -0.6293887583540458, \"daily_log_sharpe\": -0.4577542149688923, \"daily_returns\": 0.00827185883005774, \"fee_revenue_over_value\": 0.02451610914061299, \"jax_sharpe\": 0.12651824644120516, \"return\": -0.32096301374273417, \"returns_over_hodl\": -0.2189263193768568, \"returns_over_uniform_hodl\": -0.21892631937685691, \"sharpe\": 0.134193420515464, \"sterling\": -1.3956953840246322, \"ulcer\": -0.1827140245849004}], \"train_return\": -0.32096301374273417, \"train_returns_over_hodl\": -0.2189263193768568, \"train_sharpe\": 0.12651824644120516, \"validation_return\": -0.33901167482424266, \"validation_returns_over_hodl\": -0.009253953378769886, \"validation_sharpe\": -2.242738342128667}, {\"centeredness_margin\": 0.1624773941788875, \"continuous_test_metrics\": [{\"annualised_returns\": -0.4932550898454988, \"annualised_returns_over_hodl\": -0.016971337440733203, \"annualised_returns_over_uniform_hodl\": 0.7885849407064827, \"calmar\": -0.8508969562652382, \"daily_log_sharpe\": -0.718577013390583, \"daily_returns\": 0.031016041698793496, \"fee_revenue_over_value\": 4.3439347534437015e-05, \"jax_sharpe\": -0.03852236304955567, \"return\": -0.23948545649840114, \"returns_over_hodl\": -0.006869956937825883, \"returns_over_uniform_hodl\": 0.26384889929988686, \"sharpe\": -0.23278437389972653, \"sterling\": -1.2699081391739298, \"ulcer\": -0.17641031433764442}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -0.08527713210465548, \"optuna_trial_number\": 165, \"price_ratio\": 4.688959626528553, \"shift_exponent\": 0.0010864912066666586, \"step\": 165, \"test_objective\": [{\"annualised_returns\": -0.4932550898454988, \"annualised_returns_over_hodl\": -0.016971337440733203, \"annualised_returns_over_uniform_hodl\": 0.7885849407064827, \"calmar\": -0.8508969562652382, \"daily_log_sharpe\": -0.718577013390583, \"daily_returns\": 0.031016041698793496, \"fee_revenue_over_value\": 4.3439347534437015e-05, \"jax_sharpe\": -0.03852236304955567, \"return\": -0.23948545649840114, \"returns_over_hodl\": -0.006869956937825883, \"returns_over_uniform_hodl\": 0.26384889929988686, \"sharpe\": -0.23278437389972653, \"sterling\": -1.2699081391739298, \"ulcer\": -0.17641031433764442}], \"train_objective\": [{\"annualised_returns\": -0.4784535287490739, \"annualised_returns_over_hodl\": -0.3431966187091784, \"annualised_returns_over_uniform_hodl\": -0.3431966187091784, \"calmar\": -0.6392059604801904, \"daily_log_sharpe\": -0.4617125853241087, \"daily_returns\": 0.008255800614646437, \"fee_revenue_over_value\": 0.02797508027275513, \"jax_sharpe\": 0.1294108839943303, \"return\": -0.32646131996822114, \"returns_over_hodl\": -0.2252508383171361, \"returns_over_uniform_hodl\": -0.2252508383171361, \"sharpe\": 0.13629755483236825, \"sterling\": -1.4068336524327372, \"ulcer\": -0.18404609888787}], \"train_return\": -0.32646131996822114, \"train_returns_over_hodl\": -0.2252508383171361, \"train_sharpe\": 0.1294108839943303, \"validation_return\": -0.33914271220528314, \"validation_returns_over_hodl\": -2.506322038797748e-09, \"validation_sharpe\": -2.2438531770187864}, {\"centeredness_margin\": 0.2726341860711652, \"continuous_test_metrics\": [{\"annualised_returns\": -0.4845064822286159, \"annualised_returns_over_hodl\": 4.11413125789295e-11, \"annualised_returns_over_uniform_hodl\": 0.8194636481629354, \"calmar\": -0.8358050146677865, \"daily_log_sharpe\": -0.6924636699848581, \"daily_returns\": 0.031016041368273798, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": 0.005019196524147988, \"return\": -0.23422461671438577, \"returns_over_hodl\": 1.656919046411076e-11, \"returns_over_uniform_hodl\": 0.27259154153761167, \"sharpe\": -0.20210861540663877, \"sterling\": -1.2473844085999368, \"ulcer\": -0.1764103136543606}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -0.19474843617068255, \"optuna_trial_number\": 166, \"price_ratio\": 1.1164065262415488, \"shift_exponent\": 0.000209462371376187, \"step\": 166, \"test_objective\": [{\"annualised_returns\": -0.4845064822286159, \"annualised_returns_over_hodl\": 4.11413125789295e-11, \"annualised_returns_over_uniform_hodl\": 0.8194636481629354, \"calmar\": -0.8358050146677865, \"daily_log_sharpe\": -0.6924636699848581, \"daily_returns\": 0.031016041368273798, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": 0.005019196524147988, \"return\": -0.23422461671438577, \"returns_over_hodl\": 1.656919046411076e-11, \"returns_over_uniform_hodl\": 0.27259154153761167, \"sharpe\": -0.20210861540663877, \"sterling\": -1.2473844085999368, \"ulcer\": -0.1764103136543606}], \"train_objective\": [{\"annualised_returns\": -0.6753109668305048, \"annualised_returns_over_hodl\": -0.5911067055208405, \"annualised_returns_over_uniform_hodl\": -0.5911067055208393, \"calmar\": -0.8429006776305354, \"daily_log_sharpe\": -0.7628423425782822, \"daily_returns\": 0.012411451180088429, \"fee_revenue_over_value\": 0.0014661621119711422, \"jax_sharpe\": 0.036465259363997554, \"return\": -0.4948718813605615, \"returns_over_hodl\": -0.4189679107369426, \"returns_over_uniform_hodl\": -0.4189679107369415, \"sharpe\": -0.08150074629691582, \"sterling\": -1.789746043264265, \"ulcer\": -0.21209649855808693}], \"train_return\": -0.4948718813605615, \"train_returns_over_hodl\": -0.4189679107369426, \"train_sharpe\": 0.036465259363997554, \"validation_return\": -0.3391427108105821, \"validation_returns_over_hodl\": -5.8017992632386495e-09, \"validation_sharpe\": -2.2438531860597966}, {\"centeredness_margin\": 0.8565806395130919, \"continuous_test_metrics\": [{\"annualised_returns\": -0.6724509530314574, \"annualised_returns_over_hodl\": 0.141639558139812, \"annualised_returns_over_uniform_hodl\": 0.15610296425488812, \"calmar\": -1.1727658836709793, \"daily_log_sharpe\": -1.4527158950282941, \"daily_returns\": 0.01834227318405872, \"fee_revenue_over_value\": 0.06125411137652064, \"jax_sharpe\": -0.9182834694992537, \"return\": -0.36205469103292787, \"returns_over_hodl\": 0.054797538850657634, \"returns_over_uniform_hodl\": 0.06015918228112804, \"sharpe\": -1.069778650124822, \"sterling\": -2.078903943214025, \"ulcer\": -0.14664567060204287}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -2.613620214653062, \"optuna_trial_number\": 167, \"price_ratio\": 71.42914180509291, \"shift_exponent\": 0.34568229105617976, \"step\": 167, \"test_objective\": [{\"annualised_returns\": -0.6724509530314574, \"annualised_returns_over_hodl\": 0.141639558139812, \"annualised_returns_over_uniform_hodl\": 0.15610296425488812, \"calmar\": -1.1727658836709793, \"daily_log_sharpe\": -1.4527158950282941, \"daily_returns\": 0.01834227318405872, \"fee_revenue_over_value\": 0.06125411137652064, \"jax_sharpe\": -0.9182834694992537, \"return\": -0.36205469103292787, \"returns_over_hodl\": 0.054797538850657634, \"returns_over_uniform_hodl\": 0.06015918228112804, \"sharpe\": -1.069778650124822, \"sterling\": -2.078903943214025, \"ulcer\": -0.14664567060204287}], \"train_objective\": [{\"annualised_returns\": -0.3051299529976327, \"annualised_returns_over_hodl\": -0.12492362313522876, \"annualised_returns_over_uniform_hodl\": -0.12492362313522876, \"calmar\": -0.41851528222623696, \"daily_log_sharpe\": -0.30994941269888604, \"daily_returns\": 0.008027998556007183, \"fee_revenue_over_value\": 0.027493473285540585, \"jax_sharpe\": 0.16201709982224388, \"return\": -0.19829186682894795, \"returns_over_hodl\": -0.07782177549283342, \"returns_over_uniform_hodl\": -0.07782177549283342, \"sharpe\": 0.1779110620744326, \"sterling\": -1.0130471028643773, \"ulcer\": -0.16270721205751376}], \"train_return\": -0.19829186682894795, \"train_returns_over_hodl\": -0.07782177549283342, \"train_sharpe\": 0.16201709982224388, \"validation_return\": -0.15865966358749628, \"validation_returns_over_hodl\": -0.023874092649366463, \"validation_sharpe\": -1.2520747734508186}, {\"centeredness_margin\": 0.24981722767708692, \"continuous_test_metrics\": [{\"annualised_returns\": -0.6004342026526479, \"annualised_returns_over_hodl\": 0.11827628186237105, \"annualised_returns_over_uniform_hodl\": 0.4102901748711416, \"calmar\": -1.0868716529632334, \"daily_log_sharpe\": -1.1495644008890529, \"daily_returns\": 0.022938919531569436, \"fee_revenue_over_value\": 0.0944106609144066, \"jax_sharpe\": -0.5673186765748577, \"return\": -0.308894194138705, \"returns_over_hodl\": 0.046050290513957925, \"returns_over_uniform_hodl\": 0.14850310161849367, \"sharpe\": -0.747920008539147, \"sterling\": -1.7374235699856764, \"ulcer\": -0.15181908306605618}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -2.556803772472978, \"optuna_trial_number\": 168, \"price_ratio\": 7.792420318705736, \"shift_exponent\": 0.9225651463540683, \"step\": 168, \"test_objective\": [{\"annualised_returns\": -0.6004342026526479, \"annualised_returns_over_hodl\": 0.11827628186237105, \"annualised_returns_over_uniform_hodl\": 0.4102901748711416, \"calmar\": -1.0868716529632334, \"daily_log_sharpe\": -1.1495644008890529, \"daily_returns\": 0.022938919531569436, \"fee_revenue_over_value\": 0.0944106609144066, \"jax_sharpe\": -0.5673186765748577, \"return\": -0.308894194138705, \"returns_over_hodl\": 0.046050290513957925, \"returns_over_uniform_hodl\": 0.14850310161849367, \"sharpe\": -0.747920008539147, \"sterling\": -1.7374235699856764, \"ulcer\": -0.15181908306605618}], \"train_objective\": [{\"annualised_returns\": -0.36508687443650323, \"annualised_returns_over_hodl\": -0.20042966315959276, \"annualised_returns_over_uniform_hodl\": -0.20042966315959299, \"calmar\": -0.49484523105303985, \"daily_log_sharpe\": -0.36262351424903966, \"daily_returns\": 0.008181197687748476, \"fee_revenue_over_value\": 0.04250485574924539, \"jax_sharpe\": 0.12909130625532814, \"return\": -0.24103170209355373, \"returns_over_hodl\": -0.1269839877360185, \"returns_over_uniform_hodl\": -0.1269839877360186, \"sharpe\": 0.1624725465031541, \"sterling\": -1.1538714134498076, \"ulcer\": -0.17033837864169746}], \"train_return\": -0.24103170209355373, \"train_returns_over_hodl\": -0.1269839877360185, \"train_sharpe\": 0.12909130625532814, \"validation_return\": -0.22420592945218432, \"validation_returns_over_hodl\": -0.026113002576199418, \"validation_sharpe\": -1.7203316138659175}, {\"centeredness_margin\": 0.15716712775449931, \"continuous_test_metrics\": [{\"annualised_returns\": -0.4913072843303685, \"annualised_returns_over_hodl\": -0.01319281238150205, \"annualised_returns_over_uniform_hodl\": 0.795459830896748, \"calmar\": -0.8475368597471441, \"daily_log_sharpe\": -0.7137771309246401, \"daily_returns\": 0.0310160417139098, \"fee_revenue_over_value\": 2.3598204791603756e-06, \"jax_sharpe\": -0.0331006346296926, \"return\": -0.23830950784599103, \"returns_over_hodl\": -0.005334325886914959, \"returns_over_uniform_hodl\": 0.26580313071160866, \"sharpe\": -0.2274317515811818, \"sterling\": -1.2648934142430195, \"ulcer\": -0.1764103143688924}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -0.09327078027940428, \"optuna_trial_number\": 169, \"price_ratio\": 5.2042093967295875, \"shift_exponent\": 0.0008483920855084161, \"step\": 169, \"test_objective\": [{\"annualised_returns\": -0.4913072843303685, \"annualised_returns_over_hodl\": -0.01319281238150205, \"annualised_returns_over_uniform_hodl\": 0.795459830896748, \"calmar\": -0.8475368597471441, \"daily_log_sharpe\": -0.7137771309246401, \"daily_returns\": 0.0310160417139098, \"fee_revenue_over_value\": 2.3598204791603756e-06, \"jax_sharpe\": -0.0331006346296926, \"return\": -0.23830950784599103, \"returns_over_hodl\": -0.005334325886914959, \"returns_over_uniform_hodl\": 0.26580313071160866, \"sharpe\": -0.2274317515811818, \"sterling\": -1.2648934142430195, \"ulcer\": -0.1764103143688924}], \"train_objective\": [{\"annualised_returns\": -0.4671568455806334, \"annualised_returns_over_hodl\": -0.3289702743441082, \"annualised_returns_over_uniform_hodl\": -0.3289702743441083, \"calmar\": -0.6262296606288337, \"daily_log_sharpe\": -0.4494666176187397, \"daily_returns\": 0.008189664884457229, \"fee_revenue_over_value\": 0.02986586513236988, \"jax_sharpe\": 0.18230125238067735, \"return\": -0.3176414410298457, \"returns_over_hodl\": -0.2151056246624009, \"returns_over_uniform_hodl\": -0.21510562466240102, \"sharpe\": 0.1414226003252728, \"sterling\": -1.3833409960617478, \"ulcer\": -0.18261662117356453}], \"train_return\": -0.3176414410298457, \"train_returns_over_hodl\": -0.2151056246624009, \"train_sharpe\": 0.18230125238067738, \"validation_return\": -0.33914271230496007, \"validation_returns_over_hodl\": -2.74768285901672e-09, \"validation_sharpe\": -2.243853176976165}, {\"centeredness_margin\": 0.06344770289255754, \"continuous_test_metrics\": [{\"annualised_returns\": -0.5069139781221628, \"annualised_returns_over_hodl\": -0.04346806260419567, \"annualised_returns_over_uniform_hodl\": 0.740375118784474, \"calmar\": -0.8744594664874719, \"daily_log_sharpe\": -0.7515685556685292, \"daily_returns\": 0.031016041796645966, \"fee_revenue_over_value\": 0.0017395578702795783, \"jax_sharpe\": -0.06664569915644541, \"return\": -0.24780861849526659, \"returns_over_hodl\": -0.017738892325950273, \"returns_over_uniform_hodl\": 0.2500171859969438, \"sharpe\": -0.2685526310126888, \"sterling\": -1.3050736448564801, \"ulcer\": -0.17641031453992756}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -4.074834132148903, \"optuna_trial_number\": 170, \"price_ratio\": 6.3994238312576455, \"shift_exponent\": 0.0009295393364384107, \"step\": 170, \"test_objective\": [{\"annualised_returns\": -0.5069139781221628, \"annualised_returns_over_hodl\": -0.04346806260419567, \"annualised_returns_over_uniform_hodl\": 0.740375118784474, \"calmar\": -0.8744594664874719, \"daily_log_sharpe\": -0.7515685556685292, \"daily_returns\": 0.031016041796645966, \"fee_revenue_over_value\": 0.0017395578702795783, \"jax_sharpe\": -0.06664569915644541, \"return\": -0.24780861849526659, \"returns_over_hodl\": -0.017738892325950273, \"returns_over_uniform_hodl\": 0.2500171859969438, \"sharpe\": -0.2685526310126888, \"sterling\": -1.3050736448564801, \"ulcer\": -0.17641031453992756}], \"train_objective\": [{\"annualised_returns\": -0.42259537400895864, \"annualised_returns_over_hodl\": -0.27285231205903826, \"annualised_returns_over_uniform_hodl\": -0.27285231205903826, \"calmar\": -0.5731431574344072, \"daily_log_sharpe\": -0.39385283264467835, \"daily_returns\": 0.008205502907607465, \"fee_revenue_over_value\": 0.04293869711746119, \"jax_sharpe\": 0.20766004767403212, \"return\": -0.28354393331904315, \"returns_over_hodl\": -0.1758843946163806, \"returns_over_uniform_hodl\": -0.1758843946163806, \"sharpe\": 0.18258754982784633, \"sterling\": -1.2755158731478982, \"ulcer\": -0.17860022687372326}], \"train_return\": -0.28354393331904315, \"train_returns_over_hodl\": -0.1758843946163806, \"train_sharpe\": 0.20766004767403212, \"validation_return\": -0.3372864570946382, \"validation_returns_over_hodl\": -0.032048116430124174, \"validation_sharpe\": -2.2273556306165827}, {\"centeredness_margin\": 0.15080781936429358, \"continuous_test_metrics\": [{\"annualised_returns\": -0.5425296629123818, \"annualised_returns_over_hodl\": -0.08429866954577292, \"annualised_returns_over_uniform_hodl\": 0.6146675365429219, \"calmar\": -0.9712166155932472, \"daily_log_sharpe\": -0.8846951839972621, \"daily_returns\": 0.030313413752301777, \"fee_revenue_over_value\": 0.06199315336657437, \"jax_sharpe\": -0.23576691800099722, \"return\": -0.27018075831365407, \"returns_over_hodl\": -0.0348455289873425, \"returns_over_uniform_hodl\": 0.2128384041760636, \"sharpe\": -0.4344206661153364, \"sterling\": -1.4447457752420019, \"ulcer\": -0.16655666717447248}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -2.9807828138967385, \"optuna_trial_number\": 171, \"price_ratio\": 9.354746501677099, \"shift_exponent\": 0.0032833641044477417, \"step\": 171, \"test_objective\": [{\"annualised_returns\": -0.5425296629123818, \"annualised_returns_over_hodl\": -0.08429866954577292, \"annualised_returns_over_uniform_hodl\": 0.6146675365429219, \"calmar\": -0.9712166155932472, \"daily_log_sharpe\": -0.8846951839972621, \"daily_returns\": 0.030313413752301777, \"fee_revenue_over_value\": 0.06199315336657437, \"jax_sharpe\": -0.23576691800099722, \"return\": -0.27018075831365407, \"returns_over_hodl\": -0.0348455289873425, \"returns_over_uniform_hodl\": 0.2128384041760636, \"sharpe\": -0.4344206661153364, \"sterling\": -1.4447457752420019, \"ulcer\": -0.16655666717447248}], \"train_objective\": [{\"annualised_returns\": -0.3949569752176568, \"annualised_returns_over_hodl\": -0.23804622136485476, \"annualised_returns_over_uniform_hodl\": -0.23804622136485465, \"calmar\": -0.5366530642047269, \"daily_log_sharpe\": -0.37916158735880556, \"daily_returns\": 0.00815395400750886, \"fee_revenue_over_value\": 0.035559743392038025, \"jax_sharpe\": 0.15236335022650124, \"return\": -0.2629146642231661, \"returns_over_hodl\": -0.152155231894194, \"returns_over_uniform_hodl\": -0.1521552318941939, \"sharpe\": 0.17319091833962064, \"sterling\": -1.2193371324074376, \"ulcer\": -0.17441558323758366}], \"train_return\": -0.2629146642231661, \"train_returns_over_hodl\": -0.152155231894194, \"train_sharpe\": 0.15236335022650124, \"validation_return\": -0.2950900367727025, \"validation_returns_over_hodl\": -0.05458762165333264, \"validation_sharpe\": -2.0464524685412067}, {\"centeredness_margin\": 0.029016058407977385, \"continuous_test_metrics\": [{\"annualised_returns\": -0.4973462656895963, \"annualised_returns_over_hodl\": -0.024907764271261623, \"annualised_returns_over_uniform_hodl\": 0.7741449032085161, \"calmar\": -0.8579545122440014, \"daily_log_sharpe\": -0.7311876583359425, \"daily_returns\": 0.03101604173757412, \"fee_revenue_over_value\": 0.002387369277068794, \"jax_sharpe\": -0.04997059963458717, \"return\": -0.241964242429469, \"returns_over_hodl\": -0.010106919701868389, \"returns_over_uniform_hodl\": 0.25972956864756847, \"sharpe\": -0.24693953391309972, \"sterling\": -1.2804410797724841, \"ulcer\": -0.17641031441781746}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -0.084781270624809, \"optuna_trial_number\": 172, \"price_ratio\": 4.161667257212629, \"shift_exponent\": 0.0018616465423456647, \"step\": 172, \"test_objective\": [{\"annualised_returns\": -0.4973462656895963, \"annualised_returns_over_hodl\": -0.024907764271261623, \"annualised_returns_over_uniform_hodl\": 0.7741449032085161, \"calmar\": -0.8579545122440014, \"daily_log_sharpe\": -0.7311876583359425, \"daily_returns\": 0.03101604173757412, \"fee_revenue_over_value\": 0.002387369277068794, \"jax_sharpe\": -0.04997059963458717, \"return\": -0.241964242429469, \"returns_over_hodl\": -0.010106919701868389, \"returns_over_uniform_hodl\": 0.25972956864756847, \"sharpe\": -0.24693953391309972, \"sterling\": -1.2804410797724841, \"ulcer\": -0.17641031441781746}], \"train_objective\": [{\"annualised_returns\": -0.4851457503972143, \"annualised_returns_over_hodl\": -0.3516243889063456, \"annualised_returns_over_uniform_hodl\": -0.3516243889063456, \"calmar\": -0.6496762540086737, \"daily_log_sharpe\": -0.46151097670060587, \"daily_returns\": 0.008295071516092043, \"fee_revenue_over_value\": 0.04022351230272943, \"jax_sharpe\": 0.15182378400514654, \"return\": -0.33172167725402446, \"returns_over_hodl\": -0.23130165249923507, \"returns_over_uniform_hodl\": -0.23130165249923507, \"sharpe\": 0.15018357515448577, \"sterling\": -1.408619846668903, \"ulcer\": -0.18657715986339096}], \"train_return\": -0.33172167725402446, \"train_returns_over_hodl\": -0.23130165249923507, \"train_sharpe\": 0.15182378400514654, \"validation_return\": -0.3391427126960428, \"validation_returns_over_hodl\": -2.4885237204230748e-09, \"validation_sharpe\": -2.24385317924085}, {\"centeredness_margin\": 0.033315139084253716, \"continuous_test_metrics\": [{\"annualised_returns\": -0.5165451381965157, \"annualised_returns_over_hodl\": -0.06215143920297306, \"annualised_returns_over_uniform_hodl\": 0.7063813923052689, \"calmar\": -0.8912507589417605, \"daily_log_sharpe\": -0.7749869148585175, \"daily_returns\": 0.031016041777432342, \"fee_revenue_over_value\": 0.004550513043212802, \"jax_sharpe\": -0.09404787727073285, \"return\": -0.25376056587712026, \"returns_over_hodl\": -0.025511337398005485, \"returns_over_uniform_hodl\": 0.24012603767963236, \"sharpe\": -0.29412307389217274, \"sterling\": -1.3302341923031784, \"ulcer\": -0.17630543994973227}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -4.275335282984836, \"optuna_trial_number\": 173, \"price_ratio\": 5.409887033420166, \"shift_exponent\": 0.0019303997282931864, \"step\": 173, \"test_objective\": [{\"annualised_returns\": -0.5165451381965157, \"annualised_returns_over_hodl\": -0.06215143920297306, \"annualised_returns_over_uniform_hodl\": 0.7063813923052689, \"calmar\": -0.8912507589417605, \"daily_log_sharpe\": -0.7749869148585175, \"daily_returns\": 0.031016041777432342, \"fee_revenue_over_value\": 0.004550513043212802, \"jax_sharpe\": -0.09404787727073285, \"return\": -0.25376056587712026, \"returns_over_hodl\": -0.025511337398005485, \"returns_over_uniform_hodl\": 0.24012603767963236, \"sharpe\": -0.29412307389217274, \"sterling\": -1.3302341923031784, \"ulcer\": -0.17630543994973227}], \"train_objective\": [{\"annualised_returns\": -0.438555950079888, \"annualised_returns_over_hodl\": -0.29295207480039553, \"annualised_returns_over_uniform_hodl\": -0.29295207480039553, \"calmar\": -0.593437948379184, \"daily_log_sharpe\": -0.4065872230766334, \"daily_returns\": 0.00823682716420459, \"fee_revenue_over_value\": 0.046262262131840884, \"jax_sharpe\": 0.2147903717162674, \"return\": -0.29563364180387164, \"returns_over_hodl\": -0.1897907845406669, \"returns_over_uniform_hodl\": -0.1897907845406669, \"sharpe\": 0.18187172671384091, \"sterling\": -1.3078386946759055, \"ulcer\": -0.1809314217635904}], \"train_return\": -0.29563364180387164, \"train_returns_over_hodl\": -0.1897907845406669, \"train_sharpe\": 0.21479037171626741, \"validation_return\": -0.339060591493332, \"validation_returns_over_hodl\": -0.005318327032747705, \"validation_sharpe\": -2.2431557632611057}, {\"centeredness_margin\": 0.6478369526780471, \"continuous_test_metrics\": [{\"annualised_returns\": -0.6399594223761187, \"annualised_returns_over_hodl\": -0.1297171564255526, \"annualised_returns_over_uniform_hodl\": 0.27078366704265444, \"calmar\": -1.088677838419354, \"daily_log_sharpe\": -1.1746561647068752, \"daily_returns\": 0.025876957150069425, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": -0.5225158435817372, \"return\": -0.3372863002401252, \"returns_over_hodl\": -0.054418493603503704, \"returns_over_uniform_hodl\": 0.1013201353600579, \"sharpe\": -0.7427305642203492, \"sterling\": -1.7440733721457191, \"ulcer\": -0.16232351453750268}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -2.9896866399042312, \"optuna_trial_number\": 174, \"price_ratio\": 35.448005392536096, \"shift_exponent\": 0.00018187934351102382, \"step\": 174, \"test_objective\": [{\"annualised_returns\": -0.6399594223761187, \"annualised_returns_over_hodl\": -0.1297171564255526, \"annualised_returns_over_uniform_hodl\": 0.27078366704265444, \"calmar\": -1.088677838419354, \"daily_log_sharpe\": -1.1746561647068752, \"daily_returns\": 0.025876957150069425, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": -0.5225158435817372, \"return\": -0.3372863002401252, \"returns_over_hodl\": -0.054418493603503704, \"returns_over_uniform_hodl\": 0.1013201353600579, \"sharpe\": -0.7427305642203492, \"sterling\": -1.7440733721457191, \"ulcer\": -0.16232351453750268}], \"train_objective\": [{\"annualised_returns\": -0.3791553371526001, \"annualised_returns_over_hodl\": -0.2181466153217534, \"annualised_returns_over_uniform_hodl\": -0.2181466153217536, \"calmar\": -0.5145127860395113, \"daily_log_sharpe\": -0.3860820045931395, \"daily_returns\": 0.008013410991421825, \"fee_revenue_over_value\": 0.011529714437779434, \"jax_sharpe\": 0.13627938806381865, \"return\": -0.25128674280312535, \"returns_over_hodl\": -0.13878002028516534, \"returns_over_uniform_hodl\": -0.13878002028516545, \"sharpe\": 0.1347951029490261, \"sterling\": -1.2071188757435212, \"ulcer\": -0.16946446072573207}], \"train_return\": -0.25128674280312535, \"train_returns_over_hodl\": -0.13878002028516534, \"train_sharpe\": 0.13627938806381865, \"validation_return\": -0.24614666979809652, \"validation_returns_over_hodl\": -0.05100707549434935, \"validation_sharpe\": -1.8097984984694444}, {\"centeredness_margin\": 0.057190542616654905, \"continuous_test_metrics\": [{\"annualised_returns\": -0.5157754788744264, \"annualised_returns_over_hodl\": -0.060658386246252194, \"annualised_returns_over_uniform_hodl\": 0.7090979485949878, \"calmar\": -0.8898306298365678, \"daily_log_sharpe\": -0.7722947821697247, \"daily_returns\": 0.03101604178695606, \"fee_revenue_over_value\": 0.002065797815748485, \"jax_sharpe\": -0.08945339967937335, \"return\": -0.2532823358192585, \"returns_over_hodl\": -0.02488683320513685, \"returns_over_uniform_hodl\": 0.24092077663289135, \"sharpe\": -0.290895266003177, \"sterling\": -1.3281204132722215, \"ulcer\": -0.17634337115540188}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -4.160837146650833, \"optuna_trial_number\": 175, \"price_ratio\": 5.930397239730402, \"shift_exponent\": 0.00143269849123118, \"step\": 175, \"test_objective\": [{\"annualised_returns\": -0.5157754788744264, \"annualised_returns_over_hodl\": -0.060658386246252194, \"annualised_returns_over_uniform_hodl\": 0.7090979485949878, \"calmar\": -0.8898306298365678, \"daily_log_sharpe\": -0.7722947821697247, \"daily_returns\": 0.03101604178695606, \"fee_revenue_over_value\": 0.002065797815748485, \"jax_sharpe\": -0.08945339967937335, \"return\": -0.2532823358192585, \"returns_over_hodl\": -0.02488683320513685, \"returns_over_uniform_hodl\": 0.24092077663289135, \"sharpe\": -0.290895266003177, \"sterling\": -1.3281204132722215, \"ulcer\": -0.17634337115540188}], \"train_objective\": [{\"annualised_returns\": -0.4316264806494572, \"annualised_returns_over_hodl\": -0.2842255294140523, \"annualised_returns_over_uniform_hodl\": -0.28422552941405255, \"calmar\": -0.5844044408797116, \"daily_log_sharpe\": -0.4032816740311561, \"daily_returns\": 0.00819729221753611, \"fee_revenue_over_value\": 0.04323189382194828, \"jax_sharpe\": 0.1616045922414494, \"return\": -0.2903683867374923, \"returns_over_hodl\": -0.1837343366043267, \"returns_over_uniform_hodl\": -0.1837343366043268, \"sharpe\": 0.17800733872470223, \"sterling\": -1.2960013109550068, \"ulcer\": -0.17962111481747037}], \"train_return\": -0.2903683867374923, \"train_returns_over_hodl\": -0.1837343366043267, \"train_sharpe\": 0.1616045922414494, \"validation_return\": -0.3381722597708461, \"validation_returns_over_hodl\": -0.02408869140067038, \"validation_sharpe\": -2.2350567060823123}, {\"centeredness_margin\": 0.6517391519869618, \"continuous_test_metrics\": [{\"annualised_returns\": -0.6223203940169413, \"annualised_returns_over_hodl\": -0.1330932950504592, \"annualised_returns_over_uniform_hodl\": 0.3330416194359025, \"calmar\": -1.062613374608367, \"daily_log_sharpe\": -1.1002249558379062, \"daily_returns\": 0.02705013603349753, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": -0.43521228504770726, \"return\": -0.3243968704794764, \"returns_over_hodl\": -0.055897551297591774, \"returns_over_uniform_hodl\": 0.12274022752633629, \"sharpe\": -0.6588943269192152, \"sterling\": -1.671091623819029, \"ulcer\": -0.16477169145205028}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -2.926352382327127, \"optuna_trial_number\": 176, \"price_ratio\": 29.017287725713896, \"shift_exponent\": 8.134247699145589e-05, \"step\": 176, \"test_objective\": [{\"annualised_returns\": -0.6223203940169413, \"annualised_returns_over_hodl\": -0.1330932950504592, \"annualised_returns_over_uniform_hodl\": 0.3330416194359025, \"calmar\": -1.062613374608367, \"daily_log_sharpe\": -1.1002249558379062, \"daily_returns\": 0.02705013603349753, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": -0.43521228504770726, \"return\": -0.3243968704794764, \"returns_over_hodl\": -0.055897551297591774, \"returns_over_uniform_hodl\": 0.12274022752633629, \"sharpe\": -0.6588943269192152, \"sterling\": -1.671091623819029, \"ulcer\": -0.16477169145205028}], \"train_objective\": [{\"annualised_returns\": -0.38548316677352834, \"annualised_returns_over_hodl\": -0.22611549272838094, \"annualised_returns_over_uniform_hodl\": -0.22611549272838116, \"calmar\": -0.5227617577039471, \"daily_log_sharpe\": -0.3914360226318557, \"daily_returns\": 0.008038187353421308, \"fee_revenue_over_value\": 0.011832073161120431, \"jax_sharpe\": 0.10778750478170655, \"return\": -0.2559290691916698, \"returns_over_hodl\": -0.14411993406355939, \"returns_over_uniform_hodl\": -0.1441199340635595, \"sharpe\": 0.13333995893670597, \"sterling\": -1.2217068752684845, \"ulcer\": -0.1701958317531793}], \"train_return\": -0.2559290691916698, \"train_returns_over_hodl\": -0.14411993406355939, \"train_sharpe\": 0.10778750478170655, \"validation_return\": -0.2572202497288347, \"validation_returns_over_hodl\": -0.05248144257664844, \"validation_sharpe\": -1.8620415255495864}, {\"centeredness_margin\": 0.025621675335214047, \"continuous_test_metrics\": [{\"annualised_returns\": -0.45203445740064485, \"annualised_returns_over_hodl\": 0.0629920950773688, \"annualised_returns_over_uniform_hodl\": 0.9340755040251849, \"calmar\": -0.8165927845723918, \"daily_log_sharpe\": -0.6767284049872099, \"daily_returns\": 0.031016041700357423, \"fee_revenue_over_value\": 0.08892230357768828, \"jax_sharpe\": -0.03178223852728052, \"return\": -0.2151511026406404, \"returns_over_hodl\": 0.024907446480838535, \"returns_over_uniform_hodl\": 0.3042885550580814, \"sharpe\": -0.2135160479819821, \"sterling\": -1.1916377867656593, \"ulcer\": -0.16990311766853938}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -3.5955448251676594, \"optuna_trial_number\": 177, \"price_ratio\": 6.935039785439521, \"shift_exponent\": 0.005131078999604597, \"step\": 177, \"test_objective\": [{\"annualised_returns\": -0.45203445740064485, \"annualised_returns_over_hodl\": 0.0629920950773688, \"annualised_returns_over_uniform_hodl\": 0.9340755040251849, \"calmar\": -0.8165927845723918, \"daily_log_sharpe\": -0.6767284049872099, \"daily_returns\": 0.031016041700357423, \"fee_revenue_over_value\": 0.08892230357768828, \"jax_sharpe\": -0.03178223852728052, \"return\": -0.2151511026406404, \"returns_over_hodl\": 0.024907446480838535, \"returns_over_uniform_hodl\": 0.3042885550580814, \"sharpe\": -0.2135160479819821, \"sterling\": -1.1916377867656593, \"ulcer\": -0.16990311766853938}], \"train_objective\": [{\"annualised_returns\": -0.4113969524836312, \"annualised_returns_over_hodl\": -0.25874971233228006, \"annualised_returns_over_uniform_hodl\": -0.25874971233228006, \"calmar\": -0.5584407211900522, \"daily_log_sharpe\": -0.38264009344789585, \"daily_returns\": 0.008177724614819023, \"fee_revenue_over_value\": 0.04505660279383981, \"jax_sharpe\": 0.16967254374864146, \"return\": -0.2751396886759093, \"returns_over_hodl\": -0.16621727127976926, \"returns_over_uniform_hodl\": -0.16621727127976926, \"sharpe\": 0.1888019296735392, \"sterling\": -1.2476481925925726, \"ulcer\": -0.17764272341537996}], \"train_return\": -0.2751396886759093, \"train_returns_over_hodl\": -0.16621727127976926, \"train_sharpe\": 0.16967254374864146, \"validation_return\": -0.33253871689930825, \"validation_returns_over_hodl\": -0.04243244204639185, \"validation_sharpe\": -2.2176030217031113}, {\"centeredness_margin\": 0.011614883617876565, \"continuous_test_metrics\": [{\"annualised_returns\": -0.4793973244999804, \"annualised_returns_over_hodl\": 0.009911180755421967, \"annualised_returns_over_uniform_hodl\": 0.8374967105381359, \"calmar\": -0.8269913730777397, \"daily_log_sharpe\": -0.682424980188457, \"daily_returns\": 0.031016041759329666, \"fee_revenue_over_value\": 0.0004193242118150121, \"jax_sharpe\": 0.008472283417430322, \"return\": -0.2311769367896348, \"returns_over_hodl\": 0.0039798550035439195, \"returns_over_uniform_hodl\": 0.27765627955114147, \"sharpe\": -0.19169553283744376, \"sterling\": -1.2342306162169614, \"ulcer\": -0.1764103144627765}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -0.08421055349376627, \"optuna_trial_number\": 178, \"price_ratio\": 4.718583818243449, \"shift_exponent\": 5.4650305012308576e-05, \"step\": 178, \"test_objective\": [{\"annualised_returns\": -0.4793973244999804, \"annualised_returns_over_hodl\": 0.009911180755421967, \"annualised_returns_over_uniform_hodl\": 0.8374967105381359, \"calmar\": -0.8269913730777397, \"daily_log_sharpe\": -0.682424980188457, \"daily_returns\": 0.031016041759329666, \"fee_revenue_over_value\": 0.0004193242118150121, \"jax_sharpe\": 0.008472283417430322, \"return\": -0.2311769367896348, \"returns_over_hodl\": 0.0039798550035439195, \"returns_over_uniform_hodl\": 0.27765627955114147, \"sharpe\": -0.19169553283744376, \"sterling\": -1.2342306162169614, \"ulcer\": -0.1764103144627765}], \"train_objective\": [{\"annualised_returns\": -0.45716933192071296, \"annualised_returns_over_hodl\": -0.3163926171186836, \"annualised_returns_over_uniform_hodl\": -0.3163926171186836, \"calmar\": -0.6171746947507671, \"daily_log_sharpe\": -0.424500107593791, \"daily_returns\": 0.008249320923442677, \"fee_revenue_over_value\": 0.04794824615495211, \"jax_sharpe\": 0.1697932123787882, \"return\": -0.3099047019722778, \"returns_over_hodl\": -0.20620631081939056, \"returns_over_uniform_hodl\": -0.20620631081939056, \"sharpe\": 0.17687291344541062, \"sterling\": -1.3453479322059, \"ulcer\": -0.18366631181928172}], \"train_return\": -0.3099047019722778, \"train_returns_over_hodl\": -0.20620631081939056, \"train_sharpe\": 0.1697932123787882, \"validation_return\": -0.3391427129244573, \"validation_returns_over_hodl\": -2.4709383428245246e-09, \"validation_sharpe\": -2.2438531800290464}, {\"centeredness_margin\": 0.01201833119731392, \"continuous_test_metrics\": [{\"annualised_returns\": -0.462835833551711, \"annualised_returns_over_hodl\": 0.08613643737181276, \"annualised_returns_over_uniform_hodl\": 0.8959514334414007, \"calmar\": -0.8404209866059335, \"daily_log_sharpe\": -0.7127425530170235, \"daily_returns\": 0.03024843909783023, \"fee_revenue_over_value\": 0.10750021173704281, \"jax_sharpe\": -0.08157411330319395, \"return\": -0.22141883338931256, \"returns_over_hodl\": 0.03383682725983839, \"returns_over_uniform_hodl\": 0.29387262721619534, \"sharpe\": -0.2604401805807155, \"sterling\": -1.2414653040107122, \"ulcer\": -0.16357192008528987}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -3.1491034381623715, \"optuna_trial_number\": 179, \"price_ratio\": 7.9239602055451615, \"shift_exponent\": 0.046126257365457835, \"step\": 179, \"test_objective\": [{\"annualised_returns\": -0.462835833551711, \"annualised_returns_over_hodl\": 0.08613643737181276, \"annualised_returns_over_uniform_hodl\": 0.8959514334414007, \"calmar\": -0.8404209866059335, \"daily_log_sharpe\": -0.7127425530170235, \"daily_returns\": 0.03024843909783023, \"fee_revenue_over_value\": 0.10750021173704281, \"jax_sharpe\": -0.08157411330319395, \"return\": -0.22141883338931256, \"returns_over_hodl\": 0.03383682725983839, \"returns_over_uniform_hodl\": 0.29387262721619534, \"sharpe\": -0.2604401805807155, \"sterling\": -1.2414653040107122, \"ulcer\": -0.16357192008528987}], \"train_objective\": [{\"annualised_returns\": -0.3997268214521009, \"annualised_returns_over_hodl\": -0.2440530708168428, \"annualised_returns_over_uniform_hodl\": -0.24405307081684335, \"calmar\": -0.5432451475729312, \"daily_log_sharpe\": -0.37146034431919794, \"daily_returns\": 0.008178938090491535, \"fee_revenue_over_value\": 0.043726343097217554, \"jax_sharpe\": 0.1703864426966808, \"return\": -0.2664480082998004, \"returns_over_hodl\": -0.15621952017111995, \"returns_over_uniform_hodl\": -0.15621952017112029, \"sharpe\": 0.19243170394922277, \"sterling\": -1.2214766404811674, \"ulcer\": -0.176222229872541}], \"train_return\": -0.2664480082998004, \"train_returns_over_hodl\": -0.15621952017111995, \"train_sharpe\": 0.17038644269668077, \"validation_return\": -0.3176867706218566, \"validation_returns_over_hodl\": -0.03300925102419472, \"validation_sharpe\": -2.194715484151478}, {\"centeredness_margin\": 0.13845686854830586, \"continuous_test_metrics\": [{\"annualised_returns\": -0.48081584310406966, \"annualised_returns_over_hodl\": 0.007159418149730312, \"annualised_returns_over_uniform_hodl\": 0.8324899685609688, \"calmar\": -0.829438413221143, \"daily_log_sharpe\": -0.6850213839577901, \"daily_returns\": 0.031016041627516228, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": 0.008412645338705145, \"return\": -0.23202130372056273, \"returns_over_hodl\": 0.00287722715941241, \"returns_over_uniform_hodl\": 0.27625308190636644, \"sharpe\": -0.19428055174181721, \"sterling\": -1.2378826664654101, \"ulcer\": -0.1764103141902946}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -0.10986655629355835, \"optuna_trial_number\": 180, \"price_ratio\": 3.3536686619680953, \"shift_exponent\": 0.0007313069340259335, \"step\": 180, \"test_objective\": [{\"annualised_returns\": -0.48081584310406966, \"annualised_returns_over_hodl\": 0.007159418149730312, \"annualised_returns_over_uniform_hodl\": 0.8324899685609688, \"calmar\": -0.829438413221143, \"daily_log_sharpe\": -0.6850213839577901, \"daily_returns\": 0.031016041627516228, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": 0.008412645338705145, \"return\": -0.23202130372056273, \"returns_over_hodl\": 0.00287722715941241, \"returns_over_uniform_hodl\": 0.27625308190636644, \"sharpe\": -0.19428055174181721, \"sterling\": -1.2378826664654101, \"ulcer\": -0.1764103141902946}], \"train_objective\": [{\"annualised_returns\": -0.5280627740400823, \"annualised_returns_over_hodl\": -0.4056714351378441, \"annualised_returns_over_uniform_hodl\": -0.4056714351378442, \"calmar\": -0.6967112016984051, \"daily_log_sharpe\": -0.5186627967329761, \"daily_returns\": 0.008373876791652166, \"fee_revenue_over_value\": 0.027622095562935652, \"jax_sharpe\": 0.1345431831391814, \"return\": -0.36611840022461306, \"returns_over_hodl\": -0.27086705991554527, \"returns_over_uniform_hodl\": -0.2708670599155454, \"sharpe\": 0.11193180719360082, \"sterling\": -1.4952988481682332, \"ulcer\": -0.19255650878298874}], \"train_return\": -0.36611840022461306, \"train_returns_over_hodl\": -0.27086705991554527, \"train_sharpe\": 0.1345431831391814, \"validation_return\": -0.3391427119741174, \"validation_returns_over_hodl\": -3.2337920119474006e-09, \"validation_sharpe\": -2.243853179657643}, {\"centeredness_margin\": 0.039978536352313056, \"continuous_test_metrics\": [{\"annualised_returns\": -0.5254646646656176, \"annualised_returns_over_hodl\": -0.07945432708175826, \"annualised_returns_over_uniform_hodl\": 0.6748994170526621, \"calmar\": -0.9084487917145786, \"daily_log_sharpe\": -0.7951669576961375, \"daily_returns\": 0.0310160417570602, \"fee_revenue_over_value\": 0.003578185553942392, \"jax_sharpe\": -0.11271115743109426, \"return\": -0.25933622692717484, \"returns_over_hodl\": -0.03279240345538725, \"returns_over_uniform_hodl\": 0.23086021476908614, \"sharpe\": -0.31582558938048133, \"sterling\": -1.3564861891792908, \"ulcer\": -0.17535096467321504}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -3.1221863641148797, \"optuna_trial_number\": 181, \"price_ratio\": 10.657345122224738, \"shift_exponent\": 4.741033327993184e-05, \"step\": 181, \"test_objective\": [{\"annualised_returns\": -0.5254646646656176, \"annualised_returns_over_hodl\": -0.07945432708175826, \"annualised_returns_over_uniform_hodl\": 0.6748994170526621, \"calmar\": -0.9084487917145786, \"daily_log_sharpe\": -0.7951669576961375, \"daily_returns\": 0.0310160417570602, \"fee_revenue_over_value\": 0.003578185553942392, \"jax_sharpe\": -0.11271115743109426, \"return\": -0.25933622692717484, \"returns_over_hodl\": -0.03279240345538725, \"returns_over_uniform_hodl\": 0.23086021476908614, \"sharpe\": -0.31582558938048133, \"sterling\": -1.3564861891792908, \"ulcer\": -0.17535096467321504}], \"train_objective\": [{\"annualised_returns\": -0.3818071065578429, \"annualised_returns_over_hodl\": -0.2214860897651133, \"annualised_returns_over_uniform_hodl\": -0.22148608976511353, \"calmar\": -0.5199445715321522, \"daily_log_sharpe\": -0.3586638987932985, \"daily_returns\": 0.008132614741916738, \"fee_revenue_over_value\": 0.04015209445445306, \"jax_sharpe\": 0.16709772435033052, \"return\": -0.2532299039471799, \"returns_over_hodl\": -0.14101517397715524, \"returns_over_uniform_hodl\": -0.14101517397715535, \"sharpe\": 0.19162691959781228, \"sterling\": -1.182737867259072, \"ulcer\": -0.1737022916820321}], \"train_return\": -0.2532299039471799, \"train_returns_over_hodl\": -0.14101517397715524, \"train_sharpe\": 0.16709772435033055, \"validation_return\": -0.3149970708780552, \"validation_returns_over_hodl\": -0.06537482623286439, \"validation_sharpe\": -2.085219988788854}, {\"centeredness_margin\": 0.03097351935811341, \"continuous_test_metrics\": [{\"annualised_returns\": -0.4828783970570867, \"annualised_returns_over_hodl\": 0.0031582921379027518, \"annualised_returns_over_uniform_hodl\": 0.8252100672421061, \"calmar\": -0.8329964583426175, \"daily_log_sharpe\": -0.6891033751731692, \"daily_returns\": 0.031016041655522252, \"fee_revenue_over_value\": 1.6033572250650393e-06, \"jax_sharpe\": 0.0068171038983114345, \"return\": -0.23325149174894788, \"returns_over_hodl\": 0.0012707660439230661, \"returns_over_uniform_hodl\": 0.2742087136574072, \"sharpe\": -0.1985182758798755, \"sterling\": -1.2431928158924292, \"ulcer\": -0.17641031424818554}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -0.10861445914063839, \"optuna_trial_number\": 182, \"price_ratio\": 3.589105592263118, \"shift_exponent\": 0.00048184336403686616, \"step\": 182, \"test_objective\": [{\"annualised_returns\": -0.4828783970570867, \"annualised_returns_over_hodl\": 0.0031582921379027518, \"annualised_returns_over_uniform_hodl\": 0.8252100672421061, \"calmar\": -0.8329964583426175, \"daily_log_sharpe\": -0.6891033751731692, \"daily_returns\": 0.031016041655522252, \"fee_revenue_over_value\": 1.6033572250650393e-06, \"jax_sharpe\": 0.0068171038983114345, \"return\": -0.23325149174894788, \"returns_over_hodl\": 0.0012707660439230661, \"returns_over_uniform_hodl\": 0.2742087136574072, \"sharpe\": -0.1985182758798755, \"sterling\": -1.2431928158924292, \"ulcer\": -0.17641031424818554}], \"train_objective\": [{\"annualised_returns\": -0.504798928099021, \"annualised_returns_over_hodl\": -0.37637438584659066, \"annualised_returns_over_uniform_hodl\": -0.37637438584659066, \"calmar\": -0.6723089606607288, \"daily_log_sharpe\": -0.4823544411687392, \"daily_returns\": 0.008336664935956006, \"fee_revenue_over_value\": 0.0401324851604487, \"jax_sharpe\": 0.15983206915636655, \"return\": -0.3473274197567705, \"returns_over_hodl\": -0.2492524195151259, \"returns_over_uniform_hodl\": -0.2492524195151259, \"sharpe\": 0.1443219422575701, \"sterling\": -1.4379416361337491, \"ulcer\": -0.1909062095148773}], \"train_return\": -0.3473274197567705, \"train_returns_over_hodl\": -0.2492524195151259, \"train_sharpe\": 0.15983206915636655, \"validation_return\": -0.33914271225797676, \"validation_returns_over_hodl\": -3.1957306800833862e-09, \"validation_sharpe\": -2.243853180565511}, {\"centeredness_margin\": 0.016453591140506735, \"continuous_test_metrics\": [{\"annualised_returns\": -0.4509914673667266, \"annualised_returns_over_hodl\": 0.17123484171734193, \"annualised_returns_over_uniform_hodl\": 0.9377567965859812, \"calmar\": -0.8236307067650345, \"daily_log_sharpe\": -0.6837776509685437, \"daily_returns\": 0.02901011548150148, \"fee_revenue_over_value\": 0.08895565979635243, \"jax_sharpe\": -0.042323076816051027, \"return\": -0.2145498061042136, \"returns_over_hodl\": 0.06572591956302287, \"returns_over_uniform_hodl\": 0.305287810065378, \"sharpe\": -0.22746488604894213, \"sterling\": -1.2018080637727662, \"ulcer\": -0.16563356928233872}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -2.521766778650347, \"optuna_trial_number\": 183, \"price_ratio\": 19.510946428190174, \"shift_exponent\": 1.0322516168222758e-05, \"step\": 183, \"test_objective\": [{\"annualised_returns\": -0.4509914673667266, \"annualised_returns_over_hodl\": 0.17123484171734193, \"annualised_returns_over_uniform_hodl\": 0.9377567965859812, \"calmar\": -0.8236307067650345, \"daily_log_sharpe\": -0.6837776509685437, \"daily_returns\": 0.02901011548150148, \"fee_revenue_over_value\": 0.08895565979635243, \"jax_sharpe\": -0.042323076816051027, \"return\": -0.2145498061042136, \"returns_over_hodl\": 0.06572591956302287, \"returns_over_uniform_hodl\": 0.305287810065378, \"sharpe\": -0.22746488604894213, \"sterling\": -1.2018080637727662, \"ulcer\": -0.16563356928233872}], \"train_objective\": [{\"annualised_returns\": -0.35687180296177445, \"annualised_returns_over_hodl\": -0.19008411004098769, \"annualised_returns_over_uniform_hodl\": -0.19008411004098769, \"calmar\": -0.48738473719228304, \"daily_log_sharpe\": -0.34026169568671455, \"daily_returns\": 0.008072899808450086, \"fee_revenue_over_value\": 0.034943070017637134, \"jax_sharpe\": 0.1639423481136662, \"return\": -0.23508470741146625, \"returns_over_hodl\": -0.12014335737420723, \"returns_over_uniform_hodl\": -0.12014335737420723, \"sharpe\": 0.1918723055199976, \"sterling\": -1.127136301808619, \"ulcer\": -0.17023916746751483}], \"train_return\": -0.23508470741146625, \"train_returns_over_hodl\": -0.12014335737420723, \"train_sharpe\": 0.1639423481136662, \"validation_return\": -0.26786366435355347, \"validation_returns_over_hodl\": -0.04938532125119699, \"validation_sharpe\": -1.8721327088130526}, {\"centeredness_margin\": 0.016753711844864606, \"continuous_test_metrics\": [{\"annualised_returns\": -0.5012384597049607, \"annualised_returns_over_hodl\": -0.03245818922528465, \"annualised_returns_over_uniform_hodl\": 0.7604071833761326, \"calmar\": -0.8668474732069789, \"daily_log_sharpe\": -0.7431756785287318, \"daily_returns\": 0.031016041764659316, \"fee_revenue_over_value\": 0.018086426112635702, \"jax_sharpe\": -0.07014065248083606, \"return\": -0.24433367812861673, \"returns_over_hodl\": -0.013201086015389052, \"returns_over_uniform_hodl\": 0.25579195992474, \"sharpe\": -0.2618254315043391, \"sterling\": -1.2943561293604238, \"ulcer\": -0.17521709237053484}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -2.8826457030917934, \"optuna_trial_number\": 184, \"price_ratio\": 10.723733703501953, \"shift_exponent\": 6.207041173108971e-05, \"step\": 184, \"test_objective\": [{\"annualised_returns\": -0.5012384597049607, \"annualised_returns_over_hodl\": -0.03245818922528465, \"annualised_returns_over_uniform_hodl\": 0.7604071833761326, \"calmar\": -0.8668474732069789, \"daily_log_sharpe\": -0.7431756785287318, \"daily_returns\": 0.031016041764659316, \"fee_revenue_over_value\": 0.018086426112635702, \"jax_sharpe\": -0.07014065248083606, \"return\": -0.24433367812861673, \"returns_over_hodl\": -0.013201086015389052, \"returns_over_uniform_hodl\": 0.25579195992474, \"sharpe\": -0.2618254315043391, \"sterling\": -1.2943561293604238, \"ulcer\": -0.17521709237053484}], \"train_objective\": [{\"annualised_returns\": -0.38110151867472575, \"annualised_returns_over_hodl\": -0.22059751600807564, \"annualised_returns_over_uniform_hodl\": -0.22059751600807564, \"calmar\": -0.5190770776500514, \"daily_log_sharpe\": -0.3576786268431494, \"daily_returns\": 0.00816139495153858, \"fee_revenue_over_value\": 0.04008941070622244, \"jax_sharpe\": 0.1674265573257246, \"return\": -0.2527125446922618, \"returns_over_hodl\": -0.1404200728182763, \"returns_over_uniform_hodl\": -0.1404200728182763, \"sharpe\": 0.19238347373925624, \"sterling\": -1.1810043743058647, \"ulcer\": -0.17363124979086642}], \"train_return\": -0.2527125446922618, \"train_returns_over_hodl\": -0.1404200728182763, \"train_sharpe\": 0.1674265573257246, \"validation_return\": -0.31107708522229405, \"validation_returns_over_hodl\": -0.06065092805976102, \"validation_sharpe\": -2.052977080872013}, {\"centeredness_margin\": 0.051280987470590636, \"continuous_test_metrics\": [{\"annualised_returns\": -0.4824568532972906, \"annualised_returns_over_hodl\": 0.003976037580391667, \"annualised_returns_over_uniform_hodl\": 0.8266979298836672, \"calmar\": -0.8322692670003831, \"daily_log_sharpe\": -0.6882824193775242, \"daily_returns\": 0.03101604171606352, \"fee_revenue_over_value\": 5.524714724787989e-07, \"jax_sharpe\": 0.006994953806817764, \"return\": -0.23299982858509494, \"returns_over_hodl\": 0.0015994034017388081, \"returns_over_uniform_hodl\": 0.2746269360508482, \"sharpe\": -0.1976776940422164, \"sterling\": -1.2421075290849806, \"ulcer\": -0.176410314373346}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -0.09499935072837251, \"optuna_trial_number\": 185, \"price_ratio\": 4.274844754827158, \"shift_exponent\": 4.656501382300084e-05, \"step\": 185, \"test_objective\": [{\"annualised_returns\": -0.4824568532972906, \"annualised_returns_over_hodl\": 0.003976037580391667, \"annualised_returns_over_uniform_hodl\": 0.8266979298836672, \"calmar\": -0.8322692670003831, \"daily_log_sharpe\": -0.6882824193775242, \"daily_returns\": 0.03101604171606352, \"fee_revenue_over_value\": 5.524714724787989e-07, \"jax_sharpe\": 0.006994953806817764, \"return\": -0.23299982858509494, \"returns_over_hodl\": 0.0015994034017388081, \"returns_over_uniform_hodl\": 0.2746269360508482, \"sharpe\": -0.1976776940422164, \"sterling\": -1.2421075290849806, \"ulcer\": -0.176410314373346}], \"train_objective\": [{\"annualised_returns\": -0.48694234798201386, \"annualised_returns_over_hodl\": -0.35388691283002394, \"annualised_returns_over_uniform_hodl\": -0.35388691283002416, \"calmar\": -0.6522630402023222, \"daily_log_sharpe\": -0.46393477621658835, \"daily_returns\": 0.008276887261193947, \"fee_revenue_over_value\": 0.03864323353369112, \"jax_sharpe\": 0.20302561936910118, \"return\": -0.3331384411975876, \"returns_over_hodl\": -0.23293130898388748, \"returns_over_uniform_hodl\": -0.23293130898388759, \"sharpe\": 0.1484174461607755, \"sterling\": -1.4125319139503993, \"ulcer\": -0.18684506206422288}], \"train_return\": -0.3331384411975876, \"train_returns_over_hodl\": -0.23293130898388748, \"train_sharpe\": 0.20302561936910118, \"validation_return\": -0.3391427126582346, \"validation_returns_over_hodl\": -2.790506936634074e-09, \"validation_sharpe\": -2.2438531803639736}, {\"centeredness_margin\": 0.11942858035378483, \"continuous_test_metrics\": [{\"annualised_returns\": -0.48095644739217713, \"annualised_returns_over_hodl\": 0.006886658646813926, \"annualised_returns_over_uniform_hodl\": 0.831993697740546, \"calmar\": -0.8296809653022059, \"daily_log_sharpe\": -0.6853406577283113, \"daily_returns\": 0.031016041696794613, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": 0.007149431412632058, \"return\": -0.23210507282125603, \"returns_over_hodl\": 0.0027678348004258613, \"returns_over_uniform_hodl\": 0.2761138715696134, \"sharpe\": -0.19464674030933635, \"sterling\": -1.2382446582176012, \"ulcer\": -0.17641031433350934}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -0.0914548098287935, \"optuna_trial_number\": 186, \"price_ratio\": 4.887722060743193, \"shift_exponent\": 1.1881769962972602e-05, \"step\": 186, \"test_objective\": [{\"annualised_returns\": -0.48095644739217713, \"annualised_returns_over_hodl\": 0.006886658646813926, \"annualised_returns_over_uniform_hodl\": 0.831993697740546, \"calmar\": -0.8296809653022059, \"daily_log_sharpe\": -0.6853406577283113, \"daily_returns\": 0.031016041696794613, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": 0.007149431412632058, \"return\": -0.23210507282125603, \"returns_over_hodl\": 0.0027678348004258613, \"returns_over_uniform_hodl\": 0.2761138715696134, \"sharpe\": -0.19464674030933635, \"sterling\": -1.2382446582176012, \"ulcer\": -0.17641031433350934}], \"train_objective\": [{\"annualised_returns\": -0.4756795699921782, \"annualised_returns_over_hodl\": -0.3397032665507015, \"annualised_returns_over_uniform_hodl\": -0.3397032665507015, \"calmar\": -0.638446838159696, \"daily_log_sharpe\": -0.45463913948131723, \"daily_returns\": 0.00825835737567791, \"fee_revenue_over_value\": 0.03420765531114366, \"jax_sharpe\": 0.14046473776204638, \"return\": -0.3242886578701426, \"returns_over_hodl\": -0.22275169730414024, \"returns_over_uniform_hodl\": -0.22275169730414024, \"sharpe\": 0.14577050021534763, \"sterling\": -1.3957434716219794, \"ulcer\": -0.18444510091738167}], \"train_return\": -0.3242886578701426, \"train_returns_over_hodl\": -0.22275169730414024, \"train_sharpe\": 0.14046473776204638, \"validation_return\": -0.3391427123769275, \"validation_returns_over_hodl\": -2.6865579760837477e-09, \"validation_sharpe\": -2.2438531788835503}, {\"centeredness_margin\": 0.15584522886966895, \"continuous_test_metrics\": [{\"annualised_returns\": -0.48400508803139763, \"annualised_returns_over_hodl\": 0.0009726377155803156, \"annualised_returns_over_uniform_hodl\": 0.8212333474584426, \"calmar\": -0.8349400764607012, \"daily_log_sharpe\": -0.6914175675895499, \"daily_returns\": 0.03101604164250684, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": 0.005577482957868991, \"return\": -0.2339247323551149, \"returns_over_hodl\": 0.0003916042770972794, \"returns_over_uniform_hodl\": 0.27308989955143304, \"sharpe\": -0.20098261708574675, \"sterling\": -1.2460935392698487, \"ulcer\": -0.176410314221283}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -0.10890183793102015, \"optuna_trial_number\": 187, \"price_ratio\": 3.92438727961223, \"shift_exponent\": 6.167332685426009e-05, \"step\": 187, \"test_objective\": [{\"annualised_returns\": -0.48400508803139763, \"annualised_returns_over_hodl\": 0.0009726377155803156, \"annualised_returns_over_uniform_hodl\": 0.8212333474584426, \"calmar\": -0.8349400764607012, \"daily_log_sharpe\": -0.6914175675895499, \"daily_returns\": 0.03101604164250684, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": 0.005577482957868991, \"return\": -0.2339247323551149, \"returns_over_hodl\": 0.0003916042770972794, \"returns_over_uniform_hodl\": 0.27308989955143304, \"sharpe\": -0.20098261708574675, \"sterling\": -1.2460935392698487, \"ulcer\": -0.176410314221283}], \"train_objective\": [{\"annualised_returns\": -0.5209204044203412, \"annualised_returns_over_hodl\": -0.3966767764156346, \"annualised_returns_over_uniform_hodl\": -0.3966767764156346, \"calmar\": -0.6909840817310581, \"daily_log_sharpe\": -0.5132374372318917, \"daily_returns\": 0.008253831845682756, \"fee_revenue_over_value\": 0.025300593497025823, \"jax_sharpe\": 0.12258038974697785, \"return\": -0.360311321360148, \"returns_over_hodl\": -0.26418737006928694, \"returns_over_uniform_hodl\": -0.26418737006928694, \"sharpe\": 0.10876446740894906, \"sterling\": -1.4886298286317363, \"ulcer\": -0.19064709106497832}], \"train_return\": -0.360311321360148, \"train_returns_over_hodl\": -0.26418737006928694, \"train_sharpe\": 0.12258038974697785, \"validation_return\": -0.33914271212026126, \"validation_returns_over_hodl\": -3.204639997811398e-09, \"validation_sharpe\": -2.2438531800850967}, {\"centeredness_margin\": 0.05252183179033458, \"continuous_test_metrics\": [{\"annualised_returns\": -0.49135983457619714, \"annualised_returns_over_hodl\": -0.013294757886054498, \"annualised_returns_over_uniform_hodl\": 0.7952743518195302, \"calmar\": -0.8476275127354304, \"daily_log_sharpe\": -0.7157852468066411, \"daily_returns\": 0.031016041810235942, \"fee_revenue_over_value\": 0.0015142881578177696, \"jax_sharpe\": -0.03067906456511197, \"return\": -0.23834119867388748, \"returns_over_hodl\": -0.005375711436216402, \"returns_over_uniform_hodl\": 0.26575046581742967, \"sharpe\": -0.22971162021368915, \"sterling\": -1.2650287059344048, \"ulcer\": -0.17641031456802891}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -3.962417799679249, \"optuna_trial_number\": 188, \"price_ratio\": 7.190216019033875, \"shift_exponent\": 1.3428503520766615e-05, \"step\": 188, \"test_objective\": [{\"annualised_returns\": -0.49135983457619714, \"annualised_returns_over_hodl\": -0.013294757886054498, \"annualised_returns_over_uniform_hodl\": 0.7952743518195302, \"calmar\": -0.8476275127354304, \"daily_log_sharpe\": -0.7157852468066411, \"daily_returns\": 0.031016041810235942, \"fee_revenue_over_value\": 0.0015142881578177696, \"jax_sharpe\": -0.03067906456511197, \"return\": -0.23834119867388748, \"returns_over_hodl\": -0.005375711436216402, \"returns_over_uniform_hodl\": 0.26575046581742967, \"sharpe\": -0.22971162021368915, \"sterling\": -1.2650287059344048, \"ulcer\": -0.17641031456802891}], \"train_objective\": [{\"annualised_returns\": -0.4078385127214904, \"annualised_returns_over_hodl\": -0.25426843329632387, \"annualised_returns_over_uniform_hodl\": -0.25426843329632387, \"calmar\": -0.5538508264473759, \"daily_log_sharpe\": -0.3779515772456762, \"daily_returns\": 0.008177129447883387, \"fee_revenue_over_value\": 0.04415626120715065, \"jax_sharpe\": 0.17042060758126784, \"return\": -0.2724823167713133, \"returns_over_hodl\": -0.16316058468343564, \"returns_over_uniform_hodl\": -0.16316058468343564, \"sharpe\": 0.1915584726584778, \"sterling\": -1.2396034937751226, \"ulcer\": -0.1772560473782031}], \"train_return\": -0.2724823167713133, \"train_returns_over_hodl\": -0.16316058468343564, \"train_sharpe\": 0.17042060758126784, \"validation_return\": -0.3356029286420419, \"validation_returns_over_hodl\": -0.04260182769599885, \"validation_sharpe\": -2.2127396552884435}, {\"centeredness_margin\": 0.022138680846448627, \"continuous_test_metrics\": [{\"annualised_returns\": -0.4966997803730454, \"annualised_returns_over_hodl\": -0.02365365774194972, \"annualised_returns_over_uniform_hodl\": 0.7764267098500843, \"calmar\": -0.8568392813933607, \"daily_log_sharpe\": -0.7281422820228877, \"daily_returns\": 0.03101604181151785, \"fee_revenue_over_value\": 0.0020315520705648125, \"jax_sharpe\": -0.044947468908615854, \"return\": -0.2415717465153675, \"returns_over_hodl\": -0.009594373859322602, \"returns_over_uniform_hodl\": 0.26038183168876317, \"sharpe\": -0.24326100440413864, \"sterling\": -1.2787766690280495, \"ulcer\": -0.1764103145706742}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -3.9403629992672706, \"optuna_trial_number\": 189, \"price_ratio\": 7.194270857920096, \"shift_exponent\": 0.00028495664686909475, \"step\": 189, \"test_objective\": [{\"annualised_returns\": -0.4966997803730454, \"annualised_returns_over_hodl\": -0.02365365774194972, \"annualised_returns_over_uniform_hodl\": 0.7764267098500843, \"calmar\": -0.8568392813933607, \"daily_log_sharpe\": -0.7281422820228877, \"daily_returns\": 0.03101604181151785, \"fee_revenue_over_value\": 0.0020315520705648125, \"jax_sharpe\": -0.044947468908615854, \"return\": -0.2415717465153675, \"returns_over_hodl\": -0.009594373859322602, \"returns_over_uniform_hodl\": 0.26038183168876317, \"sharpe\": -0.24326100440413864, \"sterling\": -1.2787766690280495, \"ulcer\": -0.1764103145706742}], \"train_objective\": [{\"annualised_returns\": -0.4072759506882352, \"annualised_returns_over_hodl\": -0.2535599774520312, \"annualised_returns_over_uniform_hodl\": -0.2535599774520312, \"calmar\": -0.5530319326000502, \"daily_log_sharpe\": -0.3772577120866628, \"daily_returns\": 0.008178871754766271, \"fee_revenue_over_value\": 0.04490280732818551, \"jax_sharpe\": 0.2118194630857632, \"return\": -0.27206278164890274, \"returns_over_hodl\": -0.1626780073734454, \"returns_over_uniform_hodl\": -0.1626780073734454, \"sharpe\": 0.1921219539192502, \"sterling\": -1.237879462076548, \"ulcer\": -0.17722147671387936}], \"train_return\": -0.27206278164890274, \"train_returns_over_hodl\": -0.1626780073734454, \"train_sharpe\": 0.21181946308576316, \"validation_return\": -0.3353849710203298, \"validation_returns_over_hodl\": -0.042826416548658264, \"validation_sharpe\": -2.2106475001623047}, {\"centeredness_margin\": 0.5252437498540489, \"continuous_test_metrics\": [{\"annualised_returns\": -0.6616749152879767, \"annualised_returns_over_hodl\": 0.12412996085492445, \"annualised_returns_over_uniform_hodl\": 0.19413760149001713, \"calmar\": -1.1596396043258412, \"daily_log_sharpe\": -1.398210238143921, \"daily_returns\": 0.019354366609603837, \"fee_revenue_over_value\": 0.0614745859529679, \"jax_sharpe\": -0.8578033767224842, \"return\": -0.3536837432644916, \"returns_over_hodl\": 0.048252087332151916, \"returns_over_uniform_hodl\": 0.07407030760231392, \"sharpe\": -1.0111517503699727, \"sterling\": -2.021899712234974, \"ulcer\": -0.14736024355885768}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -2.6276384780540183, \"optuna_trial_number\": 190, \"price_ratio\": 96.12043469822196, \"shift_exponent\": 0.0506773975956194, \"step\": 190, \"test_objective\": [{\"annualised_returns\": -0.6616749152879767, \"annualised_returns_over_hodl\": 0.12412996085492445, \"annualised_returns_over_uniform_hodl\": 0.19413760149001713, \"calmar\": -1.1596396043258412, \"daily_log_sharpe\": -1.398210238143921, \"daily_returns\": 0.019354366609603837, \"fee_revenue_over_value\": 0.0614745859529679, \"jax_sharpe\": -0.8578033767224842, \"return\": -0.3536837432644916, \"returns_over_hodl\": 0.048252087332151916, \"returns_over_uniform_hodl\": 0.07407030760231392, \"sharpe\": -1.0111517503699727, \"sterling\": -2.021899712234974, \"ulcer\": -0.14736024355885768}], \"train_objective\": [{\"annualised_returns\": -0.31238390989781417, \"annualised_returns_over_hodl\": -0.13405880797954295, \"annualised_returns_over_uniform_hodl\": -0.13405880797954295, \"calmar\": -0.4275373112398961, \"daily_log_sharpe\": -0.31203816230041015, \"daily_returns\": 0.007995370583066614, \"fee_revenue_over_value\": 0.024838963701331332, \"jax_sharpe\": 0.1703684929189247, \"return\": -0.20338350138528727, \"returns_over_hodl\": -0.08367851352595312, \"returns_over_uniform_hodl\": -0.08367851352595312, \"sharpe\": 0.1847592496367564, \"sterling\": -1.0251206706268952, \"ulcer\": -0.1643189535107644}], \"train_return\": -0.20338350138528727, \"train_returns_over_hodl\": -0.08367851352595312, \"train_sharpe\": 0.17036849291892472, \"validation_return\": -0.17346952282715145, \"validation_returns_over_hodl\": -0.02401391940870834, \"validation_sharpe\": -1.3666021643957171}, {\"centeredness_margin\": 0.055931167501814455, \"continuous_test_metrics\": [{\"annualised_returns\": -0.47736908707889736, \"annualised_returns_over_hodl\": 0.01384573884961493, \"annualised_returns_over_uniform_hodl\": 0.8446554897085481, \"calmar\": -0.823492526260058, \"daily_log_sharpe\": -0.6795099168345122, \"daily_returns\": 0.03101604167483378, \"fee_revenue_over_value\": 0.00021048536229255381, \"jax_sharpe\": 0.007585907328533424, \"return\": -0.22997202101564773, \"returns_over_hodl\": 0.005553314875806681, \"returns_over_uniform_hodl\": 0.2796586495093689, \"sharpe\": -0.18877937825082344, \"sterling\": -1.229008817521682, \"ulcer\": -0.1764103142881089}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -0.09861747646954078, \"optuna_trial_number\": 191, \"price_ratio\": 3.9692040510743625, \"shift_exponent\": 0.0008372993770434412, \"step\": 191, \"test_objective\": [{\"annualised_returns\": -0.47736908707889736, \"annualised_returns_over_hodl\": 0.01384573884961493, \"annualised_returns_over_uniform_hodl\": 0.8446554897085481, \"calmar\": -0.823492526260058, \"daily_log_sharpe\": -0.6795099168345122, \"daily_returns\": 0.03101604167483378, \"fee_revenue_over_value\": 0.00021048536229255381, \"jax_sharpe\": 0.007585907328533424, \"return\": -0.22997202101564773, \"returns_over_hodl\": 0.005553314875806681, \"returns_over_uniform_hodl\": 0.2796586495093689, \"sharpe\": -0.18877937825082344, \"sterling\": -1.229008817521682, \"ulcer\": -0.1764103142881089}], \"train_objective\": [{\"annualised_returns\": -0.494451823537864, \"annualised_returns_over_hodl\": -0.3633438820718504, \"annualised_returns_over_uniform_hodl\": -0.3633438820718504, \"calmar\": -0.6605382523120688, \"daily_log_sharpe\": -0.47302700401818837, \"daily_returns\": 0.008305966759430777, \"fee_revenue_over_value\": 0.038378349205361484, \"jax_sharpe\": 0.14984963016945993, \"return\": -0.3390815008764878, \"returns_over_hodl\": -0.2397674130422942, \"returns_over_uniform_hodl\": -0.2397674130422942, \"sharpe\": 0.14356471233640236, \"sterling\": -1.4272097334575746, \"ulcer\": -0.1879547524676799}], \"train_return\": -0.3390815008764878, \"train_returns_over_hodl\": -0.2397674130422942, \"train_sharpe\": 0.14984963016945996, \"validation_return\": -0.33914271227493153, \"validation_returns_over_hodl\": -2.898920770100233e-09, \"validation_sharpe\": -2.2438531793918752}, {\"centeredness_margin\": 0.054152116747341564, \"continuous_test_metrics\": [{\"annualised_returns\": -0.48932423664767033, \"annualised_returns_over_hodl\": 0.10954689412434448, \"annualised_returns_over_uniform_hodl\": 0.8024591103190013, \"calmar\": -0.8943001955871984, \"daily_log_sharpe\": -0.8093260208470169, \"daily_returns\": 0.028818361273484156, \"fee_revenue_over_value\": 0.16018232018687553, \"jax_sharpe\": -0.20665562047900377, \"return\": -0.2371150419439253, \"returns_over_hodl\": 0.042754002897557486, \"returns_over_uniform_hodl\": 0.26778813471774643, \"sharpe\": -0.379122191724518, \"sterling\": -1.3549379210496098, \"ulcer\": -0.1543066865294917}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -2.922641092784977, \"optuna_trial_number\": 192, \"price_ratio\": 2.284802305400768, \"shift_exponent\": 0.05960188192333599, \"step\": 192, \"test_objective\": [{\"annualised_returns\": -0.48932423664767033, \"annualised_returns_over_hodl\": 0.10954689412434448, \"annualised_returns_over_uniform_hodl\": 0.8024591103190013, \"calmar\": -0.8943001955871984, \"daily_log_sharpe\": -0.8093260208470169, \"daily_returns\": 0.028818361273484156, \"fee_revenue_over_value\": 0.16018232018687553, \"jax_sharpe\": -0.20665562047900377, \"return\": -0.2371150419439253, \"returns_over_hodl\": 0.042754002897557486, \"returns_over_uniform_hodl\": 0.26778813471774643, \"sharpe\": -0.379122191724518, \"sterling\": -1.3549379210496098, \"ulcer\": -0.1543066865294917}], \"train_objective\": [{\"annualised_returns\": -0.45765825922719905, \"annualised_returns_over_hodl\": -0.31700834194054883, \"annualised_returns_over_uniform_hodl\": -0.3170083419405487, \"calmar\": -0.603425831669532, \"daily_log_sharpe\": -0.4674204463820068, \"daily_returns\": 0.008496193025793594, \"fee_revenue_over_value\": 0.0646053175674535, \"jax_sharpe\": 0.05402316626763583, \"return\": -0.31028213628767065, \"returns_over_hodl\": -0.2066404609700948, \"returns_over_uniform_hodl\": -0.2066404609700947, \"sharpe\": 0.09673867057232795, \"sterling\": -1.3675382254839399, \"ulcer\": -0.17980603145252563}], \"train_return\": -0.31028213628767065, \"train_returns_over_hodl\": -0.2066404609700948, \"train_sharpe\": 0.054023166267635835, \"validation_return\": -0.29826186340518734, \"validation_returns_over_hodl\": -0.03544179027961025, \"validation_sharpe\": -2.1460049217835744}, {\"centeredness_margin\": 0.04323551117448613, \"continuous_test_metrics\": [{\"annualised_returns\": -0.4842912598482496, \"annualised_returns_over_hodl\": 0.00041749608883279166, \"annualised_returns_over_uniform_hodl\": 0.8202232877778821, \"calmar\": -0.8354337422194181, \"daily_log_sharpe\": -0.6920134108922047, \"daily_returns\": 0.031016041647080467, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": 0.0053117270108274705, \"return\": -0.23409587066768056, \"returns_over_hodl\": 0.00016812050734382744, \"returns_over_uniform_hodl\": 0.2728054960908912, \"sharpe\": -0.20162298277785407, \"sterling\": -1.2468303030840213, \"ulcer\": -0.176410314230736}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -0.11268551773688884, \"optuna_trial_number\": 193, \"price_ratio\": 3.502127078082194, \"shift_exponent\": 0.0002089072052463931, \"step\": 193, \"test_objective\": [{\"annualised_returns\": -0.4842912598482496, \"annualised_returns_over_hodl\": 0.00041749608883279166, \"annualised_returns_over_uniform_hodl\": 0.8202232877778821, \"calmar\": -0.8354337422194181, \"daily_log_sharpe\": -0.6920134108922047, \"daily_returns\": 0.031016041647080467, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": 0.0053117270108274705, \"return\": -0.23409587066768056, \"returns_over_hodl\": 0.00016812050734382744, \"returns_over_uniform_hodl\": 0.2728054960908912, \"sharpe\": -0.20162298277785407, \"sterling\": -1.2468303030840213, \"ulcer\": -0.176410314230736}], \"train_objective\": [{\"annualised_returns\": -0.5095382049692397, \"annualised_returns_over_hodl\": -0.3823427381312242, \"annualised_returns_over_uniform_hodl\": -0.3823427381312242, \"calmar\": -0.677593284195879, \"daily_log_sharpe\": -0.48781682186394754, \"daily_returns\": 0.008348353768722627, \"fee_revenue_over_value\": 0.03987173053128181, \"jax_sharpe\": 0.19778335596871027, \"return\": -0.35112686993855724, \"returns_over_hodl\": -0.2536228008019975, \"returns_over_uniform_hodl\": -0.2536228008019975, \"sharpe\": 0.1419467831848637, \"sterling\": -1.4447091980977365, \"ulcer\": -0.19198725709598158}], \"train_return\": -0.35112686993855724, \"train_returns_over_hodl\": -0.2536228008019975, \"train_sharpe\": 0.19778335596871024, \"validation_return\": -0.33914271224492454, \"validation_returns_over_hodl\": -3.316899532812556e-09, \"validation_sharpe\": -2.243853181019227}, {\"centeredness_margin\": 0.15066534637548362, \"continuous_test_metrics\": [{\"annualised_returns\": -0.4856802688020364, \"annualised_returns_over_hodl\": -0.002277025537674615, \"annualised_returns_over_uniform_hodl\": 0.8153207017874429, \"calmar\": -0.8378298766029966, \"daily_log_sharpe\": -0.7028477401352076, \"daily_returns\": 0.031016041622996104, \"fee_revenue_over_value\": 0.0005242186039681281, \"jax_sharpe\": -0.02606537947689068, \"return\": -0.2349273415809473, \"returns_over_hodl\": -0.0009176686475927953, \"returns_over_uniform_hodl\": 0.27142372948627935, \"sharpe\": -0.21601749751294927, \"sterling\": -1.2504063776620593, \"ulcer\": -0.1764103141809469}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -0.10266686052584584, \"optuna_trial_number\": 194, \"price_ratio\": 2.9868610147567716, \"shift_exponent\": 0.0017487419899004336, \"step\": 194, \"test_objective\": [{\"annualised_returns\": -0.4856802688020364, \"annualised_returns_over_hodl\": -0.002277025537674615, \"annualised_returns_over_uniform_hodl\": 0.8153207017874429, \"calmar\": -0.8378298766029966, \"daily_log_sharpe\": -0.7028477401352076, \"daily_returns\": 0.031016041622996104, \"fee_revenue_over_value\": 0.0005242186039681281, \"jax_sharpe\": -0.02606537947689068, \"return\": -0.2349273415809473, \"returns_over_hodl\": -0.0009176686475927953, \"returns_over_uniform_hodl\": 0.27142372948627935, \"sharpe\": -0.21601749751294927, \"sterling\": -1.2504063776620593, \"ulcer\": -0.1764103141809469}], \"train_objective\": [{\"annualised_returns\": -0.5297958022816069, \"annualised_returns_over_hodl\": -0.4078539037607758, \"annualised_returns_over_uniform_hodl\": -0.4078539037607759, \"calmar\": -0.6962314975010644, \"daily_log_sharpe\": -0.5210008516050814, \"daily_returns\": 0.008410621558266603, \"fee_revenue_over_value\": 0.02889296279849424, \"jax_sharpe\": 0.13615898785257033, \"return\": -0.3675326261147569, \"returns_over_hodl\": -0.27249379696169007, \"returns_over_uniform_hodl\": -0.2724937969616902, \"sharpe\": 0.11064127090879484, \"sterling\": -1.5009045558755918, \"ulcer\": -0.19212028483624746}], \"train_return\": -0.3675326261147569, \"train_returns_over_hodl\": -0.27249379696169007, \"train_sharpe\": 0.13615898785257033, \"validation_return\": -0.3391427117833038, \"validation_returns_over_hodl\": -3.020528716035642e-09, \"validation_sharpe\": -2.2438531780627313}, {\"centeredness_margin\": 0.12401906448575456, \"continuous_test_metrics\": [{\"annualised_returns\": -0.5524161436395266, \"annualised_returns_over_hodl\": -0.1317371883934325, \"annualised_returns_over_uniform_hodl\": 0.5797726413188831, \"calmar\": -0.9550267269884816, \"daily_log_sharpe\": -0.8619922268821868, \"daily_returns\": 0.031016041581070786, \"fee_revenue_over_value\": 0.002241684519961644, \"jax_sharpe\": -0.181073735163714, \"return\": -0.27657431978135905, \"returns_over_hodl\": -0.055303039784566965, \"returns_over_uniform_hodl\": 0.20221336657144318, \"sharpe\": -0.38710212915556225, \"sterling\": -1.4272533015906153, \"ulcer\": -0.17510281632728272}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -4.102721309719339, \"optuna_trial_number\": 195, \"price_ratio\": 5.012027161963555, \"shift_exponent\": 0.002385260826383151, \"step\": 195, \"test_objective\": [{\"annualised_returns\": -0.5524161436395266, \"annualised_returns_over_hodl\": -0.1317371883934325, \"annualised_returns_over_uniform_hodl\": 0.5797726413188831, \"calmar\": -0.9550267269884816, \"daily_log_sharpe\": -0.8619922268821868, \"daily_returns\": 0.031016041581070786, \"fee_revenue_over_value\": 0.002241684519961644, \"jax_sharpe\": -0.181073735163714, \"return\": -0.27657431978135905, \"returns_over_hodl\": -0.055303039784566965, \"returns_over_uniform_hodl\": 0.20221336657144318, \"sharpe\": -0.38710212915556225, \"sterling\": -1.4272533015906153, \"ulcer\": -0.17510281632728272}], \"train_objective\": [{\"annualised_returns\": -0.4562295738692099, \"annualised_returns_over_hodl\": -0.3152091439291478, \"annualised_returns_over_uniform_hodl\": -0.3152091439291478, \"calmar\": -0.6122473318377218, \"daily_log_sharpe\": -0.4380983853505861, \"daily_returns\": 0.008213458535311443, \"fee_revenue_over_value\": 0.03440714244695578, \"jax_sharpe\": 0.13992552245118356, \"return\": -0.3091796173787744, \"returns_over_hodl\": -0.2053722701063324, \"returns_over_uniform_hodl\": -0.2053722701063324, \"sharpe\": 0.14994027929106765, \"sterling\": -1.3562335203466602, \"ulcer\": -0.18180145752807542}], \"train_return\": -0.3091796173787744, \"train_returns_over_hodl\": -0.2053722701063324, \"train_sharpe\": 0.13992552245118356, \"validation_return\": -0.3376036595315167, \"validation_returns_over_hodl\": -0.029735504400551616, \"validation_sharpe\": -2.22999024642957}, {\"centeredness_margin\": 0.026980643979172485, \"continuous_test_metrics\": [{\"annualised_returns\": -0.4845064823285852, \"annualised_returns_over_hodl\": 4.127986841240272e-11, \"annualised_returns_over_uniform_hodl\": 0.8194636478100883, \"calmar\": -0.8358050148279585, \"daily_log_sharpe\": -0.6924636702358739, \"daily_returns\": 0.03101604136352336, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": 0.005019196223631962, \"return\": -0.23422461677419482, \"returns_over_hodl\": 1.6624923659946944e-11, \"returns_over_uniform_hodl\": 0.27259154143821895, \"sharpe\": -0.20210861570106978, \"sterling\": -1.2473844089349453, \"ulcer\": -0.17641031364454038}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -0.19533787589621138, \"optuna_trial_number\": 196, \"price_ratio\": 1.1631833832031069, \"shift_exponent\": 7.855951911493266e-05, \"step\": 196, \"test_objective\": [{\"annualised_returns\": -0.4845064823285852, \"annualised_returns_over_hodl\": 4.127986841240272e-11, \"annualised_returns_over_uniform_hodl\": 0.8194636478100883, \"calmar\": -0.8358050148279585, \"daily_log_sharpe\": -0.6924636702358739, \"daily_returns\": 0.03101604136352336, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": 0.005019196223631962, \"return\": -0.23422461677419482, \"returns_over_hodl\": 1.6624923659946944e-11, \"returns_over_uniform_hodl\": 0.27259154143821895, \"sharpe\": -0.20210861570106978, \"sterling\": -1.2473844089349453, \"ulcer\": -0.17641031364454038}], \"train_objective\": [{\"annualised_returns\": -0.6769389088908637, \"annualised_returns_over_hodl\": -0.5931568351041617, \"annualised_returns_over_uniform_hodl\": -0.5931568351041621, \"calmar\": -0.845330796834431, \"daily_log_sharpe\": -0.7667461056015086, \"daily_returns\": 0.011343613249156538, \"fee_revenue_over_value\": 0.0017749408062897162, \"jax_sharpe\": 0.03275641571402571, \"return\": -0.49641101712812197, \"returns_over_hodl\": -0.4207383274642734, \"returns_over_uniform_hodl\": -0.42073832746427375, \"sharpe\": -0.08541317360120447, \"sterling\": -1.794735571055236, \"ulcer\": -0.21203544789558307}], \"train_return\": -0.49641101712812197, \"train_returns_over_hodl\": -0.4207383274642734, \"train_sharpe\": 0.03275641571402572, \"validation_return\": -0.3391427107703183, \"validation_returns_over_hodl\": -5.820189552530053e-09, \"validation_sharpe\": -2.243853185942819}, {\"centeredness_margin\": 0.11813765379130539, \"continuous_test_metrics\": [{\"annualised_returns\": -0.4777197493910361, \"annualised_returns_over_hodl\": 0.01316549406558587, \"annualised_returns_over_uniform_hodl\": 0.8434178071621687, \"calmar\": -0.8240974423750559, \"daily_log_sharpe\": -0.6815520360941445, \"daily_returns\": 0.031016041649941206, \"fee_revenue_over_value\": 2.5071118623012724e-06, \"jax_sharpe\": 0.0016123983338466325, \"return\": -0.23018013916884106, \"returns_over_hodl\": 0.005281540878600222, \"returns_over_uniform_hodl\": 0.2793127916936502, \"sharpe\": -0.1913791971119837, \"sterling\": -1.229911615815249, \"ulcer\": -0.1764103142366549}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -0.10050453502890133, \"optuna_trial_number\": 197, \"price_ratio\": 3.6040142544074314, \"shift_exponent\": 0.0010856483211115291, \"step\": 197, \"test_objective\": [{\"annualised_returns\": -0.4777197493910361, \"annualised_returns_over_hodl\": 0.01316549406558587, \"annualised_returns_over_uniform_hodl\": 0.8434178071621687, \"calmar\": -0.8240974423750559, \"daily_log_sharpe\": -0.6815520360941445, \"daily_returns\": 0.031016041649941206, \"fee_revenue_over_value\": 2.5071118623012724e-06, \"jax_sharpe\": 0.0016123983338466325, \"return\": -0.23018013916884106, \"returns_over_hodl\": 0.005281540878600222, \"returns_over_uniform_hodl\": 0.2793127916936502, \"sharpe\": -0.1913791971119837, \"sterling\": -1.229911615815249, \"ulcer\": -0.1764103142366549}], \"train_objective\": [{\"annualised_returns\": -0.5127965651959372, \"annualised_returns_over_hodl\": -0.38644611555265596, \"annualised_returns_over_uniform_hodl\": -0.38644611555265584, \"calmar\": -0.6795799889381405, \"daily_log_sharpe\": -0.49944220051519095, \"daily_returns\": 0.008355332379846913, \"fee_revenue_over_value\": 0.029328602646258015, \"jax_sharpe\": 0.1362780036475599, \"return\": -0.3537474497239118, \"returns_over_hodl\": -0.2566371666461844, \"returns_over_uniform_hodl\": -0.2566371666461843, \"sharpe\": 0.12265326725601737, \"sterling\": -1.4687081646411249, \"ulcer\": -0.18979733074224062}], \"train_return\": -0.3537474497239118, \"train_returns_over_hodl\": -0.2566371666461844, \"train_sharpe\": 0.1362780036475599, \"validation_return\": -0.3391427120377105, \"validation_returns_over_hodl\": -2.9555690117533118e-09, \"validation_sharpe\": -2.243853178732796}, {\"centeredness_margin\": 0.23449757014858413, \"continuous_test_metrics\": [{\"annualised_returns\": -0.48450647875073005, \"annualised_returns_over_hodl\": 3.632760758875975e-11, \"annualised_returns_over_uniform_hodl\": 0.8194636604383314, \"calmar\": -0.8358050090954755, \"daily_log_sharpe\": -0.6924636612521154, \"daily_returns\": 0.03101604153353439, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": 0.005019206978992858, \"return\": -0.23422461463365474, \"returns_over_hodl\": 1.4630519018510313e-11, \"returns_over_uniform_hodl\": 0.27259154499544125, \"sharpe\": -0.202108605163503, \"sterling\": -1.2473843969451386, \"ulcer\": -0.17641031399600343}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -0.1384010626109271, \"optuna_trial_number\": 198, \"price_ratio\": 2.360014309001962, \"shift_exponent\": 0.0006545600944651336, \"step\": 198, \"test_objective\": [{\"annualised_returns\": -0.48450647875073005, \"annualised_returns_over_hodl\": 3.632760758875975e-11, \"annualised_returns_over_uniform_hodl\": 0.8194636604383314, \"calmar\": -0.8358050090954755, \"daily_log_sharpe\": -0.6924636612521154, \"daily_returns\": 0.03101604153353439, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": 0.005019206978992858, \"return\": -0.23422461463365474, \"returns_over_hodl\": 1.4630519018510313e-11, \"returns_over_uniform_hodl\": 0.27259154499544125, \"sharpe\": -0.202108605163503, \"sterling\": -1.2473843969451386, \"ulcer\": -0.17641031399600343}], \"train_objective\": [{\"annualised_returns\": -0.586520018206923, \"annualised_returns_over_hodl\": -0.47928887432333855, \"annualised_returns_over_uniform_hodl\": -0.47928887432333855, \"calmar\": -0.7602455132856635, \"daily_log_sharpe\": -0.5977677541573112, \"daily_returns\": 0.008471442448828416, \"fee_revenue_over_value\": 0.02725982786568218, \"jax_sharpe\": 0.1219746806874129, \"return\": -0.41501959638362074, \"returns_over_hodl\": -0.3271164808510928, \"returns_over_uniform_hodl\": -0.3271164808510928, \"sharpe\": 0.06309372333615729, \"sterling\": -1.6076754146046732, \"ulcer\": -0.20171617575391257}], \"train_return\": -0.41501959638362074, \"train_returns_over_hodl\": -0.3271164808510928, \"train_sharpe\": 0.1219746806874129, \"validation_return\": -0.3391427115018407, \"validation_returns_over_hodl\": -4.088396954315954e-09, \"validation_sharpe\": -2.2438531814713456}, {\"centeredness_margin\": 0.010119122709665496, \"continuous_test_metrics\": [{\"annualised_returns\": -0.4727041056225103, \"annualised_returns_over_hodl\": 0.022895285168961, \"annualised_returns_over_uniform_hodl\": 0.8611208067040856, \"calmar\": -0.8154451181481659, \"daily_log_sharpe\": -0.6750903666389043, \"daily_returns\": 0.031016041625243518, \"fee_revenue_over_value\": 0.0024030032756633883, \"jax_sharpe\": 0.008298287129217973, \"return\": -0.22721125214765703, \"returns_over_hodl\": 0.009158509747421162, \"returns_over_uniform_hodl\": 0.2842465889838284, \"sharpe\": -0.1849263635108203, \"sterling\": -1.2169985874255043, \"ulcer\": -0.17641031418559558}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -0.11156630198419404, \"optuna_trial_number\": 199, \"price_ratio\": 2.9850455500762365, \"shift_exponent\": 0.00176703265067941, \"step\": 199, \"test_objective\": [{\"annualised_returns\": -0.4727041056225103, \"annualised_returns_over_hodl\": 0.022895285168961, \"annualised_returns_over_uniform_hodl\": 0.8611208067040856, \"calmar\": -0.8154451181481659, \"daily_log_sharpe\": -0.6750903666389043, \"daily_returns\": 0.031016041625243518, \"fee_revenue_over_value\": 0.0024030032756633883, \"jax_sharpe\": 0.008298287129217973, \"return\": -0.22721125214765703, \"returns_over_hodl\": 0.009158509747421162, \"returns_over_uniform_hodl\": 0.2842465889838284, \"sharpe\": -0.1849263635108203, \"sterling\": -1.2169985874255043, \"ulcer\": -0.17641031418559558}], \"train_objective\": [{\"annualised_returns\": -0.5283213892562677, \"annualised_returns_over_hodl\": -0.4059971191522266, \"annualised_returns_over_uniform_hodl\": -0.40599711915222636, \"calmar\": -0.6965517819084165, \"daily_log_sharpe\": -0.5123995468605024, \"daily_returns\": 0.008399942463987748, \"fee_revenue_over_value\": 0.03982739010895036, \"jax_sharpe\": 0.1850205517621723, \"return\": -0.36632931173118854, \"returns_over_hodl\": -0.2711096644128862, \"returns_over_uniform_hodl\": -0.2711096644128861, \"sharpe\": 0.12633936126886575, \"sterling\": -1.4810845958515872, \"ulcer\": -0.19472899833896928}], \"train_return\": -0.36632931173118854, \"train_returns_over_hodl\": -0.2711096644128862, \"train_sharpe\": 0.1850205517621723, \"validation_return\": -0.33914271198250046, \"validation_returns_over_hodl\": -3.284414185067419e-09, \"validation_sharpe\": -2.2438531799043044}, {\"centeredness_margin\": 0.014290658886633431, \"continuous_test_metrics\": [{\"annualised_returns\": -0.4768265240575609, \"annualised_returns_over_hodl\": 0.014898252414115243, \"annualised_returns_over_uniform_hodl\": 0.8465704967069432, \"calmar\": -0.8225565682898264, \"daily_log_sharpe\": -0.6778045809742381, \"daily_returns\": 0.031016041632476368, \"fee_revenue_over_value\": 0.0005672557147736703, \"jax_sharpe\": 0.010186493737451207, \"return\": -0.22965017403858556, \"returns_over_hodl\": 0.005973604583893177, \"returns_over_uniform_hodl\": 0.2801935057473972, \"sharpe\": -0.18693687123442337, \"sterling\": -1.2276119622156398, \"ulcer\": -0.17641031420054634}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -0.11200810932858218, \"optuna_trial_number\": 200, \"price_ratio\": 3.124123764082673, \"shift_exponent\": 0.0013090456936314821, \"step\": 200, \"test_objective\": [{\"annualised_returns\": -0.4768265240575609, \"annualised_returns_over_hodl\": 0.014898252414115243, \"annualised_returns_over_uniform_hodl\": 0.8465704967069432, \"calmar\": -0.8225565682898264, \"daily_log_sharpe\": -0.6778045809742381, \"daily_returns\": 0.031016041632476368, \"fee_revenue_over_value\": 0.0005672557147736703, \"jax_sharpe\": 0.010186493737451207, \"return\": -0.22965017403858556, \"returns_over_hodl\": 0.005973604583893177, \"returns_over_uniform_hodl\": 0.2801935057473972, \"sharpe\": -0.18693687123442337, \"sterling\": -1.2276119622156398, \"ulcer\": -0.17641031420054634}], \"train_objective\": [{\"annualised_returns\": -0.5208379661491374, \"annualised_returns_over_hodl\": -0.3965729587536255, \"annualised_returns_over_uniform_hodl\": -0.3965729587536253, \"calmar\": -0.6890716005058123, \"daily_log_sharpe\": -0.5017012020240147, \"daily_returns\": 0.008391946013517376, \"fee_revenue_over_value\": 0.04190093600251857, \"jax_sharpe\": 0.19482518703239304, \"return\": -0.3602444945521758, \"returns_over_hodl\": -0.26411050141275794, \"returns_over_uniform_hodl\": -0.2641105014127578, \"sharpe\": 0.13451689332913988, \"sterling\": -1.465216470511282, \"ulcer\": -0.1938526893286506}], \"train_return\": -0.3602444945521758, \"train_returns_over_hodl\": -0.26411050141275794, \"train_sharpe\": 0.19482518703239302, \"validation_return\": -0.3391427120707968, \"validation_returns_over_hodl\": -3.2972585772839125e-09, \"validation_sharpe\": -2.243853180290789}, {\"centeredness_margin\": 0.02003667918244851, \"continuous_test_metrics\": [{\"annualised_returns\": -0.46209719278493555, \"annualised_returns_over_hodl\": 0.043471514813775425, \"annualised_returns_over_uniform_hodl\": 0.8985585079784566, \"calmar\": -0.8350104197012631, \"daily_log_sharpe\": -0.737816889877272, \"daily_returns\": 0.03105385636284079, \"fee_revenue_over_value\": 0.16314367864081483, \"jax_sharpe\": -0.13020796213739075, \"return\": -0.22098783632930363, \"returns_over_hodl\": 0.017285456746008787, \"returns_over_uniform_hodl\": 0.29458887276935175, \"sharpe\": -0.2984509666391886, \"sterling\": -1.2674547193122017, \"ulcer\": -0.15847640119191017}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -3.4346098591111716, \"optuna_trial_number\": 201, \"price_ratio\": 2.183259430062297, \"shift_exponent\": 0.012321240937922704, \"step\": 201, \"test_objective\": [{\"annualised_returns\": -0.46209719278493555, \"annualised_returns_over_hodl\": 0.043471514813775425, \"annualised_returns_over_uniform_hodl\": 0.8985585079784566, \"calmar\": -0.8350104197012631, \"daily_log_sharpe\": -0.737816889877272, \"daily_returns\": 0.03105385636284079, \"fee_revenue_over_value\": 0.16314367864081483, \"jax_sharpe\": -0.13020796213739075, \"return\": -0.22098783632930363, \"returns_over_hodl\": 0.017285456746008787, \"returns_over_uniform_hodl\": 0.29458887276935175, \"sharpe\": -0.2984509666391886, \"sterling\": -1.2674547193122017, \"ulcer\": -0.15847640119191017}], \"train_objective\": [{\"annualised_returns\": -0.5257551253329357, \"annualised_returns_over_hodl\": -0.4027653250264097, \"annualised_returns_over_uniform_hodl\": -0.40276532502640994, \"calmar\": -0.6860168300212638, \"daily_log_sharpe\": -0.5304565458954197, \"daily_returns\": 0.008605075345939225, \"fee_revenue_over_value\": 0.04671032689987351, \"jax_sharpe\": 0.0953026833394854, \"return\": -0.36423842240018023, \"returns_over_hodl\": -0.26870458389650953, \"returns_over_uniform_hodl\": -0.26870458389650975, \"sharpe\": 0.09022072247224518, \"sterling\": -1.4926864927008692, \"ulcer\": -0.1906152840302692}], \"train_return\": -0.36423842240018023, \"train_returns_over_hodl\": -0.26870458389650953, \"train_sharpe\": 0.0953026833394854, \"validation_return\": -0.3299702123997742, \"validation_returns_over_hodl\": -0.0318535291828439, \"validation_sharpe\": -2.310891761698747}, {\"centeredness_margin\": 0.01165271793463854, \"continuous_test_metrics\": [{\"annualised_returns\": -0.4821905146934523, \"annualised_returns_over_hodl\": 0.004492704503263889, \"annualised_returns_over_uniform_hodl\": 0.8276379871124782, \"calmar\": -0.8318098149138122, \"daily_log_sharpe\": -0.6877334498500008, \"daily_returns\": 0.03101604172317599, \"fee_revenue_over_value\": 1.589724411982728e-05, \"jax_sharpe\": 0.006980416396388491, \"return\": -0.23284088657306778, \"returns_over_hodl\": 0.001806960245172684, \"returns_over_uniform_hodl\": 0.2748910712849071, \"sharpe\": -0.19709133122333727, \"sterling\": -1.2414218267075576, \"ulcer\": -0.17641031438804672}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -0.09732686819943936, \"optuna_trial_number\": 202, \"price_ratio\": 3.901877084299043, \"shift_exponent\": 0.00024962593050387107, \"step\": 202, \"test_objective\": [{\"annualised_returns\": -0.4821905146934523, \"annualised_returns_over_hodl\": 0.004492704503263889, \"annualised_returns_over_uniform_hodl\": 0.8276379871124782, \"calmar\": -0.8318098149138122, \"daily_log_sharpe\": -0.6877334498500008, \"daily_returns\": 0.03101604172317599, \"fee_revenue_over_value\": 1.589724411982728e-05, \"jax_sharpe\": 0.006980416396388491, \"return\": -0.23284088657306778, \"returns_over_hodl\": 0.001806960245172684, \"returns_over_uniform_hodl\": 0.2748910712849071, \"sharpe\": -0.19709133122333727, \"sterling\": -1.2414218267075576, \"ulcer\": -0.17641031438804672}], \"train_objective\": [{\"annualised_returns\": -0.49472845317489533, \"annualised_returns_over_hodl\": -0.3636922523341046, \"annualised_returns_over_uniform_hodl\": -0.3636922523341046, \"calmar\": -0.6614226018006838, \"daily_log_sharpe\": -0.4709262575727719, \"daily_returns\": 0.00830950399489818, \"fee_revenue_over_value\": 0.04148776462503916, \"jax_sharpe\": 0.15860782052806147, \"return\": -0.3393010877061644, \"returns_over_hodl\": -0.24001999647551375, \"returns_over_uniform_hodl\": -0.24001999647551375, \"sharpe\": 0.14898861216618486, \"sterling\": -1.422141035854017, \"ulcer\": -0.1888191543811373}], \"train_return\": -0.3393010877061644, \"train_returns_over_hodl\": -0.24001999647551375, \"train_sharpe\": 0.15860782052806147, \"validation_return\": -0.33914271278206853, \"validation_returns_over_hodl\": -2.8591189416005136e-09, \"validation_sharpe\": -2.2438531811212576}, {\"centeredness_margin\": 0.10252568398142159, \"continuous_test_metrics\": [{\"annualised_returns\": -0.5135686815055944, \"annualised_returns_over_hodl\": 0.08439296181032296, \"annualised_returns_over_uniform_hodl\": 0.7168869652421994, \"calmar\": -0.9446523291047725, \"daily_log_sharpe\": -0.8695341133855133, \"daily_returns\": 0.027877230140446154, \"fee_revenue_over_value\": 0.1333713999441106, \"jax_sharpe\": -0.2587549612791406, \"return\": -0.25191364773147373, \"returns_over_hodl\": 0.03316815360667413, \"returns_over_uniform_hodl\": 0.2431953089847212, \"sharpe\": -0.443501546132481, \"sterling\": -1.421584646738735, \"ulcer\": -0.15506073988020547}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -3.1846966402709884, \"optuna_trial_number\": 203, \"price_ratio\": 3.147051550852888, \"shift_exponent\": 0.01903383993532752, \"step\": 203, \"test_objective\": [{\"annualised_returns\": -0.5135686815055944, \"annualised_returns_over_hodl\": 0.08439296181032296, \"annualised_returns_over_uniform_hodl\": 0.7168869652421994, \"calmar\": -0.9446523291047725, \"daily_log_sharpe\": -0.8695341133855133, \"daily_returns\": 0.027877230140446154, \"fee_revenue_over_value\": 0.1333713999441106, \"jax_sharpe\": -0.2587549612791406, \"return\": -0.25191364773147373, \"returns_over_hodl\": 0.03316815360667413, \"returns_over_uniform_hodl\": 0.2431953089847212, \"sharpe\": -0.443501546132481, \"sterling\": -1.421584646738735, \"ulcer\": -0.15506073988020547}], \"train_objective\": [{\"annualised_returns\": -0.4646530532049392, \"annualised_returns_over_hodl\": -0.3258171530230842, \"annualised_returns_over_uniform_hodl\": -0.32581715302308434, \"calmar\": -0.6153202630925431, \"daily_log_sharpe\": -0.4701992421357488, \"daily_returns\": 0.008405690336476531, \"fee_revenue_over_value\": 0.04596496519641685, \"jax_sharpe\": 0.09308508291076037, \"return\": -0.3156965874692442, \"returns_over_hodl\": -0.21286852423989677, \"returns_over_uniform_hodl\": -0.21286852423989688, \"sharpe\": 0.10769979097855273, \"sterling\": -1.3807323364294741, \"ulcer\": -0.18160772792993768}], \"train_return\": -0.3156965874692442, \"train_returns_over_hodl\": -0.21286852423989677, \"train_sharpe\": 0.09308508291076037, \"validation_return\": -0.2847730675486716, \"validation_returns_over_hodl\": -0.03867731786327333, \"validation_sharpe\": -2.086376920363755}, {\"centeredness_margin\": 0.46677837143500606, \"continuous_test_metrics\": [{\"annualised_returns\": -0.47907619672671087, \"annualised_returns_over_hodl\": 0.010534138670366788, \"annualised_returns_over_uniform_hodl\": 0.8386301492521768, \"calmar\": -0.8264374056051709, \"daily_log_sharpe\": -0.6816531466631555, \"daily_returns\": 0.0310160416184164, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": 0.008531875624573125, \"return\": -0.23098597771019846, \"returns_over_hodl\": 0.00422922429030681, \"returns_over_uniform_hodl\": 0.27797362183528196, \"sharpe\": -0.1908389085392301, \"sterling\": -1.233403858756765, \"ulcer\": -0.1764103141714783}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -0.108996116041567, \"optuna_trial_number\": 204, \"price_ratio\": 3.174958363907615, \"shift_exponent\": 0.0008665843913073143, \"step\": 204, \"test_objective\": [{\"annualised_returns\": -0.47907619672671087, \"annualised_returns_over_hodl\": 0.010534138670366788, \"annualised_returns_over_uniform_hodl\": 0.8386301492521768, \"calmar\": -0.8264374056051709, \"daily_log_sharpe\": -0.6816531466631555, \"daily_returns\": 0.0310160416184164, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": 0.008531875624573125, \"return\": -0.23098597771019846, \"returns_over_hodl\": 0.00422922429030681, \"returns_over_uniform_hodl\": 0.27797362183528196, \"sharpe\": -0.1908389085392301, \"sterling\": -1.233403858756765, \"ulcer\": -0.1764103141714783}], \"train_objective\": [{\"annualised_returns\": -0.5417991202922685, \"annualised_returns_over_hodl\": -0.42297014035845104, \"annualised_returns_over_uniform_hodl\": -0.42297014035845104, \"calmar\": -0.7114203618239034, \"daily_log_sharpe\": -0.5414902283336053, \"daily_returns\": 0.008405626451314692, \"fee_revenue_over_value\": 0.0154332669469264, \"jax_sharpe\": 0.1171544732635541, \"return\": -0.37738468402024805, \"returns_over_hodl\": -0.2838262917825818, \"returns_over_uniform_hodl\": -0.2838262917825818, \"sharpe\": 0.09074455139820942, \"sterling\": -1.5300887659490778, \"ulcer\": -0.1932447556188471}], \"train_return\": -0.37738468402024805, \"train_returns_over_hodl\": -0.2838262917825818, \"train_sharpe\": 0.11715447326355409, \"validation_return\": -0.3391427118568443, \"validation_returns_over_hodl\": -3.2082884127149214e-09, \"validation_sharpe\": -2.243853179116187}, {\"centeredness_margin\": 0.3129626665427487, \"continuous_test_metrics\": [{\"annualised_returns\": -0.5951828208552021, \"annualised_returns_over_hodl\": -0.1983437372156801, \"annualised_returns_over_uniform_hodl\": 0.42882522517475086, \"calmar\": -1.0224321201788604, \"daily_log_sharpe\": -0.9815930823056749, \"daily_returns\": 0.03056249962897944, \"fee_revenue_over_value\": 5.142544331944429e-05, \"jax_sharpe\": -0.3087030445939236, \"return\": -0.3052503798199474, \"returns_over_hodl\": -0.08518682100489183, \"returns_over_uniform_hodl\": 0.1545585160157139, \"sharpe\": -0.5190030363587427, \"sterling\": -1.5622468261371572, \"ulcer\": -0.17037172228042854}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -2.935617227635528, \"optuna_trial_number\": 205, \"price_ratio\": 12.442534937642378, \"shift_exponent\": 0.0009499543164804677, \"step\": 205, \"test_objective\": [{\"annualised_returns\": -0.5951828208552021, \"annualised_returns_over_hodl\": -0.1983437372156801, \"annualised_returns_over_uniform_hodl\": 0.42882522517475086, \"calmar\": -1.0224321201788604, \"daily_log_sharpe\": -0.9815930823056749, \"daily_returns\": 0.03056249962897944, \"fee_revenue_over_value\": 5.142544331944429e-05, \"jax_sharpe\": -0.3087030445939236, \"return\": -0.3052503798199474, \"returns_over_hodl\": -0.08518682100489183, \"returns_over_uniform_hodl\": 0.1545585160157139, \"sharpe\": -0.5190030363587427, \"sterling\": -1.5622468261371572, \"ulcer\": -0.17037172228042854}], \"train_objective\": [{\"annualised_returns\": -0.39840689858656264, \"annualised_returns_over_hodl\": -0.2423908415641951, \"annualised_returns_over_uniform_hodl\": -0.2423908415641951, \"calmar\": -0.5390120699583046, \"daily_log_sharpe\": -0.39434118470835955, \"daily_returns\": 0.008129038570756007, \"fee_revenue_over_value\": 0.021344187440151607, \"jax_sharpe\": 0.12537768958567047, \"return\": -0.2654691527563262, \"returns_over_hodl\": -0.15509357516722, \"returns_over_uniform_hodl\": -0.15509357516722, \"sharpe\": 0.1474036783121208, \"sterling\": -1.2395992792864101, \"ulcer\": -0.1732086640462208}], \"train_return\": -0.2654691527563262, \"train_returns_over_hodl\": -0.15509357516722, \"train_sharpe\": 0.1253776895856705, \"validation_return\": -0.2906387550107179, \"validation_returns_over_hodl\": -0.06249911515631046, \"validation_sharpe\": -2.0042157503989375}, {\"centeredness_margin\": 0.27326145284246217, \"continuous_test_metrics\": [{\"annualised_returns\": -0.5415296788685129, \"annualised_returns_over_hodl\": -0.11061866143042587, \"annualised_returns_over_uniform_hodl\": 0.6181970370193353, \"calmar\": -0.936556357430821, \"daily_log_sharpe\": -0.832033213821708, \"daily_returns\": 0.031016041656742842, \"fee_revenue_over_value\": 1.36230214756301e-06, \"jax_sharpe\": -0.15065608783023124, \"return\": -0.26953868503137557, \"returns_over_hodl\": -0.04611544470131457, \"returns_over_uniform_hodl\": 0.21390542336460028, \"sharpe\": -0.35501875755503404, \"sterling\": -1.3987964721959276, \"ulcer\": -0.17513731637887595}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -3.4462116311035933, \"optuna_trial_number\": 206, \"price_ratio\": 7.950668427174431, \"shift_exponent\": 0.0008822308258764424, \"step\": 206, \"test_objective\": [{\"annualised_returns\": -0.5415296788685129, \"annualised_returns_over_hodl\": -0.11061866143042587, \"annualised_returns_over_uniform_hodl\": 0.6181970370193353, \"calmar\": -0.936556357430821, \"daily_log_sharpe\": -0.832033213821708, \"daily_returns\": 0.031016041656742842, \"fee_revenue_over_value\": 1.36230214756301e-06, \"jax_sharpe\": -0.15065608783023124, \"return\": -0.26953868503137557, \"returns_over_hodl\": -0.04611544470131457, \"returns_over_uniform_hodl\": 0.21390542336460028, \"sharpe\": -0.35501875755503404, \"sterling\": -1.3987964721959276, \"ulcer\": -0.17513731637887595}], \"train_objective\": [{\"annualised_returns\": -0.4272864437645474, \"annualised_returns_over_hodl\": -0.27875995528391084, \"annualised_returns_over_uniform_hodl\": -0.27875995528391084, \"calmar\": -0.5752338246007562, \"daily_log_sharpe\": -0.42008516006255475, \"daily_returns\": 0.008164330778418978, \"fee_revenue_over_value\": 0.021991606838435904, \"jax_sharpe\": 0.12235364866845193, \"return\": -0.28708351477410854, \"returns_over_hodl\": -0.17995585754244547, \"returns_over_uniform_hodl\": -0.17995585754244547, \"sharpe\": 0.13977178473112978, \"sterling\": -1.3045903824770186, \"ulcer\": -0.1767705793770858}], \"train_return\": -0.28708351477410854, \"train_returns_over_hodl\": -0.17995585754244547, \"train_sharpe\": 0.12235364866845191, \"validation_return\": -0.32410452026275083, \"validation_returns_over_hodl\": -0.0643565745385386, \"validation_sharpe\": -2.132815624214069}, {\"centeredness_margin\": 0.3411607292948119, \"continuous_test_metrics\": [{\"annualised_returns\": -0.4841790912768906, \"annualised_returns_over_hodl\": 0.000635091998443027, \"annualised_returns_over_uniform_hodl\": 0.8206191931210496, \"calmar\": -0.8352402437570244, \"daily_log_sharpe\": -0.6917788196125549, \"daily_returns\": 0.03101604161335034, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": 0.005454003809634689, \"return\": -0.23402878413909212, \"returns_over_hodl\": 0.0002557270755954022, \"returns_over_uniform_hodl\": 0.27291698276269627, \"sharpe\": -0.20136994623776014, \"sterling\": -1.2465415199460734, \"ulcer\": -0.17641031416100675}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -0.11645356081847835, \"optuna_trial_number\": 207, \"price_ratio\": 3.331167291311933, \"shift_exponent\": 0.0003201293137438422, \"step\": 207, \"test_objective\": [{\"annualised_returns\": -0.4841790912768906, \"annualised_returns_over_hodl\": 0.000635091998443027, \"annualised_returns_over_uniform_hodl\": 0.8206191931210496, \"calmar\": -0.8352402437570244, \"daily_log_sharpe\": -0.6917788196125549, \"daily_returns\": 0.03101604161335034, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": 0.005454003809634689, \"return\": -0.23402878413909212, \"returns_over_hodl\": 0.0002557270755954022, \"returns_over_uniform_hodl\": 0.27291698276269627, \"sharpe\": -0.20136994623776014, \"sterling\": -1.2465415199460734, \"ulcer\": -0.17641031416100675}], \"train_objective\": [{\"annualised_returns\": -0.5413351521514531, \"annualised_returns_over_hodl\": -0.42238584756673136, \"annualised_returns_over_uniform_hodl\": -0.42238584756673136, \"calmar\": -0.713068473107407, \"daily_log_sharpe\": -0.5387233831509067, \"daily_returns\": 0.008295789943738634, \"fee_revenue_over_value\": 0.022309817840073937, \"jax_sharpe\": 0.12247428515880504, \"return\": -0.37700199921598265, \"returns_over_hodl\": -0.2833861021690032, \"returns_over_uniform_hodl\": -0.2833861021690032, \"sharpe\": 0.09558438420444808, \"sterling\": -1.5237170504746822, \"ulcer\": -0.19410754220255017}], \"train_return\": -0.37700199921598265, \"train_returns_over_hodl\": -0.2833861021690032, \"train_sharpe\": 0.12247428515880504, \"validation_return\": -0.3391427119475753, \"validation_returns_over_hodl\": -3.43016337556179e-09, \"validation_sharpe\": -2.2438531803838013}, {\"centeredness_margin\": 0.34224468924591434, \"continuous_test_metrics\": [{\"annualised_returns\": -0.5184796851391844, \"annualised_returns_over_hodl\": -0.0659042404817114, \"annualised_returns_over_uniform_hodl\": 0.6995532989995217, \"calmar\": -0.8945664489856079, \"daily_log_sharpe\": -0.7760859399075505, \"daily_returns\": 0.031016041595226678, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": -0.09454062144901379, \"return\": -0.25496461365940115, \"returns_over_hodl\": -0.02708366061894374, \"returns_over_uniform_hodl\": 0.23812511017950344, \"sharpe\": -0.2947109341230471, \"sterling\": -1.3352169270263499, \"ulcer\": -0.17630521806181287}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -3.879615069095503, \"optuna_trial_number\": 208, \"price_ratio\": 6.496576844488245, \"shift_exponent\": 0.0007529401817092155, \"step\": 208, \"test_objective\": [{\"annualised_returns\": -0.5184796851391844, \"annualised_returns_over_hodl\": -0.0659042404817114, \"annualised_returns_over_uniform_hodl\": 0.6995532989995217, \"calmar\": -0.8945664489856079, \"daily_log_sharpe\": -0.7760859399075505, \"daily_returns\": 0.031016041595226678, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": -0.09454062144901379, \"return\": -0.25496461365940115, \"returns_over_hodl\": -0.02708366061894374, \"returns_over_uniform_hodl\": 0.23812511017950344, \"sharpe\": -0.2947109341230471, \"sterling\": -1.3352169270263499, \"ulcer\": -0.17630521806181287}], \"train_objective\": [{\"annualised_returns\": -0.44856827550261646, \"annualised_returns_over_hodl\": -0.3055609784259141, \"annualised_returns_over_uniform_hodl\": -0.3055609784259141, \"calmar\": -0.6016214700164916, \"daily_log_sharpe\": -0.44257472798073977, \"daily_returns\": 0.008220763701154574, \"fee_revenue_over_value\": 0.018854776077018572, \"jax_sharpe\": 0.11443335580555465, \"return\": -0.3032866826381829, \"returns_over_hodl\": -0.1985938230987906, \"returns_over_uniform_hodl\": -0.1985938230987906, \"sharpe\": 0.1280727722178341, \"sterling\": -1.3554990575288066, \"ulcer\": -0.17876135399780615}], \"train_return\": -0.3032866826381829, \"train_returns_over_hodl\": -0.1985938230987906, \"train_sharpe\": 0.11443335580555464, \"validation_return\": -0.3337939869619424, \"validation_returns_over_hodl\": -0.051143724785110645, \"validation_sharpe\": -2.1969455905595137}, {\"centeredness_margin\": 0.317949730228706, \"continuous_test_metrics\": [{\"annualised_returns\": -0.4849907644735667, \"annualised_returns_over_hodl\": -0.0009394669837282654, \"annualised_returns_over_uniform_hodl\": 0.8177543464748209, \"calmar\": -0.8366404351622313, \"daily_log_sharpe\": -0.6989323852782591, \"daily_returns\": 0.031016041697004032, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": -0.017394828330172845, \"return\": -0.23451443183725063, \"returns_over_hodl\": -0.0003784650914657739, \"returns_over_uniform_hodl\": 0.2721099169228518, \"sharpe\": -0.21102104323678636, \"sterling\": -1.248631212489754, \"ulcer\": -0.17641031433393956}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -0.09597971537734729, \"optuna_trial_number\": 209, \"price_ratio\": 4.925609367624716, \"shift_exponent\": 0.0006027493820947921, \"step\": 209, \"test_objective\": [{\"annualised_returns\": -0.4849907644735667, \"annualised_returns_over_hodl\": -0.0009394669837282654, \"annualised_returns_over_uniform_hodl\": 0.8177543464748209, \"calmar\": -0.8366404351622313, \"daily_log_sharpe\": -0.6989323852782591, \"daily_returns\": 0.031016041697004032, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": -0.017394828330172845, \"return\": -0.23451443183725063, \"returns_over_hodl\": -0.0003784650914657739, \"returns_over_uniform_hodl\": 0.2721099169228518, \"sharpe\": -0.21102104323678636, \"sterling\": -1.248631212489754, \"ulcer\": -0.17641031433393956}], \"train_objective\": [{\"annualised_returns\": -0.48287874126970687, \"annualised_returns_over_hodl\": -0.3487694577689672, \"annualised_returns_over_uniform_hodl\": -0.3487694577689672, \"calmar\": -0.6446628656066922, \"daily_log_sharpe\": -0.472323695480086, \"daily_returns\": 0.008205630932817138, \"fee_revenue_over_value\": 0.01991165637231606, \"jax_sharpe\": 0.11450187990833624, \"return\": -0.32993672408158525, \"returns_over_hodl\": -0.22924848017968058, \"returns_over_uniform_hodl\": -0.22924848017968058, \"sharpe\": 0.12192012306418738, \"sterling\": -1.4258814762520606, \"ulcer\": -0.18330012482917174}], \"train_return\": -0.32993672408158525, \"train_returns_over_hodl\": -0.22924848017968058, \"train_sharpe\": 0.11450187990833623, \"validation_return\": -0.33914271217971803, \"validation_returns_over_hodl\": -2.8280384700707373e-09, \"validation_sharpe\": -2.2438531768916423}, {\"centeredness_margin\": 0.37943281312088034, \"continuous_test_metrics\": [{\"annualised_returns\": -0.48246845087399504, \"annualised_returns_over_hodl\": 0.003953543518195435, \"annualised_returns_over_uniform_hodl\": 0.8266569955780105, \"calmar\": -0.832289273357046, \"daily_log_sharpe\": -0.6882848271683012, \"daily_returns\": 0.031016041619895844, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": 0.007217668695524679, \"return\": -0.23300675074260913, \"returns_over_hodl\": 0.0015903655767792735, \"returns_over_uniform_hodl\": 0.2746154325743797, \"sharpe\": -0.1976644093526644, \"sterling\": -1.2421373892002743, \"ulcer\": -0.1764103141745345}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -0.1120264689312499, \"optuna_trial_number\": 210, \"price_ratio\": 3.3438109225434687, \"shift_exponent\": 0.0005294316676901436, \"step\": 210, \"test_objective\": [{\"annualised_returns\": -0.48246845087399504, \"annualised_returns_over_hodl\": 0.003953543518195435, \"annualised_returns_over_uniform_hodl\": 0.8266569955780105, \"calmar\": -0.832289273357046, \"daily_log_sharpe\": -0.6882848271683012, \"daily_returns\": 0.031016041619895844, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": 0.007217668695524679, \"return\": -0.23300675074260913, \"returns_over_hodl\": 0.0015903655767792735, \"returns_over_uniform_hodl\": 0.2746154325743797, \"sharpe\": -0.1976644093526644, \"sterling\": -1.2421373892002743, \"ulcer\": -0.1764103141745345}], \"train_objective\": [{\"annualised_returns\": -0.5364068701495299, \"annualised_returns_over_hodl\": -0.41617947390446874, \"annualised_returns_over_uniform_hodl\": -0.41617947390446863, \"calmar\": -0.7072002148439469, \"daily_log_sharpe\": -0.5326053234308865, \"daily_returns\": 0.00835492055861549, \"fee_revenue_over_value\": 0.022047382401410576, \"jax_sharpe\": 0.12278347887636579, \"return\": -0.37294645451689323, \"returns_over_hodl\": -0.27872114386900093, \"returns_over_uniform_hodl\": -0.2787211438690008, \"sharpe\": 0.09873655193486718, \"sterling\": -1.5170830269790014, \"ulcer\": -0.19287196639047346}], \"train_return\": -0.37294645451689323, \"train_returns_over_hodl\": -0.27872114386900093, \"train_sharpe\": 0.12278347887636577, \"validation_return\": -0.3391427119325693, \"validation_returns_over_hodl\": -3.2981839481749375e-09, \"validation_sharpe\": -2.2438531797811723}, {\"centeredness_margin\": 0.203130417642393, \"continuous_test_metrics\": [{\"annualised_returns\": -0.5423991834246005, \"annualised_returns_over_hodl\": -0.08395412229959687, \"annualised_returns_over_uniform_hodl\": 0.6151280713055618, \"calmar\": -0.9831967831364726, \"daily_log_sharpe\": -0.9149126358742119, \"daily_returns\": 0.030331686496168967, \"fee_revenue_over_value\": 0.10449416272016777, \"jax_sharpe\": -0.2925620385838032, \"return\": -0.27009693206239216, \"returns_over_hodl\": -0.034699289252451804, \"returns_over_uniform_hodl\": 0.21297770948757289, \"sharpe\": -0.47964190591684036, \"sterling\": -1.4795461254706646, \"ulcer\": -0.160865281843323}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -3.0861360495036863, \"optuna_trial_number\": 211, \"price_ratio\": 3.885757270801902, \"shift_exponent\": 0.006311886721481345, \"step\": 211, \"test_objective\": [{\"annualised_returns\": -0.5423991834246005, \"annualised_returns_over_hodl\": -0.08395412229959687, \"annualised_returns_over_uniform_hodl\": 0.6151280713055618, \"calmar\": -0.9831967831364726, \"daily_log_sharpe\": -0.9149126358742119, \"daily_returns\": 0.030331686496168967, \"fee_revenue_over_value\": 0.10449416272016777, \"jax_sharpe\": -0.2925620385838032, \"return\": -0.27009693206239216, \"returns_over_hodl\": -0.034699289252451804, \"returns_over_uniform_hodl\": 0.21297770948757289, \"sharpe\": -0.47964190591684036, \"sterling\": -1.4795461254706646, \"ulcer\": -0.160865281843323}], \"train_objective\": [{\"annualised_returns\": -0.4567710805333387, \"annualised_returns_over_hodl\": -0.31589108394331067, \"annualised_returns_over_uniform_hodl\": -0.31589108394331045, \"calmar\": -0.6072643210015356, \"daily_log_sharpe\": -0.4549896488140068, \"daily_returns\": 0.008257056073921137, \"fee_revenue_over_value\": 0.03125716689521185, \"jax_sharpe\": 0.11950202264177327, \"return\": -0.30959736490182677, \"returns_over_hodl\": -0.20585279120018374, \"returns_over_uniform_hodl\": -0.20585279120018363, \"sharpe\": 0.12533849213023304, \"sterling\": -1.363991224135231, \"ulcer\": -0.18073409244514468}], \"train_return\": -0.30959736490182677, \"train_returns_over_hodl\": -0.20585279120018374, \"train_sharpe\": 0.11950202264177326, \"validation_return\": -0.299235851479542, \"validation_returns_over_hodl\": -0.0520959336042639, \"validation_sharpe\": -2.0998896053928875}, {\"centeredness_margin\": 0.35705082623170487, \"continuous_test_metrics\": [{\"annualised_returns\": -0.48861767773681697, \"annualised_returns_over_hodl\": -0.007975276180624546, \"annualised_returns_over_uniform_hodl\": 0.8049529501235089, \"calmar\": -0.8428971061992651, \"daily_log_sharpe\": -0.7067979697544744, \"daily_returns\": 0.031016041736184382, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": -0.024172646946158737, \"return\": -0.23669012381990306, \"returns_over_hodl\": -0.0032196278011793478, \"returns_over_uniform_hodl\": 0.26849427808860904, \"sharpe\": -0.2195916383896906, \"sterling\": -1.2579688846210053, \"ulcer\": -0.1764103144149436}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -4.081538926589459, \"optuna_trial_number\": 212, \"price_ratio\": 7.356506314092795, \"shift_exponent\": 1.0991629402278014e-05, \"step\": 212, \"test_objective\": [{\"annualised_returns\": -0.48861767773681697, \"annualised_returns_over_hodl\": -0.007975276180624546, \"annualised_returns_over_uniform_hodl\": 0.8049529501235089, \"calmar\": -0.8428971061992651, \"daily_log_sharpe\": -0.7067979697544744, \"daily_returns\": 0.031016041736184382, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": -0.024172646946158737, \"return\": -0.23669012381990306, \"returns_over_hodl\": -0.0032196278011793478, \"returns_over_uniform_hodl\": 0.26849427808860904, \"sharpe\": -0.2195916383896906, \"sterling\": -1.2579688846210053, \"ulcer\": -0.1764103144149436}], \"train_objective\": [{\"annualised_returns\": -0.45796467457691015, \"annualised_returns_over_hodl\": -0.317394222487851, \"annualised_returns_over_uniform_hodl\": -0.317394222487851, \"calmar\": -0.6150142138983106, \"daily_log_sharpe\": -0.4539883412850241, \"daily_returns\": 0.008188828315355182, \"fee_revenue_over_value\": 0.017046727708904755, \"jax_sharpe\": 0.10155355111836348, \"return\": -0.31051874607316154, \"returns_over_hodl\": -0.20691262534370802, \"returns_over_uniform_hodl\": -0.20691262534370802, \"sharpe\": 0.11759957184340562, \"sterling\": -1.381170446256447, \"ulcer\": -0.17934362520264807}], \"train_return\": -0.31051874607316154, \"train_returns_over_hodl\": -0.20691262534370802, \"train_sharpe\": 0.10155355111836346, \"validation_return\": -0.3373310890849025, \"validation_returns_over_hodl\": -0.03197967921235789, \"validation_sharpe\": -2.2277757936690135}, {\"centeredness_margin\": 0.29209504319265656, \"continuous_test_metrics\": [{\"annualised_returns\": -0.5271239949135182, \"annualised_returns_over_hodl\": -0.08267324052965341, \"annualised_returns_over_uniform_hodl\": 0.6690427167018895, \"calmar\": -0.9103428826082216, \"daily_log_sharpe\": -0.795758234365741, \"daily_returns\": 0.03101604168724132, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": -0.11257362195856857, \"return\": -0.2603803745650082, \"returns_over_hodl\": -0.03415591930578987, \"returns_over_uniform_hodl\": 0.22912501476001546, \"sharpe\": -0.31568915793779345, \"sterling\": -1.3596584507117186, \"ulcer\": -0.17569581640772966}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -3.2525594609068325, \"optuna_trial_number\": 213, \"price_ratio\": 11.168718459463529, \"shift_exponent\": 2.308237105479429e-05, \"step\": 213, \"test_objective\": [{\"annualised_returns\": -0.5271239949135182, \"annualised_returns_over_hodl\": -0.08267324052965341, \"annualised_returns_over_uniform_hodl\": 0.6690427167018895, \"calmar\": -0.9103428826082216, \"daily_log_sharpe\": -0.795758234365741, \"daily_returns\": 0.03101604168724132, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": -0.11257362195856857, \"return\": -0.2603803745650082, \"returns_over_hodl\": -0.03415591930578987, \"returns_over_uniform_hodl\": 0.22912501476001546, \"sharpe\": -0.31568915793779345, \"sterling\": -1.3596584507117186, \"ulcer\": -0.17569581640772966}], \"train_objective\": [{\"annualised_returns\": -0.41584270266346823, \"annualised_returns_over_hodl\": -0.2643484152503305, \"annualised_returns_over_uniform_hodl\": -0.26434841525033037, \"calmar\": -0.5618856904210843, \"daily_log_sharpe\": -0.41047268325345976, \"daily_returns\": 0.008101245202690987, \"fee_revenue_over_value\": 0.0205857596270074, \"jax_sharpe\": 0.15457621224638377, \"return\": -0.2784685718895217, \"returns_over_hodl\": -0.1700463750202772, \"returns_over_uniform_hodl\": -0.17004637502027709, \"sharpe\": 0.13969318301102499, \"sterling\": -1.2821703728769056, \"ulcer\": -0.17500824883818433}], \"train_return\": -0.2784685718895217, \"train_returns_over_hodl\": -0.1700463750202772, \"train_sharpe\": 0.1545762122463838, \"validation_return\": -0.3184205708472656, \"validation_returns_over_hodl\": -0.06592512881164458, \"validation_sharpe\": -2.10236766946296}, {\"centeredness_margin\": 0.18322671921722286, \"continuous_test_metrics\": [{\"annualised_returns\": -0.48091125512088917, \"annualised_returns_over_hodl\": 0.0069743270299282845, \"annualised_returns_over_uniform_hodl\": 0.8321532064287298, \"calmar\": -0.8296030055529922, \"daily_log_sharpe\": -0.685299894387135, \"daily_returns\": 0.031016041686773434, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": 0.006819949471087242, \"return\": -0.23207814673123528, \"returns_over_hodl\": 0.0028029968297693664, \"returns_over_uniform_hodl\": 0.276158618260556, \"sharpe\": -0.19464537954406796, \"sterling\": -1.2381283085840558, \"ulcer\": -0.1764103143127945}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -0.09244547351928456, \"optuna_trial_number\": 214, \"price_ratio\": 5.05830123901082, \"shift_exponent\": 1.3643287622414414e-05, \"step\": 214, \"test_objective\": [{\"annualised_returns\": -0.48091125512088917, \"annualised_returns_over_hodl\": 0.0069743270299282845, \"annualised_returns_over_uniform_hodl\": 0.8321532064287298, \"calmar\": -0.8296030055529922, \"daily_log_sharpe\": -0.685299894387135, \"daily_returns\": 0.031016041686773434, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": 0.006819949471087242, \"return\": -0.23207814673123528, \"returns_over_hodl\": 0.0028029968297693664, \"returns_over_uniform_hodl\": 0.276158618260556, \"sharpe\": -0.19464537954406796, \"sterling\": -1.2381283085840558, \"ulcer\": -0.1764103143127945}], \"train_objective\": [{\"annualised_returns\": -0.488491365602151, \"annualised_returns_over_hodl\": -0.35583764985283406, \"annualised_returns_over_uniform_hodl\": -0.35583764985283406, \"calmar\": -0.6527524183334624, \"daily_log_sharpe\": -0.47680745627785387, \"daily_returns\": 0.00826869191219216, \"fee_revenue_over_value\": 0.02452738951102155, \"jax_sharpe\": 0.11772519163084605, \"return\": -0.3343615335823048, \"returns_over_hodl\": -0.23433819151018231, \"returns_over_uniform_hodl\": -0.23433819151018231, \"sharpe\": 0.12217362773535437, \"sterling\": -1.4328091657342918, \"ulcer\": -0.1848757047065187}], \"train_return\": -0.3343615335823048, \"train_returns_over_hodl\": -0.23433819151018231, \"train_sharpe\": 0.11772519163084605, \"validation_return\": -0.33914271226351567, \"validation_returns_over_hodl\": -2.716603830776876e-09, \"validation_sharpe\": -2.243853178443646}, {\"centeredness_margin\": 0.17150117096038087, \"continuous_test_metrics\": [{\"annualised_returns\": -0.4808119881178238, \"annualised_returns_over_hodl\": 0.007166894293095583, \"annualised_returns_over_uniform_hodl\": 0.8325035749538656, \"calmar\": -0.8294317632403193, \"daily_log_sharpe\": -0.6850488023631724, \"daily_returns\": 0.031016041678563887, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": 0.007838883219097837, \"return\": -0.2320190071910545, \"returns_over_hodl\": 0.00288022527676679, \"returns_over_uniform_hodl\": 0.276256898357105, \"sharpe\": -0.19433923190778601, \"sterling\": -1.2378727407798054, \"ulcer\": -0.1764103142958135}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -0.09413440824992603, \"optuna_trial_number\": 215, \"price_ratio\": 4.792001194454625, \"shift_exponent\": 7.479229713962985e-05, \"step\": 215, \"test_objective\": [{\"annualised_returns\": -0.4808119881178238, \"annualised_returns_over_hodl\": 0.007166894293095583, \"annualised_returns_over_uniform_hodl\": 0.8325035749538656, \"calmar\": -0.8294317632403193, \"daily_log_sharpe\": -0.6850488023631724, \"daily_returns\": 0.031016041678563887, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": 0.007838883219097837, \"return\": -0.2320190071910545, \"returns_over_hodl\": 0.00288022527676679, \"returns_over_uniform_hodl\": 0.276256898357105, \"sharpe\": -0.19433923190778601, \"sterling\": -1.2378727407798054, \"ulcer\": -0.1764103142958135}], \"train_objective\": [{\"annualised_returns\": -0.4932117066433215, \"annualised_returns_over_hodl\": -0.3617821555250714, \"annualised_returns_over_uniform_hodl\": -0.36178215552507165, \"calmar\": -0.6583322264866623, \"daily_log_sharpe\": -0.48120759313113504, \"daily_returns\": 0.008264148625823739, \"fee_revenue_over_value\": 0.025114166274742954, \"jax_sharpe\": 0.12056279913573582, \"return\": -0.3380976836350431, \"returns_over_hodl\": -0.2386357607620977, \"returns_over_uniform_hodl\": -0.23863576076209791, \"sharpe\": 0.12216846062353398, \"sterling\": -1.4405007462093768, \"ulcer\": -0.1857184656096066}], \"train_return\": -0.3380976836350431, \"train_returns_over_hodl\": -0.2386357607620977, \"train_sharpe\": 0.12056279913573581, \"validation_return\": -0.3391427122284366, \"validation_returns_over_hodl\": -2.7662661050698034e-09, \"validation_sharpe\": -2.2438531786543274}, {\"centeredness_margin\": 0.27509576476464503, \"continuous_test_metrics\": [{\"annualised_returns\": -0.4809627778620137, \"annualised_returns_over_hodl\": 0.006874377461916659, \"annualised_returns_over_uniform_hodl\": 0.8319713539877218, \"calmar\": -0.8296918858393076, \"daily_log_sharpe\": -0.6885154843788577, \"daily_returns\": 0.031016041715728016, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": -0.004122311678934375, \"return\": -0.23210884470190396, \"returns_over_hodl\": 0.002762908917577578, \"returns_over_uniform_hodl\": 0.27610760333024054, \"sharpe\": -0.19908690607631865, \"sterling\": -1.2382609560263564, \"ulcer\": -0.17641031437264654}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -4.276945376869117, \"optuna_trial_number\": 216, \"price_ratio\": 6.286458830816999, \"shift_exponent\": 3.379918919753604e-05, \"step\": 216, \"test_objective\": [{\"annualised_returns\": -0.4809627778620137, \"annualised_returns_over_hodl\": 0.006874377461916659, \"annualised_returns_over_uniform_hodl\": 0.8319713539877218, \"calmar\": -0.8296918858393076, \"daily_log_sharpe\": -0.6885154843788577, \"daily_returns\": 0.031016041715728016, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": -0.004122311678934375, \"return\": -0.23210884470190396, \"returns_over_hodl\": 0.002762908917577578, \"returns_over_uniform_hodl\": 0.27610760333024054, \"sharpe\": -0.19908690607631865, \"sterling\": -1.2382609560263564, \"ulcer\": -0.17641031437264654}], \"train_objective\": [{\"annualised_returns\": -0.4689557536202338, \"annualised_returns_over_hodl\": -0.33123570791171886, \"annualised_returns_over_uniform_hodl\": -0.33123570791171897, \"calmar\": -0.6286764292144664, \"daily_log_sharpe\": -0.4605059316401276, \"daily_returns\": 0.008211649149454174, \"fee_revenue_over_value\": 0.020059478249038268, \"jax_sharpe\": 0.10768743831697172, \"return\": -0.3190409836303254, \"returns_over_hodl\": -0.2167154720083765, \"returns_over_uniform_hodl\": -0.2167154720083766, \"sharpe\": 0.12127983381737469, \"sterling\": -1.40015347851421, \"ulcer\": -0.18128022788728174}], \"train_return\": -0.3190409836303254, \"train_returns_over_hodl\": -0.2167154720083765, \"train_sharpe\": 0.10768743831697171, \"validation_return\": -0.3391043270235947, \"validation_returns_over_hodl\": -0.002621839080004351, \"validation_sharpe\": -2.243541038454081}, {\"centeredness_margin\": 0.010825191846983225, \"continuous_test_metrics\": [{\"annualised_returns\": -0.492147885605665, \"annualised_returns_over_hodl\": -0.014823486887411419, \"annualised_returns_over_uniform_hodl\": 0.7924928809540706, \"calmar\": -0.8508282682014804, \"daily_log_sharpe\": -0.7236367674323365, \"daily_returns\": 0.031016041702120075, \"fee_revenue_over_value\": 0.021309312208991734, \"jax_sharpe\": -0.05196851958528081, \"return\": -0.23881667361165615, \"returns_over_hodl\": -0.005996617077509314, \"returns_over_uniform_hodl\": 0.264960305416319, \"sharpe\": -0.2408998700388625, \"sterling\": -1.2692443334096697, \"ulcer\": -0.17575691862495338}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -3.5386846311677607, \"optuna_trial_number\": 217, \"price_ratio\": 8.402229484892048, \"shift_exponent\": 0.0008737694014226831, \"step\": 217, \"test_objective\": [{\"annualised_returns\": -0.492147885605665, \"annualised_returns_over_hodl\": -0.014823486887411419, \"annualised_returns_over_uniform_hodl\": 0.7924928809540706, \"calmar\": -0.8508282682014804, \"daily_log_sharpe\": -0.7236367674323365, \"daily_returns\": 0.031016041702120075, \"fee_revenue_over_value\": 0.021309312208991734, \"jax_sharpe\": -0.05196851958528081, \"return\": -0.23881667361165615, \"returns_over_hodl\": -0.005996617077509314, \"returns_over_uniform_hodl\": 0.264960305416319, \"sharpe\": -0.2408998700388625, \"sterling\": -1.2692443334096697, \"ulcer\": -0.17575691862495338}], \"train_objective\": [{\"annualised_returns\": -0.3956387198336543, \"annualised_returns_over_hodl\": -0.23890476838539132, \"annualised_returns_over_uniform_hodl\": -0.23890476838539143, \"calmar\": -0.5379081150953229, \"daily_log_sharpe\": -0.36851571932063076, \"daily_returns\": 0.008149264452695409, \"fee_revenue_over_value\": 0.04294743671932296, \"jax_sharpe\": 0.16950071890537782, \"return\": -0.2634190059803462, \"returns_over_hodl\": -0.15273535945800132, \"returns_over_uniform_hodl\": -0.15273535945800143, \"sharpe\": 0.19236010005619164, \"sterling\": -1.2125914053533904, \"ulcer\": -0.17566256784023615}], \"train_return\": -0.2634190059803462, \"train_returns_over_hodl\": -0.15273535945800132, \"train_sharpe\": 0.16950071890537785, \"validation_return\": -0.3282859766397376, \"validation_returns_over_hodl\": -0.05600519459043285, \"validation_sharpe\": -2.152799687233959}, {\"centeredness_margin\": 0.052734511802309356, \"continuous_test_metrics\": [{\"annualised_returns\": -0.4845064772269535, \"annualised_returns_over_hodl\": 3.4117819680545836e-11, \"annualised_returns_over_uniform_hodl\": 0.8194636658165861, \"calmar\": -0.8358050066540504, \"daily_log_sharpe\": -0.6924636574259768, \"daily_returns\": 0.03101604160594182, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": 0.00501921155967192, \"return\": -0.23422461372201786, \"returns_over_hodl\": 1.3740564241970787e-11, \"returns_over_uniform_hodl\": 0.27259154651043005, \"sharpe\": -0.20210860067559885, \"sterling\": -1.2473843918387402, \"ulcer\": -0.17641031414569605}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -0.1251158544516794, \"optuna_trial_number\": 218, \"price_ratio\": 3.1101118021343814, \"shift_exponent\": 1.1920726165943284e-05, \"step\": 218, \"test_objective\": [{\"annualised_returns\": -0.4845064772269535, \"annualised_returns_over_hodl\": 3.4117819680545836e-11, \"annualised_returns_over_uniform_hodl\": 0.8194636658165861, \"calmar\": -0.8358050066540504, \"daily_log_sharpe\": -0.6924636574259768, \"daily_returns\": 0.03101604160594182, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": 0.00501921155967192, \"return\": -0.23422461372201786, \"returns_over_hodl\": 1.3740564241970787e-11, \"returns_over_uniform_hodl\": 0.27259154651043005, \"sharpe\": -0.20210860067559885, \"sterling\": -1.2473843918387402, \"ulcer\": -0.17641031414569605}], \"train_objective\": [{\"annualised_returns\": -0.5410557031632272, \"annualised_returns_over_hodl\": -0.422033926787889, \"annualised_returns_over_uniform_hodl\": -0.422033926787889, \"calmar\": -0.7113250074737854, \"daily_log_sharpe\": -0.5309663604352812, \"daily_returns\": 0.008408744049685751, \"fee_revenue_over_value\": 0.031174832298628462, \"jax_sharpe\": 0.1450544300098967, \"return\": -0.3767715807389912, \"returns_over_hodl\": -0.2831210594518174, \"returns_over_uniform_hodl\": -0.2831210594518174, \"sharpe\": 0.11184028300667036, \"sterling\": -1.5072026554977365, \"ulcer\": -0.19671564332136}], \"train_return\": -0.3767715807389912, \"train_returns_over_hodl\": -0.2831210594518174, \"train_sharpe\": 0.1450544300098967, \"validation_return\": -0.3391427120366003, \"validation_returns_over_hodl\": -3.688559124803703e-09, \"validation_sharpe\": -2.2438531817872156}, {\"centeredness_margin\": 0.0983488881161608, \"continuous_test_metrics\": [{\"annualised_returns\": -0.48147776677410536, \"annualised_returns_over_hodl\": 0.005875357322473906, \"annualised_returns_over_uniform_hodl\": 0.8301536713739639, \"calmar\": -0.8305802764733651, \"daily_log_sharpe\": -0.6863681067634804, \"daily_returns\": 0.031016041692177258, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": 0.007085788706991835, \"return\": -0.23241578237042204, \"returns_over_hodl\": 0.0023620898574554516, \"returns_over_uniform_hodl\": 0.2755975238875461, \"sharpe\": -0.1957107796695083, \"sterling\": -1.2395868215035923, \"ulcer\": -0.17641031432398102}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -0.09374296078730504, \"optuna_trial_number\": 219, \"price_ratio\": 4.721693717654979, \"shift_exponent\": 1.9723790719809796e-05, \"step\": 219, \"test_objective\": [{\"annualised_returns\": -0.48147776677410536, \"annualised_returns_over_hodl\": 0.005875357322473906, \"annualised_returns_over_uniform_hodl\": 0.8301536713739639, \"calmar\": -0.8305802764733651, \"daily_log_sharpe\": -0.6863681067634804, \"daily_returns\": 0.031016041692177258, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": 0.007085788706991835, \"return\": -0.23241578237042204, \"returns_over_hodl\": 0.0023620898574554516, \"returns_over_uniform_hodl\": 0.2755975238875461, \"sharpe\": -0.1957107796695083, \"sterling\": -1.2395868215035923, \"ulcer\": -0.17641031432398102}], \"train_objective\": [{\"annualised_returns\": -0.47843340647901467, \"annualised_returns_over_hodl\": -0.3431712779663252, \"annualised_returns_over_uniform_hodl\": -0.34317127796632496, \"calmar\": -0.6417817841874207, \"daily_log_sharpe\": -0.456670007286717, \"daily_returns\": 0.008254740252291773, \"fee_revenue_over_value\": 0.03520157533456963, \"jax_sharpe\": 0.1432888905916105, \"return\": -0.32644554317466856, \"returns_over_hodl\": -0.2252326907957819, \"returns_over_uniform_hodl\": -0.2252326907957818, \"sharpe\": 0.14697940088303926, \"sterling\": -1.3995858408273503, \"ulcer\": -0.1850649850997474}], \"train_return\": -0.32644554317466856, \"train_returns_over_hodl\": -0.2252326907957819, \"train_sharpe\": 0.14328889059161054, \"validation_return\": -0.3391427123707482, \"validation_returns_over_hodl\": -2.7542499392296804e-09, \"validation_sharpe\": -2.2438531791406136}, {\"centeredness_margin\": 0.02780449891161621, \"continuous_test_metrics\": [{\"annualised_returns\": -0.5171124992538046, \"annualised_returns_over_hodl\": -0.06325205523564503, \"annualised_returns_over_uniform_hodl\": 0.7043788592305886, \"calmar\": -0.8934188263763956, \"daily_log_sharpe\": -0.7771716590961205, \"daily_returns\": 0.03101604168936395, \"fee_revenue_over_value\": 0.011220517875441578, \"jax_sharpe\": -0.10010178362970848, \"return\": -0.2541133885423421, \"returns_over_hodl\": -0.025972075998983546, \"returns_over_uniform_hodl\": 0.2395397049909298, \"sharpe\": -0.2969694897924739, \"sterling\": -1.3343126115630144, \"ulcer\": -0.1755677660760969}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -3.7371575132910504, \"optuna_trial_number\": 220, \"price_ratio\": 7.895237622506752, \"shift_exponent\": 0.001282005207538511, \"step\": 220, \"test_objective\": [{\"annualised_returns\": -0.5171124992538046, \"annualised_returns_over_hodl\": -0.06325205523564503, \"annualised_returns_over_uniform_hodl\": 0.7043788592305886, \"calmar\": -0.8934188263763956, \"daily_log_sharpe\": -0.7771716590961205, \"daily_returns\": 0.03101604168936395, \"fee_revenue_over_value\": 0.011220517875441578, \"jax_sharpe\": -0.10010178362970848, \"return\": -0.2541133885423421, \"returns_over_hodl\": -0.025972075998983546, \"returns_over_uniform_hodl\": 0.2395397049909298, \"sharpe\": -0.2969694897924739, \"sterling\": -1.3343126115630144, \"ulcer\": -0.1755677660760969}], \"train_objective\": [{\"annualised_returns\": -0.4004738945258317, \"annualised_returns_over_hodl\": -0.24499388845811831, \"annualised_returns_over_uniform_hodl\": -0.24499388845811831, \"calmar\": -0.5441901083772107, \"daily_log_sharpe\": -0.37253842963925027, \"daily_returns\": 0.008149669954961638, \"fee_revenue_over_value\": 0.04361924410786233, \"jax_sharpe\": 0.1697771884191614, \"return\": -0.26700241286889737, \"returns_over_hodl\": -0.15685723332386847, \"returns_over_uniform_hodl\": -0.15685723332386847, \"sharpe\": 0.19154316217945858, \"sterling\": -1.2234843419338817, \"ulcer\": -0.17628582042183424}], \"train_return\": -0.26700241286889737, \"train_returns_over_hodl\": -0.15685723332386847, \"train_sharpe\": 0.1697771884191614, \"validation_return\": -0.33169914785530763, \"validation_returns_over_hodl\": -0.05338464804674914, \"validation_sharpe\": -2.1809082197280816}, {\"centeredness_margin\": 0.05368157571491869, \"continuous_test_metrics\": [{\"annualised_returns\": -0.6127207998470223, \"annualised_returns_over_hodl\": -0.24872150067999588, \"annualised_returns_over_uniform_hodl\": 0.36692393226263764, \"calmar\": -1.109053261219886, \"daily_log_sharpe\": -1.1769290645095132, \"daily_returns\": 0.031016041593244937, \"fee_revenue_over_value\": 0.2665567239179799, \"jax_sharpe\": -0.5439704407204679, \"return\": -0.31753284969703055, \"returns_over_hodl\": -0.1087893884235338, \"returns_over_uniform_hodl\": 0.13414709039936357, \"sharpe\": -0.7733879385983957, \"sterling\": -1.8160343218412631, \"ulcer\": -0.14193307293036767}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -3.0117862177978565, \"optuna_trial_number\": 221, \"price_ratio\": 1.08341614703464, \"shift_exponent\": 0.013919488326048392, \"step\": 221, \"test_objective\": [{\"annualised_returns\": -0.6127207998470223, \"annualised_returns_over_hodl\": -0.24872150067999588, \"annualised_returns_over_uniform_hodl\": 0.36692393226263764, \"calmar\": -1.109053261219886, \"daily_log_sharpe\": -1.1769290645095132, \"daily_returns\": 0.031016041593244937, \"fee_revenue_over_value\": 0.2665567239179799, \"jax_sharpe\": -0.5439704407204679, \"return\": -0.31753284969703055, \"returns_over_hodl\": -0.1087893884235338, \"returns_over_uniform_hodl\": 0.13414709039936357, \"sharpe\": -0.7733879385983957, \"sterling\": -1.8160343218412631, \"ulcer\": -0.14193307293036767}], \"train_objective\": [{\"annualised_returns\": -0.5844432937303536, \"annualised_returns_over_hodl\": -0.4766735759110078, \"annualised_returns_over_uniform_hodl\": -0.476673575911006, \"calmar\": -0.750387575103335, \"daily_log_sharpe\": -0.6745395999554046, \"daily_returns\": 0.01428271464064722, \"fee_revenue_over_value\": 0.03336422205810937, \"jax_sharpe\": -0.11909491410919594, \"return\": -0.41323757241642756, \"returns_over_hodl\": -0.3250666778989957, \"returns_over_uniform_hodl\": -0.32506667789899424, \"sharpe\": -0.064401037556181, \"sterling\": -1.6278848572052103, \"ulcer\": -0.19895576503211035}], \"train_return\": -0.41323757241642756, \"train_returns_over_hodl\": -0.3250666778989957, \"train_sharpe\": -0.11909491410919594, \"validation_return\": -0.3088688275397976, \"validation_returns_over_hodl\": -0.06221618633084969, \"validation_sharpe\": -2.468974145877613}, {\"centeredness_margin\": 0.20179647835467157, \"continuous_test_metrics\": [{\"annualised_returns\": -0.4842873671811351, \"annualised_returns_over_hodl\": 0.00042505019140803846, \"annualised_returns_over_uniform_hodl\": 0.820237027167545, \"calmar\": -0.8354270269306004, \"daily_log_sharpe\": -0.692005269581793, \"daily_returns\": 0.031016041579500952, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": 0.005317690254843482, \"return\": -0.2340935423693593, \"returns_over_hodl\": 0.00017116206495004782, \"returns_over_uniform_hodl\": 0.2728093653361223, \"sharpe\": -0.20161420189657508, \"sterling\": -1.2468202823185968, \"ulcer\": -0.17641031409102664}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -0.1252282778192776, \"optuna_trial_number\": 222, \"price_ratio\": 2.7758928178027715, \"shift_exponent\": 0.0006314377612759651, \"step\": 222, \"test_objective\": [{\"annualised_returns\": -0.4842873671811351, \"annualised_returns_over_hodl\": 0.00042505019140803846, \"annualised_returns_over_uniform_hodl\": 0.820237027167545, \"calmar\": -0.8354270269306004, \"daily_log_sharpe\": -0.692005269581793, \"daily_returns\": 0.031016041579500952, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": 0.005317690254843482, \"return\": -0.2340935423693593, \"returns_over_hodl\": 0.00017116206495004782, \"returns_over_uniform_hodl\": 0.2728093653361223, \"sharpe\": -0.20161420189657508, \"sterling\": -1.2468202823185968, \"ulcer\": -0.17641031409102664}], \"train_objective\": [{\"annualised_returns\": -0.5600770741502146, \"annualised_returns_over_hodl\": -0.44598826541293124, \"annualised_returns_over_uniform_hodl\": -0.44598826541293113, \"calmar\": -0.7318296649362604, \"daily_log_sharpe\": -0.5605538025748826, \"daily_returns\": 0.008438349033314967, \"fee_revenue_over_value\": 0.025202377443820666, \"jax_sharpe\": 0.15539138550174728, \"return\": -0.39258389238934643, \"returns_over_hodl\": -0.30130943609382843, \"returns_over_uniform_hodl\": -0.3013094360938283, \"sharpe\": 0.08740168043480138, \"sterling\": -1.5530109076519212, \"ulcer\": -0.1979097644173941}], \"train_return\": -0.39258389238934643, \"train_returns_over_hodl\": -0.30130943609382843, \"train_sharpe\": 0.15539138550174728, \"validation_return\": -0.3391427117475404, \"validation_returns_over_hodl\": -3.6926749436005935e-09, \"validation_sharpe\": -2.2438531807309587}, {\"centeredness_margin\": 0.013912648622778856, \"continuous_test_metrics\": [{\"annualised_returns\": -0.4595589927524789, \"annualised_returns_over_hodl\": 0.08074571774568606, \"annualised_returns_over_uniform_hodl\": 0.9075172291487761, \"calmar\": -0.8347981686913665, \"daily_log_sharpe\": -0.7254226936230183, \"daily_returns\": 0.03068393263323377, \"fee_revenue_over_value\": 0.15033895793424748, \"jax_sharpe\": -0.11425049528107108, \"return\": -0.2195094844775658, \"returns_over_hodl\": 0.031767249718404056, \"returns_over_uniform_hodl\": 0.2970456480888535, \"sharpe\": -0.2831283137654719, \"sterling\": -1.2530139303964456, \"ulcer\": -0.15865200624143866}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -3.137629317246156, \"optuna_trial_number\": 223, \"price_ratio\": 2.7292660929210677, \"shift_exponent\": 0.03721837804358103, \"step\": 223, \"test_objective\": [{\"annualised_returns\": -0.4595589927524789, \"annualised_returns_over_hodl\": 0.08074571774568606, \"annualised_returns_over_uniform_hodl\": 0.9075172291487761, \"calmar\": -0.8347981686913665, \"daily_log_sharpe\": -0.7254226936230183, \"daily_returns\": 0.03068393263323377, \"fee_revenue_over_value\": 0.15033895793424748, \"jax_sharpe\": -0.11425049528107108, \"return\": -0.2195094844775658, \"returns_over_hodl\": 0.031767249718404056, \"returns_over_uniform_hodl\": 0.2970456480888535, \"sharpe\": -0.2831283137654719, \"sterling\": -1.2530139303964456, \"ulcer\": -0.15865200624143866}], \"train_objective\": [{\"annualised_returns\": -0.4936840655645206, \"annualised_returns_over_hodl\": -0.36237701514684684, \"annualised_returns_over_uniform_hodl\": -0.3623770151468467, \"calmar\": -0.6488685714523486, \"daily_log_sharpe\": -0.5031523181350483, \"daily_returns\": 0.008445235365223454, \"fee_revenue_over_value\": 0.05274145582909687, \"jax_sharpe\": 0.06622206728413897, \"return\": -0.3384723067090373, \"returns_over_hodl\": -0.23906667723524, \"returns_over_uniform_hodl\": -0.2390666772352399, \"sharpe\": 0.08519588515080766, \"sterling\": -1.4450974296956822, \"ulcer\": -0.18452231169261782}], \"train_return\": -0.3384723067090373, \"train_returns_over_hodl\": -0.23906667723524, \"train_sharpe\": 0.06622206728413897, \"validation_return\": -0.32001749579866623, \"validation_returns_over_hodl\": -0.03400403679539432, \"validation_sharpe\": -2.2443192959441114}, {\"centeredness_margin\": 0.02872345976973972, \"continuous_test_metrics\": [{\"annualised_returns\": -0.4845064821399546, \"annualised_returns_over_hodl\": 4.101852191240596e-11, \"annualised_returns_over_uniform_hodl\": 0.8194636484758702, \"calmar\": -0.8358050145257323, \"daily_log_sharpe\": -0.6924636697622365, \"daily_returns\": 0.031016041372486636, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": 0.005019196790673321, \"return\": -0.23422461666134198, \"returns_over_hodl\": 1.651967451721248e-11, \"returns_over_uniform_hodl\": 0.2725915416257616, \"sharpe\": -0.20210861514551232, \"sterling\": -1.2473844083028227, \"ulcer\": -0.17641031366307008}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -0.18281569822673055, \"optuna_trial_number\": 224, \"price_ratio\": 1.2248782996436818, \"shift_exponent\": 0.0009535047334746417, \"step\": 224, \"test_objective\": [{\"annualised_returns\": -0.4845064821399546, \"annualised_returns_over_hodl\": 4.101852191240596e-11, \"annualised_returns_over_uniform_hodl\": 0.8194636484758702, \"calmar\": -0.8358050145257323, \"daily_log_sharpe\": -0.6924636697622365, \"daily_returns\": 0.031016041372486636, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": 0.005019196790673321, \"return\": -0.23422461666134198, \"returns_over_hodl\": 1.651967451721248e-11, \"returns_over_uniform_hodl\": 0.2725915416257616, \"sharpe\": -0.20210861514551232, \"sterling\": -1.2473844083028227, \"ulcer\": -0.17641031366307008}], \"train_objective\": [{\"annualised_returns\": -0.6738716582241675, \"annualised_returns_over_hodl\": -0.58929412924725, \"annualised_returns_over_uniform_hodl\": -0.5892941292472506, \"calmar\": -0.8444369388216382, \"daily_log_sharpe\": -0.7604497580513507, \"daily_returns\": 0.010426983790891868, \"fee_revenue_over_value\": 0.0037179169106873406, \"jax_sharpe\": 0.036800758936237764, \"return\": -0.4935136122958692, \"returns_over_hodl\": -0.41740553896764565, \"returns_over_uniform_hodl\": -0.4174055389676461, \"sharpe\": -0.0799521511464465, \"sterling\": -1.791577870799915, \"ulcer\": -0.21099488798696125}], \"train_return\": -0.4935136122958692, \"train_returns_over_hodl\": -0.41740553896764565, \"train_sharpe\": 0.03680075893623776, \"validation_return\": -0.33914271061562584, \"validation_returns_over_hodl\": -5.436449068696447e-09, \"validation_sharpe\": -2.243853183765675}, {\"centeredness_margin\": 0.02632004432149314, \"continuous_test_metrics\": [{\"annualised_returns\": -0.4415870327188932, \"annualised_returns_over_hodl\": 0.0832589364330878, \"annualised_returns_over_uniform_hodl\": 0.9709502827955305, \"calmar\": -0.7977196562961412, \"daily_log_sharpe\": -0.6646939549235746, \"daily_returns\": 0.031016041674663916, \"fee_revenue_over_value\": 0.10808199479412284, \"jax_sharpe\": -0.030541161035070832, \"return\": -0.20915858091629047, \"returns_over_hodl\": 0.03273287713047757, \"returns_over_uniform_hodl\": 0.3142471311958621, \"sharpe\": -0.2065076393293669, \"sterling\": -1.1759148555428833, \"ulcer\": -0.16710452153196384}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -3.5368510438106644, \"optuna_trial_number\": 225, \"price_ratio\": 5.019800166326078, \"shift_exponent\": 0.007563206213419185, \"step\": 225, \"test_objective\": [{\"annualised_returns\": -0.4415870327188932, \"annualised_returns_over_hodl\": 0.0832589364330878, \"annualised_returns_over_uniform_hodl\": 0.9709502827955305, \"calmar\": -0.7977196562961412, \"daily_log_sharpe\": -0.6646939549235746, \"daily_returns\": 0.031016041674663916, \"fee_revenue_over_value\": 0.10808199479412284, \"jax_sharpe\": -0.030541161035070832, \"return\": -0.20915858091629047, \"returns_over_hodl\": 0.03273287713047757, \"returns_over_uniform_hodl\": 0.3142471311958621, \"sharpe\": -0.2065076393293669, \"sterling\": -1.1759148555428833, \"ulcer\": -0.16710452153196384}], \"train_objective\": [{\"annualised_returns\": -0.4441696426603181, \"annualised_returns_over_hodl\": -0.30002161216973156, \"annualised_returns_over_uniform_hodl\": -0.30002161216973156, \"calmar\": -0.5996815813492525, \"daily_log_sharpe\": -0.41676559522588796, \"daily_returns\": 0.00824650396793101, \"fee_revenue_over_value\": 0.04765829358934314, \"jax_sharpe\": 0.16401561390524014, \"return\": -0.299917868359004, \"returns_over_hodl\": -0.1947187879804868, \"returns_over_uniform_hodl\": -0.1947187879804868, \"sharpe\": 0.17405244407726556, \"sterling\": -1.3197711680162414, \"ulcer\": -0.18161767690427133}], \"train_return\": -0.299917868359004, \"train_returns_over_hodl\": -0.1947187879804868, \"train_sharpe\": 0.16401561390524014, \"validation_return\": -0.3353267248444922, \"validation_returns_over_hodl\": -0.024782963868753116, \"validation_sharpe\": -2.2581088053195537}, {\"centeredness_margin\": 0.032494887492274874, \"continuous_test_metrics\": [{\"annualised_returns\": -0.38133903529375246, \"annualised_returns_over_hodl\": 0.20013334293735152, \"annualised_returns_over_uniform_hodl\": 1.1835990114615473, \"calmar\": -0.688882511868861, \"daily_log_sharpe\": -0.5454522773310998, \"daily_returns\": 0.031016041605119647, \"fee_revenue_over_value\": 0.11303047351385428, \"jax_sharpe\": 0.09071411554808546, \"return\": -0.17584268030635597, \"returns_over_hodl\": 0.07623897879757613, \"returns_over_uniform_hodl\": 0.36961262640543735, \"sharpe\": -0.07440043265645528, \"sterling\": -0.9877751432040215, \"ulcer\": -0.17420856154789433}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -0.10028805548511277, \"optuna_trial_number\": 226, \"price_ratio\": 2.5131690617268703, \"shift_exponent\": 0.004252419519103484, \"step\": 226, \"test_objective\": [{\"annualised_returns\": -0.38133903529375246, \"annualised_returns_over_hodl\": 0.20013334293735152, \"annualised_returns_over_uniform_hodl\": 1.1835990114615473, \"calmar\": -0.688882511868861, \"daily_log_sharpe\": -0.5454522773310998, \"daily_returns\": 0.031016041605119647, \"fee_revenue_over_value\": 0.11303047351385428, \"jax_sharpe\": 0.09071411554808546, \"return\": -0.17584268030635597, \"returns_over_hodl\": 0.07623897879757613, \"returns_over_uniform_hodl\": 0.36961262640543735, \"sharpe\": -0.07440043265645528, \"sterling\": -0.9877751432040215, \"ulcer\": -0.17420856154789433}], \"train_objective\": [{\"annualised_returns\": -0.5401791876998693, \"annualised_returns_over_hodl\": -0.420930097403891, \"annualised_returns_over_uniform_hodl\": -0.4209300974038911, \"calmar\": -0.706178130381115, \"daily_log_sharpe\": -0.5319458854322247, \"daily_returns\": 0.008530009365360452, \"fee_revenue_over_value\": 0.03913052031699647, \"jax_sharpe\": 0.13971536327799391, \"return\": -0.3760492109675323, \"returns_over_hodl\": -0.2822901415083423, \"returns_over_uniform_hodl\": -0.2822901415083424, \"sharpe\": 0.10818971578481969, \"sterling\": -1.5128929943585148, \"ulcer\": -0.19453037109051047}], \"train_return\": -0.3760492109675323, \"train_returns_over_hodl\": -0.2822901415083423, \"train_sharpe\": 0.13971536327799394, \"validation_return\": -0.3391427115160006, \"validation_returns_over_hodl\": -2.9514323207635584e-09, \"validation_sharpe\": -2.2438531766214864}, {\"centeredness_margin\": 0.18833497709431576, \"continuous_test_metrics\": [{\"annualised_returns\": -0.47643420495606603, \"annualised_returns_over_hodl\": 0.015659312191720343, \"annualised_returns_over_uniform_hodl\": 0.84795520925723, \"calmar\": -0.8218797910695913, \"daily_log_sharpe\": -0.6766390126933032, \"daily_returns\": 0.031016041525662938, \"fee_revenue_over_value\": 1.0770400198774654e-06, \"jax_sharpe\": 0.01109991432584472, \"return\": -0.2294175755351412, \"returns_over_hodl\": 0.006277348817222084, \"returns_over_uniform_hodl\": 0.2805800458406391, \"sharpe\": -0.1855978742537091, \"sterling\": -1.2266019185323147, \"ulcer\": -0.1764103139797308}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -0.13288287973735635, \"optuna_trial_number\": 227, \"price_ratio\": 2.09059629474202, \"shift_exponent\": 0.001714254634788037, \"step\": 227, \"test_objective\": [{\"annualised_returns\": -0.47643420495606603, \"annualised_returns_over_hodl\": 0.015659312191720343, \"annualised_returns_over_uniform_hodl\": 0.84795520925723, \"calmar\": -0.8218797910695913, \"daily_log_sharpe\": -0.6766390126933032, \"daily_returns\": 0.031016041525662938, \"fee_revenue_over_value\": 1.0770400198774654e-06, \"jax_sharpe\": 0.01109991432584472, \"return\": -0.2294175755351412, \"returns_over_hodl\": 0.006277348817222084, \"returns_over_uniform_hodl\": 0.2805800458406391, \"sharpe\": -0.1855978742537091, \"sterling\": -1.2266019185323147, \"ulcer\": -0.1764103139797308}], \"train_objective\": [{\"annualised_returns\": -0.5873196784954691, \"annualised_returns_over_hodl\": -0.48029591705174846, \"annualised_returns_over_uniform_hodl\": -0.48029591705174846, \"calmar\": -0.7586830068557203, \"daily_log_sharpe\": -0.5996662759868494, \"daily_returns\": 0.008548752476129585, \"fee_revenue_over_value\": 0.029881438589392066, \"jax_sharpe\": 0.12501830923617396, \"return\": -0.4157067170303749, \"returns_over_hodl\": -0.3279068528977622, \"returns_over_uniform_hodl\": -0.3279068528977622, \"sharpe\": 0.06088830767319891, \"sterling\": -1.6090063533861918, \"ulcer\": -0.20112550077454008}], \"train_return\": -0.4157067170303749, \"train_returns_over_hodl\": -0.3279068528977622, \"train_sharpe\": 0.12501830923617396, \"validation_return\": -0.33914271126608553, \"validation_returns_over_hodl\": -3.923673386196924e-09, \"validation_sharpe\": -2.243853179659657}, {\"centeredness_margin\": 0.03379817398516684, \"continuous_test_metrics\": [{\"annualised_returns\": -0.478539734309943, \"annualised_returns_over_hodl\": 0.011574815304116859, \"annualised_returns_over_uniform_hodl\": 0.840523623820266, \"calmar\": -0.8255119717257955, \"daily_log_sharpe\": -0.688797176712757, \"daily_returns\": 0.031016041634964832, \"fee_revenue_over_value\": 0.0023280116543749423, \"jax_sharpe\": -0.008512344749537912, \"return\": -0.2306671263553154, \"returns_over_hodl\": 0.004645601143207001, \"returns_over_uniform_hodl\": 0.2785034998466267, \"sharpe\": -0.2004257960597785, \"sterling\": -1.2320227087955362, \"ulcer\": -0.17641031420568593}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -0.10647760857170574, \"optuna_trial_number\": 228, \"price_ratio\": 3.156979678664138, \"shift_exponent\": 0.0018634191650980746, \"step\": 228, \"test_objective\": [{\"annualised_returns\": -0.478539734309943, \"annualised_returns_over_hodl\": 0.011574815304116859, \"annualised_returns_over_uniform_hodl\": 0.840523623820266, \"calmar\": -0.8255119717257955, \"daily_log_sharpe\": -0.688797176712757, \"daily_returns\": 0.031016041634964832, \"fee_revenue_over_value\": 0.0023280116543749423, \"jax_sharpe\": -0.008512344749537912, \"return\": -0.2306671263553154, \"returns_over_hodl\": 0.004645601143207001, \"returns_over_uniform_hodl\": 0.2785034998466267, \"sharpe\": -0.2004257960597785, \"sterling\": -1.2320227087955362, \"ulcer\": -0.17641031420568593}], \"train_objective\": [{\"annualised_returns\": -0.5224053565807008, \"annualised_returns_over_hodl\": -0.3985468333592471, \"annualised_returns_over_uniform_hodl\": -0.398546833359247, \"calmar\": -0.6902754681796245, \"daily_log_sharpe\": -0.5067241407079374, \"daily_returns\": 0.008385660411668945, \"fee_revenue_over_value\": 0.03782770931547937, \"jax_sharpe\": 0.14918140875952182, \"return\": -0.361515840535348, \"returns_over_hodl\": -0.26557288844986937, \"returns_over_uniform_hodl\": -0.26557288844986926, \"sharpe\": 0.1265834508358506, \"sterling\": -1.4751291681841527, \"ulcer\": -0.19300163125285583}], \"train_return\": -0.361515840535348, \"train_returns_over_hodl\": -0.26557288844986937, \"train_sharpe\": 0.14918140875952182, \"validation_return\": -0.33914271198969825, \"validation_returns_over_hodl\": -3.133029280455446e-09, \"validation_sharpe\": -2.2438531793049346}, {\"centeredness_margin\": 0.011579194459364717, \"continuous_test_metrics\": [{\"annualised_returns\": -0.49099060332555977, \"annualised_returns_over_hodl\": -0.012578488675236699, \"annualised_returns_over_uniform_hodl\": 0.7965775744889538, \"calmar\": -0.8469905636914701, \"daily_log_sharpe\": -0.7160203920183383, \"daily_returns\": 0.03101604176738083, \"fee_revenue_over_value\": 0.0022755039707047157, \"jax_sharpe\": -0.034402009821543816, \"return\": -0.23811857232591116, \"returns_over_hodl\": -0.005084990568934744, \"returns_over_uniform_hodl\": 0.2661204338440102, \"sharpe\": -0.23029927619220295, \"sterling\": -1.2640781019614729, \"ulcer\": -0.17641031447943661}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -0.07801180082710124, \"optuna_trial_number\": 229, \"price_ratio\": 4.956644259406975, \"shift_exponent\": 0.0013218349256573236, \"step\": 229, \"test_objective\": [{\"annualised_returns\": -0.49099060332555977, \"annualised_returns_over_hodl\": -0.012578488675236699, \"annualised_returns_over_uniform_hodl\": 0.7965775744889538, \"calmar\": -0.8469905636914701, \"daily_log_sharpe\": -0.7160203920183383, \"daily_returns\": 0.03101604176738083, \"fee_revenue_over_value\": 0.0022755039707047157, \"jax_sharpe\": -0.034402009821543816, \"return\": -0.23811857232591116, \"returns_over_hodl\": -0.005084990568934744, \"returns_over_uniform_hodl\": 0.2661204338440102, \"sharpe\": -0.23029927619220295, \"sterling\": -1.2640781019614729, \"ulcer\": -0.17641031447943661}], \"train_objective\": [{\"annualised_returns\": -0.44651905119934054, \"annualised_returns_over_hodl\": -0.3029803120316299, \"annualised_returns_over_uniform_hodl\": -0.3029803120316298, \"calmar\": -0.6036791040852184, \"daily_log_sharpe\": -0.41170904069668385, \"daily_returns\": 0.008196169794483397, \"fee_revenue_over_value\": 0.048703660667345064, \"jax_sharpe\": 0.17356488915271856, \"return\": -0.3017159191939808, \"returns_over_hodl\": -0.19678702610601784, \"returns_over_uniform_hodl\": -0.19678702610601773, \"sharpe\": 0.1846259889825468, \"sterling\": -1.3211536519036948, \"ulcer\": -0.1824667924512802}], \"train_return\": -0.3017159191939808, \"train_returns_over_hodl\": -0.19678702610601784, \"train_sharpe\": 0.17356488915271856, \"validation_return\": -0.3391427128926793, \"validation_returns_over_hodl\": -2.2883712702537196e-09, \"validation_sharpe\": -2.243853179142804}, {\"centeredness_margin\": 0.1853990323391508, \"continuous_test_metrics\": [{\"annualised_returns\": -0.5236248749591232, \"annualised_returns_over_hodl\": -0.07588533501714811, \"annualised_returns_over_uniform_hodl\": 0.6813930593116428, \"calmar\": -0.9467720498743832, \"daily_log_sharpe\": -0.8708785068222671, \"daily_returns\": 0.031320079396992925, \"fee_revenue_over_value\": 0.12356563632095187, \"jax_sharpe\": -0.2519850870034653, \"return\": -0.2581810697854481, \"returns_over_hodl\": -0.03128392095630217, \"returns_over_uniform_hodl\": 0.23277989414216926, \"sharpe\": -0.43440313338318826, \"sterling\": -1.431900913466215, \"ulcer\": -0.16050770609290577}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -3.2646830171571275, \"optuna_trial_number\": 230, \"price_ratio\": 2.988415895987006, \"shift_exponent\": 0.007043437479949541, \"step\": 230, \"test_objective\": [{\"annualised_returns\": -0.5236248749591232, \"annualised_returns_over_hodl\": -0.07588533501714811, \"annualised_returns_over_uniform_hodl\": 0.6813930593116428, \"calmar\": -0.9467720498743832, \"daily_log_sharpe\": -0.8708785068222671, \"daily_returns\": 0.031320079396992925, \"fee_revenue_over_value\": 0.12356563632095187, \"jax_sharpe\": -0.2519850870034653, \"return\": -0.2581810697854481, \"returns_over_hodl\": -0.03128392095630217, \"returns_over_uniform_hodl\": 0.23277989414216926, \"sharpe\": -0.43440313338318826, \"sterling\": -1.431900913466215, \"ulcer\": -0.16050770609290577}], \"train_objective\": [{\"annualised_returns\": -0.47087590321220485, \"annualised_returns_over_hodl\": -0.3336538255946284, \"annualised_returns_over_uniform_hodl\": -0.3336538255946283, \"calmar\": -0.6226414922524083, \"daily_log_sharpe\": -0.4657002834928052, \"daily_returns\": 0.008436848371052897, \"fee_revenue_over_value\": 0.03505518263780663, \"jax_sharpe\": 0.12657702363898773, \"return\": -0.32053690836719895, \"returns_over_hodl\": -0.2184361845230296, \"returns_over_uniform_hodl\": -0.2184361845230295, \"sharpe\": 0.12839585005753248, \"sterling\": -1.3863599553869785, \"ulcer\": -0.18305959588533682}], \"train_return\": -0.32053690836719895, \"train_returns_over_hodl\": -0.2184361845230296, \"train_sharpe\": 0.12657702363898773, \"validation_return\": -0.31220459994573924, \"validation_returns_over_hodl\": -0.05714466023404219, \"validation_sharpe\": -2.174358567868442}, {\"centeredness_margin\": 0.14339308305098286, \"continuous_test_metrics\": [{\"annualised_returns\": -0.5407252358047496, \"annualised_returns_over_hodl\": 0.10177306906467054, \"annualised_returns_over_uniform_hodl\": 0.6210363645008177, \"calmar\": -1.0093884644849416, \"daily_log_sharpe\": -0.94924006224548, \"daily_returns\": 0.02625391485900838, \"fee_revenue_over_value\": 0.11790861039805832, \"jax_sharpe\": -0.3319091672606724, \"return\": -0.2690227722200008, \"returns_over_hodl\": 0.03980547997261996, \"returns_over_uniform_hodl\": 0.21476278479754352, \"sharpe\": -0.5306145836663465, \"sterling\": -1.5085345166755066, \"ulcer\": -0.15391686360591275}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -3.0671203271267578, \"optuna_trial_number\": 231, \"price_ratio\": 4.098625000297857, \"shift_exponent\": 0.020386181751239706, \"step\": 231, \"test_objective\": [{\"annualised_returns\": -0.5407252358047496, \"annualised_returns_over_hodl\": 0.10177306906467054, \"annualised_returns_over_uniform_hodl\": 0.6210363645008177, \"calmar\": -1.0093884644849416, \"daily_log_sharpe\": -0.94924006224548, \"daily_returns\": 0.02625391485900838, \"fee_revenue_over_value\": 0.11790861039805832, \"jax_sharpe\": -0.3319091672606724, \"return\": -0.2690227722200008, \"returns_over_hodl\": 0.03980547997261996, \"returns_over_uniform_hodl\": 0.21476278479754352, \"sharpe\": -0.5306145836663465, \"sterling\": -1.5085345166755066, \"ulcer\": -0.15391686360591275}], \"train_objective\": [{\"annualised_returns\": -0.43890547399008606, \"annualised_returns_over_hodl\": -0.29339224360358873, \"annualised_returns_over_uniform_hodl\": -0.29339224360358873, \"calmar\": -0.5852999950115375, \"daily_log_sharpe\": -0.4450487128067362, \"daily_returns\": 0.008302784243918603, \"fee_revenue_over_value\": 0.04134961580966328, \"jax_sharpe\": 0.09574471069301606, \"return\": -0.29589989673838213, \"returns_over_hodl\": -0.1900970487440773, \"returns_over_uniform_hodl\": -0.1900970487440773, \"sharpe\": 0.11497566226405065, \"sterling\": -1.3288492525009137, \"ulcer\": -0.1780658636610634}], \"train_return\": -0.29589989673838213, \"train_returns_over_hodl\": -0.1900970487440773, \"train_sharpe\": 0.09574471069301606, \"validation_return\": -0.2663008340766265, \"validation_returns_over_hodl\": -0.034978780871751214, \"validation_sharpe\": -1.9853027525765161}, {\"centeredness_margin\": 0.02603142176712682, \"continuous_test_metrics\": [{\"annualised_returns\": -0.46352058250394446, \"annualised_returns_over_hodl\": 0.040710295076059166, \"annualised_returns_over_uniform_hodl\": 0.8935345731245286, \"calmar\": -0.8391875912821397, \"daily_log_sharpe\": -0.7251301972004733, \"daily_returns\": 0.03129189763267405, \"fee_revenue_over_value\": 0.12467807394398507, \"jax_sharpe\": -0.10549166477768676, \"return\": -0.22181870084862332, \"returns_over_hodl\": 0.01620045817037541, \"returns_over_uniform_hodl\": 0.2932081138907974, \"sharpe\": -0.27858495459308685, \"sterling\": -1.2531492765182253, \"ulcer\": -0.16191143525783186}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -3.568717967139995, \"optuna_trial_number\": 232, \"price_ratio\": 3.7118003805851, \"shift_exponent\": 0.013646327887868888, \"step\": 232, \"test_objective\": [{\"annualised_returns\": -0.46352058250394446, \"annualised_returns_over_hodl\": 0.040710295076059166, \"annualised_returns_over_uniform_hodl\": 0.8935345731245286, \"calmar\": -0.8391875912821397, \"daily_log_sharpe\": -0.7251301972004733, \"daily_returns\": 0.03129189763267405, \"fee_revenue_over_value\": 0.12467807394398507, \"jax_sharpe\": -0.10549166477768676, \"return\": -0.22181870084862332, \"returns_over_hodl\": 0.01620045817037541, \"returns_over_uniform_hodl\": 0.2932081138907974, \"sharpe\": -0.27858495459308685, \"sterling\": -1.2531492765182253, \"ulcer\": -0.16191143525783186}], \"train_objective\": [{\"annualised_returns\": -0.48189560405669085, \"annualised_returns_over_hodl\": -0.347531355545338, \"annualised_returns_over_uniform_hodl\": -0.347531355545338, \"calmar\": -0.6418685540715185, \"daily_log_sharpe\": -0.47117228276913337, \"daily_returns\": 0.008329204102617229, \"fee_revenue_over_value\": 0.04392769243384686, \"jax_sharpe\": 0.1313099467419636, \"return\": -0.32916359678169727, \"returns_over_hodl\": -0.22835917753794654, \"returns_over_uniform_hodl\": -0.22835917753794654, \"sharpe\": 0.13169888515292413, \"sterling\": -1.4062884205843118, \"ulcer\": -0.18531641545285674}], \"train_return\": -0.32916359678169727, \"train_returns_over_hodl\": -0.22835917753794654, \"train_sharpe\": 0.1313099467419636, \"validation_return\": -0.3263077404098792, \"validation_returns_over_hodl\": -0.03551302727042205, \"validation_sharpe\": -2.275912122862099}, {\"centeredness_margin\": 0.14966016214744451, \"continuous_test_metrics\": [{\"annualised_returns\": -0.5373238746645412, \"annualised_returns_over_hodl\": -0.10245987093940556, \"annualised_returns_over_uniform_hodl\": 0.6330416618237369, \"calmar\": -0.9286936694016656, \"daily_log_sharpe\": -0.8233045085405948, \"daily_returns\": 0.0310160416795479, \"fee_revenue_over_value\": 0.0013919055421760453, \"jax_sharpe\": -0.14010962154964512, \"return\": -0.2668473245636619, \"returns_over_hodl\": -0.04260088916726046, \"returns_over_uniform_hodl\": 0.21837801760202002, \"sharpe\": -0.34550653806756837, \"sterling\": -1.3870098723977693, \"ulcer\": -0.17542131220422683}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -3.6087434840027024, \"optuna_trial_number\": 233, \"price_ratio\": 7.53588028401087, \"shift_exponent\": 0.0011226878867793702, \"step\": 233, \"test_objective\": [{\"annualised_returns\": -0.5373238746645412, \"annualised_returns_over_hodl\": -0.10245987093940556, \"annualised_returns_over_uniform_hodl\": 0.6330416618237369, \"calmar\": -0.9286936694016656, \"daily_log_sharpe\": -0.8233045085405948, \"daily_returns\": 0.0310160416795479, \"fee_revenue_over_value\": 0.0013919055421760453, \"jax_sharpe\": -0.14010962154964512, \"return\": -0.2668473245636619, \"returns_over_hodl\": -0.04260088916726046, \"returns_over_uniform_hodl\": 0.21837801760202002, \"sharpe\": -0.34550653806756837, \"sterling\": -1.3870098723977693, \"ulcer\": -0.17542131220422683}], \"train_objective\": [{\"annualised_returns\": -0.4227229256027587, \"annualised_returns_over_hodl\": -0.2730129426503948, \"annualised_returns_over_uniform_hodl\": -0.2730129426503949, \"calmar\": -0.5715868902454592, \"daily_log_sharpe\": -0.4064763518732358, \"daily_returns\": 0.008165940396483485, \"fee_revenue_over_value\": 0.03184251328912708, \"jax_sharpe\": 0.1408834616384129, \"return\": -0.2836400258369255, \"returns_over_hodl\": -0.17599492664650818, \"returns_over_uniform_hodl\": -0.1759949266465083, \"sharpe\": 0.15892602836744518, \"sterling\": -1.2859679891262834, \"ulcer\": -0.17722836619235777}], \"train_return\": -0.2836400258369255, \"train_returns_over_hodl\": -0.17599492664650818, \"train_sharpe\": 0.1408834616384129, \"validation_return\": -0.3289567527910876, \"validation_returns_over_hodl\": -0.0577329346059664, \"validation_sharpe\": -2.1618590098377806}, {\"centeredness_margin\": 0.011084453397075644, \"continuous_test_metrics\": [{\"annualised_returns\": -0.4088292085487323, \"annualised_returns_over_hodl\": 0.14680546335189226, \"annualised_returns_over_uniform_hodl\": 1.0865708836678651, \"calmar\": -0.7385431079216813, \"daily_log_sharpe\": -0.5841309544829432, \"daily_returns\": 0.031016041688596816, \"fee_revenue_over_value\": 0.08153439549543474, \"jax_sharpe\": 0.06619434276925304, \"return\": -0.19079198119860752, \"returns_over_hodl\": 0.05671719348863835, \"returns_over_uniform_hodl\": 0.34476937042905087, \"sharpe\": -0.11012687065031067, \"sterling\": -1.0604548847522226, \"ulcer\": -0.17368756227189847}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -3.756333031062675, \"optuna_trial_number\": 234, \"price_ratio\": 7.511055820085785, \"shift_exponent\": 0.0027938485665758113, \"step\": 234, \"test_objective\": [{\"annualised_returns\": -0.4088292085487323, \"annualised_returns_over_hodl\": 0.14680546335189226, \"annualised_returns_over_uniform_hodl\": 1.0865708836678651, \"calmar\": -0.7385431079216813, \"daily_log_sharpe\": -0.5841309544829432, \"daily_returns\": 0.031016041688596816, \"fee_revenue_over_value\": 0.08153439549543474, \"jax_sharpe\": 0.06619434276925304, \"return\": -0.19079198119860752, \"returns_over_hodl\": 0.05671719348863835, \"returns_over_uniform_hodl\": 0.34476937042905087, \"sharpe\": -0.11012687065031067, \"sterling\": -1.0604548847522226, \"ulcer\": -0.17368756227189847}], \"train_objective\": [{\"annualised_returns\": -0.4036035795356172, \"annualised_returns_over_hodl\": -0.24893522026671455, \"annualised_returns_over_uniform_hodl\": -0.24893522026671455, \"calmar\": -0.5482630604998419, \"daily_log_sharpe\": -0.37438778178907023, \"daily_returns\": 0.008171861539571529, \"fee_revenue_over_value\": 0.04447199482009931, \"jax_sharpe\": 0.17111470877965712, \"return\": -0.26932791538763734, \"returns_over_hodl\": -0.15953218159380333, \"returns_over_uniform_hodl\": -0.15953218159380333, \"sharpe\": 0.1924409044837844, \"sterling\": -1.2298178611423392, \"ulcer\": -0.17675536986196408}], \"train_return\": -0.26932791538763734, \"train_returns_over_hodl\": -0.15953218159380333, \"train_sharpe\": 0.17111470877965712, \"validation_return\": -0.3332372405899797, \"validation_returns_over_hodl\": -0.04647844578004667, \"validation_sharpe\": -2.197709465868698}, {\"centeredness_margin\": 0.03471348327590233, \"continuous_test_metrics\": [{\"annualised_returns\": -0.4860111919918244, \"annualised_returns_over_hodl\": -0.0029189866811571985, \"annualised_returns_over_uniform_hodl\": 0.8141526895944753, \"calmar\": -0.8384007419523166, \"daily_log_sharpe\": -0.7037904234346877, \"daily_returns\": 0.03101604179547542, \"fee_revenue_over_value\": 0.0017273750321544657, \"jax_sharpe\": -0.018447915492622518, \"return\": -0.2351256323899107, \"returns_over_hodl\": -0.0011766126970653712, \"returns_over_uniform_hodl\": 0.27109420308498855, \"sharpe\": -0.21661566455700856, \"sterling\": -1.2512583534213058, \"ulcer\": -0.176410314537509}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -4.165090229809673, \"optuna_trial_number\": 235, \"price_ratio\": 6.3303935680196055, \"shift_exponent\": 0.00021803473060339138, \"step\": 235, \"test_objective\": [{\"annualised_returns\": -0.4860111919918244, \"annualised_returns_over_hodl\": -0.0029189866811571985, \"annualised_returns_over_uniform_hodl\": 0.8141526895944753, \"calmar\": -0.8384007419523166, \"daily_log_sharpe\": -0.7037904234346877, \"daily_returns\": 0.03101604179547542, \"fee_revenue_over_value\": 0.0017273750321544657, \"jax_sharpe\": -0.018447915492622518, \"return\": -0.2351256323899107, \"returns_over_hodl\": -0.0011766126970653712, \"returns_over_uniform_hodl\": 0.27109420308498855, \"sharpe\": -0.21661566455700856, \"sterling\": -1.2512583534213058, \"ulcer\": -0.176410314537509}], \"train_objective\": [{\"annualised_returns\": -0.4187458499128477, \"annualised_returns_over_hodl\": -0.2680044594091644, \"annualised_returns_over_uniform_hodl\": -0.2680044594091644, \"calmar\": -0.5679204371658844, \"daily_log_sharpe\": -0.38607943033486275, \"daily_returns\": 0.008209705767142682, \"fee_revenue_over_value\": 0.046108649686369886, \"jax_sharpe\": 0.17247927472343297, \"return\": -0.28064776040217465, \"returns_over_hodl\": -0.17255302315108245, \"returns_over_uniform_hodl\": -0.17255302315108245, \"sharpe\": 0.19171548701154786, \"sterling\": -1.262576440364536, \"ulcer\": -0.1787576513437837}], \"train_return\": -0.28064776040217465, \"train_returns_over_hodl\": -0.17255302315108245, \"train_sharpe\": 0.172479274723433, \"validation_return\": -0.33822244642206045, \"validation_returns_over_hodl\": -0.023385016420907512, \"validation_sharpe\": -2.235459278369394}, {\"centeredness_margin\": 0.012682576002860681, \"continuous_test_metrics\": [{\"annualised_returns\": -0.483570663636696, \"annualised_returns_over_hodl\": 0.0018153726024172645, \"annualised_returns_over_uniform_hodl\": 0.8227666730324517, \"calmar\": -0.8341906649908247, \"daily_log_sharpe\": -0.6905190452288439, \"daily_returns\": 0.031016041643381776, \"fee_revenue_over_value\": 2.8514377410654747e-06, \"jax_sharpe\": 0.005803710140623759, \"return\": -0.23366504339367022, \"returns_over_hodl\": 0.0007307232340347802, \"returns_over_uniform_hodl\": 0.2735214594879085, \"sharpe\": -0.2000217951220114, \"sterling\": -1.2449750915728552, \"ulcer\": -0.17641031422309295}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -0.11287199187611796, \"optuna_trial_number\": 236, \"price_ratio\": 3.3556175285049727, \"shift_exponent\": 0.0005626305554459242, \"step\": 236, \"test_objective\": [{\"annualised_returns\": -0.483570663636696, \"annualised_returns_over_hodl\": 0.0018153726024172645, \"annualised_returns_over_uniform_hodl\": 0.8227666730324517, \"calmar\": -0.8341906649908247, \"daily_log_sharpe\": -0.6905190452288439, \"daily_returns\": 0.031016041643381776, \"fee_revenue_over_value\": 2.8514377410654747e-06, \"jax_sharpe\": 0.005803710140623759, \"return\": -0.23366504339367022, \"returns_over_hodl\": 0.0007307232340347802, \"returns_over_uniform_hodl\": 0.2735214594879085, \"sharpe\": -0.2000217951220114, \"sterling\": -1.2449750915728552, \"ulcer\": -0.17641031422309295}], \"train_objective\": [{\"annualised_returns\": -0.5129286087231191, \"annualised_returns_over_hodl\": -0.3866124030071998, \"annualised_returns_over_uniform_hodl\": -0.3866124030071998, \"calmar\": -0.6810201119388333, \"daily_log_sharpe\": -0.49125719793086436, \"daily_returns\": 0.00837761192236571, \"fee_revenue_over_value\": 0.04155949649392348, \"jax_sharpe\": 0.1975678610903289, \"return\": -0.3538537924281294, \"returns_over_hodl\": -0.25675948912503566, \"returns_over_uniform_hodl\": -0.25675948912503566, \"sharpe\": 0.14134743449617285, \"sterling\": -1.4495283626233555, \"ulcer\": -0.19276229566614644}], \"train_return\": -0.3538537924281294, \"train_returns_over_hodl\": -0.25675948912503566, \"train_sharpe\": 0.19756786109032892, \"validation_return\": -0.3391427122078835, \"validation_returns_over_hodl\": -3.3225969753303275e-09, \"validation_sharpe\": -2.2438531809075712}, {\"centeredness_margin\": 0.15267517190106938, \"continuous_test_metrics\": [{\"annualised_returns\": -0.4848903333762884, \"annualised_returns_over_hodl\": -0.0007446431206954429, \"annualised_returns_over_uniform_hodl\": 0.8181088237365974, \"calmar\": -0.8364671848036441, \"daily_log_sharpe\": -0.6983715981191129, \"daily_returns\": 0.031016041728244504, \"fee_revenue_over_value\": 3.179871937448621e-07, \"jax_sharpe\": -0.016156493609687187, \"return\": -0.23445431621775503, \"returns_over_hodl\": -0.0002999626650584952, \"returns_over_uniform_hodl\": 0.27220981910115927, \"sharpe\": -0.2103121104341762, \"sterling\": -1.2483726470121328, \"ulcer\": -0.17641031439852528}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -4.287547182659424, \"optuna_trial_number\": 237, \"price_ratio\": 5.897213027565751, \"shift_exponent\": 0.000357367749472578, \"step\": 237, \"test_objective\": [{\"annualised_returns\": -0.4848903333762884, \"annualised_returns_over_hodl\": -0.0007446431206954429, \"annualised_returns_over_uniform_hodl\": 0.8181088237365974, \"calmar\": -0.8364671848036441, \"daily_log_sharpe\": -0.6983715981191129, \"daily_returns\": 0.031016041728244504, \"fee_revenue_over_value\": 3.179871937448621e-07, \"jax_sharpe\": -0.016156493609687187, \"return\": -0.23445431621775503, \"returns_over_hodl\": -0.0002999626650584952, \"returns_over_uniform_hodl\": 0.27220981910115927, \"sharpe\": -0.2103121104341762, \"sterling\": -1.2483726470121328, \"ulcer\": -0.17641031439852528}], \"train_objective\": [{\"annualised_returns\": -0.45147910201410846, \"annualised_returns_over_hodl\": -0.3092266933726825, \"annualised_returns_over_uniform_hodl\": -0.3092266933726825, \"calmar\": -0.6078628284098201, \"daily_log_sharpe\": -0.4314567315136596, \"daily_returns\": 0.008235949571583677, \"fee_revenue_over_value\": 0.03194532919234875, \"jax_sharpe\": 0.18460170714486557, \"return\": -0.3055218262152083, \"returns_over_hodl\": -0.20116483448073452, \"returns_over_uniform_hodl\": -0.20116483448073452, \"sharpe\": 0.15180708731480394, \"sterling\": -1.348532893194898, \"ulcer\": -0.18085987626423078}], \"train_return\": -0.3055218262152083, \"train_returns_over_hodl\": -0.20116483448073452, \"train_sharpe\": 0.18460170714486557, \"validation_return\": -0.3390968573971376, \"validation_returns_over_hodl\": -0.0035884581753570233, \"validation_sharpe\": -2.243475046404047}, {\"centeredness_margin\": 0.025502177506689185, \"continuous_test_metrics\": [{\"annualised_returns\": -0.5057680617947484, \"annualised_returns_over_hodl\": -0.041245111039865834, \"annualised_returns_over_uniform_hodl\": 0.7444196955438089, \"calmar\": -0.8725315180734754, \"daily_log_sharpe\": -0.7485404491963946, \"daily_returns\": 0.031016041697848242, \"fee_revenue_over_value\": 0.001940400828821747, \"jax_sharpe\": -0.0641706639989891, \"return\": -0.24710509298227912, \"returns_over_hodl\": -0.016820181666143852, \"returns_over_uniform_hodl\": 0.2511863285897, \"sharpe\": -0.2651793956536324, \"sterling\": -1.3022461372834668, \"ulcer\": -0.17637425725615702}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -3.602449569338833, \"optuna_trial_number\": 238, \"price_ratio\": 8.509160538399898, \"shift_exponent\": 1.4205730154679616e-05, \"step\": 238, \"test_objective\": [{\"annualised_returns\": -0.5057680617947484, \"annualised_returns_over_hodl\": -0.041245111039865834, \"annualised_returns_over_uniform_hodl\": 0.7444196955438089, \"calmar\": -0.8725315180734754, \"daily_log_sharpe\": -0.7485404491963946, \"daily_returns\": 0.031016041697848242, \"fee_revenue_over_value\": 0.001940400828821747, \"jax_sharpe\": -0.0641706639989891, \"return\": -0.24710509298227912, \"returns_over_hodl\": -0.016820181666143852, \"returns_over_uniform_hodl\": 0.2511863285897, \"sharpe\": -0.2651793956536324, \"sterling\": -1.3022461372834668, \"ulcer\": -0.17637425725615702}], \"train_objective\": [{\"annualised_returns\": -0.39488229515732254, \"annualised_returns_over_hodl\": -0.23795217391396317, \"annualised_returns_over_uniform_hodl\": -0.23795217391396317, \"calmar\": -0.5369096237487906, \"daily_log_sharpe\": -0.3680308989509946, \"daily_returns\": 0.008155628998938163, \"fee_revenue_over_value\": 0.042783471914982885, \"jax_sharpe\": 0.20730832362816692, \"return\": -0.2628594309000114, \"returns_over_hodl\": -0.15209169883799423, \"returns_over_uniform_hodl\": -0.15209169883799423, \"sharpe\": 0.19218594698917113, \"sterling\": -1.2109964974807377, \"ulcer\": -0.17554628422681143}], \"train_return\": -0.2628594309000114, \"train_returns_over_hodl\": -0.15209169883799423, \"train_sharpe\": 0.2073083236281669, \"validation_return\": -0.32852028558267876, \"validation_returns_over_hodl\": -0.058858651770048453, \"validation_sharpe\": -2.153823878084784}, {\"centeredness_margin\": 0.014418341802385184, \"continuous_test_metrics\": [{\"annualised_returns\": -0.47755716745716903, \"annualised_returns_over_hodl\": 0.013480879991894845, \"annualised_returns_over_uniform_hodl\": 0.84399164933151, \"calmar\": -0.8238169778573664, \"daily_log_sharpe\": -0.6796698290729928, \"daily_returns\": 0.031016041768541443, \"fee_revenue_over_value\": 0.0004852214885470507, \"jax_sharpe\": 0.009288223633919712, \"return\": -0.2300836363846378, \"returns_over_hodl\": 0.005407558616254082, \"returns_over_uniform_hodl\": 0.2794731632981604, \"sharpe\": -0.188930302481749, \"sterling\": -1.2294930384766536, \"ulcer\": -0.176410314481848}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -0.07996291652710719, \"optuna_trial_number\": 239, \"price_ratio\": 5.014833572834744, \"shift_exponent\": 4.783466635196402e-05, \"step\": 239, \"test_objective\": [{\"annualised_returns\": -0.47755716745716903, \"annualised_returns_over_hodl\": 0.013480879991894845, \"annualised_returns_over_uniform_hodl\": 0.84399164933151, \"calmar\": -0.8238169778573664, \"daily_log_sharpe\": -0.6796698290729928, \"daily_returns\": 0.031016041768541443, \"fee_revenue_over_value\": 0.0004852214885470507, \"jax_sharpe\": 0.009288223633919712, \"return\": -0.2300836363846378, \"returns_over_hodl\": 0.005407558616254082, \"returns_over_uniform_hodl\": 0.2794731632981604, \"sharpe\": -0.188930302481749, \"sterling\": -1.2294930384766536, \"ulcer\": -0.176410314481848}], \"train_objective\": [{\"annualised_returns\": -0.44749223585606224, \"annualised_returns_over_hodl\": -0.30420588062117837, \"annualised_returns_over_uniform_hodl\": -0.30420588062117837, \"calmar\": -0.6049965228848682, \"daily_log_sharpe\": -0.41346628748981984, \"daily_returns\": 0.008249553459823488, \"fee_revenue_over_value\": 0.04803480027947837, \"jax_sharpe\": 0.22067943129084966, \"return\": -0.30246159551082985, \"returns_over_hodl\": -0.19764475279416915, \"returns_over_uniform_hodl\": -0.19764475279416915, \"sharpe\": 0.18223293144635608, \"sterling\": -1.3247013463827242, \"ulcer\": -0.18239195574286932}], \"train_return\": -0.30246159551082985, \"train_returns_over_hodl\": -0.19764475279416915, \"train_sharpe\": 0.22067943129084966, \"validation_return\": -0.33914271294333287, \"validation_returns_over_hodl\": -2.345626914923571e-09, \"validation_sharpe\": -2.243853179573982}, {\"centeredness_margin\": 0.016473628441560965, \"continuous_test_metrics\": [{\"annualised_returns\": -0.47733132747675244, \"annualised_returns_over_hodl\": 0.013918983977741828, \"annualised_returns_over_uniform_hodl\": 0.8447887643688552, \"calmar\": -0.8234273886593829, \"daily_log_sharpe\": -0.6832256136285569, \"daily_returns\": 0.031016041778465973, \"fee_revenue_over_value\": 0.0017297124903859985, \"jax_sharpe\": 0.004408666131337622, \"return\": -0.22994961565856642, \"returns_over_hodl\": 0.005582571553954807, \"returns_over_uniform_hodl\": 0.2796958834927654, \"sharpe\": -0.19351893968818776, \"sterling\": -1.228911601844035, \"ulcer\": -0.17641031450236505}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -0.08924654823401956, \"optuna_trial_number\": 240, \"price_ratio\": 5.425024084346551, \"shift_exponent\": 0.00017639425081926346, \"step\": 240, \"test_objective\": [{\"annualised_returns\": -0.47733132747675244, \"annualised_returns_over_hodl\": 0.013918983977741828, \"annualised_returns_over_uniform_hodl\": 0.8447887643688552, \"calmar\": -0.8234273886593829, \"daily_log_sharpe\": -0.6832256136285569, \"daily_returns\": 0.031016041778465973, \"fee_revenue_over_value\": 0.0017297124903859985, \"jax_sharpe\": 0.004408666131337622, \"return\": -0.22994961565856642, \"returns_over_hodl\": 0.005582571553954807, \"returns_over_uniform_hodl\": 0.2796958834927654, \"sharpe\": -0.19351893968818776, \"sterling\": -1.228911601844035, \"ulcer\": -0.17641031450236505}], \"train_objective\": [{\"annualised_returns\": -0.4355135632998146, \"annualised_returns_over_hodl\": -0.28912068098508736, \"annualised_returns_over_uniform_hodl\": -0.28912068098508736, \"calmar\": -0.5894755246842769, \"daily_log_sharpe\": -0.4000737254654309, \"daily_returns\": 0.008242251001763696, \"fee_revenue_over_value\": 0.048401127620834, \"jax_sharpe\": 0.22074034533476605, \"return\": -0.29331880331270965, \"returns_over_hodl\": -0.1871281027472853, \"returns_over_uniform_hodl\": -0.1871281027472853, \"sharpe\": 0.1890650072463369, \"sterling\": -1.2980119749330252, \"ulcer\": -0.18101759125215638}], \"train_return\": -0.29331880331270965, \"train_returns_over_hodl\": -0.1871281027472853, \"train_sharpe\": 0.22074034533476605, \"validation_return\": -0.33914271295502707, \"validation_returns_over_hodl\": -2.625822892454721e-09, \"validation_sharpe\": -2.243853179013918}, {\"centeredness_margin\": 0.04250865712834351, \"continuous_test_metrics\": [{\"annualised_returns\": -0.5147805194859395, \"annualised_returns_over_hodl\": 0.1715737403731723, \"annualised_returns_over_uniform_hodl\": 0.7126097142648584, \"calmar\": -0.942174074035535, \"daily_log_sharpe\": -0.8553787194207645, \"daily_returns\": 0.026137319701167196, \"fee_revenue_over_value\": 0.08152603406446375, \"jax_sharpe\": -0.22741888920701733, \"return\": -0.25266478721982477, \"returns_over_hodl\": 0.06585010088737642, \"returns_over_uniform_hodl\": 0.24194703987049482, \"sharpe\": -0.421639105588369, \"sterling\": -1.4118753133018696, \"ulcer\": -0.15972914507504704}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -2.505586117000965, \"optuna_trial_number\": 241, \"price_ratio\": 34.69899852699559, \"shift_exponent\": 0.0012142376645076548, \"step\": 241, \"test_objective\": [{\"annualised_returns\": -0.5147805194859395, \"annualised_returns_over_hodl\": 0.1715737403731723, \"annualised_returns_over_uniform_hodl\": 0.7126097142648584, \"calmar\": -0.942174074035535, \"daily_log_sharpe\": -0.8553787194207645, \"daily_returns\": 0.026137319701167196, \"fee_revenue_over_value\": 0.08152603406446375, \"jax_sharpe\": -0.22741888920701733, \"return\": -0.25266478721982477, \"returns_over_hodl\": 0.06585010088737642, \"returns_over_uniform_hodl\": 0.24194703987049482, \"sharpe\": -0.421639105588369, \"sterling\": -1.4118753133018696, \"ulcer\": -0.15972914507504704}], \"train_objective\": [{\"annualised_returns\": -0.34260600692006604, \"annualised_returns_over_hodl\": -0.17211864848868175, \"annualised_returns_over_uniform_hodl\": -0.17211864848868175, \"calmar\": -0.4686532882802878, \"daily_log_sharpe\": -0.3302262643865479, \"daily_returns\": 0.008064942414970785, \"fee_revenue_over_value\": 0.03157374246747776, \"jax_sharpe\": 0.1871471895607079, \"return\": -0.22482794902592584, \"returns_over_hodl\": -0.10834534903947246, \"returns_over_uniform_hodl\": -0.10834534903947246, \"sharpe\": 0.19112843111036573, \"sterling\": -1.0951721283105487, \"ulcer\": -0.1682862677197868}], \"train_return\": -0.22482794902592584, \"train_returns_over_hodl\": -0.10834534903947246, \"train_sharpe\": 0.18714718956070786, \"validation_return\": -0.24088125392281368, \"validation_returns_over_hodl\": -0.04170641773645234, \"validation_sharpe\": -1.7522454914760563}, {\"centeredness_margin\": 0.22101884379124528, \"continuous_test_metrics\": [{\"annualised_returns\": -0.4845064797712044, \"annualised_returns_over_hodl\": 3.773958923147802e-11, \"annualised_returns_over_uniform_hodl\": 0.8194636568365081, \"calmar\": -0.8358050107304917, \"daily_log_sharpe\": -0.6924636638144593, \"daily_returns\": 0.031016041485043923, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": 0.005019203911353498, \"return\": -0.2342246152441787, \"returns_over_hodl\": 1.5199175251723318e-11, \"returns_over_uniform_hodl\": 0.2725915439808515, \"sharpe\": -0.20210860816902398, \"sterling\": -1.2473844003648658, \"ulcer\": -0.17641031389575929}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -0.15874373589695012, \"optuna_trial_number\": 242, \"price_ratio\": 2.065560935019055, \"shift_exponent\": 1.031685151190376e-05, \"step\": 242, \"test_objective\": [{\"annualised_returns\": -0.4845064797712044, \"annualised_returns_over_hodl\": 3.773958923147802e-11, \"annualised_returns_over_uniform_hodl\": 0.8194636568365081, \"calmar\": -0.8358050107304917, \"daily_log_sharpe\": -0.6924636638144593, \"daily_returns\": 0.031016041485043923, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": 0.005019203911353498, \"return\": -0.2342246152441787, \"returns_over_hodl\": 1.5199175251723318e-11, \"returns_over_uniform_hodl\": 0.2725915439808515, \"sharpe\": -0.20210860816902398, \"sterling\": -1.2473844003648658, \"ulcer\": -0.17641031389575929}], \"train_objective\": [{\"annualised_returns\": -0.6103370146628926, \"annualised_returns_over_hodl\": -0.5092825271745458, \"annualised_returns_over_uniform_hodl\": -0.5092825271745458, \"calmar\": -0.7867138676370794, \"daily_log_sharpe\": -0.6373250292503245, \"daily_returns\": 0.008582110039443041, \"fee_revenue_over_value\": 0.029281517378170883, \"jax_sharpe\": 0.11878868237153863, \"return\": -0.43571483621191043, \"returns_over_hodl\": -0.3509215275145402, \"returns_over_uniform_hodl\": -0.3509215275145402, \"sharpe\": 0.030296335785464912, \"sterling\": -1.6594090738889438, \"ulcer\": -0.2042478632219833}], \"train_return\": -0.43571483621191043, \"train_returns_over_hodl\": -0.3509215275145402, \"train_sharpe\": 0.11878868237153863, \"validation_return\": -0.3391427113726747, \"validation_returns_over_hodl\": -4.702604083917095e-09, \"validation_sharpe\": -2.2438531835492874}, {\"centeredness_margin\": 0.9729523170965145, \"continuous_test_metrics\": [{\"annualised_returns\": -0.5289420101812417, \"annualised_returns_over_hodl\": -0.08619998718135702, \"annualised_returns_over_uniform_hodl\": 0.6626259285612186, \"calmar\": -0.9138774666860796, \"daily_log_sharpe\": -0.8003650638158577, \"daily_returns\": 0.0310160416784154, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": -0.1174289466569915, \"return\": -0.26152689143574914, \"returns_over_hodl\": -0.03565311647430258, \"returns_over_uniform_hodl\": 0.22721969408272358, \"sharpe\": -0.3207285223770064, \"sterling\": -1.364790290356849, \"ulcer\": -0.1755618346670853}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -3.2052087958845425, \"optuna_trial_number\": 243, \"price_ratio\": 11.489818839970235, \"shift_exponent\": 3.9896604316920594e-05, \"step\": 243, \"test_objective\": [{\"annualised_returns\": -0.5289420101812417, \"annualised_returns_over_hodl\": -0.08619998718135702, \"annualised_returns_over_uniform_hodl\": 0.6626259285612186, \"calmar\": -0.9138774666860796, \"daily_log_sharpe\": -0.8003650638158577, \"daily_returns\": 0.0310160416784154, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": -0.1174289466569915, \"return\": -0.26152689143574914, \"returns_over_hodl\": -0.03565311647430258, \"returns_over_uniform_hodl\": 0.22721969408272358, \"sharpe\": -0.3207285223770064, \"sterling\": -1.364790290356849, \"ulcer\": -0.1755618346670853}], \"train_objective\": [{\"annualised_returns\": -0.44174951803079254, \"annualised_returns_over_hodl\": -0.29697385683547517, \"annualised_returns_over_uniform_hodl\": -0.29697385683547517, \"calmar\": -0.5925517330688022, \"daily_log_sharpe\": -0.4516897807262284, \"daily_returns\": 0.008039899975027694, \"fee_revenue_over_value\": 0.00018558885925325097, \"jax_sharpe\": 0.07980239131777903, \"return\": -0.29806881635093496, \"returns_over_hodl\": -0.19259188490033707, \"returns_over_uniform_hodl\": -0.19259188490033707, \"sharpe\": 0.09909695668655062, \"sterling\": -1.3551885404512805, \"ulcer\": -0.17649854776621535}], \"train_return\": -0.29806881635093496, \"train_returns_over_hodl\": -0.19259188490033707, \"train_sharpe\": 0.07980239131777903, \"validation_return\": -0.3171867975221797, \"validation_returns_over_hodl\": -0.06591629218967254, \"validation_sharpe\": -2.096662093203561}, {\"centeredness_margin\": 0.034284245310300304, \"continuous_test_metrics\": [{\"annualised_returns\": -0.4816928733717163, \"annualised_returns_over_hodl\": 0.005458072954907411, \"annualised_returns_over_uniform_hodl\": 0.8293944404208289, \"calmar\": -0.8309513499961974, \"daily_log_sharpe\": -0.6867751314915357, \"daily_returns\": 0.031016041729572355, \"fee_revenue_over_value\": 2.4154563965345072e-06, \"jax_sharpe\": 0.007516436718525462, \"return\": -0.23254404159644004, \"returns_over_hodl\": 0.0021945998854646653, \"returns_over_uniform_hodl\": 0.27538437834941965, \"sharpe\": -0.19611824943930922, \"sterling\": -1.2401406237523773, \"ulcer\": -0.17641031440127583}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -0.09315361509819564, \"optuna_trial_number\": 244, \"price_ratio\": 4.220983096170188, \"shift_exponent\": 0.000153038755485778, \"step\": 244, \"test_objective\": [{\"annualised_returns\": -0.4816928733717163, \"annualised_returns_over_hodl\": 0.005458072954907411, \"annualised_returns_over_uniform_hodl\": 0.8293944404208289, \"calmar\": -0.8309513499961974, \"daily_log_sharpe\": -0.6867751314915357, \"daily_returns\": 0.031016041729572355, \"fee_revenue_over_value\": 2.4154563965345072e-06, \"jax_sharpe\": 0.007516436718525462, \"return\": -0.23254404159644004, \"returns_over_hodl\": 0.0021945998854646653, \"returns_over_uniform_hodl\": 0.27538437834941965, \"sharpe\": -0.19611824943930922, \"sterling\": -1.2401406237523773, \"ulcer\": -0.17641031440127583}], \"train_objective\": [{\"annualised_returns\": -0.4865443395682698, \"annualised_returns_over_hodl\": -0.3533856856406188, \"annualised_returns_over_uniform_hodl\": -0.3533856856406188, \"calmar\": -0.6518474499898658, \"daily_log_sharpe\": -0.4627429563702894, \"daily_returns\": 0.008287321004198048, \"fee_revenue_over_value\": 0.03974500851794923, \"jax_sharpe\": 0.20601517577072792, \"return\": -0.3328244112009663, \"returns_over_hodl\": -0.2325700907141155, \"returns_over_uniform_hodl\": -0.2325700907141155, \"sharpe\": 0.1503313030750812, \"sterling\": -1.4106862101116413, \"ulcer\": -0.1869357739060962}], \"train_return\": -0.3328244112009663, \"train_returns_over_hodl\": -0.2325700907141155, \"train_sharpe\": 0.20601517577072792, \"validation_return\": -0.3391427127710458, \"validation_returns_over_hodl\": -2.7356242826570565e-09, \"validation_sharpe\": -2.2438531805580544}, {\"centeredness_margin\": 0.1356952633702054, \"continuous_test_metrics\": [{\"annualised_returns\": -0.48450647747106423, \"annualised_returns_over_hodl\": 3.4615643684787756e-11, \"annualised_returns_over_uniform_hodl\": 0.8194636649549842, \"calmar\": -0.8358050070451862, \"daily_log_sharpe\": -0.6924636580389716, \"daily_returns\": 0.03101604159433956, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": 0.005019210825743215, \"return\": -0.23422461386806315, \"returns_over_hodl\": 1.394107052021809e-11, \"returns_over_uniform_hodl\": 0.2725915462677273, \"sharpe\": -0.20210860139463685, \"sterling\": -1.247384392656854, \"ulcer\": -0.17641031412170544}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -0.12972550710364583, \"optuna_trial_number\": 245, \"price_ratio\": 3.021950512656762, \"shift_exponent\": 2.0678876042543667e-05, \"step\": 245, \"test_objective\": [{\"annualised_returns\": -0.48450647747106423, \"annualised_returns_over_hodl\": 3.4615643684787756e-11, \"annualised_returns_over_uniform_hodl\": 0.8194636649549842, \"calmar\": -0.8358050070451862, \"daily_log_sharpe\": -0.6924636580389716, \"daily_returns\": 0.03101604159433956, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": 0.005019210825743215, \"return\": -0.23422461386806315, \"returns_over_hodl\": 1.394107052021809e-11, \"returns_over_uniform_hodl\": 0.2725915462677273, \"sharpe\": -0.20210860139463685, \"sterling\": -1.247384392656854, \"ulcer\": -0.17641031412170544}], \"train_objective\": [{\"annualised_returns\": -0.5534666648702913, \"annualised_returns_over_hodl\": -0.43766352465424785, \"annualised_returns_over_uniform_hodl\": -0.4376635246542482, \"calmar\": -0.7258048441502156, \"daily_log_sharpe\": -0.5501514289722735, \"daily_returns\": 0.008345369758842007, \"fee_revenue_over_value\": 0.025634554778064547, \"jax_sharpe\": 0.13317901884157216, \"return\": -0.38705881133839404, \"returns_over_hodl\": -0.294954118961554, \"returns_over_uniform_hodl\": -0.2949541189615542, \"sharpe\": 0.09588323222595743, \"sterling\": -1.5368139922917585, \"ulcer\": -0.19769396333806366}], \"train_return\": -0.38705881133839404, \"train_returns_over_hodl\": -0.294954118961554, \"train_sharpe\": 0.13317901884157213, \"validation_return\": -0.33914271197530266, \"validation_returns_over_hodl\": -3.826776340254412e-09, \"validation_sharpe\": -2.243853181988838}, {\"centeredness_margin\": 0.05472040498600861, \"continuous_test_metrics\": [{\"annualised_returns\": -0.479114387370157, \"annualised_returns_over_hodl\": 0.010460047628056302, \"annualised_returns_over_uniform_hodl\": 0.8384953532071167, \"calmar\": -0.8265032873835614, \"daily_log_sharpe\": -0.6822443060377369, \"daily_returns\": 0.03101604175041591, \"fee_revenue_over_value\": 3.2936612094131455e-06, \"jax_sharpe\": 0.007038470329429248, \"return\": -0.2310086841454736, \"returns_over_hodl\": 0.004199570548744536, \"returns_over_uniform_hodl\": 0.27793588750991693, \"sharpe\": -0.19152645247964167, \"sterling\": -1.2335021803718476, \"ulcer\": -0.1764103144443644}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -0.08604170643470588, \"optuna_trial_number\": 246, \"price_ratio\": 4.808014166382604, \"shift_exponent\": 0.0001118905399061133, \"step\": 246, \"test_objective\": [{\"annualised_returns\": -0.479114387370157, \"annualised_returns_over_hodl\": 0.010460047628056302, \"annualised_returns_over_uniform_hodl\": 0.8384953532071167, \"calmar\": -0.8265032873835614, \"daily_log_sharpe\": -0.6822443060377369, \"daily_returns\": 0.03101604175041591, \"fee_revenue_over_value\": 3.2936612094131455e-06, \"jax_sharpe\": 0.007038470329429248, \"return\": -0.2310086841454736, \"returns_over_hodl\": 0.004199570548744536, \"returns_over_uniform_hodl\": 0.27793588750991693, \"sharpe\": -0.19152645247964167, \"sterling\": -1.2335021803718476, \"ulcer\": -0.1764103144443644}], \"train_objective\": [{\"annualised_returns\": -0.47026036511531344, \"annualised_returns_over_hodl\": -0.33287865497103686, \"annualised_returns_over_uniform_hodl\": -0.33287865497103686, \"calmar\": -0.6322648738818493, \"daily_log_sharpe\": -0.445445366719127, \"daily_returns\": 0.008214498654265483, \"fee_revenue_over_value\": 0.038330678587187264, \"jax_sharpe\": 0.15001530536638735, \"return\": -0.32005713117807455, \"returns_over_hodl\": -0.2178843127655662, \"returns_over_uniform_hodl\": -0.2178843127655662, \"sharpe\": 0.15559685069046622, \"sterling\": -1.3806772002502528, \"ulcer\": -0.18418120692095008}], \"train_return\": -0.32005713117807455, \"train_returns_over_hodl\": -0.2178843127655662, \"train_sharpe\": 0.15001530536638735, \"validation_return\": -0.3391427128295462, \"validation_returns_over_hodl\": -2.5258630742541754e-09, \"validation_sharpe\": -2.2438531796990606}, {\"centeredness_margin\": 0.013065874541032132, \"continuous_test_metrics\": [{\"annualised_returns\": -0.48450647854901585, \"annualised_returns_over_hodl\": 3.604805343115913e-11, \"annualised_returns_over_uniform_hodl\": 0.8194636611502923, \"calmar\": -0.8358050087722866, \"daily_log_sharpe\": -0.692463660745616, \"daily_returns\": 0.031016041543119387, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": 0.005019207585363845, \"return\": -0.23422461451297427, \"returns_over_hodl\": 1.4517942403813322e-11, \"returns_over_uniform_hodl\": 0.27259154519599194, \"sharpe\": -0.2021086045694098, \"sterling\": -1.2473843962691706, \"ulcer\": -0.1764103140158184}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -0.1445203516413014, \"optuna_trial_number\": 247, \"price_ratio\": 2.461718437116476, \"shift_exponent\": 0.0001318243666934581, \"step\": 247, \"test_objective\": [{\"annualised_returns\": -0.48450647854901585, \"annualised_returns_over_hodl\": 3.604805343115913e-11, \"annualised_returns_over_uniform_hodl\": 0.8194636611502923, \"calmar\": -0.8358050087722866, \"daily_log_sharpe\": -0.692463660745616, \"daily_returns\": 0.031016041543119387, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": 0.005019207585363845, \"return\": -0.23422461451297427, \"returns_over_hodl\": 1.4517942403813322e-11, \"returns_over_uniform_hodl\": 0.27259154519599194, \"sharpe\": -0.2021086045694098, \"sterling\": -1.2473843962691706, \"ulcer\": -0.1764103140158184}], \"train_objective\": [{\"annualised_returns\": -0.5806089844833682, \"annualised_returns_over_hodl\": -0.47184488390146284, \"annualised_returns_over_uniform_hodl\": -0.47184488390146306, \"calmar\": -0.7538515310216498, \"daily_log_sharpe\": -0.5862307151472079, \"daily_returns\": 0.008541611371386258, \"fee_revenue_over_value\": 0.03185002882055618, \"jax_sharpe\": 0.1319785971661381, \"return\": -0.4099565428603045, \"returns_over_hodl\": -0.3212926186305002, \"returns_over_uniform_hodl\": -0.3212926186305004, \"sharpe\": 0.07499130220458655, \"sterling\": -1.5929364707496307, \"ulcer\": -0.20187696159343574}], \"train_return\": -0.4099565428603045, \"train_returns_over_hodl\": -0.3212926186305002, \"train_sharpe\": 0.1319785971661381, \"validation_return\": -0.3391427117032447, \"validation_returns_over_hodl\": -4.271947684486577e-09, \"validation_sharpe\": -2.243853182823951}, {\"centeredness_margin\": 0.05720630948291085, \"continuous_test_metrics\": [{\"annualised_returns\": -0.49173179668180766, \"annualised_returns_over_hodl\": -0.014016322399988068, \"annualised_returns_over_uniform_hodl\": 0.7939614904423822, \"calmar\": -0.8482691725451642, \"daily_log_sharpe\": -0.7169602026175792, \"daily_returns\": 0.031016041797268544, \"fee_revenue_over_value\": 0.0015588275367595804, \"jax_sharpe\": -0.03381278449124404, \"return\": -0.23856556919638194, \"returns_over_hodl\": -0.00566870904787975, \"returns_over_uniform_hodl\": 0.2653775992623941, \"sharpe\": -0.2310568506807971, \"sterling\": -1.2659863415608752, \"ulcer\": -0.1764103145412161}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -4.113189421547813, \"optuna_trial_number\": 248, \"price_ratio\": 6.480300010140309, \"shift_exponent\": 0.00038469552461923244, \"step\": 248, \"test_objective\": [{\"annualised_returns\": -0.49173179668180766, \"annualised_returns_over_hodl\": -0.014016322399988068, \"annualised_returns_over_uniform_hodl\": 0.7939614904423822, \"calmar\": -0.8482691725451642, \"daily_log_sharpe\": -0.7169602026175792, \"daily_returns\": 0.031016041797268544, \"fee_revenue_over_value\": 0.0015588275367595804, \"jax_sharpe\": -0.03381278449124404, \"return\": -0.23856556919638194, \"returns_over_hodl\": -0.00566870904787975, \"returns_over_uniform_hodl\": 0.2653775992623941, \"sharpe\": -0.2310568506807971, \"sterling\": -1.2659863415608752, \"ulcer\": -0.1764103145412161}], \"train_objective\": [{\"annualised_returns\": -0.4208755664179704, \"annualised_returns_over_hodl\": -0.2706864927060256, \"annualised_returns_over_uniform_hodl\": -0.27068649270602585, \"calmar\": -0.5708156507884609, \"daily_log_sharpe\": -0.39113438590923905, \"daily_returns\": 0.00817016453803836, \"fee_revenue_over_value\": 0.043628684720358996, \"jax_sharpe\": 0.16613143633972563, \"return\": -0.2822491089339354, \"returns_over_hodl\": -0.17439500115371942, \"returns_over_uniform_hodl\": -0.17439500115371953, \"sharpe\": 0.1849319663596603, \"sterling\": -1.2708266928427476, \"ulcer\": -0.1785097541696835}], \"train_return\": -0.2822491089339354, \"train_returns_over_hodl\": -0.17439500115371942, \"train_sharpe\": 0.16613143633972563, \"validation_return\": -0.3376393538481193, \"validation_returns_over_hodl\": -0.02990460843643905, \"validation_sharpe\": -2.2303660494436772}, {\"centeredness_margin\": 0.18073233216658666, \"continuous_test_metrics\": [{\"annualised_returns\": -0.48808814647997034, \"annualised_returns_over_hodl\": -0.006948044573293233, \"annualised_returns_over_uniform_hodl\": 0.8068219607690028, \"calmar\": -0.8419836289185112, \"daily_log_sharpe\": -0.7057690409902186, \"daily_returns\": 0.03101604173685896, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": -0.023533688864966117, \"return\": -0.23637189834282368, \"returns_over_hodl\": -0.0028040679991208073, \"returns_over_uniform_hodl\": 0.2690231159949612, \"sharpe\": -0.21850512654124912, \"sterling\": -1.2566055794215178, \"ulcer\": -0.1764103144163386}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -4.227985873244737, \"optuna_trial_number\": 249, \"price_ratio\": 6.387742589855961, \"shift_exponent\": 0.00027924390939249846, \"step\": 249, \"test_objective\": [{\"annualised_returns\": -0.48808814647997034, \"annualised_returns_over_hodl\": -0.006948044573293233, \"annualised_returns_over_uniform_hodl\": 0.8068219607690028, \"calmar\": -0.8419836289185112, \"daily_log_sharpe\": -0.7057690409902186, \"daily_returns\": 0.03101604173685896, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": -0.023533688864966117, \"return\": -0.23637189834282368, \"returns_over_hodl\": -0.0028040679991208073, \"returns_over_uniform_hodl\": 0.2690231159949612, \"sharpe\": -0.21850512654124912, \"sterling\": -1.2566055794215178, \"ulcer\": -0.1764103144163386}], \"train_objective\": [{\"annualised_returns\": -0.4445628088271517, \"annualised_returns_over_hodl\": -0.30051674133274964, \"annualised_returns_over_uniform_hodl\": -0.30051674133274975, \"calmar\": -0.5990175483620617, \"daily_log_sharpe\": -0.42627225237297, \"daily_returns\": 0.008194545631473172, \"fee_revenue_over_value\": 0.030370388400049877, \"jax_sharpe\": 0.1355510182044457, \"return\": -0.3002185582038327, \"returns_over_hodl\": -0.19506466151695034, \"returns_over_uniform_hodl\": -0.19506466151695045, \"sharpe\": 0.15158357300208122, \"sterling\": -1.3347642527593397, \"ulcer\": -0.17982649157153832}], \"train_return\": -0.3002185582038327, \"train_returns_over_hodl\": -0.19506466151695034, \"train_sharpe\": 0.1355510182044457, \"validation_return\": -0.3386345890540463, \"validation_returns_over_hodl\": -0.01886852258782712, \"validation_sharpe\": -2.239272922384672}, {\"centeredness_margin\": 0.03976409376134829, \"continuous_test_metrics\": [{\"annualised_returns\": -0.49362289658838276, \"annualised_returns_over_hodl\": -0.017684846011996602, \"annualised_returns_over_uniform_hodl\": 0.787286745918079, \"calmar\": -0.8515314481311904, \"daily_log_sharpe\": -0.7210541012225489, \"daily_returns\": 0.031016041810236254, \"fee_revenue_over_value\": 0.001688960758239626, \"jax_sharpe\": -0.037530653551136404, \"return\": -0.23970781533575836, \"returns_over_hodl\": -0.007160329591559322, \"returns_over_uniform_hodl\": 0.26347937583153924, \"sharpe\": -0.23551162710245105, \"sterling\": -1.2708550745545601, \"ulcer\": -0.1764103145680247}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -3.971006612978783, \"optuna_trial_number\": 250, \"price_ratio\": 7.149605487889128, \"shift_exponent\": 0.00014324836304079763, \"step\": 250, \"test_objective\": [{\"annualised_returns\": -0.49362289658838276, \"annualised_returns_over_hodl\": -0.017684846011996602, \"annualised_returns_over_uniform_hodl\": 0.787286745918079, \"calmar\": -0.8515314481311904, \"daily_log_sharpe\": -0.7210541012225489, \"daily_returns\": 0.031016041810236254, \"fee_revenue_over_value\": 0.001688960758239626, \"jax_sharpe\": -0.037530653551136404, \"return\": -0.23970781533575836, \"returns_over_hodl\": -0.007160329591559322, \"returns_over_uniform_hodl\": 0.26347937583153924, \"sharpe\": -0.23551162710245105, \"sterling\": -1.2708550745545601, \"ulcer\": -0.1764103145680247}], \"train_objective\": [{\"annualised_returns\": -0.408756308371103, \"annualised_returns_over_hodl\": -0.2554242483947441, \"annualised_returns_over_uniform_hodl\": -0.2554242483947441, \"calmar\": -0.5549654792786857, \"daily_log_sharpe\": -0.3790962580980466, \"daily_returns\": 0.008185282807616968, \"fee_revenue_over_value\": 0.044413518051316825, \"jax_sharpe\": 0.17022581248719348, \"return\": -0.27316710647586573, \"returns_over_hodl\": -0.16394827552474867, \"returns_over_uniform_hodl\": -0.16394827552474867, \"sharpe\": 0.1906929182325726, \"sterling\": -1.2419205425035675, \"ulcer\": -0.17730183120627993}], \"train_return\": -0.27316710647586573, \"train_returns_over_hodl\": -0.16394827552474867, \"train_sharpe\": 0.17022581248719348, \"validation_return\": -0.3356904180938757, \"validation_returns_over_hodl\": -0.042472316623524464, \"validation_sharpe\": -2.213515959492399}, {\"centeredness_margin\": 0.017706216802351203, \"continuous_test_metrics\": [{\"annualised_returns\": -0.48161722240920424, \"annualised_returns_over_hodl\": 0.005604827186222128, \"annualised_returns_over_uniform_hodl\": 0.8296614547895713, \"calmar\": -0.8308208469831355, \"daily_log_sharpe\": -0.6866158866598557, \"daily_returns\": 0.031016041734599938, \"fee_revenue_over_value\": 3.660974408135309e-05, \"jax_sharpe\": 0.006969411428269851, \"return\": -0.23249893041204084, \"returns_over_hodl\": 0.002253508961993056, \"returns_over_uniform_hodl\": 0.2754593456478571, \"sharpe\": -0.19594429747353445, \"sterling\": -1.2399458564269912, \"ulcer\": -0.17641031441166233}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -0.09330959155472696, \"optuna_trial_number\": 251, \"price_ratio\": 4.12187603888476, \"shift_exponent\": 0.00021073641290526109, \"step\": 251, \"test_objective\": [{\"annualised_returns\": -0.48161722240920424, \"annualised_returns_over_hodl\": 0.005604827186222128, \"annualised_returns_over_uniform_hodl\": 0.8296614547895713, \"calmar\": -0.8308208469831355, \"daily_log_sharpe\": -0.6866158866598557, \"daily_returns\": 0.031016041734599938, \"fee_revenue_over_value\": 3.660974408135309e-05, \"jax_sharpe\": 0.006969411428269851, \"return\": -0.23249893041204084, \"returns_over_hodl\": 0.002253508961993056, \"returns_over_uniform_hodl\": 0.2754593456478571, \"sharpe\": -0.19594429747353445, \"sterling\": -1.2399458564269912, \"ulcer\": -0.17641031441166233}], \"train_objective\": [{\"annualised_returns\": -0.48609228097919643, \"annualised_returns_over_hodl\": -0.352816391002055, \"annualised_returns_over_uniform_hodl\": -0.3528163910020553, \"calmar\": -0.6516770444937522, \"daily_log_sharpe\": -0.46076712282375637, \"daily_returns\": 0.008237388678064951, \"fee_revenue_over_value\": 0.042557042468179256, \"jax_sharpe\": 0.2077747838744452, \"return\": -0.33246785140671187, \"returns_over_hodl\": -0.23215995183140792, \"returns_over_uniform_hodl\": -0.23215995183140814, \"sharpe\": 0.15394692983058755, \"sterling\": -1.4077359013469277, \"ulcer\": -0.18719115101110295}], \"train_return\": -0.33246785140671187, \"train_returns_over_hodl\": -0.23215995183140792, \"train_sharpe\": 0.20777478387444517, \"validation_return\": -0.3391427128301444, \"validation_returns_over_hodl\": -2.740065396800162e-09, \"validation_sharpe\": -2.2438531808001523}, {\"centeredness_margin\": 0.02046492342819195, \"continuous_test_metrics\": [{\"annualised_returns\": -0.4769989995322025, \"annualised_returns_over_hodl\": 0.014563663126725412, \"annualised_returns_over_uniform_hodl\": 0.8459617347235397, \"calmar\": -0.8228541005073997, \"daily_log_sharpe\": -0.6821476717197203, \"daily_returns\": 0.031016041779246113, \"fee_revenue_over_value\": 0.0014013986525775954, \"jax_sharpe\": 0.004581897179361273, \"return\": -0.22975246442050656, \"returns_over_hodl\": 0.005840024628523954, \"returns_over_uniform_hodl\": 0.28002351611642373, \"sharpe\": -0.19215315231191427, \"sterling\": -1.228056006701553, \"ulcer\": -0.17641031450397782}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -0.08889581189010654, \"optuna_trial_number\": 252, \"price_ratio\": 5.462816671875719, \"shift_exponent\": 7.277001824872824e-05, \"step\": 252, \"test_objective\": [{\"annualised_returns\": -0.4769989995322025, \"annualised_returns_over_hodl\": 0.014563663126725412, \"annualised_returns_over_uniform_hodl\": 0.8459617347235397, \"calmar\": -0.8228541005073997, \"daily_log_sharpe\": -0.6821476717197203, \"daily_returns\": 0.031016041779246113, \"fee_revenue_over_value\": 0.0014013986525775954, \"jax_sharpe\": 0.004581897179361273, \"return\": -0.22975246442050656, \"returns_over_hodl\": 0.005840024628523954, \"returns_over_uniform_hodl\": 0.28002351611642373, \"sharpe\": -0.19215315231191427, \"sterling\": -1.228056006701553, \"ulcer\": -0.17641031450397782}], \"train_objective\": [{\"annualised_returns\": -0.43513314761763056, \"annualised_returns_over_hodl\": -0.2886416089941385, \"annualised_returns_over_uniform_hodl\": -0.2886416089941384, \"calmar\": -0.5891907802932747, \"daily_log_sharpe\": -0.40009735317826284, \"daily_returns\": 0.008225018528826776, \"fee_revenue_over_value\": 0.04800539011912585, \"jax_sharpe\": 0.21984940340259132, \"return\": -0.29302970438033127, \"returns_over_hodl\": -0.18679556185224278, \"returns_over_uniform_hodl\": -0.18679556185224266, \"sharpe\": 0.18842012410338663, \"sterling\": -1.2977181837926277, \"ulcer\": -0.18087850584445542}], \"train_return\": -0.29302970438033127, \"train_returns_over_hodl\": -0.18679556185224278, \"train_sharpe\": 0.21984940340259132, \"validation_return\": -0.33914271295700016, \"validation_returns_over_hodl\": -2.615463179367339e-09, \"validation_sharpe\": -2.2438531789732536}, {\"centeredness_margin\": 0.11245337197225819, \"continuous_test_metrics\": [{\"annualised_returns\": -0.4836034218371805, \"annualised_returns_over_hodl\": 0.0017518260290552146, \"annualised_returns_over_uniform_hodl\": 0.8226510511033553, \"calmar\": -0.834247175059581, \"daily_log_sharpe\": -0.6905858056532452, \"daily_returns\": 0.031016041626628077, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": 0.006087396114823659, \"return\": -0.23368462094691111, \"returns_over_hodl\": 0.0007051578498151745, \"returns_over_uniform_hodl\": 0.2734889248449801, \"sharpe\": -0.2000922923938534, \"sterling\": -1.2450594294980075, \"ulcer\": -0.1764103141884559}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -0.11312809037292948, \"optuna_trial_number\": 253, \"price_ratio\": 3.425798375504521, \"shift_exponent\": 0.0004248504168693792, \"step\": 253, \"test_objective\": [{\"annualised_returns\": -0.4836034218371805, \"annualised_returns_over_hodl\": 0.0017518260290552146, \"annualised_returns_over_uniform_hodl\": 0.8226510511033553, \"calmar\": -0.834247175059581, \"daily_log_sharpe\": -0.6905858056532452, \"daily_returns\": 0.031016041626628077, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": 0.006087396114823659, \"return\": -0.23368462094691111, \"returns_over_hodl\": 0.0007051578498151745, \"returns_over_uniform_hodl\": 0.2734889248449801, \"sharpe\": -0.2000922923938534, \"sterling\": -1.2450594294980075, \"ulcer\": -0.1764103141884559}], \"train_objective\": [{\"annualised_returns\": -0.5284324730978028, \"annualised_returns_over_hodl\": -0.40613701127449897, \"annualised_returns_over_uniform_hodl\": -0.40613701127449897, \"calmar\": -0.697760186474923, \"daily_log_sharpe\": -0.5186752001771261, \"daily_returns\": 0.008373033063428818, \"fee_revenue_over_value\": 0.02814632059823421, \"jax_sharpe\": 0.13570643944197025, \"return\": -0.3664199191899463, \"returns_over_hodl\": -0.2712138871617733, \"returns_over_uniform_hodl\": -0.2712138871617733, \"sharpe\": 0.11260938400419794, \"sterling\": -1.4936116128577608, \"ulcer\": -0.19305686160435961}], \"train_return\": -0.3664199191899463, \"train_returns_over_hodl\": -0.2712138871617733, \"train_sharpe\": 0.13570643944197025, \"validation_return\": -0.3391427120283571, \"validation_returns_over_hodl\": -3.3307009372762764e-09, \"validation_sharpe\": -2.2438531802642014}, {\"centeredness_margin\": 0.14565911548933136, \"continuous_test_metrics\": [{\"annualised_returns\": -0.4797732738977136, \"annualised_returns_over_hodl\": 0.009181886021655261, \"annualised_returns_over_uniform_hodl\": 0.8361697757869844, \"calmar\": -0.8276399108759516, \"daily_log_sharpe\": -0.687480637153435, \"daily_returns\": 0.031016041633043525, \"fee_revenue_over_value\": 4.385144122607438e-05, \"jax_sharpe\": -0.0051120224167091576, \"return\": -0.23140058503876038, \"returns_over_hodl\": 0.0036878024570927703, \"returns_over_uniform_hodl\": 0.27728461329452236, \"sharpe\": -0.19837066410144688, \"sterling\": -1.2351985190532058, \"ulcer\": -0.17641031420171976}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -0.1024703001668189, \"optuna_trial_number\": 254, \"price_ratio\": 3.2482071086296904, \"shift_exponent\": 0.001384172746835277, \"step\": 254, \"test_objective\": [{\"annualised_returns\": -0.4797732738977136, \"annualised_returns_over_hodl\": 0.009181886021655261, \"annualised_returns_over_uniform_hodl\": 0.8361697757869844, \"calmar\": -0.8276399108759516, \"daily_log_sharpe\": -0.687480637153435, \"daily_returns\": 0.031016041633043525, \"fee_revenue_over_value\": 4.385144122607438e-05, \"jax_sharpe\": -0.0051120224167091576, \"return\": -0.23140058503876038, \"returns_over_hodl\": 0.0036878024570927703, \"returns_over_uniform_hodl\": 0.27728461329452236, \"sharpe\": -0.19837066410144688, \"sterling\": -1.2351985190532058, \"ulcer\": -0.17641031420171976}], \"train_objective\": [{\"annualised_returns\": -0.5242163003244146, \"annualised_returns_over_hodl\": -0.4008274239485108, \"annualised_returns_over_uniform_hodl\": -0.4008274239485111, \"calmar\": -0.6910901953415971, \"daily_log_sharpe\": -0.5143467926996536, \"daily_returns\": 0.008371063442581386, \"fee_revenue_over_value\": 0.028165788875188197, \"jax_sharpe\": 0.17132863006422205, \"return\": -0.36298678104228244, \"returns_over_hodl\": -0.26726486243505887, \"returns_over_uniform_hodl\": -0.2672648624350591, \"sharpe\": 0.11355903209729801, \"sterling\": -1.4915595194924827, \"ulcer\": -0.19126582613433443}], \"train_return\": -0.36298678104228244, \"train_returns_over_hodl\": -0.26726486243505887, \"train_sharpe\": 0.17132863006422203, \"validation_return\": -0.33914271189009004, \"validation_returns_over_hodl\": -3.0143475493460414e-09, \"validation_sharpe\": -2.243853178437991}, {\"centeredness_margin\": 0.02256185486224602, \"continuous_test_metrics\": [{\"annualised_returns\": -0.48711048936607837, \"annualised_returns_over_hodl\": -0.00505149928500459, \"annualised_returns_over_uniform_hodl\": 0.8102726570780172, \"calmar\": -0.8402971042302653, \"daily_log_sharpe\": -0.7074460141500579, \"daily_returns\": 0.031016041751031707, \"fee_revenue_over_value\": 0.002266236853329618, \"jax_sharpe\": -0.026140554693561528, \"return\": -0.23578488470741388, \"returns_over_hodl\": -0.002037507116538717, \"returns_over_uniform_hodl\": 0.26999863519222855, \"sharpe\": -0.22097477069858554, \"sterling\": -1.2540885511769613, \"ulcer\": -0.1764103144456318}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -0.08405039158331873, \"optuna_trial_number\": 255, \"price_ratio\": 4.5006319430753035, \"shift_exponent\": 0.001357678240690951, \"step\": 255, \"test_objective\": [{\"annualised_returns\": -0.48711048936607837, \"annualised_returns_over_hodl\": -0.00505149928500459, \"annualised_returns_over_uniform_hodl\": 0.8102726570780172, \"calmar\": -0.8402971042302653, \"daily_log_sharpe\": -0.7074460141500579, \"daily_returns\": 0.031016041751031707, \"fee_revenue_over_value\": 0.002266236853329618, \"jax_sharpe\": -0.026140554693561528, \"return\": -0.23578488470741388, \"returns_over_hodl\": -0.002037507116538717, \"returns_over_uniform_hodl\": 0.26999863519222855, \"sharpe\": -0.22097477069858554, \"sterling\": -1.2540885511769613, \"ulcer\": -0.1764103144456318}], \"train_objective\": [{\"annualised_returns\": -0.4652666118868354, \"annualised_returns_over_hodl\": -0.3265898308938082, \"annualised_returns_over_uniform_hodl\": -0.3265898308938082, \"calmar\": -0.6268108711189093, \"daily_log_sharpe\": -0.4348342741947091, \"daily_returns\": 0.008257495674870538, \"fee_revenue_over_value\": 0.04629996630390366, \"jax_sharpe\": 0.21833725034931742, \"return\": -0.31617284606744933, \"returns_over_hodl\": -0.21341634867914994, \"returns_over_uniform_hodl\": -0.21341634867914994, \"sharpe\": 0.16985327607160286, \"sterling\": -1.363817837947956, \"ulcer\": -0.18442183359195052}], \"train_return\": -0.31617284606744933, \"train_returns_over_hodl\": -0.21341634867914994, \"train_sharpe\": 0.2183372503493174, \"validation_return\": -0.33914271280555464, \"validation_returns_over_hodl\": -2.467096082980902e-09, \"validation_sharpe\": -2.243853179413395}, {\"centeredness_margin\": 0.01507546195896283, \"continuous_test_metrics\": [{\"annualised_returns\": -0.4843249525418918, \"annualised_returns_over_hodl\": 0.0003521344963173245, \"annualised_returns_over_uniform_hodl\": 0.8201043675021122, \"calmar\": -0.8354918644919029, \"daily_log_sharpe\": -0.6920838857070495, \"daily_returns\": 0.031016041684294494, \"fee_revenue_over_value\": 6.067394520522111e-07, \"jax_sharpe\": 0.005266239897187892, \"return\": -0.23411602352101957, \"returns_over_hodl\": 0.00014180297007193587, \"returns_over_uniform_hodl\": 0.2727720053947198, \"sharpe\": -0.201698996575159, \"sterling\": -1.2469170460286063, \"ulcer\": -0.17641031430766885}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -0.1071544854779114, \"optuna_trial_number\": 256, \"price_ratio\": 3.6381212314573705, \"shift_exponent\": 1.5759917614968584e-05, \"step\": 256, \"test_objective\": [{\"annualised_returns\": -0.4843249525418918, \"annualised_returns_over_hodl\": 0.0003521344963173245, \"annualised_returns_over_uniform_hodl\": 0.8201043675021122, \"calmar\": -0.8354918644919029, \"daily_log_sharpe\": -0.6920838857070495, \"daily_returns\": 0.031016041684294494, \"fee_revenue_over_value\": 6.067394520522111e-07, \"jax_sharpe\": 0.005266239897187892, \"return\": -0.23411602352101957, \"returns_over_hodl\": 0.00014180297007193587, \"returns_over_uniform_hodl\": 0.2727720053947198, \"sharpe\": -0.201698996575159, \"sterling\": -1.2469170460286063, \"ulcer\": -0.17641031430766885}], \"train_objective\": [{\"annualised_returns\": -0.5027134908502371, \"annualised_returns_over_hodl\": -0.37374811510759787, \"annualised_returns_over_uniform_hodl\": -0.37374811510759787, \"calmar\": -0.6703682191421317, \"daily_log_sharpe\": -0.4788729457231685, \"daily_returns\": 0.008320686220306227, \"fee_revenue_over_value\": 0.04130837045695459, \"jax_sharpe\": 0.16302746495648374, \"return\": -0.3456600661043957, \"returns_over_hodl\": -0.24733451801562278, \"returns_over_uniform_hodl\": -0.24733451801562278, \"sharpe\": 0.1478482924014575, \"sterling\": -1.4318820203575318, \"ulcer\": -0.1908761878659665}], \"train_return\": -0.3456600661043957, \"train_returns_over_hodl\": -0.24733451801562278, \"train_sharpe\": 0.16302746495648374, \"validation_return\": -0.3391427125462432, \"validation_returns_over_hodl\": -3.1514810761024137e-09, \"validation_sharpe\": -2.243853181455954}, {\"centeredness_margin\": 0.04275731904120194, \"continuous_test_metrics\": [{\"annualised_returns\": -0.4825971794813786, \"annualised_returns_over_hodl\": 0.003703817272786347, \"annualised_returns_over_uniform_hodl\": 0.8262026406471754, \"calmar\": -0.8325113393567188, \"daily_log_sharpe\": -0.6947746822760208, \"daily_returns\": 0.03101604179279143, \"fee_revenue_over_value\": 0.0010311171340441046, \"jax_sharpe\": -0.010560107439880355, \"return\": -0.23308359023085967, \"returns_over_hodl\": 0.001490020656742752, \"returns_over_uniform_hodl\": 0.27448773810294846, \"sharpe\": -0.20642552727343527, \"sterling\": -1.2424688047241346, \"ulcer\": -0.1764103145319626}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -4.231964126535585, \"optuna_trial_number\": 257, \"price_ratio\": 6.203698884167288, \"shift_exponent\": 3.817323566814747e-05, \"step\": 257, \"test_objective\": [{\"annualised_returns\": -0.4825971794813786, \"annualised_returns_over_hodl\": 0.003703817272786347, \"annualised_returns_over_uniform_hodl\": 0.8262026406471754, \"calmar\": -0.8325113393567188, \"daily_log_sharpe\": -0.6947746822760208, \"daily_returns\": 0.03101604179279143, \"fee_revenue_over_value\": 0.0010311171340441046, \"jax_sharpe\": -0.010560107439880355, \"return\": -0.23308359023085967, \"returns_over_hodl\": 0.001490020656742752, \"returns_over_uniform_hodl\": 0.27448773810294846, \"sharpe\": -0.20642552727343527, \"sterling\": -1.2424688047241346, \"ulcer\": -0.1764103145319626}], \"train_objective\": [{\"annualised_returns\": -0.42267826704165334, \"annualised_returns_over_hodl\": -0.2729567024195535, \"annualised_returns_over_uniform_hodl\": -0.2729567024195535, \"calmar\": -0.5730740782548125, \"daily_log_sharpe\": -0.39031766257031547, \"daily_returns\": 0.008206281474897738, \"fee_revenue_over_value\": 0.04568226631889138, \"jax_sharpe\": 0.17001577316061853, \"return\": -0.2836063808271697, \"returns_over_hodl\": -0.17595622590982307, \"returns_over_uniform_hodl\": -0.17595622590982307, \"sharpe\": 0.18884775426770142, \"sterling\": -1.272398222975733, \"ulcer\": -0.1790623114818349}], \"train_return\": -0.2836063808271697, \"train_returns_over_hodl\": -0.17595622590982307, \"train_sharpe\": 0.17001577316061856, \"validation_return\": -0.3386128761733256, \"validation_returns_over_hodl\": -0.020071304901885023, \"validation_sharpe\": -2.239075136251583}, {\"centeredness_margin\": 0.10442094016919638, \"continuous_test_metrics\": [{\"annualised_returns\": -0.4788616039337533, \"annualised_returns_over_hodl\": 0.010950422857627151, \"annualised_returns_over_uniform_hodl\": 0.8393875667026083, \"calmar\": -0.8260672186540599, \"daily_log_sharpe\": -0.6818180929011785, \"daily_returns\": 0.03101604166494564, \"fee_revenue_over_value\": 3.7527036264491933e-07, \"jax_sharpe\": 0.0070600852978132565, \"return\": -0.2308584089929674, \"returns_over_hodl\": 0.004395811165036401, \"returns_over_uniform_hodl\": 0.2781856198624537, \"sharpe\": -0.19107632881092113, \"sterling\": -1.2328513779776251, \"ulcer\": -0.17641031426767542}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -0.09939097307627587, \"optuna_trial_number\": 258, \"price_ratio\": 4.017683925409362, \"shift_exponent\": 0.0006505622753307497, \"step\": 258, \"test_objective\": [{\"annualised_returns\": -0.4788616039337533, \"annualised_returns_over_hodl\": 0.010950422857627151, \"annualised_returns_over_uniform_hodl\": 0.8393875667026083, \"calmar\": -0.8260672186540599, \"daily_log_sharpe\": -0.6818180929011785, \"daily_returns\": 0.03101604166494564, \"fee_revenue_over_value\": 3.7527036264491933e-07, \"jax_sharpe\": 0.0070600852978132565, \"return\": -0.2308584089929674, \"returns_over_hodl\": 0.004395811165036401, \"returns_over_uniform_hodl\": 0.2781856198624537, \"sharpe\": -0.19107632881092113, \"sterling\": -1.2328513779776251, \"ulcer\": -0.17641031426767542}], \"train_objective\": [{\"annualised_returns\": -0.5013114775953006, \"annualised_returns_over_hodl\": -0.37198250629377916, \"annualised_returns_over_uniform_hodl\": -0.37198250629377916, \"calmar\": -0.6677655868186827, \"daily_log_sharpe\": -0.4848467492373152, \"daily_returns\": 0.008300201352053864, \"fee_revenue_over_value\": 0.032201495885549554, \"jax_sharpe\": 0.13796638576433776, \"return\": -0.34454066914596615, \"returns_over_hodl\": -0.2460469128923467, \"returns_over_uniform_hodl\": -0.2460469128923467, \"sharpe\": 0.131149848339109, \"sterling\": -1.4456681698473592, \"ulcer\": -0.18826381374226106}], \"train_return\": -0.34454066914596615, \"train_returns_over_hodl\": -0.2460469128923467, \"train_sharpe\": 0.13796638576433773, \"validation_return\": -0.3391427121811982, \"validation_returns_over_hodl\": -2.922136754790472e-09, \"validation_sharpe\": -2.2438531791309932}, {\"centeredness_margin\": 0.015087343447872322, \"continuous_test_metrics\": [{\"annualised_returns\": -0.4896974764883031, \"annualised_returns_over_hodl\": -0.010069968336906432, \"annualised_returns_over_uniform_hodl\": 0.801141739103524, \"calmar\": -0.844759832379811, \"daily_log_sharpe\": -0.7122874929371397, \"daily_returns\": 0.03101604180780003, \"fee_revenue_over_value\": 0.0020756234720928895, \"jax_sharpe\": -0.028763393704319986, \"return\": -0.23733964674511276, \"returns_over_hodl\": -0.004067818842702797, \"returns_over_uniform_hodl\": 0.2674148788304216, \"sharpe\": -0.22600322309959306, \"sterling\": -1.2607488803357985, \"ulcer\": -0.17641031456299416}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -3.99512172533863, \"optuna_trial_number\": 259, \"price_ratio\": 6.93212181456604, \"shift_exponent\": 0.0001011787066971035, \"step\": 259, \"test_objective\": [{\"annualised_returns\": -0.4896974764883031, \"annualised_returns_over_hodl\": -0.010069968336906432, \"annualised_returns_over_uniform_hodl\": 0.801141739103524, \"calmar\": -0.844759832379811, \"daily_log_sharpe\": -0.7122874929371397, \"daily_returns\": 0.03101604180780003, \"fee_revenue_over_value\": 0.0020756234720928895, \"jax_sharpe\": -0.028763393704319986, \"return\": -0.23733964674511276, \"returns_over_hodl\": -0.004067818842702797, \"returns_over_uniform_hodl\": 0.2674148788304216, \"sharpe\": -0.22600322309959306, \"sterling\": -1.2607488803357985, \"ulcer\": -0.17641031456299416}], \"train_objective\": [{\"annualised_returns\": -0.4098767485681234, \"annualised_returns_over_hodl\": -0.25683526150767333, \"annualised_returns_over_uniform_hodl\": -0.25683526150767355, \"calmar\": -0.5563891797102545, \"daily_log_sharpe\": -0.37880187342912275, \"daily_returns\": 0.008197586346465635, \"fee_revenue_over_value\": 0.04548494296564, \"jax_sharpe\": 0.1723769068856912, \"return\": -0.27400366060972714, \"returns_over_hodl\": -0.16491053594590344, \"returns_over_uniform_hodl\": -0.16491053594590355, \"sharpe\": 0.19292145395508306, \"sterling\": -1.2430315470144742, \"ulcer\": -0.1776369571477162}], \"train_return\": -0.27400366060972714, \"train_returns_over_hodl\": -0.16491053594590344, \"train_sharpe\": 0.1723769068856912, \"validation_return\": -0.33631249033006017, \"validation_returns_over_hodl\": -0.03795423052523661, \"validation_sharpe\": -2.2187419085316975}, {\"centeredness_margin\": 0.0426859038935182, \"continuous_test_metrics\": [{\"annualised_returns\": -0.4776488654433225, \"annualised_returns_over_hodl\": 0.013302996593457905, \"annualised_returns_over_uniform_hodl\": 0.843667996081441, \"calmar\": -0.8239751630492734, \"daily_log_sharpe\": -0.6801809932551698, \"daily_returns\": 0.03101604175702455, \"fee_revenue_over_value\": 0.0002817295847168282, \"jax_sharpe\": 0.006885389502615411, \"return\": -0.23013806280308713, \"returns_over_hodl\": 0.005336485200532426, \"returns_over_uniform_hodl\": 0.2793827156273856, \"sharpe\": -0.18957314515288093, \"sterling\": -1.229729119774291, \"ulcer\": -0.1764103144580194}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -0.08203332602190583, \"optuna_trial_number\": 260, \"price_ratio\": 4.87672914498849, \"shift_exponent\": 0.00023002276634380726, \"step\": 260, \"test_objective\": [{\"annualised_returns\": -0.4776488654433225, \"annualised_returns_over_hodl\": 0.013302996593457905, \"annualised_returns_over_uniform_hodl\": 0.843667996081441, \"calmar\": -0.8239751630492734, \"daily_log_sharpe\": -0.6801809932551698, \"daily_returns\": 0.03101604175702455, \"fee_revenue_over_value\": 0.0002817295847168282, \"jax_sharpe\": 0.006885389502615411, \"return\": -0.23013806280308713, \"returns_over_hodl\": 0.005336485200532426, \"returns_over_uniform_hodl\": 0.2793827156273856, \"sharpe\": -0.18957314515288093, \"sterling\": -1.229729119774291, \"ulcer\": -0.1764103144580194}], \"train_objective\": [{\"annualised_returns\": -0.45908888774094425, \"annualised_returns_over_hodl\": -0.31880998704217356, \"annualised_returns_over_uniform_hodl\": -0.3188099870421738, \"calmar\": -0.619587759673522, \"daily_log_sharpe\": -0.42952938355178444, \"daily_returns\": 0.008234661260770541, \"fee_revenue_over_value\": 0.04384154214895012, \"jax_sharpe\": 0.21167662563858328, \"return\": -0.31138729871624693, \"returns_over_hodl\": -0.20791169258669984, \"returns_over_uniform_hodl\": -0.20791169258669995, \"sharpe\": 0.16899548511029355, \"sterling\": -1.3540390273650558, \"ulcer\": -0.18317609812816943}], \"train_return\": -0.31138729871624693, \"train_returns_over_hodl\": -0.20791169258669984, \"train_sharpe\": 0.21167662563858328, \"validation_return\": -0.33914271285674813, \"validation_returns_over_hodl\": -2.4069666260118083e-09, \"validation_sharpe\": -2.2438531795103356}, {\"centeredness_margin\": 0.7415224274994406, \"continuous_test_metrics\": [{\"annualised_returns\": -0.5966998289183812, \"annualised_returns_over_hodl\": 0.38070054228533734, \"annualised_returns_over_uniform_hodl\": 0.4234708590580687, \"calmar\": -1.1040287630223173, \"daily_log_sharpe\": -1.1978889657057166, \"daily_returns\": 0.019076060836155068, \"fee_revenue_over_value\": 0.15051531421178377, \"jax_sharpe\": -0.6089541761591335, \"return\": -0.30630008273723974, \"returns_over_hodl\": 0.1387368264653226, \"returns_over_uniform_hodl\": 0.1528140840545531, \"sharpe\": -0.814328388120275, \"sterling\": -1.773825664603141, \"ulcer\": -0.14502931058815594}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -2.247319932132292, \"optuna_trial_number\": 261, \"price_ratio\": 2.07427607904257, \"shift_exponent\": 0.4620935118819347, \"step\": 261, \"test_objective\": [{\"annualised_returns\": -0.5966998289183812, \"annualised_returns_over_hodl\": 0.38070054228533734, \"annualised_returns_over_uniform_hodl\": 0.4234708590580687, \"calmar\": -1.1040287630223173, \"daily_log_sharpe\": -1.1978889657057166, \"daily_returns\": 0.019076060836155068, \"fee_revenue_over_value\": 0.15051531421178377, \"jax_sharpe\": -0.6089541761591335, \"return\": -0.30630008273723974, \"returns_over_hodl\": 0.1387368264653226, \"returns_over_uniform_hodl\": 0.1528140840545531, \"sharpe\": -0.814328388120275, \"sterling\": -1.773825664603141, \"ulcer\": -0.14502931058815594}], \"train_objective\": [{\"annualised_returns\": -0.4806911900275691, \"annualised_returns_over_hodl\": -0.3460145909799408, \"annualised_returns_over_uniform_hodl\": -0.34601459097994114, \"calmar\": -0.6336518365342219, \"daily_log_sharpe\": -0.6140207338974759, \"daily_returns\": 0.008657969727244813, \"fee_revenue_over_value\": 0.07496212639860803, \"jax_sharpe\": -0.219607088710353, \"return\": -0.3282172451676144, \"returns_over_hodl\": -0.2272706207239067, \"returns_over_uniform_hodl\": -0.2272706207239069, \"sharpe\": -0.13529655814653094, \"sterling\": -1.5495789044133688, \"ulcer\": -0.16714422906359208}], \"train_return\": -0.3282172451676144, \"train_returns_over_hodl\": -0.2272706207239067, \"train_sharpe\": -0.21960708871035298, \"validation_return\": -0.18084681839788552, \"validation_returns_over_hodl\": -0.024959890863479384, \"validation_sharpe\": -1.4864925383891834}, {\"centeredness_margin\": 0.020165196989007296, \"continuous_test_metrics\": [{\"annualised_returns\": -0.47769765224733063, \"annualised_returns_over_hodl\": 0.013208355135096106, \"annualised_returns_over_uniform_hodl\": 0.8434958002858723, \"calmar\": -0.8240593236128182, \"daily_log_sharpe\": -0.6803036569071411, \"daily_returns\": 0.03101604176897809, \"fee_revenue_over_value\": 0.00047546936455876746, \"jax_sharpe\": 0.00696834395160903, \"return\": -0.2301670220461982, \"returns_over_hodl\": 0.0052986681134796765, \"returns_over_uniform_hodl\": 0.27933459017358153, \"sharpe\": -0.18971764168391822, \"sterling\": -1.2298547236809234, \"ulcer\": -0.1764103144827218}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -0.07919327228426831, \"optuna_trial_number\": 262, \"price_ratio\": 5.068161502257739, \"shift_exponent\": 0.0001046337444345997, \"step\": 262, \"test_objective\": [{\"annualised_returns\": -0.47769765224733063, \"annualised_returns_over_hodl\": 0.013208355135096106, \"annualised_returns_over_uniform_hodl\": 0.8434958002858723, \"calmar\": -0.8240593236128182, \"daily_log_sharpe\": -0.6803036569071411, \"daily_returns\": 0.03101604176897809, \"fee_revenue_over_value\": 0.00047546936455876746, \"jax_sharpe\": 0.00696834395160903, \"return\": -0.2301670220461982, \"returns_over_hodl\": 0.0052986681134796765, \"returns_over_uniform_hodl\": 0.27933459017358153, \"sharpe\": -0.18971764168391822, \"sterling\": -1.2298547236809234, \"ulcer\": -0.1764103144827218}], \"train_objective\": [{\"annualised_returns\": -0.44726199347202333, \"annualised_returns_over_hodl\": -0.3039159275974517, \"annualised_returns_over_uniform_hodl\": -0.3039159275974517, \"calmar\": -0.60468018480169, \"daily_log_sharpe\": -0.41385063781467696, \"daily_returns\": 0.008211839425502875, \"fee_revenue_over_value\": 0.047372654058632634, \"jax_sharpe\": 0.16948907791665008, \"return\": -0.30228513196720597, \"returns_over_hodl\": -0.19744177264389218, \"returns_over_uniform_hodl\": -0.19744177264389218, \"sharpe\": 0.18089674785028145, \"sterling\": -1.3252463981089047, \"ulcer\": -0.1822135075666958}], \"train_return\": -0.30228513196720597, \"train_returns_over_hodl\": -0.19744177264389218, \"train_sharpe\": 0.16948907791665005, \"validation_return\": -0.3391427129331531, \"validation_returns_over_hodl\": -2.3229920209644206e-09, \"validation_sharpe\": -2.243853179439735}, {\"centeredness_margin\": 0.020664807751334082, \"continuous_test_metrics\": [{\"annualised_returns\": -0.47991607674814496, \"annualised_returns_over_hodl\": 0.008904859902723494, \"annualised_returns_over_uniform_hodl\": 0.8356657450159723, \"calmar\": -0.8278862557250491, \"daily_log_sharpe\": -0.6833583555584227, \"daily_returns\": 0.031016041742217674, \"fee_revenue_over_value\": 0.0003178354111000054, \"jax_sharpe\": 0.007810067663205223, \"return\": -0.2314855622138151, \"returns_over_hodl\": 0.0035768318552535927, \"returns_over_uniform_hodl\": 0.277143395338763, \"sharpe\": -0.19265013134766318, \"sterling\": -1.2355661704687482, \"ulcer\": -0.17641031442742267}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -0.0895839308312435, \"optuna_trial_number\": 263, \"price_ratio\": 4.36250924223491, \"shift_exponent\": 0.00023045693045733274, \"step\": 263, \"test_objective\": [{\"annualised_returns\": -0.47991607674814496, \"annualised_returns_over_hodl\": 0.008904859902723494, \"annualised_returns_over_uniform_hodl\": 0.8356657450159723, \"calmar\": -0.8278862557250491, \"daily_log_sharpe\": -0.6833583555584227, \"daily_returns\": 0.031016041742217674, \"fee_revenue_over_value\": 0.0003178354111000054, \"jax_sharpe\": 0.007810067663205223, \"return\": -0.2314855622138151, \"returns_over_hodl\": 0.0035768318552535927, \"returns_over_uniform_hodl\": 0.277143395338763, \"sharpe\": -0.19265013134766318, \"sterling\": -1.2355661704687482, \"ulcer\": -0.17641031442742267}], \"train_objective\": [{\"annualised_returns\": -0.47345603464395813, \"annualised_returns_over_hodl\": -0.336903083603192, \"annualised_returns_over_uniform_hodl\": -0.3369030836031921, \"calmar\": -0.6369998855705056, \"daily_log_sharpe\": -0.4446254455926365, \"daily_returns\": 0.008277168380942243, \"fee_revenue_over_value\": 0.044907321763005166, \"jax_sharpe\": 0.1635587956704238, \"return\": -0.32255036573704476, \"returns_over_hodl\": -0.22075219762757536, \"returns_over_uniform_hodl\": -0.22075219762757547, \"sharpe\": 0.16447690930716502, \"sterling\": -1.381024466212565, \"ulcer\": -0.18559458973595136}], \"train_return\": -0.32255036573704476, \"train_returns_over_hodl\": -0.22075219762757536, \"train_sharpe\": 0.1635587956704238, \"validation_return\": -0.33914271284068553, \"validation_returns_over_hodl\": -2.629859885416863e-09, \"validation_sharpe\": -2.2438531803709307}, {\"centeredness_margin\": 0.017833242612901777, \"continuous_test_metrics\": [{\"annualised_returns\": -0.4813318337169449, \"annualised_returns_over_hodl\": 0.006158452162197792, \"annualised_returns_over_uniform_hodl\": 0.8306687503874051, \"calmar\": -0.8303285320215267, \"daily_log_sharpe\": -0.6860412107724728, \"daily_returns\": 0.031016041668602654, \"fee_revenue_over_value\": 6.0693002900352596e-05, \"jax_sharpe\": 0.008222597206234635, \"return\": -0.23232878657649025, \"returns_over_hodl\": 0.002475695090982999, \"returns_over_uniform_hodl\": 0.27574209645271797, \"sharpe\": -0.19534093376360637, \"sterling\": -1.2392111098224556, \"ulcer\": -0.17641031427523504}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -0.10658078919592229, \"optuna_trial_number\": 264, \"price_ratio\": 3.5215452561012452, \"shift_exponent\": 0.0006836177344488509, \"step\": 264, \"test_objective\": [{\"annualised_returns\": -0.4813318337169449, \"annualised_returns_over_hodl\": 0.006158452162197792, \"annualised_returns_over_uniform_hodl\": 0.8306687503874051, \"calmar\": -0.8303285320215267, \"daily_log_sharpe\": -0.6860412107724728, \"daily_returns\": 0.031016041668602654, \"fee_revenue_over_value\": 6.0693002900352596e-05, \"jax_sharpe\": 0.008222597206234635, \"return\": -0.23232878657649025, \"returns_over_hodl\": 0.002475695090982999, \"returns_over_uniform_hodl\": 0.27574209645271797, \"sharpe\": -0.19534093376360637, \"sterling\": -1.2392111098224556, \"ulcer\": -0.17641031427523504}], \"train_objective\": [{\"annualised_returns\": -0.5060505952987178, \"annualised_returns_over_hodl\": -0.3779506581335832, \"annualised_returns_over_uniform_hodl\": -0.37795065813358353, \"calmar\": -0.6736068521008197, \"daily_log_sharpe\": -0.4834609799126753, \"daily_returns\": 0.008291117340271442, \"fee_revenue_over_value\": 0.041088388613396434, \"jax_sharpe\": 0.1611356913448333, \"return\": -0.34832948045492784, \"returns_over_hodl\": -0.25040505663734147, \"returns_over_uniform_hodl\": -0.2504050566373417, \"sharpe\": 0.14446324261294605, \"sterling\": -1.4394818182933462, \"ulcer\": -0.19122010552242752}], \"train_return\": -0.34832948045492784, \"train_returns_over_hodl\": -0.25040505663734147, \"train_sharpe\": 0.1611356913448333, \"validation_return\": -0.33914271236218974, \"validation_returns_over_hodl\": -3.1349480789089057e-09, \"validation_sharpe\": -2.243853180698072}, {\"centeredness_margin\": 0.01844422602699046, \"continuous_test_metrics\": [{\"annualised_returns\": -0.49816590266484706, \"annualised_returns_over_hodl\": -0.026497771585496377, \"annualised_returns_over_uniform_hodl\": 0.7712519479535089, \"calmar\": -0.8593684418577072, \"daily_log_sharpe\": -0.7314920087918004, \"daily_returns\": 0.031016041810775753, \"fee_revenue_over_value\": 0.0020420584554966077, \"jax_sharpe\": -0.04821458634896337, \"return\": -0.24246229633857597, \"returns_over_hodl\": -0.010757312531613827, \"returns_over_uniform_hodl\": 0.2589018857449923, \"sharpe\": -0.24690400818737882, \"sterling\": -1.2825512758620674, \"ulcer\": -0.17641031456914524}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -3.946445256860598, \"optuna_trial_number\": 265, \"price_ratio\": 7.145594107839213, \"shift_exponent\": 0.0003661342738259562, \"step\": 265, \"test_objective\": [{\"annualised_returns\": -0.49816590266484706, \"annualised_returns_over_hodl\": -0.026497771585496377, \"annualised_returns_over_uniform_hodl\": 0.7712519479535089, \"calmar\": -0.8593684418577072, \"daily_log_sharpe\": -0.7314920087918004, \"daily_returns\": 0.031016041810775753, \"fee_revenue_over_value\": 0.0020420584554966077, \"jax_sharpe\": -0.04821458634896337, \"return\": -0.24246229633857597, \"returns_over_hodl\": -0.010757312531613827, \"returns_over_uniform_hodl\": 0.2589018857449923, \"sharpe\": -0.24690400818737882, \"sterling\": -1.2825512758620674, \"ulcer\": -0.17641031456914524}], \"train_objective\": [{\"annualised_returns\": -0.40776065200453193, \"annualised_returns_over_hodl\": -0.2541703803231239, \"annualised_returns_over_uniform_hodl\": -0.2541703803231241, \"calmar\": -0.5536493049335747, \"daily_log_sharpe\": -0.3776183653330294, \"daily_returns\": 0.008151939811458196, \"fee_revenue_over_value\": 0.04502664884416956, \"jax_sharpe\": 0.21196970380669072, \"return\": -0.27242424218730643, \"returns_over_hodl\": -0.16309378341929404, \"returns_over_uniform_hodl\": -0.16309378341929415, \"sharpe\": 0.1921950910256238, \"sterling\": -1.238917060581717, \"ulcer\": -0.17729264387186439}], \"train_return\": -0.27242424218730643, \"train_returns_over_hodl\": -0.16309378341929404, \"train_sharpe\": 0.21196970380669072, \"validation_return\": -0.3355331776416801, \"validation_returns_over_hodl\": -0.041961582587748736, \"validation_sharpe\": -2.21193493807281}, {\"centeredness_margin\": 0.011880472563760125, \"continuous_test_metrics\": [{\"annualised_returns\": -0.48249254548143183, \"annualised_returns_over_hodl\": 0.0039067956977707485, \"annualised_returns_over_uniform_hodl\": 0.8265719522926216, \"calmar\": -0.8323308386218674, \"daily_log_sharpe\": -0.6959975747064535, \"daily_returns\": 0.031016041789753753, \"fee_revenue_over_value\": 0.001972990717110058, \"jax_sharpe\": -0.012361689739964923, \"return\": -0.23302113220412346, \"returns_over_hodl\": 0.0015715825167588182, \"returns_over_uniform_hodl\": 0.2745915329731683, \"sharpe\": -0.20806047837068545, \"sterling\": -1.242199419226514, \"ulcer\": -0.17641031452568579}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -4.273000531099356, \"optuna_trial_number\": 266, \"price_ratio\": 5.943924831622884, \"shift_exponent\": 0.00028801665073587945, \"step\": 266, \"test_objective\": [{\"annualised_returns\": -0.48249254548143183, \"annualised_returns_over_hodl\": 0.0039067956977707485, \"annualised_returns_over_uniform_hodl\": 0.8265719522926216, \"calmar\": -0.8323308386218674, \"daily_log_sharpe\": -0.6959975747064535, \"daily_returns\": 0.031016041789753753, \"fee_revenue_over_value\": 0.001972990717110058, \"jax_sharpe\": -0.012361689739964923, \"return\": -0.23302113220412346, \"returns_over_hodl\": 0.0015715825167588182, \"returns_over_uniform_hodl\": 0.2745915329731683, \"sharpe\": -0.20806047837068545, \"sterling\": -1.242199419226514, \"ulcer\": -0.17641031452568579}], \"train_objective\": [{\"annualised_returns\": -0.42401168330694006, \"annualised_returns_over_hodl\": -0.2746359244256157, \"annualised_returns_over_uniform_hodl\": -0.27463592442561535, \"calmar\": -0.5746919483033189, \"daily_log_sharpe\": -0.3894358186891893, \"daily_returns\": 0.008205557773891677, \"fee_revenue_over_value\": 0.047430035703960205, \"jax_sharpe\": 0.17451234166333893, \"return\": -0.28461139524248746, \"returns_over_hodl\": -0.1771122605947354, \"returns_over_uniform_hodl\": -0.17711226059473517, \"sharpe\": 0.19277515386598493, \"sterling\": -1.2728410036010864, \"ulcer\": -0.1796453735900559}], \"train_return\": -0.28461139524248746, \"train_returns_over_hodl\": -0.1771122605947354, \"train_sharpe\": 0.1745123416633389, \"validation_return\": -0.3389397530432361, \"validation_returns_over_hodl\": -0.011893899420600373, \"validation_sharpe\": -2.2420878782656724}, {\"centeredness_margin\": 0.01674233687931997, \"continuous_test_metrics\": [{\"annualised_returns\": -0.5012219747686188, \"annualised_returns_over_hodl\": -0.03242620837761756, \"annualised_returns_over_uniform_hodl\": 0.7604653678952051, \"calmar\": -0.8646403736642397, \"daily_log_sharpe\": -0.7383188579089434, \"daily_returns\": 0.03101604167993208, \"fee_revenue_over_value\": 0.002121516617544596, \"jax_sharpe\": -0.05433753160921374, \"return\": -0.2443236194133136, \"returns_over_hodl\": -0.01318794989790395, \"returns_over_uniform_hodl\": 0.25580867583947553, \"sharpe\": -0.25421442613453443, \"sterling\": -1.2904192904005107, \"ulcer\": -0.17641031452332454}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -3.7422945403247447, \"optuna_trial_number\": 267, \"price_ratio\": 7.942076715452882, \"shift_exponent\": 0.00010217015810720596, \"step\": 267, \"test_objective\": [{\"annualised_returns\": -0.5012219747686188, \"annualised_returns_over_hodl\": -0.03242620837761756, \"annualised_returns_over_uniform_hodl\": 0.7604653678952051, \"calmar\": -0.8646403736642397, \"daily_log_sharpe\": -0.7383188579089434, \"daily_returns\": 0.03101604167993208, \"fee_revenue_over_value\": 0.002121516617544596, \"jax_sharpe\": -0.05433753160921374, \"return\": -0.2443236194133136, \"returns_over_hodl\": -0.01318794989790395, \"returns_over_uniform_hodl\": 0.25580867583947553, \"sharpe\": -0.25421442613453443, \"sterling\": -1.2904192904005107, \"ulcer\": -0.17641031452332454}], \"train_objective\": [{\"annualised_returns\": -0.3994861042123675, \"annualised_returns_over_hodl\": -0.2437499264074625, \"annualised_returns_over_uniform_hodl\": -0.2437499264074625, \"calmar\": -0.5428994225519809, \"daily_log_sharpe\": -0.3712648223561544, \"daily_returns\": 0.008175566169686893, \"fee_revenue_over_value\": 0.043694887794110245, \"jax_sharpe\": 0.21003853180814097, \"return\": -0.26626942926102193, \"returns_over_hodl\": -0.1560141066370655, \"returns_over_uniform_hodl\": -0.1560141066370655, \"sharpe\": 0.19254734696767625, \"sterling\": -1.2208108802461939, \"ulcer\": -0.1762075285998773}], \"train_return\": -0.26626942926102193, \"train_returns_over_hodl\": -0.1560141066370655, \"train_sharpe\": 0.21003853180814094, \"validation_return\": -0.33178337522984436, \"validation_returns_over_hodl\": -0.052947782872657445, \"validation_sharpe\": -2.1790072791512642}, {\"centeredness_margin\": 0.028020424258965664, \"continuous_test_metrics\": [{\"annualised_returns\": -0.4972079401792858, \"annualised_returns_over_hodl\": -0.024639430895567638, \"annualised_returns_over_uniform_hodl\": 0.7746331309532837, \"calmar\": -0.8577158915301533, \"daily_log_sharpe\": -0.7294483307522799, \"daily_returns\": 0.031016041805365626, \"fee_revenue_over_value\": 0.0020501619392979064, \"jax_sharpe\": -0.04639587651201155, \"return\": -0.24188023652889223, \"returns_over_hodl\": -0.009997220361404824, \"returns_over_uniform_hodl\": 0.25986917250641106, \"sharpe\": -0.24472818888804332, \"sterling\": -1.2800849525351325, \"ulcer\": -0.1764103145579571}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -4.015505034010766, \"optuna_trial_number\": 268, \"price_ratio\": 6.810427002339558, \"shift_exponent\": 0.0004976210677709855, \"step\": 268, \"test_objective\": [{\"annualised_returns\": -0.4972079401792858, \"annualised_returns_over_hodl\": -0.024639430895567638, \"annualised_returns_over_uniform_hodl\": 0.7746331309532837, \"calmar\": -0.8577158915301533, \"daily_log_sharpe\": -0.7294483307522799, \"daily_returns\": 0.031016041805365626, \"fee_revenue_over_value\": 0.0020501619392979064, \"jax_sharpe\": -0.04639587651201155, \"return\": -0.24188023652889223, \"returns_over_hodl\": -0.009997220361404824, \"returns_over_uniform_hodl\": 0.25986917250641106, \"sharpe\": -0.24472818888804332, \"sterling\": -1.2800849525351325, \"ulcer\": -0.1764103145579571}], \"train_objective\": [{\"annualised_returns\": -0.41224847569466794, \"annualised_returns_over_hodl\": -0.2598220680188502, \"annualised_returns_over_uniform_hodl\": -0.2598220680188502, \"calmar\": -0.5593854054695243, \"daily_log_sharpe\": -0.38136773548924724, \"daily_returns\": 0.008196968601790462, \"fee_revenue_over_value\": 0.045264875919960666, \"jax_sharpe\": 0.17136815565642027, \"return\": -0.2757765242594, \"returns_over_hodl\": -0.16694980208915933, \"returns_over_uniform_hodl\": -0.16694980208915933, \"sharpe\": 0.19145108372940425, \"sterling\": -1.2487122884379653, \"ulcer\": -0.17787721710862425}], \"train_return\": -0.2757765242594, \"train_returns_over_hodl\": -0.16694980208915933, \"train_sharpe\": 0.17136815565642025, \"validation_return\": -0.3366283171161273, \"validation_returns_over_hodl\": -0.035876606329742655, \"validation_sharpe\": -2.221513830976848}, {\"centeredness_margin\": 0.014190160760194857, \"continuous_test_metrics\": [{\"annualised_returns\": -0.45751459050424914, \"annualised_returns_over_hodl\": 0.052361246665900696, \"annualised_returns_over_uniform_hodl\": 0.9147330629946759, \"calmar\": -0.8265606221426434, \"daily_log_sharpe\": -0.6952029100809518, \"daily_returns\": 0.031044200661098125, \"fee_revenue_over_value\": 0.0985323288171987, \"jax_sharpe\": -0.05812287903359835, \"return\": -0.21832175317597302, \"returns_over_hodl\": 0.020767001605401347, \"returns_over_uniform_hodl\": 0.29901946030718474, \"sharpe\": -0.23813832051610445, \"sterling\": -1.2196028941319328, \"ulcer\": -0.16664661102842468}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -3.46966151383914, \"optuna_trial_number\": 269, \"price_ratio\": 7.737404843817861, \"shift_exponent\": 0.008930929992924675, \"step\": 269, \"test_objective\": [{\"annualised_returns\": -0.45751459050424914, \"annualised_returns_over_hodl\": 0.052361246665900696, \"annualised_returns_over_uniform_hodl\": 0.9147330629946759, \"calmar\": -0.8265606221426434, \"daily_log_sharpe\": -0.6952029100809518, \"daily_returns\": 0.031044200661098125, \"fee_revenue_over_value\": 0.0985323288171987, \"jax_sharpe\": -0.05812287903359835, \"return\": -0.21832175317597302, \"returns_over_hodl\": 0.020767001605401347, \"returns_over_uniform_hodl\": 0.29901946030718474, \"sharpe\": -0.23813832051610445, \"sterling\": -1.2196028941319328, \"ulcer\": -0.16664661102842468}], \"train_objective\": [{\"annualised_returns\": -0.4013948805190243, \"annualised_returns_over_hodl\": -0.24615372127800028, \"annualised_returns_over_uniform_hodl\": -0.24615372127800017, \"calmar\": -0.5453642510173737, \"daily_log_sharpe\": -0.37261921355313965, \"daily_returns\": 0.008146381912235327, \"fee_revenue_over_value\": 0.04405398165176038, \"jax_sharpe\": 0.1706514725740061, \"return\": -0.26768625228925913, \"returns_over_hodl\": -0.15764383108485536, \"returns_over_uniform_hodl\": -0.15764383108485525, \"sharpe\": 0.1925744945795476, \"sterling\": -1.2250396926906149, \"ulcer\": -0.17645564036725125}], \"train_return\": -0.26768625228925913, \"train_returns_over_hodl\": -0.15764383108485536, \"train_sharpe\": 0.1706514725740061, \"validation_return\": -0.3270029231946261, \"validation_returns_over_hodl\": -0.0425521540309598, \"validation_sharpe\": -2.2326436965703986}, {\"centeredness_margin\": 0.019707788367387548, \"continuous_test_metrics\": [{\"annualised_returns\": -0.48183109764099574, \"annualised_returns_over_hodl\": 0.005189932806473063, \"annualised_returns_over_uniform_hodl\": 0.8289065700120304, \"calmar\": -0.831189796237375, \"daily_log_sharpe\": -0.6870395734662894, \"daily_returns\": 0.031016041740965207, \"fee_revenue_over_value\": 1.0173943521666942e-05, \"jax_sharpe\": 0.00748181348418218, \"return\": -0.23262647582803675, \"returns_over_hodl\": 0.002086951641968815, \"returns_over_uniform_hodl\": 0.2752473863434699, \"sharpe\": -0.19638594290332698, \"sterling\": -1.2404964889664347, \"ulcer\": -0.17641031442481794}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -0.09103364008784043, \"optuna_trial_number\": 270, \"price_ratio\": 4.325501687708117, \"shift_exponent\": 3.1710560592359574e-05, \"step\": 270, \"test_objective\": [{\"annualised_returns\": -0.48183109764099574, \"annualised_returns_over_hodl\": 0.005189932806473063, \"annualised_returns_over_uniform_hodl\": 0.8289065700120304, \"calmar\": -0.831189796237375, \"daily_log_sharpe\": -0.6870395734662894, \"daily_returns\": 0.031016041740965207, \"fee_revenue_over_value\": 1.0173943521666942e-05, \"jax_sharpe\": 0.00748181348418218, \"return\": -0.23262647582803675, \"returns_over_hodl\": 0.002086951641968815, \"returns_over_uniform_hodl\": 0.2752473863434699, \"sharpe\": -0.19638594290332698, \"sterling\": -1.2404964889664347, \"ulcer\": -0.17641031442481794}], \"train_objective\": [{\"annualised_returns\": -0.47490320621081406, \"annualised_returns_over_hodl\": -0.3387255620031304, \"annualised_returns_over_uniform_hodl\": -0.3387255620031304, \"calmar\": -0.6388800475332752, \"daily_log_sharpe\": -0.44618686925032813, \"daily_returns\": 0.008268038677718031, \"fee_revenue_over_value\": 0.044990558205331446, \"jax_sharpe\": 0.1639608100398493, \"return\": -0.3236813918625894, \"returns_over_hodl\": -0.2220531793953423, \"returns_over_uniform_hodl\": -0.2220531793953423, \"sharpe\": 0.16393789636363137, \"sterling\": -1.383719821517123, \"ulcer\": -0.1857892237147359}], \"train_return\": -0.3236813918625894, \"train_returns_over_hodl\": -0.2220531793953423, \"train_sharpe\": 0.1639608100398493, \"validation_return\": -0.33914271285511954, \"validation_returns_over_hodl\": -2.672653209856435e-09, \"validation_sharpe\": -2.243853180610646}, {\"centeredness_margin\": 0.016779465673709974, \"continuous_test_metrics\": [{\"annualised_returns\": -0.47962581065460064, \"annualised_returns_over_hodl\": 0.009467943265799583, \"annualised_returns_over_uniform_hodl\": 0.83669025567865, \"calmar\": -0.827385527125794, \"daily_log_sharpe\": -0.6828527983899518, \"daily_returns\": 0.031016041754707055, \"fee_revenue_over_value\": 0.0003663586111232788, \"jax_sharpe\": 0.007824500867654283, \"return\": -0.23131284924080486, \"returns_over_hodl\": 0.0038023716385569006, \"returns_over_uniform_hodl\": 0.2774304156235148, \"sharpe\": -0.1921261035565364, \"sterling\": -1.2348188654852124, \"ulcer\": -0.17641031445322447}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -0.08574590341644306, \"optuna_trial_number\": 271, \"price_ratio\": 4.615797690694338, \"shift_exponent\": 0.00010265546560374454, \"step\": 271, \"test_objective\": [{\"annualised_returns\": -0.47962581065460064, \"annualised_returns_over_hodl\": 0.009467943265799583, \"annualised_returns_over_uniform_hodl\": 0.83669025567865, \"calmar\": -0.827385527125794, \"daily_log_sharpe\": -0.6828527983899518, \"daily_returns\": 0.031016041754707055, \"fee_revenue_over_value\": 0.0003663586111232788, \"jax_sharpe\": 0.007824500867654283, \"return\": -0.23131284924080486, \"returns_over_hodl\": 0.0038023716385569006, \"returns_over_uniform_hodl\": 0.2774304156235148, \"sharpe\": -0.1921261035565364, \"sterling\": -1.2348188654852124, \"ulcer\": -0.17641031445322447}], \"train_objective\": [{\"annualised_returns\": -0.46162176240681596, \"annualised_returns_over_hodl\": -0.321999732801435, \"annualised_returns_over_uniform_hodl\": -0.321999732801435, \"calmar\": -0.6227792147238075, \"daily_log_sharpe\": -0.42989119031125067, \"daily_returns\": 0.008258977334787752, \"fee_revenue_over_value\": 0.0466630553888784, \"jax_sharpe\": 0.16799290790656676, \"return\": -0.31334676830572883, \"returns_over_hodl\": -0.21016560534152862, \"returns_over_uniform_hodl\": -0.21016560534152862, \"sharpe\": 0.1737108052539931, \"sterling\": -1.3551903095366415, \"ulcer\": -0.18416277510086862}], \"train_return\": -0.31334676830572883, \"train_returns_over_hodl\": -0.21016560534152862, \"train_sharpe\": 0.16799290790656674, \"validation_return\": -0.3391427129034472, \"validation_returns_over_hodl\": -2.516309827171881e-09, \"validation_sharpe\": -2.243853180133314}, {\"centeredness_margin\": 0.16986158839373666, \"continuous_test_metrics\": [{\"annualised_returns\": -0.5282839356867708, \"annualised_returns_over_hodl\": -0.08492339783105562, \"annualised_returns_over_uniform_hodl\": 0.6649486398644557, \"calmar\": -0.9123755636540202, \"daily_log_sharpe\": -0.7986252444599742, \"daily_returns\": 0.03101604172438936, \"fee_revenue_over_value\": 2.7500658668201284e-06, \"jax_sharpe\": -0.1157564619565554, \"return\": -0.26111157731163415, \"returns_over_hodl\": -0.03511077268606422, \"returns_over_uniform_hodl\": 0.22790987720033984, \"sharpe\": -0.3187527877054174, \"sterling\": -1.3626420942162272, \"ulcer\": -0.1756925422728325}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -3.3049678065855925, \"optuna_trial_number\": 272, \"price_ratio\": 9.7654857620781, \"shift_exponent\": 0.00020015053515443014, \"step\": 272, \"test_objective\": [{\"annualised_returns\": -0.5282839356867708, \"annualised_returns_over_hodl\": -0.08492339783105562, \"annualised_returns_over_uniform_hodl\": 0.6649486398644557, \"calmar\": -0.9123755636540202, \"daily_log_sharpe\": -0.7986252444599742, \"daily_returns\": 0.03101604172438936, \"fee_revenue_over_value\": 2.7500658668201284e-06, \"jax_sharpe\": -0.1157564619565554, \"return\": -0.26111157731163415, \"returns_over_hodl\": -0.03511077268606422, \"returns_over_uniform_hodl\": 0.22790987720033984, \"sharpe\": -0.3187527877054174, \"sterling\": -1.3626420942162272, \"ulcer\": -0.1756925422728325}], \"train_objective\": [{\"annualised_returns\": -0.40482987257614933, \"annualised_returns_over_hodl\": -0.2504795379064122, \"annualised_returns_over_uniform_hodl\": -0.2504795379064122, \"calmar\": -0.5493098317690062, \"daily_log_sharpe\": -0.3890210931018299, \"daily_returns\": 0.008140052394489991, \"fee_revenue_over_value\": 0.0310879300681559, \"jax_sharpe\": 0.14294658526335757, \"return\": -0.27024041529847576, \"returns_over_hodl\": -0.16058179991850652, \"returns_over_uniform_hodl\": -0.16058179991850652, \"sharpe\": 0.16536983393887966, \"sterling\": -1.2462368988572674, \"ulcer\": -0.17495908372799165}], \"train_return\": -0.27024041529847576, \"train_returns_over_hodl\": -0.16058179991850652, \"train_sharpe\": 0.14294658526335757, \"validation_return\": -0.3205310960747244, \"validation_returns_over_hodl\": -0.0644754929569814, \"validation_sharpe\": -2.1118957282026734}, {\"centeredness_margin\": 0.02862446528550666, \"continuous_test_metrics\": [{\"annualised_returns\": -0.4903173755554613, \"annualised_returns_over_hodl\": -0.011272503515115995, \"annualised_returns_over_uniform_hodl\": 0.7989537701391403, \"calmar\": -0.8458292002763822, \"daily_log_sharpe\": -0.7134258863437444, \"daily_returns\": 0.03101604180939475, \"fee_revenue_over_value\": 0.0017444932214399982, \"jax_sharpe\": -0.029256371825543505, \"return\": -0.23771290075063456, \"returns_over_hodl\": -0.004555238562606645, \"returns_over_uniform_hodl\": 0.266794592646457, \"sharpe\": -0.22714300579471455, \"sterling\": -1.262344842050598, \"ulcer\": -0.17641031456628592}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -3.968217039215463, \"optuna_trial_number\": 273, \"price_ratio\": 7.093988876656527, \"shift_exponent\": 1.0628854459577334e-05, \"step\": 273, \"test_objective\": [{\"annualised_returns\": -0.4903173755554613, \"annualised_returns_over_hodl\": -0.011272503515115995, \"annualised_returns_over_uniform_hodl\": 0.7989537701391403, \"calmar\": -0.8458292002763822, \"daily_log_sharpe\": -0.7134258863437444, \"daily_returns\": 0.03101604180939475, \"fee_revenue_over_value\": 0.0017444932214399982, \"jax_sharpe\": -0.029256371825543505, \"return\": -0.23771290075063456, \"returns_over_hodl\": -0.004555238562606645, \"returns_over_uniform_hodl\": 0.266794592646457, \"sharpe\": -0.22714300579471455, \"sterling\": -1.262344842050598, \"ulcer\": -0.17641031456628592}], \"train_objective\": [{\"annualised_returns\": -0.4087873533971764, \"annualised_returns_over_hodl\": -0.25546334458122255, \"annualised_returns_over_uniform_hodl\": -0.25546334458122255, \"calmar\": -0.5549677851476674, \"daily_log_sharpe\": -0.37858992902088334, \"daily_returns\": 0.008179038688767942, \"fee_revenue_over_value\": 0.044736856966493196, \"jax_sharpe\": 0.21183503455689223, \"return\": -0.2731902772259345, \"returns_over_hodl\": -0.1639749280685795, \"returns_over_uniform_hodl\": -0.1639749280685795, \"sharpe\": 0.19168546577501794, \"sterling\": -1.2414322853718456, \"ulcer\": -0.177385310670448}], \"train_return\": -0.2731902772259345, \"train_returns_over_hodl\": -0.1639749280685795, \"train_sharpe\": 0.21183503455689223, \"validation_return\": -0.33582002865562166, \"validation_returns_over_hodl\": -0.040973447782942984, \"validation_sharpe\": -2.2145030751637114}, {\"centeredness_margin\": 0.1351585987457004, \"continuous_test_metrics\": [{\"annualised_returns\": -0.5585174490815994, \"annualised_returns_over_hodl\": -0.14357304623262768, \"annualised_returns_over_uniform_hodl\": 0.5582377372405871, \"calmar\": -0.9634126322362282, \"daily_log_sharpe\": -0.8781567528362869, \"daily_returns\": 0.031088816884683148, \"fee_revenue_over_value\": 0.0011157983647198453, \"jax_sharpe\": -0.1953766381393909, \"return\": -0.28056219846844155, \"returns_over_hodl\": -0.060510677006522906, \"returns_over_uniform_hodl\": 0.19558617432077963, \"sharpe\": -0.4058000349881629, \"sterling\": -1.44946767577571, \"ulcer\": -0.17359815153980104}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -2.9653296553074857, \"optuna_trial_number\": 274, \"price_ratio\": 13.277533906101263, \"shift_exponent\": 0.00015130577684528614, \"step\": 274, \"test_objective\": [{\"annualised_returns\": -0.5585174490815994, \"annualised_returns_over_hodl\": -0.14357304623262768, \"annualised_returns_over_uniform_hodl\": 0.5582377372405871, \"calmar\": -0.9634126322362282, \"daily_log_sharpe\": -0.8781567528362869, \"daily_returns\": 0.031088816884683148, \"fee_revenue_over_value\": 0.0011157983647198453, \"jax_sharpe\": -0.1953766381393909, \"return\": -0.28056219846844155, \"returns_over_hodl\": -0.060510677006522906, \"returns_over_uniform_hodl\": 0.19558617432077963, \"sharpe\": -0.4058000349881629, \"sterling\": -1.44946767577571, \"ulcer\": -0.17359815153980104}], \"train_objective\": [{\"annualised_returns\": -0.37332966774248544, \"annualised_returns_over_hodl\": -0.21081012743857708, \"annualised_returns_over_uniform_hodl\": -0.21081012743857708, \"calmar\": -0.5090625097582333, \"daily_log_sharpe\": -0.3538389420043366, \"daily_returns\": 0.008118221687537579, \"fee_revenue_over_value\": 0.03682829613726309, \"jax_sharpe\": 0.19464097410447967, \"return\": -0.24702922818954753, \"returns_over_hodl\": -0.13388274270406741, \"returns_over_uniform_hodl\": -0.13388274270406741, \"sharpe\": 0.18866875789123425, \"sterling\": -1.1659668996667611, \"ulcer\": -0.17223129542172497}], \"train_return\": -0.24702922818954753, \"train_returns_over_hodl\": -0.13388274270406741, \"train_sharpe\": 0.19464097410447967, \"validation_return\": -0.30053816231711283, \"validation_returns_over_hodl\": -0.06592736293527457, \"validation_sharpe\": -2.0419252392295157}, {\"centeredness_margin\": 0.14967965690834933, \"continuous_test_metrics\": [{\"annualised_returns\": -0.581867394676121, \"annualised_returns_over_hodl\": -0.1594467505653573, \"annualised_returns_over_uniform_hodl\": 0.4758227781165898, \"calmar\": -1.001032196129879, \"daily_log_sharpe\": -0.9467374158323966, \"daily_returns\": 0.03015496539494665, \"fee_revenue_over_value\": 0.001221823957369898, \"jax_sharpe\": -0.27118383688794223, \"return\": -0.2961358618565083, \"returns_over_hodl\": -0.06756285600279288, \"returns_over_uniform_hodl\": 0.16970533154234868, \"sharpe\": -0.48226070322439785, \"sterling\": -1.5251565973633718, \"ulcer\": -0.1706849717671564}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -2.9255486296737314, \"optuna_trial_number\": 275, \"price_ratio\": 15.665146847155304, \"shift_exponent\": 0.000338780577346889, \"step\": 275, \"test_objective\": [{\"annualised_returns\": -0.581867394676121, \"annualised_returns_over_hodl\": -0.1594467505653573, \"annualised_returns_over_uniform_hodl\": 0.4758227781165898, \"calmar\": -1.001032196129879, \"daily_log_sharpe\": -0.9467374158323966, \"daily_returns\": 0.03015496539494665, \"fee_revenue_over_value\": 0.001221823957369898, \"jax_sharpe\": -0.27118383688794223, \"return\": -0.2961358618565083, \"returns_over_hodl\": -0.06756285600279288, \"returns_over_uniform_hodl\": 0.16970533154234868, \"sharpe\": -0.48226070322439785, \"sterling\": -1.5251565973633718, \"ulcer\": -0.1706849717671564}], \"train_objective\": [{\"annualised_returns\": -0.36674045658484544, \"annualised_returns_over_hodl\": -0.20251208228452433, \"annualised_returns_over_uniform_hodl\": -0.2025120822845241, \"calmar\": -0.5004178699806949, \"daily_log_sharpe\": -0.349166024781472, \"daily_returns\": 0.008075592468609182, \"fee_revenue_over_value\": 0.035528185612592376, \"jax_sharpe\": 0.16175204725063932, \"return\": -0.24223239802258145, \"returns_over_hodl\": -0.12836510836357695, \"returns_over_uniform_hodl\": -0.12836510836357684, \"sharpe\": 0.18849306900182164, \"sterling\": -1.1512937605059421, \"ulcer\": -0.17132028161674076}], \"train_return\": -0.24223239802258145, \"train_returns_over_hodl\": -0.12836510836357695, \"train_sharpe\": 0.16175204725063932, \"validation_return\": -0.28696792346740807, \"validation_returns_over_hodl\": -0.06136931459501982, \"validation_sharpe\": -1.9867157166155214}, {\"centeredness_margin\": 0.027615844839001397, \"continuous_test_metrics\": [{\"annualised_returns\": -0.484506477699575, \"annualised_returns_over_hodl\": 3.4904745760400147e-11, \"annualised_returns_over_uniform_hodl\": 0.8194636641484419, \"calmar\": -0.8358050074112975, \"daily_log_sharpe\": -0.6924636586127431, \"daily_returns\": 0.031016041583482173, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": 0.005019210138837484, \"return\": -0.23422461400477534, \"returns_over_hodl\": 1.4057421893198807e-11, \"returns_over_uniform_hodl\": 0.27259154604053415, \"sharpe\": -0.20210860206763753, \"sterling\": -1.2473843934226096, \"ulcer\": -0.17641031409926033}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -0.1314820241822352, \"optuna_trial_number\": 276, \"price_ratio\": 2.8424263333868773, \"shift_exponent\": 7.442175921627412e-05, \"step\": 276, \"test_objective\": [{\"annualised_returns\": -0.484506477699575, \"annualised_returns_over_hodl\": 3.4904745760400147e-11, \"annualised_returns_over_uniform_hodl\": 0.8194636641484419, \"calmar\": -0.8358050074112975, \"daily_log_sharpe\": -0.6924636586127431, \"daily_returns\": 0.031016041583482173, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": 0.005019210138837484, \"return\": -0.23422461400477534, \"returns_over_hodl\": 1.4057421893198807e-11, \"returns_over_uniform_hodl\": 0.27259154604053415, \"sharpe\": -0.20210860206763753, \"sterling\": -1.2473843934226096, \"ulcer\": -0.17641031409926033}], \"train_objective\": [{\"annualised_returns\": -0.5536406585934637, \"annualised_returns_over_hodl\": -0.43788264159205215, \"annualised_returns_over_uniform_hodl\": -0.43788264159205215, \"calmar\": -0.7244918629679415, \"daily_log_sharpe\": -0.5464433873700368, \"daily_returns\": 0.008448277857608459, \"fee_revenue_over_value\": 0.03217432695865394, \"jax_sharpe\": 0.14497958627919186, \"return\": -0.3872038244526679, \"returns_over_hodl\": -0.2951209227280601, \"returns_over_uniform_hodl\": -0.2951209227280601, \"sharpe\": 0.10430434871996466, \"sterling\": -1.531217006257297, \"ulcer\": -0.19888960101341835}], \"train_return\": -0.3872038244526679, \"train_returns_over_hodl\": -0.2951209227280601, \"train_sharpe\": 0.14497958627919186, \"validation_return\": -0.33914271191495216, \"validation_returns_over_hodl\": -3.879522258998236e-09, \"validation_sharpe\": -2.243853182139774}, {\"centeredness_margin\": 0.038312413338612614, \"continuous_test_metrics\": [{\"annualised_returns\": -0.4842705759837894, \"annualised_returns_over_hodl\": 0.0004576204174264742, \"annualised_returns_over_uniform_hodl\": 0.8202962926522244, \"calmar\": -0.8353980611576634, \"daily_log_sharpe\": -0.6919701485206231, \"daily_returns\": 0.031016041648643307, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": 0.005337405374331925, \"return\": -0.23408349926852445, \"returns_over_hodl\": 0.00018427587048752692, \"returns_over_uniform_hodl\": 0.27282605530220416, \"sharpe\": -0.2015763195740941, \"sterling\": -1.2467770513979797, \"ulcer\": -0.17641031423397172}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -0.11228920669796816, \"optuna_trial_number\": 277, \"price_ratio\": 3.5145843024632506, \"shift_exponent\": 0.0002172707474901702, \"step\": 277, \"test_objective\": [{\"annualised_returns\": -0.4842705759837894, \"annualised_returns_over_hodl\": 0.0004576204174264742, \"annualised_returns_over_uniform_hodl\": 0.8202962926522244, \"calmar\": -0.8353980611576634, \"daily_log_sharpe\": -0.6919701485206231, \"daily_returns\": 0.031016041648643307, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": 0.005337405374331925, \"return\": -0.23408349926852445, \"returns_over_hodl\": 0.00018427587048752692, \"returns_over_uniform_hodl\": 0.27282605530220416, \"sharpe\": -0.2015763195740941, \"sterling\": -1.2467770513979797, \"ulcer\": -0.17641031423397172}], \"train_objective\": [{\"annualised_returns\": -0.5089452161229397, \"annualised_returns_over_hodl\": -0.38159596463972023, \"annualised_returns_over_uniform_hodl\": -0.38159596463972056, \"calmar\": -0.6770818423720041, \"daily_log_sharpe\": -0.4871214295653209, \"daily_returns\": 0.008335565526513435, \"fee_revenue_over_value\": 0.0399288722686444, \"jax_sharpe\": 0.16033661091437504, \"return\": -0.3506506872951387, \"returns_over_hodl\": -0.2530750637310113, \"returns_over_uniform_hodl\": -0.2530750637310115, \"sharpe\": 0.1422726489438116, \"sterling\": -1.4439865696256726, \"ulcer\": -0.19183975250356985}], \"train_return\": -0.3506506872951387, \"train_returns_over_hodl\": -0.2530750637310113, \"train_sharpe\": 0.160336610914375, \"validation_return\": -0.3391427122543482, \"validation_returns_over_hodl\": -3.3050623349240027e-09, \"validation_sharpe\": -2.2438531810032476}, {\"centeredness_margin\": 0.053474125805298756, \"continuous_test_metrics\": [{\"annualised_returns\": -0.48450647911279254, \"annualised_returns_over_hodl\": 3.682831817286569e-11, \"annualised_returns_over_uniform_hodl\": 0.8194636591604099, \"calmar\": -0.8358050096755765, \"daily_log_sharpe\": -0.6924636621612265, \"daily_returns\": 0.031016041516330008, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": 0.005019205890598719, \"return\": -0.23422461485026758, \"returns_over_hodl\": 1.483213551978224e-11, \"returns_over_uniform_hodl\": 0.2725915446354663, \"sharpe\": -0.20210860622985607, \"sterling\": -1.2473843981584518, \"ulcer\": -0.176410313960437}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -0.149403448813077, \"optuna_trial_number\": 278, \"price_ratio\": 2.2597752208782267, \"shift_exponent\": 0.00013668207333042268, \"step\": 278, \"test_objective\": [{\"annualised_returns\": -0.48450647911279254, \"annualised_returns_over_hodl\": 3.682831817286569e-11, \"annualised_returns_over_uniform_hodl\": 0.8194636591604099, \"calmar\": -0.8358050096755765, \"daily_log_sharpe\": -0.6924636621612265, \"daily_returns\": 0.031016041516330008, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": 0.005019205890598719, \"return\": -0.23422461485026758, \"returns_over_hodl\": 1.483213551978224e-11, \"returns_over_uniform_hodl\": 0.2725915446354663, \"sharpe\": -0.20210860622985607, \"sterling\": -1.2473843981584518, \"ulcer\": -0.176410313960437}], \"train_objective\": [{\"annualised_returns\": -0.5951436941121111, \"annualised_returns_over_hodl\": -0.4901489985997016, \"annualised_returns_over_uniform_hodl\": -0.4901489985997016, \"calmar\": -0.7704291107090954, \"daily_log_sharpe\": -0.609863754208805, \"daily_returns\": 0.008497684375739624, \"fee_revenue_over_value\": 0.029336008998466515, \"jax_sharpe\": 0.1334073739584203, \"return\": -0.42245745354499353, \"returns_over_hodl\": -0.335672000780874, \"returns_over_uniform_hodl\": -0.335672000780874, \"sharpe\": 0.05552986047108173, \"sterling\": -1.6272906657444832, \"ulcer\": -0.20303486269395257}], \"train_return\": -0.42245745354499353, \"train_returns_over_hodl\": -0.335672000780874, \"train_sharpe\": 0.1334073739584203, \"validation_return\": -0.33914271153091113, \"validation_returns_over_hodl\": -4.419651644660405e-09, \"validation_sharpe\": -2.2438531829622548}, {\"centeredness_margin\": 0.17865265085001422, \"continuous_test_metrics\": [{\"annualised_returns\": -0.48450647840774763, \"annualised_returns_over_hodl\": 3.5860647784602406e-11, \"annualised_returns_over_uniform_hodl\": 0.8194636616489068, \"calmar\": -0.8358050085459442, \"daily_log_sharpe\": -0.6924636603909103, \"daily_returns\": 0.03101604154983195, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": 0.005019208010022389, \"return\": -0.2342246144284571, \"returns_over_hodl\": 1.4442447238138811e-11, \"returns_over_uniform_hodl\": 0.2725915453364456, \"sharpe\": -0.2021086041533513, \"sterling\": -1.2473843957957753, \"ulcer\": -0.1764103140296954}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -0.13733811928069162, \"optuna_trial_number\": 279, \"price_ratio\": 2.6096239951160527, \"shift_exponent\": 0.00024688264007323614, \"step\": 279, \"test_objective\": [{\"annualised_returns\": -0.48450647840774763, \"annualised_returns_over_hodl\": 3.5860647784602406e-11, \"annualised_returns_over_uniform_hodl\": 0.8194636616489068, \"calmar\": -0.8358050085459442, \"daily_log_sharpe\": -0.6924636603909103, \"daily_returns\": 0.03101604154983195, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": 0.005019208010022389, \"return\": -0.2342246144284571, \"returns_over_hodl\": 1.4442447238138811e-11, \"returns_over_uniform_hodl\": 0.2725915453364456, \"sharpe\": -0.2021086041533513, \"sterling\": -1.2473843957957753, \"ulcer\": -0.1764103140296954}], \"train_objective\": [{\"annualised_returns\": -0.5764333282216324, \"annualised_returns_over_hodl\": -0.46658632056531746, \"annualised_returns_over_uniform_hodl\": -0.46658632056531746, \"calmar\": -0.7503478474813865, \"daily_log_sharpe\": -0.5824664541612072, \"daily_returns\": 0.008435060984448307, \"fee_revenue_over_value\": 0.02609694962255368, \"jax_sharpe\": 0.12489162205684937, \"return\": -0.4063967984643828, \"returns_over_hodl\": -0.31719796294359104, \"returns_over_uniform_hodl\": -0.31719796294359104, \"sharpe\": 0.07444281286302955, \"sterling\": -1.5866789245917579, \"ulcer\": -0.20060355004566482}], \"train_return\": -0.4063967984643828, \"train_returns_over_hodl\": -0.31719796294359104, \"train_sharpe\": 0.12489162205684937, \"validation_return\": -0.3391427116602851, \"validation_returns_over_hodl\": -4.056028624077612e-09, \"validation_sharpe\": -2.243853181923322}, {\"centeredness_margin\": 0.026381365774196565, \"continuous_test_metrics\": [{\"annualised_returns\": -0.4844263409905588, \"annualised_returns_over_hodl\": 0.0001554542079456045, \"annualised_returns_over_uniform_hodl\": 0.8197465112142379, \"calmar\": -0.8356667662994307, \"daily_log_sharpe\": -0.692295987798361, \"daily_returns\": 0.03101604163522324, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": 0.0051284293050655695, \"return\": -0.23417667246793183, \"returns_over_hodl\": 6.260438314620309e-05, \"returns_over_uniform_hodl\": 0.2726712169147971, \"sharpe\": -0.20192776389332256, \"sterling\": -1.24717807654562, \"ulcer\": -0.1764103142062232}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -0.11689380015938566, \"optuna_trial_number\": 280, \"price_ratio\": 3.2789534387691526, \"shift_exponent\": 0.0002651950304722134, \"step\": 280, \"test_objective\": [{\"annualised_returns\": -0.4844263409905588, \"annualised_returns_over_hodl\": 0.0001554542079456045, \"annualised_returns_over_uniform_hodl\": 0.8197465112142379, \"calmar\": -0.8356667662994307, \"daily_log_sharpe\": -0.692295987798361, \"daily_returns\": 0.03101604163522324, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": 0.0051284293050655695, \"return\": -0.23417667246793183, \"returns_over_hodl\": 6.260438314620309e-05, \"returns_over_uniform_hodl\": 0.2726712169147971, \"sharpe\": -0.20192776389332256, \"sterling\": -1.24717807654562, \"ulcer\": -0.1764103142062232}], \"train_objective\": [{\"annualised_returns\": -0.5196093035117657, \"annualised_returns_over_hodl\": -0.39502565699016823, \"annualised_returns_over_uniform_hodl\": -0.39502565699016823, \"calmar\": -0.6883518146879113, \"daily_log_sharpe\": -0.5000781022125238, \"daily_returns\": 0.008388909261365889, \"fee_revenue_over_value\": 0.04068978703713644, \"jax_sharpe\": 0.16041552435495568, \"return\": -0.35924904040015704, \"returns_over_hodl\": -0.26296546358102724, \"returns_over_uniform_hodl\": -0.26296546358102724, \"sharpe\": 0.13551339403185841, \"sterling\": -1.4614221087109691, \"ulcer\": -0.1939479485371265}], \"train_return\": -0.35924904040015704, \"train_returns_over_hodl\": -0.26296546358102724, \"train_sharpe\": 0.16041552435495568, \"validation_return\": -0.33914271219709213, \"validation_returns_over_hodl\": -3.4425384765057743e-09, \"validation_sharpe\": -2.243853181369075}, {\"centeredness_margin\": 0.014212803066448015, \"continuous_test_metrics\": [{\"annualised_returns\": -0.48450647864264507, \"annualised_returns_over_hodl\": 3.61781715696452e-11, \"annualised_returns_over_uniform_hodl\": 0.8194636608198225, \"calmar\": -0.8358050089223005, \"daily_log_sharpe\": -0.692463660980718, \"daily_returns\": 0.031016041538670488, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": 0.0050192073039041505, \"return\": -0.23422461456899024, \"returns_over_hodl\": 1.457034493057563e-11, \"returns_over_uniform_hodl\": 0.2725915451029026, \"sharpe\": -0.20210860484516915, \"sterling\": -1.2473843965829334, \"ulcer\": -0.17641031400662094}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -0.1442542861719529, \"optuna_trial_number\": 281, \"price_ratio\": 2.4410028300115383, \"shift_exponent\": 4.392234074638575e-05, \"step\": 281, \"test_objective\": [{\"annualised_returns\": -0.48450647864264507, \"annualised_returns_over_hodl\": 3.61781715696452e-11, \"annualised_returns_over_uniform_hodl\": 0.8194636608198225, \"calmar\": -0.8358050089223005, \"daily_log_sharpe\": -0.692463660980718, \"daily_returns\": 0.031016041538670488, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": 0.0050192073039041505, \"return\": -0.23422461456899024, \"returns_over_hodl\": 1.457034493057563e-11, \"returns_over_uniform_hodl\": 0.2725915451029026, \"sharpe\": -0.20210860484516915, \"sterling\": -1.2473843965829334, \"ulcer\": -0.17641031400662094}], \"train_objective\": [{\"annualised_returns\": -0.58279378057839, \"annualised_returns_over_hodl\": -0.47459628102854867, \"annualised_returns_over_uniform_hodl\": -0.47459628102854845, \"calmar\": -0.7564022747686825, \"daily_log_sharpe\": -0.5895252098070054, \"daily_returns\": 0.008595343102061488, \"fee_revenue_over_value\": 0.03154196589578955, \"jax_sharpe\": 0.1304824428162166, \"return\": -0.41182462771341477, \"returns_over_hodl\": -0.3234414145598322, \"returns_over_uniform_hodl\": -0.323441414559832, \"sharpe\": 0.07255256329213959, \"sterling\": -1.598145660147445, \"ulcer\": -0.2021312316803474}], \"train_return\": -0.41182462771341477, \"train_returns_over_hodl\": -0.3234414145598322, \"train_sharpe\": 0.13048244281621663, \"validation_return\": -0.3391427116755218, \"validation_returns_over_hodl\": -4.264196218350946e-09, \"validation_sharpe\": -2.2438531828487656}, {\"centeredness_margin\": 0.028166784270253854, \"continuous_test_metrics\": [{\"annualised_returns\": -0.48368241282027424, \"annualised_returns_over_hodl\": 0.0015985885582938675, \"annualised_returns_over_uniform_hodl\": 0.8223722479422744, \"calmar\": -0.8343834400868884, \"daily_log_sharpe\": -0.690744344323473, \"daily_returns\": 0.03101604171862533, \"fee_revenue_over_value\": 5.686088631648034e-07, \"jax_sharpe\": 0.005727781493807839, \"return\": -0.23373183202991166, \"returns_over_hodl\": 0.0006435049895481271, \"returns_over_uniform_hodl\": 0.27341046786372347, \"sharpe\": -0.2002573922915823, \"sterling\": -1.2452627942973216, \"ulcer\": -0.17641031437864466}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -0.09814444920740495, \"optuna_trial_number\": 282, \"price_ratio\": 3.9847318243169254, \"shift_exponent\": 1.1640662736879014e-05, \"step\": 282, \"test_objective\": [{\"annualised_returns\": -0.48368241282027424, \"annualised_returns_over_hodl\": 0.0015985885582938675, \"annualised_returns_over_uniform_hodl\": 0.8223722479422744, \"calmar\": -0.8343834400868884, \"daily_log_sharpe\": -0.690744344323473, \"daily_returns\": 0.03101604171862533, \"fee_revenue_over_value\": 5.686088631648034e-07, \"jax_sharpe\": 0.005727781493807839, \"return\": -0.23373183202991166, \"returns_over_hodl\": 0.0006435049895481271, \"returns_over_uniform_hodl\": 0.27341046786372347, \"sharpe\": -0.2002573922915823, \"sterling\": -1.2452627942973216, \"ulcer\": -0.17641031437864466}], \"train_objective\": [{\"annualised_returns\": -0.4939135830737249, \"annualised_returns_over_hodl\": -0.3626660553080221, \"annualised_returns_over_uniform_hodl\": -0.3626660553080222, \"calmar\": -0.6606455052297592, \"daily_log_sharpe\": -0.4706055145780786, \"daily_returns\": 0.008296593120616954, \"fee_revenue_over_value\": 0.04059974436450773, \"jax_sharpe\": 0.15640228064471315, \"return\": -0.3386543842864279, \"returns_over_hodl\": -0.2392761150220022, \"returns_over_uniform_hodl\": -0.2392761150220023, \"sharpe\": 0.14797849735997143, \"sterling\": -1.4219299020526301, \"ulcer\": -0.18850915224329237}], \"train_return\": -0.3386543842864279, \"train_returns_over_hodl\": -0.2392761150220022, \"train_sharpe\": 0.15640228064471315, \"validation_return\": -0.33914271274796337, \"validation_returns_over_hodl\": -2.883456695634834e-09, \"validation_sharpe\": -2.24385318108536}, {\"centeredness_margin\": 0.5660368323030471, \"continuous_test_metrics\": [{\"annualised_returns\": -0.484506477671925, \"annualised_returns_over_hodl\": 3.484124100339159e-11, \"annualised_returns_over_uniform_hodl\": 0.8194636642460347, \"calmar\": -0.8358050073670047, \"daily_log_sharpe\": -0.6924636585433036, \"daily_returns\": 0.031016041584796913, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": 0.0050192102219735, \"return\": -0.23422461398823302, \"returns_over_hodl\": 1.4031886763632428e-11, \"returns_over_uniform_hodl\": 0.2725915460680248, \"sharpe\": -0.2021086019861902, \"sterling\": -1.2473843933299407, \"ulcer\": -0.17641031410197705}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -0.12817375117730623, \"optuna_trial_number\": 283, \"price_ratio\": 3.0926835899761342, \"shift_exponent\": 1.8984705424886802e-05, \"step\": 283, \"test_objective\": [{\"annualised_returns\": -0.484506477671925, \"annualised_returns_over_hodl\": 3.484124100339159e-11, \"annualised_returns_over_uniform_hodl\": 0.8194636642460347, \"calmar\": -0.8358050073670047, \"daily_log_sharpe\": -0.6924636585433036, \"daily_returns\": 0.031016041584796913, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": 0.0050192102219735, \"return\": -0.23422461398823302, \"returns_over_hodl\": 1.4031886763632428e-11, \"returns_over_uniform_hodl\": 0.2725915460680248, \"sharpe\": -0.2021086019861902, \"sterling\": -1.2473843933299407, \"ulcer\": -0.17641031410197705}], \"train_objective\": [{\"annualised_returns\": -0.5711801483635899, \"annualised_returns_over_hodl\": -0.4599707906298628, \"annualised_returns_over_uniform_hodl\": -0.4599707906298628, \"calmar\": -0.7440429840409752, \"daily_log_sharpe\": -0.5816449786052592, \"daily_returns\": 0.008394744878838695, \"fee_revenue_over_value\": 0.0034465624266005784, \"jax_sharpe\": 0.10522122778974859, \"return\": -0.40193798853410057, \"returns_over_hodl\": -0.3120691420488112, \"returns_over_uniform_hodl\": -0.3120691420488112, \"sharpe\": 0.06539275196918469, \"sterling\": -1.5777178156366094, \"ulcer\": -0.1993788474202719}], \"train_return\": -0.40193798853410057, \"train_returns_over_hodl\": -0.3120691420488112, \"train_sharpe\": 0.1052212277897486, \"validation_return\": -0.3391427118640209, \"validation_returns_over_hodl\": -3.780505242190202e-09, \"validation_sharpe\": -2.2438531815409783}, {\"centeredness_margin\": 0.010202611684982868, \"continuous_test_metrics\": [{\"annualised_returns\": -0.4276801168830243, \"annualised_returns_over_hodl\": 0.11023679823538712, \"annualised_returns_over_uniform_hodl\": 1.0200355320743522, \"calmar\": -0.772613426055268, \"daily_log_sharpe\": -0.6183912444121201, \"daily_returns\": 0.031145862668117277, \"fee_revenue_over_value\": 0.07403312325819608, \"jax_sharpe\": 0.03524809735624398, \"return\": -0.20128472241972073, \"returns_over_hodl\": 0.043015078766467596, \"returns_over_uniform_hodl\": 0.3273321766814017, \"sharpe\": -0.14594609112037804, \"sterling\": -1.1127486938042266, \"ulcer\": -0.1728828193617616}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -2.587928841649933, \"optuna_trial_number\": 284, \"price_ratio\": 13.57438278932822, \"shift_exponent\": 0.00014538854967734727, \"step\": 284, \"test_objective\": [{\"annualised_returns\": -0.4276801168830243, \"annualised_returns_over_hodl\": 0.11023679823538712, \"annualised_returns_over_uniform_hodl\": 1.0200355320743522, \"calmar\": -0.772613426055268, \"daily_log_sharpe\": -0.6183912444121201, \"daily_returns\": 0.031145862668117277, \"fee_revenue_over_value\": 0.07403312325819608, \"jax_sharpe\": 0.03524809735624398, \"return\": -0.20128472241972073, \"returns_over_hodl\": 0.043015078766467596, \"returns_over_uniform_hodl\": 0.3273321766814017, \"sharpe\": -0.14594609112037804, \"sterling\": -1.1127486938042266, \"ulcer\": -0.1728828193617616}], \"train_objective\": [{\"annualised_returns\": -0.3705842245888422, \"annualised_returns_over_hodl\": -0.20735268606785973, \"annualised_returns_over_uniform_hodl\": -0.20735268606785984, \"calmar\": -0.5053276113393869, \"daily_log_sharpe\": -0.3505807111611579, \"daily_returns\": 0.008098833758455307, \"fee_revenue_over_value\": 0.0377949271817332, \"jax_sharpe\": 0.16518467912416193, \"return\": -0.24502819720336133, \"returns_over_hodl\": -0.1315810232557113, \"returns_over_uniform_hodl\": -0.13158102325571142, \"sharpe\": 0.19128678665091997, \"sterling\": -1.1582075776410656, \"ulcer\": -0.17210721384325522}], \"train_return\": -0.24502819720336133, \"train_returns_over_hodl\": -0.1315810232557113, \"train_sharpe\": 0.16518467912416196, \"validation_return\": -0.29296190173909087, \"validation_returns_over_hodl\": -0.057462213310349064, \"validation_sharpe\": -1.975889879227977}, {\"centeredness_margin\": 0.027245747225847182, \"continuous_test_metrics\": [{\"annualised_returns\": -0.48172533923840977, \"annualised_returns_over_hodl\": 0.005395090889342535, \"annualised_returns_over_uniform_hodl\": 0.8292798503004524, \"calmar\": -0.8310073559307402, \"daily_log_sharpe\": -0.6940918238756496, \"daily_returns\": 0.03101604177587188, \"fee_revenue_over_value\": 0.0018215915442544974, \"jax_sharpe\": -0.009660498887652294, \"return\": -0.2325634024199308, \"returns_over_hodl\": 0.0021693164819800437, \"returns_over_uniform_hodl\": 0.27535220387535153, \"sharpe\": -0.20589087040995388, \"sterling\": -1.2402242080221821, \"ulcer\": -0.1764103144969908}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -0.08954204120939291, \"optuna_trial_number\": 285, \"price_ratio\": 5.377299352952719, \"shift_exponent\": 0.0005386971723504098, \"step\": 285, \"test_objective\": [{\"annualised_returns\": -0.48172533923840977, \"annualised_returns_over_hodl\": 0.005395090889342535, \"annualised_returns_over_uniform_hodl\": 0.8292798503004524, \"calmar\": -0.8310073559307402, \"daily_log_sharpe\": -0.6940918238756496, \"daily_returns\": 0.03101604177587188, \"fee_revenue_over_value\": 0.0018215915442544974, \"jax_sharpe\": -0.009660498887652294, \"return\": -0.2325634024199308, \"returns_over_hodl\": 0.0021693164819800437, \"returns_over_uniform_hodl\": 0.27535220387535153, \"sharpe\": -0.20589087040995388, \"sterling\": -1.2402242080221821, \"ulcer\": -0.1764103144969908}], \"train_objective\": [{\"annualised_returns\": -0.44002446991853916, \"annualised_returns_over_hodl\": -0.2948014378939763, \"annualised_returns_over_uniform_hodl\": -0.2948014378939763, \"calmar\": -0.5954668887999017, \"daily_log_sharpe\": -0.4067533878614453, \"daily_returns\": 0.008239951178261344, \"fee_revenue_over_value\": 0.04673172562080366, \"jax_sharpe\": 0.2167767898893653, \"return\": -0.296752746783998, \"returns_over_hodl\": -0.19107805381100362, \"returns_over_uniform_hodl\": -0.19107805381100362, \"sharpe\": 0.18297582068220045, \"sterling\": -1.3104255930245745, \"ulcer\": -0.18117770047153461}], \"train_return\": -0.296752746783998, \"train_returns_over_hodl\": -0.19107805381100362, \"train_sharpe\": 0.21677678988936533, \"validation_return\": -0.3391427129230853, \"validation_returns_over_hodl\": -2.6349508130962818e-09, \"validation_sharpe\": -2.2438531788631244}, {\"centeredness_margin\": 0.02064982819554226, \"continuous_test_metrics\": [{\"annualised_returns\": -0.46110082948251585, \"annualised_returns_over_hodl\": 0.04540434827106066, \"annualised_returns_over_uniform_hodl\": 0.9020752288422877, \"calmar\": -0.8334675926545532, \"daily_log_sharpe\": -0.7344369417386055, \"daily_returns\": 0.031094035794155546, \"fee_revenue_over_value\": 0.16064506155225106, \"jax_sharpe\": -0.12613423893213574, \"return\": -0.22040701774068716, \"returns_over_hodl\": 0.018043927829552553, \"returns_over_uniform_hodl\": 0.2955540968274428, \"sharpe\": -0.29447066787926, \"sterling\": -1.2628785734364794, \"ulcer\": -0.1587097343189442}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -3.5572602516374947, \"optuna_trial_number\": 286, \"price_ratio\": 2.245896448801519, \"shift_exponent\": 0.013171188609369071, \"step\": 286, \"test_objective\": [{\"annualised_returns\": -0.46110082948251585, \"annualised_returns_over_hodl\": 0.04540434827106066, \"annualised_returns_over_uniform_hodl\": 0.9020752288422877, \"calmar\": -0.8334675926545532, \"daily_log_sharpe\": -0.7344369417386055, \"daily_returns\": 0.031094035794155546, \"fee_revenue_over_value\": 0.16064506155225106, \"jax_sharpe\": -0.12613423893213574, \"return\": -0.22040701774068716, \"returns_over_hodl\": 0.018043927829552553, \"returns_over_uniform_hodl\": 0.2955540968274428, \"sharpe\": -0.29447066787926, \"sterling\": -1.2628785734364794, \"ulcer\": -0.1587097343189442}], \"train_objective\": [{\"annualised_returns\": -0.5228999947667565, \"annualised_returns_over_hodl\": -0.3991697501097672, \"annualised_returns_over_uniform_hodl\": -0.3991697501097672, \"calmar\": -0.682909163401488, \"daily_log_sharpe\": -0.5289692465890681, \"daily_returns\": 0.008647427232824507, \"fee_revenue_over_value\": 0.04675148000923146, \"jax_sharpe\": 0.0909783418330357, \"return\": -0.36191739295002323, \"returns_over_hodl\": -0.2660347808487239, \"returns_over_uniform_hodl\": -0.2660347808487239, \"sharpe\": 0.08838180249445607, \"sterling\": -1.4891389941569189, \"ulcer\": -0.18996085512190314}], \"train_return\": -0.36191739295002323, \"train_returns_over_hodl\": -0.2660347808487239, \"train_sharpe\": 0.09097834183303571, \"validation_return\": -0.32949159131485284, \"validation_returns_over_hodl\": -0.03663059390984946, \"validation_sharpe\": -2.307854948398632}, {\"centeredness_margin\": 0.8820040998892353, \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -Infinity, \"optuna_trial_number\": 287, \"price_ratio\": 1.2320607984452931, \"shift_exponent\": 28.582647850193275, \"step\": 287, \"test_objective\": -Infinity, \"train_objective\": -Infinity, \"train_return\": -Infinity, \"train_returns_over_hodl\": -Infinity, \"train_sharpe\": -Infinity, \"validation_return\": -Infinity, \"validation_returns_over_hodl\": -Infinity, \"validation_sharpe\": -Infinity}, {\"centeredness_margin\": 0.01174094370010087, \"continuous_test_metrics\": [{\"annualised_returns\": -0.4729058524760572, \"annualised_returns_over_hodl\": 0.06767170828243474, \"annualised_returns_over_uniform_hodl\": 0.8604087297263829, \"calmar\": -0.8604562123363381, \"daily_log_sharpe\": -0.7734247401676297, \"daily_returns\": 0.030656306819407394, \"fee_revenue_over_value\": 0.1893913816393413, \"jax_sharpe\": -0.17345761981460894, \"return\": -0.22733034486861503, \"returns_over_hodl\": 0.026722197291373595, \"returns_over_uniform_hodl\": 0.2840486766551509, \"sharpe\": -0.34078269503276276, \"sterling\": -1.3156454181904704, \"ulcer\": -0.15297172655582025}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -3.05712583750345, \"optuna_trial_number\": 288, \"price_ratio\": 1.7951379514891304, \"shift_exponent\": 0.04922176976441461, \"step\": 288, \"test_objective\": [{\"annualised_returns\": -0.4729058524760572, \"annualised_returns_over_hodl\": 0.06767170828243474, \"annualised_returns_over_uniform_hodl\": 0.8604087297263829, \"calmar\": -0.8604562123363381, \"daily_log_sharpe\": -0.7734247401676297, \"daily_returns\": 0.030656306819407394, \"fee_revenue_over_value\": 0.1893913816393413, \"jax_sharpe\": -0.17345761981460894, \"return\": -0.22733034486861503, \"returns_over_hodl\": 0.026722197291373595, \"returns_over_uniform_hodl\": 0.2840486766551509, \"sharpe\": -0.34078269503276276, \"sterling\": -1.3156454181904704, \"ulcer\": -0.15297172655582025}], \"train_objective\": [{\"annualised_returns\": -0.4664266108189524, \"annualised_returns_over_hodl\": -0.32805066183200027, \"annualised_returns_over_uniform_hodl\": -0.32805066183200027, \"calmar\": -0.6107443701478109, \"daily_log_sharpe\": -0.4784236248424625, \"daily_returns\": 0.008925623218584242, \"fee_revenue_over_value\": 0.07483500987566573, \"jax_sharpe\": 0.026528585728646706, \"return\": -0.31707385143086586, \"returns_over_hodl\": -0.21445274520792212, \"returns_over_uniform_hodl\": -0.21445274520792212, \"sharpe\": 0.08779442077334736, \"sterling\": -1.386223911013869, \"ulcer\": -0.1808713653836788}], \"train_return\": -0.31707385143086586, \"train_returns_over_hodl\": -0.21445274520792212, \"train_sharpe\": 0.026528585728646706, \"validation_return\": -0.314427158148593, \"validation_returns_over_hodl\": -0.04328298523173013, \"validation_sharpe\": -2.2401075975461278}, {\"centeredness_margin\": 0.011003464509148544, \"continuous_test_metrics\": [{\"annualised_returns\": -0.4845064800441996, \"annualised_returns_over_hodl\": 3.8321790185591453e-11, \"annualised_returns_over_uniform_hodl\": 0.8194636558729569, \"calmar\": -0.8358050111679293, \"daily_log_sharpe\": -0.6924636645000485, \"daily_returns\": 0.031016041472065006, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": 0.005019203090565127, \"return\": -0.23422461540750483, \"returns_over_hodl\": 1.543365435452415e-11, \"returns_over_uniform_hodl\": 0.27259154370943084, \"sharpe\": -0.2021086089732154, \"sterling\": -1.2473844012798234, \"ulcer\": -0.17641031386892123}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -0.1550710052041172, \"optuna_trial_number\": 289, \"price_ratio\": 1.8112028021865934, \"shift_exponent\": 0.0011207635771965747, \"step\": 289, \"test_objective\": [{\"annualised_returns\": -0.4845064800441996, \"annualised_returns_over_hodl\": 3.8321790185591453e-11, \"annualised_returns_over_uniform_hodl\": 0.8194636558729569, \"calmar\": -0.8358050111679293, \"daily_log_sharpe\": -0.6924636645000485, \"daily_returns\": 0.031016041472065006, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": 0.005019203090565127, \"return\": -0.23422461540750483, \"returns_over_hodl\": 1.543365435452415e-11, \"returns_over_uniform_hodl\": 0.27259154370943084, \"sharpe\": -0.2021086089732154, \"sterling\": -1.2473844012798234, \"ulcer\": -0.17641031386892123}], \"train_objective\": [{\"annualised_returns\": -0.6150536135139515, \"annualised_returns_over_hodl\": -0.5152223201639174, \"annualised_returns_over_uniform_hodl\": -0.5152223201639172, \"calmar\": -0.7906624233828463, \"daily_log_sharpe\": -0.6443274144342418, \"daily_returns\": 0.008765497525892927, \"fee_revenue_over_value\": 0.03440233373370852, \"jax_sharpe\": 0.11810142308298982, \"return\": -0.439871560360506, \"returns_over_hodl\": -0.35570286917307625, \"returns_over_uniform_hodl\": -0.35570286917307614, \"sharpe\": 0.025527121676578658, \"sterling\": -1.6715961539040467, \"ulcer\": -0.20432110787265761}], \"train_return\": -0.439871560360506, \"train_returns_over_hodl\": -0.35570286917307625, \"train_sharpe\": 0.1181014230829898, \"validation_return\": -0.3391427111247438, \"validation_returns_over_hodl\": -4.591820590427176e-09, \"validation_sharpe\": -2.2438531819465575}, {\"centeredness_margin\": 0.022515732958503266, \"continuous_test_metrics\": [{\"annualised_returns\": -0.47903383754284923, \"annualised_returns_over_hodl\": 0.010616310560489728, \"annualised_returns_over_uniform_hodl\": 0.838779658397385, \"calmar\": -0.8263643331631418, \"daily_log_sharpe\": -0.6909295610106839, \"daily_returns\": 0.031016041623429764, \"fee_revenue_over_value\": 0.0024714481880157547, \"jax_sharpe\": -0.011403813846189617, \"return\": -0.23096079401160574, \"returns_over_hodl\": 0.004262110741853009, \"returns_over_uniform_hodl\": 0.27801547296096896, \"sharpe\": -0.20306081376024462, \"sterling\": -1.2332948028087496, \"ulcer\": -0.17641031418184502}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -0.10926429857054643, \"optuna_trial_number\": 290, \"price_ratio\": 2.9610065099254417, \"shift_exponent\": 0.002070763126269846, \"step\": 290, \"test_objective\": [{\"annualised_returns\": -0.47903383754284923, \"annualised_returns_over_hodl\": 0.010616310560489728, \"annualised_returns_over_uniform_hodl\": 0.838779658397385, \"calmar\": -0.8263643331631418, \"daily_log_sharpe\": -0.6909295610106839, \"daily_returns\": 0.031016041623429764, \"fee_revenue_over_value\": 0.0024714481880157547, \"jax_sharpe\": -0.011403813846189617, \"return\": -0.23096079401160574, \"returns_over_hodl\": 0.004262110741853009, \"returns_over_uniform_hodl\": 0.27801547296096896, \"sharpe\": -0.20306081376024462, \"sterling\": -1.2332948028087496, \"ulcer\": -0.17641031418184502}], \"train_objective\": [{\"annualised_returns\": -0.5309223309528238, \"annualised_returns_over_hodl\": -0.40927258432168867, \"annualised_returns_over_uniform_hodl\": -0.40927258432168867, \"calmar\": -0.6991413058403131, \"daily_log_sharpe\": -0.51743673969387, \"daily_returns\": 0.008412113774858149, \"fee_revenue_over_value\": 0.03835775958578812, \"jax_sharpe\": 0.1492154972655279, \"return\": -0.3684530205386318, \"returns_over_hodl\": -0.2735524960824014, \"returns_over_uniform_hodl\": -0.2735524960824014, \"sharpe\": 0.12061988286912582, \"sterling\": -1.48945127405932, \"ulcer\": -0.1945632113463669}], \"train_return\": -0.3684530205386318, \"train_returns_over_hodl\": -0.2735524960824014, \"train_sharpe\": 0.1492154972655279, \"validation_return\": -0.3391427119173085, \"validation_returns_over_hodl\": -3.2160508700584955e-09, \"validation_sharpe\": -2.243853179380939}, {\"centeredness_margin\": 0.7293932895602651, \"continuous_test_metrics\": [{\"annualised_returns\": -0.6696567377912901, \"annualised_returns_over_hodl\": 0.12382103193753258, \"annualised_returns_over_uniform_hodl\": 0.16596530564107437, \"calmar\": -1.172395111181282, \"daily_log_sharpe\": -1.4391737496319812, \"daily_returns\": 0.018918667595957187, \"fee_revenue_over_value\": 0.07183075745839262, \"jax_sharpe\": -0.9043476463765778, \"return\": -0.35986850756402833, \"returns_over_hodl\": 0.04813605852173297, \"returns_over_uniform_hodl\": 0.06379225622356022, \"sharpe\": -1.056968394276634, \"sterling\": -2.0588052003773925, \"ulcer\": -0.14749646742759553}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -2.4955892618479583, \"optuna_trial_number\": 291, \"price_ratio\": 24.135809797676906, \"shift_exponent\": 2.146997769399618, \"step\": 291, \"test_objective\": [{\"annualised_returns\": -0.6696567377912901, \"annualised_returns_over_hodl\": 0.12382103193753258, \"annualised_returns_over_uniform_hodl\": 0.16596530564107437, \"calmar\": -1.172395111181282, \"daily_log_sharpe\": -1.4391737496319812, \"daily_returns\": 0.018918667595957187, \"fee_revenue_over_value\": 0.07183075745839262, \"jax_sharpe\": -0.9043476463765778, \"return\": -0.35986850756402833, \"returns_over_hodl\": 0.04813605852173297, \"returns_over_uniform_hodl\": 0.06379225622356022, \"sharpe\": -1.056968394276634, \"sterling\": -2.0588052003773925, \"ulcer\": -0.14749646742759553}], \"train_objective\": [{\"annualised_returns\": -0.3110410542992531, \"annualised_returns_over_hodl\": -0.13236769866074227, \"annualised_returns_over_uniform_hodl\": -0.13236769866074227, \"calmar\": -0.42544209755859086, \"daily_log_sharpe\": -0.3162476324740191, \"daily_returns\": 0.008064270130264551, \"fee_revenue_over_value\": 0.032807124880018274, \"jax_sharpe\": 0.1329201186917972, \"return\": -0.2024393500277536, \"returns_over_hodl\": -0.0825924875939067, \"returns_over_uniform_hodl\": -0.0825924875939067, \"sharpe\": 0.17398416479525225, \"sterling\": -1.0279728306379112, \"ulcer\": -0.1634309524088125}], \"train_return\": -0.2024393500277536, \"train_returns_over_hodl\": -0.0825924875939067, \"train_sharpe\": 0.1329201186917972, \"validation_return\": -0.16443380207910074, \"validation_returns_over_hodl\": -0.02386360311330893, \"validation_sharpe\": -1.2966894666521125}, {\"centeredness_margin\": 0.010140748947455824, \"continuous_test_metrics\": [{\"annualised_returns\": -0.4488322428660796, \"annualised_returns_over_hodl\": 0.0692040379776877, \"annualised_returns_over_uniform_hodl\": 0.9453779020930579, \"calmar\": -0.8121736484308438, \"daily_log_sharpe\": -0.6932246185752394, \"daily_returns\": 0.03132823901024795, \"fee_revenue_over_value\": 0.13244475403229405, \"jax_sharpe\": -0.07293282237091968, \"return\": -0.21330715141705148, \"returns_over_hodl\": 0.027315400946044788, \"returns_over_uniform_hodl\": 0.3073528958313234, \"sharpe\": -0.24435948211966405, \"sterling\": -1.211326393182325, \"ulcer\": -0.1622561593180265}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -3.553092352020335, \"optuna_trial_number\": 292, \"price_ratio\": 3.4193732313249683, \"shift_exponent\": 0.01550214443605957, \"step\": 292, \"test_objective\": [{\"annualised_returns\": -0.4488322428660796, \"annualised_returns_over_hodl\": 0.0692040379776877, \"annualised_returns_over_uniform_hodl\": 0.9453779020930579, \"calmar\": -0.8121736484308438, \"daily_log_sharpe\": -0.6932246185752394, \"daily_returns\": 0.03132823901024795, \"fee_revenue_over_value\": 0.13244475403229405, \"jax_sharpe\": -0.07293282237091968, \"return\": -0.21330715141705148, \"returns_over_hodl\": 0.027315400946044788, \"returns_over_uniform_hodl\": 0.3073528958313234, \"sharpe\": -0.24435948211966405, \"sterling\": -1.211326393182325, \"ulcer\": -0.1622561593180265}], \"train_objective\": [{\"annualised_returns\": -0.48990121667661113, \"annualised_returns_over_hodl\": -0.35761312913198795, \"annualised_returns_over_uniform_hodl\": -0.35761312913198817, \"calmar\": -0.6510243699571295, \"daily_log_sharpe\": -0.47898847676789186, \"daily_returns\": 0.008367041624324851, \"fee_revenue_over_value\": 0.046032114473819066, \"jax_sharpe\": 0.19689571640261097, \"return\": -0.33547600669053745, \"returns_over_hodl\": -0.2356201328921984, \"returns_over_uniform_hodl\": -0.23562013289219852, \"sharpe\": 0.12928561358304932, \"sterling\": -1.4208527344124386, \"ulcer\": -0.18655752205362358}], \"train_return\": -0.33547600669053745, \"train_returns_over_hodl\": -0.2356201328921984, \"train_sharpe\": 0.19689571640261097, \"validation_return\": -0.3299536698594647, \"validation_returns_over_hodl\": -0.033220122104301475, \"validation_sharpe\": -2.285237185857144}, {\"centeredness_margin\": 0.017171885533146674, \"continuous_test_metrics\": [{\"annualised_returns\": -0.4128483344699615, \"annualised_returns_over_hodl\": 0.13900880814677663, \"annualised_returns_over_uniform_hodl\": 1.0723851504647, \"calmar\": -0.7458036017806764, \"daily_log_sharpe\": -0.5940729730717247, \"daily_returns\": 0.03101604166012603, \"fee_revenue_over_value\": 0.08633625441878366, \"jax_sharpe\": 0.05426463138877609, \"return\": -0.19301214634900776, \"returns_over_hodl\": 0.05381795589228888, \"returns_over_uniform_hodl\": 0.3410798245741118, \"sharpe\": -0.12205640601626844, \"sterling\": -1.0725612824748334, \"ulcer\": -0.17319983282864587}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -4.074486221107497, \"optuna_trial_number\": 293, \"price_ratio\": 6.075060654757881, \"shift_exponent\": 0.0038373634186586393, \"step\": 293, \"test_objective\": [{\"annualised_returns\": -0.4128483344699615, \"annualised_returns_over_hodl\": 0.13900880814677663, \"annualised_returns_over_uniform_hodl\": 1.0723851504647, \"calmar\": -0.7458036017806764, \"daily_log_sharpe\": -0.5940729730717247, \"daily_returns\": 0.03101604166012603, \"fee_revenue_over_value\": 0.08633625441878366, \"jax_sharpe\": 0.05426463138877609, \"return\": -0.19301214634900776, \"returns_over_hodl\": 0.05381795589228888, \"returns_over_uniform_hodl\": 0.3410798245741118, \"sharpe\": -0.12205640601626844, \"sterling\": -1.0725612824748334, \"ulcer\": -0.17319983282864587}], \"train_objective\": [{\"annualised_returns\": -0.4232118248232968, \"annualised_returns_over_hodl\": -0.2736286321025536, \"annualised_returns_over_uniform_hodl\": -0.2736286321025537, \"calmar\": -0.5736143555508357, \"daily_log_sharpe\": -0.39129999344209404, \"daily_returns\": 0.008211480364577587, \"fee_revenue_over_value\": 0.04701003935741444, \"jax_sharpe\": 0.17214974653427798, \"return\": -0.28400842119654346, \"returns_over_hodl\": -0.17641867958675272, \"returns_over_uniform_hodl\": -0.17641867958675284, \"sharpe\": 0.1891799790715171, \"sterling\": -1.2722019483568525, \"ulcer\": -0.17935141241573732}], \"train_return\": -0.28400842119654346, \"train_returns_over_hodl\": -0.17641867958675272, \"train_sharpe\": 0.17214974653427798, \"validation_return\": -0.3378084919119747, \"validation_returns_over_hodl\": -0.024249284163081586, \"validation_sharpe\": -2.232484241603597}, {\"centeredness_margin\": 0.1973601251340913, \"continuous_test_metrics\": [{\"annualised_returns\": -0.5720707361362549, \"annualised_returns_over_hodl\": 0.10603752777042974, \"annualised_returns_over_uniform_hodl\": 0.5104006408292223, \"calmar\": -1.0466887476844422, \"daily_log_sharpe\": -1.0488962405551316, \"daily_returns\": 0.024536062070437972, \"fee_revenue_over_value\": 0.09666859947207151, \"jax_sharpe\": -0.4435695340864763, \"return\": -0.28954012310986843, \"returns_over_hodl\": 0.041424473224701774, \"returns_over_uniform_hodl\": 0.1806663542160627, \"sharpe\": -0.6383003687978139, \"sterling\": -1.6217880743797521, \"ulcer\": -0.15295750235219654}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -3.11177201632865, \"optuna_trial_number\": 294, \"price_ratio\": 7.064568587933647, \"shift_exponent\": 0.015106874565127314, \"step\": 294, \"test_objective\": [{\"annualised_returns\": -0.5720707361362549, \"annualised_returns_over_hodl\": 0.10603752777042974, \"annualised_returns_over_uniform_hodl\": 0.5104006408292223, \"calmar\": -1.0466887476844422, \"daily_log_sharpe\": -1.0488962405551316, \"daily_returns\": 0.024536062070437972, \"fee_revenue_over_value\": 0.09666859947207151, \"jax_sharpe\": -0.4435695340864763, \"return\": -0.28954012310986843, \"returns_over_hodl\": 0.041424473224701774, \"returns_over_uniform_hodl\": 0.1806663542160627, \"sharpe\": -0.6383003687978139, \"sterling\": -1.6217880743797521, \"ulcer\": -0.15295750235219654}], \"train_objective\": [{\"annualised_returns\": -0.4043046862380977, \"annualised_returns_over_hodl\": -0.24981815070190727, \"annualised_returns_over_uniform_hodl\": -0.24981815070190727, \"calmar\": -0.5445437041650031, \"daily_log_sharpe\": -0.4055826222913519, \"daily_returns\": 0.008184318436058617, \"fee_revenue_over_value\": 0.03376674515649229, \"jax_sharpe\": 0.11998287129104933, \"return\": -0.26984952726377365, \"returns_over_hodl\": -0.16013217440155325, \"returns_over_uniform_hodl\": -0.16013217440155325, \"sharpe\": 0.13947881049062122, \"sterling\": -1.2494106484492171, \"ulcer\": -0.1743378601923102}], \"train_return\": -0.26984952726377365, \"train_returns_over_hodl\": -0.16013217440155325, \"train_sharpe\": 0.11998287129104931, \"validation_return\": -0.24449078973867677, \"validation_returns_over_hodl\": -0.0338504495054871, \"validation_sharpe\": -1.8605253212449837}, {\"centeredness_margin\": 0.145722870836099, \"continuous_test_metrics\": [{\"annualised_returns\": -0.48078354878929197, \"annualised_returns_over_hodl\": 0.0072220616460070275, \"annualised_returns_over_uniform_hodl\": 0.8326039531791158, \"calmar\": -0.8293827035864603, \"daily_log_sharpe\": -0.687993007942978, \"daily_returns\": 0.03101604172171662, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": -0.003806924273336813, \"return\": -0.2320020653552478, \"returns_over_hodl\": 0.0029023483311321208, \"returns_over_uniform_hodl\": 0.2762850528752787, \"sharpe\": -0.19844844376695656, \"sterling\": -1.237799521585329, \"ulcer\": -0.17641031438503368}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -0.0938463344556183, \"optuna_trial_number\": 295, \"price_ratio\": 5.738322744730176, \"shift_exponent\": 0.00019035907834798204, \"step\": 295, \"test_objective\": [{\"annualised_returns\": -0.48078354878929197, \"annualised_returns_over_hodl\": 0.0072220616460070275, \"annualised_returns_over_uniform_hodl\": 0.8326039531791158, \"calmar\": -0.8293827035864603, \"daily_log_sharpe\": -0.687993007942978, \"daily_returns\": 0.03101604172171662, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": -0.003806924273336813, \"return\": -0.2320020653552478, \"returns_over_hodl\": 0.0029023483311321208, \"returns_over_uniform_hodl\": 0.2762850528752787, \"sharpe\": -0.19844844376695656, \"sterling\": -1.237799521585329, \"ulcer\": -0.17641031438503368}], \"train_objective\": [{\"annualised_returns\": -0.45622901966659657, \"annualised_returns_over_hodl\": -0.31520844600064, \"annualised_returns_over_uniform_hodl\": -0.31520844600064, \"calmar\": -0.6139810760027772, \"daily_log_sharpe\": -0.4354108547627764, \"daily_returns\": 0.008223048888600376, \"fee_revenue_over_value\": 0.032299952290758105, \"jax_sharpe\": 0.13683930439208347, \"return\": -0.30917918992065385, \"returns_over_hodl\": -0.20537177841546095, \"returns_over_uniform_hodl\": -0.20537177841546095, \"sharpe\": 0.1507425376936597, \"sterling\": -1.3585502008101065, \"ulcer\": -0.18143046483479844}], \"train_return\": -0.30917918992065385, \"train_returns_over_hodl\": -0.20537177841546095, \"train_sharpe\": 0.13683930439208347, \"validation_return\": -0.33914271240425353, \"validation_returns_over_hodl\": -2.7642356181800665e-09, \"validation_sharpe\": -2.243853177425984}, {\"centeredness_margin\": 0.14658324586243493, \"continuous_test_metrics\": [{\"annualised_returns\": -0.4781507729968405, \"annualised_returns_over_hodl\": 0.012329353756167727, \"annualised_returns_over_uniform_hodl\": 0.8418964848657193, \"calmar\": -0.8248409873732923, \"daily_log_sharpe\": -0.6807098733786161, \"daily_returns\": 0.031016041711322852, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": 0.007076668394660829, \"return\": -0.23043606613508805, \"returns_over_hodl\": 0.004947333553791067, \"returns_over_uniform_hodl\": 0.27888748356857085, \"sharpe\": -0.19000835483384496, \"sterling\": -1.2310213069449292, \"ulcer\": -0.17641031436354596}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -0.08573013997078383, \"optuna_trial_number\": 296, \"price_ratio\": 5.424872111844742, \"shift_exponent\": 4.9976142836030774e-05, \"step\": 296, \"test_objective\": [{\"annualised_returns\": -0.4781507729968405, \"annualised_returns_over_hodl\": 0.012329353756167727, \"annualised_returns_over_uniform_hodl\": 0.8418964848657193, \"calmar\": -0.8248409873732923, \"daily_log_sharpe\": -0.6807098733786161, \"daily_returns\": 0.031016041711322852, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": 0.007076668394660829, \"return\": -0.23043606613508805, \"returns_over_hodl\": 0.004947333553791067, \"returns_over_uniform_hodl\": 0.27888748356857085, \"sharpe\": -0.19000835483384496, \"sterling\": -1.2310213069449292, \"ulcer\": -0.17641031436354596}], \"train_objective\": [{\"annualised_returns\": -0.4640080636814017, \"annualised_returns_over_hodl\": -0.3250048930936086, \"annualised_returns_over_uniform_hodl\": -0.3250048930936086, \"calmar\": -0.6238541423178012, \"daily_log_sharpe\": -0.4430074970695694, \"daily_returns\": 0.008178720748678587, \"fee_revenue_over_value\": 0.03269043526137818, \"jax_sharpe\": 0.1867226131509606, \"return\": -0.31519616266610606, \"returns_over_hodl\": -0.212292902218162, \"returns_over_uniform_hodl\": -0.212292902218162, \"sharpe\": 0.14821200400336534, \"sterling\": -1.3742688960368026, \"ulcer\": -0.1825343592214325}], \"train_return\": -0.31519616266610606, \"train_returns_over_hodl\": -0.212292902218162, \"train_sharpe\": 0.18672261315096056, \"validation_return\": -0.3391427123917947, \"validation_returns_over_hodl\": -2.5181102758509155e-09, \"validation_sharpe\": -2.243853178023819}, {\"centeredness_margin\": 0.14267282397599818, \"continuous_test_metrics\": [{\"annualised_returns\": -0.47843621890254096, \"annualised_returns_over_hodl\": 0.011775620412022736, \"annualised_returns_over_uniform_hodl\": 0.8408889873297964, \"calmar\": -0.8253334008539487, \"daily_log_sharpe\": -0.6815575578548818, \"daily_returns\": 0.031016041713563963, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": 0.0051043613060699905, \"return\": -0.2306056235763756, \"returns_over_hodl\": 0.004725914228471906, \"returns_over_uniform_hodl\": 0.27860570725361344, \"sharpe\": -0.1909695550461787, \"sterling\": -1.2317562018626522, \"ulcer\": -0.17641031436817325}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -0.0828441112175885, \"optuna_trial_number\": 297, \"price_ratio\": 5.473800571217754, \"shift_exponent\": 7.62023586041015e-05, \"step\": 297, \"test_objective\": [{\"annualised_returns\": -0.47843621890254096, \"annualised_returns_over_hodl\": 0.011775620412022736, \"annualised_returns_over_uniform_hodl\": 0.8408889873297964, \"calmar\": -0.8253334008539487, \"daily_log_sharpe\": -0.6815575578548818, \"daily_returns\": 0.031016041713563963, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": 0.0051043613060699905, \"return\": -0.2306056235763756, \"returns_over_hodl\": 0.004725914228471906, \"returns_over_uniform_hodl\": 0.27860570725361344, \"sharpe\": -0.1909695550461787, \"sterling\": -1.2317562018626522, \"ulcer\": -0.17641031436817325}], \"train_objective\": [{\"annualised_returns\": -0.4630794005394441, \"annualised_returns_over_hodl\": -0.3238353921472117, \"annualised_returns_over_uniform_hodl\": -0.3238353921472117, \"calmar\": -0.622765274133326, \"daily_log_sharpe\": -0.44221384283494286, \"daily_returns\": 0.008250333353362614, \"fee_revenue_over_value\": 0.032716951247399574, \"jax_sharpe\": 0.18597938760975546, \"return\": -0.314476060778199, \"returns_over_hodl\": -0.21146459294577402, \"returns_over_uniform_hodl\": -0.21146459294577402, \"sharpe\": 0.1482079513527728, \"sterling\": -1.3727276713577794, \"ulcer\": -0.18233124377869864}], \"train_return\": -0.314476060778199, \"train_returns_over_hodl\": -0.21146459294577402, \"train_sharpe\": 0.1859793876097555, \"validation_return\": -0.3391427123939309, \"validation_returns_over_hodl\": -2.432285706177595e-09, \"validation_sharpe\": -2.2438531778870403}, {\"centeredness_margin\": 0.1655886270636035, \"continuous_test_metrics\": [{\"annualised_returns\": -0.479938080920843, \"annualised_returns_over_hodl\": 0.008862174924676225, \"annualised_returns_over_uniform_hodl\": 0.8355880800387192, \"calmar\": -0.8279242143690867, \"daily_log_sharpe\": -0.6858745443717487, \"daily_returns\": 0.03101604172608096, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": 0.0007110069141095713, \"return\": -0.2314986573962282, \"returns_over_hodl\": 0.0035597315689759323, \"returns_over_uniform_hodl\": 0.2771216333198505, \"sharpe\": -0.1960541058873042, \"sterling\": -1.235622821588875, \"ulcer\": -0.1764103143940508}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -0.09326671855579993, \"optuna_trial_number\": 298, \"price_ratio\": 6.023468602391574, \"shift_exponent\": 2.861479963845746e-05, \"step\": 298, \"test_objective\": [{\"annualised_returns\": -0.479938080920843, \"annualised_returns_over_hodl\": 0.008862174924676225, \"annualised_returns_over_uniform_hodl\": 0.8355880800387192, \"calmar\": -0.8279242143690867, \"daily_log_sharpe\": -0.6858745443717487, \"daily_returns\": 0.03101604172608096, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": 0.0007110069141095713, \"return\": -0.2314986573962282, \"returns_over_hodl\": 0.0035597315689759323, \"returns_over_uniform_hodl\": 0.2771216333198505, \"sharpe\": -0.1960541058873042, \"sterling\": -1.235622821588875, \"ulcer\": -0.1764103143940508}], \"train_objective\": [{\"annualised_returns\": -0.4527864180850215, \"annualised_returns_over_hodl\": -0.3108730464075944, \"annualised_returns_over_uniform_hodl\": -0.3108730464075944, \"calmar\": -0.6098924078797495, \"daily_log_sharpe\": -0.4325585765010115, \"daily_returns\": 0.008189295708976358, \"fee_revenue_over_value\": 0.031455864510912424, \"jax_sharpe\": 0.1823708437027292, \"return\": -0.3065271942212586, \"returns_over_hodl\": -0.20232127588931637, \"returns_over_uniform_hodl\": -0.20232127588931637, \"sharpe\": 0.15071205465928803, \"sterling\": -1.3522792806910424, \"ulcer\": -0.18086166654450953}], \"train_return\": -0.3065271942212586, \"train_returns_over_hodl\": -0.20232127588931637, \"train_sharpe\": 0.18237084370272919, \"validation_return\": -0.3391427124141302, \"validation_returns_over_hodl\": -2.7478161968019776e-09, \"validation_sharpe\": -2.243853177218603}, {\"centeredness_margin\": 0.19811860689672062, \"continuous_test_metrics\": [{\"annualised_returns\": -0.5084123095929269, \"annualised_returns_over_hodl\": -0.046374654961027995, \"annualised_returns_over_uniform_hodl\": 0.7350866727614467, \"calmar\": -0.8770617646062054, \"daily_log_sharpe\": -0.7520906079843405, \"daily_returns\": 0.03101604164408431, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": -0.0688467348140607, \"return\": -0.24872998101127108, \"returns_over_hodl\": -0.018942066708807603, \"returns_over_uniform_hodl\": 0.24848603447373008, \"sharpe\": -0.26877603149421614, \"sterling\": -1.3089843058282065, \"ulcer\": -0.17639478500673028}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -3.7254346540780805, \"optuna_trial_number\": 299, \"price_ratio\": 8.631692113677602, \"shift_exponent\": 0.0001336191591218227, \"step\": 299, \"test_objective\": [{\"annualised_returns\": -0.5084123095929269, \"annualised_returns_over_hodl\": -0.046374654961027995, \"annualised_returns_over_uniform_hodl\": 0.7350866727614467, \"calmar\": -0.8770617646062054, \"daily_log_sharpe\": -0.7520906079843405, \"daily_returns\": 0.03101604164408431, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": -0.0688467348140607, \"return\": -0.24872998101127108, \"returns_over_hodl\": -0.018942066708807603, \"returns_over_uniform_hodl\": 0.24848603447373008, \"sharpe\": -0.26877603149421614, \"sterling\": -1.3089843058282065, \"ulcer\": -0.17639478500673028}], \"train_objective\": [{\"annualised_returns\": -0.41977259116415155, \"annualised_returns_over_hodl\": -0.2692974738627941, \"annualised_returns_over_uniform_hodl\": -0.26929747386279435, \"calmar\": -0.5677870867547418, \"daily_log_sharpe\": -0.40571307407562146, \"daily_returns\": 0.008166564040961362, \"fee_revenue_over_value\": 0.028709408900358974, \"jax_sharpe\": 0.1346047618342165, \"return\": -0.28141948593722965, \"returns_over_hodl\": -0.17344071338931022, \"returns_over_uniform_hodl\": -0.17344071338931033, \"sharpe\": 0.1547619337236997, \"sterling\": -1.282543517743613, \"ulcer\": -0.17646703806360958}], \"train_return\": -0.28141948593722965, \"train_returns_over_hodl\": -0.17344071338931022, \"train_sharpe\": 0.1346047618342165, \"validation_return\": -0.330792937298946, \"validation_returns_over_hodl\": -0.05687280255609284, \"validation_sharpe\": -2.171481281844569}]" \ No newline at end of file diff --git a/results/run_0e471ee46490550772bc381f10ae15f90d406c94406139d1ea9af59decc3c4d2.json b/results/run_0e471ee46490550772bc381f10ae15f90d406c94406139d1ea9af59decc3c4d2.json new file mode 100644 index 0000000..10ecfec --- /dev/null +++ b/results/run_0e471ee46490550772bc381f10ae15f90d406c94406139d1ea9af59decc3c4d2.json @@ -0,0 +1 @@ +"[{\"alphabetic\": true, \"arb_fees\": 0.0, \"arb_frequency\": 3, \"arb_quality\": 1.0, \"bout_offset\": 10080, \"checkpoint_fused\": \"scan\", \"chunk_period\": 1440, \"do_arb\": true, \"do_trades\": false, \"endDateString\": \"2025-10-05 00:00:00\", \"endTestDateString\": \"2026-03-01 00:00:00\", \"ensemble_init_method\": \"gaussian\", \"ensemble_init_scale\": 0.5, \"ensemble_init_seed\": 42, \"evaluation_starts\": [77760, 82489, 84681, 87218, 91947, 94849, 96677, 96743, 99003, 101406, 106135, 109990, 110864, 115594, 117272, 119753, 120323, 120373, 121327, 123651, 125052, 126360, 129781, 130380, 132786, 134511, 139240, 143969, 146028, 148698, 153428, 157946, 158157, 158231, 158726, 160217, 162886, 164053, 167448, 167616], \"fees\": 0.003, \"freq\": \"minute\", \"gas_cost\": 3.0, \"initial_arc_length_speed\": 0.0001, \"initial_centeredness_margin\": 0.2, \"initial_daily_price_shift_base\": 0.999991935483871, \"initial_k_per_day\": 20, \"initial_log_amplitude\": 0.0, \"initial_memory_length\": 10.0, \"initial_memory_length_delta\": 0.0, \"initial_pool_value\": 2000000.0, \"initial_pre_exp_scaling\": 0.5, \"initial_price_ratio\": 4.0, \"initial_raw_exponents\": 0.0, \"initial_raw_width\": 0.0, \"initial_shift_exponent\": 1.0, \"initial_weights_logits\": 1.0, \"learnable_bounds_settings\": {\"freeze_bounds\": false, \"max_weights_per_asset\": null, \"min_weights_per_asset\": null}, \"max_memory_days\": 365, \"maximum_change\": 0.0003, \"minimum_weight\": null, \"n_ensemble_members\": 1, \"noise_arrays_path\": \"results/mm_noise/_sim_arrays/0xd321300ef77067_2025-01-01_2026-03-01_mm.npz\", \"noise_model\": \"mm_observed\", \"noise_trader_ratio\": 0.0, \"numeraire\": null, \"optimisation_settings\": {\"base_lr\": 0.1, \"batch_size\": 8, \"bfgs_settings\": {\"compute_dtype\": \"float32\", \"maxiter\": 100, \"n_evaluation_points\": 20, \"tol\": 1e-06}, \"checkpoint_interval\": 10, \"clip_norm\": 10.0, \"cma_es_settings\": {\"compute_dtype\": \"float32\", \"memory_budget\": null, \"n_evaluation_points\": 20, \"n_generations\": 300, \"population_size\": null, \"sigma0\": 0.5, \"tol\": 1e-08}, \"decay_lr_plateau\": 100, \"decay_lr_ratio\": 0.8, \"early_stopping\": true, \"early_stopping_metric\": \"daily_log_sharpe\", \"early_stopping_patience\": 200, \"force_scalar\": false, \"include_flipped_training_data\": false, \"initial_random_key\": 0, \"lr_decay_ratio\": 1000, \"lr_schedule_type\": \"constant\", \"max_mc_version\": 9, \"method\": \"optuna\", \"min_lr\": 1e-06, \"n_cycles\": 5, \"n_iterations\": 1000, \"n_parameter_sets\": 1, \"noise_scale\": 0.1, \"optimiser\": \"adamw\", \"optuna_settings\": {\"early_stopping\": {\"enabled\": false, \"min_improvement\": 0.001, \"patience\": 100}, \"expand_around\": false, \"make_scalar\": true, \"min_train_returns_over_hodl\": -0.5, \"multi_objective\": false, \"n_jobs\": 4, \"n_startup_trials\": 10, \"n_trials\": 300, \"overfitting_penalty\": 1.0, \"parameter_config\": {\"centeredness_margin\": {\"high\": 0.99, \"low\": 0.01, \"scalar\": true}, \"k_per_day\": {\"high\": 1000, \"log_scale\": true, \"low\": 0.1, \"scalar\": true}, \"log_amplitude\": {\"high\": 10, \"log_scale\": false, \"low\": -10, \"scalar\": true}, \"log_k\": {\"high\": 10.0, \"log_scale\": false, \"low\": -10.0, \"scalar\": true}, \"logit_lamb\": {\"high\": 4.602848117654388, \"log_scale\": false, \"low\": -5.955817303419269, \"scalar\": true}, \"memory_days_1\": {\"high\": 200, \"log_scale\": true, \"low\": 0.5, \"scalar\": true}, \"memory_days_2\": {\"high\": 200, \"log_scale\": true, \"low\": 0.5, \"scalar\": true}, \"memory_length\": {\"high\": 200, \"log_scale\": true, \"low\": 1, \"scalar\": true}, \"memory_length_delta\": {\"high\": 100, \"log_scale\": true, \"low\": 0.1, \"scalar\": true}, \"price_ratio\": {\"high\": 200.0, \"log_scale\": true, \"low\": 1.01, \"scalar\": true}, \"raw_exponents\": {\"high\": 10, \"log_scale\": false, \"low\": 0, \"scalar\": true}, \"raw_pre_exp_scaling\": {\"high\": 10, \"log_scale\": false, \"low\": -10, \"scalar\": true}, \"raw_width\": {\"high\": 10, \"log_scale\": false, \"low\": -10, \"scalar\": true}, \"shift_exponent\": {\"high\": 125.0, \"log_scale\": true, \"low\": 1e-05, \"scalar\": true}, \"weights_logits\": {\"high\": 10, \"log_scale\": false, \"low\": -10, \"scalar\": true}}, \"storage\": {\"type\": \"sqlite\", \"url\": null}, \"study_name\": null, \"timeout\": 7200}, \"parameter_init_method\": \"gaussian\", \"sample_method\": \"uniform\", \"swa_freq\": 10, \"swa_start_frac\": 0.75, \"track_checkpoints\": false, \"train_on_hessian_trace\": false, \"training_data_kind\": \"historic\", \"use_gradient_clipping\": true, \"use_plateau_decay\": false, \"use_swa\": false, \"val_fraction\": 0.2, \"warmup_steps\": 100, \"weight_decay\": 0.01}, \"price_noise_sigma\": 0.0, \"protocol_fee_split\": 0.25, \"reclamm_arc_length_speed\": null, \"reclamm_centeredness_scaling\": false, \"reclamm_interpolation_method\": \"geometric\", \"reclamm_learn_arc_length_speed\": false, \"reclamm_learn_fees\": false, \"reclamm_use_shift_exponent\": true, \"return_val\": \"returns_over_hodl\", \"rule\": \"reclamm\", \"startDateString\": \"2025-01-01 00:00:00\", \"ste_max_change\": false, \"ste_min_max_weight\": false, \"ste_temperature\": 10.0, \"subsidary_pools\": [], \"tokens\": [\"COW\", \"ETH\"], \"training_method\": \"optuna\", \"turnover_penalty\": 0.0, \"use_alt_lamb\": false, \"use_fused_reserves\": true, \"use_pre_exp_scaling\": true, \"weight_calculation_method\": \"auto\", \"weight_interpolation_method\": \"linear\", \"weight_interpolation_period\": 1440}, {\"centeredness_margin\": 0.6803513521719909, \"continuous_test_metrics\": [{\"annualised_returns\": -0.7201129120186481, \"annualised_returns_over_hodl\": -0.056126478266644186, \"annualised_returns_over_uniform_hodl\": -0.012122626926813429, \"calmar\": -1.2177871494349668, \"daily_log_sharpe\": -1.6249495477161169, \"daily_returns\": 0.01905966396643997, \"fee_revenue_over_value\": 0.050179554851284495, \"jax_sharpe\": -1.116650926144809, \"return\": -0.4012037358053818, \"returns_over_hodl\": -0.022994879968363913, \"returns_over_uniform_hodl\": -0.004900030020712487, \"sharpe\": -1.2474101523896004, \"sterling\": -2.217845918668267, \"ulcer\": -0.15166828872485505}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -0.025320091562264557, \"optuna_trial_number\": 0, \"price_ratio\": 14.578805561226815, \"shift_exponent\": 26.90684351671307, \"step\": 0, \"test_objective\": [{\"annualised_returns\": -0.7201129120186481, \"annualised_returns_over_hodl\": -0.056126478266644186, \"annualised_returns_over_uniform_hodl\": -0.012122626926813429, \"calmar\": -1.2177871494349668, \"daily_log_sharpe\": -1.6249495477161169, \"daily_returns\": 0.01905966396643997, \"fee_revenue_over_value\": 0.050179554851284495, \"jax_sharpe\": -1.116650926144809, \"return\": -0.4012037358053818, \"returns_over_hodl\": -0.022994879968363913, \"returns_over_uniform_hodl\": -0.004900030020712487, \"sharpe\": -1.2474101523896004, \"sterling\": -2.217845918668267, \"ulcer\": -0.15166828872485505}], \"train_objective\": [{\"annualised_returns\": -0.34771342385646775, \"annualised_returns_over_hodl\": -0.17855061361241198, \"annualised_returns_over_uniform_hodl\": -0.17855061361241198, \"calmar\": -0.4703389972731299, \"daily_log_sharpe\": -0.3736000369682451, \"daily_returns\": 0.008082976149242445, \"fee_revenue_over_value\": 0.026082728332086597, \"jax_sharpe\": 0.07801244329972819, \"return\": -0.22848991174959277, \"returns_over_hodl\": -0.11255758307203156, \"returns_over_uniform_hodl\": -0.11255758307203156, \"sharpe\": 0.1142031712643486, \"sterling\": -1.144178834346483, \"ulcer\": -0.16462208216510302}], \"train_return\": -0.22848991174959277, \"train_returns_over_hodl\": -0.11255758307203156, \"train_sharpe\": 0.07801244329972819, \"validation_return\": -0.16722601353095257, \"validation_returns_over_hodl\": -0.025320091562264557, \"validation_sharpe\": -1.3288849179787647}, {\"centeredness_margin\": 0.5610704604981004, \"continuous_test_metrics\": [{\"annualised_returns\": -0.4845064587167881, \"annualised_returns_over_hodl\": 8.662626171940246e-12, \"annualised_returns_over_uniform_hodl\": 0.819463731149267, \"calmar\": -0.8358049769968592, \"daily_log_sharpe\": -0.6924636109481972, \"daily_returns\": 0.03101604248549897, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": 0.0050192672028078755, \"return\": -0.23422460264785505, \"returns_over_hodl\": 3.488764832582092e-12, \"returns_over_uniform_hodl\": 0.27259156491385017, \"sharpe\": -0.2021085461591841, \"sterling\": -1.2473843298091103, \"ulcer\": -0.17641031596399104}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -9.190507244127843e-10, \"optuna_trial_number\": 1, \"price_ratio\": 3.117194584267474, \"shift_exponent\": 8.775442142888016e-05, \"step\": 1, \"test_objective\": [{\"annualised_returns\": -0.4845064587167881, \"annualised_returns_over_hodl\": 8.662626171940246e-12, \"annualised_returns_over_uniform_hodl\": 0.819463731149267, \"calmar\": -0.8358049769968592, \"daily_log_sharpe\": -0.6924636109481972, \"daily_returns\": 0.03101604248549897, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": 0.0050192672028078755, \"return\": -0.23422460264785505, \"returns_over_hodl\": 3.488764832582092e-12, \"returns_over_uniform_hodl\": 0.27259156491385017, \"sharpe\": -0.2021085461591841, \"sterling\": -1.2473843298091103, \"ulcer\": -0.17641031596399104}], \"train_objective\": [{\"annualised_returns\": -0.5625924876602645, \"annualised_returns_over_hodl\": -0.4491560216720861, \"annualised_returns_over_uniform_hodl\": -0.4491560216720861, \"calmar\": -0.7341180718092033, \"daily_log_sharpe\": -0.567705058985973, \"daily_returns\": 0.008345761621568588, \"fee_revenue_over_value\": 0.001912072520571561, \"jax_sharpe\": 0.11408765888224834, \"return\": -0.3946948691755444, \"returns_over_hodl\": -0.30373762254239534, \"returns_over_uniform_hodl\": -0.30373762254239534, \"sharpe\": 0.07724636228082345, \"sterling\": -1.557828656627222, \"ulcer\": -0.19858168662071113}], \"train_return\": -0.3946948691755444, \"train_returns_over_hodl\": -0.30373762254239534, \"train_sharpe\": 0.11408765888224834, \"validation_return\": -0.3391427199118129, \"validation_returns_over_hodl\": -9.190507244127843e-10, \"validation_sharpe\": -2.243853199642853}, {\"centeredness_margin\": 0.13650637262175108, \"continuous_test_metrics\": [{\"annualised_returns\": -0.6611648595498245, \"annualised_returns_over_hodl\": 0.00022952653572483328, \"annualised_returns_over_uniform_hodl\": 0.19593787218617797, \"calmar\": -1.1387166533626207, \"daily_log_sharpe\": -1.3405745405670233, \"daily_returns\": 0.021725291221651226, \"fee_revenue_over_value\": 0.04328303898498064, \"jax_sharpe\": -0.785248927165991, \"return\": -0.35329149997300147, \"returns_over_hodl\": 9.243268207592692e-05, \"returns_over_uniform_hodl\": 0.07472215082667222, \"sharpe\": -0.9419116636027252, \"sterling\": -1.9498155335173024, \"ulcer\": -0.15372441043030197}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -0.03261829997562904, \"optuna_trial_number\": 2, \"price_ratio\": 168.2205655533988, \"shift_exponent\": 13.576410341360358, \"step\": 2, \"test_objective\": [{\"annualised_returns\": -0.6611648595498245, \"annualised_returns_over_hodl\": 0.00022952653572483328, \"annualised_returns_over_uniform_hodl\": 0.19593787218617797, \"calmar\": -1.1387166533626207, \"daily_log_sharpe\": -1.3405745405670233, \"daily_returns\": 0.021725291221651226, \"fee_revenue_over_value\": 0.04328303898498064, \"jax_sharpe\": -0.785248927165991, \"return\": -0.35329149997300147, \"returns_over_hodl\": 9.243268207592692e-05, \"returns_over_uniform_hodl\": 0.07472215082667222, \"sharpe\": -0.9419116636027252, \"sterling\": -1.9498155335173024, \"ulcer\": -0.15372441043030197}], \"train_objective\": [{\"annualised_returns\": -0.3199466328391827, \"annualised_returns_over_hodl\": -0.14358283368667257, \"annualised_returns_over_uniform_hodl\": -0.14358283368667257, \"calmar\": -0.4383293648323738, \"daily_log_sharpe\": -0.31306560151379537, \"daily_returns\": 0.00796954963502113, \"fee_revenue_over_value\": 0.020535447414474686, \"jax_sharpe\": 0.18046252328724602, \"return\": -0.2087143836953026, \"returns_over_hodl\": -0.08981045029982859, \"returns_over_uniform_hodl\": -0.08981045029982859, \"sharpe\": 0.1927281285579789, \"sterling\": -1.0409497286566587, \"ulcer\": -0.16561074759759012}], \"train_return\": -0.2087143836953026, \"train_returns_over_hodl\": -0.08981045029982859, \"train_sharpe\": 0.18046252328724605, \"validation_return\": -0.2017057071985695, \"validation_returns_over_hodl\": -0.03261829997562904, \"validation_sharpe\": -1.5440393435752056}, {\"centeredness_margin\": 0.09292592746937685, \"continuous_test_metrics\": [{\"annualised_returns\": -0.6182163355666155, \"annualised_returns_over_hodl\": 0.03747688732344878, \"annualised_returns_over_uniform_hodl\": 0.34752712682421283, \"calmar\": -1.0836556807287818, \"daily_log_sharpe\": -1.1713291527815513, \"daily_returns\": 0.023443316875613237, \"fee_revenue_over_value\": 0.04686598966038902, \"jax_sharpe\": -0.5732482872827512, \"return\": -0.3214497317038033, \"returns_over_hodl\": 0.014927729407949064, \"returns_over_uniform_hodl\": 0.1276378828434439, \"sharpe\": -0.7594783021776691, \"sterling\": -1.7669341273923942, \"ulcer\": -0.15521905733908048}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -0.036660593291191645, \"optuna_trial_number\": 3, \"price_ratio\": 84.59877240857433, \"shift_exponent\": 0.03512983466449952, \"step\": 3, \"test_objective\": [{\"annualised_returns\": -0.6182163355666155, \"annualised_returns_over_hodl\": 0.03747688732344878, \"annualised_returns_over_uniform_hodl\": 0.34752712682421283, \"calmar\": -1.0836556807287818, \"daily_log_sharpe\": -1.1713291527815513, \"daily_returns\": 0.023443316875613237, \"fee_revenue_over_value\": 0.04686598966038902, \"jax_sharpe\": -0.5732482872827512, \"return\": -0.3214497317038033, \"returns_over_hodl\": 0.014927729407949064, \"returns_over_uniform_hodl\": 0.1276378828434439, \"sharpe\": -0.7594783021776691, \"sterling\": -1.7669341273923942, \"ulcer\": -0.15521905733908048}], \"train_objective\": [{\"annualised_returns\": -0.32925569835797575, \"annualised_returns_over_hodl\": -0.1553060952682076, \"annualised_returns_over_uniform_hodl\": -0.15530609526820793, \"calmar\": -0.4505736691956947, \"daily_log_sharpe\": -0.32129151251240917, \"daily_returns\": 0.007967660087355602, \"fee_revenue_over_value\": 0.021646393915866587, \"jax_sharpe\": 0.18021121659868608, \"return\": -0.21530833308355768, \"returns_over_hodl\": -0.09739525116158287, \"returns_over_uniform_hodl\": -0.0973952511615831, \"sharpe\": 0.18974200692815738, \"sterling\": -1.0642933115649442, \"ulcer\": -0.1666288835399801}], \"train_return\": -0.21530833308355768, \"train_returns_over_hodl\": -0.09739525116158287, \"train_sharpe\": 0.18021121659868608, \"validation_return\": -0.2152202900450788, \"validation_returns_over_hodl\": -0.036660593291191645, \"validation_sharpe\": -1.6212044525642746}, {\"centeredness_margin\": 0.1340078133734959, \"continuous_test_metrics\": [{\"annualised_returns\": -0.6476090952546906, \"annualised_returns_over_hodl\": -0.14142822394717425, \"annualised_returns_over_uniform_hodl\": 0.2437837121585047, \"calmar\": -1.1549986788640794, \"daily_log_sharpe\": -1.2640559224728942, \"daily_returns\": 0.025978912231425517, \"fee_revenue_over_value\": 0.07999826656910461, \"jax_sharpe\": -0.6449151680772425, \"return\": -0.3429934339744205, \"returns_over_hodl\": -0.059563806123643426, \"returns_over_uniform_hodl\": 0.09183582667736534, \"sharpe\": -0.8575722463410643, \"sterling\": -1.815539177618198, \"ulcer\": -0.15496921809990055}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -0.03596360098884688, \"optuna_trial_number\": 4, \"price_ratio\": 2.5719967313768204, \"shift_exponent\": 1.5163853810983714, \"step\": 4, \"test_objective\": [{\"annualised_returns\": -0.6476090952546906, \"annualised_returns_over_hodl\": -0.14142822394717425, \"annualised_returns_over_uniform_hodl\": 0.2437837121585047, \"calmar\": -1.1549986788640794, \"daily_log_sharpe\": -1.2640559224728942, \"daily_returns\": 0.025978912231425517, \"fee_revenue_over_value\": 0.07999826656910461, \"jax_sharpe\": -0.6449151680772425, \"return\": -0.3429934339744205, \"returns_over_hodl\": -0.059563806123643426, \"returns_over_uniform_hodl\": 0.09183582667736534, \"sharpe\": -0.8575722463410643, \"sterling\": -1.815539177618198, \"ulcer\": -0.15496921809990055}], \"train_objective\": [{\"annualised_returns\": -0.42200555418577823, \"annualised_returns_over_hodl\": -0.27210952943932654, \"annualised_returns_over_uniform_hodl\": -0.27210952943932676, \"calmar\": -0.5602910637240405, \"daily_log_sharpe\": -0.43428276670249577, \"daily_returns\": 0.008430669499184598, \"fee_revenue_over_value\": 0.0396519536008041, \"jax_sharpe\": 0.05357462265390232, \"return\": -0.2830996939062542, \"returns_over_hodl\": -0.17537340078210162, \"returns_over_uniform_hodl\": -0.17537340078210173, \"sharpe\": 0.10579052116849687, \"sterling\": -1.2944558423248658, \"ulcer\": -0.1754866421240969}], \"train_return\": -0.2830996939062542, \"train_returns_over_hodl\": -0.17537340078210162, \"train_sharpe\": 0.053574622653902323, \"validation_return\": -0.26681916724866184, \"validation_returns_over_hodl\": -0.03596360098884688, \"validation_sharpe\": -1.991755107535089}, {\"centeredness_margin\": 0.6372773927359549, \"continuous_test_metrics\": [{\"annualised_returns\": -0.7072542707961356, \"annualised_returns_over_hodl\": -0.020638882142834936, \"annualised_returns_over_uniform_hodl\": 0.03326267756794965, \"calmar\": -1.207899595750015, \"daily_log_sharpe\": -1.5763075900087606, \"daily_returns\": 0.019185801007787013, \"fee_revenue_over_value\": 0.047393482299970015, \"jax_sharpe\": -1.0528522815291521, \"return\": -0.39027279553432137, \"returns_over_hodl\": -0.008363859721415956, \"returns_over_uniform_hodl\": 0.01326537779158854, \"sharpe\": -1.1958926375728498, \"sterling\": -2.179066592544445, \"ulcer\": -0.14895798685938627}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -0.02513441771456118, \"optuna_trial_number\": 5, \"price_ratio\": 24.686641483010394, \"shift_exponent\": 9.519573192170208, \"step\": 5, \"test_objective\": [{\"annualised_returns\": -0.7072542707961356, \"annualised_returns_over_hodl\": -0.020638882142834936, \"annualised_returns_over_uniform_hodl\": 0.03326267756794965, \"calmar\": -1.207899595750015, \"daily_log_sharpe\": -1.5763075900087606, \"daily_returns\": 0.019185801007787013, \"fee_revenue_over_value\": 0.047393482299970015, \"jax_sharpe\": -1.0528522815291521, \"return\": -0.39027279553432137, \"returns_over_hodl\": -0.008363859721415956, \"returns_over_uniform_hodl\": 0.01326537779158854, \"sharpe\": -1.1958926375728498, \"sterling\": -2.179066592544445, \"ulcer\": -0.14895798685938627}], \"train_objective\": [{\"annualised_returns\": -0.31706762006635825, \"annualised_returns_over_hodl\": -0.13995718299549798, \"annualised_returns_over_uniform_hodl\": -0.1399571829954983, \"calmar\": -0.43202121772747804, \"daily_log_sharpe\": -0.32281093271250233, \"daily_returns\": 0.008062192226561125, \"fee_revenue_over_value\": 0.024307960305456424, \"jax_sharpe\": 0.130526475847855, \"return\": -0.2066822663378297, \"returns_over_hodl\": -0.08747297323159398, \"returns_over_uniform_hodl\": -0.0874729732315942, \"sharpe\": 0.1695626408926242, \"sterling\": -1.0427591343915603, \"ulcer\": -0.16409707600559842}], \"train_return\": -0.2066822663378297, \"train_returns_over_hodl\": -0.08747297323159398, \"train_sharpe\": 0.13052647584785504, \"validation_return\": -0.17025817037930846, \"validation_returns_over_hodl\": -0.025134417714561152, \"validation_sharpe\": -1.3449475288965642}, {\"centeredness_margin\": 0.8093058012926287, \"continuous_test_metrics\": [{\"annualised_returns\": -0.5021582539077409, \"annualised_returns_over_hodl\": -0.03424251555327629, \"annualised_returns_over_uniform_hodl\": 0.7571607174981818, \"calmar\": -0.8662555207877072, \"daily_log_sharpe\": -0.7382727427338363, \"daily_returns\": 0.031016042488633093, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": -0.05589540535894536, \"return\": -0.24489522956344523, \"returns_over_hodl\": -0.013934408451109381, \"returns_over_uniform_hodl\": 0.25485875467722363, \"sharpe\": -0.2540148949486179, \"sterling\": -1.2928297766329073, \"ulcer\": -0.1764103160296434}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -0.05350615313186613, \"optuna_trial_number\": 6, \"price_ratio\": 8.7125918058313, \"shift_exponent\": 3.2654517898093676e-05, \"step\": 6, \"test_objective\": [{\"annualised_returns\": -0.5021582539077409, \"annualised_returns_over_hodl\": -0.03424251555327629, \"annualised_returns_over_uniform_hodl\": 0.7571607174981818, \"calmar\": -0.8662555207877072, \"daily_log_sharpe\": -0.7382727427338363, \"daily_returns\": 0.031016042488633093, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": -0.05589540535894536, \"return\": -0.24489522956344523, \"returns_over_hodl\": -0.013934408451109381, \"returns_over_uniform_hodl\": 0.25485875467722363, \"sharpe\": -0.2540148949486179, \"sterling\": -1.2928297766329073, \"ulcer\": -0.1764103160296434}], \"train_objective\": [{\"annualised_returns\": -0.45093050001269464, \"annualised_returns_over_hodl\": -0.30853581792941254, \"annualised_returns_over_uniform_hodl\": -0.30853581792941254, \"calmar\": -0.6041647810550002, \"daily_log_sharpe\": -0.45271385521051377, \"daily_returns\": 0.008129893506203049, \"fee_revenue_over_value\": 0.000499358097519751, \"jax_sharpe\": 0.13576658119056983, \"return\": -0.30510021413376276, \"returns_over_hodl\": -0.2006798681137847, \"returns_over_uniform_hodl\": -0.2006798681137847, \"sharpe\": 0.11026371056613285, \"sterling\": -1.367427645648747, \"ulcer\": -0.17858587573228293}], \"train_return\": -0.30510021413376276, \"train_returns_over_hodl\": -0.2006798681137847, \"train_sharpe\": 0.13576658119056983, \"validation_return\": -0.3317458171567431, \"validation_returns_over_hodl\": -0.05350615313186613, \"validation_sharpe\": -2.1786092410079165}, {\"centeredness_margin\": 0.40172573502235914, \"continuous_test_metrics\": [{\"annualised_returns\": -0.6801353721965393, \"annualised_returns_over_hodl\": -0.03454379618779546, \"annualised_returns_over_uniform_hodl\": 0.12898037037910282, \"calmar\": -1.1860943269376654, \"daily_log_sharpe\": -1.4433995882274664, \"daily_returns\": 0.021309581263128038, \"fee_revenue_over_value\": 0.05777870433437785, \"jax_sharpe\": -0.8858599988047927, \"return\": -0.3681250021821145, \"returns_over_hodl\": -0.014058308598337566, \"returns_over_uniform_hodl\": 0.05007133303503242, \"sharpe\": -1.054364936394679, \"sterling\": -2.0132622738403, \"ulcer\": -0.14983853592964458}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -0.027829492553473778, \"optuna_trial_number\": 7, \"price_ratio\": 7.156228163130151, \"shift_exponent\": 1.3104716239738672, \"step\": 7, \"test_objective\": [{\"annualised_returns\": -0.6801353721965393, \"annualised_returns_over_hodl\": -0.03454379618779546, \"annualised_returns_over_uniform_hodl\": 0.12898037037910282, \"calmar\": -1.1860943269376654, \"daily_log_sharpe\": -1.4433995882274664, \"daily_returns\": 0.021309581263128038, \"fee_revenue_over_value\": 0.05777870433437785, \"jax_sharpe\": -0.8858599988047927, \"return\": -0.3681250021821145, \"returns_over_hodl\": -0.014058308598337566, \"returns_over_uniform_hodl\": 0.05007133303503242, \"sharpe\": -1.054364936394679, \"sterling\": -2.0132622738403, \"ulcer\": -0.14983853592964458}], \"train_objective\": [{\"annualised_returns\": -0.3386989049608171, \"annualised_returns_over_hodl\": -0.16719828583772245, \"annualised_returns_over_uniform_hodl\": -0.16719828583772245, \"calmar\": -0.45965846566360186, \"daily_log_sharpe\": -0.33984595811415946, \"daily_returns\": 0.008157383421555295, \"fee_revenue_over_value\": 0.029802139728508925, \"jax_sharpe\": 0.12314830993437738, \"return\": -0.22203414004049815, \"returns_over_hodl\": -0.10513172340291665, \"returns_over_uniform_hodl\": -0.10513172340291665, \"sharpe\": 0.16753199825312406, \"sterling\": -1.0908188099693161, \"ulcer\": -0.16723549875595248}], \"train_return\": -0.22203414004049815, \"train_returns_over_hodl\": -0.10513172340291665, \"train_sharpe\": 0.12314830993437739, \"validation_return\": -0.20077334915273914, \"validation_returns_over_hodl\": -0.027829492553473778, \"validation_sharpe\": -1.5730496548836754}, {\"centeredness_margin\": 0.9283661219242054, \"continuous_test_metrics\": [{\"annualised_returns\": -0.7055702143510854, \"annualised_returns_over_hodl\": 0.02035724226927127, \"annualised_returns_over_uniform_hodl\": 0.039206650435873014, \"calmar\": -1.2065289284655654, \"daily_log_sharpe\": -1.572195821498555, \"daily_returns\": 0.01820014635970887, \"fee_revenue_over_value\": 0.04296148697508504, \"jax_sharpe\": -0.9838825249657162, \"return\": -0.38886259905550324, \"returns_over_hodl\": 0.008149322638695278, \"returns_over_uniform_hodl\": 0.015608890197472247, \"sharpe\": -1.1893678368656506, \"sterling\": -2.0989742002372194, \"ulcer\": -0.1458316899932139}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -0.037608262058648756, \"optuna_trial_number\": 8, \"price_ratio\": 2.3595348250993498, \"shift_exponent\": 0.034584479663115376, \"step\": 8, \"test_objective\": [{\"annualised_returns\": -0.7055702143510854, \"annualised_returns_over_hodl\": 0.02035724226927127, \"annualised_returns_over_uniform_hodl\": 0.039206650435873014, \"calmar\": -1.2065289284655654, \"daily_log_sharpe\": -1.572195821498555, \"daily_returns\": 0.01820014635970887, \"fee_revenue_over_value\": 0.04296148697508504, \"jax_sharpe\": -0.9838825249657162, \"return\": -0.38886259905550324, \"returns_over_hodl\": 0.008149322638695278, \"returns_over_uniform_hodl\": 0.015608890197472247, \"sharpe\": -1.1893678368656506, \"sterling\": -2.0989742002372194, \"ulcer\": -0.1458316899932139}], \"train_objective\": [{\"annualised_returns\": -0.4007839761597157, \"annualised_returns_over_hodl\": -0.2453843860970395, \"annualised_returns_over_uniform_hodl\": -0.24538438609703972, \"calmar\": -0.5384301346137615, \"daily_log_sharpe\": -0.47033655576959144, \"daily_returns\": 0.008092168784733143, \"fee_revenue_over_value\": 0.01592426613206595, \"jax_sharpe\": -0.06856628127636519, \"return\": -0.2672326048304956, \"returns_over_hodl\": -0.1571220154879246, \"returns_over_uniform_hodl\": -0.1571220154879247, \"sharpe\": 0.012640814733855215, \"sterling\": -1.3121488048321919, \"ulcer\": -0.16590568522474533}], \"train_return\": -0.2672326048304956, \"train_returns_over_hodl\": -0.1571220154879246, \"train_sharpe\": -0.06856628127636519, \"validation_return\": -0.16505066655786627, \"validation_returns_over_hodl\": -0.037608262058648756, \"validation_sharpe\": -1.3477145340680512}, {\"centeredness_margin\": 0.10932874791381679, \"continuous_test_metrics\": [{\"annualised_returns\": -0.7882225751224061, \"annualised_returns_over_hodl\": -0.49525196640479807, \"annualised_returns_over_uniform_hodl\": -0.2525195511047632, \"calmar\": -1.3144563932603273, \"daily_log_sharpe\": -1.8639859005120736, \"daily_returns\": 0.02669324569190372, \"fee_revenue_over_value\": 0.11102905758487737, \"jax_sharpe\": -1.3514768346497155, \"return\": -0.4648121531754269, \"returns_over_hodl\": -0.24069383672512257, \"returns_over_uniform_hodl\": -0.1106066584688643, \"sharpe\": -1.509480916412636, \"sterling\": -2.2980838547159452, \"ulcer\": -0.15881211122662192}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -0.052734783645591365, \"optuna_trial_number\": 9, \"price_ratio\": 1.6078259419459497, \"shift_exponent\": 25.14328541540202, \"step\": 9, \"test_objective\": [{\"annualised_returns\": -0.7882225751224061, \"annualised_returns_over_hodl\": -0.49525196640479807, \"annualised_returns_over_uniform_hodl\": -0.2525195511047632, \"calmar\": -1.3144563932603273, \"daily_log_sharpe\": -1.8639859005120736, \"daily_returns\": 0.02669324569190372, \"fee_revenue_over_value\": 0.11102905758487737, \"jax_sharpe\": -1.3514768346497155, \"return\": -0.4648121531754269, \"returns_over_hodl\": -0.24069383672512257, \"returns_over_uniform_hodl\": -0.1106066584688643, \"sharpe\": -1.509480916412636, \"sterling\": -2.2980838547159452, \"ulcer\": -0.15881211122662192}], \"train_objective\": [{\"annualised_returns\": -0.6096086587902866, \"annualised_returns_over_hodl\": -0.5083652808191763, \"annualised_returns_over_uniform_hodl\": -0.5083652808191763, \"calmar\": -0.763480857129112, \"daily_log_sharpe\": -0.8366762445768395, \"daily_returns\": 0.008944762229014643, \"fee_revenue_over_value\": 0.04785635433213251, \"jax_sharpe\": -0.38156040018751, \"return\": -0.4350747045941895, \"returns_over_hodl\": -0.35018520538650355, \"returns_over_uniform_hodl\": -0.35018520538650355, \"sharpe\": -0.3214875445987922, \"sterling\": -1.808255266670419, \"ulcer\": -0.1843526656099988}], \"train_return\": -0.4350747045941895, \"train_returns_over_hodl\": -0.35018520538650355, \"train_sharpe\": -0.3815604001875099, \"validation_return\": -0.26827703869535446, \"validation_returns_over_hodl\": -0.052734783645591365, \"validation_sharpe\": -2.084131755576996}, {\"centeredness_margin\": 0.7909433349008377, \"continuous_test_metrics\": [{\"annualised_returns\": -0.4979650376813658, \"annualised_returns_over_hodl\": -0.02610814353118862, \"annualised_returns_over_uniform_hodl\": 0.7719609123207292, \"calmar\": -0.8590219379708393, \"daily_log_sharpe\": -0.7314525083908714, \"daily_returns\": 0.03101604250524389, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": -0.05339172913398967, \"return\": -0.2423401954469564, \"returns_over_hodl\": -0.01059787644089405, \"returns_over_uniform_hodl\": 0.2591047971538476, \"sharpe\": -0.2474259328571405, \"sterling\": -1.2820341270215447, \"ulcer\": -0.1764103160048136}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -6.358730209043983e-10, \"optuna_trial_number\": 10, \"price_ratio\": 3.2168166721048594, \"shift_exponent\": 0.0015100163058745192, \"step\": 10, \"test_objective\": [{\"annualised_returns\": -0.4979650376813658, \"annualised_returns_over_hodl\": -0.02610814353118862, \"annualised_returns_over_uniform_hodl\": 0.7719609123207292, \"calmar\": -0.8590219379708393, \"daily_log_sharpe\": -0.7314525083908714, \"daily_returns\": 0.03101604250524389, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": -0.05339172913398967, \"return\": -0.2423401954469564, \"returns_over_hodl\": -0.01059787644089405, \"returns_over_uniform_hodl\": 0.2591047971538476, \"sharpe\": -0.2474259328571405, \"sterling\": -1.2820341270215447, \"ulcer\": -0.1764103160048136}], \"train_objective\": [{\"annualised_returns\": -0.5108537716054935, \"annualised_returns_over_hodl\": -0.3839994814180351, \"annualised_returns_over_uniform_hodl\": -0.3839994814180351, \"calmar\": -0.6719310787409686, \"daily_log_sharpe\": -0.5001207258368972, \"daily_returns\": 0.008303124426348738, \"fee_revenue_over_value\": 0.000374476482667527, \"jax_sharpe\": 0.17055087844373004, \"return\": -0.352184106193269, \"returns_over_hodl\": -0.25483890453341207, \"returns_over_uniform_hodl\": -0.25483890453341207, \"sharpe\": 0.11762560170441703, \"sterling\": -1.466883588566339, \"ulcer\": -0.18883492184411832}], \"train_return\": -0.352184106193269, \"train_returns_over_hodl\": -0.25483890453341207, \"train_sharpe\": 0.17055087844373001, \"validation_return\": -0.33914271994256195, \"validation_returns_over_hodl\": -6.358730209043983e-10, \"validation_sharpe\": -2.2438531985774213}, {\"centeredness_margin\": 0.07757455456349761, \"continuous_test_metrics\": [{\"annualised_returns\": -0.6323068716246993, \"annualised_returns_over_hodl\": 0.07317749655823591, \"annualised_returns_over_uniform_hodl\": 0.2977937795424681, \"calmar\": -1.104727231870646, \"daily_log_sharpe\": -1.2360287365441618, \"daily_returns\": 0.021931939197762606, \"fee_revenue_over_value\": 0.044252883457950865, \"jax_sharpe\": -0.6562855413768076, \"return\": -0.33164903182085437, \"returns_over_hodl\": 0.028851265876902232, \"returns_over_uniform_hodl\": 0.11068833948926482, \"sharpe\": -0.8310886215780401, \"sterling\": -1.8455357723017722, \"ulcer\": -0.15333582241604485}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -0.03198849362310585, \"optuna_trial_number\": 11, \"price_ratio\": 198.69468803684032, \"shift_exponent\": 0.11173242643610201, \"step\": 11, \"test_objective\": [{\"annualised_returns\": -0.6323068716246993, \"annualised_returns_over_hodl\": 0.07317749655823591, \"annualised_returns_over_uniform_hodl\": 0.2977937795424681, \"calmar\": -1.104727231870646, \"daily_log_sharpe\": -1.2360287365441618, \"daily_returns\": 0.021931939197762606, \"fee_revenue_over_value\": 0.044252883457950865, \"jax_sharpe\": -0.6562855413768076, \"return\": -0.33164903182085437, \"returns_over_hodl\": 0.028851265876902232, \"returns_over_uniform_hodl\": 0.11068833948926482, \"sharpe\": -0.8310886215780401, \"sterling\": -1.8455357723017722, \"ulcer\": -0.15333582241604485}], \"train_objective\": [{\"annualised_returns\": -0.31825014230534554, \"annualised_returns_over_hodl\": -0.14144637839385943, \"annualised_returns_over_uniform_hodl\": -0.14144637839385943, \"calmar\": -0.43612870533946757, \"daily_log_sharpe\": -0.31173912868470727, \"daily_returns\": 0.00799675959344142, \"fee_revenue_over_value\": 0.020309336091323335, \"jax_sharpe\": 0.1809987651435683, \"return\": -0.20751652768254436, \"returns_over_hodl\": -0.08843259633358369, \"returns_over_uniform_hodl\": -0.08843259633358369, \"sharpe\": 0.1929981763202188, \"sterling\": -1.0367840119052922, \"ulcer\": -0.16541355903038416}], \"train_return\": -0.20751652768254436, \"train_returns_over_hodl\": -0.08843259633358369, \"train_sharpe\": 0.1809987651435683, \"validation_return\": -0.19905196021586335, \"validation_returns_over_hodl\": -0.03198849362310585, \"validation_sharpe\": -1.5248264755788867}, {\"centeredness_margin\": 0.39130466910475015, \"continuous_test_metrics\": [{\"annualised_returns\": -0.6972904620510232, \"annualised_returns_over_hodl\": -0.11059516348917586, \"annualised_returns_over_uniform_hodl\": 0.06843050642321002, \"calmar\": -1.2306027041684153, \"daily_log_sharpe\": -1.5260575786993023, \"daily_returns\": 0.022151609343035776, \"fee_revenue_over_value\": 0.09357579047019726, \"jax_sharpe\": -0.8924350041091119, \"return\": -0.38199842319169064, \"returns_over_hodl\": -0.04610529492175297, \"returns_over_uniform_hodl\": 0.02701601078998217, \"sharpe\": -1.1448876731152597, \"sterling\": -2.0288139388450417, \"ulcer\": -0.14540475978580963}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -0.05922062248734272, \"optuna_trial_number\": 12, \"price_ratio\": 1.5228181588457768, \"shift_exponent\": 0.15870241305950317, \"step\": 12, \"test_objective\": [{\"annualised_returns\": -0.6972904620510232, \"annualised_returns_over_hodl\": -0.11059516348917586, \"annualised_returns_over_uniform_hodl\": 0.06843050642321002, \"calmar\": -1.2306027041684153, \"daily_log_sharpe\": -1.5260575786993023, \"daily_returns\": 0.022151609343035776, \"fee_revenue_over_value\": 0.09357579047019726, \"jax_sharpe\": -0.8924350041091119, \"return\": -0.38199842319169064, \"returns_over_hodl\": -0.04610529492175297, \"returns_over_uniform_hodl\": 0.02701601078998217, \"sharpe\": -1.1448876731152597, \"sterling\": -2.0288139388450417, \"ulcer\": -0.14540475978580963}], \"train_objective\": [{\"annualised_returns\": -0.49100534978332144, \"annualised_returns_over_hodl\": -0.3590036061035665, \"annualised_returns_over_uniform_hodl\": -0.35900360610356663, \"calmar\": -0.642354033800144, \"daily_log_sharpe\": -0.6181196955810094, \"daily_returns\": 0.008946214325932272, \"fee_revenue_over_value\": 0.046270796688847024, \"jax_sharpe\": -0.22228519858996454, \"return\": -0.33634965803734407, \"returns_over_hodl\": -0.23662506500464908, \"returns_over_uniform_hodl\": -0.2366250650046492, \"sharpe\": -0.12406905991558878, \"sterling\": -1.5350015674763386, \"ulcer\": -0.17460710260866505}], \"train_return\": -0.33634965803734407, \"train_returns_over_hodl\": -0.23662506500464908, \"train_sharpe\": -0.22228519858996454, \"validation_return\": -0.20877702823993616, \"validation_returns_over_hodl\": -0.05922062248734272, \"validation_sharpe\": -1.691469645861666}, {\"centeredness_margin\": 0.377306530179145, \"continuous_test_metrics\": [{\"annualised_returns\": -0.47764789094148485, \"annualised_returns_over_hodl\": 0.01330485575475282, \"annualised_returns_over_uniform_hodl\": 0.843671435641024, \"calmar\": -0.8239734838926897, \"daily_log_sharpe\": -0.6808453174943461, \"daily_returns\": 0.031016042512234554, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": 0.006675139722121855, \"return\": -0.23013748436731263, \"returns_over_hodl\": 0.00533722806804704, \"returns_over_uniform_hodl\": 0.2793836768916025, \"sharpe\": -0.19047259365326993, \"sterling\": -1.2297265987024772, \"ulcer\": -0.1764103160192643}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -6.135362218273599e-10, \"optuna_trial_number\": 13, \"price_ratio\": 5.596272649295502, \"shift_exponent\": 9.129926955961972e-05, \"step\": 13, \"test_objective\": [{\"annualised_returns\": -0.47764789094148485, \"annualised_returns_over_hodl\": 0.01330485575475282, \"annualised_returns_over_uniform_hodl\": 0.843671435641024, \"calmar\": -0.8239734838926897, \"daily_log_sharpe\": -0.6808453174943461, \"daily_returns\": 0.031016042512234554, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": 0.006675139722121855, \"return\": -0.23013748436731263, \"returns_over_hodl\": 0.00533722806804704, \"returns_over_uniform_hodl\": 0.2793836768916025, \"sharpe\": -0.19047259365326993, \"sterling\": -1.2297265987024772, \"ulcer\": -0.1764103160192643}], \"train_objective\": [{\"annualised_returns\": -0.4818745461532562, \"annualised_returns_over_hodl\": -0.34750483652365693, \"annualised_returns_over_uniform_hodl\": -0.34750483652365693, \"calmar\": -0.6439154822814582, \"daily_log_sharpe\": -0.4731752703900377, \"daily_returns\": 0.008205044205014191, \"fee_revenue_over_value\": 0.010968965392725016, \"jax_sharpe\": 0.10745723440384529, \"return\": -0.3291470434073088, \"returns_over_hodl\": -0.2283401367414627, \"returns_over_uniform_hodl\": -0.2283401367414627, \"sharpe\": 0.11719599380844398, \"sterling\": -1.4250946117031755, \"ulcer\": -0.1832626037226378}], \"train_return\": -0.3291470434073088, \"train_returns_over_hodl\": -0.2283401367414627, \"train_sharpe\": 0.10745723440384529, \"validation_return\": -0.3391427199963645, \"validation_returns_over_hodl\": -6.135362218273599e-10, \"validation_sharpe\": -2.243853198630772}, {\"centeredness_margin\": 0.35375578057357104, \"continuous_test_metrics\": [{\"annualised_returns\": -0.48450645947773074, \"annualised_returns_over_hodl\": 9.728440275580397e-12, \"annualised_returns_over_uniform_hodl\": 0.819463728463476, \"calmar\": -0.8358049782160547, \"daily_log_sharpe\": -0.692463612858883, \"daily_returns\": 0.03101604244933611, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": 0.005019264915335632, \"return\": -0.23422460310310778, \"returns_over_hodl\": 3.9179770539021774e-12, \"returns_over_uniform_hodl\": 0.2725915641572956, \"sharpe\": -0.20210854840033574, \"sterling\": -1.2473843323591158, \"ulcer\": -0.1764103158892411}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -1.2292098450217281e-09, \"optuna_trial_number\": 14, \"price_ratio\": 1.9073033160855102, \"shift_exponent\": 2.1922732601582732e-05, \"step\": 14, \"test_objective\": [{\"annualised_returns\": -0.48450645947773074, \"annualised_returns_over_hodl\": 9.728440275580397e-12, \"annualised_returns_over_uniform_hodl\": 0.819463728463476, \"calmar\": -0.8358049782160547, \"daily_log_sharpe\": -0.692463612858883, \"daily_returns\": 0.03101604244933611, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": 0.005019264915335632, \"return\": -0.23422460310310778, \"returns_over_hodl\": 3.9179770539021774e-12, \"returns_over_uniform_hodl\": 0.2725915641572956, \"sharpe\": -0.20210854840033574, \"sterling\": -1.2473843323591158, \"ulcer\": -0.1764103158892411}], \"train_objective\": [{\"annualised_returns\": -0.6377330990879262, \"annualised_returns_over_hodl\": -0.5437834621369333, \"annualised_returns_over_uniform_hodl\": -0.5437834621369331, \"calmar\": -0.8127890839076417, \"daily_log_sharpe\": -0.6912301188723352, \"daily_returns\": 0.008534124064731618, \"fee_revenue_over_value\": 0.0025480400420597837, \"jax_sharpe\": 0.07787179372097905, \"return\": -0.4601453248093279, \"returns_over_hodl\": -0.37902310671331607, \"returns_over_uniform_hodl\": -0.37902310671331585, \"sharpe\": -0.021218726332984068, \"sterling\": -1.7176063865782458, \"ulcer\": -0.20710426729359127}], \"train_return\": -0.4601453248093279, \"train_returns_over_hodl\": -0.37902310671331607, \"train_sharpe\": 0.07787179372097904, \"validation_return\": -0.3391427197177923, \"validation_returns_over_hodl\": -1.2292098450217281e-09, \"validation_sharpe\": -2.243853200210737}, {\"centeredness_margin\": 0.4682398511781016, \"continuous_test_metrics\": [{\"annualised_returns\": -0.4845064599069633, \"annualised_returns_over_hodl\": 1.024713647268527e-11, \"annualised_returns_over_uniform_hodl\": 0.8194637269484759, \"calmar\": -0.8358049789037639, \"daily_log_sharpe\": -0.6924636139366335, \"daily_returns\": 0.031016042428944453, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": 0.005019263625075835, \"return\": -0.2342246033599067, \"returns_over_hodl\": 4.126921027136632e-12, \"returns_over_uniform_hodl\": 0.2725915637305385, \"sharpe\": -0.202108549664483, \"sterling\": -1.2473843337974935, \"ulcer\": -0.17641031584708128}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -1.4167569339917918e-09, \"optuna_trial_number\": 15, \"price_ratio\": 1.2701472222368417, \"shift_exponent\": 0.0001399024041072852, \"step\": 15, \"test_objective\": [{\"annualised_returns\": -0.4845064599069633, \"annualised_returns_over_hodl\": 1.024713647268527e-11, \"annualised_returns_over_uniform_hodl\": 0.8194637269484759, \"calmar\": -0.8358049789037639, \"daily_log_sharpe\": -0.6924636139366335, \"daily_returns\": 0.031016042428944453, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": 0.005019263625075835, \"return\": -0.2342246033599067, \"returns_over_hodl\": 4.126921027136632e-12, \"returns_over_uniform_hodl\": 0.2725915637305385, \"sharpe\": -0.202108549664483, \"sterling\": -1.2473843337974935, \"ulcer\": -0.17641031584708128}], \"train_objective\": [{\"annualised_returns\": -0.6731058785257222, \"annualised_returns_over_hodl\": -0.588329753639346, \"annualised_returns_over_uniform_hodl\": -0.5883297536393466, \"calmar\": -0.8439679297680522, \"daily_log_sharpe\": -0.7592506937405438, \"daily_returns\": 0.009630181214297694, \"fee_revenue_over_value\": 0.0004236805043418813, \"jax_sharpe\": 0.03786812234828173, \"return\": -0.49279190937163386, \"returns_over_hodl\": -0.4165753880763723, \"returns_over_uniform_hodl\": -0.41657538807637273, \"sharpe\": -0.0793006259609806, \"sterling\": -1.790372339787404, \"ulcer\": -0.2108324398606525}], \"train_return\": -0.49279190937163386, \"train_returns_over_hodl\": -0.4165753880763723, \"train_sharpe\": 0.03786812234828173, \"validation_return\": -0.3391427196166925, \"validation_returns_over_hodl\": -1.4167569339917918e-09, \"validation_sharpe\": -2.2438532006211784}, {\"centeredness_margin\": 0.5076699466369065, \"continuous_test_metrics\": [{\"annualised_returns\": -0.48450645993036767, \"annualised_returns_over_hodl\": 1.027977702960925e-11, \"annualised_returns_over_uniform_hodl\": 0.8194637268658689, \"calmar\": -0.8358049789412626, \"daily_log_sharpe\": -0.6924636139954027, \"daily_returns\": 0.031016042427832374, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": 0.00501926355472025, \"return\": -0.23422460337390894, \"returns_over_hodl\": 4.140021658827209e-12, \"returns_over_uniform_hodl\": 0.2725915637072691, \"sharpe\": -0.20210854973341427, \"sterling\": -1.2473843338759243, \"ulcer\": -0.17641031584478212}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -1.4682066673543659e-09, \"optuna_trial_number\": 16, \"price_ratio\": 1.0998052342292364, \"shift_exponent\": 1.3320315589108241e-05, \"step\": 16, \"test_objective\": [{\"annualised_returns\": -0.48450645993036767, \"annualised_returns_over_hodl\": 1.027977702960925e-11, \"annualised_returns_over_uniform_hodl\": 0.8194637268658689, \"calmar\": -0.8358049789412626, \"daily_log_sharpe\": -0.6924636139954027, \"daily_returns\": 0.031016042427832374, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": 0.00501926355472025, \"return\": -0.23422460337390894, \"returns_over_hodl\": 4.140021658827209e-12, \"returns_over_uniform_hodl\": 0.2725915637072691, \"sharpe\": -0.20210854973341427, \"sterling\": -1.2473843338759243, \"ulcer\": -0.17641031584478212}], \"train_objective\": [{\"annualised_returns\": -0.675190501359585, \"annualised_returns_over_hodl\": -0.5909549987545382, \"annualised_returns_over_uniform_hodl\": -0.5909549987545386, \"calmar\": -0.8422546723883908, \"daily_log_sharpe\": -0.7625307722245455, \"daily_returns\": 0.012060598230958838, \"fee_revenue_over_value\": 0.00029957019281359574, \"jax_sharpe\": 0.0369200532189897, \"return\": -0.49475810805140674, \"returns_over_hodl\": -0.41883704108015263, \"returns_over_uniform_hodl\": -0.41883704108015296, \"sharpe\": -0.08117519648800585, \"sterling\": -1.7885862725983672, \"ulcer\": -0.21226971612766454}], \"train_return\": -0.49475810805140674, \"train_returns_over_hodl\": -0.41883704108015263, \"train_sharpe\": 0.03692005321898971, \"validation_return\": -0.3391427196384217, \"validation_returns_over_hodl\": -1.4682066673543659e-09, \"validation_sharpe\": -2.243853200915237}, {\"centeredness_margin\": 0.19079633155135367, \"continuous_test_metrics\": [{\"annualised_returns\": -0.5227935320197894, \"annualised_returns_over_hodl\": -0.07427265526452598, \"annualised_returns_over_uniform_hodl\": 0.6843273314317153, \"calmar\": -0.9028460028178072, \"daily_log_sharpe\": -0.7859535420732878, \"daily_returns\": 0.03101604251687332, \"fee_revenue_over_value\": 1.43574407755744e-07, \"jax_sharpe\": -0.1031905353634067, \"return\": -0.2576599647328708, \"returns_over_hodl\": -0.030603441498123107, \"returns_over_uniform_hodl\": 0.23364588421789745, \"sharpe\": -0.30526103758058326, \"sterling\": -1.3480355811784337, \"ulcer\": -0.17582139662337845}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -0.06364645476683842, \"optuna_trial_number\": 17, \"price_ratio\": 10.275301935045068, \"shift_exponent\": 1.514373282382875e-05, \"step\": 17, \"test_objective\": [{\"annualised_returns\": -0.5227935320197894, \"annualised_returns_over_hodl\": -0.07427265526452598, \"annualised_returns_over_uniform_hodl\": 0.6843273314317153, \"calmar\": -0.9028460028178072, \"daily_log_sharpe\": -0.7859535420732878, \"daily_returns\": 0.03101604251687332, \"fee_revenue_over_value\": 1.43574407755744e-07, \"jax_sharpe\": -0.1031905353634067, \"return\": -0.2576599647328708, \"returns_over_hodl\": -0.030603441498123107, \"returns_over_uniform_hodl\": 0.23364588421789745, \"sharpe\": -0.30526103758058326, \"sterling\": -1.3480355811784337, \"ulcer\": -0.17582139662337845}], \"train_objective\": [{\"annualised_returns\": -0.4090552355586998, \"annualised_returns_over_hodl\": -0.25580069881363476, \"annualised_returns_over_uniform_hodl\": -0.25580069881363476, \"calmar\": -0.553469544114119, \"daily_log_sharpe\": -0.39678400984399176, \"daily_returns\": 0.008116782065028905, \"fee_revenue_over_value\": 0.020027007686943012, \"jax_sharpe\": 0.17212340006747812, \"return\": -0.2733902334836681, \"returns_over_hodl\": -0.16420493110722467, \"returns_over_uniform_hodl\": -0.16420493110722467, \"sharpe\": 0.1564845306463582, \"sterling\": -1.2583424342920635, \"ulcer\": -0.1752770105952015}], \"train_return\": -0.2733902334836681, \"train_returns_over_hodl\": -0.16420493110722467, \"train_sharpe\": 0.17212340006747812, \"validation_return\": -0.3191443678452007, \"validation_returns_over_hodl\": -0.06364645476683839, \"validation_sharpe\": -2.100530609656689}, {\"centeredness_margin\": 0.32682007873759145, \"continuous_test_metrics\": [{\"annualised_returns\": -0.6440280771219439, \"annualised_returns_over_hodl\": -0.0919097211589075, \"annualised_returns_over_uniform_hodl\": 0.2564231190401143, \"calmar\": -1.1190033339506222, \"daily_log_sharpe\": -1.2496375413364202, \"daily_returns\": 0.024685512390665017, \"fee_revenue_over_value\": 0.03985792300063062, \"jax_sharpe\": -0.6335170326617855, \"return\": -0.3403126544182353, \"returns_over_hodl\": -0.03808438390676072, \"returns_over_uniform_hodl\": 0.09629083719662579, \"sharpe\": -0.838422077768016, \"sterling\": -1.8115255207891203, \"ulcer\": -0.1563949325541149}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -0.04075704823071857, \"optuna_trial_number\": 18, \"price_ratio\": 14.25915134951358, \"shift_exponent\": 0.003957974922888253, \"step\": 18, \"test_objective\": [{\"annualised_returns\": -0.6440280771219439, \"annualised_returns_over_hodl\": -0.0919097211589075, \"annualised_returns_over_uniform_hodl\": 0.2564231190401143, \"calmar\": -1.1190033339506222, \"daily_log_sharpe\": -1.2496375413364202, \"daily_returns\": 0.024685512390665017, \"fee_revenue_over_value\": 0.03985792300063062, \"jax_sharpe\": -0.6335170326617855, \"return\": -0.3403126544182353, \"returns_over_hodl\": -0.03808438390676072, \"returns_over_uniform_hodl\": 0.09629083719662579, \"sharpe\": -0.838422077768016, \"sterling\": -1.8115255207891203, \"ulcer\": -0.1563949325541149}], \"train_objective\": [{\"annualised_returns\": -0.3779655654630002, \"annualised_returns_over_hodl\": -0.21664829041348777, \"annualised_returns_over_uniform_hodl\": -0.21664829041348777, \"calmar\": -0.5115178491917958, \"daily_log_sharpe\": -0.3780183576139651, \"daily_returns\": 0.008097379574025837, \"fee_revenue_over_value\": 0.015881883093623563, \"jax_sharpe\": 0.16617781009912538, \"return\": -0.25041596298247426, \"returns_over_hodl\": -0.1377783911938295, \"returns_over_uniform_hodl\": -0.1377783911938295, \"sharpe\": 0.15214576142182248, \"sterling\": -1.1899577187956374, \"ulcer\": -0.17122663598061677}], \"train_return\": -0.25041596298247426, \"train_returns_over_hodl\": -0.1377783911938295, \"train_sharpe\": 0.16617781009912538, \"validation_return\": -0.23754933639889575, \"validation_returns_over_hodl\": -0.04075704823071857, \"validation_sharpe\": -1.7834428308815895}, {\"centeredness_margin\": 0.1920813845612581, \"continuous_test_metrics\": [{\"annualised_returns\": -0.5658153077257472, \"annualised_returns_over_hodl\": -0.1500190561347906, \"annualised_returns_over_uniform_hodl\": 0.5324795306779446, \"calmar\": -0.9772319993871905, \"daily_log_sharpe\": -0.8984983128264739, \"daily_returns\": 0.0308255729652906, \"fee_revenue_over_value\": 6.608578308533913e-05, \"jax_sharpe\": -0.22161812879312137, \"return\": -0.28537563405612476, \"returns_over_hodl\": -0.06336493938458465, \"returns_over_uniform_hodl\": 0.18758704357262257, \"sharpe\": -0.4290004966805298, \"sterling\": -1.4761562298144681, \"ulcer\": -0.17198207279867472}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -0.06207557358239124, \"optuna_trial_number\": 19, \"price_ratio\": 15.025142741763561, \"shift_exponent\": 8.258576052015238e-05, \"step\": 19, \"test_objective\": [{\"annualised_returns\": -0.5658153077257472, \"annualised_returns_over_hodl\": -0.1500190561347906, \"annualised_returns_over_uniform_hodl\": 0.5324795306779446, \"calmar\": -0.9772319993871905, \"daily_log_sharpe\": -0.8984983128264739, \"daily_returns\": 0.0308255729652906, \"fee_revenue_over_value\": 6.608578308533913e-05, \"jax_sharpe\": -0.22161812879312137, \"return\": -0.28537563405612476, \"returns_over_hodl\": -0.06336493938458465, \"returns_over_uniform_hodl\": 0.18758704357262257, \"sharpe\": -0.4290004966805298, \"sterling\": -1.4761562298144681, \"ulcer\": -0.17198207279867472}], \"train_objective\": [{\"annualised_returns\": -0.3803391409875686, \"annualised_returns_over_hodl\": -0.21963742468285896, \"annualised_returns_over_uniform_hodl\": -0.21963742468285896, \"calmar\": -0.5173716429205596, \"daily_log_sharpe\": -0.36769470438588525, \"daily_returns\": 0.008078330977156418, \"fee_revenue_over_value\": 0.022740741099300375, \"jax_sharpe\": 0.1460088414492867, \"return\": -0.2521538057524253, \"returns_over_hodl\": -0.13977737398289924, \"returns_over_uniform_hodl\": -0.13977737398289924, \"sharpe\": 0.17174998894742183, \"sterling\": -1.189408618987802, \"ulcer\": -0.17218136143678445}], \"train_return\": -0.2521538057524253, \"train_returns_over_hodl\": -0.13977737398289924, \"train_sharpe\": 0.1460088414492867, \"validation_return\": -0.29106920411270587, \"validation_returns_over_hodl\": -0.06207557358239124, \"validation_sharpe\": -1.9937174149633539}, {\"centeredness_margin\": 0.39585218798472704, \"continuous_test_metrics\": [{\"annualised_returns\": -0.6336357905302482, \"annualised_returns_over_hodl\": -0.1186971534164204, \"annualised_returns_over_uniform_hodl\": 0.293103284790069, \"calmar\": -1.0803507704303545, \"daily_log_sharpe\": -1.152829580573141, \"daily_returns\": 0.02600532792664212, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": -0.5008345628596464, \"return\": -0.33262292058869636, \"returns_over_hodl\": -0.049614434581106814, \"returns_over_uniform_hodl\": 0.10906989805669154, \"sharpe\": -0.7195199600252605, \"sterling\": -1.7245981929324705, \"ulcer\": -0.16249237172535666}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -0.048553994857351374, \"optuna_trial_number\": 20, \"price_ratio\": 36.9575417055304, \"shift_exponent\": 4.8324665881744296e-05, \"step\": 20, \"test_objective\": [{\"annualised_returns\": -0.6336357905302482, \"annualised_returns_over_hodl\": -0.1186971534164204, \"annualised_returns_over_uniform_hodl\": 0.293103284790069, \"calmar\": -1.0803507704303545, \"daily_log_sharpe\": -1.152829580573141, \"daily_returns\": 0.02600532792664212, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": -0.5008345628596464, \"return\": -0.33262292058869636, \"returns_over_hodl\": -0.049614434581106814, \"returns_over_uniform_hodl\": 0.10906989805669154, \"sharpe\": -0.7195199600252605, \"sterling\": -1.7245981929324705, \"ulcer\": -0.16249237172535666}], \"train_objective\": [{\"annualised_returns\": -0.3680202366746239, \"annualised_returns_over_hodl\": -0.20412375820720696, \"annualised_returns_over_uniform_hodl\": -0.20412375820720696, \"calmar\": -0.5000999628944037, \"daily_log_sharpe\": -0.36857094384960654, \"daily_returns\": 0.008041298758865296, \"fee_revenue_over_value\": 0.012083006312435517, \"jax_sharpe\": 0.12492270083372121, \"return\": -0.24316251529417554, \"returns_over_hodl\": -0.12943499135292547, \"returns_over_uniform_hodl\": -0.12943499135292547, \"sharpe\": 0.15242820015818223, \"sterling\": -1.171658590308386, \"ulcer\": -0.16937442142037995}], \"train_return\": -0.24316251529417554, \"train_returns_over_hodl\": -0.12943499135292547, \"train_sharpe\": 0.12492270083372123, \"validation_return\": -0.24633745921971129, \"validation_returns_over_hodl\": -0.048553994857351346, \"validation_sharpe\": -1.8066839687257044}, {\"centeredness_margin\": 0.40225928535622546, \"continuous_test_metrics\": [{\"annualised_returns\": -0.6501970747849068, \"annualised_returns_over_hodl\": -0.1336363333459053, \"annualised_returns_over_uniform_hodl\": 0.23464929142364177, \"calmar\": -1.1047485979962564, \"daily_log_sharpe\": -1.2205326741021179, \"daily_returns\": 0.02532822218127843, \"fee_revenue_over_value\": 1.6167801139021197e-07, \"jax_sharpe\": -0.5798584056875409, \"return\": -0.34494096078410974, \"returns_over_hodl\": -0.05613577276695836, \"returns_over_uniform_hodl\": 0.08859936047719241, \"sharpe\": -0.7941449207768917, \"sterling\": -1.7891153158080972, \"ulcer\": -0.1606945285697468}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -0.0468225705864149, \"optuna_trial_number\": 21, \"price_ratio\": 35.80041696470667, \"shift_exponent\": 0.0005196651920343949, \"step\": 21, \"test_objective\": [{\"annualised_returns\": -0.6501970747849068, \"annualised_returns_over_hodl\": -0.1336363333459053, \"annualised_returns_over_uniform_hodl\": 0.23464929142364177, \"calmar\": -1.1047485979962564, \"daily_log_sharpe\": -1.2205326741021179, \"daily_returns\": 0.02532822218127843, \"fee_revenue_over_value\": 1.6167801139021197e-07, \"jax_sharpe\": -0.5798584056875409, \"return\": -0.34494096078410974, \"returns_over_hodl\": -0.05613577276695836, \"returns_over_uniform_hodl\": 0.08859936047719241, \"sharpe\": -0.7941449207768917, \"sterling\": -1.7891153158080972, \"ulcer\": -0.1606945285697468}], \"train_objective\": [{\"annualised_returns\": -0.3646578613938648, \"annualised_returns_over_hodl\": -0.19988939065741895, \"annualised_returns_over_uniform_hodl\": -0.19988939065741917, \"calmar\": -0.49552983689192703, \"daily_log_sharpe\": -0.3651105500905461, \"daily_returns\": 0.008030077182850464, \"fee_revenue_over_value\": 0.012411726639262481, \"jax_sharpe\": 0.1539797926980475, \"return\": -0.24072038875782253, \"returns_over_hodl\": -0.1266258943509927, \"returns_over_uniform_hodl\": -0.1266258943509928, \"sharpe\": 0.15491932002455822, \"sterling\": -1.162379681430695, \"ulcer\": -0.16914220009134684}], \"train_return\": -0.24072038875782253, \"train_returns_over_hodl\": -0.1266258943509927, \"train_sharpe\": 0.1539797926980475, \"validation_return\": -0.2399243725189718, \"validation_returns_over_hodl\": -0.046822570586414924, \"validation_sharpe\": -1.7775293406351003}, {\"centeredness_margin\": 0.19885530349712233, \"continuous_test_metrics\": [{\"annualised_returns\": -0.6732307091888456, \"annualised_returns_over_hodl\": -0.09961803045531448, \"annualised_returns_over_uniform_hodl\": 0.15335077061153712, \"calmar\": -1.1366057224880104, \"daily_log_sharpe\": -1.3400788736665947, \"daily_returns\": 0.02300538217447894, \"fee_revenue_over_value\": 0.0009586293270896532, \"jax_sharpe\": -0.7361806208827104, \"return\": -0.3626667557822896, \"returns_over_hodl\": -0.041381194531550314, \"returns_over_uniform_hodl\": 0.05914203229183346, \"sharpe\": -0.9283056504512293, \"sterling\": -1.9179870939724653, \"ulcer\": -0.15694283579993815}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -0.03980179138135875, \"optuna_trial_number\": 22, \"price_ratio\": 98.11199345195139, \"shift_exponent\": 6.702240962476032e-05, \"step\": 22, \"test_objective\": [{\"annualised_returns\": -0.6732307091888456, \"annualised_returns_over_hodl\": -0.09961803045531448, \"annualised_returns_over_uniform_hodl\": 0.15335077061153712, \"calmar\": -1.1366057224880104, \"daily_log_sharpe\": -1.3400788736665947, \"daily_returns\": 0.02300538217447894, \"fee_revenue_over_value\": 0.0009586293270896532, \"jax_sharpe\": -0.7361806208827104, \"return\": -0.3626667557822896, \"returns_over_hodl\": -0.041381194531550314, \"returns_over_uniform_hodl\": 0.05914203229183346, \"sharpe\": -0.9283056504512293, \"sterling\": -1.9179870939724653, \"ulcer\": -0.15694283579993815}], \"train_objective\": [{\"annualised_returns\": -0.32691903867750516, \"annualised_returns_over_hodl\": -0.15236345053057176, \"annualised_returns_over_uniform_hodl\": -0.15236345053057188, \"calmar\": -0.44750040959398163, \"daily_log_sharpe\": -0.31916707071517053, \"daily_returns\": 0.008001213901319786, \"fee_revenue_over_value\": 0.0213800921347362, \"jax_sharpe\": 0.18042283722210495, \"return\": -0.2136498302382993, \"returns_over_hodl\": -0.09548753045139002, \"returns_over_uniform_hodl\": -0.09548753045139013, \"sharpe\": 0.1905851853625822, \"sterling\": -1.0584146215649455, \"ulcer\": -0.16638065478825717}], \"train_return\": -0.2136498302382993, \"train_returns_over_hodl\": -0.09548753045139002, \"train_sharpe\": 0.18042283722210492, \"validation_return\": -0.21552543236433241, \"validation_returns_over_hodl\": -0.03980179138135875, \"validation_sharpe\": -1.6406327581931612}, {\"centeredness_margin\": 0.628868341378121, \"continuous_test_metrics\": [{\"annualised_returns\": -0.710839105665422, \"annualised_returns_over_hodl\": -0.13536302006421508, \"annualised_returns_over_uniform_hodl\": 0.020609799297953924, \"calmar\": -1.1891370028625623, \"daily_log_sharpe\": -1.5313492380663956, \"daily_returns\": 0.021318456650852552, \"fee_revenue_over_value\": 0.005367581124695705, \"jax_sharpe\": -0.9699480360700093, \"return\": -0.393290886007716, \"returns_over_hodl\": -0.05689383340008869, \"returns_over_uniform_hodl\": 0.008249812533329681, \"sharpe\": -1.1355246295690569, \"sterling\": -2.0936279584569504, \"ulcer\": -0.15245483277581054}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -0.036094223141930865, \"optuna_trial_number\": 23, \"price_ratio\": 58.271782855203895, \"shift_exponent\": 0.0022039292919051687, \"step\": 23, \"test_objective\": [{\"annualised_returns\": -0.710839105665422, \"annualised_returns_over_hodl\": -0.13536302006421508, \"annualised_returns_over_uniform_hodl\": 0.020609799297953924, \"calmar\": -1.1891370028625623, \"daily_log_sharpe\": -1.5313492380663956, \"daily_returns\": 0.021318456650852552, \"fee_revenue_over_value\": 0.005367581124695705, \"jax_sharpe\": -0.9699480360700093, \"return\": -0.393290886007716, \"returns_over_hodl\": -0.05689383340008869, \"returns_over_uniform_hodl\": 0.008249812533329681, \"sharpe\": -1.1355246295690569, \"sterling\": -2.0936279584569504, \"ulcer\": -0.15245483277581054}], \"train_objective\": [{\"annualised_returns\": -0.34003289069959564, \"annualised_returns_over_hodl\": -0.16887822500349237, \"annualised_returns_over_uniform_hodl\": -0.16887822500349237, \"calmar\": -0.4625666885415824, \"daily_log_sharpe\": -0.34397073580796084, \"daily_returns\": 0.008006201259579944, \"fee_revenue_over_value\": 0.009600415356900238, \"jax_sharpe\": 0.13558367698215024, \"return\": -0.22298728780849097, \"returns_over_hodl\": -0.10622809760696017, \"returns_over_uniform_hodl\": -0.10622809760696017, \"sharpe\": 0.16289660676010426, \"sterling\": -1.101865446134212, \"ulcer\": -0.16644488454161102}], \"train_return\": -0.22298728780849097, \"train_returns_over_hodl\": -0.10622809760696017, \"train_sharpe\": 0.13558367698215024, \"validation_return\": -0.19658202327009655, \"validation_returns_over_hodl\": -0.03609422314193089, \"validation_sharpe\": -1.528840156141668}, {\"centeredness_margin\": 0.5665615129267872, \"continuous_test_metrics\": [{\"annualised_returns\": -0.689845544814252, \"annualised_returns_over_hodl\": -0.023485494485300684, \"annualised_returns_over_uniform_hodl\": 0.09470776464063357, \"calmar\": -1.2068527086986718, \"daily_log_sharpe\": -1.5041356821166825, \"daily_returns\": 0.02047403500355324, \"fee_revenue_over_value\": 0.05762475728153627, \"jax_sharpe\": -0.9375639417491327, \"return\": -0.37592147281248245, \"returns_over_hodl\": -0.009525676445733322, \"returns_over_uniform_hodl\": 0.037114893334029286, \"sharpe\": -1.119980398367672, \"sterling\": -2.0692799074468717, \"ulcer\": -0.14588264606104323}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -0.03384281273680259, \"optuna_trial_number\": 24, \"price_ratio\": 5.067907005287249, \"shift_exponent\": 0.029289837327161353, \"step\": 24, \"test_objective\": [{\"annualised_returns\": -0.689845544814252, \"annualised_returns_over_hodl\": -0.023485494485300684, \"annualised_returns_over_uniform_hodl\": 0.09470776464063357, \"calmar\": -1.2068527086986718, \"daily_log_sharpe\": -1.5041356821166825, \"daily_returns\": 0.02047403500355324, \"fee_revenue_over_value\": 0.05762475728153627, \"jax_sharpe\": -0.9375639417491327, \"return\": -0.37592147281248245, \"returns_over_hodl\": -0.009525676445733322, \"returns_over_uniform_hodl\": 0.037114893334029286, \"sharpe\": -1.119980398367672, \"sterling\": -2.0692799074468717, \"ulcer\": -0.14588264606104323}], \"train_objective\": [{\"annualised_returns\": -0.3372836442955003, \"annualised_returns_over_hodl\": -0.1654159940542913, \"annualised_returns_over_uniform_hodl\": -0.1654159940542913, \"calmar\": -0.45770998275845043, \"daily_log_sharpe\": -0.34886287656680753, \"daily_returns\": 0.008213809907502931, \"fee_revenue_over_value\": 0.02403300677725595, \"jax_sharpe\": 0.09295252516511024, \"return\": -0.22102374587817564, \"returns_over_hodl\": -0.10396950057379606, \"returns_over_uniform_hodl\": -0.10396950057379606, \"sharpe\": 0.14805809588366028, \"sterling\": -1.0953004179574986, \"ulcer\": -0.16647486098086528}], \"train_return\": -0.22102374587817564, \"train_returns_over_hodl\": -0.10396950057379606, \"train_sharpe\": 0.09295252516511025, \"validation_return\": -0.18678519178555175, \"validation_returns_over_hodl\": -0.03384281273680256, \"validation_sharpe\": -1.4845120656742208}, {\"centeredness_margin\": 0.5770659883979493, \"continuous_test_metrics\": [{\"annualised_returns\": -0.5060549886867192, \"annualised_returns_over_hodl\": -0.04180174710476847, \"annualised_returns_over_uniform_hodl\": 0.7434069707827338, \"calmar\": -0.8729776532316886, \"daily_log_sharpe\": -0.7482828315132392, \"daily_returns\": 0.031016042519256878, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": -0.06822599770312662, \"return\": -0.2472811577814804, \"returns_over_hodl\": -0.01705011103023668, \"returns_over_uniform_hodl\": 0.25089373812633897, \"sharpe\": -0.265129616892687, \"sterling\": -1.302862120099012, \"ulcer\": -0.17641031603377716}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -0.03205944379103953, \"optuna_trial_number\": 25, \"price_ratio\": 5.282761687559317, \"shift_exponent\": 0.0008421031350632165, \"step\": 25, \"test_objective\": [{\"annualised_returns\": -0.5060549886867192, \"annualised_returns_over_hodl\": -0.04180174710476847, \"annualised_returns_over_uniform_hodl\": 0.7434069707827338, \"calmar\": -0.8729776532316886, \"daily_log_sharpe\": -0.7482828315132392, \"daily_returns\": 0.031016042519256878, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": -0.06822599770312662, \"return\": -0.2472811577814804, \"returns_over_hodl\": -0.01705011103023668, \"returns_over_uniform_hodl\": 0.25089373812633897, \"sharpe\": -0.265129616892687, \"sterling\": -1.302862120099012, \"ulcer\": -0.17641031603377716}], \"train_objective\": [{\"annualised_returns\": -0.46479051134249527, \"annualised_returns_over_hodl\": -0.32599025930317516, \"annualised_returns_over_uniform_hodl\": -0.32599025930317493, \"calmar\": -0.6199922709610469, \"daily_log_sharpe\": -0.4562421042688257, \"daily_returns\": 0.008198704129709772, \"fee_revenue_over_value\": 0.006607808383848758, \"jax_sharpe\": 0.1169539371237963, \"return\": -0.3158032670422749, \"returns_over_hodl\": -0.21299123420774946, \"returns_over_uniform_hodl\": -0.21299123420774935, \"sharpe\": 0.1269342983128421, \"sterling\": -1.3856397644554659, \"ulcer\": -0.18148727779423968}], \"train_return\": -0.3158032670422749, \"train_returns_over_hodl\": -0.21299123420774946, \"train_sharpe\": 0.1169539371237963, \"validation_return\": -0.3373451121728017, \"validation_returns_over_hodl\": -0.03205944379103953, \"validation_sharpe\": -2.227722741303018}, {\"centeredness_margin\": 0.850438197253974, \"continuous_test_metrics\": [{\"annualised_returns\": -0.48450645991819563, \"annualised_returns_over_hodl\": 1.0262679595030022e-11, \"annualised_returns_over_uniform_hodl\": 0.8194637269088307, \"calmar\": -0.8358049789217605, \"daily_log_sharpe\": -0.6924636139648328, \"daily_returns\": 0.03101604242841083, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": 0.005019263591309562, \"return\": -0.23422460336662676, \"returns_over_hodl\": 4.133138276074533e-12, \"returns_over_uniform_hodl\": 0.272591563719371, \"sharpe\": -0.20210854969756434, \"sterling\": -1.2473843338351347, \"ulcer\": -0.17641031584597783}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -1.429508622585729e-09, \"optuna_trial_number\": 26, \"price_ratio\": 1.259166614847615, \"shift_exponent\": 3.682586173501245e-05, \"step\": 26, \"test_objective\": [{\"annualised_returns\": -0.48450645991819563, \"annualised_returns_over_hodl\": 1.0262679595030022e-11, \"annualised_returns_over_uniform_hodl\": 0.8194637269088307, \"calmar\": -0.8358049789217605, \"daily_log_sharpe\": -0.6924636139648328, \"daily_returns\": 0.03101604242841083, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": 0.005019263591309562, \"return\": -0.23422460336662676, \"returns_over_hodl\": 4.133138276074533e-12, \"returns_over_uniform_hodl\": 0.272591563719371, \"sharpe\": -0.20210854969756434, \"sterling\": -1.2473843338351347, \"ulcer\": -0.17641031584597783}], \"train_objective\": [{\"annualised_returns\": -0.6740661994017723, \"annualised_returns_over_hodl\": -0.5895391223788207, \"annualised_returns_over_uniform_hodl\": -0.5895391223788206, \"calmar\": -0.8447863272511777, \"daily_log_sharpe\": -0.7613632698765921, \"daily_returns\": 0.009899533252375225, \"fee_revenue_over_value\": 0.00015626066392749097, \"jax_sharpe\": 0.03669299836597281, \"return\": -0.4936970620812755, \"returns_over_hodl\": -0.4176165551596899, \"returns_over_uniform_hodl\": -0.41761655515968976, \"sharpe\": -0.08128111725458086, \"sterling\": -1.7922738407691763, \"ulcer\": -0.2109282589606764}], \"train_return\": -0.4936970620812755, \"train_returns_over_hodl\": -0.4176165551596899, \"train_sharpe\": 0.03669299836597281, \"validation_return\": -0.33914271961923004, \"validation_returns_over_hodl\": -1.429508622585729e-09, \"validation_sharpe\": -2.2438532006753205}, {\"centeredness_margin\": 0.593893832901862, \"continuous_test_metrics\": [{\"annualised_returns\": -0.5531489860501968, \"annualised_returns_over_hodl\": -0.13315885144024842, \"annualised_returns_over_uniform_hodl\": 0.5771860324090154, \"calmar\": -0.9576391828403471, \"daily_log_sharpe\": -0.8632326154770026, \"daily_returns\": 0.03101604250748899, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": -0.18420263374787818, \"return\": -0.2770515907364882, \"returns_over_hodl\": -0.05592630501614282, \"returns_over_uniform_hodl\": 0.20142022148768124, \"sharpe\": -0.3893817464542542, \"sterling\": -1.432151435606905, \"ulcer\": -0.1743092649033141}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -0.06623588696981383, \"optuna_trial_number\": 27, \"price_ratio\": 6.965369985900944, \"shift_exponent\": 0.0010263354035752415, \"step\": 27, \"test_objective\": [{\"annualised_returns\": -0.5531489860501968, \"annualised_returns_over_hodl\": -0.13315885144024842, \"annualised_returns_over_uniform_hodl\": 0.5771860324090154, \"calmar\": -0.9576391828403471, \"daily_log_sharpe\": -0.8632326154770026, \"daily_returns\": 0.03101604250748899, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": -0.18420263374787818, \"return\": -0.2770515907364882, \"returns_over_hodl\": -0.05592630501614282, \"returns_over_uniform_hodl\": 0.20142022148768124, \"sharpe\": -0.3893817464542542, \"sterling\": -1.432151435606905, \"ulcer\": -0.1743092649033141}], \"train_objective\": [{\"annualised_returns\": -0.42868381034831027, \"annualised_returns_over_hodl\": -0.28051971236733453, \"annualised_returns_over_uniform_hodl\": -0.2805197123673344, \"calmar\": -0.5752044938866325, \"daily_log_sharpe\": -0.4233856898182715, \"daily_returns\": 0.008158483047087828, \"fee_revenue_over_value\": 0.009040401209418328, \"jax_sharpe\": 0.1669122069539441, \"return\": -0.28814007782494266, \"returns_over_hodl\": -0.1811711869098107, \"returns_over_uniform_hodl\": -0.1811711869098106, \"sharpe\": 0.13713567394809606, \"sterling\": -1.3082966215469611, \"ulcer\": -0.17681001687651815}], \"train_return\": -0.28814007782494266, \"train_returns_over_hodl\": -0.1811711869098107, \"train_sharpe\": 0.16691220695394413, \"validation_return\": -0.31855962011728356, \"validation_returns_over_hodl\": -0.06623588696981386, \"validation_sharpe\": -2.102686061121086}, {\"centeredness_margin\": 0.8129669463778022, \"continuous_test_metrics\": [{\"annualised_returns\": -0.48450645935013936, \"annualised_returns_over_hodl\": 9.476419648990486e-12, \"annualised_returns_over_uniform_hodl\": 0.819463728913818, \"calmar\": -0.8358049780116139, \"daily_log_sharpe\": -0.6924636125384819, \"daily_returns\": 0.031016042455403348, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": 0.005019265298939046, \"return\": -0.23422460302677306, \"returns_over_hodl\": 3.816502669451438e-12, \"returns_over_uniform_hodl\": 0.27259156428415143, \"sharpe\": -0.2021085480245149, \"sterling\": -1.247384331931512, \"ulcer\": -0.17641031590177966}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -1.1614893491440625e-09, \"optuna_trial_number\": 28, \"price_ratio\": 2.0758536958739677, \"shift_exponent\": 0.00019931159291905156, \"step\": 28, \"test_objective\": [{\"annualised_returns\": -0.48450645935013936, \"annualised_returns_over_hodl\": 9.476419648990486e-12, \"annualised_returns_over_uniform_hodl\": 0.819463728913818, \"calmar\": -0.8358049780116139, \"daily_log_sharpe\": -0.6924636125384819, \"daily_returns\": 0.031016042455403348, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": 0.005019265298939046, \"return\": -0.23422460302677306, \"returns_over_hodl\": 3.816502669451438e-12, \"returns_over_uniform_hodl\": 0.27259156428415143, \"sharpe\": -0.2021085480245149, \"sterling\": -1.247384331931512, \"ulcer\": -0.17641031590177966}], \"train_objective\": [{\"annualised_returns\": -0.6275684474133507, \"annualised_returns_over_hodl\": -0.5309827282474067, \"annualised_returns_over_uniform_hodl\": -0.5309827282474067, \"calmar\": -0.8022732766263457, \"daily_log_sharpe\": -0.6713375846518187, \"daily_returns\": 0.008486758089834207, \"fee_revenue_over_value\": 0.0003325711402698241, \"jax_sharpe\": 0.09181160523758844, \"return\": -0.45099899784362607, \"returns_over_hodl\": -0.36850239074074465, \"returns_over_uniform_hodl\": -0.36850239074074465, \"sharpe\": -0.0025442761221808672, \"sterling\": -1.6958589192228084, \"ulcer\": -0.20635275967609934}], \"train_return\": -0.45099899784362607, \"train_returns_over_hodl\": -0.36850239074074465, \"train_sharpe\": 0.09181160523758844, \"validation_return\": -0.3391427197399596, \"validation_returns_over_hodl\": -1.1614893491440625e-09, \"validation_sharpe\": -2.243853200010367}, {\"centeredness_margin\": 0.4128862764596324, \"continuous_test_metrics\": [{\"annualised_returns\": -0.47941554208868764, \"annualised_returns_over_hodl\": 0.009875809511228129, \"annualised_returns_over_uniform_hodl\": 0.8374324105241353, \"calmar\": -0.8270228015811782, \"daily_log_sharpe\": -0.685509402107856, \"daily_returns\": 0.03101604251439623, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": -0.0016394972710695509, \"return\": -0.23118777201271423, \"returns_over_hodl\": 0.003965693182046426, \"returns_over_uniform_hodl\": 0.27763827320940937, \"sharpe\": -0.1959016859926715, \"sterling\": -1.2342775061128015, \"ulcer\": -0.17641031602373403}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -6.952727282794058e-10, \"optuna_trial_number\": 29, \"price_ratio\": 5.881559507427358, \"shift_exponent\": 0.00011230832242569256, \"step\": 29, \"test_objective\": [{\"annualised_returns\": -0.47941554208868764, \"annualised_returns_over_hodl\": 0.009875809511228129, \"annualised_returns_over_uniform_hodl\": 0.8374324105241353, \"calmar\": -0.8270228015811782, \"daily_log_sharpe\": -0.685509402107856, \"daily_returns\": 0.03101604251439623, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": -0.0016394972710695509, \"return\": -0.23118777201271423, \"returns_over_hodl\": 0.003965693182046426, \"returns_over_uniform_hodl\": 0.27763827320940937, \"sharpe\": -0.1959016859926715, \"sterling\": -1.2342775061128015, \"ulcer\": -0.17641031602373403}], \"train_objective\": [{\"annualised_returns\": -0.4748864165906196, \"annualised_returns_over_hodl\": -0.33870441819347563, \"annualised_returns_over_uniform_hodl\": -0.33870441819347563, \"calmar\": -0.635268019147673, \"daily_log_sharpe\": -0.46581446669500776, \"daily_returns\": 0.008190967630367963, \"fee_revenue_over_value\": 0.010780999153188364, \"jax_sharpe\": 0.10830386846090773, \"return\": -0.32366826304407537, \"returns_over_hodl\": -0.2220380777516815, \"returns_over_uniform_hodl\": -0.2220380777516815, \"sharpe\": 0.12036795664765765, \"sterling\": -1.4108456265145266, \"ulcer\": -0.18233780679871545}], \"train_return\": -0.32366826304407537, \"train_returns_over_hodl\": -0.2220380777516815, \"train_sharpe\": 0.10830386846090771, \"validation_return\": -0.3391427200003795, \"validation_returns_over_hodl\": -6.952727282794058e-10, \"validation_sharpe\": -2.243853198521972}, {\"centeredness_margin\": 0.7494527117953039, \"continuous_test_metrics\": [{\"annualised_returns\": -0.5536131905309711, \"annualised_returns_over_hodl\": -0.1340593556450651, \"annualised_returns_over_uniform_hodl\": 0.5755475963298684, \"calmar\": -0.9553004725279806, \"daily_log_sharpe\": -0.8700890122019166, \"daily_returns\": 0.031016042495000184, \"fee_revenue_over_value\": 0.0009559395419643437, \"jax_sharpe\": -0.19892185296997206, \"return\": -0.27735415044264444, \"returns_over_hodl\": -0.056321407104995114, \"returns_over_uniform_hodl\": 0.20091741749153758, \"sharpe\": -0.3964649275999281, \"sterling\": -1.4258262286733145, \"ulcer\": -0.17626998722788972}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -6.789770967685627e-10, \"optuna_trial_number\": 30, \"price_ratio\": 2.174739546439921, \"shift_exponent\": 0.0027133949575331192, \"step\": 30, \"test_objective\": [{\"annualised_returns\": -0.5536131905309711, \"annualised_returns_over_hodl\": -0.1340593556450651, \"annualised_returns_over_uniform_hodl\": 0.5755475963298684, \"calmar\": -0.9553004725279806, \"daily_log_sharpe\": -0.8700890122019166, \"daily_returns\": 0.031016042495000184, \"fee_revenue_over_value\": 0.0009559395419643437, \"jax_sharpe\": -0.19892185296997206, \"return\": -0.27735415044264444, \"returns_over_hodl\": -0.056321407104995114, \"returns_over_uniform_hodl\": 0.20091741749153758, \"sharpe\": -0.3964649275999281, \"sterling\": -1.4258262286733145, \"ulcer\": -0.17626998722788972}], \"train_objective\": [{\"annualised_returns\": -0.5397750407990494, \"annualised_returns_over_hodl\": -0.4204211397833745, \"annualised_returns_over_uniform_hodl\": -0.4204211397833746, \"calmar\": -0.6968488513752424, \"daily_log_sharpe\": -0.5351524368711941, \"daily_returns\": 0.008538738746560704, \"fee_revenue_over_value\": 0.00042059489588731756, \"jax_sharpe\": 0.1595106597832736, \"return\": -0.3757163203626088, \"returns_over_hodl\": -0.28190722850758587, \"returns_over_uniform_hodl\": -0.281907228507586, \"sharpe\": 0.10104410626502097, \"sterling\": -1.515333044638403, \"ulcer\": -0.19354730905469097}], \"train_return\": -0.3757163203626088, \"train_returns_over_hodl\": -0.28190722850758587, \"train_sharpe\": 0.15951065978327364, \"validation_return\": -0.33914271985209554, \"validation_returns_over_hodl\": -6.789770967685627e-10, \"validation_sharpe\": -2.2438531983814287}, {\"centeredness_margin\": 0.958329475643128, \"continuous_test_metrics\": [{\"annualised_returns\": -0.7379048038485073, \"annualised_returns_over_hodl\": -0.13933802180535826, \"annualised_returns_over_uniform_hodl\": -0.07492011962163436, \"calmar\": -1.228945733703011, \"daily_log_sharpe\": -1.6928948359445541, \"daily_returns\": 0.019436724208402062, \"fee_revenue_over_value\": 0.003118531177181942, \"jax_sharpe\": -1.1629783631256922, \"return\": -0.41683497706334716, \"returns_over_hodl\": -0.05864240659127562, \"returns_over_uniform_hodl\": -0.0308765576589759, \"sharpe\": -1.309004777633515, \"sterling\": -2.2406231123115825, \"ulcer\": -0.1490596954549468}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -0.03244236341129436, \"optuna_trial_number\": 31, \"price_ratio\": 63.321859356949155, \"shift_exponent\": 0.0033119501603066865, \"step\": 31, \"test_objective\": [{\"annualised_returns\": -0.7379048038485073, \"annualised_returns_over_hodl\": -0.13933802180535826, \"annualised_returns_over_uniform_hodl\": -0.07492011962163436, \"calmar\": -1.228945733703011, \"daily_log_sharpe\": -1.6928948359445541, \"daily_returns\": 0.019436724208402062, \"fee_revenue_over_value\": 0.003118531177181942, \"jax_sharpe\": -1.1629783631256922, \"return\": -0.41683497706334716, \"returns_over_hodl\": -0.05864240659127562, \"returns_over_uniform_hodl\": -0.0308765576589759, \"sharpe\": -1.309004777633515, \"sterling\": -2.2406231123115825, \"ulcer\": -0.1490596954549468}], \"train_objective\": [{\"annualised_returns\": -0.3294956386312957, \"annualised_returns_over_hodl\": -0.15560826121412308, \"annualised_returns_over_uniform_hodl\": -0.15560826121412308, \"calmar\": -0.44790130092159897, \"daily_log_sharpe\": -0.33778955602712163, \"daily_returns\": 0.007985513659060582, \"fee_revenue_over_value\": 0.0009106743845636981, \"jax_sharpe\": 0.15571435076798068, \"return\": -0.21547876512690356, \"returns_over_hodl\": -0.09759129347751927, \"returns_over_uniform_hodl\": -0.09759129347751927, \"sharpe\": 0.1609023649463048, \"sterling\": -1.077328461532898, \"ulcer\": -0.1651171154748559}], \"train_return\": -0.21547876512690356, \"train_returns_over_hodl\": -0.09759129347751927, \"train_sharpe\": 0.15571435076798065, \"validation_return\": -0.1730030707055057, \"validation_returns_over_hodl\": -0.03244236341129436, \"validation_sharpe\": -1.3651887166440817}, {\"centeredness_margin\": 0.83636065566304, \"continuous_test_metrics\": [{\"annualised_returns\": -0.712372712949747, \"annualised_returns_over_hodl\": -0.027203783354979638, \"annualised_returns_over_uniform_hodl\": 0.015196845287492433, \"calmar\": -1.2208494754138979, \"daily_log_sharpe\": -1.6071391052671193, \"daily_returns\": 0.018944174536850183, \"fee_revenue_over_value\": 0.03292614831152089, \"jax_sharpe\": -1.0699270328254098, \"return\": -0.3945888623987023, \"returns_over_hodl\": -0.01104631020210034, \"returns_over_uniform_hodl\": 0.006092791280965182, \"sharpe\": -1.2264849132042055, \"sterling\": -2.1751245215679686, \"ulcer\": -0.14642519427241438}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -0.031243322199007023, \"optuna_trial_number\": 32, \"price_ratio\": 9.542335133645036, \"shift_exponent\": 0.012079104620820397, \"step\": 32, \"test_objective\": [{\"annualised_returns\": -0.712372712949747, \"annualised_returns_over_hodl\": -0.027203783354979638, \"annualised_returns_over_uniform_hodl\": 0.015196845287492433, \"calmar\": -1.2208494754138979, \"daily_log_sharpe\": -1.6071391052671193, \"daily_returns\": 0.018944174536850183, \"fee_revenue_over_value\": 0.03292614831152089, \"jax_sharpe\": -1.0699270328254098, \"return\": -0.3945888623987023, \"returns_over_hodl\": -0.01104631020210034, \"returns_over_uniform_hodl\": 0.006092791280965182, \"sharpe\": -1.2264849132042055, \"sterling\": -2.1751245215679686, \"ulcer\": -0.14642519427241438}], \"train_objective\": [{\"annualised_returns\": -0.33261777539347614, \"annualised_returns_over_hodl\": -0.1595400872263557, \"annualised_returns_over_uniform_hodl\": -0.1595400872263557, \"calmar\": -0.45212830589194364, \"daily_log_sharpe\": -0.34857845663554354, \"daily_returns\": 0.008103607190474593, \"fee_revenue_over_value\": 0.008461544858646254, \"jax_sharpe\": 0.09864222812853583, \"return\": -0.2176986399100398, \"returns_over_hodl\": -0.10014474167324072, \"returns_over_uniform_hodl\": -0.10014474167324072, \"sharpe\": 0.14414601707204605, \"sterling\": -1.0887592015859948, \"ulcer\": -0.16537164084290215}], \"train_return\": -0.2176986399100398, \"train_returns_over_hodl\": -0.10014474167324072, \"train_sharpe\": 0.0986422281285358, \"validation_return\": -0.16509576202534382, \"validation_returns_over_hodl\": -0.03124332219900705, \"validation_sharpe\": -1.3198956626204097}, {\"centeredness_margin\": 0.6527573555569173, \"continuous_test_metrics\": [{\"annualised_returns\": -0.7615039789821587, \"annualised_returns_over_hodl\": -0.3031230717765676, \"annualised_returns_over_uniform_hodl\": -0.15821474855885376, \"calmar\": -1.2932578099202054, \"daily_log_sharpe\": -1.8055023121492577, \"daily_returns\": 0.02205765513018387, \"fee_revenue_over_value\": 0.03385001399721638, \"jax_sharpe\": -1.1849295219894378, \"return\": -0.43857970456730044, \"returns_over_hodl\": -0.1353645778571504, \"returns_over_uniform_hodl\": -0.06701268438564312, \"sharpe\": -1.4250144399423295, \"sterling\": -2.220722001160202, \"ulcer\": -0.14410109849223937}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -0.06003800601699594, \"optuna_trial_number\": 33, \"price_ratio\": 1.6490776411971744, \"shift_exponent\": 0.011860048478247866, \"step\": 33, \"test_objective\": [{\"annualised_returns\": -0.7615039789821587, \"annualised_returns_over_hodl\": -0.3031230717765676, \"annualised_returns_over_uniform_hodl\": -0.15821474855885376, \"calmar\": -1.2932578099202054, \"daily_log_sharpe\": -1.8055023121492577, \"daily_returns\": 0.02205765513018387, \"fee_revenue_over_value\": 0.03385001399721638, \"jax_sharpe\": -1.1849295219894378, \"return\": -0.43857970456730044, \"returns_over_hodl\": -0.1353645778571504, \"returns_over_uniform_hodl\": -0.06701268438564312, \"sharpe\": -1.4250144399423295, \"sterling\": -2.220722001160202, \"ulcer\": -0.14410109849223937}], \"train_objective\": [{\"annualised_returns\": -0.4548273115014223, \"annualised_returns_over_hodl\": -0.3134432213980055, \"annualised_returns_over_uniform_hodl\": -0.3134432213980055, \"calmar\": -0.5991496157172983, \"daily_log_sharpe\": -0.4981950521006969, \"daily_returns\": 0.008809765524663253, \"fee_revenue_over_value\": 0.009858578194227596, \"jax_sharpe\": 0.015731099649590814, \"return\": -0.3080985952749856, \"returns_over_hodl\": -0.2041288063031379, \"returns_over_uniform_hodl\": -0.2041288063031379, \"sharpe\": 0.0565450066931467, \"sterling\": -1.3831387286152284, \"ulcer\": -0.17906619069695717}], \"train_return\": -0.3080985952749856, \"train_returns_over_hodl\": -0.2041288063031379, \"train_sharpe\": 0.01573109964959081, \"validation_return\": -0.18860684415855877, \"validation_returns_over_hodl\": -0.06003800601699594, \"validation_sharpe\": -1.5388748684734608}, {\"centeredness_margin\": 0.5115569361986103, \"continuous_test_metrics\": [{\"annualised_returns\": -0.500236195821496, \"annualised_returns_over_hodl\": -0.03051393716253825, \"annualised_returns_over_uniform_hodl\": 0.7639447306758835, \"calmar\": -0.8629398400048385, \"daily_log_sharpe\": -0.7338901684712147, \"daily_returns\": 0.031016042488419358, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": -0.05155349651778945, \"return\": -0.2437224811632862, \"returns_over_hodl\": -0.012402956278406596, \"returns_over_uniform_hodl\": 0.25680766780105757, \"sharpe\": -0.24927540966687953, \"sterling\": -1.2878813396556499, \"ulcer\": -0.17641031603032986}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -0.051785863248496655, \"optuna_trial_number\": 34, \"price_ratio\": 8.546638160738295, \"shift_exponent\": 2.050792722803713e-05, \"step\": 34, \"test_objective\": [{\"annualised_returns\": -0.500236195821496, \"annualised_returns_over_hodl\": -0.03051393716253825, \"annualised_returns_over_uniform_hodl\": 0.7639447306758835, \"calmar\": -0.8629398400048385, \"daily_log_sharpe\": -0.7338901684712147, \"daily_returns\": 0.031016042488419358, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": -0.05155349651778945, \"return\": -0.2437224811632862, \"returns_over_hodl\": -0.012402956278406596, \"returns_over_uniform_hodl\": 0.25680766780105757, \"sharpe\": -0.24927540966687953, \"sterling\": -1.2878813396556499, \"ulcer\": -0.17641031603032986}], \"train_objective\": [{\"annualised_returns\": -0.4418312950187011, \"annualised_returns_over_hodl\": -0.2970768417181865, \"annualised_returns_over_uniform_hodl\": -0.2970768417181865, \"calmar\": -0.5940343667478964, \"daily_log_sharpe\": -0.4377625121717487, \"daily_returns\": 0.008141390375037933, \"fee_revenue_over_value\": 0.009929784558723072, \"jax_sharpe\": 0.14933264876261715, \"return\": -0.2981312450907826, \"returns_over_hodl\": -0.19266369460525223, \"returns_over_uniform_hodl\": -0.19266369460525223, \"sharpe\": 0.12525643957176413, \"sterling\": -1.3430889036132267, \"ulcer\": -0.17786413231113685}], \"train_return\": -0.2981312450907826, \"train_returns_over_hodl\": -0.19266369460525223, \"train_sharpe\": 0.14933264876261712, \"validation_return\": -0.3325038655828354, \"validation_returns_over_hodl\": -0.051785863248496655, \"validation_sharpe\": -2.1851438896595443}, {\"centeredness_margin\": 0.7046756535415696, \"continuous_test_metrics\": [{\"annualised_returns\": -0.6864461012780059, \"annualised_returns_over_hodl\": 0.044774289530694844, \"annualised_returns_over_uniform_hodl\": 0.10670629367145912, \"calmar\": -1.2063240324215792, \"daily_log_sharpe\": -1.4717076156652178, \"daily_returns\": 0.01864718612217024, \"fee_revenue_over_value\": 0.1130614076579353, \"jax_sharpe\": -0.8461160747379031, \"return\": -0.37317562861287645, \"returns_over_hodl\": 0.01779677598350715, \"returns_over_uniform_hodl\": 0.041678030487650064, \"sharpe\": -1.085995618967962, \"sterling\": -1.9759061813904601, \"ulcer\": -0.1452714728362834}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -0.21351658009373953, \"optuna_trial_number\": 35, \"price_ratio\": 1.153419442317359, \"shift_exponent\": 0.08646351606339432, \"step\": 35, \"test_objective\": [{\"annualised_returns\": -0.6864461012780059, \"annualised_returns_over_hodl\": 0.044774289530694844, \"annualised_returns_over_uniform_hodl\": 0.10670629367145912, \"calmar\": -1.2063240324215792, \"daily_log_sharpe\": -1.4717076156652178, \"daily_returns\": 0.01864718612217024, \"fee_revenue_over_value\": 0.1130614076579353, \"jax_sharpe\": -0.8461160747379031, \"return\": -0.37317562861287645, \"returns_over_hodl\": 0.01779677598350715, \"returns_over_uniform_hodl\": 0.041678030487650064, \"sharpe\": -1.085995618967962, \"sterling\": -1.9759061813904601, \"ulcer\": -0.1452714728362834}], \"train_objective\": [{\"annualised_returns\": -0.7263483637370289, \"annualised_returns_over_hodl\": -0.6553800477986339, \"annualised_returns_over_uniform_hodl\": -0.6553800477986339, \"calmar\": -0.9023690408544841, \"daily_log_sharpe\": -1.291208960525545, \"daily_returns\": 0.00807931570673304, \"fee_revenue_over_value\": 0.03367209992789592, \"jax_sharpe\": -0.9399299328059537, \"return\": -0.544686109082898, \"returns_over_hodl\": -0.47626756153938477, \"returns_over_uniform_hodl\": -0.47626756153938477, \"sharpe\": -0.8217429792848793, \"sterling\": -2.2113022660292243, \"ulcer\": -0.1812314903458506}], \"train_return\": -0.544686109082898, \"train_returns_over_hodl\": -0.47626756153938477, \"train_sharpe\": -0.9399299328059538, \"validation_return\": -0.21084961404699498, \"validation_returns_over_hodl\": -0.06001574513704566, \"validation_sharpe\": -1.853814119027507}, {\"centeredness_margin\": 0.7086295885023812, \"continuous_test_metrics\": [{\"annualised_returns\": -0.47375023677493855, \"annualised_returns_over_hodl\": 0.020865873536267454, \"annualised_returns_over_uniform_hodl\": 0.8574284273871098, \"calmar\": -0.8172497670361812, \"daily_log_sharpe\": -0.6708974714526418, \"daily_returns\": 0.03101604243456805, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": 0.017202152806481488, \"return\": -0.22782908707511695, \"returns_over_hodl\": 0.008351686727583907, \"returns_over_uniform_hodl\": 0.28321984991658566, \"sharpe\": -0.17939821813561502, \"sterling\": -1.219691892054382, \"ulcer\": -0.1764103158587189}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -1.1630992835520715e-09, \"optuna_trial_number\": 36, \"price_ratio\": 1.3361775156697682, \"shift_exponent\": 0.0023384116240952415, \"step\": 36, \"test_objective\": [{\"annualised_returns\": -0.47375023677493855, \"annualised_returns_over_hodl\": 0.020865873536267454, \"annualised_returns_over_uniform_hodl\": 0.8574284273871098, \"calmar\": -0.8172497670361812, \"daily_log_sharpe\": -0.6708974714526418, \"daily_returns\": 0.03101604243456805, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": 0.017202152806481488, \"return\": -0.22782908707511695, \"returns_over_hodl\": 0.008351686727583907, \"returns_over_uniform_hodl\": 0.28321984991658566, \"sharpe\": -0.17939821813561502, \"sterling\": -1.219691892054382, \"ulcer\": -0.1764103158587189}], \"train_objective\": [{\"annualised_returns\": -0.6642607482900205, \"annualised_returns_over_hodl\": -0.5771907434705448, \"annualised_returns_over_uniform_hodl\": -0.577190743470545, \"calmar\": -0.8358967302795856, \"daily_log_sharpe\": -0.7403545534735296, \"daily_returns\": 0.009480802618740536, \"fee_revenue_over_value\": 0.0003475760761230448, \"jax_sharpe\": 0.05033150420418283, \"return\": -0.484503466188522, \"returns_over_hodl\": -0.40704146731109603, \"returns_over_uniform_hodl\": -0.40704146731109614, \"sharpe\": -0.06182292732858929, \"sterling\": -1.7747124462241257, \"ulcer\": -0.2096859938166237}], \"train_return\": -0.484503466188522, \"train_returns_over_hodl\": -0.40704146731109603, \"train_sharpe\": 0.05033150420418283, \"validation_return\": -0.3391427195112027, \"validation_returns_over_hodl\": -1.1630992835520715e-09, \"validation_sharpe\": -2.2438531991629462}, {\"centeredness_margin\": 0.5052980571138177, \"continuous_test_metrics\": [{\"annualised_returns\": -0.47990205971728517, \"annualised_returns_over_hodl\": 0.008932019517190026, \"annualised_returns_over_uniform_hodl\": 0.8357152189224828, \"calmar\": -0.8278620773790923, \"daily_log_sharpe\": -0.686491212033073, \"daily_returns\": 0.031016042515255447, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": -0.0020509356832316443, \"return\": -0.23147722053326203, \"returns_over_hodl\": 0.003587712207129945, \"returns_over_uniform_hodl\": 0.27715725782683176, \"sharpe\": -0.19693064437031044, \"sterling\": -1.2355300703983418, \"ulcer\": -0.1764103160255059}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -0.0003904328679297242, \"optuna_trial_number\": 37, \"price_ratio\": 6.211798646164876, \"shift_exponent\": 2.779357755085777e-05, \"step\": 37, \"test_objective\": [{\"annualised_returns\": -0.47990205971728517, \"annualised_returns_over_hodl\": 0.008932019517190026, \"annualised_returns_over_uniform_hodl\": 0.8357152189224828, \"calmar\": -0.8278620773790923, \"daily_log_sharpe\": -0.686491212033073, \"daily_returns\": 0.031016042515255447, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": -0.0020509356832316443, \"return\": -0.23147722053326203, \"returns_over_hodl\": 0.003587712207129945, \"returns_over_uniform_hodl\": 0.27715725782683176, \"sharpe\": -0.19693064437031044, \"sterling\": -1.2355300703983418, \"ulcer\": -0.1764103160255059}], \"train_objective\": [{\"annualised_returns\": -0.47229235499387023, \"annualised_returns_over_hodl\": -0.33543761739634803, \"annualised_returns_over_uniform_hodl\": -0.33543761739634825, \"calmar\": -0.6321474883217897, \"daily_log_sharpe\": -0.46449916434982064, \"daily_returns\": 0.008177757322745175, \"fee_revenue_over_value\": 0.010428200311765414, \"jax_sharpe\": 0.1542403275603459, \"return\": -0.3216417861987628, \"returns_over_hodl\": -0.2197070887771485, \"returns_over_uniform_hodl\": -0.21970708877714862, \"sharpe\": 0.11867723841224931, \"sterling\": -1.4068832311743067, \"ulcer\": -0.18183046101777411}], \"train_return\": -0.3216417861987628, \"train_returns_over_hodl\": -0.2197070887771485, \"train_sharpe\": 0.1542403275603459, \"validation_return\": -0.33909109220590805, \"validation_returns_over_hodl\": -0.0003904328679297242, \"validation_sharpe\": -2.2433855460307766}, {\"centeredness_margin\": 0.3625617011576409, \"continuous_test_metrics\": [{\"annualised_returns\": -0.4767803335034797, \"annualised_returns_over_hodl\": 0.014987820761557913, \"annualised_returns_over_uniform_hodl\": 0.8467335288909403, \"calmar\": -0.8224768886839772, \"daily_log_sharpe\": -0.6785771300886128, \"daily_returns\": 0.031016042505022382, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": 0.0075592420077063, \"return\": -0.2296227831317167, \"returns_over_hodl\": 0.006009358993849023, \"returns_over_uniform_hodl\": 0.280239024886765, \"sharpe\": -0.18785729351689195, \"sterling\": -1.2274930282618257, \"ulcer\": -0.17641031600435814}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -6.773461791453883e-10, \"optuna_trial_number\": 38, \"price_ratio\": 4.322143053654341, \"shift_exponent\": 0.0004712026638049547, \"step\": 38, \"test_objective\": [{\"annualised_returns\": -0.4767803335034797, \"annualised_returns_over_hodl\": 0.014987820761557913, \"annualised_returns_over_uniform_hodl\": 0.8467335288909403, \"calmar\": -0.8224768886839772, \"daily_log_sharpe\": -0.6785771300886128, \"daily_returns\": 0.031016042505022382, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": 0.0075592420077063, \"return\": -0.2296227831317167, \"returns_over_hodl\": 0.006009358993849023, \"returns_over_uniform_hodl\": 0.280239024886765, \"sharpe\": -0.18785729351689195, \"sterling\": -1.2274930282618257, \"ulcer\": -0.17641031600435814}], \"train_objective\": [{\"annualised_returns\": -0.5035646951425214, \"annualised_returns_over_hodl\": -0.3748200691676087, \"annualised_returns_over_uniform_hodl\": -0.3748200691676087, \"calmar\": -0.6689755993770131, \"daily_log_sharpe\": -0.49442215290411834, \"daily_returns\": 0.008226368491062449, \"fee_revenue_over_value\": 0.011583898553432772, \"jax_sharpe\": 0.1720659002344994, \"return\": -0.3463402905020063, \"returns_over_hodl\": -0.24811695753606766, \"returns_over_uniform_hodl\": -0.24811695753606766, \"sharpe\": 0.1143835569868649, \"sterling\": -1.4622450333415418, \"ulcer\": -0.18686234147576158}], \"train_return\": -0.3463402905020063, \"train_returns_over_hodl\": -0.24811695753606766, \"train_sharpe\": 0.1720659002344994, \"validation_return\": -0.33914271996752476, \"validation_returns_over_hodl\": -6.773461791453883e-10, \"validation_sharpe\": -2.243853198844044}, {\"centeredness_margin\": 0.35080381281321377, \"continuous_test_metrics\": [{\"annualised_returns\": -0.5666288097613262, \"annualised_returns_over_hodl\": -0.159308206414022, \"annualised_returns_over_uniform_hodl\": 0.5296082290409396, \"calmar\": -0.9808631948051224, \"daily_log_sharpe\": -0.8992236905651806, \"daily_returns\": 0.031016042499060682, \"fee_revenue_over_value\": 0.0002594723389470649, \"jax_sharpe\": -0.22176535948368198, \"return\": -0.28591517890144635, \"returns_over_hodl\": -0.06750096271203665, \"returns_over_uniform_hodl\": 0.18669040962300354, \"sharpe\": -0.4281944035119144, \"sterling\": -1.4695456748476565, \"ulcer\": -0.17377252267032664}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -0.06444299498710235, \"optuna_trial_number\": 39, \"price_ratio\": 5.619449905376056, \"shift_exponent\": 0.001667467141487576, \"step\": 39, \"test_objective\": [{\"annualised_returns\": -0.5666288097613262, \"annualised_returns_over_hodl\": -0.159308206414022, \"annualised_returns_over_uniform_hodl\": 0.5296082290409396, \"calmar\": -0.9808631948051224, \"daily_log_sharpe\": -0.8992236905651806, \"daily_returns\": 0.031016042499060682, \"fee_revenue_over_value\": 0.0002594723389470649, \"jax_sharpe\": -0.22176535948368198, \"return\": -0.28591517890144635, \"returns_over_hodl\": -0.06750096271203665, \"returns_over_uniform_hodl\": 0.18669040962300354, \"sharpe\": -0.4281944035119144, \"sterling\": -1.4695456748476565, \"ulcer\": -0.17377252267032664}], \"train_objective\": [{\"annualised_returns\": -0.43576107863780766, \"annualised_returns_over_hodl\": -0.28943238649912373, \"annualised_returns_over_uniform_hodl\": -0.28943238649912373, \"calmar\": -0.5831387806458265, \"daily_log_sharpe\": -0.42626850081473766, \"daily_returns\": 0.008168114471102177, \"fee_revenue_over_value\": 0.012949269309451053, \"jax_sharpe\": 0.18835578049987872, \"return\": -0.2935069450219666, \"returns_over_hodl\": -0.18734451590341983, \"returns_over_uniform_hodl\": -0.18734451590341983, \"sharpe\": 0.1438651699112451, \"sterling\": -1.318181678954201, \"ulcer\": -0.1783710164970671}], \"train_return\": -0.2935069450219666, \"train_returns_over_hodl\": -0.18734451590341983, \"train_sharpe\": 0.18835578049987872, \"validation_return\": -0.32399985746360505, \"validation_returns_over_hodl\": -0.06444299498710238, \"validation_sharpe\": -2.1336280285298916}, {\"centeredness_margin\": 0.1675781045091378, \"continuous_test_metrics\": [{\"annualised_returns\": -0.48450645933341907, \"annualised_returns_over_hodl\": 9.453327010078283e-12, \"annualised_returns_over_uniform_hodl\": 0.8194637289728322, \"calmar\": -0.8358049779848244, \"daily_log_sharpe\": -0.6924636124965015, \"daily_returns\": 0.031016042456197827, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": 0.00501926534920096, \"return\": -0.23422460301676973, \"returns_over_hodl\": 3.807176796044587e-12, \"returns_over_uniform_hodl\": 0.272591564300775, \"sharpe\": -0.20210854797527047, \"sterling\": -1.2473843318754805, \"ulcer\": -0.17641031590342213}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -1.1516467779415507e-09, \"optuna_trial_number\": 40, \"price_ratio\": 2.046539938143315, \"shift_exponent\": 0.0003340605960565864, \"step\": 40, \"test_objective\": [{\"annualised_returns\": -0.48450645933341907, \"annualised_returns_over_hodl\": 9.453327010078283e-12, \"annualised_returns_over_uniform_hodl\": 0.8194637289728322, \"calmar\": -0.8358049779848244, \"daily_log_sharpe\": -0.6924636124965015, \"daily_returns\": 0.031016042456197827, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": 0.00501926534920096, \"return\": -0.23422460301676973, \"returns_over_hodl\": 3.807176796044587e-12, \"returns_over_uniform_hodl\": 0.272591564300775, \"sharpe\": -0.20210854797527047, \"sterling\": -1.2473843318754805, \"ulcer\": -0.17641031590342213}], \"train_objective\": [{\"annualised_returns\": -0.6190961684426337, \"annualised_returns_over_hodl\": -0.520313263910203, \"annualised_returns_over_uniform_hodl\": -0.520313263910203, \"calmar\": -0.7943742367589129, \"daily_log_sharpe\": -0.6542337089859827, \"daily_returns\": 0.008588841228895795, \"fee_revenue_over_value\": 0.01426370884269257, \"jax_sharpe\": 0.09955625403773082, \"return\": -0.4434502020685043, \"returns_over_hodl\": -0.35981926181010204, \"returns_over_uniform_hodl\": -0.35981926181010204, \"sharpe\": 0.01415204748848527, \"sterling\": -1.6783801969285108, \"ulcer\": -0.20527414010777617}], \"train_return\": -0.4434502020685043, \"train_returns_over_hodl\": -0.35981926181010204, \"train_sharpe\": 0.09955625403773082, \"validation_return\": -0.339142719742222, \"validation_returns_over_hodl\": -1.1516467779415507e-09, \"validation_sharpe\": -2.2438531999760505}, {\"centeredness_margin\": 0.3204215514508628, \"continuous_test_metrics\": [{\"annualised_returns\": -0.5026553264902301, \"annualised_returns_over_hodl\": -0.03520678095345431, \"annualised_returns_over_uniform_hodl\": 0.7554062715873049, \"calmar\": -0.8671130047588574, \"daily_log_sharpe\": -0.7419994794839764, \"daily_returns\": 0.031016042506253536, \"fee_revenue_over_value\": 1.6996166332116465e-06, \"jax_sharpe\": -0.06394190049917635, \"return\": -0.24519895955175475, \"returns_over_hodl\": -0.014331039097092435, \"returns_over_uniform_hodl\": 0.25435400586644885, \"sharpe\": -0.2587796897243782, \"sterling\": -1.2941095156345086, \"ulcer\": -0.176410316006906}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -6.288776166485377e-10, \"optuna_trial_number\": 41, \"price_ratio\": 3.4794114460496, \"shift_exponent\": 0.0015652022909308817, \"step\": 41, \"test_objective\": [{\"annualised_returns\": -0.5026553264902301, \"annualised_returns_over_hodl\": -0.03520678095345431, \"annualised_returns_over_uniform_hodl\": 0.7554062715873049, \"calmar\": -0.8671130047588574, \"daily_log_sharpe\": -0.7419994794839764, \"daily_returns\": 0.031016042506253536, \"fee_revenue_over_value\": 1.6996166332116465e-06, \"jax_sharpe\": -0.06394190049917635, \"return\": -0.24519895955175475, \"returns_over_hodl\": -0.014331039097092435, \"returns_over_uniform_hodl\": 0.25435400586644885, \"sharpe\": -0.2587796897243782, \"sterling\": -1.2941095156345086, \"ulcer\": -0.176410316006906}], \"train_objective\": [{\"annualised_returns\": -0.4980968483860101, \"annualised_returns_over_hodl\": -0.36793420101200014, \"annualised_returns_over_uniform_hodl\": -0.36793420101200036, \"calmar\": -0.6583134988756256, \"daily_log_sharpe\": -0.4814470767681689, \"daily_returns\": 0.008289436102841644, \"fee_revenue_over_value\": 0.012808531113307504, \"jax_sharpe\": 0.14042200778942857, \"return\": -0.34197869850147544, \"returns_over_hodl\": -0.24309996319529037, \"returns_over_uniform_hodl\": -0.24309996319529048, \"sharpe\": 0.13295346302625607, \"sterling\": -1.440055102480544, \"ulcer\": -0.18718609324229887}], \"train_return\": -0.34197869850147544, \"train_returns_over_hodl\": -0.24309996319529037, \"train_sharpe\": 0.1404220077894286, \"validation_return\": -0.3391427199424788, \"validation_returns_over_hodl\": -6.288776166485377e-10, \"validation_sharpe\": -2.2438531985065424}, {\"centeredness_margin\": 0.05869945048540731, \"continuous_test_metrics\": [{\"annualised_returns\": -0.5126334670899839, \"annualised_returns_over_hodl\": -0.0545632618191394, \"annualised_returns_over_uniform_hodl\": 0.7201878576371186, \"calmar\": -0.9160403464380146, \"daily_log_sharpe\": -0.7914399320430129, \"daily_returns\": 0.031016042510378577, \"fee_revenue_over_value\": 0.04368700222340003, \"jax_sharpe\": -0.12661072691908315, \"return\": -0.2513347329378245, \"returns_over_hodl\": -0.022343536725628166, \"returns_over_uniform_hodl\": 0.24415736925167342, \"sharpe\": -0.3252871631368491, \"sterling\": -1.3375326737942137, \"ulcer\": -0.17209292018196987}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -0.05226793492791304, \"optuna_trial_number\": 42, \"price_ratio\": 7.748856170965325, \"shift_exponent\": 0.003069659509092334, \"step\": 42, \"test_objective\": [{\"annualised_returns\": -0.5126334670899839, \"annualised_returns_over_hodl\": -0.0545632618191394, \"annualised_returns_over_uniform_hodl\": 0.7201878576371186, \"calmar\": -0.9160403464380146, \"daily_log_sharpe\": -0.7914399320430129, \"daily_returns\": 0.031016042510378577, \"fee_revenue_over_value\": 0.04368700222340003, \"jax_sharpe\": -0.12661072691908315, \"return\": -0.2513347329378245, \"returns_over_hodl\": -0.022343536725628166, \"returns_over_uniform_hodl\": 0.24415736925167342, \"sharpe\": -0.3252871631368491, \"sterling\": -1.3375326737942137, \"ulcer\": -0.17209292018196987}], \"train_objective\": [{\"annualised_returns\": -0.41321272039987644, \"annualised_returns_over_hodl\": -0.26103637818617564, \"annualised_returns_over_uniform_hodl\": -0.26103637818617587, \"calmar\": -0.5593628564726078, \"daily_log_sharpe\": -0.39102481400702666, \"daily_returns\": 0.008137283365210813, \"fee_revenue_over_value\": 0.029550014377508958, \"jax_sharpe\": 0.15550794586931466, \"return\": -0.2764980995825329, \"returns_over_hodl\": -0.16777980620512467, \"returns_over_uniform_hodl\": -0.16777980620512478, \"sharpe\": 0.17491368595547455, \"sterling\": -1.2570858554052557, \"ulcer\": -0.17727886719606661}], \"train_return\": -0.2764980995825329, \"train_returns_over_hodl\": -0.16777980620512467, \"train_sharpe\": 0.15550794586931468, \"validation_return\": -0.3284026353982902, \"validation_returns_over_hodl\": -0.05226793492791304, \"validation_sharpe\": -2.1738523036611928}, {\"centeredness_margin\": 0.9265142512941076, \"continuous_test_metrics\": [{\"annualised_returns\": -0.5396169828510383, \"annualised_returns_over_hodl\": -0.10690827373438683, \"annualised_returns_over_uniform_hodl\": 0.6249480062435968, \"calmar\": -0.9334613254820129, \"daily_log_sharpe\": -0.8279551886824956, \"daily_returns\": 0.031016042498855416, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": -0.14753810566043343, \"return\": -0.26831290043957656, \"returns_over_hodl\": -0.04451474703990976, \"returns_over_uniform_hodl\": 0.21594247383307774, \"sharpe\": -0.35074473653307087, \"sterling\": -1.3935072068947794, \"ulcer\": -0.17525794889270715}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -0.06398749651244873, \"optuna_trial_number\": 43, \"price_ratio\": 6.585465557066333, \"shift_exponent\": 0.0009127979231305436, \"step\": 43, \"test_objective\": [{\"annualised_returns\": -0.5396169828510383, \"annualised_returns_over_hodl\": -0.10690827373438683, \"annualised_returns_over_uniform_hodl\": 0.6249480062435968, \"calmar\": -0.9334613254820129, \"daily_log_sharpe\": -0.8279551886824956, \"daily_returns\": 0.031016042498855416, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": -0.14753810566043343, \"return\": -0.26831290043957656, \"returns_over_hodl\": -0.04451474703990976, \"returns_over_uniform_hodl\": 0.21594247383307774, \"sharpe\": -0.35074473653307087, \"sterling\": -1.3935072068947794, \"ulcer\": -0.17525794889270715}], \"train_objective\": [{\"annualised_returns\": -0.4422813996622702, \"annualised_returns_over_hodl\": -0.2976436756785845, \"annualised_returns_over_uniform_hodl\": -0.2976436756785845, \"calmar\": -0.5914941923276341, \"daily_log_sharpe\": -0.4405693743245383, \"daily_returns\": 0.008187930426504912, \"fee_revenue_over_value\": 0.00024211098055314638, \"jax_sharpe\": 0.16154245195437517, \"return\": -0.29847492034786527, \"returns_over_hodl\": -0.19305901283300908, \"returns_over_uniform_hodl\": -0.19305901283300908, \"sharpe\": 0.12370129543031516, \"sterling\": -1.3416733157202976, \"ulcer\": -0.17818874211837377}], \"train_return\": -0.29847492034786527, \"train_returns_over_hodl\": -0.19305901283300908, \"train_sharpe\": 0.1615424519543752, \"validation_return\": -0.32474296566980176, \"validation_returns_over_hodl\": -0.06398749651244873, \"validation_sharpe\": -2.131627205028309}, {\"centeredness_margin\": 0.245712438931568, \"continuous_test_metrics\": [{\"annualised_returns\": -0.48394278757774634, \"annualised_returns_over_hodl\": 0.0010934587369249016, \"annualised_returns_over_uniform_hodl\": 0.8214532404479273, \"calmar\": -0.834832606167788, \"daily_log_sharpe\": -0.6912911276158765, \"daily_returns\": 0.031016042496261963, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": 0.005500564277341165, \"return\": -0.23388748255113234, \"returns_over_hodl\": 0.0004402334742807934, \"returns_over_uniform_hodl\": 0.27315180254088256, \"sharpe\": -0.2008496994435849, \"sterling\": -1.2459331296489446, \"ulcer\": -0.17641031598624943}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -8.037909227098794e-10, \"optuna_trial_number\": 44, \"price_ratio\": 3.736581599516051, \"shift_exponent\": 0.00017700114263404374, \"step\": 44, \"test_objective\": [{\"annualised_returns\": -0.48394278757774634, \"annualised_returns_over_hodl\": 0.0010934587369249016, \"annualised_returns_over_uniform_hodl\": 0.8214532404479273, \"calmar\": -0.834832606167788, \"daily_log_sharpe\": -0.6912911276158765, \"daily_returns\": 0.031016042496261963, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": 0.005500564277341165, \"return\": -0.23388748255113234, \"returns_over_hodl\": 0.0004402334742807934, \"returns_over_uniform_hodl\": 0.27315180254088256, \"sharpe\": -0.2008496994435849, \"sterling\": -1.2459331296489446, \"ulcer\": -0.17641031598624943}], \"train_objective\": [{\"annualised_returns\": -0.5276441884629599, \"annualised_returns_over_hodl\": -0.40514429434106347, \"annualised_returns_over_uniform_hodl\": -0.40514429434106347, \"calmar\": -0.6977897321112798, \"daily_log_sharpe\": -0.5216634186474908, \"daily_returns\": 0.008238252151468454, \"fee_revenue_over_value\": 0.012289555955148902, \"jax_sharpe\": 0.12182163892560884, \"return\": -0.3657771224100005, \"returns_over_hodl\": -0.27047449938619383, \"returns_over_uniform_hodl\": -0.27047449938619383, \"sharpe\": 0.10433814724827478, \"sterling\": -1.500147914911726, \"ulcer\": -0.19183956965296628}], \"train_return\": -0.3657771224100005, \"train_returns_over_hodl\": -0.27047449938619383, \"train_sharpe\": 0.12182163892560882, \"validation_return\": -0.3391427199544347, \"validation_returns_over_hodl\": -8.037909227098794e-10, \"validation_sharpe\": -2.2438531993223885}, {\"centeredness_margin\": 0.7875125755905152, \"continuous_test_metrics\": [{\"annualised_returns\": -0.7009448969944942, \"annualised_returns_over_hodl\": 0.03697948144204655, \"annualised_returns_over_uniform_hodl\": 0.055531970738478265, \"calmar\": -1.1970208245212366, \"daily_log_sharpe\": -1.5552867699800386, \"daily_returns\": 0.018457563476500703, \"fee_revenue_over_value\": 0.041568072037715925, \"jax_sharpe\": -1.0313751209082858, \"return\": -0.38501406072850575, \"returns_over_hodl\": 0.014731731148686089, \"returns_over_uniform_hodl\": 0.022004521905045094, \"sharpe\": -1.1736268868034827, \"sterling\": -2.165538945927085, \"ulcer\": -0.14836276854934893}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -0.02366787887498234, \"optuna_trial_number\": 45, \"price_ratio\": 126.429991275987, \"shift_exponent\": 8.796052625722862, \"step\": 45, \"test_objective\": [{\"annualised_returns\": -0.7009448969944942, \"annualised_returns_over_hodl\": 0.03697948144204655, \"annualised_returns_over_uniform_hodl\": 0.055531970738478265, \"calmar\": -1.1970208245212366, \"daily_log_sharpe\": -1.5552867699800386, \"daily_returns\": 0.018457563476500703, \"fee_revenue_over_value\": 0.041568072037715925, \"jax_sharpe\": -1.0313751209082858, \"return\": -0.38501406072850575, \"returns_over_hodl\": 0.014731731148686089, \"returns_over_uniform_hodl\": 0.022004521905045094, \"sharpe\": -1.1736268868034827, \"sterling\": -2.165538945927085, \"ulcer\": -0.14836276854934893}], \"train_objective\": [{\"annualised_returns\": -0.30908728031103083, \"annualised_returns_over_hodl\": -0.12990723649202207, \"annualised_returns_over_uniform_hodl\": -0.12990723649202207, \"calmar\": -0.422914172259621, \"daily_log_sharpe\": -0.3151521320948033, \"daily_returns\": 0.00796174600438561, \"fee_revenue_over_value\": 0.020739306755903672, \"jax_sharpe\": 0.13743662279504107, \"return\": -0.2010669568593475, \"returns_over_hodl\": -0.08101386933745891, \"returns_over_uniform_hodl\": -0.08101386933745891, \"sharpe\": 0.17330573706108154, \"sterling\": -1.0242866620050648, \"ulcer\": -0.16311400395476894}], \"train_return\": -0.2010669568593475, \"train_returns_over_hodl\": -0.08101386933745891, \"train_sharpe\": 0.1374366227950411, \"validation_return\": -0.1597602586639837, \"validation_returns_over_hodl\": -0.02366787887498234, \"validation_sharpe\": -1.259140627761828}, {\"centeredness_margin\": 0.24057576870177455, \"continuous_test_metrics\": [{\"annualised_returns\": -0.590563623066134, \"annualised_returns_over_hodl\": -0.20573907588783125, \"annualised_returns_over_uniform_hodl\": 0.4451289461162262, \"calmar\": -1.0446878081895792, \"daily_log_sharpe\": -1.0087351794745574, \"daily_returns\": 0.03126421494411242, \"fee_revenue_over_value\": 0.050168044520315404, \"jax_sharpe\": -0.3615243723255649, \"return\": -0.3020684916838239, \"returns_over_hodl\": -0.08859502353644622, \"returns_over_uniform_hodl\": 0.1598462857932903, \"sharpe\": -0.5626927548130617, \"sterling\": -1.579764506993147, \"ulcer\": -0.16549592571197058}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -0.06060823579202637, \"optuna_trial_number\": 46, \"price_ratio\": 4.699244604340656, \"shift_exponent\": 0.0043181417449733445, \"step\": 46, \"test_objective\": [{\"annualised_returns\": -0.590563623066134, \"annualised_returns_over_hodl\": -0.20573907588783125, \"annualised_returns_over_uniform_hodl\": 0.4451289461162262, \"calmar\": -1.0446878081895792, \"daily_log_sharpe\": -1.0087351794745574, \"daily_returns\": 0.03126421494411242, \"fee_revenue_over_value\": 0.050168044520315404, \"jax_sharpe\": -0.3615243723255649, \"return\": -0.3020684916838239, \"returns_over_hodl\": -0.08859502353644622, \"returns_over_uniform_hodl\": 0.1598462857932903, \"sharpe\": -0.5626927548130617, \"sterling\": -1.579764506993147, \"ulcer\": -0.16549592571197058}], \"train_objective\": [{\"annualised_returns\": -0.44116747727500405, \"annualised_returns_over_hodl\": -0.2962408706922961, \"annualised_returns_over_uniform_hodl\": -0.2962408706922961, \"calmar\": -0.5884531016128154, \"daily_log_sharpe\": -0.43632621718939935, \"daily_returns\": 0.008219759796810817, \"fee_revenue_over_value\": 0.017439712493953725, \"jax_sharpe\": 0.18325842902591427, \"return\": -0.29762458884876486, \"returns_over_hodl\": -0.19208090476644712, \"returns_over_uniform_hodl\": -0.19208090476644712, \"sharpe\": 0.13649912013262974, \"sterling\": -1.3282588816975678, \"ulcer\": -0.17936273888071255}], \"train_return\": -0.29762458884876486, \"train_returns_over_hodl\": -0.19208090476644712, \"train_sharpe\": 0.18325842902591427, \"validation_return\": -0.3033284254190568, \"validation_returns_over_hodl\": -0.060608235792026344, \"validation_sharpe\": -2.0855616288773513}, {\"centeredness_margin\": 0.3197611991795718, \"continuous_test_metrics\": [{\"annualised_returns\": -0.6639351393235239, \"annualised_returns_over_hodl\": -0.095644452825697, \"annualised_returns_over_uniform_hodl\": 0.1861600123882925, \"calmar\": -1.179779090309236, \"daily_log_sharpe\": -1.3640680561870093, \"daily_returns\": 0.023713704075313126, \"fee_revenue_over_value\": 0.06862484375603413, \"jax_sharpe\": -0.7531415118223315, \"return\": -0.3554261656837787, \"returns_over_hodl\": -0.03967961648285889, \"returns_over_uniform_hodl\": 0.07117469084449035, \"sharpe\": -0.9674577473855839, \"sterling\": -1.91920102212684, \"ulcer\": -0.14920388929572534}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -0.039867613299356286, \"optuna_trial_number\": 47, \"price_ratio\": 3.3310949469963553, \"shift_exponent\": 0.014528135196325688, \"step\": 47, \"test_objective\": [{\"annualised_returns\": -0.6639351393235239, \"annualised_returns_over_hodl\": -0.095644452825697, \"annualised_returns_over_uniform_hodl\": 0.1861600123882925, \"calmar\": -1.179779090309236, \"daily_log_sharpe\": -1.3640680561870093, \"daily_returns\": 0.023713704075313126, \"fee_revenue_over_value\": 0.06862484375603413, \"jax_sharpe\": -0.7531415118223315, \"return\": -0.3554261656837787, \"returns_over_hodl\": -0.03967961648285889, \"returns_over_uniform_hodl\": 0.07117469084449035, \"sharpe\": -0.9674577473855839, \"sterling\": -1.91920102212684, \"ulcer\": -0.14920388929572534}], \"train_objective\": [{\"annualised_returns\": -0.4080121452959965, \"annualised_returns_over_hodl\": -0.2544870954258255, \"annualised_returns_over_uniform_hodl\": -0.2544870954258255, \"calmar\": -0.543803153599918, \"daily_log_sharpe\": -0.423097948423104, \"daily_returns\": 0.008302422321850245, \"fee_revenue_over_value\": 0.022965381191731266, \"jax_sharpe\": 0.07724685860014181, \"return\": -0.2726118362681359, \"returns_over_hodl\": -0.16330956665664642, \"returns_over_uniform_hodl\": -0.16330956665664642, \"sharpe\": 0.1125813733087092, \"sterling\": -1.2635540324509444, \"ulcer\": -0.17389327756996337}], \"train_return\": -0.2726118362681359, \"train_returns_over_hodl\": -0.16330956665664642, \"train_sharpe\": 0.07724685860014183, \"validation_return\": -0.22932154183106435, \"validation_returns_over_hodl\": -0.039867613299356286, \"validation_sharpe\": -1.7848178125610483}, {\"centeredness_margin\": 0.8298871434526581, \"continuous_test_metrics\": [{\"annualised_returns\": -0.7539945583751015, \"annualised_returns_over_hodl\": -0.3591964540358429, \"annualised_returns_over_uniform_hodl\": -0.13170982203256953, \"calmar\": -1.2627420713452078, \"daily_log_sharpe\": -1.7167313661820665, \"daily_returns\": 0.024296999082254744, \"fee_revenue_over_value\": 0.013368492867110225, \"jax_sharpe\": -1.103104387204163, \"return\": -0.43152627056870474, \"returns_over_hodl\": -0.16408751955014222, \"returns_over_uniform_hodl\": -0.05529104819658992, \"sharpe\": -1.3262644339800647, \"sterling\": -2.160033864323977, \"ulcer\": -0.15035802763316194}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -0.054328013704637246, \"optuna_trial_number\": 48, \"price_ratio\": 2.8420195103062853, \"shift_exponent\": 0.005146752058424224, \"step\": 48, \"test_objective\": [{\"annualised_returns\": -0.7539945583751015, \"annualised_returns_over_hodl\": -0.3591964540358429, \"annualised_returns_over_uniform_hodl\": -0.13170982203256953, \"calmar\": -1.2627420713452078, \"daily_log_sharpe\": -1.7167313661820665, \"daily_returns\": 0.024296999082254744, \"fee_revenue_over_value\": 0.013368492867110225, \"jax_sharpe\": -1.103104387204163, \"return\": -0.43152627056870474, \"returns_over_hodl\": -0.16408751955014222, \"returns_over_uniform_hodl\": -0.05529104819658992, \"sharpe\": -1.3262644339800647, \"sterling\": -2.160033864323977, \"ulcer\": -0.15035802763316194}], \"train_objective\": [{\"annualised_returns\": -0.3864112629116099, \"annualised_returns_over_hodl\": -0.22728427962515152, \"annualised_returns_over_uniform_hodl\": -0.22728427962515152, \"calmar\": -0.5143535676959279, \"daily_log_sharpe\": -0.37948132174312976, \"daily_returns\": 0.008381102711694873, \"fee_revenue_over_value\": 0.00322995816513102, \"jax_sharpe\": 0.1687081064939173, \"return\": -0.25661153039539497, \"returns_over_hodl\": -0.14490494650774777, \"returns_over_uniform_hodl\": -0.14490494650774777, \"sharpe\": 0.17804682234654734, \"sterling\": -1.1927859626464352, \"ulcer\": -0.17404611416792895}], \"train_return\": -0.25661153039539497, \"train_returns_over_hodl\": -0.14490494650774777, \"train_sharpe\": 0.1687081064939173, \"validation_return\": -0.2178856475854949, \"validation_returns_over_hodl\": -0.054328013704637246, \"validation_sharpe\": -1.678087809020629}, {\"centeredness_margin\": 0.37469941057374667, \"continuous_test_metrics\": [{\"annualised_returns\": -0.685135978778191, \"annualised_returns_over_hodl\": -0.3891989021049833, \"annualised_returns_over_uniform_hodl\": 0.1113304454422881, \"calmar\": -1.2283361999021751, \"daily_log_sharpe\": -1.355067247169328, \"daily_returns\": 0.031046893807007616, \"fee_revenue_over_value\": 0.0786918952415367, \"jax_sharpe\": -0.7195233056459107, \"return\": -0.3721221470047048, \"returns_over_hodl\": -0.18007570500831227, \"returns_over_uniform_hodl\": 0.043428742005657384, \"sharpe\": -0.9359730464499029, \"sterling\": -1.9185156072996483, \"ulcer\": -0.15545459070193543}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -0.07887432563872154, \"optuna_trial_number\": 49, \"price_ratio\": 1.8394356045700329, \"shift_exponent\": 0.0068687012867974144, \"step\": 49, \"test_objective\": [{\"annualised_returns\": -0.685135978778191, \"annualised_returns_over_hodl\": -0.3891989021049833, \"annualised_returns_over_uniform_hodl\": 0.1113304454422881, \"calmar\": -1.2283361999021751, \"daily_log_sharpe\": -1.355067247169328, \"daily_returns\": 0.031046893807007616, \"fee_revenue_over_value\": 0.0786918952415367, \"jax_sharpe\": -0.7195233056459107, \"return\": -0.3721221470047048, \"returns_over_hodl\": -0.18007570500831227, \"returns_over_uniform_hodl\": 0.043428742005657384, \"sharpe\": -0.9359730464499029, \"sterling\": -1.9185156072996483, \"ulcer\": -0.15545459070193543}], \"train_objective\": [{\"annualised_returns\": -0.47955051796524617, \"annualised_returns_over_hodl\": -0.34457809910668447, \"annualised_returns_over_uniform_hodl\": -0.34457809910668447, \"calmar\": -0.6229633023953813, \"daily_log_sharpe\": -0.4791694853717543, \"daily_returns\": 0.008642069501505993, \"fee_revenue_over_value\": 0.00975321362178738, \"jax_sharpe\": 0.13919002942491557, \"return\": -0.3273217726619778, \"returns_over_hodl\": -0.22624058845758, \"returns_over_uniform_hodl\": -0.22624058845758, \"sharpe\": 0.12361642328694052, \"sterling\": -1.3913966198675036, \"ulcer\": -0.18525882290325962}], \"train_return\": -0.3273217726619778, \"train_returns_over_hodl\": -0.22624058845758, \"train_sharpe\": 0.13919002942491557, \"validation_return\": -0.30930070203585636, \"validation_returns_over_hodl\": -0.07887432563872154, \"validation_sharpe\": -2.165918319784039}, {\"centeredness_margin\": 0.12391359038864314, \"continuous_test_metrics\": [{\"annualised_returns\": -0.6919945776437391, \"annualised_returns_over_hodl\": -0.16872943063160306, \"annualised_returns_over_uniform_hodl\": 0.08712263121574648, \"calmar\": -1.1663479775039802, \"daily_log_sharpe\": -1.4116497983927567, \"daily_returns\": 0.02363008885395152, \"fee_revenue_over_value\": 0.05135853953394076, \"jax_sharpe\": -0.8891974666887309, \"return\": -0.37766660254003104, \"returns_over_hodl\": -0.07172376781706036, \"returns_over_uniform_hodl\": 0.034214777479383285, \"sharpe\": -1.014220958738343, \"sterling\": -2.007463060333442, \"ulcer\": -0.15980382026387951}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -0.029102733345190535, \"optuna_trial_number\": 50, \"price_ratio\": 19.53653078391691, \"shift_exponent\": 46.36490349548378, \"step\": 50, \"test_objective\": [{\"annualised_returns\": -0.6919945776437391, \"annualised_returns_over_hodl\": -0.16872943063160306, \"annualised_returns_over_uniform_hodl\": 0.08712263121574648, \"calmar\": -1.1663479775039802, \"daily_log_sharpe\": -1.4116497983927567, \"daily_returns\": 0.02363008885395152, \"fee_revenue_over_value\": 0.05135853953394076, \"jax_sharpe\": -0.8891974666887309, \"return\": -0.37766660254003104, \"returns_over_hodl\": -0.07172376781706036, \"returns_over_uniform_hodl\": 0.034214777479383285, \"sharpe\": -1.014220958738343, \"sterling\": -2.007463060333442, \"ulcer\": -0.15980382026387951}], \"train_objective\": [{\"annualised_returns\": -0.3638522681340449, \"annualised_returns_over_hodl\": -0.1988748763117928, \"annualised_returns_over_uniform_hodl\": -0.1988748763117928, \"calmar\": -0.49586419496409745, \"daily_log_sharpe\": -0.35257219567417114, \"daily_returns\": 0.00807228685237187, \"fee_revenue_over_value\": 0.025529552998360017, \"jax_sharpe\": 0.15443044308705756, \"return\": -0.24013603302545894, \"returns_over_hodl\": -0.1259537293704278, \"returns_over_uniform_hodl\": -0.1259537293704278, \"sharpe\": 0.17960471166851696, \"sterling\": -1.1471422506351034, \"ulcer\": -0.17067384196924085}], \"train_return\": -0.24013603302545894, \"train_returns_over_hodl\": -0.1259537293704278, \"train_sharpe\": 0.15443044308705756, \"validation_return\": -0.24194319179293688, \"validation_returns_over_hodl\": -0.029102733345190535, \"validation_sharpe\": -1.828508007453688}, {\"centeredness_margin\": 0.7918701182676334, \"continuous_test_metrics\": [{\"annualised_returns\": -0.7594484124980699, \"annualised_returns_over_hodl\": -0.3513869711906348, \"annualised_returns_over_uniform_hodl\": -0.15095950990842222, \"calmar\": -1.274610888397562, \"daily_log_sharpe\": -1.7660142230352, \"daily_returns\": 0.023617698158956386, \"fee_revenue_over_value\": 0.017671123168468063, \"jax_sharpe\": -1.1600675887515721, \"return\": -0.43663592605994606, \"returns_over_hodl\": -0.15999955288800427, \"returns_over_uniform_hodl\": -0.06378244724160154, \"sharpe\": -1.3807568342892167, \"sterling\": -2.194357251224931, \"ulcer\": -0.1478953422060698}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -0.049593474507634516, \"optuna_trial_number\": 51, \"price_ratio\": 2.298739463479969, \"shift_exponent\": 0.006284870523136573, \"step\": 51, \"test_objective\": [{\"annualised_returns\": -0.7594484124980699, \"annualised_returns_over_hodl\": -0.3513869711906348, \"annualised_returns_over_uniform_hodl\": -0.15095950990842222, \"calmar\": -1.274610888397562, \"daily_log_sharpe\": -1.7660142230352, \"daily_returns\": 0.023617698158956386, \"fee_revenue_over_value\": 0.017671123168468063, \"jax_sharpe\": -1.1600675887515721, \"return\": -0.43663592605994606, \"returns_over_hodl\": -0.15999955288800427, \"returns_over_uniform_hodl\": -0.06378244724160154, \"sharpe\": -1.3807568342892167, \"sterling\": -2.194357251224931, \"ulcer\": -0.1478953422060698}], \"train_objective\": [{\"annualised_returns\": -0.4124035622232267, \"annualised_returns_over_hodl\": -0.2600173744046953, \"annualised_returns_over_uniform_hodl\": -0.2600173744046952, \"calmar\": -0.5468952347744162, \"daily_log_sharpe\": -0.414220655443405, \"daily_returns\": 0.008490923884629093, \"fee_revenue_over_value\": 0.003584363887817942, \"jax_sharpe\": 0.21121246216622583, \"return\": -0.27589254909262906, \"returns_over_hodl\": -0.16708326159927167, \"returns_over_uniform_hodl\": -0.16708326159927156, \"sharpe\": 0.151020138102475, \"sterling\": -1.2601443273608306, \"ulcer\": -0.17634726013116106}], \"train_return\": -0.27589254909262906, \"train_returns_over_hodl\": -0.16708326159927167, \"train_sharpe\": 0.21121246216622583, \"validation_return\": -0.2136191876761968, \"validation_returns_over_hodl\": -0.049593474507634516, \"validation_sharpe\": -1.678141664635403}, {\"centeredness_margin\": 0.3852516711233269, \"continuous_test_metrics\": [{\"annualised_returns\": -0.4827673514442282, \"annualised_returns_over_hodl\": 0.0033736744670003205, \"annualised_returns_over_uniform_hodl\": 0.8256020090394691, \"calmar\": -0.8328048992726634, \"daily_log_sharpe\": -0.688868955763285, \"daily_returns\": 0.03101604247976651, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": 0.006871348150945098, \"return\": -0.23318518522900988, \"returns_over_hodl\": 0.0013573398902786415, \"returns_over_uniform_hodl\": 0.2743189040843441, \"sharpe\": -0.1982623787236217, \"sterling\": -1.2429069098990337, \"ulcer\": -0.17641031595214723}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -9.065057593460324e-10, \"optuna_trial_number\": 52, \"price_ratio\": 2.612287910573122, \"shift_exponent\": 0.0009664439257612656, \"step\": 52, \"test_objective\": [{\"annualised_returns\": -0.4827673514442282, \"annualised_returns_over_hodl\": 0.0033736744670003205, \"annualised_returns_over_uniform_hodl\": 0.8256020090394691, \"calmar\": -0.8328048992726634, \"daily_log_sharpe\": -0.688868955763285, \"daily_returns\": 0.03101604247976651, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": 0.006871348150945098, \"return\": -0.23318518522900988, \"returns_over_hodl\": 0.0013573398902786415, \"returns_over_uniform_hodl\": 0.2743189040843441, \"sharpe\": -0.1982623787236217, \"sterling\": -1.2429069098990337, \"ulcer\": -0.17641031595214723}], \"train_objective\": [{\"annualised_returns\": -0.5696564360572894, \"annualised_returns_over_hodl\": -0.4580519215547978, \"annualised_returns_over_uniform_hodl\": -0.458051921554798, \"calmar\": -0.7393397624671489, \"daily_log_sharpe\": -0.5761415152827236, \"daily_returns\": 0.008374790089276794, \"fee_revenue_over_value\": 0.01090506251232587, \"jax_sharpe\": 0.12087739337427947, \"return\": -0.4006487089786366, \"returns_over_hodl\": -0.3105861266194322, \"returns_over_uniform_hodl\": -0.3105861266194323, \"sharpe\": 0.07411034424624548, \"sterling\": -1.5724660441965241, \"ulcer\": -0.19917329753394808}], \"train_return\": -0.4006487089786366, \"train_returns_over_hodl\": -0.3105861266194322, \"train_sharpe\": 0.12087739337427948, \"validation_return\": -0.3391427198402892, \"validation_returns_over_hodl\": -9.065057593460324e-10, \"validation_sharpe\": -2.2438531993208977}, {\"centeredness_margin\": 0.4073836838189104, \"continuous_test_metrics\": [{\"annualised_returns\": -0.48365227063945926, \"annualised_returns_over_hodl\": 0.0016570291918991398, \"annualised_returns_over_uniform_hodl\": 0.8224786364813714, \"calmar\": -0.8343314447938033, \"daily_log_sharpe\": -0.6906776655841161, \"daily_returns\": 0.031016042496668894, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": 0.005921583446677653, \"return\": -0.23371381623687004, \"returns_over_hodl\": 0.0006670183902153237, \"returns_over_uniform_hodl\": 0.27344040712050743, \"sharpe\": -0.2001820617683964, \"sterling\": -1.2451851790282114, \"ulcer\": -0.17641031598708556}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -7.943616875394355e-10, \"optuna_trial_number\": 53, \"price_ratio\": 3.736305007409588, \"shift_exponent\": 0.0002349209027263808, \"step\": 53, \"test_objective\": [{\"annualised_returns\": -0.48365227063945926, \"annualised_returns_over_hodl\": 0.0016570291918991398, \"annualised_returns_over_uniform_hodl\": 0.8224786364813714, \"calmar\": -0.8343314447938033, \"daily_log_sharpe\": -0.6906776655841161, \"daily_returns\": 0.031016042496668894, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": 0.005921583446677653, \"return\": -0.23371381623687004, \"returns_over_hodl\": 0.0006670183902153237, \"returns_over_uniform_hodl\": 0.27344040712050743, \"sharpe\": -0.2001820617683964, \"sterling\": -1.2451851790282114, \"ulcer\": -0.17641031598708556}], \"train_objective\": [{\"annualised_returns\": -0.5275610642600242, \"annualised_returns_over_hodl\": -0.40503961285903856, \"annualised_returns_over_uniform_hodl\": -0.4050396128590388, \"calmar\": -0.6975648418312564, \"daily_log_sharpe\": -0.5222875892025907, \"daily_returns\": 0.008227546413675933, \"fee_revenue_over_value\": 0.011756756546282067, \"jax_sharpe\": 0.16300979483326283, \"return\": -0.36570936439834767, \"returns_over_hodl\": -0.27039655959701514, \"returns_over_uniform_hodl\": -0.27039655959701525, \"sharpe\": 0.1027853246412666, \"sterling\": -1.5016270189495253, \"ulcer\": -0.19156215285054565}], \"train_return\": -0.36570936439834767, \"train_returns_over_hodl\": -0.27039655959701514, \"train_sharpe\": 0.16300979483326283, \"validation_return\": -0.3391427199526835, \"validation_returns_over_hodl\": -7.943616875394355e-10, \"validation_sharpe\": -2.2438531992765123}, {\"centeredness_margin\": 0.3800054672360175, \"continuous_test_metrics\": [{\"annualised_returns\": -0.5204753149468655, \"annualised_returns_over_hodl\": -0.06977557228400222, \"annualised_returns_over_uniform_hodl\": 0.6925096102525452, \"calmar\": -0.8979873197990833, \"daily_log_sharpe\": -0.7822918434668394, \"daily_returns\": 0.03101604251744062, \"fee_revenue_over_value\": 1.7158745674750642e-07, \"jax_sharpe\": -0.10357467673001208, \"return\": -0.25620971026800554, \"returns_over_hodl\": -0.028709603474485257, \"returns_over_uniform_hodl\": 0.23605596634556547, \"sharpe\": -0.3017858635253542, \"sterling\": -1.3402274120621203, \"ulcer\": -0.17634196471018684}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -0.029804659231455366, \"optuna_trial_number\": 54, \"price_ratio\": 4.523331177165092, \"shift_exponent\": 0.0013303206646943036, \"step\": 54, \"test_objective\": [{\"annualised_returns\": -0.5204753149468655, \"annualised_returns_over_hodl\": -0.06977557228400222, \"annualised_returns_over_uniform_hodl\": 0.6925096102525452, \"calmar\": -0.8979873197990833, \"daily_log_sharpe\": -0.7822918434668394, \"daily_returns\": 0.03101604251744062, \"fee_revenue_over_value\": 1.7158745674750642e-07, \"jax_sharpe\": -0.10357467673001208, \"return\": -0.25620971026800554, \"returns_over_hodl\": -0.028709603474485257, \"returns_over_uniform_hodl\": 0.23605596634556547, \"sharpe\": -0.3017858635253542, \"sterling\": -1.3402274120621203, \"ulcer\": -0.17634196471018684}], \"train_objective\": [{\"annualised_returns\": -0.4666415849440655, \"annualised_returns_over_hodl\": -0.3283213869543956, \"annualised_returns_over_uniform_hodl\": -0.3283213869543956, \"calmar\": -0.6213815464312716, \"daily_log_sharpe\": -0.45344690712115265, \"daily_returns\": 0.008213464288752742, \"fee_revenue_over_value\": 0.011859400826748981, \"jax_sharpe\": 0.13191280337790676, \"return\": -0.3172409126620328, \"returns_over_hodl\": -0.21464491019062237, \"returns_over_uniform_hodl\": -0.21464491019062237, \"sharpe\": 0.13748923954838055, \"sterling\": -1.3836887001873748, \"ulcer\": -0.18224642571583616}], \"train_return\": -0.3172409126620328, \"train_returns_over_hodl\": -0.21464491019062237, \"train_sharpe\": 0.13191280337790676, \"validation_return\": -0.33757993676057574, \"validation_returns_over_hodl\": -0.029804659231455366, \"validation_sharpe\": -2.229678905973636}, {\"centeredness_margin\": 0.30074941163031027, \"continuous_test_metrics\": [{\"annualised_returns\": -0.48189644752163807, \"annualised_returns_over_hodl\": 0.005063130029267171, \"annualised_returns_over_uniform_hodl\": 0.8286759138967903, \"calmar\": -0.8313025311603803, \"daily_log_sharpe\": -0.6871921236255449, \"daily_returns\": 0.031016042503018985, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": 0.007454599911872517, \"return\": -0.23266545378218517, \"returns_over_hodl\": 0.0020360390951732477, \"returns_over_uniform_hodl\": 0.27518261145537815, \"sharpe\": -0.1965629518238538, \"sterling\": -1.2406647231652483, \"ulcer\": -0.17641031600022283}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -7.221140352342559e-10, \"optuna_trial_number\": 55, \"price_ratio\": 4.491723093091295, \"shift_exponent\": 7.497949287299131e-05, \"step\": 55, \"test_objective\": [{\"annualised_returns\": -0.48189644752163807, \"annualised_returns_over_hodl\": 0.005063130029267171, \"annualised_returns_over_uniform_hodl\": 0.8286759138967903, \"calmar\": -0.8313025311603803, \"daily_log_sharpe\": -0.6871921236255449, \"daily_returns\": 0.031016042503018985, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": 0.007454599911872517, \"return\": -0.23266545378218517, \"returns_over_hodl\": 0.0020360390951732477, \"returns_over_uniform_hodl\": 0.27518261145537815, \"sharpe\": -0.1965629518238538, \"sterling\": -1.2406647231652483, \"ulcer\": -0.17641031600022283}], \"train_objective\": [{\"annualised_returns\": -0.5100237135141226, \"annualised_returns_over_hodl\": -0.3829541575760911, \"annualised_returns_over_uniform_hodl\": -0.3829541575760913, \"calmar\": -0.6780028638060676, \"daily_log_sharpe\": -0.5039053146504864, \"daily_returns\": 0.008214661762172504, \"fee_revenue_over_value\": 0.01167367749089928, \"jax_sharpe\": 0.16699520624610142, \"return\": -0.3515169120484579, \"returns_over_hodl\": -0.254071453279762, \"returns_over_uniform_hodl\": -0.2540714532797621, \"sharpe\": 0.10663595469853206, \"sterling\": -1.477037803085583, \"ulcer\": -0.1877548727288001}], \"train_return\": -0.3515169120484579, \"train_returns_over_hodl\": -0.254071453279762, \"train_sharpe\": 0.1669952062461014, \"validation_return\": -0.3391427199750099, \"validation_returns_over_hodl\": -7.221140352342559e-10, \"validation_sharpe\": -2.2438531990565527}, {\"centeredness_margin\": 0.8835921499287618, \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -Infinity, \"optuna_trial_number\": 56, \"price_ratio\": 11.737142888980037, \"shift_exponent\": 67.70351133083277, \"step\": 56, \"test_objective\": -Infinity, \"train_objective\": -Infinity, \"train_return\": -Infinity, \"train_returns_over_hodl\": -Infinity, \"train_sharpe\": -Infinity, \"validation_return\": -Infinity, \"validation_returns_over_hodl\": -Infinity, \"validation_sharpe\": -Infinity}, {\"centeredness_margin\": 0.9662718392756653, \"continuous_test_metrics\": [{\"annualised_returns\": -0.7169770585313295, \"annualised_returns_over_hodl\": -0.0027713991880597977, \"annualised_returns_over_uniform_hodl\": -0.0010544539441392775, \"calmar\": -1.210272551222532, \"daily_log_sharpe\": -1.6112861957593225, \"daily_returns\": 0.017805032438181375, \"fee_revenue_over_value\": 0.0484876026634861, \"jax_sharpe\": -1.0978001624269613, \"return\": -0.39851079064459827, \"returns_over_hodl\": -0.0011170723948398242, \"returns_over_uniform_hodl\": -0.00042480228652519436, \"sharpe\": -1.2302474678154773, \"sterling\": -2.210903562161558, \"ulcer\": -0.15116201074007415}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -0.037353964167109566, \"optuna_trial_number\": 57, \"price_ratio\": 23.1597945034033, \"shift_exponent\": 21.600037931557356, \"step\": 57, \"test_objective\": [{\"annualised_returns\": -0.7169770585313295, \"annualised_returns_over_hodl\": -0.0027713991880597977, \"annualised_returns_over_uniform_hodl\": -0.0010544539441392775, \"calmar\": -1.210272551222532, \"daily_log_sharpe\": -1.6112861957593225, \"daily_returns\": 0.017805032438181375, \"fee_revenue_over_value\": 0.0484876026634861, \"jax_sharpe\": -1.0978001624269613, \"return\": -0.39851079064459827, \"returns_over_hodl\": -0.0011170723948398242, \"returns_over_uniform_hodl\": -0.00042480228652519436, \"sharpe\": -1.2302474678154773, \"sterling\": -2.210903562161558, \"ulcer\": -0.15116201074007415}], \"train_objective\": [{\"annualised_returns\": -0.4090566598701776, \"annualised_returns_over_hodl\": -0.25580249250345166, \"annualised_returns_over_uniform_hodl\": -0.25580249250345166, \"calmar\": -0.5488056685415819, \"daily_log_sharpe\": -0.4800975273719408, \"daily_returns\": 0.0075297316619298005, \"fee_revenue_over_value\": 0.023697336511893, \"jax_sharpe\": -0.027044794819935932, \"return\": -0.27339129673283713, \"returns_over_hodl\": -0.16420615412741157, \"returns_over_uniform_hodl\": -0.16420615412741157, \"sharpe\": 0.0021795375511791085, \"sterling\": -1.3480245577212733, \"ulcer\": -0.1653452073382434}], \"train_return\": -0.27339129673283713, \"train_returns_over_hodl\": -0.16420615412741157, \"train_sharpe\": -0.02704479481993593, \"validation_return\": -0.16782631659813596, \"validation_returns_over_hodl\": -0.037353964167109566, \"validation_sharpe\": -1.357892695632681}, {\"centeredness_margin\": 0.3415999496816157, \"continuous_test_metrics\": [{\"annualised_returns\": -0.4847166212695402, \"annualised_returns_over_hodl\": -0.00040769330551204686, \"annualised_returns_over_uniform_hodl\": 0.818721950483255, \"calmar\": -0.8361675217109028, \"daily_log_sharpe\": -0.6983649375806094, \"daily_returns\": 0.031016042519582298, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": -0.015881155541906517, \"return\": -0.2343503528865366, \"returns_over_hodl\": -0.0001642135093765651, \"returns_over_uniform_hodl\": 0.2723825888960971, \"sharpe\": -0.21042244763374318, \"sterling\": -1.2479254034229927, \"ulcer\": -0.17641031603445279}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -0.024315896860047204, \"optuna_trial_number\": 58, \"price_ratio\": 6.923971712832317, \"shift_exponent\": 2.8661543388009192e-05, \"step\": 58, \"test_objective\": [{\"annualised_returns\": -0.4847166212695402, \"annualised_returns_over_hodl\": -0.00040769330551204686, \"annualised_returns_over_uniform_hodl\": 0.818721950483255, \"calmar\": -0.8361675217109028, \"daily_log_sharpe\": -0.6983649375806094, \"daily_returns\": 0.031016042519582298, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": -0.015881155541906517, \"return\": -0.2343503528865366, \"returns_over_hodl\": -0.0001642135093765651, \"returns_over_uniform_hodl\": 0.2723825888960971, \"sharpe\": -0.21042244763374318, \"sterling\": -1.2479254034229927, \"ulcer\": -0.17641031603445279}], \"train_objective\": [{\"annualised_returns\": -0.4587729536477324, \"annualised_returns_over_hodl\": -0.318412119177803, \"annualised_returns_over_uniform_hodl\": -0.318412119177803, \"calmar\": -0.6153162365217052, \"daily_log_sharpe\": -0.4512188509671917, \"daily_returns\": 0.00816544210561303, \"fee_revenue_over_value\": 0.01098590978649947, \"jax_sharpe\": 0.10870190411641693, \"return\": -0.31114314050658953, \"returns_over_hodl\": -0.20763084551160516, \"returns_over_uniform_hodl\": -0.20763084551160516, \"sharpe\": 0.12428323123114741, \"sterling\": -1.3776503733379752, \"ulcer\": -0.18021827056407624}], \"train_return\": -0.31114314050658953, \"train_returns_over_hodl\": -0.20763084551160516, \"train_sharpe\": 0.10870190411641692, \"validation_return\": -0.3380895205896616, \"validation_returns_over_hodl\": -0.024315896860047204, \"validation_sharpe\": -2.234265413851502}, {\"centeredness_margin\": 0.3984925299741193, \"continuous_test_metrics\": [{\"annualised_returns\": -0.5681775977928332, \"annualised_returns_over_hodl\": -0.16231268307169322, \"annualised_returns_over_uniform_hodl\": 0.5241416937211167, \"calmar\": -0.980889673411341, \"daily_log_sharpe\": -0.9038780730604793, \"daily_returns\": 0.031206314328709232, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": -0.22562502631919942, \"return\": -0.28694406738197487, \"returns_over_hodl\": -0.06884455336696993, \"returns_over_uniform_hodl\": 0.18498056779981997, \"sharpe\": -0.4342307655603662, \"sterling\": -1.4803115594747926, \"ulcer\": -0.1724710690188076}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -0.06479662509997755, \"optuna_trial_number\": 59, \"price_ratio\": 12.686287591409833, \"shift_exponent\": 0.00042346560540073013, \"step\": 59, \"test_objective\": [{\"annualised_returns\": -0.5681775977928332, \"annualised_returns_over_hodl\": -0.16231268307169322, \"annualised_returns_over_uniform_hodl\": 0.5241416937211167, \"calmar\": -0.980889673411341, \"daily_log_sharpe\": -0.9038780730604793, \"daily_returns\": 0.031206314328709232, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": -0.22562502631919942, \"return\": -0.28694406738197487, \"returns_over_hodl\": -0.06884455336696993, \"returns_over_uniform_hodl\": 0.18498056779981997, \"sharpe\": -0.4342307655603662, \"sterling\": -1.4803115594747926, \"ulcer\": -0.1724710690188076}], \"train_objective\": [{\"annualised_returns\": -0.40582731078681333, \"annualised_returns_over_hodl\": -0.2517356499223201, \"annualised_returns_over_uniform_hodl\": -0.25173564992232034, \"calmar\": -0.5478814647005711, \"daily_log_sharpe\": -0.4041213439650706, \"daily_returns\": 0.008101069997183916, \"fee_revenue_over_value\": 0.010766372448829708, \"jax_sharpe\": 0.11698092364902125, \"return\": -0.2709831663523774, \"returns_over_hodl\": -0.16143616177389708, \"returns_over_uniform_hodl\": -0.1614361617738972, \"sharpe\": 0.13873709659951697, \"sterling\": -1.2606093480606306, \"ulcer\": -0.17369342951246344}], \"train_return\": -0.2709831663523774, \"train_returns_over_hodl\": -0.16143616177389708, \"train_sharpe\": 0.11698092364902124, \"validation_return\": -0.2967619441054633, \"validation_returns_over_hodl\": -0.06479662509997752, \"validation_sharpe\": -2.0224660456008383}, {\"centeredness_margin\": 0.3993676341713254, \"continuous_test_metrics\": [{\"annualised_returns\": -0.5307790799777625, \"annualised_returns_over_hodl\": -0.08976372780442221, \"annualised_returns_over_uniform_hodl\": 0.6561418863789661, \"calmar\": -0.9184269563076649, \"daily_log_sharpe\": -0.8059800080462609, \"daily_returns\": 0.03101604251229259, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": -0.12404818816265344, \"return\": -0.2626881125770789, \"returns_over_hodl\": -0.03716952835997023, \"returns_over_uniform_hodl\": 0.22528993734913638, \"sharpe\": -0.3273127241164907, \"sterling\": -1.3706692795716426, \"ulcer\": -0.17521052913603938}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -0.06501543554217881, \"optuna_trial_number\": 60, \"price_ratio\": 11.476905829024231, \"shift_exponent\": 6.67541184402672e-05, \"step\": 60, \"test_objective\": [{\"annualised_returns\": -0.5307790799777625, \"annualised_returns_over_hodl\": -0.08976372780442221, \"annualised_returns_over_uniform_hodl\": 0.6561418863789661, \"calmar\": -0.9184269563076649, \"daily_log_sharpe\": -0.8059800080462609, \"daily_returns\": 0.03101604251229259, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": -0.12404818816265344, \"return\": -0.2626881125770789, \"returns_over_hodl\": -0.03716952835997023, \"returns_over_uniform_hodl\": 0.22528993734913638, \"sharpe\": -0.3273127241164907, \"sterling\": -1.3706692795716426, \"ulcer\": -0.17521052913603938}], \"train_objective\": [{\"annualised_returns\": -0.4193101762014362, \"annualised_returns_over_hodl\": -0.26871513704754846, \"annualised_returns_over_uniform_hodl\": -0.2687151370475487, \"calmar\": -0.5655409673798952, \"daily_log_sharpe\": -0.4172956289455016, \"daily_returns\": 0.008089901025803016, \"fee_revenue_over_value\": 0.010382940181264403, \"jax_sharpe\": 0.15262565072095874, \"return\": -0.28107185642853305, \"returns_over_hodl\": -0.17304084671732312, \"returns_over_uniform_hodl\": -0.17304084671732323, \"sharpe\": 0.13198550842887896, \"sterling\": -1.2930368198544036, \"ulcer\": -0.17511611769175903}], \"train_return\": -0.28107185642853305, \"train_returns_over_hodl\": -0.17304084671732312, \"train_sharpe\": 0.15262565072095874, \"validation_return\": -0.3141309591316426, \"validation_returns_over_hodl\": -0.06501543554217881, \"validation_sharpe\": -2.0813640262002813}, {\"centeredness_margin\": 0.7567576597653048, \"continuous_test_metrics\": [{\"annualised_returns\": -0.480932156952976, \"annualised_returns_over_hodl\": 0.006933746220850745, \"annualised_returns_over_uniform_hodl\": 0.8320794322252696, \"calmar\": -0.8296390647216565, \"daily_log_sharpe\": -0.6853204170726217, \"daily_returns\": 0.031016042503159598, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": 0.007673790842912978, \"return\": -0.23209060011610005, \"returns_over_hodl\": 0.0027867209088254796, \"returns_over_uniform_hodl\": 0.27613792280260774, \"sharpe\": -0.19464883128692623, \"sterling\": -1.2381821081680608, \"ulcer\": -0.17641031600051474}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -7.056278894523871e-10, \"optuna_trial_number\": 61, \"price_ratio\": 4.518493598654748, \"shift_exponent\": 0.00016793831380629396, \"step\": 61, \"test_objective\": [{\"annualised_returns\": -0.480932156952976, \"annualised_returns_over_hodl\": 0.006933746220850745, \"annualised_returns_over_uniform_hodl\": 0.8320794322252696, \"calmar\": -0.8296390647216565, \"daily_log_sharpe\": -0.6853204170726217, \"daily_returns\": 0.031016042503159598, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": 0.007673790842912978, \"return\": -0.23209060011610005, \"returns_over_hodl\": 0.0027867209088254796, \"returns_over_uniform_hodl\": 0.27613792280260774, \"sharpe\": -0.19464883128692623, \"sterling\": -1.2381821081680608, \"ulcer\": -0.17641031600051474}], \"train_objective\": [{\"annualised_returns\": -0.5167265996894677, \"annualised_returns_over_hodl\": -0.3913953580194858, \"annualised_returns_over_uniform_hodl\": -0.39139535801948566, \"calmar\": -0.6843566607515184, \"daily_log_sharpe\": -0.5160623605829167, \"daily_returns\": 0.008240326622409203, \"fee_revenue_over_value\": 0.0005623634286835101, \"jax_sharpe\": 0.15781450258079394, \"return\": -0.35691741158521684, \"returns_over_hodl\": -0.2602834684361527, \"returns_over_uniform_hodl\": -0.2602834684361526, \"sharpe\": 0.09379374229783317, \"sterling\": -1.494044070767831, \"ulcer\": -0.188253245589458}], \"train_return\": -0.35691741158521684, \"train_returns_over_hodl\": -0.2602834684361527, \"train_sharpe\": 0.1578145025807939, \"validation_return\": -0.3391427199656756, \"validation_returns_over_hodl\": -7.056278894523871e-10, \"validation_sharpe\": -2.2438531989540422}, {\"centeredness_margin\": 0.2862735339500885, \"continuous_test_metrics\": [{\"annualised_returns\": -0.47895175714930716, \"annualised_returns_over_hodl\": 0.01077550057549792, \"annualised_returns_over_uniform_hodl\": 0.839069365808105, \"calmar\": -0.8262227412151628, \"daily_log_sharpe\": -0.6844537808832474, \"daily_returns\": 0.03101604251468242, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": -0.0003777675491630366, \"return\": -0.23091199844325228, \"returns_over_hodl\": 0.004325816485413858, \"returns_over_uniform_hodl\": 0.27809656309380215, \"sharpe\": -0.19471519503198187, \"sterling\": -1.2330834682747818, \"ulcer\": -0.17641031602432275}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -6.872246105515956e-10, \"optuna_trial_number\": 62, \"price_ratio\": 5.859386046822714, \"shift_exponent\": 0.00010729065124873387, \"step\": 62, \"test_objective\": [{\"annualised_returns\": -0.47895175714930716, \"annualised_returns_over_hodl\": 0.01077550057549792, \"annualised_returns_over_uniform_hodl\": 0.839069365808105, \"calmar\": -0.8262227412151628, \"daily_log_sharpe\": -0.6844537808832474, \"daily_returns\": 0.03101604251468242, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": -0.0003777675491630366, \"return\": -0.23091199844325228, \"returns_over_hodl\": 0.004325816485413858, \"returns_over_uniform_hodl\": 0.27809656309380215, \"sharpe\": -0.19471519503198187, \"sterling\": -1.2330834682747818, \"ulcer\": -0.17641031602432275}], \"train_objective\": [{\"annualised_returns\": -0.47374922963560784, \"annualised_returns_over_hodl\": -0.3372723152489, \"annualised_returns_over_uniform_hodl\": -0.3372723152489, \"calmar\": -0.6337494250595173, \"daily_log_sharpe\": -0.463413085620743, \"daily_returns\": 0.00818168731331135, \"fee_revenue_over_value\": 0.0121713721454385, \"jax_sharpe\": 0.11107240416872224, \"return\": -0.32277941134325816, \"returns_over_hodl\": -0.22101566117712634, \"returns_over_uniform_hodl\": -0.22101566117712634, \"sharpe\": 0.12323269190053639, \"sterling\": -1.406718030221344, \"ulcer\": -0.1824259181674941}], \"train_return\": -0.32277941134325816, \"train_returns_over_hodl\": -0.22101566117712634, \"train_sharpe\": 0.11107240416872224, \"validation_return\": -0.3391427200055168, \"validation_returns_over_hodl\": -6.872246105515956e-10, \"validation_sharpe\": -2.243853198552458}, {\"centeredness_margin\": 0.7275697162167538, \"continuous_test_metrics\": [{\"annualised_returns\": -0.5084304408410949, \"annualised_returns_over_hodl\": -0.04640985896609928, \"annualised_returns_over_uniform_hodl\": 0.7350226774912834, \"calmar\": -0.8771158300252421, \"daily_log_sharpe\": -0.7527252389649594, \"daily_returns\": 0.031016042490163088, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": -0.0703393744207544, \"return\": -0.24874114064556485, \"returns_over_hodl\": -0.018956652706830113, \"returns_over_uniform_hodl\": 0.2484674890144234, \"sharpe\": -0.2696214183274058, \"sterling\": -1.3090687712075741, \"ulcer\": -0.17638373947928765}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -0.05507612995234479, \"optuna_trial_number\": 63, \"price_ratio\": 8.118882932491294, \"shift_exponent\": 0.00022594130742189554, \"step\": 63, \"test_objective\": [{\"annualised_returns\": -0.5084304408410949, \"annualised_returns_over_hodl\": -0.04640985896609928, \"annualised_returns_over_uniform_hodl\": 0.7350226774912834, \"calmar\": -0.8771158300252421, \"daily_log_sharpe\": -0.7527252389649594, \"daily_returns\": 0.031016042490163088, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": -0.0703393744207544, \"return\": -0.24874114064556485, \"returns_over_hodl\": -0.018956652706830113, \"returns_over_uniform_hodl\": 0.2484674890144234, \"sharpe\": -0.2696214183274058, \"sterling\": -1.3090687712075741, \"ulcer\": -0.17638373947928765}], \"train_objective\": [{\"annualised_returns\": -0.44905051689650355, \"annualised_returns_over_hodl\": -0.3061682834228088, \"annualised_returns_over_uniform_hodl\": -0.3061682834228088, \"calmar\": -0.6015315139343128, \"daily_log_sharpe\": -0.4494667615090285, \"daily_returns\": 0.008130256024926414, \"fee_revenue_over_value\": 0.0012324556688585013, \"jax_sharpe\": 0.09821694381737521, \"return\": -0.30365666158557514, \"returns_over_hodl\": -0.1990193975874286, \"returns_over_uniform_hodl\": -0.1990193975874286, \"sharpe\": 0.11433303664876959, \"sterling\": -1.3613623673969097, \"ulcer\": -0.1785565896829491}], \"train_return\": -0.30365666158557514, \"train_returns_over_hodl\": -0.1990193975874286, \"train_sharpe\": 0.0982169438173752, \"validation_return\": -0.3309676748315672, \"validation_returns_over_hodl\": -0.05507612995234479, \"validation_sharpe\": -2.1723716207609995}, {\"centeredness_margin\": 0.22339722051820524, \"continuous_test_metrics\": [{\"annualised_returns\": -0.48450645871078946, \"annualised_returns_over_hodl\": 8.694600595049451e-12, \"annualised_returns_over_uniform_hodl\": 0.8194637311704389, \"calmar\": -0.8358049769872531, \"daily_log_sharpe\": -0.6924636109331498, \"daily_returns\": 0.031016042485779154, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": 0.005019267220812715, \"return\": -0.23422460264426626, \"returns_over_hodl\": 3.5016434196677437e-12, \"returns_over_uniform_hodl\": 0.27259156491981407, \"sharpe\": -0.20210854614154145, \"sterling\": -1.2473843297890244, \"ulcer\": -0.17641031596457718}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -9.279760293523509e-10, \"optuna_trial_number\": 64, \"price_ratio\": 3.094272282334012, \"shift_exponent\": 2.033877339745128e-05, \"step\": 64, \"test_objective\": [{\"annualised_returns\": -0.48450645871078946, \"annualised_returns_over_hodl\": 8.694600595049451e-12, \"annualised_returns_over_uniform_hodl\": 0.8194637311704389, \"calmar\": -0.8358049769872531, \"daily_log_sharpe\": -0.6924636109331498, \"daily_returns\": 0.031016042485779154, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": 0.005019267220812715, \"return\": -0.23422460264426626, \"returns_over_hodl\": 3.5016434196677437e-12, \"returns_over_uniform_hodl\": 0.27259156491981407, \"sharpe\": -0.20210854614154145, \"sterling\": -1.2473843297890244, \"ulcer\": -0.17641031596457718}], \"train_objective\": [{\"annualised_returns\": -0.5549877906270491, \"annualised_returns_over_hodl\": -0.43957913638873236, \"annualised_returns_over_uniform_hodl\": -0.43957913638873236, \"calmar\": -0.7267219191038986, \"daily_log_sharpe\": -0.5538507410018687, \"daily_returns\": 0.008310757019986837, \"fee_revenue_over_value\": 0.012781559047141539, \"jax_sharpe\": 0.12755472277724936, \"return\": -0.3883273291018424, \"returns_over_hodl\": -0.2964132527915002, \"returns_over_uniform_hodl\": -0.2964132527915002, \"sharpe\": 0.09112169254552924, \"sterling\": -1.5400438802144254, \"ulcer\": -0.19790065191384323}], \"train_return\": -0.3883273291018424, \"train_returns_over_hodl\": -0.2964132527915002, \"train_sharpe\": 0.12755472277724936, \"validation_return\": -0.33914271992084544, \"validation_returns_over_hodl\": -9.279760293523509e-10, \"validation_sharpe\": -2.243853199716617}, {\"centeredness_margin\": 0.6725583832421064, \"continuous_test_metrics\": [{\"annualised_returns\": -0.484506458730663, \"annualised_returns_over_hodl\": 8.535172568713278e-12, \"annualised_returns_over_uniform_hodl\": 0.8194637311002944, \"calmar\": -0.8358049770190639, \"daily_log_sharpe\": -0.6924636109829985, \"daily_returns\": 0.031016042484841213, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": 0.005019267161198873, \"return\": -0.23422460265615608, \"returns_over_hodl\": 3.4374725288444097e-12, \"returns_over_uniform_hodl\": 0.2725915649000552, \"sharpe\": -0.20210854619998275, \"sterling\": -1.2473843298555385, \"ulcer\": -0.17641031596263843}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -9.28086718587906e-10, \"optuna_trial_number\": 65, \"price_ratio\": 3.1051083053605506, \"shift_exponent\": 4.29474105304936e-05, \"step\": 65, \"test_objective\": [{\"annualised_returns\": -0.484506458730663, \"annualised_returns_over_hodl\": 8.535172568713278e-12, \"annualised_returns_over_uniform_hodl\": 0.8194637311002944, \"calmar\": -0.8358049770190639, \"daily_log_sharpe\": -0.6924636109829985, \"daily_returns\": 0.031016042484841213, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": 0.005019267161198873, \"return\": -0.23422460265615608, \"returns_over_hodl\": 3.4374725288444097e-12, \"returns_over_uniform_hodl\": 0.2725915649000552, \"sharpe\": -0.20210854619998275, \"sterling\": -1.2473843298555385, \"ulcer\": -0.17641031596263843}], \"train_objective\": [{\"annualised_returns\": -0.5653803537786686, \"annualised_returns_over_hodl\": -0.4526668879018244, \"annualised_returns_over_uniform_hodl\": -0.4526668879018244, \"calmar\": -0.737064518702832, \"daily_log_sharpe\": -0.5718475783196596, \"daily_returns\": 0.008271315248137942, \"fee_revenue_over_value\": 0.0008111336433712767, \"jax_sharpe\": 0.11229766526919263, \"return\": -0.39704007294728094, \"returns_over_hodl\": -0.30643523250897176, \"returns_over_uniform_hodl\": -0.30643523250897176, \"sharpe\": 0.07416938293992548, \"sterling\": -1.5632643336673555, \"ulcer\": -0.19902867757668663}], \"train_return\": -0.39704007294728094, \"train_returns_over_hodl\": -0.30643523250897176, \"train_sharpe\": 0.11229766526919263, \"validation_return\": -0.3391427199105488, \"validation_returns_over_hodl\": -9.28086718587906e-10, \"validation_sharpe\": -2.243853199677446}, {\"centeredness_margin\": 0.6235308722245099, \"continuous_test_metrics\": [{\"annualised_returns\": -0.48342385727647363, \"annualised_returns_over_hodl\": 0.002100125939577646, \"annualised_returns_over_uniform_hodl\": 0.8232848344186487, \"calmar\": -0.8339374162708715, \"daily_log_sharpe\": -0.6902132495016776, \"daily_returns\": 0.03101604248962331, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": 0.00625163333932902, \"return\": -0.23357731540807358, \"returns_over_hodl\": 0.000845270287705091, \"returns_over_uniform_hodl\": 0.2736672488340579, \"sharpe\": -0.1996926184998844, \"sterling\": -1.2445971173394847, \"ulcer\": -0.17641031597251836}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -8.446598975808683e-10, \"optuna_trial_number\": 66, \"price_ratio\": 3.180349403840526, \"shift_exponent\": 0.0005111004095700159, \"step\": 66, \"test_objective\": [{\"annualised_returns\": -0.48342385727647363, \"annualised_returns_over_hodl\": 0.002100125939577646, \"annualised_returns_over_uniform_hodl\": 0.8232848344186487, \"calmar\": -0.8339374162708715, \"daily_log_sharpe\": -0.6902132495016776, \"daily_returns\": 0.03101604248962331, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": 0.00625163333932902, \"return\": -0.23357731540807358, \"returns_over_hodl\": 0.000845270287705091, \"returns_over_uniform_hodl\": 0.2736672488340579, \"sharpe\": -0.1996926184998844, \"sterling\": -1.2445971173394847, \"ulcer\": -0.17641031597251836}], \"train_objective\": [{\"annualised_returns\": -0.5527654871617165, \"annualised_returns_over_hodl\": -0.43678050479837005, \"annualised_returns_over_uniform_hodl\": -0.43678050479837016, \"calmar\": -0.722795609820631, \"daily_log_sharpe\": -0.556873013279334, \"daily_returns\": 0.008313542339429207, \"fee_revenue_over_value\": 0.0013084849208951513, \"jax_sharpe\": 0.11112638616893367, \"return\": -0.3864746475056422, \"returns_over_hodl\": -0.2942821747167358, \"returns_over_uniform_hodl\": -0.2942821747167359, \"sharpe\": 0.08023085755425023, \"sterling\": -1.5452550216265968, \"ulcer\": -0.1959702880526723}], \"train_return\": -0.3864746475056422, \"train_returns_over_hodl\": -0.2942821747167358, \"train_sharpe\": 0.11112638616893365, \"validation_return\": -0.33914271990816935, \"validation_returns_over_hodl\": -8.446598975808683e-10, \"validation_sharpe\": -2.2438531993190733}, {\"centeredness_margin\": 0.31510647044653634, \"continuous_test_metrics\": [{\"annualised_returns\": -0.4845064586450919, \"annualised_returns_over_hodl\": 8.40461034101736e-12, \"annualised_returns_over_uniform_hodl\": 0.819463731402323, \"calmar\": -0.8358049768819659, \"daily_log_sharpe\": -0.6924636107681268, \"daily_returns\": 0.03101604248890699, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": 0.005019267418442246, \"return\": -0.23422460260496103, \"returns_over_hodl\": 3.3848479574771773e-12, \"returns_over_uniform_hodl\": 0.27259156498513293, \"sharpe\": -0.20210854594794625, \"sterling\": -1.247384329568772, \"ulcer\": -0.1764103159710454}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -8.905787218793648e-10, \"optuna_trial_number\": 67, \"price_ratio\": 3.301970689035276, \"shift_exponent\": 4.80548446703462e-05, \"step\": 67, \"test_objective\": [{\"annualised_returns\": -0.4845064586450919, \"annualised_returns_over_hodl\": 8.40461034101736e-12, \"annualised_returns_over_uniform_hodl\": 0.819463731402323, \"calmar\": -0.8358049768819659, \"daily_log_sharpe\": -0.6924636107681268, \"daily_returns\": 0.03101604248890699, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": 0.005019267418442246, \"return\": -0.23422460260496103, \"returns_over_hodl\": 3.3848479574771773e-12, \"returns_over_uniform_hodl\": 0.27259156498513293, \"sharpe\": -0.20210854594794625, \"sterling\": -1.247384329568772, \"ulcer\": -0.1764103159710454}], \"train_objective\": [{\"annualised_returns\": -0.5473649107443017, \"annualised_returns_over_hodl\": -0.42997935274883214, \"annualised_returns_over_uniform_hodl\": -0.42997935274883226, \"calmar\": -0.7188909113783915, \"daily_log_sharpe\": -0.5454666356425725, \"daily_returns\": 0.008253086492513636, \"fee_revenue_over_value\": 0.012367299723288311, \"jax_sharpe\": 0.12310032383778997, \"return\": -0.3819873260360632, \"returns_over_hodl\": -0.28912055794575264, \"returns_over_uniform_hodl\": -0.28912055794575275, \"sharpe\": 0.09342479707077535, \"sterling\": -1.5289021600796946, \"ulcer\": -0.19617647984859427}], \"train_return\": -0.3819873260360632, \"train_returns_over_hodl\": -0.28912055794575264, \"train_sharpe\": 0.12310032383778997, \"validation_return\": -0.3391427199306325, \"validation_returns_over_hodl\": -8.905787218793648e-10, \"validation_sharpe\": -2.243853199591918}, {\"centeredness_margin\": 0.24311713883344302, \"continuous_test_metrics\": [{\"annualised_returns\": -0.5238152141861303, \"annualised_returns_over_hodl\": -0.07625460436858922, \"annualised_returns_over_uniform_hodl\": 0.6807212461997034, \"calmar\": -0.9043426214565154, \"daily_log_sharpe\": -0.7885243174319068, \"daily_returns\": 0.031016042502907105, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": -0.10512908304266186, \"return\": -0.2583004552308217, \"returns_over_hodl\": -0.03143983605035661, \"returns_over_uniform_hodl\": 0.23258149535411587, \"sharpe\": -0.3080142840443022, \"sterling\": -1.3503545825038563, \"ulcer\": -0.17591199513594624}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -0.0622678789437735, \"optuna_trial_number\": 68, \"price_ratio\": 9.281046043551505, \"shift_exponent\": 0.00032874540854977005, \"step\": 68, \"test_objective\": [{\"annualised_returns\": -0.5238152141861303, \"annualised_returns_over_hodl\": -0.07625460436858922, \"annualised_returns_over_uniform_hodl\": 0.6807212461997034, \"calmar\": -0.9043426214565154, \"daily_log_sharpe\": -0.7885243174319068, \"daily_returns\": 0.031016042502907105, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": -0.10512908304266186, \"return\": -0.2583004552308217, \"returns_over_hodl\": -0.03143983605035661, \"returns_over_uniform_hodl\": 0.23258149535411587, \"sharpe\": -0.3080142840443022, \"sterling\": -1.3503545825038563, \"ulcer\": -0.17591199513594624}], \"train_objective\": [{\"annualised_returns\": -0.4183473533809424, \"annualised_returns_over_hodl\": -0.2675026175139249, \"annualised_returns_over_uniform_hodl\": -0.267502617513925, \"calmar\": -0.5645677705085715, \"daily_log_sharpe\": -0.4081166608704649, \"daily_returns\": 0.00814054929880652, \"fee_revenue_over_value\": 0.01734464189389539, \"jax_sharpe\": 0.172070700972151, \"return\": -0.28034838429552766, \"returns_over_hodl\": -0.17220866076399322, \"returns_over_uniform_hodl\": -0.17220866076399333, \"sharpe\": 0.14861073480159592, \"sterling\": -1.2810263326858873, \"ulcer\": -0.1762513257715778}], \"train_return\": -0.28034838429552766, \"train_returns_over_hodl\": -0.17220866076399322, \"train_sharpe\": 0.172070700972151, \"validation_return\": -0.3240621455910413, \"validation_returns_over_hodl\": -0.0622678789437735, \"validation_sharpe\": -2.126471726229322}, {\"centeredness_margin\": 0.38182944228174254, \"continuous_test_metrics\": [{\"annualised_returns\": -0.47999768973012347, \"annualised_returns_over_hodl\": 0.00874650858055559, \"annualised_returns_over_uniform_hodl\": 0.8353776873609124, \"calmar\": -0.8280270455768359, \"daily_log_sharpe\": -0.6834545322643575, \"daily_returns\": 0.031016042500046775, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": 0.008334637340151965, \"return\": -0.2315341336136263, \"returns_over_hodl\": 0.0035133916057328296, \"returns_over_uniform_hodl\": 0.27706267773682725, \"sharpe\": -0.1926959938014995, \"sterling\": -1.2357762749613144, \"ulcer\": -0.17641031599407422}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -7.363507581459316e-10, \"optuna_trial_number\": 69, \"price_ratio\": 3.9688488482123163, \"shift_exponent\": 0.0004184603575601345, \"step\": 69, \"test_objective\": [{\"annualised_returns\": -0.47999768973012347, \"annualised_returns_over_hodl\": 0.00874650858055559, \"annualised_returns_over_uniform_hodl\": 0.8353776873609124, \"calmar\": -0.8280270455768359, \"daily_log_sharpe\": -0.6834545322643575, \"daily_returns\": 0.031016042500046775, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": 0.008334637340151965, \"return\": -0.2315341336136263, \"returns_over_hodl\": 0.0035133916057328296, \"returns_over_uniform_hodl\": 0.27706267773682725, \"sharpe\": -0.1926959938014995, \"sterling\": -1.2357762749613144, \"ulcer\": -0.17641031599407422}], \"train_objective\": [{\"annualised_returns\": -0.5179176115511421, \"annualised_returns_over_hodl\": -0.39289524472378845, \"annualised_returns_over_uniform_hodl\": -0.39289524472378856, \"calmar\": -0.6860515556203096, \"daily_log_sharpe\": -0.5119623180982927, \"daily_returns\": 0.00820572224835759, \"fee_revenue_over_value\": 0.01171593416349758, \"jax_sharpe\": 0.16700981082357552, \"return\": -0.3578800782126622, \"returns_over_hodl\": -0.2613907918679088, \"returns_over_uniform_hodl\": -0.2613907918679089, \"sharpe\": 0.10541167027592294, \"sterling\": -1.4892479488645032, \"ulcer\": -0.18917032335905618}], \"train_return\": -0.3578800782126622, \"train_returns_over_hodl\": -0.2613907918679088, \"train_sharpe\": 0.1670098108235755, \"validation_return\": -0.3391427199516337, \"validation_returns_over_hodl\": -7.363507581459316e-10, \"validation_sharpe\": -2.243853199028062}, {\"centeredness_margin\": 0.06917956580992218, \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -Infinity, \"optuna_trial_number\": 70, \"price_ratio\": 1.0174288578178756, \"shift_exponent\": 0.18385289315428613, \"step\": 70, \"test_objective\": -Infinity, \"train_objective\": -Infinity, \"train_return\": -Infinity, \"train_returns_over_hodl\": -Infinity, \"train_sharpe\": -Infinity, \"validation_return\": -Infinity, \"validation_returns_over_hodl\": -Infinity, \"validation_sharpe\": -Infinity}, {\"centeredness_margin\": 0.18262755353911317, \"continuous_test_metrics\": [{\"annualised_returns\": -0.5593079908996288, \"annualised_returns_over_hodl\": -0.1451066348631347, \"annualised_returns_over_uniform_hodl\": 0.5554474749954368, \"calmar\": -0.9657970112543681, \"daily_log_sharpe\": -0.8795347086294217, \"daily_returns\": 0.031157342790772518, \"fee_revenue_over_value\": 0.00011651322481767825, \"jax_sharpe\": -0.1990508325693456, \"return\": -0.28108130844186574, \"returns_over_hodl\": -0.06118857636119779, \"returns_over_uniform_hodl\": 0.19472349973535086, \"sharpe\": -0.40768793025017974, \"sterling\": -1.4532612431903615, \"ulcer\": -0.17327443241594856}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -0.06444089655485752, \"optuna_trial_number\": 71, \"price_ratio\": 13.489522186933778, \"shift_exponent\": 0.00017190168402193726, \"step\": 71, \"test_objective\": [{\"annualised_returns\": -0.5593079908996288, \"annualised_returns_over_hodl\": -0.1451066348631347, \"annualised_returns_over_uniform_hodl\": 0.5554474749954368, \"calmar\": -0.9657970112543681, \"daily_log_sharpe\": -0.8795347086294217, \"daily_returns\": 0.031157342790772518, \"fee_revenue_over_value\": 0.00011651322481767825, \"jax_sharpe\": -0.1990508325693456, \"return\": -0.28108130844186574, \"returns_over_hodl\": -0.06118857636119779, \"returns_over_uniform_hodl\": 0.19472349973535086, \"sharpe\": -0.40768793025017974, \"sterling\": -1.4532612431903615, \"ulcer\": -0.17327443241594856}], \"train_objective\": [{\"annualised_returns\": -0.38527218981861133, \"annualised_returns_over_hodl\": -0.22584980139500588, \"annualised_returns_over_uniform_hodl\": -0.22584980139500588, \"calmar\": -0.5237106426945495, \"daily_log_sharpe\": -0.37186656232996873, \"daily_returns\": 0.008097956404448848, \"fee_revenue_over_value\": 0.023107546927210683, \"jax_sharpe\": 0.1792079516089747, \"return\": -0.2557739870397939, \"returns_over_hodl\": -0.14394154821769822, \"returns_over_uniform_hodl\": -0.14394154821769822, \"sharpe\": 0.17077665336208453, \"sterling\": -1.2005812506264395, \"ulcer\": -0.17282668838701956}], \"train_return\": -0.2557739870397939, \"train_returns_over_hodl\": -0.14394154821769822, \"train_sharpe\": 0.1792079516089747, \"validation_return\": -0.2985051906617804, \"validation_returns_over_hodl\": -0.06444089655485752, \"validation_sharpe\": -2.0271948884999977}, {\"centeredness_margin\": 0.24941911338070782, \"continuous_test_metrics\": [{\"annualised_returns\": -0.6507633131372845, \"annualised_returns_over_hodl\": 0.03234130584165751, \"annualised_returns_over_uniform_hodl\": 0.23265072099970907, \"calmar\": -1.1348381596125763, \"daily_log_sharpe\": -1.3144218120916198, \"daily_returns\": 0.021675299828124512, \"fee_revenue_over_value\": 0.04899520923018083, \"jax_sharpe\": -0.7424599185737162, \"return\": -0.34536821738147916, \"returns_over_hodl\": 0.012901391146249885, \"returns_over_uniform_hodl\": 0.08788933095189533, \"sharpe\": -0.9159902807545999, \"sterling\": -1.9137842055331395, \"ulcer\": -0.1516911205185082}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -0.024697943389264565, \"optuna_trial_number\": 72, \"price_ratio\": 32.428634088489254, \"shift_exponent\": 0.45086621087371787, \"step\": 72, \"test_objective\": [{\"annualised_returns\": -0.6507633131372845, \"annualised_returns_over_hodl\": 0.03234130584165751, \"annualised_returns_over_uniform_hodl\": 0.23265072099970907, \"calmar\": -1.1348381596125763, \"daily_log_sharpe\": -1.3144218120916198, \"daily_returns\": 0.021675299828124512, \"fee_revenue_over_value\": 0.04899520923018083, \"jax_sharpe\": -0.7424599185737162, \"return\": -0.34536821738147916, \"returns_over_hodl\": 0.012901391146249885, \"returns_over_uniform_hodl\": 0.08788933095189533, \"sharpe\": -0.9159902807545999, \"sterling\": -1.9137842055331395, \"ulcer\": -0.1516911205185082}], \"train_objective\": [{\"annualised_returns\": -0.3466944567326725, \"annualised_returns_over_hodl\": -0.1772673894143315, \"annualised_returns_over_uniform_hodl\": -0.1772673894143315, \"calmar\": -0.4726984050288443, \"daily_log_sharpe\": -0.3423003741903159, \"daily_returns\": 0.008019953339854136, \"fee_revenue_over_value\": 0.02373157178564436, \"jax_sharpe\": 0.14792730187551373, \"return\": -0.22775842632570042, \"returns_over_hodl\": -0.1117161796964794, \"returns_over_uniform_hodl\": -0.1117161796964794, \"sharpe\": 0.17545613073336014, \"sterling\": -1.1095571788227796, \"ulcer\": -0.16827209552932432}], \"train_return\": -0.22775842632570042, \"train_returns_over_hodl\": -0.1117161796964794, \"train_sharpe\": 0.1479273018755137, \"validation_return\": -0.20820461636164111, \"validation_returns_over_hodl\": -0.024697943389264565, \"validation_sharpe\": -1.613658721269167}, {\"centeredness_margin\": 0.4464790629039067, \"continuous_test_metrics\": [{\"annualised_returns\": -0.4919477510786132, \"annualised_returns_over_hodl\": -0.01443527893188401, \"annualised_returns_over_uniform_hodl\": 0.7931992671338379, \"calmar\": -0.8486417104131079, \"daily_log_sharpe\": -0.71543472073817, \"daily_returns\": 0.031016042519153873, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": -0.033991919252913096, \"return\": -0.2386958796187224, \"returns_over_hodl\": -0.005838888798546216, \"returns_over_uniform_hodl\": 0.2651610449765496, \"sharpe\": -0.22934838396580204, \"sterling\": -1.2665423151921784, \"ulcer\": -0.17641031603357538}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -0.027541215458305124, \"optuna_trial_number\": 73, \"price_ratio\": 6.171819469598469, \"shift_exponent\": 0.00037451520720622393, \"step\": 73, \"test_objective\": [{\"annualised_returns\": -0.4919477510786132, \"annualised_returns_over_hodl\": -0.01443527893188401, \"annualised_returns_over_uniform_hodl\": 0.7931992671338379, \"calmar\": -0.8486417104131079, \"daily_log_sharpe\": -0.71543472073817, \"daily_returns\": 0.031016042519153873, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": -0.033991919252913096, \"return\": -0.2386958796187224, \"returns_over_hodl\": -0.005838888798546216, \"returns_over_uniform_hodl\": 0.2651610449765496, \"sharpe\": -0.22934838396580204, \"sterling\": -1.2665423151921784, \"ulcer\": -0.17641031603357538}], \"train_objective\": [{\"annualised_returns\": -0.4602056337734163, \"annualised_returns_over_hodl\": -0.3202163479526625, \"annualised_returns_over_uniform_hodl\": -0.3202163479526625, \"calmar\": -0.6162554122366559, \"daily_log_sharpe\": -0.4515386374872835, \"daily_returns\": 0.008133831399784604, \"fee_revenue_over_value\": 0.010626632235459432, \"jax_sharpe\": 0.16377229523178424, \"return\": -0.3122507847959408, \"returns_over_hodl\": -0.20890493193018655, \"returns_over_uniform_hodl\": -0.20890493193018655, \"sharpe\": 0.12707534110159346, \"sterling\": -1.37837575108771, \"ulcer\": -0.18062995589795272}], \"train_return\": -0.3122507847959408, \"train_returns_over_hodl\": -0.20890493193018655, \"train_sharpe\": 0.16377229523178424, \"validation_return\": -0.3378865299631928, \"validation_returns_over_hodl\": -0.027541215458305124, \"validation_sharpe\": -2.232474856014465}, {\"centeredness_margin\": 0.815551371319919, \"continuous_test_metrics\": [{\"annualised_returns\": -0.6827761839068095, \"annualised_returns_over_hodl\": -0.09718684544714185, \"annualised_returns_over_uniform_hodl\": 0.11965947546416333, \"calmar\": -1.1501311549635076, \"daily_log_sharpe\": -1.3863533442477471, \"daily_returns\": 0.022358295062386145, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": -0.7951625321511512, \"return\": -0.37023119908379176, \"returns_over_hodl\": -0.04033957347955952, \"returns_over_uniform_hodl\": 0.046571183486757484, \"sharpe\": -0.9788944097499925, \"sterling\": -1.965144620176195, \"ulcer\": -0.15586117431614366}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -0.038591086535917585, \"optuna_trial_number\": 74, \"price_ratio\": 153.79815092808775, \"shift_exponent\": 1.1284565300262488e-05, \"step\": 74, \"test_objective\": [{\"annualised_returns\": -0.6827761839068095, \"annualised_returns_over_hodl\": -0.09718684544714185, \"annualised_returns_over_uniform_hodl\": 0.11965947546416333, \"calmar\": -1.1501311549635076, \"daily_log_sharpe\": -1.3863533442477471, \"daily_returns\": 0.022358295062386145, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": -0.7951625321511512, \"return\": -0.37023119908379176, \"returns_over_hodl\": -0.04033957347955952, \"returns_over_uniform_hodl\": 0.046571183486757484, \"sharpe\": -0.9788944097499925, \"sterling\": -1.965144620176195, \"ulcer\": -0.15586117431614366}], \"train_objective\": [{\"annualised_returns\": -0.3554737012965429, \"annualised_returns_over_hodl\": -0.18832342724140916, \"annualised_returns_over_uniform_hodl\": -0.18832342724140916, \"calmar\": -0.48315548327968144, \"daily_log_sharpe\": -0.3650805091507116, \"daily_returns\": 0.007971100295700157, \"fee_revenue_over_value\": 0.0012203285253290894, \"jax_sharpe\": 0.11275116331847644, \"return\": -0.23407558288408625, \"returns_over_hodl\": -0.11898259496397068, \"returns_over_uniform_hodl\": -0.11898259496397068, \"sharpe\": 0.14183078729733045, \"sterling\": -1.149134954910913, \"ulcer\": -0.167204132539247}], \"train_return\": -0.23407558288408625, \"train_returns_over_hodl\": -0.11898259496397068, \"train_sharpe\": 0.11275116331847644, \"validation_return\": -0.20857790460988834, \"validation_returns_over_hodl\": -0.038591086535917585, \"validation_sharpe\": -1.5999022410727015}, {\"centeredness_margin\": 0.8287096513496287, \"continuous_test_metrics\": [{\"annualised_returns\": -0.4790907623928099, \"annualised_returns_over_hodl\": 0.010505846227590876, \"annualised_returns_over_uniform_hodl\": 0.8385787389064316, \"calmar\": -0.8264625346347042, \"daily_log_sharpe\": -0.6822377341580519, \"daily_returns\": 0.031016042506758292, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": 0.007528866196203434, \"return\": -0.23099463768146933, \"returns_over_hodl\": 0.004217900850863376, \"returns_over_uniform_hodl\": 0.277959230400888, \"sharpe\": -0.19150894320281625, \"sterling\": -1.2334413444471877, \"ulcer\": -0.17641031600794152}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -6.614512271241324e-10, \"optuna_trial_number\": 75, \"price_ratio\": 4.9591001453902575, \"shift_exponent\": 0.0001291813127910192, \"step\": 75, \"test_objective\": [{\"annualised_returns\": -0.4790907623928099, \"annualised_returns_over_hodl\": 0.010505846227590876, \"annualised_returns_over_uniform_hodl\": 0.8385787389064316, \"calmar\": -0.8264625346347042, \"daily_log_sharpe\": -0.6822377341580519, \"daily_returns\": 0.031016042506758292, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": 0.007528866196203434, \"return\": -0.23099463768146933, \"returns_over_hodl\": 0.004217900850863376, \"returns_over_uniform_hodl\": 0.277959230400888, \"sharpe\": -0.19150894320281625, \"sterling\": -1.2334413444471877, \"ulcer\": -0.17641031600794152}], \"train_objective\": [{\"annualised_returns\": -0.5063103884121465, \"annualised_returns_over_hodl\": -0.3782778255189303, \"annualised_returns_over_uniform_hodl\": -0.3782778255189302, \"calmar\": -0.6721173046447059, \"daily_log_sharpe\": -0.5047972311662222, \"daily_returns\": 0.008156052503847436, \"fee_revenue_over_value\": 0.0003097915173779381, \"jax_sharpe\": 0.15235586551784616, \"return\": -0.3485375907919036, \"returns_over_hodl\": -0.25064443904237577, \"returns_over_uniform_hodl\": -0.25064443904237566, \"sharpe\": 0.096936399058304, \"sterling\": -1.476112755488708, \"ulcer\": -0.1864649096226076}], \"train_return\": -0.3485375907919036, \"train_returns_over_hodl\": -0.25064443904237577, \"train_sharpe\": 0.15235586551784616, \"validation_return\": -0.33914271997617895, \"validation_returns_over_hodl\": -6.614512271241324e-10, \"validation_sharpe\": -2.2438531988092856}, {\"centeredness_margin\": 0.9027211499860914, \"continuous_test_metrics\": [{\"annualised_returns\": -0.4803280975630212, \"annualised_returns_over_hodl\": 0.008105553973391855, \"annualised_returns_over_uniform_hodl\": 0.8342114941493575, \"calmar\": -0.8285970214406905, \"daily_log_sharpe\": -0.6841198659750509, \"daily_returns\": 0.031016042506057256, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": 0.0073337648703424305, \"return\": -0.23173082008497292, \"returns_over_hodl\": 0.003256545345171391, \"returns_over_uniform_hodl\": 0.276735817478279, \"sharpe\": -0.19339762066094643, \"sterling\": -1.2366269265842667, \"ulcer\": -0.17641031600648752}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -6.752336467741316e-10, \"optuna_trial_number\": 76, \"price_ratio\": 4.983221005637841, \"shift_exponent\": 4.0708954268474025e-05, \"step\": 76, \"test_objective\": [{\"annualised_returns\": -0.4803280975630212, \"annualised_returns_over_hodl\": 0.008105553973391855, \"annualised_returns_over_uniform_hodl\": 0.8342114941493575, \"calmar\": -0.8285970214406905, \"daily_log_sharpe\": -0.6841198659750509, \"daily_returns\": 0.031016042506057256, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": 0.0073337648703424305, \"return\": -0.23173082008497292, \"returns_over_hodl\": 0.003256545345171391, \"returns_over_uniform_hodl\": 0.276735817478279, \"sharpe\": -0.19339762066094643, \"sterling\": -1.2366269265842667, \"ulcer\": -0.17641031600648752}], \"train_objective\": [{\"annualised_returns\": -0.5084828304277929, \"annualised_returns_over_hodl\": -0.38101366468224085, \"annualised_returns_over_uniform_hodl\": -0.38101366468224085, \"calmar\": -0.6749583385774924, \"daily_log_sharpe\": -0.5074694743692064, \"daily_returns\": 0.008205967921920198, \"fee_revenue_over_value\": 0.00026268616751156526, \"jax_sharpe\": 0.09440731402207492, \"return\": -0.3502795384998072, \"returns_over_hodl\": -0.2526481436051634, \"returns_over_uniform_hodl\": -0.2526481436051634, \"sharpe\": 0.09554048596776295, \"sterling\": -1.4804211503928695, \"ulcer\": -0.18681864779834295}], \"train_return\": -0.3502795384998072, \"train_returns_over_hodl\": -0.2526481436051634, \"train_sharpe\": 0.0944073140220749, \"validation_return\": -0.33914271997753476, \"validation_returns_over_hodl\": -6.752336467741316e-10, \"validation_sharpe\": -2.2438531988701738}, {\"centeredness_margin\": 0.8048150095561876, \"continuous_test_metrics\": [{\"annualised_returns\": -0.4839969712192608, \"annualised_returns_over_hodl\": 0.0009883476984609008, \"annualised_returns_over_uniform_hodl\": 0.8212619962078691, \"calmar\": -0.8349260766671005, \"daily_log_sharpe\": -0.6969396306701461, \"daily_returns\": 0.031016042516184197, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": -0.014493749622293259, \"return\": -0.23391987911369727, \"returns_over_hodl\": 0.0003979275791432535, \"returns_over_uniform_hodl\": 0.2730979648328613, \"sharpe\": -0.20892300594655205, \"sterling\": -1.246072627861222, \"ulcer\": -0.17641031602743137}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -0.01323769688745513, \"optuna_trial_number\": 77, \"price_ratio\": 6.1614618426851075, \"shift_exponent\": 0.00019931409151324313, \"step\": 77, \"test_objective\": [{\"annualised_returns\": -0.4839969712192608, \"annualised_returns_over_hodl\": 0.0009883476984609008, \"annualised_returns_over_uniform_hodl\": 0.8212619962078691, \"calmar\": -0.8349260766671005, \"daily_log_sharpe\": -0.6969396306701461, \"daily_returns\": 0.031016042516184197, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": -0.014493749622293259, \"return\": -0.23391987911369727, \"returns_over_hodl\": 0.0003979275791432535, \"returns_over_uniform_hodl\": 0.2730979648328613, \"sharpe\": -0.20892300594655205, \"sterling\": -1.246072627861222, \"ulcer\": -0.17641031602743137}], \"train_objective\": [{\"annualised_returns\": -0.4770080475292481, \"annualised_returns_over_hodl\": -0.34137626902778395, \"annualised_returns_over_uniform_hodl\": -0.34137626902778406, \"calmar\": -0.6361881291426773, \"daily_log_sharpe\": -0.47435718975227864, \"daily_returns\": 0.008177824550939166, \"fee_revenue_over_value\": 0.0003733062978318945, \"jax_sharpe\": 0.09559872257284757, \"return\": -0.3253286032571864, \"returns_over_hodl\": -0.2239479119250587, \"returns_over_uniform_hodl\": -0.2239479119250588, \"sharpe\": 0.10782580964515998, \"sterling\": -1.4195168494888628, \"ulcer\": -0.18223600143552451}], \"train_return\": -0.3253286032571864, \"train_returns_over_hodl\": -0.2239479119250587, \"train_sharpe\": 0.09559872257284756, \"validation_return\": -0.3388046644960512, \"validation_returns_over_hodl\": -0.01323769688745513, \"validation_sharpe\": -2.240805583753659}, {\"centeredness_margin\": 0.8721557806313043, \"continuous_test_metrics\": [{\"annualised_returns\": -0.4844763011532466, \"annualised_returns_over_hodl\": 5.850212894342732e-05, \"annualised_returns_over_uniform_hodl\": 0.81957017398257, \"calmar\": -0.8357529531710354, \"daily_log_sharpe\": -0.6924005089676742, \"daily_returns\": 0.031016042490168278, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": 0.005060370955940354, \"return\": -0.23420656045735722, \"returns_over_hodl\": 2.356060846597785e-05, \"returns_over_uniform_hodl\": 0.2726215480388232, \"sharpe\": -0.202040489361731, \"sterling\": -1.2473066875531038, \"ulcer\": -0.17641031597364812}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -8.632675685404934e-10, \"optuna_trial_number\": 78, \"price_ratio\": 3.4627736124971373, \"shift_exponent\": 9.464759950724333e-05, \"step\": 78, \"test_objective\": [{\"annualised_returns\": -0.4844763011532466, \"annualised_returns_over_hodl\": 5.850212894342732e-05, \"annualised_returns_over_uniform_hodl\": 0.81957017398257, \"calmar\": -0.8357529531710354, \"daily_log_sharpe\": -0.6924005089676742, \"daily_returns\": 0.031016042490168278, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": 0.005060370955940354, \"return\": -0.23420656045735722, \"returns_over_hodl\": 2.356060846597785e-05, \"returns_over_uniform_hodl\": 0.2726215480388232, \"sharpe\": -0.202040489361731, \"sterling\": -1.2473066875531038, \"ulcer\": -0.17641031597364812}], \"train_objective\": [{\"annualised_returns\": -0.5512537166368718, \"annualised_returns_over_hodl\": -0.43487667446457023, \"annualised_returns_over_uniform_hodl\": -0.43487667446457023, \"calmar\": -0.7226078111499733, \"daily_log_sharpe\": -0.5554925186535584, \"daily_returns\": 0.008282964479560086, \"fee_revenue_over_value\": 0.0002880836665108018, \"jax_sharpe\": 0.10731225733124225, \"return\": -0.3852163869429458, \"returns_over_hodl\": -0.29283483940396415, \"returns_over_uniform_hodl\": -0.29283483940396415, \"sharpe\": 0.0799187212150386, \"sterling\": -1.5427117605081546, \"ulcer\": -0.19586299200127677}], \"train_return\": -0.3852163869429458, \"train_returns_over_hodl\": -0.29283483940396415, \"train_sharpe\": 0.10731225733124225, \"validation_return\": -0.33914271992649514, \"validation_returns_over_hodl\": -8.632675685404934e-10, \"validation_sharpe\": -2.2438531994660083}, {\"centeredness_margin\": 0.9106388819134384, \"continuous_test_metrics\": [{\"annualised_returns\": -0.4836211404045583, \"annualised_returns_over_hodl\": 0.0017174186781798717, \"annualised_returns_over_uniform_hodl\": 0.8225885124134797, \"calmar\": -0.8342777430133785, \"daily_log_sharpe\": -0.6906222032020177, \"daily_returns\": 0.031016042489309437, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": 0.0058544102122616275, \"return\": -0.23369521054942022, \"returns_over_hodl\": 0.0006913150446901106, \"returns_over_uniform_hodl\": 0.27347132668385443, \"sharpe\": -0.20013099895957412, \"sterling\": -1.2451050327773903, \"ulcer\": -0.1764103159718749}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -8.491367609053668e-10, \"optuna_trial_number\": 79, \"price_ratio\": 3.174908978223464, \"shift_exponent\": 0.00048086739127278414, \"step\": 79, \"test_objective\": [{\"annualised_returns\": -0.4836211404045583, \"annualised_returns_over_hodl\": 0.0017174186781798717, \"annualised_returns_over_uniform_hodl\": 0.8225885124134797, \"calmar\": -0.8342777430133785, \"daily_log_sharpe\": -0.6906222032020177, \"daily_returns\": 0.031016042489309437, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": 0.0058544102122616275, \"return\": -0.23369521054942022, \"returns_over_hodl\": 0.0006913150446901106, \"returns_over_uniform_hodl\": 0.27347132668385443, \"sharpe\": -0.20013099895957412, \"sterling\": -1.2451050327773903, \"ulcer\": -0.1764103159718749}], \"train_objective\": [{\"annualised_returns\": -0.5540864128214962, \"annualised_returns_over_hodl\": -0.4384439969080881, \"annualised_returns_over_uniform_hodl\": -0.438443996908088, \"calmar\": -0.7243789214125167, \"daily_log_sharpe\": -0.5589390278100967, \"daily_returns\": 0.008285647172605099, \"fee_revenue_over_value\": 0.00024925229061300213, \"jax_sharpe\": 0.10963425071563412, \"return\": -0.38757543467138333, \"returns_over_hodl\": -0.2955483736138935, \"returns_over_uniform_hodl\": -0.2955483736138934, \"sharpe\": 0.07854545921962675, \"sterling\": -1.547835368509295, \"ulcer\": -0.1961628894120655}], \"train_return\": -0.38757543467138333, \"train_returns_over_hodl\": -0.2955483736138935, \"train_sharpe\": 0.10963425071563412, \"validation_return\": -0.3391427199076753, \"validation_returns_over_hodl\": -8.491367609053668e-10, \"validation_sharpe\": -2.243853199333911}, {\"centeredness_margin\": 0.9571037781055879, \"continuous_test_metrics\": [{\"annualised_returns\": -0.4797681309950864, \"annualised_returns_over_hodl\": 0.009191826344807419, \"annualised_returns_over_uniform_hodl\": 0.8361879279538951, \"calmar\": -0.8276310412775718, \"daily_log_sharpe\": -0.686327449567997, \"daily_returns\": 0.03101604251433228, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": -0.0022597554850735963, \"return\": -0.23139752493024135, \"returns_over_hodl\": 0.003691783994900222, \"returns_over_uniform_hodl\": 0.27728969868679587, \"sharpe\": -0.1968094076907198, \"sterling\": -1.2351852641277188, \"ulcer\": -0.17641031602360463}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -0.0008824862342392548, \"optuna_trial_number\": 80, \"price_ratio\": 6.2266706992450755, \"shift_exponent\": 3.842616708446885e-05, \"step\": 80, \"test_objective\": [{\"annualised_returns\": -0.4797681309950864, \"annualised_returns_over_hodl\": 0.009191826344807419, \"annualised_returns_over_uniform_hodl\": 0.8361879279538951, \"calmar\": -0.8276310412775718, \"daily_log_sharpe\": -0.686327449567997, \"daily_returns\": 0.03101604251433228, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": -0.0022597554850735963, \"return\": -0.23139752493024135, \"returns_over_hodl\": 0.003691783994900222, \"returns_over_uniform_hodl\": 0.27728969868679587, \"sharpe\": -0.1968094076907198, \"sterling\": -1.2351852641277188, \"ulcer\": -0.17641031602360463}], \"train_objective\": [{\"annualised_returns\": -0.48257818674336594, \"annualised_returns_over_hodl\": -0.34839095798027275, \"annualised_returns_over_uniform_hodl\": -0.34839095798027275, \"calmar\": -0.6434485921844145, \"daily_log_sharpe\": -0.48053988545330467, \"daily_returns\": 0.008151474972078689, \"fee_revenue_over_value\": 0.00017390907512597917, \"jax_sharpe\": 0.14195658885965023, \"return\": -0.3297003103738875, \"returns_over_hodl\": -0.2289765413478374, \"returns_over_uniform_hodl\": -0.2289765413478374, \"sharpe\": 0.10381565311532055, \"sterling\": -1.432209601445763, \"ulcer\": -0.18284021678524737}], \"train_return\": -0.3297003103738875, \"train_returns_over_hodl\": -0.2289765413478374, \"train_sharpe\": 0.1419565888596502, \"validation_return\": -0.33908576771398047, \"validation_returns_over_hodl\": -0.0008824862342392548, \"validation_sharpe\": -2.243342796083319}, {\"centeredness_margin\": 0.9803059943907017, \"continuous_test_metrics\": [{\"annualised_returns\": -0.48450645894405975, \"annualised_returns_over_hodl\": 8.842260257324597e-12, \"annualised_returns_over_uniform_hodl\": 0.8194637303470986, \"calmar\": -0.8358049773609788, \"daily_log_sharpe\": -0.6924636115188226, \"daily_returns\": 0.031016042474699627, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": 0.005019266519699998, \"return\": -0.23422460278382595, \"returns_over_hodl\": 3.561151373787652e-12, \"returns_over_uniform_hodl\": 0.27259156468788914, \"sharpe\": -0.20210854682848592, \"sterling\": -1.2473843305706627, \"ulcer\": -0.17641031594167403}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -9.930762878695987e-10, \"optuna_trial_number\": 81, \"price_ratio\": 2.68298845727127, \"shift_exponent\": 0.000262746682171698, \"step\": 81, \"test_objective\": [{\"annualised_returns\": -0.48450645894405975, \"annualised_returns_over_hodl\": 8.842260257324597e-12, \"annualised_returns_over_uniform_hodl\": 0.8194637303470986, \"calmar\": -0.8358049773609788, \"daily_log_sharpe\": -0.6924636115188226, \"daily_returns\": 0.031016042474699627, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": 0.005019266519699998, \"return\": -0.23422460278382595, \"returns_over_hodl\": 3.561151373787652e-12, \"returns_over_uniform_hodl\": 0.27259156468788914, \"sharpe\": -0.20210854682848592, \"sterling\": -1.2473843305706627, \"ulcer\": -0.17641031594167403}], \"train_objective\": [{\"annualised_returns\": -0.5883839652861413, \"annualised_returns_over_hodl\": -0.48163621403640644, \"annualised_returns_over_uniform_hodl\": -0.48163621403640666, \"calmar\": -0.7609816206207877, \"daily_log_sharpe\": -0.6057816462384454, \"daily_returns\": 0.008206893312120593, \"fee_revenue_over_value\": 5.281724100295034e-05, \"jax_sharpe\": 0.10187939045045165, \"return\": -0.4166220341004917, \"returns_over_hodl\": -0.328959711707168, \"returns_over_uniform_hodl\": -0.3289597117071681, \"sharpe\": 0.050351772229525193, \"sterling\": -1.6117916866421247, \"ulcer\": -0.20205496483126315}], \"train_return\": -0.4166220341004917, \"train_returns_over_hodl\": -0.328959711707168, \"train_sharpe\": 0.10187939045045165, \"validation_return\": -0.3391427198416034, \"validation_returns_over_hodl\": -9.930762878695987e-10, \"validation_sharpe\": -2.2438531996912223}, {\"centeredness_margin\": 0.6549629458792188, \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -Infinity, \"optuna_trial_number\": 82, \"price_ratio\": 2.826395694453845, \"shift_exponent\": 58.460468698603876, \"step\": 82, \"test_objective\": -Infinity, \"train_objective\": -Infinity, \"train_return\": -Infinity, \"train_returns_over_hodl\": -Infinity, \"train_sharpe\": -Infinity, \"validation_return\": -Infinity, \"validation_returns_over_hodl\": -Infinity, \"validation_sharpe\": -Infinity}, {\"centeredness_margin\": 0.9698114480618902, \"continuous_test_metrics\": [{\"annualised_returns\": -0.48411891619605796, \"annualised_returns_over_hodl\": 0.0007517888998054278, \"annualised_returns_over_uniform_hodl\": 0.8208315844864618, \"calmar\": -0.8351364399490365, \"daily_log_sharpe\": -0.6916529794678283, \"daily_returns\": 0.03101604249512612, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": 0.005496830793285321, \"return\": -0.23399279781563376, \"returns_over_hodl\": 0.0003027058773830138, \"returns_over_uniform_hodl\": 0.27297678605729647, \"sharpe\": -0.20123420221214114, \"sterling\": -1.2463865817425805, \"ulcer\": -0.17641031598389897}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -8.166838316725489e-10, \"optuna_trial_number\": 83, \"price_ratio\": 3.975927450358294, \"shift_exponent\": 1.8631925259464805e-05, \"step\": 83, \"test_objective\": [{\"annualised_returns\": -0.48411891619605796, \"annualised_returns_over_hodl\": 0.0007517888998054278, \"annualised_returns_over_uniform_hodl\": 0.8208315844864618, \"calmar\": -0.8351364399490365, \"daily_log_sharpe\": -0.6916529794678283, \"daily_returns\": 0.03101604249512612, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": 0.005496830793285321, \"return\": -0.23399279781563376, \"returns_over_hodl\": 0.0003027058773830138, \"returns_over_uniform_hodl\": 0.27297678605729647, \"sharpe\": -0.20123420221214114, \"sterling\": -1.2463865817425805, \"ulcer\": -0.17641031598389897}], \"train_objective\": [{\"annualised_returns\": -0.5386682469858461, \"annualised_returns_over_hodl\": -0.41902731208253685, \"annualised_returns_over_uniform_hodl\": -0.41902731208253685, \"calmar\": -0.7099852492252731, \"daily_log_sharpe\": -0.542724477813777, \"daily_returns\": 0.008183982095613164, \"fee_revenue_over_value\": 0.00010547406861340318, \"jax_sharpe\": 0.14505044292458688, \"return\": -0.3748052559567301, \"returns_over_hodl\": -0.2808592613965354, \"returns_over_uniform_hodl\": -0.2808592613965354, \"sharpe\": 0.08125081427573798, \"sterling\": -1.5297830852237277, \"ulcer\": -0.1924533980075137}], \"train_return\": -0.3748052559567301, \"train_returns_over_hodl\": -0.2808592613965354, \"train_sharpe\": 0.14505044292458688, \"validation_return\": -0.3391427199444933, \"validation_returns_over_hodl\": -8.166838316725489e-10, \"validation_sharpe\": -2.243853199299335}, {\"centeredness_margin\": 0.8024144708008893, \"continuous_test_metrics\": [{\"annualised_returns\": -0.5713095043435077, \"annualised_returns_over_hodl\": -0.1597251917021224, \"annualised_returns_over_uniform_hodl\": 0.5130874516754922, \"calmar\": -0.9860309314955585, \"daily_log_sharpe\": -0.9134476778660644, \"daily_returns\": 0.03080785537265869, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": -0.23734433241732728, \"return\": -0.2890314059882536, \"returns_over_hodl\": -0.06768726538074343, \"returns_over_uniform_hodl\": 0.18151175760736127, \"sharpe\": -0.4451581826450003, \"sterling\": -1.4919424142286661, \"ulcer\": -0.17171269577054882}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -0.06258410572581538, \"optuna_trial_number\": 84, \"price_ratio\": 14.629864245600528, \"shift_exponent\": 0.00023247033822142063, \"step\": 84, \"test_objective\": [{\"annualised_returns\": -0.5713095043435077, \"annualised_returns_over_hodl\": -0.1597251917021224, \"annualised_returns_over_uniform_hodl\": 0.5130874516754922, \"calmar\": -0.9860309314955585, \"daily_log_sharpe\": -0.9134476778660644, \"daily_returns\": 0.03080785537265869, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": -0.23734433241732728, \"return\": -0.2890314059882536, \"returns_over_hodl\": -0.06768726538074343, \"returns_over_uniform_hodl\": 0.18151175760736127, \"sharpe\": -0.4451581826450003, \"sterling\": -1.4919424142286661, \"ulcer\": -0.17171269577054882}], \"train_objective\": [{\"annualised_returns\": -0.4128011405648797, \"annualised_returns_over_hodl\": -0.260518059987898, \"annualised_returns_over_uniform_hodl\": -0.260518059987898, \"calmar\": -0.5560135697371325, \"daily_log_sharpe\": -0.4181918233386614, \"daily_returns\": 0.008085791673045004, \"fee_revenue_over_value\": 0.0007748533576995513, \"jax_sharpe\": 0.09938057159417228, \"return\": -0.2761900442144739, \"returns_over_hodl\": -0.16742546035204864, \"returns_over_uniform_hodl\": -0.16742546035204864, \"sharpe\": 0.12157107920326918, \"sterling\": -1.2833167002950934, \"ulcer\": -0.17380652069275376}], \"train_return\": -0.2761900442144739, \"train_returns_over_hodl\": -0.16742546035204864, \"train_sharpe\": 0.09938057159417227, \"validation_return\": -0.2906031065737966, \"validation_returns_over_hodl\": -0.06258410572581541, \"validation_sharpe\": -1.9928452138599075}, {\"centeredness_margin\": 0.800030971700904, \"continuous_test_metrics\": [{\"annualised_returns\": -0.5179259165846966, \"annualised_returns_over_hodl\": -0.06483002352722467, \"annualised_returns_over_uniform_hodl\": 0.7015078565635866, \"calmar\": -0.8938497094675741, \"daily_log_sharpe\": -0.7744754918025155, \"daily_returns\": 0.031016042500809672, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": -0.09195745346076799, \"return\": -0.254619657496689, \"returns_over_hodl\": -0.02663320766980004, \"returns_over_uniform_hodl\": 0.23869837004714922, \"sharpe\": -0.29289127434162204, \"sterling\": -1.3345749946884622, \"ulcer\": -0.17608098499548946}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -0.06230069989114262, \"optuna_trial_number\": 85, \"price_ratio\": 10.178529021964072, \"shift_exponent\": 4.504502946768814e-05, \"step\": 85, \"test_objective\": [{\"annualised_returns\": -0.5179259165846966, \"annualised_returns_over_hodl\": -0.06483002352722467, \"annualised_returns_over_uniform_hodl\": 0.7015078565635866, \"calmar\": -0.8938497094675741, \"daily_log_sharpe\": -0.7744754918025155, \"daily_returns\": 0.031016042500809672, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": -0.09195745346076799, \"return\": -0.254619657496689, \"returns_over_hodl\": -0.02663320766980004, \"returns_over_uniform_hodl\": 0.23869837004714922, \"sharpe\": -0.29289127434162204, \"sterling\": -1.3345749946884622, \"ulcer\": -0.17608098499548946}], \"train_objective\": [{\"annualised_returns\": -0.4392008767539849, \"annualised_returns_over_hodl\": -0.2937642555811717, \"annualised_returns_over_uniform_hodl\": -0.2937642555811717, \"calmar\": -0.5895095897208217, \"daily_log_sharpe\": -0.4423160515890717, \"daily_returns\": 0.008082580166959228, \"fee_revenue_over_value\": 0.0006184250408248654, \"jax_sharpe\": 0.09390228458000219, \"return\": -0.29612497491327416, \"returns_over_hodl\": -0.19035594868921857, \"returns_over_uniform_hodl\": -0.19035594868921857, \"sharpe\": 0.11299758197178456, \"sterling\": -1.3424665578224781, \"ulcer\": -0.17705292428832142}], \"train_return\": -0.29612497491327416, \"train_returns_over_hodl\": -0.19035594868921857, \"train_sharpe\": 0.09390228458000219, \"validation_return\": -0.3229049251914098, \"validation_returns_over_hodl\": -0.06230069989114262, \"validation_sharpe\": -2.1179364816658635}, {\"centeredness_margin\": 0.9666183002424162, \"continuous_test_metrics\": [{\"annualised_returns\": -0.480693615311686, \"annualised_returns_over_hodl\": 0.007396489895697966, \"annualised_returns_over_uniform_hodl\": 0.8329213784960514, \"calmar\": -0.8292275642940052, \"daily_log_sharpe\": -0.6887002527416625, \"daily_returns\": 0.03101604251567288, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": -0.004835096720645853, \"return\": -0.2319484939608376, \"returns_over_hodl\": 0.0029722922886172842, \"returns_over_uniform_hodl\": 0.27637407963806426, \"sharpe\": -0.19952956573376915, \"sterling\": -1.2375679704100158, \"ulcer\": -0.1764103160263714}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -0.01062682213139321, \"optuna_trial_number\": 86, \"price_ratio\": 6.5198227537468805, \"shift_exponent\": 1.1010516720269496e-05, \"step\": 86, \"test_objective\": [{\"annualised_returns\": -0.480693615311686, \"annualised_returns_over_hodl\": 0.007396489895697966, \"annualised_returns_over_uniform_hodl\": 0.8329213784960514, \"calmar\": -0.8292275642940052, \"daily_log_sharpe\": -0.6887002527416625, \"daily_returns\": 0.03101604251567288, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": -0.004835096720645853, \"return\": -0.2319484939608376, \"returns_over_hodl\": 0.0029722922886172842, \"returns_over_uniform_hodl\": 0.27637407963806426, \"sharpe\": -0.19952956573376915, \"sterling\": -1.2375679704100158, \"ulcer\": -0.1764103160263714}], \"train_objective\": [{\"annualised_returns\": -0.4795115392664506, \"annualised_returns_over_hodl\": -0.34452901174327855, \"annualised_returns_over_uniform_hodl\": -0.34452901174327855, \"calmar\": -0.639692125290497, \"daily_log_sharpe\": -0.47855051396577286, \"daily_returns\": 0.008119994921253032, \"fee_revenue_over_value\": 0.00013691518616352707, \"jax_sharpe\": 0.09021653733904464, \"return\": -0.32729118647993904, \"returns_over_hodl\": -0.2262054061887716, \"returns_over_uniform_hodl\": -0.2262054061887716, \"sharpe\": 0.10299194198432837, \"sterling\": -1.4270477857599493, \"ulcer\": -0.18229872395416527}], \"train_return\": -0.32729118647993904, \"train_returns_over_hodl\": -0.2262054061887716, \"train_sharpe\": 0.09021653733904464, \"validation_return\": -0.3389528568975362, \"validation_returns_over_hodl\": -0.01062682213139321, \"validation_sharpe\": -2.24218170300996}, {\"centeredness_margin\": 0.7700254203783207, \"continuous_test_metrics\": [{\"annualised_returns\": -0.5238516358424656, \"annualised_returns_over_hodl\": -0.0763252583589954, \"annualised_returns_over_uniform_hodl\": 0.6805926938951177, \"calmar\": -0.9047607790773706, \"daily_log_sharpe\": -0.7885190325066026, \"daily_returns\": 0.031016042507348168, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": -0.105700697113621, \"return\": -0.258323303038154, \"returns_over_hodl\": -0.031469672239386126, \"returns_over_uniform_hodl\": 0.23254352609186402, \"sharpe\": -0.30804286197977193, \"sterling\": -1.350954101788876, \"ulcer\": -0.17576515778625085}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -0.06408395300492309, \"optuna_trial_number\": 87, \"price_ratio\": 10.980801087598353, \"shift_exponent\": 2.301219792579155e-05, \"step\": 87, \"test_objective\": [{\"annualised_returns\": -0.5238516358424656, \"annualised_returns_over_hodl\": -0.0763252583589954, \"annualised_returns_over_uniform_hodl\": 0.6805926938951177, \"calmar\": -0.9047607790773706, \"daily_log_sharpe\": -0.7885190325066026, \"daily_returns\": 0.031016042507348168, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": -0.105700697113621, \"return\": -0.258323303038154, \"returns_over_hodl\": -0.031469672239386126, \"returns_over_uniform_hodl\": 0.23254352609186402, \"sharpe\": -0.30804286197977193, \"sterling\": -1.350954101788876, \"ulcer\": -0.17576515778625085}], \"train_objective\": [{\"annualised_returns\": -0.4339210644471766, \"annualised_returns_over_hodl\": -0.2871151863862923, \"annualised_returns_over_uniform_hodl\": -0.2871151863862921, \"calmar\": -0.5829637379773691, \"daily_log_sharpe\": -0.43719566333302734, \"daily_returns\": 0.00810353792145803, \"fee_revenue_over_value\": 0.00100462873812858, \"jax_sharpe\": 0.09528295361908588, \"return\": -0.29210908500754895, \"returns_over_hodl\": -0.18573660398027847, \"returns_over_uniform_hodl\": -0.18573660398027836, \"sharpe\": 0.11515845176375747, \"sterling\": -1.3306494404507752, \"ulcer\": -0.17643263822470054}], \"train_return\": -0.29210908500754895, \"train_returns_over_hodl\": -0.18573660398027847, \"train_sharpe\": 0.09528295361908587, \"validation_return\": -0.318752449229603, \"validation_returns_over_hodl\": -0.06408395300492309, \"validation_sharpe\": -2.099884469048884}, {\"centeredness_margin\": 0.6907309676965583, \"continuous_test_metrics\": [{\"annualised_returns\": -0.47455150687649506, \"annualised_returns_over_hodl\": 0.019311496503731762, \"annualised_returns_over_uniform_hodl\": 0.8546002990559438, \"calmar\": -0.8186320121981266, \"daily_log_sharpe\": -0.6743944241749767, \"daily_returns\": 0.031016042491305306, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": 0.01047159310135336, \"return\": -0.2283028057096449, \"returns_over_hodl\": 0.00773307276212587, \"returns_over_uniform_hodl\": 0.2824326081998536, \"sharpe\": -0.1834802695859539, \"sterling\": -1.221754801543677, \"ulcer\": -0.17641031597599657}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -7.895332165830382e-10, \"optuna_trial_number\": 88, \"price_ratio\": 2.924069727353905, \"shift_exponent\": 0.001121888325410609, \"step\": 88, \"test_objective\": [{\"annualised_returns\": -0.47455150687649506, \"annualised_returns_over_hodl\": 0.019311496503731762, \"annualised_returns_over_uniform_hodl\": 0.8546002990559438, \"calmar\": -0.8186320121981266, \"daily_log_sharpe\": -0.6743944241749767, \"daily_returns\": 0.031016042491305306, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": 0.01047159310135336, \"return\": -0.2283028057096449, \"returns_over_hodl\": 0.00773307276212587, \"returns_over_uniform_hodl\": 0.2824326081998536, \"sharpe\": -0.1834802695859539, \"sterling\": -1.221754801543677, \"ulcer\": -0.17641031597599657}], \"train_objective\": [{\"annualised_returns\": -0.5491125580378025, \"annualised_returns_over_hodl\": -0.43218023170199993, \"annualised_returns_over_uniform_hodl\": -0.43218023170199993, \"calmar\": -0.7166839607981603, \"daily_log_sharpe\": -0.551965839855307, \"daily_returns\": 0.008358440172497542, \"fee_revenue_over_value\": 0.0006777570198370309, \"jax_sharpe\": 0.14912743071074097, \"return\": -0.3834371268693523, \"returns_over_hodl\": -0.290788215666828, \"returns_over_uniform_hodl\": -0.290788215666828, \"sharpe\": 0.0834381932415299, \"sterling\": -1.5417143051673075, \"ulcer\": -0.19468718701875284}], \"train_return\": -0.3834371268693523, \"train_returns_over_hodl\": -0.290788215666828, \"train_sharpe\": 0.14912743071074097, \"validation_return\": -0.33914271989029854, \"validation_returns_over_hodl\": -7.895332165830382e-10, \"validation_sharpe\": -2.2438531990227863}, {\"centeredness_margin\": 0.9623772157357409, \"continuous_test_metrics\": [{\"annualised_returns\": -0.6910676020929283, \"annualised_returns_over_hodl\": 0.08834521638595638, \"annualised_returns_over_uniform_hodl\": 0.0903944440694322, \"calmar\": -1.190355582836223, \"daily_log_sharpe\": -1.5178147921228544, \"daily_returns\": 0.01779311593690066, \"fee_revenue_over_value\": 0.051381059313731596, \"jax_sharpe\": -0.9571796429614574, \"return\": -0.3769129597203371, \"returns_over_hodl\": 0.0346830384409611, \"returns_over_uniform_hodl\": 0.035467206714661303, \"sharpe\": -1.1346641841739453, \"sterling\": -2.089998702371757, \"ulcer\": -0.14712990371180332}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -0.02636328027541901, \"optuna_trial_number\": 89, \"price_ratio\": 4.425732704070968, \"shift_exponent\": 0.1235166363358838, \"step\": 89, \"test_objective\": [{\"annualised_returns\": -0.6910676020929283, \"annualised_returns_over_hodl\": 0.08834521638595638, \"annualised_returns_over_uniform_hodl\": 0.0903944440694322, \"calmar\": -1.190355582836223, \"daily_log_sharpe\": -1.5178147921228544, \"daily_returns\": 0.01779311593690066, \"fee_revenue_over_value\": 0.051381059313731596, \"jax_sharpe\": -0.9571796429614574, \"return\": -0.3769129597203371, \"returns_over_hodl\": 0.0346830384409611, \"returns_over_uniform_hodl\": 0.035467206714661303, \"sharpe\": -1.1346641841739453, \"sterling\": -2.089998702371757, \"ulcer\": -0.14712990371180332}], \"train_objective\": [{\"annualised_returns\": -0.38169048943639305, \"annualised_returns_over_hodl\": -0.22133922937221118, \"annualised_returns_over_uniform_hodl\": -0.22133922937221118, \"calmar\": -0.5155808465424183, \"daily_log_sharpe\": -0.4350219627817791, \"daily_returns\": 0.007954651154446938, \"fee_revenue_over_value\": 0.023759993549543657, \"jax_sharpe\": -0.019286201198928404, \"return\": -0.2531443806047361, \"returns_over_hodl\": -0.1409167993183167, \"returns_over_uniform_hodl\": -0.1409167993183167, \"sharpe\": 0.04654233466050376, \"sterling\": -1.2624795575501933, \"ulcer\": -0.16400468662151188}], \"train_return\": -0.2531443806047361, \"train_returns_over_hodl\": -0.1409167993183167, \"train_sharpe\": -0.019286201198928404, \"validation_return\": -0.16253714286029108, \"validation_returns_over_hodl\": -0.02636328027541901, \"validation_sharpe\": -1.3083860191919097}, {\"centeredness_margin\": 0.694388072212554, \"continuous_test_metrics\": [{\"annualised_returns\": -0.6989608079477481, \"annualised_returns_over_hodl\": -0.03138514604602005, \"annualised_returns_over_uniform_hodl\": 0.06253492571428443, \"calmar\": -1.2213158060210647, \"daily_log_sharpe\": -1.5570916297758834, \"daily_returns\": 0.019978319403617065, \"fee_revenue_over_value\": 0.05457543229663232, \"jax_sharpe\": -0.9878936788254675, \"return\": -0.3833740780208257, \"returns_over_hodl\": -0.012760475595233634, \"returns_over_uniform_hodl\": 0.024729900870750443, \"sharpe\": -1.177295478409906, \"sterling\": -2.0931372591738437, \"ulcer\": -0.14551158870957734}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -0.03874188602206363, \"optuna_trial_number\": 90, \"price_ratio\": 3.3815603150207356, \"shift_exponent\": 0.015696374487508446, \"step\": 90, \"test_objective\": [{\"annualised_returns\": -0.6989608079477481, \"annualised_returns_over_hodl\": -0.03138514604602005, \"annualised_returns_over_uniform_hodl\": 0.06253492571428443, \"calmar\": -1.2213158060210647, \"daily_log_sharpe\": -1.5570916297758834, \"daily_returns\": 0.019978319403617065, \"fee_revenue_over_value\": 0.05457543229663232, \"jax_sharpe\": -0.9878936788254675, \"return\": -0.3833740780208257, \"returns_over_hodl\": -0.012760475595233634, \"returns_over_uniform_hodl\": 0.024729900870750443, \"sharpe\": -1.177295478409906, \"sterling\": -2.0931372591738437, \"ulcer\": -0.14551158870957734}], \"train_objective\": [{\"annualised_returns\": -0.35285190945535383, \"annualised_returns_over_hodl\": -0.18502170468885581, \"annualised_returns_over_uniform_hodl\": -0.18502170468885581, \"calmar\": -0.47725733172382473, \"daily_log_sharpe\": -0.37549626391135804, \"daily_returns\": 0.008291904873497713, \"fee_revenue_over_value\": 0.016117538500187906, \"jax_sharpe\": 0.1304969461165761, \"return\": -0.2321855363652655, \"returns_over_hodl\": -0.1168085372603671, \"returns_over_uniform_hodl\": -0.1168085372603671, \"sharpe\": 0.12246171123924683, \"sterling\": -1.1397943269466613, \"ulcer\": -0.1677277265527997}], \"train_return\": -0.2321855363652655, \"train_returns_over_hodl\": -0.1168085372603671, \"train_sharpe\": 0.1304969461165761, \"validation_return\": -0.17759407527760673, \"validation_returns_over_hodl\": -0.03874188602206363, \"validation_sharpe\": -1.429962201468901}, {\"centeredness_margin\": 0.7484924741188737, \"continuous_test_metrics\": [{\"annualised_returns\": -0.7008189641544864, \"annualised_returns_over_hodl\": -0.017315847567899056, \"annualised_returns_over_uniform_hodl\": 0.05597645784957672, \"calmar\": -1.2189485425625095, \"daily_log_sharpe\": -1.5648872934515872, \"daily_returns\": 0.019572459877438667, \"fee_revenue_over_value\": 0.046805136889587054, \"jax_sharpe\": -1.018544821841001, \"return\": -0.38490977598446985, \"returns_over_hodl\": -0.007010144604995383, \"returns_over_uniform_hodl\": 0.022177825834719833, \"sharpe\": -1.184614136654544, \"sterling\": -2.1222616299989214, \"ulcer\": -0.1459504921058385}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -0.034431011849928694, \"optuna_trial_number\": 91, \"price_ratio\": 5.402650359691597, \"shift_exponent\": 0.011993383327729699, \"step\": 91, \"test_objective\": [{\"annualised_returns\": -0.7008189641544864, \"annualised_returns_over_hodl\": -0.017315847567899056, \"annualised_returns_over_uniform_hodl\": 0.05597645784957672, \"calmar\": -1.2189485425625095, \"daily_log_sharpe\": -1.5648872934515872, \"daily_returns\": 0.019572459877438667, \"fee_revenue_over_value\": 0.046805136889587054, \"jax_sharpe\": -1.018544821841001, \"return\": -0.38490977598446985, \"returns_over_hodl\": -0.007010144604995383, \"returns_over_uniform_hodl\": 0.022177825834719833, \"sharpe\": -1.184614136654544, \"sterling\": -2.1222616299989214, \"ulcer\": -0.1459504921058385}], \"train_objective\": [{\"annualised_returns\": -0.34493886042131394, \"annualised_returns_over_hodl\": -0.17505649995952144, \"annualised_returns_over_uniform_hodl\": -0.17505649995952144, \"calmar\": -0.46724869865123386, \"daily_log_sharpe\": -0.36317133634727916, \"daily_returns\": 0.008185509291310289, \"fee_revenue_over_value\": 0.010388156236377302, \"jax_sharpe\": 0.0863990399390776, \"return\": -0.22649918782280165, \"returns_over_hodl\": -0.1102677194915902, \"returns_over_uniform_hodl\": -0.1102677194915902, \"sharpe\": 0.1347761271866537, \"sterling\": -1.118627998183364, \"ulcer\": -0.16702094962347266}], \"train_return\": -0.22649918782280165, \"train_returns_over_hodl\": -0.1102677194915902, \"train_sharpe\": 0.08639903993907759, \"validation_return\": -0.17246551595024606, \"validation_returns_over_hodl\": -0.034431011849928694, \"validation_sharpe\": -1.3829752019356933}, {\"centeredness_margin\": 0.17339455749153368, \"continuous_test_metrics\": [{\"annualised_returns\": -0.4845064591218168, \"annualised_returns_over_hodl\": 9.160006086972317e-12, \"annualised_returns_over_uniform_hodl\": 0.8194637297196952, \"calmar\": -0.8358049776457926, \"daily_log_sharpe\": -0.6924636119651809, \"daily_returns\": 0.031016042466252718, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": 0.005019265985297694, \"return\": -0.23422460289017344, \"returns_over_hodl\": 3.68904906622447e-12, \"returns_over_uniform_hodl\": 0.2725915645111572, \"sharpe\": -0.2021085473520542, \"sterling\": -1.2473843311663764, \"ulcer\": -0.17641031592420847}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -1.0423751861665664e-09, \"optuna_trial_number\": 92, \"price_ratio\": 2.2812314633975537, \"shift_exponent\": 0.0006820487777585658, \"step\": 92, \"test_objective\": [{\"annualised_returns\": -0.4845064591218168, \"annualised_returns_over_hodl\": 9.160006086972317e-12, \"annualised_returns_over_uniform_hodl\": 0.8194637297196952, \"calmar\": -0.8358049776457926, \"daily_log_sharpe\": -0.6924636119651809, \"daily_returns\": 0.031016042466252718, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": 0.005019265985297694, \"return\": -0.23422460289017344, \"returns_over_hodl\": 3.68904906622447e-12, \"returns_over_uniform_hodl\": 0.2725915645111572, \"sharpe\": -0.2021085473520542, \"sterling\": -1.2473843311663764, \"ulcer\": -0.17641031592420847}], \"train_objective\": [{\"annualised_returns\": -0.598088469601165, \"annualised_returns_over_hodl\": -0.4938574667899166, \"annualised_returns_over_uniform_hodl\": -0.4938574667899167, \"calmar\": -0.7711539103060102, \"daily_log_sharpe\": -0.6172453380068702, \"daily_returns\": 0.008392733663459636, \"fee_revenue_over_value\": 0.013929482563304748, \"jax_sharpe\": 0.11134110031328688, \"return\": -0.4250115246635262, \"returns_over_hodl\": -0.338609863915725, \"returns_over_uniform_hodl\": -0.3386098639157251, \"sharpe\": 0.04645022552792187, \"sterling\": -1.6316574588228105, \"ulcer\": -0.20337147479676856}], \"train_return\": -0.4250115246635262, \"train_returns_over_hodl\": -0.338609863915725, \"train_sharpe\": 0.11134110031328687, \"validation_return\": -0.33914271978095945, \"validation_returns_over_hodl\": -1.0423751861665664e-09, \"validation_sharpe\": -2.2438531996699753}, {\"centeredness_margin\": 0.727885800503199, \"continuous_test_metrics\": [{\"annualised_returns\": -0.7185011538000616, \"annualised_returns_over_hodl\": -0.21835123026062953, \"annualised_returns_over_uniform_hodl\": -0.006433834755334766, \"calmar\": -1.2450316536702444, \"daily_log_sharpe\": -1.6029731042619477, \"daily_returns\": 0.022884877946806728, \"fee_revenue_over_value\": 0.039740735323545155, \"jax_sharpe\": -0.9853895086904104, \"return\": -0.39981738442937254, \"returns_over_hodl\": -0.09445144008915718, \"returns_over_uniform_hodl\": -0.0025961442165766124, \"sharpe\": -1.217588741534061, \"sterling\": -2.093582139380818, \"ulcer\": -0.14790107009864778}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -0.04455310486617714, \"optuna_trial_number\": 93, \"price_ratio\": 3.3061637747901393, \"shift_exponent\": 0.005757080472739055, \"step\": 93, \"test_objective\": [{\"annualised_returns\": -0.7185011538000616, \"annualised_returns_over_hodl\": -0.21835123026062953, \"annualised_returns_over_uniform_hodl\": -0.006433834755334766, \"calmar\": -1.2450316536702444, \"daily_log_sharpe\": -1.6029731042619477, \"daily_returns\": 0.022884877946806728, \"fee_revenue_over_value\": 0.039740735323545155, \"jax_sharpe\": -0.9853895086904104, \"return\": -0.39981738442937254, \"returns_over_hodl\": -0.09445144008915718, \"returns_over_uniform_hodl\": -0.0025961442165766124, \"sharpe\": -1.217588741534061, \"sterling\": -2.093582139380818, \"ulcer\": -0.14790107009864778}], \"train_objective\": [{\"annualised_returns\": -0.3853194792542789, \"annualised_returns_over_hodl\": -0.225909354786617, \"annualised_returns_over_uniform_hodl\": -0.22590935478661667, \"calmar\": -0.5154641282562478, \"daily_log_sharpe\": -0.3873838479555221, \"daily_returns\": 0.008283795978770592, \"fee_revenue_over_value\": 0.004875202325685279, \"jax_sharpe\": 0.13797443785260402, \"return\": -0.25580874611474014, \"returns_over_hodl\": -0.14398153042655104, \"returns_over_uniform_hodl\": -0.14398153042655082, \"sharpe\": 0.15573605458052503, \"sterling\": -1.2007324619731843, \"ulcer\": -0.17297395378275976}], \"train_return\": -0.25580874611474014, \"train_returns_over_hodl\": -0.14398153042655104, \"train_sharpe\": 0.13797443785260402, \"validation_return\": -0.21003668156765642, \"validation_returns_over_hodl\": -0.04455310486617714, \"validation_sharpe\": -1.6487797986476045}, {\"centeredness_margin\": 0.7424018049618505, \"continuous_test_metrics\": [{\"annualised_returns\": -0.7097947397168434, \"annualised_returns_over_hodl\": -0.12991126796929608, \"annualised_returns_over_uniform_hodl\": 0.024295948227687658, \"calmar\": -1.2320048043541378, \"daily_log_sharpe\": -1.5848702827941128, \"daily_returns\": 0.021309876382421933, \"fee_revenue_over_value\": 0.04023907679114424, \"jax_sharpe\": -1.0070709741982447, \"return\": -0.39240933324345184, \"returns_over_hodl\": -0.05450343927615231, \"returns_over_uniform_hodl\": 0.009714806858960978, \"sharpe\": -1.2020716489696386, \"sterling\": -2.112436359165207, \"ulcer\": -0.14722424434020787}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -0.0369284432883209, \"optuna_trial_number\": 94, \"price_ratio\": 5.091218640168151, \"shift_exponent\": 0.005687227128781962, \"step\": 94, \"test_objective\": [{\"annualised_returns\": -0.7097947397168434, \"annualised_returns_over_hodl\": -0.12991126796929608, \"annualised_returns_over_uniform_hodl\": 0.024295948227687658, \"calmar\": -1.2320048043541378, \"daily_log_sharpe\": -1.5848702827941128, \"daily_returns\": 0.021309876382421933, \"fee_revenue_over_value\": 0.04023907679114424, \"jax_sharpe\": -1.0070709741982447, \"return\": -0.39240933324345184, \"returns_over_hodl\": -0.05450343927615231, \"returns_over_uniform_hodl\": 0.009714806858960978, \"sharpe\": -1.2020716489696386, \"sterling\": -2.112436359165207, \"ulcer\": -0.14722424434020787}], \"train_objective\": [{\"annualised_returns\": -0.3639159919147562, \"annualised_returns_over_hodl\": -0.1989551261014989, \"annualised_returns_over_uniform_hodl\": -0.19895512610149868, \"calmar\": -0.4896868124498959, \"daily_log_sharpe\": -0.3702221491984971, \"daily_returns\": 0.008164043961946655, \"fee_revenue_over_value\": 0.005709883432316939, \"jax_sharpe\": 0.12894585979302023, \"return\": -0.24018224595270943, \"returns_over_hodl\": -0.12600688656497017, \"returns_over_uniform_hodl\": -0.12600688656497006, \"sharpe\": 0.1531016901706732, \"sterling\": -1.1551294077050196, \"ulcer\": -0.1699713204857458}], \"train_return\": -0.24018224595270943, \"train_returns_over_hodl\": -0.12600688656497017, \"train_sharpe\": 0.12894585979302023, \"validation_return\": -0.1945103043670443, \"validation_returns_over_hodl\": -0.0369284432883209, \"validation_sharpe\": -1.53968129154747}, {\"centeredness_margin\": 0.7286665895086691, \"continuous_test_metrics\": [{\"annualised_returns\": -0.48077114441587954, \"annualised_returns_over_hodl\": 0.0072460925068291715, \"annualised_returns_over_uniform_hodl\": 0.8326477351195751, \"calmar\": -0.8293613072138619, \"daily_log_sharpe\": -0.68500249276506, \"daily_returns\": 0.031016042505232117, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": 0.007656629491657516, \"return\": -0.23199467601338675, \"returns_over_hodl\": 0.002911984892046471, \"returns_over_uniform_hodl\": 0.2762973327345628, \"sharpe\": -0.1943201274152955, \"sterling\": -1.237767573190477, \"ulcer\": -0.17641031600478424}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -6.951952347122869e-10, \"optuna_trial_number\": 95, \"price_ratio\": 4.9001478036231125, \"shift_exponent\": 4.047892711718843e-05, \"step\": 95, \"test_objective\": [{\"annualised_returns\": -0.48077114441587954, \"annualised_returns_over_hodl\": 0.0072460925068291715, \"annualised_returns_over_uniform_hodl\": 0.8326477351195751, \"calmar\": -0.8293613072138619, \"daily_log_sharpe\": -0.68500249276506, \"daily_returns\": 0.031016042505232117, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": 0.007656629491657516, \"return\": -0.23199467601338675, \"returns_over_hodl\": 0.002911984892046471, \"returns_over_uniform_hodl\": 0.2762973327345628, \"sharpe\": -0.1943201274152955, \"sterling\": -1.237767573190477, \"ulcer\": -0.17641031600478424}], \"train_objective\": [{\"annualised_returns\": -0.5105567813401386, \"annualised_returns_over_hodl\": -0.38362547023926585, \"annualised_returns_over_uniform_hodl\": -0.38362547023926563, \"calmar\": -0.6775457085401496, \"daily_log_sharpe\": -0.5098198142854505, \"daily_returns\": 0.008195874488634137, \"fee_revenue_over_value\": 0.0008120859663127203, \"jax_sharpe\": 0.15843441944803818, \"return\": -0.3519453366944071, \"returns_over_hodl\": -0.25456425591327647, \"returns_over_uniform_hodl\": -0.25456425591327636, \"sharpe\": 0.09466171801686872, \"sterling\": -1.4843320721165967, \"ulcer\": -0.18712048961612396}], \"train_return\": -0.3519453366944071, \"train_returns_over_hodl\": -0.25456425591327647, \"train_sharpe\": 0.15843441944803818, \"validation_return\": -0.3391427199759127, \"validation_returns_over_hodl\": -6.951952347122869e-10, \"validation_sharpe\": -2.243853198912532}, {\"centeredness_margin\": 0.7746294716010529, \"continuous_test_metrics\": [{\"annualised_returns\": -0.4845064590588658, \"annualised_returns_over_hodl\": 9.07274255723678e-12, \"annualised_returns_over_uniform_hodl\": 0.8194637299418841, \"calmar\": -0.835804977544932, \"daily_log_sharpe\": -0.6924636118071132, \"daily_returns\": 0.03101604246924402, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": 0.0050192661745334256, \"return\": -0.23422460285251145, \"returns_over_hodl\": 3.653966018646315e-12, \"returns_over_uniform_hodl\": 0.2725915645737451, \"sharpe\": -0.2021085471666498, \"sterling\": -1.2473843309554205, \"ulcer\": -0.17641031593039233}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -1.05364927893703e-09, \"optuna_trial_number\": 96, \"price_ratio\": 2.5576185377774103, \"shift_exponent\": 6.067556747052261e-05, \"step\": 96, \"test_objective\": [{\"annualised_returns\": -0.4845064590588658, \"annualised_returns_over_hodl\": 9.07274255723678e-12, \"annualised_returns_over_uniform_hodl\": 0.8194637299418841, \"calmar\": -0.835804977544932, \"daily_log_sharpe\": -0.6924636118071132, \"daily_returns\": 0.03101604246924402, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": 0.0050192661745334256, \"return\": -0.23422460285251145, \"returns_over_hodl\": 3.653966018646315e-12, \"returns_over_uniform_hodl\": 0.2725915645737451, \"sharpe\": -0.2021085471666498, \"sterling\": -1.2473843309554205, \"ulcer\": -0.17641031593039233}], \"train_objective\": [{\"annualised_returns\": -0.6008615172581304, \"annualised_returns_over_hodl\": -0.49734967156547016, \"annualised_returns_over_uniform_hodl\": -0.49734967156546983, \"calmar\": -0.7742582515576885, \"daily_log_sharpe\": -0.6244529776576172, \"daily_returns\": 0.008409567336392339, \"fee_revenue_over_value\": 0.0003460245509805116, \"jax_sharpe\": 0.0965186889559246, \"return\": -0.427423382230129, \"returns_over_hodl\": -0.34138414352760416, \"returns_over_uniform_hodl\": -0.34138414352760393, \"sharpe\": 0.0374472460606565, \"sterling\": -1.6392187701360816, \"ulcer\": -0.2037658197227111}], \"train_return\": -0.427423382230129, \"train_returns_over_hodl\": -0.34138414352760416, \"train_sharpe\": 0.09651868895592458, \"validation_return\": -0.3391427198214173, \"validation_returns_over_hodl\": -1.05364927893703e-09, \"validation_sharpe\": -2.2438531998688753}, {\"centeredness_margin\": 0.7652657963603331, \"continuous_test_metrics\": [{\"annualised_returns\": -0.4845064588203659, \"annualised_returns_over_hodl\": 8.717249144751804e-12, \"annualised_returns_over_uniform_hodl\": 0.8194637307836832, \"calmar\": -0.8358049771628033, \"daily_log_sharpe\": -0.6924636112082464, \"daily_returns\": 0.031016042480577665, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": 0.005019266891505665, \"return\": -0.23422460270982304, \"returns_over_hodl\": 3.51074724846967e-12, \"returns_over_uniform_hodl\": 0.27259156481086966, \"sharpe\": -0.20210854646419882, \"sterling\": -1.2473843301561693, \"ulcer\": -0.17641031595382298}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -9.633495112737478e-10, \"optuna_trial_number\": 97, \"price_ratio\": 2.952005397966059, \"shift_exponent\": 3.445630099348646e-05, \"step\": 97, \"test_objective\": [{\"annualised_returns\": -0.4845064588203659, \"annualised_returns_over_hodl\": 8.717249144751804e-12, \"annualised_returns_over_uniform_hodl\": 0.8194637307836832, \"calmar\": -0.8358049771628033, \"daily_log_sharpe\": -0.6924636112082464, \"daily_returns\": 0.031016042480577665, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": 0.005019266891505665, \"return\": -0.23422460270982304, \"returns_over_hodl\": 3.51074724846967e-12, \"returns_over_uniform_hodl\": 0.27259156481086966, \"sharpe\": -0.20210854646419882, \"sterling\": -1.2473843301561693, \"ulcer\": -0.17641031595382298}], \"train_objective\": [{\"annualised_returns\": -0.5748726347027764, \"annualised_returns_over_hodl\": -0.46462087963752285, \"annualised_returns_over_uniform_hodl\": -0.4646208796375233, \"calmar\": -0.7470122314952353, \"daily_log_sharpe\": -0.5850219678143791, \"daily_returns\": 0.008350428804234589, \"fee_revenue_over_value\": 0.0003481599825248906, \"jax_sharpe\": 0.1097194283274991, \"return\": -0.4050698511239096, \"returns_over_hodl\": -0.3156716195128313, \"returns_over_uniform_hodl\": -0.3156716195128316, \"sharpe\": 0.06588329475981014, \"sterling\": -1.5819089733775042, \"ulcer\": -0.20053031888985876}], \"train_return\": -0.4050698511239096, \"train_returns_over_hodl\": -0.3156716195128313, \"train_sharpe\": 0.10971942832749909, \"validation_return\": -0.33914271988680267, \"validation_returns_over_hodl\": -9.633495112737478e-10, \"validation_sharpe\": -2.243853199735691}, {\"centeredness_margin\": 0.49738867932807845, \"continuous_test_metrics\": [{\"annualised_returns\": -0.4845064587301008, \"annualised_returns_over_hodl\": 8.572698106945609e-12, \"annualised_returns_over_uniform_hodl\": 0.8194637311022785, \"calmar\": -0.8358049770181751, \"daily_log_sharpe\": -0.6924636109815965, \"daily_returns\": 0.0310160424848684, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": 0.005019267162863386, \"return\": -0.23422460265581968, \"returns_over_hodl\": 3.452571561979312e-12, \"returns_over_uniform_hodl\": 0.2725915649006141, \"sharpe\": -0.2021085461983441, \"sterling\": -1.2473843298536702, \"ulcer\": -0.17641031596269072}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -9.234920606004948e-10, \"optuna_trial_number\": 98, \"price_ratio\": 3.058477134976627, \"shift_exponent\": 0.00013036641520245283, \"step\": 98, \"test_objective\": [{\"annualised_returns\": -0.4845064587301008, \"annualised_returns_over_hodl\": 8.572698106945609e-12, \"annualised_returns_over_uniform_hodl\": 0.8194637311022785, \"calmar\": -0.8358049770181751, \"daily_log_sharpe\": -0.6924636109815965, \"daily_returns\": 0.0310160424848684, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": 0.005019267162863386, \"return\": -0.23422460265581968, \"returns_over_hodl\": 3.452571561979312e-12, \"returns_over_uniform_hodl\": 0.2725915649006141, \"sharpe\": -0.2021085461983441, \"sterling\": -1.2473843298536702, \"ulcer\": -0.17641031596269072}], \"train_objective\": [{\"annualised_returns\": -0.5624525487769954, \"annualised_returns_over_hodl\": -0.4489797912942165, \"annualised_returns_over_uniform_hodl\": -0.4489797912942165, \"calmar\": -0.7339445681703661, \"daily_log_sharpe\": -0.5666961195939457, \"daily_returns\": 0.008318311988700344, \"fee_revenue_over_value\": 0.004508562772173961, \"jax_sharpe\": 0.11709992084407411, \"return\": -0.39457730506529387, \"returns_over_hodl\": -0.3036023924530763, \"returns_over_uniform_hodl\": -0.3036023924530763, \"sharpe\": 0.07910260317883482, \"sterling\": -1.556825563503368, \"ulcer\": -0.1986652239334557}], \"train_return\": -0.39457730506529387, \"train_returns_over_hodl\": -0.3036023924530763, \"train_sharpe\": 0.11709992084407411, \"validation_return\": -0.3391427199077969, \"validation_returns_over_hodl\": -9.234920606004948e-10, \"validation_sharpe\": -2.2438531996486866}, {\"centeredness_margin\": 0.7314927074795111, \"continuous_test_metrics\": [{\"annualised_returns\": -0.48450645995679387, \"annualised_returns_over_hodl\": 1.0316192344816955e-11, \"annualised_returns_over_uniform_hodl\": 0.8194637267725957, \"calmar\": -0.8358049789836032, \"daily_log_sharpe\": -0.6924636140617516, \"daily_returns\": 0.031016042426576646, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": 0.005019263475281529, \"return\": -0.23422460338971907, \"returns_over_hodl\": 4.154676602752261e-12, \"returns_over_uniform_hodl\": 0.27259156368099524, \"sharpe\": -0.20210854981124513, \"sterling\": -1.2473843339644815, \"ulcer\": -0.1764103158421862}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -1.4468293230152085e-09, \"optuna_trial_number\": 99, \"price_ratio\": 1.1969395617117347, \"shift_exponent\": 8.110236503205787e-05, \"step\": 99, \"test_objective\": [{\"annualised_returns\": -0.48450645995679387, \"annualised_returns_over_hodl\": 1.0316192344816955e-11, \"annualised_returns_over_uniform_hodl\": 0.8194637267725957, \"calmar\": -0.8358049789836032, \"daily_log_sharpe\": -0.6924636140617516, \"daily_returns\": 0.031016042426576646, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": 0.005019263475281529, \"return\": -0.23422460338971907, \"returns_over_hodl\": 4.154676602752261e-12, \"returns_over_uniform_hodl\": 0.27259156368099524, \"sharpe\": -0.20210854981124513, \"sterling\": -1.2473843339644815, \"ulcer\": -0.1764103158421862}], \"train_objective\": [{\"annualised_returns\": -0.6764442805631422, \"annualised_returns_over_hodl\": -0.5925339307686189, \"annualised_returns_over_uniform_hodl\": -0.5925339307686184, \"calmar\": -0.8463210858115136, \"daily_log_sharpe\": -0.7659596566578527, \"daily_returns\": 0.010425752083640646, \"fee_revenue_over_value\": 0.0002545006755661568, \"jax_sharpe\": 0.033074578065453705, \"return\": -0.4959430497985631, \"returns_over_hodl\": -0.4202000401958227, \"returns_over_uniform_hodl\": -0.42020004019582224, \"sharpe\": -0.08496624678575392, \"sterling\": -1.7961489880000245, \"ulcer\": -0.21143685964957065}], \"train_return\": -0.4959430497985631, \"train_returns_over_hodl\": -0.4202000401958227, \"train_sharpe\": 0.033074578065453705, \"validation_return\": -0.3391427196104384, \"validation_returns_over_hodl\": -1.4468293230152085e-09, \"validation_sharpe\": -2.2438532007181893}, {\"centeredness_margin\": 0.4247183872079016, \"continuous_test_metrics\": [{\"annualised_returns\": -0.4839403898348238, \"annualised_returns_over_hodl\": 0.0010981100524170007, \"annualised_returns_over_uniform_hodl\": 0.8214617034177505, \"calmar\": -0.8348284699014525, \"daily_log_sharpe\": -0.6912835852766696, \"daily_returns\": 0.0310160424972015, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": 0.005718605901019792, \"return\": -0.2338860489803536, \"returns_over_hodl\": 0.0004421055100360949, \"returns_over_uniform_hodl\": 0.2731541848974939, \"sharpe\": -0.20083924563565875, \"sterling\": -1.2459269565226465, \"ulcer\": -0.1764103159881896}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -8.08847988587047e-10, \"optuna_trial_number\": 100, \"price_ratio\": 4.0383024616317025, \"shift_exponent\": 1.8717160907479866e-05, \"step\": 100, \"test_objective\": [{\"annualised_returns\": -0.4839403898348238, \"annualised_returns_over_hodl\": 0.0010981100524170007, \"annualised_returns_over_uniform_hodl\": 0.8214617034177505, \"calmar\": -0.8348284699014525, \"daily_log_sharpe\": -0.6912835852766696, \"daily_returns\": 0.0310160424972015, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": 0.005718605901019792, \"return\": -0.2338860489803536, \"returns_over_hodl\": 0.0004421055100360949, \"returns_over_uniform_hodl\": 0.2731541848974939, \"sharpe\": -0.20083924563565875, \"sterling\": -1.2459269565226465, \"ulcer\": -0.1764103159881896}], \"train_objective\": [{\"annualised_returns\": -0.5249522971414715, \"annualised_returns_over_hodl\": -0.4017542928369203, \"annualised_returns_over_uniform_hodl\": -0.4017542928369203, \"calmar\": -0.6955046478076442, \"daily_log_sharpe\": -0.521182814208399, \"daily_returns\": 0.008246660221616828, \"fee_revenue_over_value\": 0.011524329759172195, \"jax_sharpe\": 0.11282962421500242, \"return\": -0.3635852235007163, \"returns_over_hodl\": -0.26795323090851497, \"returns_over_uniform_hodl\": -0.26795323090851497, \"sharpe\": 0.0996567863494958, \"sterling\": -1.5009062760434158, \"ulcer\": -0.19068107331994594}], \"train_return\": -0.3635852235007163, \"train_returns_over_hodl\": -0.26795323090851497, \"train_sharpe\": 0.11282962421500242, \"validation_return\": -0.33914271995910805, \"validation_returns_over_hodl\": -8.08847988587047e-10, \"validation_sharpe\": -2.243853199301956}, {\"centeredness_margin\": 0.36521957068375965, \"continuous_test_metrics\": [{\"annualised_returns\": -0.7015830967637036, \"annualised_returns_over_hodl\": -0.1149635837516021, \"annualised_returns_over_uniform_hodl\": 0.05327940840682621, \"calmar\": -1.17542461086268, \"daily_log_sharpe\": -1.4844063608611264, \"daily_returns\": 0.021467392613369562, \"fee_revenue_over_value\": 0.003615408745718806, \"jax_sharpe\": -0.9146566746078214, \"return\": -0.38554295642166403, \"returns_over_hodl\": -0.047994965023996805, \"returns_over_uniform_hodl\": 0.021125585078189735, \"sharpe\": -1.0853347572154015, \"sterling\": -2.056663758692283, \"ulcer\": -0.15342702799421376}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -0.03436373503139531, \"optuna_trial_number\": 101, \"price_ratio\": 143.3008368849721, \"shift_exponent\": 0.0017494318626294699, \"step\": 101, \"test_objective\": [{\"annualised_returns\": -0.7015830967637036, \"annualised_returns_over_hodl\": -0.1149635837516021, \"annualised_returns_over_uniform_hodl\": 0.05327940840682621, \"calmar\": -1.17542461086268, \"daily_log_sharpe\": -1.4844063608611264, \"daily_returns\": 0.021467392613369562, \"fee_revenue_over_value\": 0.003615408745718806, \"jax_sharpe\": -0.9146566746078214, \"return\": -0.38554295642166403, \"returns_over_hodl\": -0.047994965023996805, \"returns_over_uniform_hodl\": 0.021125585078189735, \"sharpe\": -1.0853347572154015, \"sterling\": -2.056663758692283, \"ulcer\": -0.15342702799421376}], \"train_objective\": [{\"annualised_returns\": -0.33009878671686776, \"annualised_returns_over_hodl\": -0.1563678286234711, \"annualised_returns_over_uniform_hodl\": -0.15636782862347143, \"calmar\": -0.4514056253366368, \"daily_log_sharpe\": -0.32840628711070563, \"daily_returns\": 0.007988287233762326, \"fee_revenue_over_value\": 0.01601524053679246, \"jax_sharpe\": 0.14779182357277323, \"return\": -0.21590729326909863, \"returns_over_hodl\": -0.09808421515932586, \"returns_over_uniform_hodl\": -0.09808421515932608, \"sharpe\": 0.17764533602498742, \"sterling\": -1.0721402587467341, \"ulcer\": -0.165966126112753}], \"train_return\": -0.21590729326909863, \"train_returns_over_hodl\": -0.09808421515932586, \"train_sharpe\": 0.14779182357277326, \"validation_return\": -0.2002615234777817, \"validation_returns_over_hodl\": -0.03436373503139534, \"validation_sharpe\": -1.5528327060222638}, {\"centeredness_margin\": 0.5324978170645743, \"continuous_test_metrics\": [{\"annualised_returns\": -0.4778064917330732, \"annualised_returns_over_hodl\": 0.012997187965671175, \"annualised_returns_over_uniform_hodl\": 0.8431116451394629, \"calmar\": -0.8242470809701747, \"daily_log_sharpe\": -0.6803163794901734, \"daily_returns\": 0.03101604251067233, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": 0.007339557238930433, \"return\": -0.2302316336410869, \"returns_over_hodl\": 0.00521428176512484, \"returns_over_uniform_hodl\": 0.2792272164306604, \"sharpe\": -0.1895954469048119, \"sterling\": -1.230134924498912, \"ulcer\": -0.17641031601603224}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -6.202391933385343e-10, \"optuna_trial_number\": 102, \"price_ratio\": 5.501629109334965, \"shift_exponent\": 3.461454522527474e-05, \"step\": 102, \"test_objective\": [{\"annualised_returns\": -0.4778064917330732, \"annualised_returns_over_hodl\": 0.012997187965671175, \"annualised_returns_over_uniform_hodl\": 0.8431116451394629, \"calmar\": -0.8242470809701747, \"daily_log_sharpe\": -0.6803163794901734, \"daily_returns\": 0.03101604251067233, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": 0.007339557238930433, \"return\": -0.2302316336410869, \"returns_over_hodl\": 0.00521428176512484, \"returns_over_uniform_hodl\": 0.2792272164306604, \"sharpe\": -0.1895954469048119, \"sterling\": -1.230134924498912, \"ulcer\": -0.17641031601603224}], \"train_objective\": [{\"annualised_returns\": -0.4865462455835443, \"annualised_returns_over_hodl\": -0.3533880859584163, \"annualised_returns_over_uniform_hodl\": -0.3533880859584163, \"calmar\": -0.6497473089591614, \"daily_log_sharpe\": -0.47883506417452526, \"daily_returns\": 0.008183546930466705, \"fee_revenue_over_value\": 0.010159423083813915, \"jax_sharpe\": 0.10546514664297091, \"return\": -0.33282591482557755, \"returns_over_hodl\": -0.23257182028353984, \"returns_over_uniform_hodl\": -0.23257182028353984, \"sharpe\": 0.11380380416371924, \"sterling\": -1.434950519554304, \"ulcer\": -0.1838544141432781}], \"train_return\": -0.33282591482557755, \"train_returns_over_hodl\": -0.23257182028353984, \"train_sharpe\": 0.10546514664297091, \"validation_return\": -0.3391427199921446, \"validation_returns_over_hodl\": -6.202391933385343e-10, \"validation_sharpe\": -2.243853198695497}, {\"centeredness_margin\": 0.44376963305253403, \"continuous_test_metrics\": [{\"annualised_returns\": -0.6801706771360978, \"annualised_returns_over_hodl\": 0.05382237096912079, \"annualised_returns_over_uniform_hodl\": 0.12885575958980522, \"calmar\": -1.1736250658795127, \"daily_log_sharpe\": -1.4575548764531778, \"daily_returns\": 0.019470345705968565, \"fee_revenue_over_value\": 0.041694763389447644, \"jax_sharpe\": -0.916998626148873, \"return\": -0.3681530912392552, \"returns_over_hodl\": 0.0213375485502183, \"returns_over_uniform_hodl\": 0.05002465368582998, \"sharpe\": -1.0695448200654474, \"sterling\": -2.069888957999109, \"ulcer\": -0.14832345441068961}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -0.023688546991437603, \"optuna_trial_number\": 103, \"price_ratio\": 162.08365778315166, \"shift_exponent\": 0.11182653384281076, \"step\": 103, \"test_objective\": [{\"annualised_returns\": -0.6801706771360978, \"annualised_returns_over_hodl\": 0.05382237096912079, \"annualised_returns_over_uniform_hodl\": 0.12885575958980522, \"calmar\": -1.1736250658795127, \"daily_log_sharpe\": -1.4575548764531778, \"daily_returns\": 0.019470345705968565, \"fee_revenue_over_value\": 0.041694763389447644, \"jax_sharpe\": -0.916998626148873, \"return\": -0.3681530912392552, \"returns_over_hodl\": 0.0213375485502183, \"returns_over_uniform_hodl\": 0.05002465368582998, \"sharpe\": -1.0695448200654474, \"sterling\": -2.069888957999109, \"ulcer\": -0.14832345441068961}], \"train_objective\": [{\"annualised_returns\": -0.31478546013726527, \"annualised_returns_over_hodl\": -0.1370831718753014, \"annualised_returns_over_uniform_hodl\": -0.13708317187530117, \"calmar\": -0.4307009555468738, \"daily_log_sharpe\": -0.313757138738425, \"daily_returns\": 0.007965069193534102, \"fee_revenue_over_value\": 0.019564719313878518, \"jax_sharpe\": 0.17125909643671292, \"return\": -0.20507382093776505, \"returns_over_hodl\": -0.0856228319371789, \"returns_over_uniform_hodl\": -0.08562283193717879, \"sharpe\": 0.18501666380447798, \"sterling\": -1.0308788410180016, \"ulcer\": -0.16470054417378097}], \"train_return\": -0.20507382093776505, \"train_returns_over_hodl\": -0.0856228319371789, \"train_sharpe\": 0.17125909643671286, \"validation_return\": -0.17570263615725878, \"validation_returns_over_hodl\": -0.02368854699143763, \"validation_sharpe\": -1.3815716839692604}, {\"centeredness_margin\": 0.6994066905944525, \"continuous_test_metrics\": [{\"annualised_returns\": -0.47883375451816024, \"annualised_returns_over_hodl\": 0.011004412749711978, \"annualised_returns_over_uniform_hodl\": 0.8394858627966373, \"calmar\": -0.8260191786875537, \"daily_log_sharpe\": -0.6819218764535755, \"daily_returns\": 0.031016042509218675, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": 0.0075189235319330005, \"return\": -0.23084185568486937, \"returns_over_hodl\": 0.004417413613932997, \"returns_over_uniform_hodl\": 0.2782131287120848, \"sharpe\": -0.19119925043617886, \"sterling\": -1.2327796646089928, \"ulcer\": -0.1764103160130177}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -6.465716850811987e-10, \"optuna_trial_number\": 104, \"price_ratio\": 5.4027819618489605, \"shift_exponent\": 1.609561174778503e-05, \"step\": 104, \"test_objective\": [{\"annualised_returns\": -0.47883375451816024, \"annualised_returns_over_hodl\": 0.011004412749711978, \"annualised_returns_over_uniform_hodl\": 0.8394858627966373, \"calmar\": -0.8260191786875537, \"daily_log_sharpe\": -0.6819218764535755, \"daily_returns\": 0.031016042509218675, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": 0.0075189235319330005, \"return\": -0.23084185568486937, \"returns_over_hodl\": 0.004417413613932997, \"returns_over_uniform_hodl\": 0.2782131287120848, \"sharpe\": -0.19119925043617886, \"sterling\": -1.2327796646089928, \"ulcer\": -0.1764103160130177}], \"train_objective\": [{\"annualised_returns\": -0.49875773873208396, \"annualised_returns_over_hodl\": -0.36876648545431, \"annualised_returns_over_uniform_hodl\": -0.3687664854543101, \"calmar\": -0.6635459314837592, \"daily_log_sharpe\": -0.4966843060846711, \"daily_returns\": 0.008183340809726913, \"fee_revenue_over_value\": 0.0011956547740314002, \"jax_sharpe\": 0.09449606960985445, \"return\": -0.3425048821641795, \"returns_over_hodl\": -0.24370521477720686, \"returns_over_uniform_hodl\": -0.24370521477720697, \"sharpe\": 0.09921579447193297, \"sterling\": -1.4628195713869252, \"ulcer\": -0.1852580716379828}], \"train_return\": -0.3425048821641795, \"train_returns_over_hodl\": -0.24370521477720686, \"train_sharpe\": 0.09449606960985443, \"validation_return\": -0.33914271998694867, \"validation_returns_over_hodl\": -6.465716850811987e-10, \"validation_sharpe\": -2.2438531987464523}, {\"centeredness_margin\": 0.5748381317893753, \"continuous_test_metrics\": [{\"annualised_returns\": -0.5237668085329156, \"annualised_returns_over_hodl\": -0.07616070251523632, \"annualised_returns_over_uniform_hodl\": 0.6808920967018981, \"calmar\": -0.9039273696380782, \"daily_log_sharpe\": -0.7888940074329936, \"daily_returns\": 0.031016042492265136, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": -0.10773727169578878, \"return\": -0.25827009131229506, \"returns_over_hodl\": -0.03140018472433137, \"returns_over_uniform_hodl\": 0.23263195514523494, \"sharpe\": -0.30854603408753023, \"sterling\": -1.3496633932450783, \"ulcer\": -0.17607028857060575}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -0.05786608388287273, \"optuna_trial_number\": 105, \"price_ratio\": 6.894758141692898, \"shift_exponent\": 0.0006782669429038161, \"step\": 105, \"test_objective\": [{\"annualised_returns\": -0.5237668085329156, \"annualised_returns_over_hodl\": -0.07616070251523632, \"annualised_returns_over_uniform_hodl\": 0.6808920967018981, \"calmar\": -0.9039273696380782, \"daily_log_sharpe\": -0.7888940074329936, \"daily_returns\": 0.031016042492265136, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": -0.10773727169578878, \"return\": -0.25827009131229506, \"returns_over_hodl\": -0.03140018472433137, \"returns_over_uniform_hodl\": 0.23263195514523494, \"sharpe\": -0.30854603408753023, \"sterling\": -1.3496633932450783, \"ulcer\": -0.17607028857060575}], \"train_objective\": [{\"annualised_returns\": -0.4401737432113957, \"annualised_returns_over_hodl\": -0.29498942345018087, \"annualised_returns_over_uniform_hodl\": -0.294989423450181, \"calmar\": -0.5904241769963787, \"daily_log_sharpe\": -0.4341619796857391, \"daily_returns\": 0.008121824515778434, \"fee_revenue_over_value\": 0.009538666856680587, \"jax_sharpe\": 0.16236197014883805, \"return\": -0.29686656699407044, \"returns_over_hodl\": -0.19120897741635923, \"returns_over_uniform_hodl\": -0.19120897741635934, \"sharpe\": 0.13198614845226625, \"sterling\": -1.3350636152723878, \"ulcer\": -0.17811675463482732}], \"train_return\": -0.29686656699407044, \"train_returns_over_hodl\": -0.19120897741635923, \"train_sharpe\": 0.16236197014883805, \"validation_return\": -0.3297865641960416, \"validation_returns_over_hodl\": -0.05786608388287273, \"validation_sharpe\": -2.1639587134934297}, {\"centeredness_margin\": 0.4726473530477179, \"continuous_test_metrics\": [{\"annualised_returns\": -0.540194209137328, \"annualised_returns_over_hodl\": -0.1080280287229839, \"annualised_returns_over_uniform_hodl\": 0.6229106532828679, \"calmar\": -0.9348078062610632, \"daily_log_sharpe\": -0.8296639851289276, \"daily_returns\": 0.031016042514089227, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": -0.14878385542931524, \"return\": -0.2686825049903202, \"returns_over_hodl\": -0.04499740117893003, \"returns_over_uniform_hodl\": 0.2153282524370177, \"sharpe\": -0.3531242025687701, \"sterling\": -1.3968747283601781, \"ulcer\": -0.1747162409099062}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -0.06573026853054567, \"optuna_trial_number\": 106, \"price_ratio\": 10.846063304182321, \"shift_exponent\": 0.00026745718934830225, \"step\": 106, \"test_objective\": [{\"annualised_returns\": -0.540194209137328, \"annualised_returns_over_hodl\": -0.1080280287229839, \"annualised_returns_over_uniform_hodl\": 0.6229106532828679, \"calmar\": -0.9348078062610632, \"daily_log_sharpe\": -0.8296639851289276, \"daily_returns\": 0.031016042514089227, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": -0.14878385542931524, \"return\": -0.2686825049903202, \"returns_over_hodl\": -0.04499740117893003, \"returns_over_uniform_hodl\": 0.2153282524370177, \"sharpe\": -0.3531242025687701, \"sterling\": -1.3968747283601781, \"ulcer\": -0.1747162409099062}], \"train_objective\": [{\"annualised_returns\": -0.41892046296624585, \"annualised_returns_over_hodl\": -0.26822435629316477, \"annualised_returns_over_uniform_hodl\": -0.26822435629316466, \"calmar\": -0.5646890895246057, \"daily_log_sharpe\": -0.41714190778820304, \"daily_returns\": 0.00812252749463401, \"fee_revenue_over_value\": 0.009708754796835627, \"jax_sharpe\": 0.11196234350194358, \"return\": -0.28077896631348276, \"returns_over_hodl\": -0.17270394495083574, \"returns_over_uniform_hodl\": -0.17270394495083563, \"sharpe\": 0.13228162545846622, \"sterling\": -1.2918909374599785, \"ulcer\": -0.1750669171082895}], \"train_return\": -0.28077896631348276, \"train_returns_over_hodl\": -0.17270394495083574, \"train_sharpe\": 0.11196234350194356, \"validation_return\": -0.31194506058193927, \"validation_returns_over_hodl\": -0.06573026853054564, \"validation_sharpe\": -2.0752160317303456}, {\"centeredness_margin\": 0.5336622304902441, \"continuous_test_metrics\": [{\"annualised_returns\": -0.5177511742509345, \"annualised_returns_over_hodl\": -0.06449104289547614, \"annualised_returns_over_uniform_hodl\": 0.7021246195550008, \"calmar\": -0.8935126385078372, \"daily_log_sharpe\": -0.7740921011319454, \"daily_returns\": 0.03101604250097933, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": -0.09138933757770296, \"return\": -0.2545108552918457, \"returns_over_hodl\": -0.026491126583572333, \"returns_over_uniform_hodl\": 0.23887918124662955, \"sharpe\": -0.29248643967195337, \"sterling\": -1.3340835826183814, \"ulcer\": -0.17609271336094956}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -0.06225207559624646, \"optuna_trial_number\": 107, \"price_ratio\": 9.952346694895821, \"shift_exponent\": 6.948596951086589e-05, \"step\": 107, \"test_objective\": [{\"annualised_returns\": -0.5177511742509345, \"annualised_returns_over_hodl\": -0.06449104289547614, \"annualised_returns_over_uniform_hodl\": 0.7021246195550008, \"calmar\": -0.8935126385078372, \"daily_log_sharpe\": -0.7740921011319454, \"daily_returns\": 0.03101604250097933, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": -0.09138933757770296, \"return\": -0.2545108552918457, \"returns_over_hodl\": -0.026491126583572333, \"returns_over_uniform_hodl\": 0.23887918124662955, \"sharpe\": -0.29248643967195337, \"sterling\": -1.3340835826183814, \"ulcer\": -0.17609271336094956}], \"train_objective\": [{\"annualised_returns\": -0.42984941981997804, \"annualised_returns_over_hodl\": -0.2819876088721648, \"annualised_returns_over_uniform_hodl\": -0.2819876088721649, \"calmar\": -0.5788231373433242, \"daily_log_sharpe\": -0.42754740754272846, \"daily_returns\": 0.008120892862504638, \"fee_revenue_over_value\": 0.009654529301720864, \"jax_sharpe\": 0.10774786681115098, \"return\": -0.2890221838661817, \"returns_over_hodl\": -0.18218584417631378, \"returns_over_uniform_hodl\": -0.1821858441763139, \"sharpe\": 0.1275350618455787, \"sterling\": -1.3173661113412864, \"ulcer\": -0.17629580421715893}], \"train_return\": -0.2890221838661817, \"train_returns_over_hodl\": -0.18218584417631378, \"train_sharpe\": 0.10774786681115099, \"validation_return\": -0.3235345651503492, \"validation_returns_over_hodl\": -0.06225207559624646, \"validation_sharpe\": -2.121730156463545}, {\"centeredness_margin\": 0.6843852680289412, \"continuous_test_metrics\": [{\"annualised_returns\": -0.4814939134182219, \"annualised_returns_over_hodl\": 0.005844000774963387, \"annualised_returns_over_uniform_hodl\": 0.8300966808765142, \"calmar\": -0.8306081326433887, \"daily_log_sharpe\": -0.6905421160741683, \"daily_returns\": 0.031016042516102044, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": -0.0058417514003691745, \"return\": -0.2324254088475719, \"returns_over_hodl\": 0.002349505380640604, \"returns_over_uniform_hodl\": 0.27558152628085475, \"sharpe\": -0.20156667648220547, \"sterling\": -1.2396283784772395, \"ulcer\": -0.1764103160272603}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -0.010662089719559065, \"optuna_trial_number\": 108, \"price_ratio\": 6.540639666869438, \"shift_exponent\": 1.778314294637775e-05, \"step\": 108, \"test_objective\": [{\"annualised_returns\": -0.4814939134182219, \"annualised_returns_over_hodl\": 0.005844000774963387, \"annualised_returns_over_uniform_hodl\": 0.8300966808765142, \"calmar\": -0.8306081326433887, \"daily_log_sharpe\": -0.6905421160741683, \"daily_returns\": 0.031016042516102044, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": -0.0058417514003691745, \"return\": -0.2324254088475719, \"returns_over_hodl\": 0.002349505380640604, \"returns_over_uniform_hodl\": 0.27558152628085475, \"sharpe\": -0.20156667648220547, \"sterling\": -1.2396283784772395, \"ulcer\": -0.1764103160272603}], \"train_objective\": [{\"annualised_returns\": -0.4760890857751243, \"annualised_returns_over_hodl\": -0.34021898540943696, \"annualised_returns_over_uniform_hodl\": -0.34021898540943696, \"calmar\": -0.6356569172455825, \"daily_log_sharpe\": -0.4736476118509998, \"daily_returns\": 0.008140087549328814, \"fee_revenue_over_value\": 0.0016034671187260825, \"jax_sharpe\": 0.14247685610014202, \"return\": -0.3246091203987793, \"returns_over_hodl\": -0.2231203147017341, \"returns_over_uniform_hodl\": -0.2231203147017341, \"sharpe\": 0.10734140975678648, \"sterling\": -1.4183795453100698, \"ulcer\": -0.18204542457695796}], \"train_return\": -0.3246091203987793, \"train_returns_over_hodl\": -0.2231203147017341, \"train_sharpe\": 0.14247685610014202, \"validation_return\": -0.3389012157232647, \"validation_returns_over_hodl\": -0.010662089719559065, \"validation_sharpe\": -2.2416987498030894}, {\"centeredness_margin\": 0.6398882308471189, \"continuous_test_metrics\": [{\"annualised_returns\": -0.4819676427074441, \"annualised_returns_over_hodl\": 0.0049250187397760214, \"annualised_returns_over_uniform_hodl\": 0.8284246264449948, \"calmar\": -0.8314253477064588, \"daily_log_sharpe\": -0.6918654065944952, \"daily_returns\": 0.031016042516814973, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": -0.008698884708507175, \"return\": -0.23270792151243924, \"returns_over_hodl\": 0.00198058170577875, \"returns_over_uniform_hodl\": 0.2751120371388238, \"sharpe\": -0.20314785978812516, \"sterling\": -1.2408480185600057, \"ulcer\": -0.1764103160287342}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -0.014484591292321602, \"optuna_trial_number\": 109, \"price_ratio\": 6.508122142263983, \"shift_exponent\": 5.4183077031998936e-05, \"step\": 109, \"test_objective\": [{\"annualised_returns\": -0.4819676427074441, \"annualised_returns_over_hodl\": 0.0049250187397760214, \"annualised_returns_over_uniform_hodl\": 0.8284246264449948, \"calmar\": -0.8314253477064588, \"daily_log_sharpe\": -0.6918654065944952, \"daily_returns\": 0.031016042516814973, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": -0.008698884708507175, \"return\": -0.23270792151243924, \"returns_over_hodl\": 0.00198058170577875, \"returns_over_uniform_hodl\": 0.2751120371388238, \"sharpe\": -0.20314785978812516, \"sterling\": -1.2408480185600057, \"ulcer\": -0.1764103160287342}], \"train_objective\": [{\"annualised_returns\": -0.4734854396688253, \"annualised_returns_over_hodl\": -0.33694011447354455, \"annualised_returns_over_uniform_hodl\": -0.33694011447354455, \"calmar\": -0.6326150862803528, \"daily_log_sharpe\": -0.4700021870213099, \"daily_returns\": 0.008166563235278541, \"fee_revenue_over_value\": 0.003566172859126578, \"jax_sharpe\": 0.1457567504170597, \"return\": -0.3225733348497589, \"returns_over_hodl\": -0.22077861823465783, \"returns_over_uniform_hodl\": -0.22077861823465783, \"sharpe\": 0.11044577536995914, \"sterling\": -1.4122142921360048, \"ulcer\": -0.181773502924732}], \"train_return\": -0.3225733348497589, \"train_returns_over_hodl\": -0.22077861823465783, \"train_sharpe\": 0.14575675041705974, \"validation_return\": -0.3388234919649885, \"validation_returns_over_hodl\": -0.014484591292321602, \"validation_sharpe\": -2.2409914754907065}, {\"centeredness_margin\": 0.6722266215283217, \"continuous_test_metrics\": [{\"annualised_returns\": -0.5092290028248614, \"annualised_returns_over_hodl\": -0.047958980304260934, \"annualised_returns_over_uniform_hodl\": 0.7322041076156633, \"calmar\": -0.8786094630846005, \"daily_log_sharpe\": -0.7543224959756042, \"daily_returns\": 0.031016042494921264, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": -0.0716445505439269, \"return\": -0.24923289305480034, \"returns_over_hodl\": -0.01959881547230824, \"returns_over_uniform_hodl\": 0.24765027815836427, \"sharpe\": -0.2712471153083687, \"sterling\": -1.3112695823794023, \"ulcer\": -0.17634150263091164}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -0.058882344829989886, \"optuna_trial_number\": 110, \"price_ratio\": 9.484025265971034, \"shift_exponent\": 1.5807869810311823e-05, \"step\": 110, \"test_objective\": [{\"annualised_returns\": -0.5092290028248614, \"annualised_returns_over_hodl\": -0.047958980304260934, \"annualised_returns_over_uniform_hodl\": 0.7322041076156633, \"calmar\": -0.8786094630846005, \"daily_log_sharpe\": -0.7543224959756042, \"daily_returns\": 0.031016042494921264, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": -0.0716445505439269, \"return\": -0.24923289305480034, \"returns_over_hodl\": -0.01959881547230824, \"returns_over_uniform_hodl\": 0.24765027815836427, \"sharpe\": -0.2712471153083687, \"sterling\": -1.3112695823794023, \"ulcer\": -0.17634150263091164}], \"train_objective\": [{\"annualised_returns\": -0.441884488148401, \"annualised_returns_over_hodl\": -0.297143829857066, \"annualised_returns_over_uniform_hodl\": -0.2971438298570658, \"calmar\": -0.593296711213928, \"daily_log_sharpe\": -0.44282141444054335, \"daily_returns\": 0.00809413464965185, \"fee_revenue_over_value\": 0.003824319460775681, \"jax_sharpe\": 0.09739689086900633, \"return\": -0.2981718547767225, \"returns_over_hodl\": -0.19271040657751282, \"returns_over_uniform_hodl\": -0.1927104065775127, \"sharpe\": 0.11583380504134168, \"sterling\": -1.3470274069816695, \"ulcer\": -0.1775101545317428}], \"train_return\": -0.2981718547767225, \"train_returns_over_hodl\": -0.19271040657751282, \"train_sharpe\": 0.09739689086900633, \"validation_return\": -0.32781643324772647, \"validation_returns_over_hodl\": -0.058882344829989886, \"validation_sharpe\": -2.148603958968989}, {\"centeredness_margin\": 0.22826689776272346, \"continuous_test_metrics\": [{\"annualised_returns\": -0.6361625898112206, \"annualised_returns_over_hodl\": -0.0042678583174941, \"annualised_returns_over_uniform_hodl\": 0.2841848032196126, \"calmar\": -1.124301848576803, \"daily_log_sharpe\": -1.2497198494422848, \"daily_returns\": 0.02336906362702345, \"fee_revenue_over_value\": 0.06052470084630521, \"jax_sharpe\": -0.6504582952221314, \"return\": -0.3344805037939296, \"returns_over_hodl\": -0.0017210236335595264, \"returns_over_uniform_hodl\": 0.10598290319334192, \"sharpe\": -0.8459184507716052, \"sterling\": -1.8227939479813682, \"ulcer\": -0.1523650355809244}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -0.02756207125127555, \"optuna_trial_number\": 111, \"price_ratio\": 7.391661249281502, \"shift_exponent\": 0.14732924584657445, \"step\": 111, \"test_objective\": [{\"annualised_returns\": -0.6361625898112206, \"annualised_returns_over_hodl\": -0.0042678583174941, \"annualised_returns_over_uniform_hodl\": 0.2841848032196126, \"calmar\": -1.124301848576803, \"daily_log_sharpe\": -1.2497198494422848, \"daily_returns\": 0.02336906362702345, \"fee_revenue_over_value\": 0.06052470084630521, \"jax_sharpe\": -0.6504582952221314, \"return\": -0.3344805037939296, \"returns_over_hodl\": -0.0017210236335595264, \"returns_over_uniform_hodl\": 0.10598290319334192, \"sharpe\": -0.8459184507716052, \"sterling\": -1.8227939479813682, \"ulcer\": -0.1523650355809244}], \"train_objective\": [{\"annualised_returns\": -0.38346286968736687, \"annualised_returns_over_hodl\": -0.2235712554829058, \"annualised_returns_over_uniform_hodl\": -0.2235712554829059, \"calmar\": -0.5171215753362245, \"daily_log_sharpe\": -0.3864919379335037, \"daily_returns\": 0.008121095893424756, \"fee_revenue_over_value\": 0.028525541961549546, \"jax_sharpe\": 0.11215885268099206, \"return\": -0.25444487344002453, \"returns_over_hodl\": -0.1424127130108559, \"returns_over_uniform_hodl\": -0.142412713010856, \"sharpe\": 0.14287277029682366, \"sterling\": -1.2029053498757931, \"ulcer\": -0.17184125465603736}], \"train_return\": -0.25444487344002453, \"train_returns_over_hodl\": -0.1424127130108559, \"train_sharpe\": 0.11215885268099206, \"validation_return\": -0.23078090759524728, \"validation_returns_over_hodl\": -0.02756207125127552, \"validation_sharpe\": -1.7655988013923838}, {\"centeredness_margin\": 0.04627332794636482, \"continuous_test_metrics\": [{\"annualised_returns\": -0.4846480359522827, \"annualised_returns_over_hodl\": -0.0002746459739694229, \"annualised_returns_over_uniform_hodl\": 0.8189640262557822, \"calmar\": -0.8360492074311692, \"daily_log_sharpe\": -0.6999515363781302, \"daily_returns\": 0.0310160425327405, \"fee_revenue_over_value\": 0.000759260343879075, \"jax_sharpe\": -0.01651397867740213, \"return\": -0.23430931166658198, \"returns_over_hodl\": -0.00011061939511791685, \"returns_over_uniform_hodl\": 0.2724507925893842, \"sharpe\": -0.21237103210363892, \"sterling\": -1.2477488268234738, \"ulcer\": -0.17641031606166208}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -0.027386261622179053, \"optuna_trial_number\": 112, \"price_ratio\": 6.586158395723974, \"shift_exponent\": 5.526954108901249e-05, \"step\": 112, \"test_objective\": [{\"annualised_returns\": -0.4846480359522827, \"annualised_returns_over_hodl\": -0.0002746459739694229, \"annualised_returns_over_uniform_hodl\": 0.8189640262557822, \"calmar\": -0.8360492074311692, \"daily_log_sharpe\": -0.6999515363781302, \"daily_returns\": 0.0310160425327405, \"fee_revenue_over_value\": 0.000759260343879075, \"jax_sharpe\": -0.01651397867740213, \"return\": -0.23430931166658198, \"returns_over_hodl\": -0.00011061939511791685, \"returns_over_uniform_hodl\": 0.2724507925893842, \"sharpe\": -0.21237103210363892, \"sterling\": -1.2477488268234738, \"ulcer\": -0.17641031606166208}], \"train_objective\": [{\"annualised_returns\": -0.4283097787599013, \"annualised_returns_over_hodl\": -0.2800486801093891, \"annualised_returns_over_uniform_hodl\": -0.2800486801093891, \"calmar\": -0.5786571044871067, \"daily_log_sharpe\": -0.4020404356902255, \"daily_returns\": 0.008129785167834688, \"fee_revenue_over_value\": 0.030267717823130407, \"jax_sharpe\": 0.20521171379641878, \"return\": -0.287857169332408, \"returns_over_hodl\": -0.18084576667200936, \"returns_over_uniform_hodl\": -0.18084576667200936, \"sharpe\": 0.17441627075468172, \"sterling\": -1.2899252526436893, \"ulcer\": -0.17923731548890792}], \"train_return\": -0.287857169332408, \"train_returns_over_hodl\": -0.18084576667200936, \"train_sharpe\": 0.20521171379641875, \"validation_return\": -0.3377379852958362, \"validation_returns_over_hodl\": -0.027386261622179053, \"validation_sharpe\": -2.231134963014371}, {\"centeredness_margin\": 0.2361562578032633, \"continuous_test_metrics\": [{\"annualised_returns\": -0.4834404131120371, \"annualised_returns_over_hodl\": 0.00206800834839882, \"annualised_returns_over_uniform_hodl\": 0.8232263996567459, \"calmar\": -0.833965976277602, \"daily_log_sharpe\": -0.6955423652757456, \"daily_returns\": 0.03101604251688064, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": -0.013351976732140215, \"return\": -0.23358720803622524, \"returns_over_hodl\": 0.0008323513927910309, \"returns_over_uniform_hodl\": 0.2736508089285916, \"sharpe\": -0.20733612243272587, \"sterling\": -1.244639740734439, \"ulcer\": -0.17641031602887206}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -0.008185806222434588, \"optuna_trial_number\": 113, \"price_ratio\": 6.027530955925001, \"shift_exponent\": 0.0002486512531707762, \"step\": 113, \"test_objective\": [{\"annualised_returns\": -0.4834404131120371, \"annualised_returns_over_hodl\": 0.00206800834839882, \"annualised_returns_over_uniform_hodl\": 0.8232263996567459, \"calmar\": -0.833965976277602, \"daily_log_sharpe\": -0.6955423652757456, \"daily_returns\": 0.03101604251688064, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": -0.013351976732140215, \"return\": -0.23358720803622524, \"returns_over_hodl\": 0.0008323513927910309, \"returns_over_uniform_hodl\": 0.2736508089285916, \"sharpe\": -0.20733612243272587, \"sterling\": -1.244639740734439, \"ulcer\": -0.17641031602887206}], \"train_objective\": [{\"annualised_returns\": -0.4639063062976365, \"annualised_returns_over_hodl\": -0.32487674613564366, \"annualised_returns_over_uniform_hodl\": -0.32487674613564355, \"calmar\": -0.6210291473603111, \"daily_log_sharpe\": -0.45203550588469144, \"daily_returns\": 0.00818247486318093, \"fee_revenue_over_value\": 0.013987569397477797, \"jax_sharpe\": 0.1664182863438459, \"return\": -0.3151172342846552, \"returns_over_hodl\": -0.21220211352380058, \"returns_over_uniform_hodl\": -0.21220211352380047, \"sharpe\": 0.1309855806680002, \"sterling\": -1.3828917428284384, \"ulcer\": -0.18160255080492757}], \"train_return\": -0.3151172342846552, \"train_returns_over_hodl\": -0.21220211352380058, \"train_sharpe\": 0.16641828634384587, \"validation_return\": -0.3389748893861516, \"validation_returns_over_hodl\": -0.008185806222434588, \"validation_sharpe\": -2.242370477084376}, {\"centeredness_margin\": 0.29026234613158525, \"continuous_test_metrics\": [{\"annualised_returns\": -0.6411950733236722, \"annualised_returns_over_hodl\": -0.05303092373485507, \"annualised_returns_over_uniform_hodl\": 0.2664223668451051, \"calmar\": -1.1275668365483946, \"daily_log_sharpe\": -1.2640681485872107, \"daily_returns\": 0.023960408191754077, \"fee_revenue_over_value\": 0.05327437388577479, \"jax_sharpe\": -0.6610910324486178, \"return\": -0.338203234957568, \"returns_over_hodl\": -0.021705683521015984, \"returns_over_uniform_hodl\": 0.09979634210288468, \"sharpe\": -0.8599658841531963, \"sterling\": -1.8313812671874776, \"ulcer\": -0.1531183755594539}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -0.032818055373535326, \"optuna_trial_number\": 114, \"price_ratio\": 7.897547916889144, \"shift_exponent\": 0.006964141836149522, \"step\": 114, \"test_objective\": [{\"annualised_returns\": -0.6411950733236722, \"annualised_returns_over_hodl\": -0.05303092373485507, \"annualised_returns_over_uniform_hodl\": 0.2664223668451051, \"calmar\": -1.1275668365483946, \"daily_log_sharpe\": -1.2640681485872107, \"daily_returns\": 0.023960408191754077, \"fee_revenue_over_value\": 0.05327437388577479, \"jax_sharpe\": -0.6610910324486178, \"return\": -0.338203234957568, \"returns_over_hodl\": -0.021705683521015984, \"returns_over_uniform_hodl\": 0.09979634210288468, \"sharpe\": -0.8599658841531963, \"sterling\": -1.8313812671874776, \"ulcer\": -0.1531183755594539}], \"train_objective\": [{\"annualised_returns\": -0.3993343019428617, \"annualised_returns_over_hodl\": -0.2435587560144199, \"annualised_returns_over_uniform_hodl\": -0.24355875601442, \"calmar\": -0.5372533131507207, \"daily_log_sharpe\": -0.40326271573429207, \"daily_returns\": 0.008157156542187953, \"fee_revenue_over_value\": 0.017733366799640587, \"jax_sharpe\": 0.1165080627482198, \"return\": -0.2661568273440694, \"returns_over_hodl\": -0.15588458439380148, \"returns_over_uniform_hodl\": -0.1558845843938016, \"sharpe\": 0.13640465306643784, \"sterling\": -1.2411108295994062, \"ulcer\": -0.1735572525763183}], \"train_return\": -0.2661568273440694, \"train_returns_over_hodl\": -0.15588458439380148, \"train_sharpe\": 0.11650806274821979, \"validation_return\": -0.23597814547937257, \"validation_returns_over_hodl\": -0.032818055373535326, \"validation_sharpe\": -1.8066992623349827}, {\"centeredness_margin\": 0.5392936623216267, \"continuous_test_metrics\": [{\"annualised_returns\": -0.4821455476464349, \"annualised_returns_over_hodl\": 0.004579903579630562, \"annualised_returns_over_uniform_hodl\": 0.8277967008588065, \"calmar\": -0.831732245721703, \"daily_log_sharpe\": -0.6876813432096821, \"daily_returns\": 0.031016042502508213, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": 0.006968818843599273, \"return\": -0.23281405655459797, \"returns_over_hodl\": 0.0018419838063676863, \"returns_over_uniform_hodl\": 0.27493565832088307, \"sharpe\": -0.19706871147921287, \"sterling\": -1.241306044087229, \"ulcer\": -0.17641031599915097}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -7.235709809094715e-10, \"optuna_trial_number\": 115, \"price_ratio\": 4.576211770957634, \"shift_exponent\": 2.4061454981284136e-05, \"step\": 115, \"test_objective\": [{\"annualised_returns\": -0.4821455476464349, \"annualised_returns_over_hodl\": 0.004579903579630562, \"annualised_returns_over_uniform_hodl\": 0.8277967008588065, \"calmar\": -0.831732245721703, \"daily_log_sharpe\": -0.6876813432096821, \"daily_returns\": 0.031016042502508213, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": 0.006968818843599273, \"return\": -0.23281405655459797, \"returns_over_hodl\": 0.0018419838063676863, \"returns_over_uniform_hodl\": 0.27493565832088307, \"sharpe\": -0.19706871147921287, \"sterling\": -1.241306044087229, \"ulcer\": -0.17641031599915097}], \"train_objective\": [{\"annualised_returns\": -0.5126387324951571, \"annualised_returns_over_hodl\": -0.3862473508072979, \"annualised_returns_over_uniform_hodl\": -0.3862473508072979, \"calmar\": -0.6808462493783336, \"daily_log_sharpe\": -0.5088361771516559, \"daily_returns\": 0.008229766216114189, \"fee_revenue_over_value\": 0.008391010363819635, \"jax_sharpe\": 0.1628544431517264, \"return\": -0.35362035228102096, \"returns_over_hodl\": -0.2564909706811256, \"returns_over_uniform_hodl\": -0.2564909706811256, \"sharpe\": 0.1011489931795614, \"sterling\": -1.4840781100303766, \"ulcer\": -0.1879369771216589}], \"train_return\": -0.35362035228102096, \"train_returns_over_hodl\": -0.2564909706811256, \"train_sharpe\": 0.16285444315172637, \"validation_return\": -0.33914271997031065, \"validation_returns_over_hodl\": -7.235709809094715e-10, \"validation_sharpe\": -2.2438531990467534}, {\"centeredness_margin\": 0.5603643860550391, \"continuous_test_metrics\": [{\"annualised_returns\": -0.4845064588718321, \"annualised_returns_over_hodl\": 8.82960371484387e-12, \"annualised_returns_over_uniform_hodl\": 0.8194637306020307, \"calmar\": -0.8358049772452701, \"daily_log_sharpe\": -0.6924636113374887, \"daily_returns\": 0.031016042478132135, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": 0.0050192667367646455, \"return\": -0.23422460274061396, \"returns_over_hodl\": 3.5560443478743764e-12, \"returns_over_uniform_hodl\": 0.27259156475970037, \"sharpe\": -0.2021085466157993, \"sterling\": -1.2473843303286574, \"ulcer\": -0.17641031594876438}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -9.846374826594229e-10, \"optuna_trial_number\": 116, \"price_ratio\": 2.8702275694545687, \"shift_exponent\": 1.68546815863115e-05, \"step\": 116, \"test_objective\": [{\"annualised_returns\": -0.4845064588718321, \"annualised_returns_over_hodl\": 8.82960371484387e-12, \"annualised_returns_over_uniform_hodl\": 0.8194637306020307, \"calmar\": -0.8358049772452701, \"daily_log_sharpe\": -0.6924636113374887, \"daily_returns\": 0.031016042478132135, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": 0.0050192667367646455, \"return\": -0.23422460274061396, \"returns_over_hodl\": 3.5560443478743764e-12, \"returns_over_uniform_hodl\": 0.27259156475970037, \"sharpe\": -0.2021085466157993, \"sterling\": -1.2473843303286574, \"ulcer\": -0.17641031594876438}], \"train_objective\": [{\"annualised_returns\": -0.5805558445911598, \"annualised_returns_over_hodl\": -0.4717779628065887, \"annualised_returns_over_uniform_hodl\": -0.4717779628065887, \"calmar\": -0.7531877335508022, \"daily_log_sharpe\": -0.593330298439353, \"daily_returns\": 0.008344130279749663, \"fee_revenue_over_value\": 0.0016313262869030226, \"jax_sharpe\": 0.10742266244778167, \"return\": -0.4099111538966307, \"returns_over_hodl\": -0.32124040921387964, \"returns_over_uniform_hodl\": -0.32124040921387964, \"sharpe\": 0.06012425167281543, \"sterling\": -1.5945753919041603, \"ulcer\": -0.20124097966101026}], \"train_return\": -0.4099111538966307, \"train_returns_over_hodl\": -0.32124040921387964, \"train_sharpe\": 0.10742266244778169, \"validation_return\": -0.33914271987387434, \"validation_returns_over_hodl\": -9.846374826594229e-10, \"validation_sharpe\": -2.2438531997757774}, {\"centeredness_margin\": 0.5604323616852205, \"continuous_test_metrics\": [{\"annualised_returns\": -0.4845064594555193, \"annualised_returns_over_hodl\": 8.65707505681712e-12, \"annualised_returns_over_uniform_hodl\": 0.8194637285418729, \"calmar\": -0.8358049781803255, \"daily_log_sharpe\": -0.6924636128028067, \"daily_returns\": 0.031016042450416576, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": 0.005019264982818396, \"return\": -0.23422460308981918, \"returns_over_hodl\": 3.4865443865328416e-12, \"returns_over_uniform_hodl\": 0.272591564179379, \"sharpe\": -0.20210854833440053, \"sterling\": -1.2473843322842157, \"ulcer\": -0.17641031589149703}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -1.2181637920605226e-09, \"optuna_trial_number\": 117, \"price_ratio\": 1.9551445726348768, \"shift_exponent\": 1.6142856607743182e-05, \"step\": 117, \"test_objective\": [{\"annualised_returns\": -0.4845064594555193, \"annualised_returns_over_hodl\": 8.65707505681712e-12, \"annualised_returns_over_uniform_hodl\": 0.8194637285418729, \"calmar\": -0.8358049781803255, \"daily_log_sharpe\": -0.6924636128028067, \"daily_returns\": 0.031016042450416576, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": 0.005019264982818396, \"return\": -0.23422460308981918, \"returns_over_hodl\": 3.4865443865328416e-12, \"returns_over_uniform_hodl\": 0.272591564179379, \"sharpe\": -0.20210854833440053, \"sterling\": -1.2473843322842157, \"ulcer\": -0.17641031589149703}], \"train_objective\": [{\"annualised_returns\": -0.6366538910685301, \"annualised_returns_over_hodl\": -0.5424243742793251, \"annualised_returns_over_uniform_hodl\": -0.5424243742793251, \"calmar\": -0.8117150175933873, \"daily_log_sharpe\": -0.6894444211360377, \"daily_returns\": 0.008504152033099266, \"fee_revenue_over_value\": 0.000712350687029736, \"jax_sharpe\": 0.07827882459895211, \"return\": -0.45916949286105313, \"returns_over_hodl\": -0.3779006396504987, \"returns_over_uniform_hodl\": -0.3779006396504986, \"sharpe\": -0.019870852777753488, \"sterling\": -1.7153729873029486, \"ulcer\": -0.20703493677122609}], \"train_return\": -0.45916949286105313, \"train_returns_over_hodl\": -0.3779006396504987, \"train_sharpe\": 0.07827882459895211, \"validation_return\": -0.3391427197224197, \"validation_returns_over_hodl\": -1.2181637920605226e-09, \"validation_sharpe\": -2.2438532001824676}, {\"centeredness_margin\": 0.5420289466600903, \"continuous_test_metrics\": [{\"annualised_returns\": -0.7167847374079376, \"annualised_returns_over_hodl\": -0.06143411831131407, \"annualised_returns_over_uniform_hodl\": -0.00037564561634773064, \"calmar\": -1.2121026060322098, \"daily_log_sharpe\": -1.6123862547782664, \"daily_returns\": 0.019352484919043293, \"fee_revenue_over_value\": 0.04333340729098572, \"jax_sharpe\": -1.1073047367792688, \"return\": -0.3983462142671078, \"returns_over_hodl\": -0.025211227164666172, \"returns_over_uniform_hodl\": -0.00015130367256765975, \"sharpe\": -1.2329682962554105, \"sterling\": -2.2108929406636673, \"ulcer\": -0.15015674424045936}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -0.021623527861337366, \"optuna_trial_number\": 118, \"price_ratio\": 64.32855813000525, \"shift_exponent\": 40.880187338304005, \"step\": 118, \"test_objective\": [{\"annualised_returns\": -0.7167847374079376, \"annualised_returns_over_hodl\": -0.06143411831131407, \"annualised_returns_over_uniform_hodl\": -0.00037564561634773064, \"calmar\": -1.2121026060322098, \"daily_log_sharpe\": -1.6123862547782664, \"daily_returns\": 0.019352484919043293, \"fee_revenue_over_value\": 0.04333340729098572, \"jax_sharpe\": -1.1073047367792688, \"return\": -0.3983462142671078, \"returns_over_hodl\": -0.025211227164666172, \"returns_over_uniform_hodl\": -0.00015130367256765975, \"sharpe\": -1.2329682962554105, \"sterling\": -2.2108929406636673, \"ulcer\": -0.15015674424045936}], \"train_objective\": [{\"annualised_returns\": -0.3055525770780163, \"annualised_returns_over_hodl\": -0.12545584977333657, \"annualised_returns_over_uniform_hodl\": -0.12545584977333657, \"calmar\": -0.41815542759142665, \"daily_log_sharpe\": -0.30377422154000727, \"daily_returns\": 0.008017612364504571, \"fee_revenue_over_value\": 0.021954317179311022, \"jax_sharpe\": 0.15153110430373343, \"return\": -0.19858793680361464, \"returns_over_hodl\": -0.07816233494616975, \"returns_over_uniform_hodl\": -0.07816233494616975, \"sharpe\": 0.1902683901273623, \"sterling\": -1.0055088470766858, \"ulcer\": -0.16389603212061934}], \"train_return\": -0.19858793680361464, \"train_returns_over_hodl\": -0.07816233494616975, \"train_sharpe\": 0.15153110430373343, \"validation_return\": -0.17158534595773967, \"validation_returns_over_hodl\": -0.021623527861337366, \"validation_sharpe\": -1.3588997903772535}, {\"centeredness_margin\": 0.6995656680831759, \"continuous_test_metrics\": [{\"annualised_returns\": -0.4845064587600554, \"annualised_returns_over_hodl\": 8.723910482899555e-12, \"annualised_returns_over_uniform_hodl\": 0.8194637309965525, \"calmar\": -0.8358049770661853, \"daily_log_sharpe\": -0.6924636110568431, \"daily_returns\": 0.03101604248344162, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": 0.0050192670727422575, \"return\": -0.2342246026737408, \"returns_over_hodl\": 3.5134117837287704e-12, \"returns_over_uniform_hodl\": 0.27259156487083236, \"sharpe\": -0.20210854628662175, \"sterling\": -1.2473843299541032, \"ulcer\": -0.17641031595973977}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -9.411504908740653e-10, \"optuna_trial_number\": 119, \"price_ratio\": 3.066147779516841, \"shift_exponent\": 1.5487687047091568e-05, \"step\": 119, \"test_objective\": [{\"annualised_returns\": -0.4845064587600554, \"annualised_returns_over_hodl\": 8.723910482899555e-12, \"annualised_returns_over_uniform_hodl\": 0.8194637309965525, \"calmar\": -0.8358049770661853, \"daily_log_sharpe\": -0.6924636110568431, \"daily_returns\": 0.03101604248344162, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": 0.0050192670727422575, \"return\": -0.2342246026737408, \"returns_over_hodl\": 3.5134117837287704e-12, \"returns_over_uniform_hodl\": 0.27259156487083236, \"sharpe\": -0.20210854628662175, \"sterling\": -1.2473843299541032, \"ulcer\": -0.17641031595973977}], \"train_objective\": [{\"annualised_returns\": -0.5678636725238349, \"annualised_returns_over_hodl\": -0.4557942259983434, \"annualised_returns_over_uniform_hodl\": -0.4557942259983432, \"calmar\": -0.7397709177151309, \"daily_log_sharpe\": -0.5749533401446143, \"daily_returns\": 0.008335602139416285, \"fee_revenue_over_value\": 0.0006220218428637628, \"jax_sharpe\": 0.1436884065017691, \"return\": -0.3991340676196877, \"returns_over_hodl\": -0.30884388499636717, \"returns_over_uniform_hodl\": -0.30884388499636706, \"sharpe\": 0.0727149101406832, \"sterling\": -1.5675031707108336, \"ulcer\": -0.19952069169676148}], \"train_return\": -0.3991340676196877, \"train_returns_over_hodl\": -0.30884388499636717, \"train_sharpe\": 0.14368840650176912, \"validation_return\": -0.3391427199037308, \"validation_returns_over_hodl\": -9.411504908740653e-10, \"validation_sharpe\": -2.243853199706048}, {\"centeredness_margin\": 0.8404059364208906, \"continuous_test_metrics\": [{\"annualised_returns\": -0.7891266819369576, \"annualised_returns_over_hodl\": -0.5909292726309274, \"annualised_returns_over_uniform_hodl\": -0.2557106474550034, \"calmar\": -1.330431225138308, \"daily_log_sharpe\": -1.7430710285163915, \"daily_returns\": 0.031016042496526813, \"fee_revenue_over_value\": 0.007424332448600002, \"jax_sharpe\": -1.0729283113881667, \"return\": -0.46573350039468886, \"returns_over_hodl\": -0.3023195818606643, \"returns_over_uniform_hodl\": -0.11213778457143142, \"sharpe\": -1.3162592600404373, \"sterling\": -2.1607032917530575, \"ulcer\": -0.15995634522334073}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -0.09852953950286325, \"optuna_trial_number\": 120, \"price_ratio\": 1.5176236653133064, \"shift_exponent\": 0.005227732446942338, \"step\": 120, \"test_objective\": [{\"annualised_returns\": -0.7891266819369576, \"annualised_returns_over_hodl\": -0.5909292726309274, \"annualised_returns_over_uniform_hodl\": -0.2557106474550034, \"calmar\": -1.330431225138308, \"daily_log_sharpe\": -1.7430710285163915, \"daily_returns\": 0.031016042496526813, \"fee_revenue_over_value\": 0.007424332448600002, \"jax_sharpe\": -1.0729283113881667, \"return\": -0.46573350039468886, \"returns_over_hodl\": -0.3023195818606643, \"returns_over_uniform_hodl\": -0.11213778457143142, \"sharpe\": -1.3162592600404373, \"sterling\": -2.1607032917530575, \"ulcer\": -0.15995634522334073}], \"train_objective\": [{\"annualised_returns\": -0.45815669741149156, \"annualised_returns_over_hodl\": -0.3176360441739139, \"annualised_returns_over_uniform_hodl\": -0.3176360441739138, \"calmar\": -0.5856499734165006, \"daily_log_sharpe\": -0.437198271778722, \"daily_returns\": 0.008731618992715167, \"fee_revenue_over_value\": 0.0015932523241251894, \"jax_sharpe\": 0.22239617007327467, \"return\": -0.3106670502919302, \"returns_over_hodl\": -0.20708321475834957, \"returns_over_uniform_hodl\": -0.20708321475834945, \"sharpe\": 0.17811090384957895, \"sterling\": -1.3248813657013387, \"ulcer\": -0.1852632721269545}], \"train_return\": -0.3106670502919302, \"train_returns_over_hodl\": -0.20708321475834957, \"train_sharpe\": 0.22239617007327467, \"validation_return\": -0.32436481477451484, \"validation_returns_over_hodl\": -0.09852953950286325, \"validation_sharpe\": -2.1809816035460803}, {\"centeredness_margin\": 0.32565065866664944, \"continuous_test_metrics\": [{\"annualised_returns\": -0.502412359735039, \"annualised_returns_over_hodl\": -0.03473545259097932, \"annualised_returns_over_uniform_hodl\": 0.7562638365489243, \"calmar\": -0.8666938706123655, \"daily_log_sharpe\": -0.7388427597741053, \"daily_returns\": 0.03101604249025401, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": -0.05643169205862948, \"return\": -0.24505047511214895, \"returns_over_hodl\": -0.014137138349207712, \"returns_over_uniform_hodl\": 0.2546007623514661, \"sharpe\": -0.2546233283629729, \"sterling\": -1.2934839850790645, \"ulcer\": -0.1764103160327866}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -0.053113182614760945, \"optuna_trial_number\": 121, \"price_ratio\": 8.54921742305649, \"shift_exponent\": 5.4854766738036764e-05, \"step\": 121, \"test_objective\": [{\"annualised_returns\": -0.502412359735039, \"annualised_returns_over_hodl\": -0.03473545259097932, \"annualised_returns_over_uniform_hodl\": 0.7562638365489243, \"calmar\": -0.8666938706123655, \"daily_log_sharpe\": -0.7388427597741053, \"daily_returns\": 0.03101604249025401, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": -0.05643169205862948, \"return\": -0.24505047511214895, \"returns_over_hodl\": -0.014137138349207712, \"returns_over_uniform_hodl\": 0.2546007623514661, \"sharpe\": -0.2546233283629729, \"sterling\": -1.2934839850790645, \"ulcer\": -0.1764103160327866}], \"train_objective\": [{\"annualised_returns\": -0.4379592272397661, \"annualised_returns_over_hodl\": -0.2922005989479265, \"annualised_returns_over_uniform_hodl\": -0.2922005989479266, \"calmar\": -0.5890445628686558, \"daily_log_sharpe\": -0.4321029225306085, \"daily_returns\": 0.008124641803835077, \"fee_revenue_over_value\": 0.011718433503682743, \"jax_sharpe\": 0.11177040632854557, \"return\": -0.2951792299254937, \"returns_over_hodl\": -0.18926808965725594, \"returns_over_uniform_hodl\": -0.18926808965725606, \"sharpe\": 0.13048773903024877, \"sterling\": -1.3321121367888844, \"ulcer\": -0.177715614543174}], \"train_return\": -0.2951792299254937, \"train_returns_over_hodl\": -0.18926808965725594, \"train_sharpe\": 0.11177040632854558, \"validation_return\": -0.3318370373986038, \"validation_returns_over_hodl\": -0.053113182614760945, \"validation_sharpe\": -2.179779853048558}, {\"centeredness_margin\": 0.8206895351458638, \"continuous_test_metrics\": [{\"annualised_returns\": -0.48450645977850837, \"annualised_returns_over_hodl\": 1.0069056699535395e-11, \"annualised_returns_over_uniform_hodl\": 0.8194637274018646, \"calmar\": -0.835804978697952, \"daily_log_sharpe\": -0.6924636136140918, \"daily_returns\": 0.031016042435048415, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": 0.0050192640112225765, \"return\": -0.2342246032830554, \"returns_over_hodl\": 4.055200619745847e-12, \"returns_over_uniform_hodl\": 0.27259156385825256, \"sharpe\": -0.20210854928615551, \"sterling\": -1.2473843333670263, \"ulcer\": -0.17641031585969974}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -1.2950838179648372e-09, \"optuna_trial_number\": 122, \"price_ratio\": 1.0130775300116421, \"shift_exponent\": 0.0019857241244137006, \"step\": 122, \"test_objective\": [{\"annualised_returns\": -0.48450645977850837, \"annualised_returns_over_hodl\": 1.0069056699535395e-11, \"annualised_returns_over_uniform_hodl\": 0.8194637274018646, \"calmar\": -0.835804978697952, \"daily_log_sharpe\": -0.6924636136140918, \"daily_returns\": 0.031016042435048415, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": 0.0050192640112225765, \"return\": -0.2342246032830554, \"returns_over_hodl\": 4.055200619745847e-12, \"returns_over_uniform_hodl\": 0.27259156385825256, \"sharpe\": -0.20210854928615551, \"sterling\": -1.2473843333670263, \"ulcer\": -0.17641031585969974}], \"train_objective\": [{\"annualised_returns\": -0.6639284535617631, \"annualised_returns_over_hodl\": -0.5767722720338928, \"annualised_returns_over_uniform_hodl\": -0.5767722720338944, \"calmar\": -0.8262487008486421, \"daily_log_sharpe\": -0.7364526997505948, \"daily_returns\": 0.016184061734922494, \"fee_revenue_over_value\": 1.1040316020905023e-05, \"jax_sharpe\": 0.06008582714113825, \"return\": -0.4841937683269264, \"returns_over_hodl\": -0.40668523215228447, \"returns_over_uniform_hodl\": -0.4066852321522857, \"sharpe\": -0.05517714937183524, \"sterling\": -1.7554318676013472, \"ulcer\": -0.21307100872040946}], \"train_return\": -0.4841937683269264, \"train_returns_over_hodl\": -0.40668523215228447, \"train_sharpe\": 0.06008582714113825, \"validation_return\": -0.33914271960363596, \"validation_returns_over_hodl\": -1.2950838179648372e-09, \"validation_sharpe\": -2.243853200060667}, {\"centeredness_margin\": 0.5005490464599175, \"continuous_test_metrics\": [{\"annualised_returns\": -0.4845064597928238, \"annualised_returns_over_hodl\": 1.0089040713978648e-11, \"annualised_returns_over_uniform_hodl\": 0.8194637273513374, \"calmar\": -0.8358049787208884, \"daily_log_sharpe\": -0.6924636136500332, \"daily_returns\": 0.031016042434368053, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": 0.0050192639681891265, \"return\": -0.23422460329161998, \"returns_over_hodl\": 4.063194225523148e-12, \"returns_over_uniform_hodl\": 0.2725915638440197, \"sharpe\": -0.20210854932831757, \"sterling\": -1.2473843334149988, \"ulcer\": -0.17641031585829353}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -1.3423991918060096e-09, \"optuna_trial_number\": 123, \"price_ratio\": 1.0250384106790742, \"shift_exponent\": 0.0014648835054985984, \"step\": 123, \"test_objective\": [{\"annualised_returns\": -0.4845064597928238, \"annualised_returns_over_hodl\": 1.0089040713978648e-11, \"annualised_returns_over_uniform_hodl\": 0.8194637273513374, \"calmar\": -0.8358049787208884, \"daily_log_sharpe\": -0.6924636136500332, \"daily_returns\": 0.031016042434368053, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": 0.0050192639681891265, \"return\": -0.23422460329161998, \"returns_over_hodl\": 4.063194225523148e-12, \"returns_over_uniform_hodl\": 0.2725915638440197, \"sharpe\": -0.20210854932831757, \"sterling\": -1.2473843334149988, \"ulcer\": -0.17641031585829353}], \"train_objective\": [{\"annualised_returns\": -0.6631454628214377, \"annualised_returns_over_hodl\": -0.5757862219039176, \"annualised_returns_over_uniform_hodl\": -0.5757862219039189, \"calmar\": -0.825690671222111, \"daily_log_sharpe\": -0.7347623288463658, \"daily_returns\": 0.018089405614334916, \"fee_revenue_over_value\": 8.484497141657221e-05, \"jax_sharpe\": 0.06116340372250115, \"return\": -0.48346449823611637, \"returns_over_hodl\": -0.4058463770007603, \"returns_over_uniform_hodl\": -0.4058463770007614, \"sharpe\": -0.05357095702494089, \"sterling\": -1.754068413575598, \"ulcer\": -0.21289921360935116}], \"train_return\": -0.48346449823611637, \"train_returns_over_hodl\": -0.4058463770007603, \"train_sharpe\": 0.061163403722501154, \"validation_return\": -0.3391427196274004, \"validation_returns_over_hodl\": -1.3423991918060096e-09, \"validation_sharpe\": -2.2438532003450407}, {\"centeredness_margin\": 0.7957108183631076, \"continuous_test_metrics\": [{\"annualised_returns\": -0.5194654562207262, \"annualised_returns_over_hodl\": -0.0678165584727366, \"annualised_returns_over_uniform_hodl\": 0.6960739639808597, \"calmar\": -0.896385551982383, \"daily_log_sharpe\": -0.7789217128680431, \"daily_returns\": 0.031016042488145424, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": -0.09795885016004796, \"return\": -0.25557926169352463, \"returns_over_hodl\": -0.027886321964767147, \"returns_over_uniform_hodl\": 0.23710366719985054, \"sharpe\": -0.29790445797774817, \"sterling\": -1.338029739016544, \"ulcer\": -0.17622692796486117}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -0.05395525673863699, \"optuna_trial_number\": 124, \"price_ratio\": 6.351219538768638, \"shift_exponent\": 0.0007222987258503782, \"step\": 124, \"test_objective\": [{\"annualised_returns\": -0.5194654562207262, \"annualised_returns_over_hodl\": -0.0678165584727366, \"annualised_returns_over_uniform_hodl\": 0.6960739639808597, \"calmar\": -0.896385551982383, \"daily_log_sharpe\": -0.7789217128680431, \"daily_returns\": 0.031016042488145424, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": -0.09795885016004796, \"return\": -0.25557926169352463, \"returns_over_hodl\": -0.027886321964767147, \"returns_over_uniform_hodl\": 0.23710366719985054, \"sharpe\": -0.29790445797774817, \"sterling\": -1.338029739016544, \"ulcer\": -0.17622692796486117}], \"train_objective\": [{\"annualised_returns\": -0.45408225056300033, \"annualised_returns_over_hodl\": -0.3125049377155382, \"annualised_returns_over_uniform_hodl\": -0.3125049377155382, \"calmar\": -0.6065697630236752, \"daily_log_sharpe\": -0.4521171646038296, \"daily_returns\": 0.008123383818369129, \"fee_revenue_over_value\": 0.0005150066097864976, \"jax_sharpe\": 0.10455930617218721, \"return\": -0.30752466264968137, \"returns_over_hodl\": -0.20346863067625942, \"returns_over_uniform_hodl\": -0.20346863067625942, \"sharpe\": 0.11826517639016518, \"sterling\": -1.3684742259031653, \"ulcer\": -0.1795342745692171}], \"train_return\": -0.30752466264968137, \"train_returns_over_hodl\": -0.20346863067625942, \"train_sharpe\": 0.10455930617218723, \"validation_return\": -0.33243046902041407, \"validation_returns_over_hodl\": -0.05395525673863699, \"validation_sharpe\": -2.185061363375249}, {\"centeredness_margin\": 0.8263112241017978, \"continuous_test_metrics\": [{\"annualised_returns\": -0.47765947323313585, \"annualised_returns_over_hodl\": 0.013282387482462577, \"annualised_returns_over_uniform_hodl\": 0.8436305552848307, \"calmar\": -0.8239934641232178, \"daily_log_sharpe\": -0.6802308452885296, \"daily_returns\": 0.031016042510244795, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": 0.007829762634397337, \"return\": -0.23014435931840393, \"returns_over_hodl\": 0.0053282503365204015, \"returns_over_uniform_hodl\": 0.27937225186437065, \"sharpe\": -0.1896030539306088, \"sterling\": -1.229756417930961, \"ulcer\": -0.17641031601516366}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -6.164396770813596e-10, \"optuna_trial_number\": 125, \"price_ratio\": 5.515106191756737, \"shift_exponent\": 6.467001933594498e-05, \"step\": 125, \"test_objective\": [{\"annualised_returns\": -0.47765947323313585, \"annualised_returns_over_hodl\": 0.013282387482462577, \"annualised_returns_over_uniform_hodl\": 0.8436305552848307, \"calmar\": -0.8239934641232178, \"daily_log_sharpe\": -0.6802308452885296, \"daily_returns\": 0.031016042510244795, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": 0.007829762634397337, \"return\": -0.23014435931840393, \"returns_over_hodl\": 0.0053282503365204015, \"returns_over_uniform_hodl\": 0.27937225186437065, \"sharpe\": -0.1896030539306088, \"sterling\": -1.229756417930961, \"ulcer\": -0.17641031601516366}], \"train_objective\": [{\"annualised_returns\": -0.4955468305307793, \"annualised_returns_over_hodl\": -0.3647228661799373, \"annualised_returns_over_uniform_hodl\": -0.3647228661799373, \"calmar\": -0.6593123395326052, \"daily_log_sharpe\": -0.4935482283168837, \"daily_returns\": 0.008140348345167416, \"fee_revenue_over_value\": 0.0003163208322489124, \"jax_sharpe\": 0.09280359912293328, \"return\": -0.3399509871192382, \"returns_over_hodl\": -0.240767554173957, \"returns_over_uniform_hodl\": -0.240767554173957, \"sharpe\": 0.09979935322203017, \"sterling\": -1.45701933307174, \"ulcer\": -0.1847281532718899}], \"train_return\": -0.3399509871192382, \"train_returns_over_hodl\": -0.240767554173957, \"train_sharpe\": 0.09280359912293328, \"validation_return\": -0.3391427199849305, \"validation_returns_over_hodl\": -6.164396770813596e-10, \"validation_sharpe\": -2.2438531986528902}, {\"centeredness_margin\": 0.7194624360272096, \"continuous_test_metrics\": [{\"annualised_returns\": -0.4773208845935808, \"annualised_returns_over_hodl\": 0.013939211912112226, \"annualised_returns_over_uniform_hodl\": 0.844825623118109, \"calmar\": -0.8234093758311385, \"daily_log_sharpe\": -0.6806837898870852, \"daily_returns\": 0.03101604250500292, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": 0.0055604753022985304, \"return\": -0.22994341935168905, \"returns_over_hodl\": 0.005590651079347753, \"returns_over_uniform_hodl\": 0.2797061807259924, \"sharpe\": -0.190455833070749, \"sterling\": -1.2288847044148343, \"ulcer\": -0.17641031600430876}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -6.781880612649616e-10, \"optuna_trial_number\": 126, \"price_ratio\": 4.222463811849046, \"shift_exponent\": 0.0005705535877203234, \"step\": 126, \"test_objective\": [{\"annualised_returns\": -0.4773208845935808, \"annualised_returns_over_hodl\": 0.013939211912112226, \"annualised_returns_over_uniform_hodl\": 0.844825623118109, \"calmar\": -0.8234093758311385, \"daily_log_sharpe\": -0.6806837898870852, \"daily_returns\": 0.03101604250500292, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": 0.0055604753022985304, \"return\": -0.22994341935168905, \"returns_over_hodl\": 0.005590651079347753, \"returns_over_uniform_hodl\": 0.2797061807259924, \"sharpe\": -0.190455833070749, \"sterling\": -1.2288847044148343, \"ulcer\": -0.17641031600430876}], \"train_objective\": [{\"annualised_returns\": -0.5109666059601617, \"annualised_returns_over_hodl\": -0.38414157802832194, \"annualised_returns_over_uniform_hodl\": -0.38414157802832194, \"calmar\": -0.6762141541757204, \"daily_log_sharpe\": -0.5069095148628115, \"daily_returns\": 0.008250337232750184, \"fee_revenue_over_value\": 0.000839916844622923, \"jax_sharpe\": 0.10487914554092818, \"return\": -0.3522748358882819, \"returns_over_hodl\": -0.2549432678866016, \"returns_over_uniform_hodl\": -0.2549432678866016, \"sharpe\": 0.10212985891918139, \"sterling\": -1.479855850397778, \"ulcer\": -0.18760021505563376}], \"train_return\": -0.3522748358882819, \"train_returns_over_hodl\": -0.2549432678866016, \"train_sharpe\": 0.1048791455409282, \"validation_return\": -0.33914271996120493, \"validation_returns_over_hodl\": -6.781880612649616e-10, \"validation_sharpe\": -2.243853198781456}, {\"centeredness_margin\": 0.8281143609000017, \"continuous_test_metrics\": [{\"annualised_returns\": -0.481609424136627, \"annualised_returns_over_hodl\": 0.005619923702639973, \"annualised_returns_over_uniform_hodl\": 0.829688979235532, \"calmar\": -0.830807396391304, \"daily_log_sharpe\": -0.6865952949363407, \"daily_returns\": 0.031016042495575023, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": 0.008040443199489706, \"return\": -0.23249428047252496, \"returns_over_hodl\": 0.002259568610749163, \"returns_over_uniform_hodl\": 0.2754670730752802, \"sharpe\": -0.19592065179585533, \"sterling\": -1.2399257670163066, \"ulcer\": -0.17641031598482634}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -7.865605944346044e-10, \"optuna_trial_number\": 127, \"price_ratio\": 3.560070426716419, \"shift_exponent\": 0.0004466687724033544, \"step\": 127, \"test_objective\": [{\"annualised_returns\": -0.481609424136627, \"annualised_returns_over_hodl\": 0.005619923702639973, \"annualised_returns_over_uniform_hodl\": 0.829688979235532, \"calmar\": -0.830807396391304, \"daily_log_sharpe\": -0.6865952949363407, \"daily_returns\": 0.031016042495575023, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": 0.008040443199489706, \"return\": -0.23249428047252496, \"returns_over_hodl\": 0.002259568610749163, \"returns_over_uniform_hodl\": 0.2754670730752802, \"sharpe\": -0.19592065179585533, \"sterling\": -1.2399257670163066, \"ulcer\": -0.17641031598482634}], \"train_objective\": [{\"annualised_returns\": -0.5377159276234913, \"annualised_returns_over_hodl\": -0.4178280199547171, \"annualised_returns_over_uniform_hodl\": -0.4178280199547173, \"calmar\": -0.7072707541876877, \"daily_log_sharpe\": -0.5384282481931878, \"daily_returns\": 0.008273464553806597, \"fee_revenue_over_value\": 0.0003246410767806104, \"jax_sharpe\": 0.10858290740996979, \"return\": -0.37402203528863875, \"returns_over_hodl\": -0.2799583486887637, \"returns_over_uniform_hodl\": -0.2799583486887638, \"sharpe\": 0.08878492337765875, \"sterling\": -1.5237080949758186, \"ulcer\": -0.19272777960178575}], \"train_return\": -0.37402203528863875, \"train_returns_over_hodl\": -0.2799583486887637, \"train_sharpe\": 0.10858290740996979, \"validation_return\": -0.33914271993546274, \"validation_returns_over_hodl\": -7.865605944346044e-10, \"validation_sharpe\": -2.243853199180213}, {\"centeredness_margin\": 0.7765835409120687, \"continuous_test_metrics\": [{\"annualised_returns\": -0.48092473140414815, \"annualised_returns_over_hodl\": 0.006948150508298445, \"annualised_returns_over_uniform_hodl\": 0.832105641121587, \"calmar\": -0.8296262551759356, \"daily_log_sharpe\": -0.6893761739276962, \"daily_returns\": 0.031016042514083804, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": -0.005593090456931449, \"return\": -0.23208617591376446, \"returns_over_hodl\": 0.0027924981436588947, \"returns_over_uniform_hodl\": 0.276145275092317, \"sharpe\": -0.2003131638201112, \"sterling\": -1.238162990538727, \"ulcer\": -0.17641031602309445}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": 5.54398211001228e-05, \"optuna_trial_number\": 128, \"price_ratio\": 5.832161383230471, \"shift_exponent\": 0.00018668202918720232, \"step\": 128, \"test_objective\": [{\"annualised_returns\": -0.48092473140414815, \"annualised_returns_over_hodl\": 0.006948150508298445, \"annualised_returns_over_uniform_hodl\": 0.832105641121587, \"calmar\": -0.8296262551759356, \"daily_log_sharpe\": -0.6893761739276962, \"daily_returns\": 0.031016042514083804, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": -0.005593090456931449, \"return\": -0.23208617591376446, \"returns_over_hodl\": 0.0027924981436588947, \"returns_over_uniform_hodl\": 0.276145275092317, \"sharpe\": -0.2003131638201112, \"sterling\": -1.238162990538727, \"ulcer\": -0.17641031602309445}], \"train_objective\": [{\"annualised_returns\": -0.4834390159849604, \"annualised_returns_over_hodl\": -0.34947503310638206, \"annualised_returns_over_uniform_hodl\": -0.34947503310638206, \"calmar\": -0.6441730320068102, \"daily_log_sharpe\": -0.4800425169542864, \"daily_returns\": 0.008194770771106363, \"fee_revenue_over_value\": 0.0005716936200709327, \"jax_sharpe\": 0.14710763184321385, \"return\": -0.33037757570765236, \"returns_over_hodl\": -0.22975557715547124, \"returns_over_uniform_hodl\": -0.22975557715547124, \"sharpe\": 0.10665044270062539, \"sterling\": -1.4320102618768882, \"ulcer\": -0.18315162782200137}], \"train_return\": -0.33037757570765236, \"train_returns_over_hodl\": -0.22975557715547124, \"train_sharpe\": 0.14710763184321385, \"validation_return\": -0.3391060817022571, \"validation_returns_over_hodl\": 5.54398211001228e-05, \"validation_sharpe\": -2.2435195108587336}, {\"centeredness_margin\": 0.29467663503240554, \"continuous_test_metrics\": [{\"annualised_returns\": -0.47741381778958814, \"annualised_returns_over_hodl\": 0.013758931685958986, \"annualised_returns_over_uniform_hodl\": 0.8444976101247075, \"calmar\": -0.8235696918842714, \"daily_log_sharpe\": -0.679425548587243, \"daily_returns\": 0.031016042509509477, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": 0.008630632368619797, \"return\": -0.22999856405977248, \"returns_over_hodl\": 0.005518639409367587, \"returns_over_uniform_hodl\": 0.2796145393771068, \"sharpe\": -0.18864108917295141, \"sterling\": -1.2291239655617667, \"ulcer\": -0.17641031601363175}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -6.402373076141998e-10, \"optuna_trial_number\": 129, \"price_ratio\": 5.1015912679236, \"shift_exponent\": 0.00014117259549305033, \"step\": 129, \"test_objective\": [{\"annualised_returns\": -0.47741381778958814, \"annualised_returns_over_hodl\": 0.013758931685958986, \"annualised_returns_over_uniform_hodl\": 0.8444976101247075, \"calmar\": -0.8235696918842714, \"daily_log_sharpe\": -0.679425548587243, \"daily_returns\": 0.031016042509509477, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": 0.008630632368619797, \"return\": -0.22999856405977248, \"returns_over_hodl\": 0.005518639409367587, \"returns_over_uniform_hodl\": 0.2796145393771068, \"sharpe\": -0.18864108917295141, \"sterling\": -1.2291239655617667, \"ulcer\": -0.17641031601363175}], \"train_objective\": [{\"annualised_returns\": -0.4902111744710451, \"annualised_returns_over_hodl\": -0.3580034708151624, \"annualised_returns_over_uniform_hodl\": -0.3580034708151624, \"calmar\": -0.6539923311787322, \"daily_log_sharpe\": -0.480801813253417, \"daily_returns\": 0.008201312272764754, \"fee_revenue_over_value\": 0.011707340783533927, \"jax_sharpe\": 0.11130763816561814, \"return\": -0.33572118741396595, \"returns_over_hodl\": -0.2359021561309056, \"returns_over_uniform_hodl\": -0.2359021561309056, \"sharpe\": 0.11683693508708126, \"sterling\": -1.4392729067931849, \"ulcer\": -0.18472665693135362}], \"train_return\": -0.33572118741396595, \"train_returns_over_hodl\": -0.2359021561309056, \"train_sharpe\": 0.11130763816561814, \"validation_return\": -0.33914271999252377, \"validation_returns_over_hodl\": -6.402373076141998e-10, \"validation_sharpe\": -2.243853198782208}, {\"centeredness_margin\": 0.6903398314711519, \"continuous_test_metrics\": [{\"annualised_returns\": -0.47454375474199595, \"annualised_returns_over_hodl\": 0.019326534706254783, \"annualised_returns_over_uniform_hodl\": 0.8546276606549368, \"calmar\": -0.8186186392472315, \"daily_log_sharpe\": -0.6748944568068009, \"daily_returns\": 0.03101604249308534, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": 0.007965894842407143, \"return\": -0.22829822050657333, \"returns_over_hodl\": 0.007739060393066266, \"returns_over_uniform_hodl\": 0.2824402280460545, \"sharpe\": -0.18417250719600053, \"sterling\": -1.2217348432528188, \"ulcer\": -0.1764103159796775}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -7.722643635688087e-10, \"optuna_trial_number\": 130, \"price_ratio\": 3.000904645235099, \"shift_exponent\": 0.0011206066701356108, \"step\": 130, \"test_objective\": [{\"annualised_returns\": -0.47454375474199595, \"annualised_returns_over_hodl\": 0.019326534706254783, \"annualised_returns_over_uniform_hodl\": 0.8546276606549368, \"calmar\": -0.8186186392472315, \"daily_log_sharpe\": -0.6748944568068009, \"daily_returns\": 0.03101604249308534, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": 0.007965894842407143, \"return\": -0.22829822050657333, \"returns_over_hodl\": 0.007739060393066266, \"returns_over_uniform_hodl\": 0.2824402280460545, \"sharpe\": -0.18417250719600053, \"sterling\": -1.2217348432528188, \"ulcer\": -0.1764103159796775}], \"train_objective\": [{\"annualised_returns\": -0.5440759831429748, \"annualised_returns_over_hodl\": -0.42583748066561966, \"annualised_returns_over_uniform_hodl\": -0.42583748066561966, \"calmar\": -0.7112894383055404, \"daily_log_sharpe\": -0.5450894427974243, \"daily_returns\": 0.008289575589822755, \"fee_revenue_over_value\": 0.0007297859736946698, \"jax_sharpe\": 0.15279331277208738, \"return\": -0.3792648707615539, \"returns_over_hodl\": -0.2859890081117542, \"returns_over_uniform_hodl\": -0.2859890081117542, \"sharpe\": 0.08784832683129708, \"sterling\": -1.533346200156535, \"ulcer\": -0.19376735765573386}], \"train_return\": -0.3792648707615539, \"train_returns_over_hodl\": -0.2859890081117542, \"train_sharpe\": 0.1527933127720874, \"validation_return\": -0.3391427198985344, \"validation_returns_over_hodl\": -7.722643635688087e-10, \"validation_sharpe\": -2.243853198982077}, {\"centeredness_margin\": 0.849322015292955, \"continuous_test_metrics\": [{\"annualised_returns\": -0.47583972985180645, \"annualised_returns_over_hodl\": 0.01681248720802997, \"annualised_returns_over_uniform_hodl\": 0.8500534429006221, \"calmar\": -0.820854283839652, \"daily_log_sharpe\": -0.6773596844488344, \"daily_returns\": 0.03101604249954889, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": 0.006975039343945996, \"return\": -0.22906532089005283, \"returns_over_hodl\": 0.0067373300318975815, \"returns_over_uniform_hodl\": 0.2811654345741912, \"sharpe\": -0.1867580182459073, \"sterling\": -1.2250713966365134, \"ulcer\": -0.17641031599303952}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -7.240726906942996e-10, \"optuna_trial_number\": 131, \"price_ratio\": 3.473907270608545, \"shift_exponent\": 0.0008526238527075407, \"step\": 131, \"test_objective\": [{\"annualised_returns\": -0.47583972985180645, \"annualised_returns_over_hodl\": 0.01681248720802997, \"annualised_returns_over_uniform_hodl\": 0.8500534429006221, \"calmar\": -0.820854283839652, \"daily_log_sharpe\": -0.6773596844488344, \"daily_returns\": 0.03101604249954889, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": 0.006975039343945996, \"return\": -0.22906532089005283, \"returns_over_hodl\": 0.0067373300318975815, \"returns_over_uniform_hodl\": 0.2811654345741912, \"sharpe\": -0.1867580182459073, \"sterling\": -1.2250713966365134, \"ulcer\": -0.17641031599303952}], \"train_objective\": [{\"annualised_returns\": -0.5263304625709896, \"annualised_returns_over_hodl\": -0.40348986917379925, \"annualised_returns_over_uniform_hodl\": -0.40348986917379914, \"calmar\": -0.6929342533191243, \"daily_log_sharpe\": -0.522455226704573, \"daily_returns\": 0.008286598224444483, \"fee_revenue_over_value\": 0.0003095759978828396, \"jax_sharpe\": 0.11508372609396118, \"return\": -0.3647067968442025, \"returns_over_hodl\": -0.26924333945519996, \"returns_over_uniform_hodl\": -0.26924333945519985, \"sharpe\": 0.10012598197097594, \"sterling\": -1.5021324969297503, \"ulcer\": -0.19075599885774466}], \"train_return\": -0.3647067968442025, \"train_returns_over_hodl\": -0.26924333945519996, \"train_sharpe\": 0.11508372609396117, \"validation_return\": -0.3391427199380006, \"validation_returns_over_hodl\": -7.240726906942996e-10, \"validation_sharpe\": -2.2438531989263}, {\"centeredness_margin\": 0.9241143430341172, \"continuous_test_metrics\": [{\"annualised_returns\": -0.5383341551437311, \"annualised_returns_over_hodl\": -0.10441973106049374, \"annualised_returns_over_uniform_hodl\": 0.6294758194940777, \"calmar\": -0.9314791486934547, \"daily_log_sharpe\": -0.8246977333870169, \"daily_returns\": 0.031016042501587328, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": -0.1443862266182377, \"return\": -0.2674924811648378, \"returns_over_hodl\": -0.04344338948803472, \"returns_over_uniform_hodl\": 0.21730587444941452, \"sharpe\": -0.3472859480563747, \"sterling\": -1.3903351421273458, \"ulcer\": -0.1751966289778025}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -0.06454444770847567, \"optuna_trial_number\": 132, \"price_ratio\": 7.312855066751981, \"shift_exponent\": 0.0007589342656133298, \"step\": 132, \"test_objective\": [{\"annualised_returns\": -0.5383341551437311, \"annualised_returns_over_hodl\": -0.10441973106049374, \"annualised_returns_over_uniform_hodl\": 0.6294758194940777, \"calmar\": -0.9314791486934547, \"daily_log_sharpe\": -0.8246977333870169, \"daily_returns\": 0.031016042501587328, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": -0.1443862266182377, \"return\": -0.2674924811648378, \"returns_over_hodl\": -0.04344338948803472, \"returns_over_uniform_hodl\": 0.21730587444941452, \"sharpe\": -0.3472859480563747, \"sterling\": -1.3903351421273458, \"ulcer\": -0.1751966289778025}], \"train_objective\": [{\"annualised_returns\": -0.43965261198490413, \"annualised_returns_over_hodl\": -0.2943331430024676, \"annualised_returns_over_uniform_hodl\": -0.2943331430024676, \"calmar\": -0.5885651624240321, \"daily_log_sharpe\": -0.4395486986397184, \"daily_returns\": 0.008159702500135924, \"fee_revenue_over_value\": 0.0002386448069793929, \"jax_sharpe\": 0.15026869339903065, \"return\": -0.296469258615374, \"returns_over_hodl\": -0.19075196679105988, \"returns_over_uniform_hodl\": -0.19075196679105988, \"sharpe\": 0.12121363534269722, \"sterling\": -1.3378272081411022, \"ulcer\": -0.17766956667724312}], \"train_return\": -0.296469258615374, \"train_returns_over_hodl\": -0.19075196679105988, \"train_sharpe\": 0.15026869339903062, \"validation_return\": -0.32252565653354526, \"validation_returns_over_hodl\": -0.06454444770847567, \"validation_sharpe\": -2.119528845440429}, {\"centeredness_margin\": 0.8356227422982425, \"continuous_test_metrics\": [{\"annualised_returns\": -0.4776336785302622, \"annualised_returns_over_hodl\": 0.013332426281752685, \"annualised_returns_over_uniform_hodl\": 0.8437215991536213, \"calmar\": -0.8239489665210205, \"daily_log_sharpe\": -0.680618194032587, \"daily_returns\": 0.031016042511417055, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": 0.005185856930833958, \"return\": -0.23012904836914538, \"returns_over_hodl\": 0.00534824436409842, \"returns_over_uniform_hodl\": 0.27939769611988585, \"sharpe\": -0.19017293549842873, \"sterling\": -1.2296900081430657, \"ulcer\": -0.17641031601757895}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -6.168152655305903e-10, \"optuna_trial_number\": 133, \"price_ratio\": 5.750183626927248, \"shift_exponent\": 4.046941739876202e-05, \"step\": 133, \"test_objective\": [{\"annualised_returns\": -0.4776336785302622, \"annualised_returns_over_hodl\": 0.013332426281752685, \"annualised_returns_over_uniform_hodl\": 0.8437215991536213, \"calmar\": -0.8239489665210205, \"daily_log_sharpe\": -0.680618194032587, \"daily_returns\": 0.031016042511417055, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": 0.005185856930833958, \"return\": -0.23012904836914538, \"returns_over_hodl\": 0.00534824436409842, \"returns_over_uniform_hodl\": 0.27939769611988585, \"sharpe\": -0.19017293549842873, \"sterling\": -1.2296900081430657, \"ulcer\": -0.17641031601757895}], \"train_objective\": [{\"annualised_returns\": -0.4919643539334967, \"annualised_returns_over_hodl\": -0.3602113166398806, \"annualised_returns_over_uniform_hodl\": -0.3602113166398805, \"calmar\": -0.6550104665014238, \"daily_log_sharpe\": -0.4900844996834354, \"daily_returns\": 0.00817763393188177, \"fee_revenue_over_value\": 0.00030129835354900087, \"jax_sharpe\": 0.0911762545098186, \"return\": -0.33710907968290194, \"returns_over_hodl\": -0.23749860248765176, \"returns_over_uniform_hodl\": -0.23749860248765164, \"sharpe\": 0.10030421303116917, \"sterling\": -1.4508418906150815, \"ulcer\": -0.18413433587016187}], \"train_return\": -0.33710907968290194, \"train_returns_over_hodl\": -0.23749860248765176, \"train_sharpe\": 0.0911762545098186, \"validation_return\": -0.33914271998715706, \"validation_returns_over_hodl\": -6.168152655305903e-10, \"validation_sharpe\": -2.2438531985953105}, {\"centeredness_margin\": 0.8957807790190497, \"continuous_test_metrics\": [{\"annualised_returns\": -0.5442947341088219, \"annualised_returns_over_hodl\": -0.11598258990110166, \"annualised_returns_over_uniform_hodl\": 0.6084376175087767, \"calmar\": -0.9421548866460546, \"daily_log_sharpe\": -0.8401294718202024, \"daily_returns\": 0.031016042509831386, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": -0.15989880281729443, \"return\": -0.2713161302610618, \"returns_over_hodl\": -0.04843656263960283, \"returns_over_uniform_hodl\": 0.21095160451090456, \"sharpe\": -0.36438589452072484, \"sterling\": -1.407878617158258, \"ulcer\": -0.1746149899624243}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -0.06656442180776592, \"optuna_trial_number\": 134, \"price_ratio\": 8.802743013700296, \"shift_exponent\": 0.0005816434488213123, \"step\": 134, \"test_objective\": [{\"annualised_returns\": -0.5442947341088219, \"annualised_returns_over_hodl\": -0.11598258990110166, \"annualised_returns_over_uniform_hodl\": 0.6084376175087767, \"calmar\": -0.9421548866460546, \"daily_log_sharpe\": -0.8401294718202024, \"daily_returns\": 0.031016042509831386, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": -0.15989880281729443, \"return\": -0.2713161302610618, \"returns_over_hodl\": -0.04843656263960283, \"returns_over_uniform_hodl\": 0.21095160451090456, \"sharpe\": -0.36438589452072484, \"sterling\": -1.407878617158258, \"ulcer\": -0.1746149899624243}], \"train_objective\": [{\"annualised_returns\": -0.43175050535577053, \"annualised_returns_over_hodl\": -0.28438171846135785, \"annualised_returns_over_uniform_hodl\": -0.28438171846135807, \"calmar\": -0.5791755548570006, \"daily_log_sharpe\": -0.433308962692242, \"daily_returns\": 0.00813188297550027, \"fee_revenue_over_value\": 0.00024917391313411167, \"jax_sharpe\": 0.1492375153757104, \"return\": -0.2904624026734993, \"returns_over_hodl\": -0.18384248001132864, \"returns_over_uniform_hodl\": -0.18384248001132875, \"sharpe\": 0.12089809172305277, \"sterling\": -1.322335012401329, \"ulcer\": -0.17650200283264386}], \"train_return\": -0.2904624026734993, \"train_returns_over_hodl\": -0.18384248001132864, \"train_sharpe\": 0.1492375153757104, \"validation_return\": -0.31518437926901743, \"validation_returns_over_hodl\": -0.0665644218077659, \"validation_sharpe\": -2.0878215866283245}, {\"centeredness_margin\": 0.3021474529275424, \"continuous_test_metrics\": [{\"annualised_returns\": -0.4819359823484539, \"annualised_returns_over_hodl\": 0.004986436494665236, \"annualised_returns_over_uniform_hodl\": 0.8285363734801836, \"calmar\": -0.8313707314341441, \"daily_log_sharpe\": -0.6924266281175144, \"daily_returns\": 0.031016042512227067, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": -0.01034174650176308, \"return\": -0.23268903576600497, \"returns_over_hodl\": 0.0020052440341320477, \"returns_over_uniform_hodl\": 0.27514342211370324, \"sharpe\": -0.20392264166536267, \"sterling\": -1.240766507433536, \"ulcer\": -0.17641031601926235}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -6.993902124108331e-10, \"optuna_trial_number\": 135, \"price_ratio\": 4.995128334906171, \"shift_exponent\": 0.0005159972444848025, \"step\": 135, \"test_objective\": [{\"annualised_returns\": -0.4819359823484539, \"annualised_returns_over_hodl\": 0.004986436494665236, \"annualised_returns_over_uniform_hodl\": 0.8285363734801836, \"calmar\": -0.8313707314341441, \"daily_log_sharpe\": -0.6924266281175144, \"daily_returns\": 0.031016042512227067, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": -0.01034174650176308, \"return\": -0.23268903576600497, \"returns_over_hodl\": 0.0020052440341320477, \"returns_over_uniform_hodl\": 0.27514342211370324, \"sharpe\": -0.20392264166536267, \"sterling\": -1.240766507433536, \"ulcer\": -0.17641031601926235}], \"train_objective\": [{\"annualised_returns\": -0.4815345731112862, \"annualised_returns_over_hodl\": -0.347076695493428, \"annualised_returns_over_uniform_hodl\": -0.34707669549342823, \"calmar\": -0.6422654634955265, \"daily_log_sharpe\": -0.46922176063268267, \"daily_returns\": 0.008186438549255283, \"fee_revenue_over_value\": 0.012126490691064497, \"jax_sharpe\": 0.1728314951643011, \"return\": -0.3288798311003771, \"returns_over_hodl\": -0.22803277130437016, \"returns_over_uniform_hodl\": -0.22803277130437027, \"sharpe\": 0.1256321740278957, \"sterling\": -1.4194080051312563, \"ulcer\": -0.18376703673361097}], \"train_return\": -0.3288798311003771, \"train_returns_over_hodl\": -0.22803277130437016, \"train_sharpe\": 0.1728314951643011, \"validation_return\": -0.33914271999225065, \"validation_returns_over_hodl\": -6.993902124108331e-10, \"validation_sharpe\": -2.2438531985889827}, {\"centeredness_margin\": 0.246023330914683, \"continuous_test_metrics\": [{\"annualised_returns\": -0.4987174838923204, \"annualised_returns_over_hodl\": -0.02756780606399445, \"annualised_returns_over_uniform_hodl\": 0.7693051106843698, \"calmar\": -0.8603199588225592, \"daily_log_sharpe\": -0.7306127960899204, \"daily_returns\": 0.031016042524667355, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": -0.0487670048459865, \"return\": -0.24279773923002956, \"returns_over_hodl\": -0.011195367593632488, \"returns_over_uniform_hodl\": 0.25834443535464247, \"sharpe\": -0.24579020685182087, \"sterling\": -1.2839713379459479, \"ulcer\": -0.17641031604496393}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -0.04387449608221394, \"optuna_trial_number\": 136, \"price_ratio\": 7.382159039078662, \"shift_exponent\": 0.00025166068765402504, \"step\": 136, \"test_objective\": [{\"annualised_returns\": -0.4987174838923204, \"annualised_returns_over_hodl\": -0.02756780606399445, \"annualised_returns_over_uniform_hodl\": 0.7693051106843698, \"calmar\": -0.8603199588225592, \"daily_log_sharpe\": -0.7306127960899204, \"daily_returns\": 0.031016042524667355, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": -0.0487670048459865, \"return\": -0.24279773923002956, \"returns_over_hodl\": -0.011195367593632488, \"returns_over_uniform_hodl\": 0.25834443535464247, \"sharpe\": -0.24579020685182087, \"sterling\": -1.2839713379459479, \"ulcer\": -0.17641031604496393}], \"train_objective\": [{\"annualised_returns\": -0.4426047458343366, \"annualised_returns_over_hodl\": -0.29805087785681317, \"annualised_returns_over_uniform_hodl\": -0.29805087785681295, \"calmar\": -0.5946183773459004, \"daily_log_sharpe\": -0.4328888801414386, \"daily_returns\": 0.008108126909419018, \"fee_revenue_over_value\": 0.014508711944823451, \"jax_sharpe\": 0.11955411806748271, \"return\": -0.29872187703995967, \"returns_over_hodl\": -0.19334307890945657, \"returns_over_uniform_hodl\": -0.19334307890945646, \"sharpe\": 0.13624832990437233, \"sterling\": -1.337724686668292, \"ulcer\": -0.17886940138104096}], \"train_return\": -0.29872187703995967, \"train_returns_over_hodl\": -0.19334307890945657, \"train_sharpe\": 0.11955411806748271, \"validation_return\": -0.3352498012256985, \"validation_returns_over_hodl\": -0.04387449608221394, \"validation_sharpe\": -2.2094680349786255}, {\"centeredness_margin\": 0.9398023079105143, \"continuous_test_metrics\": [{\"annualised_returns\": -0.5008500388621062, \"annualised_returns_over_hodl\": -0.03170472417507597, \"annualised_returns_over_uniform_hodl\": 0.76177813680115, \"calmar\": -0.8639987609440992, \"daily_log_sharpe\": -0.735240687064875, \"daily_returns\": 0.031016042486804493, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": -0.05286880782987407, \"return\": -0.24409672574431174, \"returns_over_hodl\": -0.012891669504612513, \"returns_over_uniform_hodl\": 0.25618573544507606, \"sharpe\": -0.2507307819615402, \"sterling\": -1.2894617100069834, \"ulcer\": -0.17641031602828328}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -0.051535094753967114, \"optuna_trial_number\": 137, \"price_ratio\": 8.4391159187826, \"shift_exponent\": 5.5458036876937745e-05, \"step\": 137, \"test_objective\": [{\"annualised_returns\": -0.5008500388621062, \"annualised_returns_over_hodl\": -0.03170472417507597, \"annualised_returns_over_uniform_hodl\": 0.76177813680115, \"calmar\": -0.8639987609440992, \"daily_log_sharpe\": -0.735240687064875, \"daily_returns\": 0.031016042486804493, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": -0.05286880782987407, \"return\": -0.24409672574431174, \"returns_over_hodl\": -0.012891669504612513, \"returns_over_uniform_hodl\": 0.25618573544507606, \"sharpe\": -0.2507307819615402, \"sterling\": -1.2894617100069834, \"ulcer\": -0.17641031602828328}], \"train_objective\": [{\"annualised_returns\": -0.4529225996083093, \"annualised_returns_over_hodl\": -0.31104454499860257, \"annualised_returns_over_uniform_hodl\": -0.3110445449986027, \"calmar\": -0.6065698418632368, \"daily_log_sharpe\": -0.45448944910281897, \"daily_returns\": 0.008138193984779137, \"fee_revenue_over_value\": 0.00022189344158850564, \"jax_sharpe\": 0.09288828884725227, \"return\": -0.3066319764226709, \"returns_over_hodl\": -0.2024418033736468, \"returns_over_uniform_hodl\": -0.20244180337364692, \"sharpe\": 0.10988360280107948, \"sterling\": -1.371504441492448, \"ulcer\": -0.17886134985939572}], \"train_return\": -0.3066319764226709, \"train_returns_over_hodl\": -0.2024418033736468, \"train_sharpe\": 0.09288828884725227, \"validation_return\": -0.33260398959791293, \"validation_returns_over_hodl\": -0.051535094753967114, \"validation_sharpe\": -2.1858317716951587}, {\"centeredness_margin\": 0.8652708746398254, \"continuous_test_metrics\": [{\"annualised_returns\": -0.5277503135100545, \"annualised_returns_over_hodl\": -0.08388825842617254, \"annualised_returns_over_uniform_hodl\": 0.666832089643973, \"calmar\": -0.9124241489215168, \"daily_log_sharpe\": -0.7982142777204259, \"daily_returns\": 0.03101604250954912, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": -0.11611442749194723, \"return\": -0.26077505967257486, \"returns_over_hodl\": -0.034671337711007055, \"returns_over_uniform_hodl\": 0.2284691136427648, \"sharpe\": -0.3186742709245205, \"sterling\": -1.3619697223005156, \"ulcer\": -0.17547671753994198}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -0.064708035934407, \"optuna_trial_number\": 138, \"price_ratio\": 11.240232877571664, \"shift_exponent\": 5.4495450218199655e-05, \"step\": 138, \"test_objective\": [{\"annualised_returns\": -0.5277503135100545, \"annualised_returns_over_hodl\": -0.08388825842617254, \"annualised_returns_over_uniform_hodl\": 0.666832089643973, \"calmar\": -0.9124241489215168, \"daily_log_sharpe\": -0.7982142777204259, \"daily_returns\": 0.03101604250954912, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": -0.11611442749194723, \"return\": -0.26077505967257486, \"returns_over_hodl\": -0.034671337711007055, \"returns_over_uniform_hodl\": 0.2284691136427648, \"sharpe\": -0.3186742709245205, \"sterling\": -1.3619697223005156, \"ulcer\": -0.17547671753994198}], \"train_objective\": [{\"annualised_returns\": -0.43247620405836484, \"annualised_returns_over_hodl\": -0.2852956185410942, \"annualised_returns_over_uniform_hodl\": -0.285295618541094, \"calmar\": -0.5809975789939016, \"daily_log_sharpe\": -0.4364649314593918, \"daily_returns\": 0.008094881370773861, \"fee_revenue_over_value\": 0.00028113735615932484, \"jax_sharpe\": 0.09431926068569779, \"return\": -0.29101267444417156, \"returns_over_hodl\": -0.18447543934338373, \"returns_over_uniform_hodl\": -0.18447543934338362, \"sharpe\": 0.11457246433908026, \"sterling\": -1.327820225658137, \"ulcer\": -0.17621216459417563}], \"train_return\": -0.29101267444417156, \"train_returns_over_hodl\": -0.18447543934338373, \"train_sharpe\": 0.09431926068569779, \"validation_return\": -0.316287714304724, \"validation_returns_over_hodl\": -0.064708035934407, \"validation_sharpe\": -2.0893257928708904}, {\"centeredness_margin\": 0.8084816434144269, \"continuous_test_metrics\": [{\"annualised_returns\": -0.48037587650053193, \"annualised_returns_over_hodl\": 0.008012867794561007, \"annualised_returns_over_uniform_hodl\": 0.8340428556758315, \"calmar\": -0.8286794433608319, \"daily_log_sharpe\": -0.687992897689587, \"daily_returns\": 0.03101604251496146, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": -0.0043252797280997676, \"return\": -0.23175926829897087, \"returns_over_hodl\": 0.0032193956447865713, \"returns_over_uniform_hodl\": 0.27668854127001197, \"sharpe\": -0.1987371040957138, \"sterling\": -1.2367499357375527, \"ulcer\": -0.17641031602490626}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -0.004255998010841089, \"optuna_trial_number\": 139, \"price_ratio\": 6.265121125522166, \"shift_exponent\": 5.6732672019088406e-05, \"step\": 139, \"test_objective\": [{\"annualised_returns\": -0.48037587650053193, \"annualised_returns_over_hodl\": 0.008012867794561007, \"annualised_returns_over_uniform_hodl\": 0.8340428556758315, \"calmar\": -0.8286794433608319, \"daily_log_sharpe\": -0.687992897689587, \"daily_returns\": 0.03101604251496146, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": -0.0043252797280997676, \"return\": -0.23175926829897087, \"returns_over_hodl\": 0.0032193956447865713, \"returns_over_uniform_hodl\": 0.27668854127001197, \"sharpe\": -0.1987371040957138, \"sterling\": -1.2367499357375527, \"ulcer\": -0.17641031602490626}], \"train_objective\": [{\"annualised_returns\": -0.4815337114785213, \"annualised_returns_over_hodl\": -0.34707561040639445, \"annualised_returns_over_uniform_hodl\": -0.34707561040639456, \"calmar\": -0.6421175268515891, \"daily_log_sharpe\": -0.47991693504765637, \"daily_returns\": 0.00818926776200037, \"fee_revenue_over_value\": 0.0003544581422441566, \"jax_sharpe\": 0.1418735109676465, \"return\": -0.32887915396097855, \"returns_over_hodl\": -0.22803199241342165, \"returns_over_uniform_hodl\": -0.22803199241342176, \"sharpe\": 0.1035863971350435, \"sterling\": -1.4303505180788068, \"ulcer\": -0.18270113390534998}], \"train_return\": -0.32887915396097855, \"train_returns_over_hodl\": -0.22803199241342165, \"train_sharpe\": 0.1418735109676465, \"validation_return\": -0.3390464320146014, \"validation_returns_over_hodl\": -0.004255998010841089, \"validation_sharpe\": -2.243006232962715}, {\"centeredness_margin\": 0.7765844251193065, \"continuous_test_metrics\": [{\"annualised_returns\": -0.4777705849338624, \"annualised_returns_over_hodl\": 0.013066843152326424, \"annualised_returns_over_uniform_hodl\": 0.8432383802265848, \"calmar\": -0.8241851393154985, \"daily_log_sharpe\": -0.6814788923199585, \"daily_returns\": 0.031016042510692034, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": 0.0033258289428460324, \"return\": -0.23021031699939132, \"returns_over_hodl\": 0.0052421184409447985, \"returns_over_uniform_hodl\": 0.27926264114985044, \"sharpe\": -0.19128662092029236, \"sterling\": -1.2300424806253076, \"ulcer\": -0.1764103160160653}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -6.178767497644344e-10, \"optuna_trial_number\": 140, \"price_ratio\": 5.340244406987983, \"shift_exponent\": 0.00019505966221159652, \"step\": 140, \"test_objective\": [{\"annualised_returns\": -0.4777705849338624, \"annualised_returns_over_hodl\": 0.013066843152326424, \"annualised_returns_over_uniform_hodl\": 0.8432383802265848, \"calmar\": -0.8241851393154985, \"daily_log_sharpe\": -0.6814788923199585, \"daily_returns\": 0.031016042510692034, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": 0.0033258289428460324, \"return\": -0.23021031699939132, \"returns_over_hodl\": 0.0052421184409447985, \"returns_over_uniform_hodl\": 0.27926264114985044, \"sharpe\": -0.19128662092029236, \"sterling\": -1.2300424806253076, \"ulcer\": -0.1764103160160653}], \"train_objective\": [{\"annualised_returns\": -0.494202805955261, \"annualised_returns_over_hodl\": -0.3630302847238285, \"annualised_returns_over_uniform_hodl\": -0.3630302847238287, \"calmar\": -0.6573366545687581, \"daily_log_sharpe\": -0.49111431413576295, \"daily_returns\": 0.00818921786849991, \"fee_revenue_over_value\": 0.0005355167617850149, \"jax_sharpe\": 0.09558242736929226, \"return\": -0.33888387268864106, \"returns_over_hodl\": -0.2395400879050773, \"returns_over_uniform_hodl\": -0.23954008790507741, \"sharpe\": 0.102616358760267, \"sterling\": -1.4531735464427114, \"ulcer\": -0.18466052440979022}], \"train_return\": -0.33888387268864106, \"train_returns_over_hodl\": -0.2395400879050773, \"train_sharpe\": 0.09558242736929225, \"validation_return\": -0.33914271998425227, \"validation_returns_over_hodl\": -6.178767497644344e-10, \"validation_sharpe\": -2.24385319861686}, {\"centeredness_margin\": 0.8537535279831522, \"continuous_test_metrics\": [{\"annualised_returns\": -0.4797493635253186, \"annualised_returns_over_hodl\": 0.009228233173957268, \"annualised_returns_over_uniform_hodl\": 0.836254168804329, \"calmar\": -0.8275986661217465, \"daily_log_sharpe\": -0.6864240362602124, \"daily_returns\": 0.03101604251360243, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": -0.002707388536272584, \"return\": -0.23138635814174902, \"returns_over_hodl\": 0.0037063663355860754, \"returns_over_uniform_hodl\": 0.2773082560351927, \"sharpe\": -0.19695669374147987, \"sterling\": -1.2351369463371524, \"ulcer\": -0.17641031602209203}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": 1.9385892147560924e-05, \"optuna_trial_number\": 141, \"price_ratio\": 5.902766231259061, \"shift_exponent\": 0.00012568728138589622, \"step\": 141, \"test_objective\": [{\"annualised_returns\": -0.4797493635253186, \"annualised_returns_over_hodl\": 0.009228233173957268, \"annualised_returns_over_uniform_hodl\": 0.836254168804329, \"calmar\": -0.8275986661217465, \"daily_log_sharpe\": -0.6864240362602124, \"daily_returns\": 0.03101604251360243, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": -0.002707388536272584, \"return\": -0.23138635814174902, \"returns_over_hodl\": 0.0037063663355860754, \"returns_over_uniform_hodl\": 0.2773082560351927, \"sharpe\": -0.19695669374147987, \"sterling\": -1.2351369463371524, \"ulcer\": -0.17641031602209203}], \"train_objective\": [{\"annualised_returns\": -0.4848191484506069, \"annualised_returns_over_hodl\": -0.3512130866069485, \"annualised_returns_over_uniform_hodl\": -0.3512130866069486, \"calmar\": -0.6460081532394951, \"daily_log_sharpe\": -0.4818940126611004, \"daily_returns\": 0.00818142008250776, \"fee_revenue_over_value\": 0.0002883192184063104, \"jax_sharpe\": 0.093844388808146, \"return\": -0.3314643337171165, \"returns_over_hodl\": -0.23100563878035762, \"returns_over_uniform_hodl\": -0.23100563878035774, \"sharpe\": 0.10491265634948255, \"sterling\": -1.4356319620375135, \"ulcer\": -0.1832612209809162}], \"train_return\": -0.3314643337171165, \"train_returns_over_hodl\": -0.23100563878035762, \"train_sharpe\": 0.093844388808146, \"validation_return\": -0.3391299082212662, \"validation_returns_over_hodl\": 1.9385892147560924e-05, \"validation_sharpe\": -2.243729710725302}, {\"centeredness_margin\": 0.30417850434523147, \"continuous_test_metrics\": [{\"annualised_returns\": -0.7910184795731792, \"annualised_returns_over_hodl\": -0.32987436668055603, \"annualised_returns_over_uniform_hodl\": -0.26238785465571957, \"calmar\": -1.270560642421009, \"daily_log_sharpe\": -1.906727836171878, \"daily_returns\": 0.020154014964110435, \"fee_revenue_over_value\": 0.07191281948559786, \"jax_sharpe\": -1.4566200950483372, \"return\": -0.46766903501608814, \"returns_over_hodl\": -0.1488883430378043, \"returns_over_uniform_hodl\": -0.11535432174578863, \"sharpe\": -1.5494683067060608, \"sterling\": -2.3918292881864285, \"ulcer\": -0.165069180206862}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -0.06739184266805087, \"optuna_trial_number\": 142, \"price_ratio\": 3.1298659121922845, \"shift_exponent\": 105.31592251895889, \"step\": 142, \"test_objective\": [{\"annualised_returns\": -0.7910184795731792, \"annualised_returns_over_hodl\": -0.32987436668055603, \"annualised_returns_over_uniform_hodl\": -0.26238785465571957, \"calmar\": -1.270560642421009, \"daily_log_sharpe\": -1.906727836171878, \"daily_returns\": 0.020154014964110435, \"fee_revenue_over_value\": 0.07191281948559786, \"jax_sharpe\": -1.4566200950483372, \"return\": -0.46766903501608814, \"returns_over_hodl\": -0.1488883430378043, \"returns_over_uniform_hodl\": -0.11535432174578863, \"sharpe\": -1.5494683067060608, \"sterling\": -2.3918292881864285, \"ulcer\": -0.165069180206862}], \"train_objective\": [{\"annualised_returns\": -0.5140849244217311, \"annualised_returns_over_hodl\": -0.38806859550883144, \"annualised_returns_over_uniform_hodl\": -0.38806859550883155, \"calmar\": -0.6722589995143995, \"daily_log_sharpe\": -0.6511692498425669, \"daily_returns\": 0.008309006071885087, \"fee_revenue_over_value\": 0.03630950958837636, \"jax_sharpe\": -0.19857749441403014, \"return\": -0.3547855283515212, \"returns_over_hodl\": -0.2578312339957586, \"returns_over_uniform_hodl\": -0.2578312339957587, \"sharpe\": -0.14701620890057396, \"sterling\": -1.611601795651951, \"ulcer\": -0.17687318195128732}], \"train_return\": -0.3547855283515212, \"train_returns_over_hodl\": -0.2578312339957586, \"train_sharpe\": -0.19857749441403014, \"validation_return\": -0.21374977125751138, \"validation_returns_over_hodl\": -0.06739184266805087, \"validation_sharpe\": -1.731653125032666}, {\"centeredness_margin\": 0.7672558866735872, \"continuous_test_metrics\": [{\"annualised_returns\": -0.4843792922590675, \"annualised_returns_over_hodl\": 0.00024668842876085506, \"annualised_returns_over_uniform_hodl\": 0.8199125723841443, \"calmar\": -0.8355856062727656, \"daily_log_sharpe\": -0.6921975494406511, \"daily_returns\": 0.031016042493588147, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": 0.005192376269756607, \"return\": -0.23414852778420092, \"returns_over_hodl\": 9.934344277340301e-05, \"returns_over_uniform_hodl\": 0.2727179887061564, \"sharpe\": -0.20182158753329735, \"sterling\": -1.2470569331702275, \"ulcer\": -0.17641031598072043}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -8.324619882316142e-10, \"optuna_trial_number\": 143, \"price_ratio\": 3.745252406123903, \"shift_exponent\": 1.2073861038978033e-05, \"step\": 143, \"test_objective\": [{\"annualised_returns\": -0.4843792922590675, \"annualised_returns_over_hodl\": 0.00024668842876085506, \"annualised_returns_over_uniform_hodl\": 0.8199125723841443, \"calmar\": -0.8355856062727656, \"daily_log_sharpe\": -0.6921975494406511, \"daily_returns\": 0.031016042493588147, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": 0.005192376269756607, \"return\": -0.23414852778420092, \"returns_over_hodl\": 9.934344277340301e-05, \"returns_over_uniform_hodl\": 0.2727179887061564, \"sharpe\": -0.20182158753329735, \"sterling\": -1.2470569331702275, \"ulcer\": -0.17641031598072043}], \"train_objective\": [{\"annualised_returns\": -0.5431295027772529, \"annualised_returns_over_hodl\": -0.4246455418091636, \"annualised_returns_over_uniform_hodl\": -0.4246455418091636, \"calmar\": -0.7144652731523197, \"daily_log_sharpe\": -0.5460008127593347, \"daily_returns\": 0.008262824945004577, \"fee_revenue_over_value\": 0.00036398640039576055, \"jax_sharpe\": 0.10518504194235673, \"return\": -0.37848283966499463, \"returns_over_hodl\": -0.2850894637285858, \"returns_over_uniform_hodl\": -0.2850894637285858, \"sharpe\": 0.08347203597689982, \"sterling\": -1.5318605347871845, \"ulcer\": -0.19400485113695515}], \"train_return\": -0.37848283966499463, \"train_returns_over_hodl\": -0.2850894637285858, \"train_sharpe\": 0.10518504194235674, \"validation_return\": -0.3391427199438778, \"validation_returns_over_hodl\": -8.324619882316142e-10, \"validation_sharpe\": -2.2438531994024347}, {\"centeredness_margin\": 0.66546109371973, \"continuous_test_metrics\": [{\"annualised_returns\": -0.4831196986216101, \"annualised_returns_over_hodl\": 0.002690158760932704, \"annualised_returns_over_uniform_hodl\": 0.8243583796655232, \"calmar\": -0.8334127220809618, \"daily_log_sharpe\": -0.6948287510349891, \"daily_returns\": 0.031016042515490044, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": -0.012157031860062631, \"return\": -0.2333956048104302, \"returns_over_hodl\": 0.0010825595398404886, \"returns_over_uniform_hodl\": 0.27396922167702975, \"sharpe\": -0.2065083620657881, \"sterling\": -1.2438140450152642, \"ulcer\": -0.17641031602599716}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -0.007925144219814939, \"optuna_trial_number\": 144, \"price_ratio\": 5.924269700932857, \"shift_exponent\": 0.0002336522669009045, \"step\": 144, \"test_objective\": [{\"annualised_returns\": -0.4831196986216101, \"annualised_returns_over_hodl\": 0.002690158760932704, \"annualised_returns_over_uniform_hodl\": 0.8243583796655232, \"calmar\": -0.8334127220809618, \"daily_log_sharpe\": -0.6948287510349891, \"daily_returns\": 0.031016042515490044, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": -0.012157031860062631, \"return\": -0.2333956048104302, \"returns_over_hodl\": 0.0010825595398404886, \"returns_over_uniform_hodl\": 0.27396922167702975, \"sharpe\": -0.2065083620657881, \"sterling\": -1.2438140450152642, \"ulcer\": -0.17641031602599716}], \"train_objective\": [{\"annualised_returns\": -0.47913160735818083, \"annualised_returns_over_hodl\": -0.34405054898728826, \"annualised_returns_over_uniform_hodl\": -0.34405054898728815, \"calmar\": -0.6389213196707794, \"daily_log_sharpe\": -0.47548719618120333, \"daily_returns\": 0.008148289705236938, \"fee_revenue_over_value\": 0.0018673531885756787, \"jax_sharpe\": 0.09747810889698562, \"return\": -0.32699310495856426, \"returns_over_hodl\": -0.2258625329201016, \"returns_over_uniform_hodl\": -0.2258625329201015, \"sharpe\": 0.1090638205529363, \"sterling\": -1.4229766935043522, \"ulcer\": -0.18258974850176418}], \"train_return\": -0.32699310495856426, \"train_returns_over_hodl\": -0.2258625329201016, \"train_sharpe\": 0.0974781088969856, \"validation_return\": -0.33900818798360377, \"validation_returns_over_hodl\": -0.007925144219814939, \"validation_sharpe\": -2.242688520685859}, {\"centeredness_margin\": 0.7682003623651947, \"continuous_test_metrics\": [{\"annualised_returns\": -0.47750359290201994, \"annualised_returns_over_hodl\": 0.013584777950374605, \"annualised_returns_over_uniform_hodl\": 0.8441807437666484, \"calmar\": -0.8237245600202251, \"daily_log_sharpe\": -0.6806544993095037, \"daily_returns\": 0.031016042510147036, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": 0.004507998271530284, \"return\": -0.23005184038613613, \"returns_over_hodl\": 0.005449067659527573, \"returns_over_uniform_hodl\": 0.279526002968423, \"sharpe\": -0.19032139725337224, \"sterling\": -1.2293550961252002, \"ulcer\": -0.1764103160149334}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -6.148925812965444e-10, \"optuna_trial_number\": 145, \"price_ratio\": 5.255278046984341, \"shift_exponent\": 0.00020088813619770316, \"step\": 145, \"test_objective\": [{\"annualised_returns\": -0.47750359290201994, \"annualised_returns_over_hodl\": 0.013584777950374605, \"annualised_returns_over_uniform_hodl\": 0.8441807437666484, \"calmar\": -0.8237245600202251, \"daily_log_sharpe\": -0.6806544993095037, \"daily_returns\": 0.031016042510147036, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": 0.004507998271530284, \"return\": -0.23005184038613613, \"returns_over_hodl\": 0.005449067659527573, \"returns_over_uniform_hodl\": 0.279526002968423, \"sharpe\": -0.19032139725337224, \"sterling\": -1.2293550961252002, \"ulcer\": -0.1764103160149334}], \"train_objective\": [{\"annualised_returns\": -0.4955161646274442, \"annualised_returns_over_hodl\": -0.3646842474371963, \"annualised_returns_over_uniform_hodl\": -0.3646842474371963, \"calmar\": -0.6590468233303062, \"daily_log_sharpe\": -0.49211452048169113, \"daily_returns\": 0.0081511396166563, \"fee_revenue_over_value\": 0.000595289669603113, \"jax_sharpe\": 0.09617350617349267, \"return\": -0.3399266268862462, \"returns_over_hodl\": -0.24073953340744658, \"returns_over_uniform_hodl\": -0.24073953340744658, \"sharpe\": 0.10291349687447153, \"sterling\": -1.4553380331905246, \"ulcer\": -0.18488223547127208}], \"train_return\": -0.3399266268862462, \"train_returns_over_hodl\": -0.24073953340744658, \"train_sharpe\": 0.09617350617349266, \"validation_return\": -0.33914271998277357, \"validation_returns_over_hodl\": -6.148925812965444e-10, \"validation_sharpe\": -2.243853198639667}, {\"centeredness_margin\": 0.808848784182525, \"continuous_test_metrics\": [{\"annualised_returns\": -0.47781498599243843, \"annualised_returns_over_hodl\": 0.012980710057510336, \"annualised_returns_over_uniform_hodl\": 0.8430816641686063, \"calmar\": -0.8242617341408538, \"daily_log_sharpe\": -0.6808058582582238, \"daily_returns\": 0.031016042510476603, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": 0.00657081684952829, \"return\": -0.23023667652112656, \"returns_over_hodl\": 0.005207696443102705, \"returns_over_uniform_hodl\": 0.2792188360013288, \"sharpe\": -0.19028216164218417, \"sterling\": -1.2301567934029785, \"ulcer\": -0.17641031601564838}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -6.258676910064764e-10, \"optuna_trial_number\": 146, \"price_ratio\": 5.46200803121969, \"shift_exponent\": 9.125626695472342e-05, \"step\": 146, \"test_objective\": [{\"annualised_returns\": -0.47781498599243843, \"annualised_returns_over_hodl\": 0.012980710057510336, \"annualised_returns_over_uniform_hodl\": 0.8430816641686063, \"calmar\": -0.8242617341408538, \"daily_log_sharpe\": -0.6808058582582238, \"daily_returns\": 0.031016042510476603, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": 0.00657081684952829, \"return\": -0.23023667652112656, \"returns_over_hodl\": 0.005207696443102705, \"returns_over_uniform_hodl\": 0.2792188360013288, \"sharpe\": -0.19028216164218417, \"sterling\": -1.2301567934029785, \"ulcer\": -0.17641031601564838}], \"train_objective\": [{\"annualised_returns\": -0.4959365671286604, \"annualised_returns_over_hodl\": -0.36521367635584967, \"annualised_returns_over_uniform_hodl\": -0.36521367635584967, \"calmar\": -0.6597614597018574, \"daily_log_sharpe\": -0.4938730496004757, \"daily_returns\": 0.008140756613857028, \"fee_revenue_over_value\": 0.0003413783642732908, \"jax_sharpe\": 0.09283837118242193, \"return\": -0.3402606349070437, \"returns_over_hodl\": -0.24112373173455082, \"returns_over_uniform_hodl\": -0.24112373173455082, \"sharpe\": 0.09985180881693483, \"sterling\": -1.457769134456795, \"ulcer\": -0.18476713656347116}], \"train_return\": -0.3402606349070437, \"train_returns_over_hodl\": -0.24112373173455082, \"train_sharpe\": 0.09283837118242193, \"validation_return\": -0.3391427199872199, \"validation_returns_over_hodl\": -6.258676910064764e-10, \"validation_sharpe\": -2.2438531986611965}, {\"centeredness_margin\": 0.7459752492000444, \"continuous_test_metrics\": [{\"annualised_returns\": -0.5760362458722844, \"annualised_returns_over_hodl\": -0.17755758406046118, \"annualised_returns_over_uniform_hodl\": 0.4964041489968236, \"calmar\": -0.994648585441029, \"daily_log_sharpe\": -0.9248545554414422, \"daily_returns\": 0.03101604251706158, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": -0.24645381484778905, \"return\": -0.2921989810683677, \"returns_over_hodl\": -0.07570676603992665, \"returns_over_uniform_hodl\": 0.17624777375240464, \"sharpe\": -0.45623678261352213, \"sterling\": -1.4982751983056346, \"ulcer\": -0.172936243328153}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -0.0682069134148614, \"optuna_trial_number\": 147, \"price_ratio\": 7.164702827635607, \"shift_exponent\": 0.0011734584997148453, \"step\": 147, \"test_objective\": [{\"annualised_returns\": -0.5760362458722844, \"annualised_returns_over_hodl\": -0.17755758406046118, \"annualised_returns_over_uniform_hodl\": 0.4964041489968236, \"calmar\": -0.994648585441029, \"daily_log_sharpe\": -0.9248545554414422, \"daily_returns\": 0.03101604251706158, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": -0.24645381484778905, \"return\": -0.2921989810683677, \"returns_over_hodl\": -0.07570676603992665, \"returns_over_uniform_hodl\": 0.17624777375240464, \"sharpe\": -0.45623678261352213, \"sterling\": -1.4982751983056346, \"ulcer\": -0.172936243328153}], \"train_objective\": [{\"annualised_returns\": -0.42615462374916113, \"annualised_returns_over_hodl\": -0.27733461113128854, \"annualised_returns_over_uniform_hodl\": -0.27733461113128854, \"calmar\": -0.5708911254214398, \"daily_log_sharpe\": -0.42508692589497427, \"daily_returns\": 0.008143963878430978, \"fee_revenue_over_value\": 0.0010966604247652084, \"jax_sharpe\": 0.15933160062699658, \"return\": -0.2862284763760591, \"returns_over_hodl\": -0.17897233528643464, \"returns_over_uniform_hodl\": -0.17897233528643464, \"sharpe\": 0.13060457215752816, \"sterling\": -1.3048380556019596, \"ulcer\": -0.17632607578000606}], \"train_return\": -0.2862284763760591, \"train_returns_over_hodl\": -0.17897233528643464, \"train_sharpe\": 0.15933160062699656, \"validation_return\": -0.3082619731547601, \"validation_returns_over_hodl\": -0.0682069134148614, \"validation_sharpe\": -2.065431849440031}, {\"centeredness_margin\": 0.8645469392007227, \"continuous_test_metrics\": [{\"annualised_returns\": -0.48189916284224465, \"annualised_returns_over_hodl\": 0.005057862068388097, \"annualised_returns_over_uniform_hodl\": 0.8286663300184947, \"calmar\": -0.8313072153060468, \"daily_log_sharpe\": -0.6917424793663358, \"daily_returns\": 0.03101604251620933, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": -0.008320163149858681, \"return\": -0.23266707339943926, \"returns_over_hodl\": 0.0020339238747084654, \"returns_over_uniform_hodl\": 0.275179919920415, \"sharpe\": -0.20302283504005633, \"sterling\": -1.2406717136815497, \"ulcer\": -0.17641031602748744}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -0.01408930116168694, \"optuna_trial_number\": 148, \"price_ratio\": 6.509936380986463, \"shift_exponent\": 5.439950337877038e-05, \"step\": 148, \"test_objective\": [{\"annualised_returns\": -0.48189916284224465, \"annualised_returns_over_hodl\": 0.005057862068388097, \"annualised_returns_over_uniform_hodl\": 0.8286663300184947, \"calmar\": -0.8313072153060468, \"daily_log_sharpe\": -0.6917424793663358, \"daily_returns\": 0.03101604251620933, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": -0.008320163149858681, \"return\": -0.23266707339943926, \"returns_over_hodl\": 0.0020339238747084654, \"returns_over_uniform_hodl\": 0.275179919920415, \"sharpe\": -0.20302283504005633, \"sterling\": -1.2406717136815497, \"ulcer\": -0.17641031602748744}], \"train_objective\": [{\"annualised_returns\": -0.47695547283031203, \"annualised_returns_over_hodl\": -0.3413100597023737, \"annualised_returns_over_uniform_hodl\": -0.34131005970237394, \"calmar\": -0.636434140935535, \"daily_log_sharpe\": -0.4752853304650008, \"daily_returns\": 0.00817347406305382, \"fee_revenue_over_value\": 0.00027897489692962034, \"jax_sharpe\": 0.09245247059943293, \"return\": -0.3252874275524307, \"returns_over_hodl\": -0.2239005488801632, \"returns_over_uniform_hodl\": -0.2239005488801633, \"sharpe\": 0.10553634782159717, \"sterling\": -1.4208241893455746, \"ulcer\": -0.18209011937059164}], \"train_return\": -0.3252874275524307, \"train_returns_over_hodl\": -0.2239005488801632, \"train_sharpe\": 0.09245247059943296, \"validation_return\": -0.3388460991445815, \"validation_returns_over_hodl\": -0.01408930116168694, \"validation_sharpe\": -2.2412079691516835}, {\"centeredness_margin\": 0.8022157628321475, \"continuous_test_metrics\": [{\"annualised_returns\": -0.4805099639296101, \"annualised_returns_over_hodl\": 0.007752753540831048, \"annualised_returns_over_uniform_hodl\": 0.8335695864794748, \"calmar\": -0.8289107532158048, \"daily_log_sharpe\": -0.6844656304841564, \"daily_returns\": 0.031016042505012203, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": 0.0076668208968552545, \"return\": -0.23183911408075608, \"returns_over_hodl\": 0.0031151279230841045, \"returns_over_uniform_hodl\": 0.27655585083787626, \"sharpe\": -0.19374553162975022, \"sterling\": -1.2370951507851244, \"ulcer\": -0.1764103160043424}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -6.870813917814189e-10, \"optuna_trial_number\": 149, \"price_ratio\": 4.849939807483821, \"shift_exponent\": 5.883664816725716e-05, \"step\": 149, \"test_objective\": [{\"annualised_returns\": -0.4805099639296101, \"annualised_returns_over_hodl\": 0.007752753540831048, \"annualised_returns_over_uniform_hodl\": 0.8335695864794748, \"calmar\": -0.8289107532158048, \"daily_log_sharpe\": -0.6844656304841564, \"daily_returns\": 0.031016042505012203, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": 0.0076668208968552545, \"return\": -0.23183911408075608, \"returns_over_hodl\": 0.0031151279230841045, \"returns_over_uniform_hodl\": 0.27655585083787626, \"sharpe\": -0.19374553162975022, \"sterling\": -1.2370951507851244, \"ulcer\": -0.1764103160043424}], \"train_objective\": [{\"annualised_returns\": -0.5118269443258848, \"annualised_returns_over_hodl\": -0.38522503497570726, \"annualised_returns_over_uniform_hodl\": -0.38522503497570737, \"calmar\": -0.6789145353070006, \"daily_log_sharpe\": -0.5114167229894861, \"daily_returns\": 0.008211185444087858, \"fee_revenue_over_value\": 0.0003352631789131409, \"jax_sharpe\": 0.15855023860582404, \"return\": -0.35296690148107246, \"returns_over_hodl\": -0.25573932794037413, \"returns_over_uniform_hodl\": -0.25573932794037424, \"sharpe\": 0.09378799127693682, \"sterling\": -1.4867785228369375, \"ulcer\": -0.18730481010595984}], \"train_return\": -0.35296690148107246, \"train_returns_over_hodl\": -0.25573932794037413, \"train_sharpe\": 0.15855023860582404, \"validation_return\": -0.3391427199738787, \"validation_returns_over_hodl\": -6.870813917814189e-10, \"validation_sharpe\": -2.2438531989063923}, {\"centeredness_margin\": 0.7014135464472814, \"continuous_test_metrics\": [{\"annualised_returns\": -0.6877014241578729, \"annualised_returns_over_hodl\": -0.21004488880170746, \"annualised_returns_over_uniform_hodl\": 0.10227556027155238, \"calmar\": -1.1618997803407116, \"daily_log_sharpe\": -1.3773139426518561, \"daily_returns\": 0.024864790162403328, \"fee_revenue_over_value\": 0.0008053858593778809, \"jax_sharpe\": -0.7488353250382522, \"return\": -0.3741875145570509, \"returns_over_hodl\": -0.09058812922530068, \"returns_over_uniform_hodl\": 0.03999644405687186, \"sharpe\": -0.9628575563993141, \"sterling\": -1.9178900423620688, \"ulcer\": -0.15770943551827624}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -0.04876734098155744, \"optuna_trial_number\": 150, \"price_ratio\": 13.03622695547674, \"shift_exponent\": 0.0019915518521044892, \"step\": 150, \"test_objective\": [{\"annualised_returns\": -0.6877014241578729, \"annualised_returns_over_hodl\": -0.21004488880170746, \"annualised_returns_over_uniform_hodl\": 0.10227556027155238, \"calmar\": -1.1618997803407116, \"daily_log_sharpe\": -1.3773139426518561, \"daily_returns\": 0.024864790162403328, \"fee_revenue_over_value\": 0.0008053858593778809, \"jax_sharpe\": -0.7488353250382522, \"return\": -0.3741875145570509, \"returns_over_hodl\": -0.09058812922530068, \"returns_over_uniform_hodl\": 0.03999644405687186, \"sharpe\": -0.9628575563993141, \"sterling\": -1.9178900423620688, \"ulcer\": -0.15770943551827624}], \"train_objective\": [{\"annualised_returns\": -0.3709151514546153, \"annualised_returns_over_hodl\": -0.2077694349030661, \"annualised_returns_over_uniform_hodl\": -0.2077694349030661, \"calmar\": -0.5006983973677034, \"daily_log_sharpe\": -0.3730134793129246, \"daily_returns\": 0.008095830916346232, \"fee_revenue_over_value\": 0.004151317334955153, \"jax_sharpe\": 0.13135350422000722, \"return\": -0.24526921301103233, \"returns_over_hodl\": -0.13185825573022447, \"returns_over_uniform_hodl\": -0.13185825573022447, \"sharpe\": 0.1526526299904077, \"sterling\": -1.1754285291043391, \"ulcer\": -0.16994750019949903}], \"train_return\": -0.24526921301103233, \"train_returns_over_hodl\": -0.13185825573022447, \"train_sharpe\": 0.1313535042200072, \"validation_return\": -0.2321917434924392, \"validation_returns_over_hodl\": -0.04876734098155744, \"validation_sharpe\": -1.7361066633641147}, {\"centeredness_margin\": 0.8712261095796616, \"continuous_test_metrics\": [{\"annualised_returns\": -0.48147479465546483, \"annualised_returns_over_hodl\": 0.005881089679882878, \"annualised_returns_over_uniform_hodl\": 0.8301641616355064, \"calmar\": -0.8305751514449138, \"daily_log_sharpe\": -0.6863515085977834, \"daily_returns\": 0.03101604250061026, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": 0.007690204898713385, \"return\": -0.23241401044079157, \"returns_over_hodl\": 0.0023643904272023786, \"returns_over_uniform_hodl\": 0.2756004685403899, \"sharpe\": -0.19568375734374371, \"sterling\": -1.239579156504066, \"ulcer\": -0.17641031599523394}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -7.331782958530653e-10, \"optuna_trial_number\": 151, \"price_ratio\": 4.230045022597689, \"shift_exponent\": 0.00021595962370969782, \"step\": 151, \"test_objective\": [{\"annualised_returns\": -0.48147479465546483, \"annualised_returns_over_hodl\": 0.005881089679882878, \"annualised_returns_over_uniform_hodl\": 0.8301641616355064, \"calmar\": -0.8305751514449138, \"daily_log_sharpe\": -0.6863515085977834, \"daily_returns\": 0.03101604250061026, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": 0.007690204898713385, \"return\": -0.23241401044079157, \"returns_over_hodl\": 0.0023643904272023786, \"returns_over_uniform_hodl\": 0.2756004685403899, \"sharpe\": -0.19568375734374371, \"sterling\": -1.239579156504066, \"ulcer\": -0.17641031599523394}], \"train_objective\": [{\"annualised_returns\": -0.5247380514144129, \"annualised_returns_over_hodl\": -0.4014844850140017, \"annualised_returns_over_uniform_hodl\": -0.4014844850140017, \"calmar\": -0.6935677924825794, \"daily_log_sharpe\": -0.5256310722488078, \"daily_returns\": 0.008263472390797201, \"fee_revenue_over_value\": 0.0002836548704631753, \"jax_sharpe\": 0.15351625053788745, \"return\": -0.36341098173535524, \"returns_over_hodl\": -0.26775280639594334, \"returns_over_uniform_hodl\": -0.26775280639594334, \"sharpe\": 0.0895107307932289, \"sterling\": -1.508403056969371, \"ulcer\": -0.18958294478597754}], \"train_return\": -0.36341098173535524, \"train_returns_over_hodl\": -0.26775280639594334, \"train_sharpe\": 0.15351625053788748, \"validation_return\": -0.33914271995591827, \"validation_returns_over_hodl\": -7.331782958530653e-10, \"validation_sharpe\": -2.2438531990322717}, {\"centeredness_margin\": 0.7721579216795874, \"continuous_test_metrics\": [{\"annualised_returns\": -0.628396145577617, \"annualised_returns_over_hodl\": -0.12026870299162484, \"annualised_returns_over_uniform_hodl\": 0.3115969092333166, \"calmar\": -1.0727216759161495, \"daily_log_sharpe\": -1.1302650201116966, \"daily_returns\": 0.02635880592715943, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": -0.4719202783374367, \"return\": -0.32879522176210285, \"returns_over_hodl\": -0.05029733376326806, \"returns_over_uniform_hodl\": 0.11543089797467854, \"sharpe\": -0.6940426600669066, \"sterling\": -1.7014219696317996, \"ulcer\": -0.1632265317070613}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -0.04970255567212778, \"optuna_trial_number\": 152, \"price_ratio\": 34.65363120252472, \"shift_exponent\": 2.4138270931182672e-05, \"step\": 152, \"test_objective\": [{\"annualised_returns\": -0.628396145577617, \"annualised_returns_over_hodl\": -0.12026870299162484, \"annualised_returns_over_uniform_hodl\": 0.3115969092333166, \"calmar\": -1.0727216759161495, \"daily_log_sharpe\": -1.1302650201116966, \"daily_returns\": 0.02635880592715943, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": -0.4719202783374367, \"return\": -0.32879522176210285, \"returns_over_hodl\": -0.05029733376326806, \"returns_over_uniform_hodl\": 0.11543089797467854, \"sharpe\": -0.6940426600669066, \"sterling\": -1.7014219696317996, \"ulcer\": -0.1632265317070613}], \"train_objective\": [{\"annualised_returns\": -0.38533149213156237, \"annualised_returns_over_hodl\": -0.22592448306157187, \"annualised_returns_over_uniform_hodl\": -0.22592448306157187, \"calmar\": -0.5214861608286495, \"daily_log_sharpe\": -0.39370267590157093, \"daily_returns\": 0.008017091564811401, \"fee_revenue_over_value\": 0.0015999387601174193, \"jax_sharpe\": 0.13037600059127763, \"return\": -0.25581757608721245, \"returns_over_hodl\": -0.1439916872504624, \"returns_over_uniform_hodl\": -0.1439916872504624, \"sharpe\": 0.128947110711703, \"sterling\": -1.2217393228550009, \"ulcer\": -0.17035758438794343}], \"train_return\": -0.25581757608721245, \"train_returns_over_hodl\": -0.1439916872504624, \"train_sharpe\": 0.13037600059127763, \"validation_return\": -0.24991123339977905, \"validation_returns_over_hodl\": -0.04970255567212778, \"validation_sharpe\": -1.823913763816252}, {\"centeredness_margin\": 0.9054502882773316, \"continuous_test_metrics\": [{\"annualised_returns\": -0.4845064587524802, \"annualised_returns_over_hodl\": 8.637757176188643e-12, \"annualised_returns_over_uniform_hodl\": 0.8194637310232897, \"calmar\": -0.8358049770540323, \"daily_log_sharpe\": -0.6924636110377959, \"daily_returns\": 0.031016042483804834, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": 0.005019267095565479, \"return\": -0.23422460266920875, \"returns_over_hodl\": 3.4787728253604655e-12, \"returns_over_uniform_hodl\": 0.2725915648783639, \"sharpe\": -0.2021085462642683, \"sterling\": -1.24738432992868, \"ulcer\": -0.17641031596049034}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -9.344469642513786e-10, \"optuna_trial_number\": 153, \"price_ratio\": 3.064517217794547, \"shift_exponent\": 6.26471860645069e-05, \"step\": 153, \"test_objective\": [{\"annualised_returns\": -0.4845064587524802, \"annualised_returns_over_hodl\": 8.637757176188643e-12, \"annualised_returns_over_uniform_hodl\": 0.8194637310232897, \"calmar\": -0.8358049770540323, \"daily_log_sharpe\": -0.6924636110377959, \"daily_returns\": 0.031016042483804834, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": 0.005019267095565479, \"return\": -0.23422460266920875, \"returns_over_hodl\": 3.4787728253604655e-12, \"returns_over_uniform_hodl\": 0.2725915648783639, \"sharpe\": -0.2021085462642683, \"sterling\": -1.24738432992868, \"ulcer\": -0.17641031596049034}], \"train_objective\": [{\"annualised_returns\": -0.5680616285686181, \"annualised_returns_over_hodl\": -0.4560435196025139, \"annualised_returns_over_uniform_hodl\": -0.456043519602514, \"calmar\": -0.7398402889014654, \"daily_log_sharpe\": -0.5759058340389883, \"daily_returns\": 0.008308195230407484, \"fee_revenue_over_value\": 0.0002561345388270726, \"jax_sharpe\": 0.11114848242408902, \"return\": -0.3993011921463189, \"returns_over_hodl\": -0.30903612278573644, \"returns_over_uniform_hodl\": -0.30903612278573656, \"sharpe\": 0.07108606713921657, \"sterling\": -1.5687016711592199, \"ulcer\": -0.19937321637679903}], \"train_return\": -0.3993011921463189, \"train_returns_over_hodl\": -0.30903612278573644, \"train_sharpe\": 0.11114848242408902, \"validation_return\": -0.33914271990329314, \"validation_returns_over_hodl\": -9.344469642513786e-10, \"validation_sharpe\": -2.2438531996758546}, {\"centeredness_margin\": 0.8662545596189359, \"continuous_test_metrics\": [{\"annualised_returns\": -0.5030470611472065, \"annualised_returns_over_hodl\": -0.03596670248949796, \"annualised_returns_over_uniform_hodl\": 0.7540236218671388, \"calmar\": -0.8677887736804036, \"daily_log_sharpe\": -0.7403424265264115, \"daily_returns\": 0.031016042487921232, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": -0.05798605322297598, \"return\": -0.24543845188642732, \"returns_over_hodl\": -0.01464378396655397, \"returns_over_uniform_hodl\": 0.2539560093703226, \"sharpe\": -0.2562470878780278, \"sterling\": -1.2951180564247688, \"ulcer\": -0.17641031602878987}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -0.052579823177475116, \"optuna_trial_number\": 154, \"price_ratio\": 8.323319989365936, \"shift_exponent\": 0.00010667580127638299, \"step\": 154, \"test_objective\": [{\"annualised_returns\": -0.5030470611472065, \"annualised_returns_over_hodl\": -0.03596670248949796, \"annualised_returns_over_uniform_hodl\": 0.7540236218671388, \"calmar\": -0.8677887736804036, \"daily_log_sharpe\": -0.7403424265264115, \"daily_returns\": 0.031016042487921232, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": -0.05798605322297598, \"return\": -0.24543845188642732, \"returns_over_hodl\": -0.01464378396655397, \"returns_over_uniform_hodl\": 0.2539560093703226, \"sharpe\": -0.2562470878780278, \"sterling\": -1.2951180564247688, \"ulcer\": -0.17641031602878987}], \"train_objective\": [{\"annualised_returns\": -0.4522763216897513, \"annualised_returns_over_hodl\": -0.31023066254409304, \"annualised_returns_over_uniform_hodl\": -0.31023066254409315, \"calmar\": -0.6056667675478394, \"daily_log_sharpe\": -0.45356028788050257, \"daily_returns\": 0.008133570541449349, \"fee_revenue_over_value\": 0.00027340254443204617, \"jax_sharpe\": 0.0936688031909562, \"return\": -0.3061348015807488, \"returns_over_hodl\": -0.20186991967430112, \"returns_over_uniform_hodl\": -0.20186991967430123, \"sharpe\": 0.11075476554931232, \"sterling\": -1.3697081955464607, \"ulcer\": -0.17882103982148803}], \"train_return\": -0.3061348015807488, \"train_returns_over_hodl\": -0.20186991967430112, \"train_sharpe\": 0.0936688031909562, \"validation_return\": -0.3322484861643944, \"validation_returns_over_hodl\": -0.052579823177475116, \"validation_sharpe\": -2.182865356924159}, {\"centeredness_margin\": 0.8240329720691962, \"continuous_test_metrics\": [{\"annualised_returns\": -0.482155926769512, \"annualised_returns_over_hodl\": 0.004559768647669271, \"annualised_returns_over_uniform_hodl\": 0.8277600671544287, \"calmar\": -0.8317501504480151, \"daily_log_sharpe\": -0.6923472265237186, \"daily_returns\": 0.031016042516890212, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": -0.00911757037613124, \"return\": -0.23282024923407785, \"returns_over_hodl\": 0.0018338967601310152, \"returns_over_uniform_hodl\": 0.27492536711578874, \"sharpe\": -0.2037143098639213, \"sterling\": -1.2413327654324497, \"ulcer\": -0.1764103160288864}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -0.016294839426605923, \"optuna_trial_number\": 155, \"price_ratio\": 6.6792025851472285, \"shift_exponent\": 2.60653809408391e-05, \"step\": 155, \"test_objective\": [{\"annualised_returns\": -0.482155926769512, \"annualised_returns_over_hodl\": 0.004559768647669271, \"annualised_returns_over_uniform_hodl\": 0.8277600671544287, \"calmar\": -0.8317501504480151, \"daily_log_sharpe\": -0.6923472265237186, \"daily_returns\": 0.031016042516890212, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": -0.00911757037613124, \"return\": -0.23282024923407785, \"returns_over_hodl\": 0.0018338967601310152, \"returns_over_uniform_hodl\": 0.27492536711578874, \"sharpe\": -0.2037143098639213, \"sterling\": -1.2413327654324497, \"ulcer\": -0.1764103160288864}], \"train_objective\": [{\"annualised_returns\": -0.475016369300745, \"annualised_returns_over_hodl\": -0.33886807260228324, \"annualised_returns_over_uniform_hodl\": -0.33886807260228347, \"calmar\": -0.6340931916182395, \"daily_log_sharpe\": -0.4735891265749381, \"daily_returns\": 0.008177463595886798, \"fee_revenue_over_value\": 0.0003223504793572658, \"jax_sharpe\": 0.09242485529792457, \"return\": -0.3237698852226215, \"returns_over_hodl\": -0.22215497036671783, \"returns_over_uniform_hodl\": -0.22215497036671794, \"sharpe\": 0.10591520917606721, \"sterling\": -1.4168839056960847, \"ulcer\": -0.18183489397254374}], \"train_return\": -0.3237698852226215, \"train_returns_over_hodl\": -0.22215497036671783, \"train_sharpe\": 0.09242485529792457, \"validation_return\": -0.3387139261774602, \"validation_returns_over_hodl\": -0.016294839426605923, \"validation_sharpe\": -2.239986988365486}, {\"centeredness_margin\": 0.7652873777750951, \"continuous_test_metrics\": [{\"annualised_returns\": -0.49694648409050834, \"annualised_returns_over_hodl\": -0.024132263827931766, \"annualised_returns_over_uniform_hodl\": 0.7755559550678879, \"calmar\": -0.8572648639352268, \"daily_log_sharpe\": -0.7265667179127495, \"daily_returns\": 0.031016042522987296, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": -0.04442945304743087, \"return\": -0.24172149039469137, \"returns_over_hodl\": -0.009789930882927012, \"returns_over_uniform_hodl\": 0.26013298222404546, \"sharpe\": -0.24141405119978449, \"sterling\": -1.2794118084362072, \"ulcer\": -0.17641031604149685}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -0.04357800381459531, \"optuna_trial_number\": 156, \"price_ratio\": 7.421300714555157, \"shift_exponent\": 0.00017910813789243546, \"step\": 156, \"test_objective\": [{\"annualised_returns\": -0.49694648409050834, \"annualised_returns_over_hodl\": -0.024132263827931766, \"annualised_returns_over_uniform_hodl\": 0.7755559550678879, \"calmar\": -0.8572648639352268, \"daily_log_sharpe\": -0.7265667179127495, \"daily_returns\": 0.031016042522987296, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": -0.04442945304743087, \"return\": -0.24172149039469137, \"returns_over_hodl\": -0.009789930882927012, \"returns_over_uniform_hodl\": 0.26013298222404546, \"sharpe\": -0.24141405119978449, \"sterling\": -1.2794118084362072, \"ulcer\": -0.17641031604149685}], \"train_objective\": [{\"annualised_returns\": -0.45865017939936426, \"annualised_returns_over_hodl\": -0.31825750487992444, \"annualised_returns_over_uniform_hodl\": -0.31825750487992444, \"calmar\": -0.6135129058798366, \"daily_log_sharpe\": -0.45822231820651665, \"daily_returns\": 0.008150480427408967, \"fee_revenue_over_value\": 0.0007905023896099589, \"jax_sharpe\": 0.09618157736164078, \"return\": -0.3110482739887235, \"returns_over_hodl\": -0.20752172370855604, \"returns_over_uniform_hodl\": -0.20752172370855604, \"sharpe\": 0.11155834414344717, \"sterling\": -1.381844635650734, \"ulcer\": -0.17978482147425826}], \"train_return\": -0.3110482739887235, \"train_returns_over_hodl\": -0.20752172370855604, \"train_sharpe\": 0.09618157736164076, \"validation_return\": -0.3352362663291527, \"validation_returns_over_hodl\": -0.04357800381459531, \"validation_sharpe\": -2.2093435089552353}, {\"centeredness_margin\": 0.37530690902536423, \"continuous_test_metrics\": [{\"annualised_returns\": -0.4799077970823752, \"annualised_returns_over_hodl\": 0.008920890559269479, \"annualised_returns_over_uniform_hodl\": 0.835694968566546, \"calmar\": -0.8278719746663458, \"daily_log_sharpe\": -0.689632252565913, \"daily_returns\": 0.031016042493643814, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": -0.011947279379310624, \"return\": -0.23148063488254533, \"returns_over_hodl\": 0.003583253881560111, \"returns_over_uniform_hodl\": 0.2771515837452241, \"sharpe\": -0.201348156565729, \"sterling\": -1.2355448418854562, \"ulcer\": -0.17641031598083617}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -7.554163961032145e-10, \"optuna_trial_number\": 157, \"price_ratio\": 2.9320049509977304, \"shift_exponent\": 0.001465320531274323, \"step\": 157, \"test_objective\": [{\"annualised_returns\": -0.4799077970823752, \"annualised_returns_over_hodl\": 0.008920890559269479, \"annualised_returns_over_uniform_hodl\": 0.835694968566546, \"calmar\": -0.8278719746663458, \"daily_log_sharpe\": -0.689632252565913, \"daily_returns\": 0.031016042493643814, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": -0.011947279379310624, \"return\": -0.23148063488254533, \"returns_over_hodl\": 0.003583253881560111, \"returns_over_uniform_hodl\": 0.2771515837452241, \"sharpe\": -0.201348156565729, \"sterling\": -1.2355448418854562, \"ulcer\": -0.17641031598083617}], \"train_objective\": [{\"annualised_returns\": -0.5329475714904125, \"annualised_returns_over_hodl\": -0.4118230470442624, \"annualised_returns_over_uniform_hodl\": -0.4118230470442624, \"calmar\": -0.6981937967373931, \"daily_log_sharpe\": -0.5263931407695759, \"daily_returns\": 0.008346882548365726, \"fee_revenue_over_value\": 0.012286222944864537, \"jax_sharpe\": 0.13246312883770672, \"return\": -0.37010986586165995, \"returns_over_hodl\": -0.2754583101998528, \"returns_over_uniform_hodl\": -0.2754583101998528, \"sharpe\": 0.10549962586681483, \"sterling\": -1.5073719714375127, \"ulcer\": -0.19244614891483192}], \"train_return\": -0.37010986586165995, \"train_returns_over_hodl\": -0.2754583101998528, \"train_sharpe\": 0.13246312883770672, \"validation_return\": -0.3391427198876267, \"validation_returns_over_hodl\": -7.554163961032145e-10, \"validation_sharpe\": -2.2438531988321446}, {\"centeredness_margin\": 0.8350945207697309, \"continuous_test_metrics\": [{\"annualised_returns\": -0.692932614512124, \"annualised_returns_over_hodl\": 0.06792423949948012, \"annualised_returns_over_uniform_hodl\": 0.08381177681346097, \"calmar\": -1.191945321277905, \"daily_log_sharpe\": -1.5284607364598881, \"daily_returns\": 0.018420119767090615, \"fee_revenue_over_value\": 0.04265162876685219, \"jax_sharpe\": -1.0007005129631112, \"return\": -0.37843061865572514, \"returns_over_hodl\": 0.026819993503525152, \"returns_over_uniform_hodl\": 0.032945109548482954, \"sharpe\": -1.1459557338730257, \"sterling\": -2.136374163521669, \"ulcer\": -0.14695346231285059}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -0.02428395094790914, \"optuna_trial_number\": 158, \"price_ratio\": 54.54903945728313, \"shift_exponent\": 0.14221383769620335, \"step\": 158, \"test_objective\": [{\"annualised_returns\": -0.692932614512124, \"annualised_returns_over_hodl\": 0.06792423949948012, \"annualised_returns_over_uniform_hodl\": 0.08381177681346097, \"calmar\": -1.191945321277905, \"daily_log_sharpe\": -1.5284607364598881, \"daily_returns\": 0.018420119767090615, \"fee_revenue_over_value\": 0.04265162876685219, \"jax_sharpe\": -1.0007005129631112, \"return\": -0.37843061865572514, \"returns_over_hodl\": 0.026819993503525152, \"returns_over_uniform_hodl\": 0.032945109548482954, \"sharpe\": -1.1459557338730257, \"sterling\": -2.136374163521669, \"ulcer\": -0.14695346231285059}], \"train_objective\": [{\"annualised_returns\": -0.30437400518557145, \"annualised_returns_over_hodl\": -0.12397162919718063, \"annualised_returns_over_uniform_hodl\": -0.12397162919718063, \"calmar\": -0.4171754462443346, \"daily_log_sharpe\": -0.30853748389287355, \"daily_returns\": 0.008010417274898094, \"fee_revenue_over_value\": 0.02075932205860023, \"jax_sharpe\": 0.13876224034948592, \"return\": -0.19776246276763565, \"returns_over_hodl\": -0.07721281959341297, \"returns_over_uniform_hodl\": -0.07721281959341297, \"sharpe\": 0.17991433815088168, \"sterling\": -1.0087451673869776, \"ulcer\": -0.16304425238376216}], \"train_return\": -0.19776246276763565, \"train_returns_over_hodl\": -0.07721281959341297, \"train_sharpe\": 0.13876224034948592, \"validation_return\": -0.15963013748084232, \"validation_returns_over_hodl\": -0.02428395094790914, \"validation_sharpe\": -1.2605763742242095}, {\"centeredness_margin\": 0.2911873497372549, \"continuous_test_metrics\": [{\"annualised_returns\": -0.48450645989996777, \"annualised_returns_over_hodl\": 1.0237144465463643e-11, \"annualised_returns_over_uniform_hodl\": 0.8194637269731666, \"calmar\": -0.8358049788925557, \"daily_log_sharpe\": -0.692463613919068, \"daily_returns\": 0.03101604242927675, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": 0.005019263646105225, \"return\": -0.23422460335572148, \"returns_over_hodl\": 4.122924224247981e-12, \"returns_over_uniform_hodl\": 0.2725915637374936, \"sharpe\": -0.20210854964387917, \"sterling\": -1.2473843337740504, \"ulcer\": -0.17641031584776845}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -1.3984351454610078e-09, \"optuna_trial_number\": 159, \"price_ratio\": 1.274956150288729, \"shift_exponent\": 0.0003338409264154472, \"step\": 159, \"test_objective\": [{\"annualised_returns\": -0.48450645989996777, \"annualised_returns_over_hodl\": 1.0237144465463643e-11, \"annualised_returns_over_uniform_hodl\": 0.8194637269731666, \"calmar\": -0.8358049788925557, \"daily_log_sharpe\": -0.692463613919068, \"daily_returns\": 0.03101604242927675, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": 0.005019263646105225, \"return\": -0.23422460335572148, \"returns_over_hodl\": 4.122924224247981e-12, \"returns_over_uniform_hodl\": 0.2725915637374936, \"sharpe\": -0.20210854964387917, \"sterling\": -1.2473843337740504, \"ulcer\": -0.17641031584776845}], \"train_objective\": [{\"annualised_returns\": -0.6723888018239529, \"annualised_returns_over_hodl\": -0.587426711574394, \"annualised_returns_over_uniform_hodl\": -0.5874267115743945, \"calmar\": -0.8433663511714606, \"daily_log_sharpe\": -0.7576501966405512, \"daily_returns\": 0.009792082664894779, \"fee_revenue_over_value\": 0.00049889081169413, \"jax_sharpe\": 0.03890585703158311, \"return\": -0.4921167081603267, \"returns_over_hodl\": -0.4157987265602101, \"returns_over_uniform_hodl\": -0.41579872656021055, \"sharpe\": -0.07778104662055425, \"sterling\": -1.7889685154833004, \"ulcer\": -0.21074026220725073}], \"train_return\": -0.4921167081603267, \"train_returns_over_hodl\": -0.4157987265602101, \"train_sharpe\": 0.03890585703158311, \"validation_return\": -0.3391427196082525, \"validation_returns_over_hodl\": -1.3984351454610078e-09, \"validation_sharpe\": -2.2438532005036143}, {\"centeredness_margin\": 0.3669787113650355, \"continuous_test_metrics\": [{\"annualised_returns\": -0.6256273341112637, \"annualised_returns_over_hodl\": -0.27375876624295403, \"annualised_returns_over_uniform_hodl\": 0.3213695865570325, \"calmar\": -1.1051663556210838, \"daily_log_sharpe\": -1.125855803744476, \"daily_returns\": 0.031212408344689718, \"fee_revenue_over_value\": 0.05613254086698558, \"jax_sharpe\": -0.48606482928334976, \"return\": -0.3267855411898246, \"returns_over_hodl\": -0.12087217636514358, \"returns_over_uniform_hodl\": 0.11877065340857706, \"sharpe\": -0.6900042104904756, \"sterling\": -1.694621293556873, \"ulcer\": -0.16244399533840242}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -0.06773148639789117, \"optuna_trial_number\": 160, \"price_ratio\": 3.2653753666645646, \"shift_exponent\": 0.004668288846329267, \"step\": 160, \"test_objective\": [{\"annualised_returns\": -0.6256273341112637, \"annualised_returns_over_hodl\": -0.27375876624295403, \"annualised_returns_over_uniform_hodl\": 0.3213695865570325, \"calmar\": -1.1051663556210838, \"daily_log_sharpe\": -1.125855803744476, \"daily_returns\": 0.031212408344689718, \"fee_revenue_over_value\": 0.05613254086698558, \"jax_sharpe\": -0.48606482928334976, \"return\": -0.3267855411898246, \"returns_over_hodl\": -0.12087217636514358, \"returns_over_uniform_hodl\": 0.11877065340857706, \"sharpe\": -0.6900042104904756, \"sterling\": -1.694621293556873, \"ulcer\": -0.16244399533840242}], \"train_objective\": [{\"annualised_returns\": -0.4392435013786524, \"annualised_returns_over_hodl\": -0.293817934398211, \"annualised_returns_over_uniform_hodl\": -0.293817934398211, \"calmar\": -0.5824999104763492, \"daily_log_sharpe\": -0.4293094560364036, \"daily_returns\": 0.008332942936209814, \"fee_revenue_over_value\": 0.016314013110822145, \"jax_sharpe\": 0.15150620752906654, \"return\": -0.29615745601957444, \"returns_over_hodl\": -0.1903933106264356, \"returns_over_uniform_hodl\": -0.1903933106264356, \"sharpe\": 0.15309619931909946, \"sterling\": -1.3147161208955835, \"ulcer\": -0.1796072036803215}], \"train_return\": -0.29615745601957444, \"train_returns_over_hodl\": -0.1903933106264356, \"train_sharpe\": 0.15150620752906657, \"validation_return\": -0.2940982152654933, \"validation_returns_over_hodl\": -0.06773148639789117, \"validation_sharpe\": -2.0446141017469968}, {\"centeredness_margin\": 0.7951219197268403, \"continuous_test_metrics\": [{\"annualised_returns\": -0.48410603482972614, \"annualised_returns_over_hodl\": 0.0007767764764181617, \"annualised_returns_over_uniform_hodl\": 0.8208770500005207, \"calmar\": -0.8351142187734055, \"daily_log_sharpe\": -0.6971964160191296, \"daily_returns\": 0.031016042515620155, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": -0.015229012131316791, \"return\": -0.23398509473647255, \"returns_over_hodl\": 0.00031276472867203253, \"returns_over_uniform_hodl\": 0.2729895872958821, \"sharpe\": -0.20921125979201527, \"sterling\": -1.246353417675887, \"ulcer\": -0.17641031602627272}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -0.010620194216714895, \"optuna_trial_number\": 161, \"price_ratio\": 5.923611610580612, \"shift_exponent\": 0.0002630672510092129, \"step\": 161, \"test_objective\": [{\"annualised_returns\": -0.48410603482972614, \"annualised_returns_over_hodl\": 0.0007767764764181617, \"annualised_returns_over_uniform_hodl\": 0.8208770500005207, \"calmar\": -0.8351142187734055, \"daily_log_sharpe\": -0.6971964160191296, \"daily_returns\": 0.031016042515620155, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": -0.015229012131316791, \"return\": -0.23398509473647255, \"returns_over_hodl\": 0.00031276472867203253, \"returns_over_uniform_hodl\": 0.2729895872958821, \"sharpe\": -0.20921125979201527, \"sterling\": -1.246353417675887, \"ulcer\": -0.17641031602627272}], \"train_objective\": [{\"annualised_returns\": -0.47892890217919415, \"annualised_returns_over_hodl\": -0.3437952746171168, \"annualised_returns_over_uniform_hodl\": -0.3437952746171168, \"calmar\": -0.6383784476466682, \"daily_log_sharpe\": -0.4758621705863611, \"daily_returns\": 0.008149063558885547, \"fee_revenue_over_value\": 0.000434957259237582, \"jax_sharpe\": 0.14754263320681654, \"return\": -0.32683410438588456, \"returns_over_hodl\": -0.22567963984500017, \"returns_over_uniform_hodl\": -0.22567963984500017, \"sharpe\": 0.10819370449948706, \"sterling\": -1.4226471128393199, \"ulcer\": -0.1825807699261694}], \"train_return\": -0.32683410438588456, \"train_returns_over_hodl\": -0.22567963984500017, \"train_sharpe\": 0.14754263320681654, \"validation_return\": -0.33893598964146354, \"validation_returns_over_hodl\": -0.010620194216714895, \"validation_sharpe\": -2.2420315312397343}, {\"centeredness_margin\": 0.8660430074695592, \"continuous_test_metrics\": [{\"annualised_returns\": -0.4845064594118357, \"annualised_returns_over_hodl\": 8.926637207196109e-12, \"annualised_returns_over_uniform_hodl\": 0.8194637286960567, \"calmar\": -0.8358049781103748, \"daily_log_sharpe\": -0.6924636126932026, \"daily_returns\": 0.0310160424524839, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": 0.005019265113909665, \"return\": -0.23422460306368442, \"returns_over_hodl\": 3.595124198341182e-12, \"returns_over_uniform_hodl\": 0.2725915642228107, \"sharpe\": -0.2021085482058942, \"sterling\": -1.2473843321379812, \"ulcer\": -0.1764103158957646}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -1.19919330021645e-09, \"optuna_trial_number\": 162, \"price_ratio\": 2.0142169890534443, \"shift_exponent\": 3.23723156623151e-05, \"step\": 162, \"test_objective\": [{\"annualised_returns\": -0.4845064594118357, \"annualised_returns_over_hodl\": 8.926637207196109e-12, \"annualised_returns_over_uniform_hodl\": 0.8194637286960567, \"calmar\": -0.8358049781103748, \"daily_log_sharpe\": -0.6924636126932026, \"daily_returns\": 0.0310160424524839, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": 0.005019265113909665, \"return\": -0.23422460306368442, \"returns_over_hodl\": 3.595124198341182e-12, \"returns_over_uniform_hodl\": 0.2725915642228107, \"sharpe\": -0.2021085482058942, \"sterling\": -1.2473843321379812, \"ulcer\": -0.1764103158957646}], \"train_objective\": [{\"annualised_returns\": -0.6336263494944643, \"annualised_returns_over_hodl\": -0.5386116755986579, \"annualised_returns_over_uniform_hodl\": -0.5386116755986579, \"calmar\": -0.8084257337102565, \"daily_log_sharpe\": -0.6834052008976261, \"daily_returns\": 0.008522035783970356, \"fee_revenue_over_value\": 0.00028452485202164576, \"jax_sharpe\": 0.08151499991584787, \"return\": -0.45643801543823326, \"returns_over_hodl\": -0.3747587119391078, \"returns_over_uniform_hodl\": -0.3747587119391078, \"sharpe\": -0.01409154094931299, \"sterling\": -1.7084550206376126, \"ulcer\": -0.2068626503558142}], \"train_return\": -0.45643801543823326, \"train_returns_over_hodl\": -0.3747587119391078, \"train_sharpe\": 0.08151499991584787, \"validation_return\": -0.3391427197326985, \"validation_returns_over_hodl\": -1.19919330021645e-09, \"validation_sharpe\": -2.2438532001447653}, {\"centeredness_margin\": 0.7154046535739345, \"continuous_test_metrics\": [{\"annualised_returns\": -0.48083690904071485, \"annualised_returns_over_hodl\": 0.007118516029315236, \"annualised_returns_over_uniform_hodl\": 0.8324156151411513, \"calmar\": -0.8294747556655228, \"daily_log_sharpe\": -0.6890380060281092, \"daily_returns\": 0.031016042515859894, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": -0.005199115689549737, \"return\": -0.23203385350298156, \"returns_over_hodl\": 0.0028608241679022317, \"returns_over_uniform_hodl\": 0.2762322262516892, \"sharpe\": -0.1999208254584466, \"sterling\": -1.237936887380556, \"ulcer\": -0.1764103160267544}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -0.01098980525602744, \"optuna_trial_number\": 163, \"price_ratio\": 6.5279126620959325, \"shift_exponent\": 1.042847526793087e-05, \"step\": 163, \"test_objective\": [{\"annualised_returns\": -0.48083690904071485, \"annualised_returns_over_hodl\": 0.007118516029315236, \"annualised_returns_over_uniform_hodl\": 0.8324156151411513, \"calmar\": -0.8294747556655228, \"daily_log_sharpe\": -0.6890380060281092, \"daily_returns\": 0.031016042515859894, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": -0.005199115689549737, \"return\": -0.23203385350298156, \"returns_over_hodl\": 0.0028608241679022317, \"returns_over_uniform_hodl\": 0.2762322262516892, \"sharpe\": -0.1999208254584466, \"sterling\": -1.237936887380556, \"ulcer\": -0.1764103160267544}], \"train_objective\": [{\"annualised_returns\": -0.47757540711555035, \"annualised_returns_over_hodl\": -0.34209076661377547, \"annualised_returns_over_uniform_hodl\": -0.34209076661377547, \"calmar\": -0.6374633764158198, \"daily_log_sharpe\": -0.4757608784989291, \"daily_returns\": 0.008118437803468928, \"fee_revenue_over_value\": 0.0011966191468813518, \"jax_sharpe\": 0.1415209782035108, \"return\": -0.3257730541742264, \"returns_over_hodl\": -0.22445914904564956, \"returns_over_uniform_hodl\": -0.22445914904564956, \"sharpe\": 0.10551560824898255, \"sterling\": -1.4221838185062223, \"ulcer\": -0.18214350953953354}], \"train_return\": -0.3257730541742264, \"train_returns_over_hodl\": -0.22445914904564956, \"train_sharpe\": 0.1415209782035108, \"validation_return\": -0.33894579190253205, \"validation_returns_over_hodl\": -0.01098980525602744, \"validation_sharpe\": -2.2421183480317297}, {\"centeredness_margin\": 0.02470025950266097, \"continuous_test_metrics\": [{\"annualised_returns\": -0.5867249702034851, \"annualised_returns_over_hodl\": 0.0737853516159952, \"annualised_returns_over_uniform_hodl\": 0.45867768940925746, \"calmar\": -1.0397007980451678, \"daily_log_sharpe\": -1.0578857337752994, \"daily_returns\": 0.02441710088948958, \"fee_revenue_over_value\": 0.050408632848709896, \"jax_sharpe\": -0.445894769970994, \"return\": -0.2994405480645832, \"returns_over_hodl\": 0.029085921004470716, \"returns_over_uniform_hodl\": 0.16421349175796163, \"sharpe\": -0.6363471735615159, \"sterling\": -1.6470074189274866, \"ulcer\": -0.1575294129267043}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -0.03923266244866175, \"optuna_trial_number\": 164, \"price_ratio\": 57.72345333982135, \"shift_exponent\": 0.0020067765140462505, \"step\": 164, \"test_objective\": [{\"annualised_returns\": -0.5867249702034851, \"annualised_returns_over_hodl\": 0.0737853516159952, \"annualised_returns_over_uniform_hodl\": 0.45867768940925746, \"calmar\": -1.0397007980451678, \"daily_log_sharpe\": -1.0578857337752994, \"daily_returns\": 0.02441710088948958, \"fee_revenue_over_value\": 0.050408632848709896, \"jax_sharpe\": -0.445894769970994, \"return\": -0.2994405480645832, \"returns_over_hodl\": 0.029085921004470716, \"returns_over_uniform_hodl\": 0.16421349175796163, \"sharpe\": -0.6363471735615159, \"sterling\": -1.6470074189274866, \"ulcer\": -0.1575294129267043}], \"train_objective\": [{\"annualised_returns\": -0.3357297210051453, \"annualised_returns_over_hodl\": -0.1634590791337016, \"annualised_returns_over_uniform_hodl\": -0.16345907913370195, \"calmar\": -0.45905331843601516, \"daily_log_sharpe\": -0.32683315279674324, \"daily_returns\": 0.008021904651540918, \"fee_revenue_over_value\": 0.022417288342898196, \"jax_sharpe\": 0.18110002831276972, \"return\": -0.21991533247382145, \"returns_over_hodl\": -0.10269452946778468, \"returns_over_uniform_hodl\": -0.1026945294677849, \"sharpe\": 0.18809231246108618, \"sterling\": -1.080101623103482, \"ulcer\": -0.16737653687782006}], \"train_return\": -0.21991533247382145, \"train_returns_over_hodl\": -0.10269452946778468, \"train_sharpe\": 0.18110002831276975, \"validation_return\": -0.22516971428206956, \"validation_returns_over_hodl\": -0.03923266244866175, \"validation_sharpe\": -1.6764165077341904}, {\"centeredness_margin\": 0.7902100896844766, \"continuous_test_metrics\": [{\"annualised_returns\": -0.48326579824812976, \"annualised_returns_over_hodl\": 0.0024067425358857975, \"annualised_returns_over_uniform_hodl\": 0.8238427127360686, \"calmar\": -0.8336647537724888, \"daily_log_sharpe\": -0.6898891479699896, \"daily_returns\": 0.031016042496881665, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": 0.006337157934831096, \"return\": -0.23348287974075455, \"returns_over_hodl\": 0.0009685904884748631, \"returns_over_uniform_hodl\": 0.2738241852335199, \"sharpe\": -0.19934781143264307, \"sterling\": -1.2441901862258715, \"ulcer\": -0.17641031598752938}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -7.902544174598347e-10, \"optuna_trial_number\": 165, \"price_ratio\": 4.001746217304903, \"shift_exponent\": 0.0001454708699231197, \"step\": 165, \"test_objective\": [{\"annualised_returns\": -0.48326579824812976, \"annualised_returns_over_hodl\": 0.0024067425358857975, \"annualised_returns_over_uniform_hodl\": 0.8238427127360686, \"calmar\": -0.8336647537724888, \"daily_log_sharpe\": -0.6898891479699896, \"daily_returns\": 0.031016042496881665, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": 0.006337157934831096, \"return\": -0.23348287974075455, \"returns_over_hodl\": 0.0009685904884748631, \"returns_over_uniform_hodl\": 0.2738241852335199, \"sharpe\": -0.19934781143264307, \"sterling\": -1.2441901862258715, \"ulcer\": -0.17641031598752938}], \"train_objective\": [{\"annualised_returns\": -0.53382587553542, \"annualised_returns_over_hodl\": -0.4129291288574112, \"annualised_returns_over_uniform_hodl\": -0.41292912885741107, \"calmar\": -0.7042448841799008, \"daily_log_sharpe\": -0.5364218472947502, \"daily_returns\": 0.008236060826034923, \"fee_revenue_over_value\": 0.00034636141986206395, \"jax_sharpe\": 0.09970142819627864, \"return\": -0.3708292819205071, \"returns_over_hodl\": -0.27628583058586376, \"returns_over_uniform_hodl\": -0.27628583058586365, \"sharpe\": 0.08489689624483997, \"sterling\": -1.5223686911085315, \"ulcer\": -0.19145122410639662}], \"train_return\": -0.3708292819205071, \"train_returns_over_hodl\": -0.27628583058586376, \"train_sharpe\": 0.09970142819627864, \"validation_return\": -0.3391427199464442, \"validation_returns_over_hodl\": -7.902544174598347e-10, \"validation_sharpe\": -2.2438531991982256}, {\"centeredness_margin\": 0.7226872062053351, \"continuous_test_metrics\": [{\"annualised_returns\": -0.49399329666019476, \"annualised_returns_over_hodl\": -0.01840340917159089, \"annualised_returns_over_uniform_hodl\": 0.7859793978279377, \"calmar\": -0.8521704152342053, \"daily_log_sharpe\": -0.7205879324919178, \"daily_returns\": 0.031016042515805538, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": -0.04044505962601471, \"return\": -0.2399318394870732, \"returns_over_hodl\": -0.00745288662356558, \"returns_over_uniform_hodl\": 0.26310708488789447, \"sharpe\": -0.23513286545409157, \"sterling\": -1.271808676693627, \"ulcer\": -0.1764103160266434}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -0.014407192357511422, \"optuna_trial_number\": 166, \"price_ratio\": 5.111531006013917, \"shift_exponent\": 0.0006930954546898814, \"step\": 166, \"test_objective\": [{\"annualised_returns\": -0.49399329666019476, \"annualised_returns_over_hodl\": -0.01840340917159089, \"annualised_returns_over_uniform_hodl\": 0.7859793978279377, \"calmar\": -0.8521704152342053, \"daily_log_sharpe\": -0.7205879324919178, \"daily_returns\": 0.031016042515805538, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": -0.04044505962601471, \"return\": -0.2399318394870732, \"returns_over_hodl\": -0.00745288662356558, \"returns_over_uniform_hodl\": 0.26310708488789447, \"sharpe\": -0.23513286545409157, \"sterling\": -1.271808676693627, \"ulcer\": -0.1764103160266434}], \"train_objective\": [{\"annualised_returns\": -0.4776278848417955, \"annualised_returns_over_hodl\": -0.34215685381777405, \"annualised_returns_over_uniform_hodl\": -0.34215685381777405, \"calmar\": -0.6356929542469559, \"daily_log_sharpe\": -0.4711078468609389, \"daily_returns\": 0.00820295402643206, \"fee_revenue_over_value\": 0.0009841579858216076, \"jax_sharpe\": 0.16282794916063636, \"return\": -0.32581417308507843, \"returns_over_hodl\": -0.22450644676240494, \"returns_over_uniform_hodl\": -0.22450644676240494, \"sharpe\": 0.11706226677099206, \"sterling\": -1.4149134938830452, \"ulcer\": -0.18296022649246446}], \"train_return\": -0.32581417308507843, \"train_returns_over_hodl\": -0.22450644676240494, \"train_sharpe\": 0.16282794916063636, \"validation_return\": -0.3387697115125351, \"validation_returns_over_hodl\": -0.014407192357511422, \"validation_sharpe\": -2.2404902164043534}, {\"centeredness_margin\": 0.8543777289386703, \"continuous_test_metrics\": [{\"annualised_returns\": -0.4824654508801164, \"annualised_returns_over_hodl\": 0.003959326515150519, \"annualised_returns_over_uniform_hodl\": 0.8266675842267883, \"calmar\": -0.8322841004655909, \"daily_log_sharpe\": -0.6934623757368833, \"daily_returns\": 0.031016042513624432, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": -0.011138227217897983, \"return\": -0.23300496015016714, \"returns_over_hodl\": 0.0015926891221471617, \"returns_over_uniform_hodl\": 0.27461840824171735, \"sharpe\": -0.20504953256396163, \"sterling\": -1.2421296510191662, \"ulcer\": -0.17641031602215568}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": 4.124105398028988e-05, \"optuna_trial_number\": 167, \"price_ratio\": 5.430115791518055, \"shift_exponent\": 0.00035617933625367414, \"step\": 167, \"test_objective\": [{\"annualised_returns\": -0.4824654508801164, \"annualised_returns_over_hodl\": 0.003959326515150519, \"annualised_returns_over_uniform_hodl\": 0.8266675842267883, \"calmar\": -0.8322841004655909, \"daily_log_sharpe\": -0.6934623757368833, \"daily_returns\": 0.031016042513624432, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": -0.011138227217897983, \"return\": -0.23300496015016714, \"returns_over_hodl\": 0.0015926891221471617, \"returns_over_uniform_hodl\": 0.27461840824171735, \"sharpe\": -0.20504953256396163, \"sterling\": -1.2421296510191662, \"ulcer\": -0.17641031602215568}], \"train_objective\": [{\"annualised_returns\": -0.4853438436865656, \"annualised_returns_over_hodl\": -0.3518738553478401, \"annualised_returns_over_uniform_hodl\": -0.3518738553478401, \"calmar\": -0.6460166686081407, \"daily_log_sharpe\": -0.48104075836687293, \"daily_returns\": 0.008186217172689336, \"fee_revenue_over_value\": 0.0002932991359445864, \"jax_sharpe\": 0.09872856534118048, \"return\": -0.3318777946128998, \"returns_over_hodl\": -0.23148122910930757, \"returns_over_uniform_hodl\": -0.23148122910930757, \"sharpe\": 0.10824341581633226, \"sterling\": -1.4343843931050901, \"ulcer\": -0.18355780903309776}], \"train_return\": -0.3318777946128998, \"train_returns_over_hodl\": -0.23148122910930757, \"train_sharpe\": 0.09872856534118048, \"validation_return\": -0.3391154650777809, \"validation_returns_over_hodl\": 4.124105398028988e-05, \"validation_sharpe\": -2.243596825427176}, {\"centeredness_margin\": 0.6494430660742272, \"continuous_test_metrics\": [{\"annualised_returns\": -0.4839900853929172, \"annualised_returns_over_hodl\": 0.0010017063222160782, \"annualised_returns_over_uniform_hodl\": 0.8212863001230257, \"calmar\": -0.8349141980946481, \"daily_log_sharpe\": -0.6913886696337105, \"daily_returns\": 0.03101604249446176, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": 0.005610271317079681, \"return\": -0.23391576194468033, \"returns_over_hodl\": 0.0004033044074325254, \"returns_over_uniform_hodl\": 0.2731048068841735, \"sharpe\": -0.20095370482404504, \"sterling\": -1.2460549003061483, \"ulcer\": -0.1764103159825224}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -8.143157259610234e-10, \"optuna_trial_number\": 168, \"price_ratio\": 3.6544323271007264, \"shift_exponent\": 0.00019775768292466553, \"step\": 168, \"test_objective\": [{\"annualised_returns\": -0.4839900853929172, \"annualised_returns_over_hodl\": 0.0010017063222160782, \"annualised_returns_over_uniform_hodl\": 0.8212863001230257, \"calmar\": -0.8349141980946481, \"daily_log_sharpe\": -0.6913886696337105, \"daily_returns\": 0.03101604249446176, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": 0.005610271317079681, \"return\": -0.23391576194468033, \"returns_over_hodl\": 0.0004033044074325254, \"returns_over_uniform_hodl\": 0.2731048068841735, \"sharpe\": -0.20095370482404504, \"sterling\": -1.2460549003061483, \"ulcer\": -0.1764103159825224}], \"train_objective\": [{\"annualised_returns\": -0.5398723187249773, \"annualised_returns_over_hodl\": -0.42054364558908175, \"annualised_returns_over_uniform_hodl\": -0.42054364558908197, \"calmar\": -0.7104528092153062, \"daily_log_sharpe\": -0.5408601675397298, \"daily_returns\": 0.00827785487270843, \"fee_revenue_over_value\": 0.0012763265970846787, \"jax_sharpe\": 0.1085397743151279, \"return\": -0.37579643644058336, \"returns_over_hodl\": -0.2819993833697909, \"returns_over_uniform_hodl\": -0.281999383369791, \"sharpe\": 0.08791499290221115, \"sterling\": -1.5257058477332701, \"ulcer\": -0.19344914619213627}], \"train_return\": -0.37579643644058336, \"train_returns_over_hodl\": -0.2819993833697909, \"train_sharpe\": 0.1085397743151279, \"validation_return\": -0.3391427199415147, \"validation_returns_over_hodl\": -8.143157259610234e-10, \"validation_sharpe\": -2.2438531993168866}, {\"centeredness_margin\": 0.6738710430708923, \"continuous_test_metrics\": [{\"annualised_returns\": -0.5318966704011785, \"annualised_returns_over_hodl\": -0.09193172805269711, \"annualised_returns_over_uniform_hodl\": 0.652197287506546, \"calmar\": -0.9182915350778587, \"daily_log_sharpe\": -0.8089714817054742, \"daily_returns\": 0.031016042488196713, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": -0.12874692142593228, \"return\": -0.2633958760520385, \"returns_over_hodl\": -0.038093772321311725, \"returns_over_uniform_hodl\": 0.2241137519672316, \"sharpe\": -0.3302044949468196, \"sterling\": -1.3711117713027003, \"ulcer\": -0.17593215894633704}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -0.05511588700698222, \"optuna_trial_number\": 169, \"price_ratio\": 5.4854940616007895, \"shift_exponent\": 0.0010846306542197867, \"step\": 169, \"test_objective\": [{\"annualised_returns\": -0.5318966704011785, \"annualised_returns_over_hodl\": -0.09193172805269711, \"annualised_returns_over_uniform_hodl\": 0.652197287506546, \"calmar\": -0.9182915350778587, \"daily_log_sharpe\": -0.8089714817054742, \"daily_returns\": 0.031016042488196713, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": -0.12874692142593228, \"return\": -0.2633958760520385, \"returns_over_hodl\": -0.038093772321311725, \"returns_over_uniform_hodl\": 0.2241137519672316, \"sharpe\": -0.3302044949468196, \"sterling\": -1.3711117713027003, \"ulcer\": -0.17593215894633704}], \"train_objective\": [{\"annualised_returns\": -0.45332266528485854, \"annualised_returns_over_hodl\": -0.31154836297759414, \"annualised_returns_over_uniform_hodl\": -0.31154836297759425, \"calmar\": -0.6046639367247545, \"daily_log_sharpe\": -0.44803155318630566, \"daily_returns\": 0.00815445634038461, \"fee_revenue_over_value\": 0.0017129131457511706, \"jax_sharpe\": 0.11519233182821018, \"return\": -0.30693985839638327, \"returns_over_hodl\": -0.2027959497769678, \"returns_over_uniform_hodl\": -0.2027959497769679, \"sharpe\": 0.12671508047292698, \"sterling\": -1.3620093711566321, \"ulcer\": -0.17996474187831182}], \"train_return\": -0.30693985839638327, \"train_returns_over_hodl\": -0.2027959497769678, \"train_sharpe\": 0.11519233182821016, \"validation_return\": -0.33234099255217653, \"validation_returns_over_hodl\": -0.05511588700698222, \"validation_sharpe\": -2.1843603378351757}, {\"centeredness_margin\": 0.7209898964225288, \"continuous_test_metrics\": [{\"annualised_returns\": -0.48225215423629175, \"annualised_returns_over_hodl\": 0.004373098935800845, \"annualised_returns_over_uniform_hodl\": 0.8274204268452408, \"calmar\": -0.8319161492835282, \"daily_log_sharpe\": -0.6878579699790774, \"daily_returns\": 0.03101604249628622, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": 0.007112037030175217, \"return\": -0.2328776666568293, \"returns_over_hodl\": 0.0017589176437351473, \"returns_over_uniform_hodl\": 0.27482994889247525, \"sharpe\": -0.1972230046990114, \"sterling\": -1.241580508264935, \"ulcer\": -0.17641031598629436}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -7.790625922154959e-10, \"optuna_trial_number\": 170, \"price_ratio\": 3.818830632217166, \"shift_exponent\": 0.00029005184384419135, \"step\": 170, \"test_objective\": [{\"annualised_returns\": -0.48225215423629175, \"annualised_returns_over_hodl\": 0.004373098935800845, \"annualised_returns_over_uniform_hodl\": 0.8274204268452408, \"calmar\": -0.8319161492835282, \"daily_log_sharpe\": -0.6878579699790774, \"daily_returns\": 0.03101604249628622, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": 0.007112037030175217, \"return\": -0.2328776666568293, \"returns_over_hodl\": 0.0017589176437351473, \"returns_over_uniform_hodl\": 0.27482994889247525, \"sharpe\": -0.1972230046990114, \"sterling\": -1.241580508264935, \"ulcer\": -0.17641031598629436}], \"train_objective\": [{\"annualised_returns\": -0.5360405695665161, \"annualised_returns_over_hodl\": -0.4157181775967489, \"annualised_returns_over_uniform_hodl\": -0.415718177596749, \"calmar\": -0.7062095106808937, \"daily_log_sharpe\": -0.5387133241998567, \"daily_returns\": 0.008221292017863547, \"fee_revenue_over_value\": 0.0007052175865104346, \"jax_sharpe\": 0.1019135577667958, \"return\": -0.3726456991484959, \"returns_over_hodl\": -0.2783751949629536, \"returns_over_uniform_hodl\": -0.2783751949629537, \"sharpe\": 0.0845769032876402, \"sterling\": -1.5256112333675729, \"ulcer\": -0.191836596282181}], \"train_return\": -0.3726456991484959, \"train_returns_over_hodl\": -0.2783751949629536, \"train_sharpe\": 0.1019135577667958, \"validation_return\": -0.3391427199383391, \"validation_returns_over_hodl\": -7.790625922154959e-10, \"validation_sharpe\": -2.243853199161362}, {\"centeredness_margin\": 0.9167530945356379, \"continuous_test_metrics\": [{\"annualised_returns\": -0.7173876108228171, \"annualised_returns_over_hodl\": -0.2209383781238019, \"annualised_returns_over_uniform_hodl\": -0.002503521574050205, \"calmar\": -1.2232328376347543, \"daily_log_sharpe\": -1.5605705819347857, \"daily_returns\": 0.02305268858647519, \"fee_revenue_over_value\": 0.01648904370436123, \"jax_sharpe\": -0.9651857237542543, \"return\": -0.3988623392160825, \"returns_over_hodl\": -0.09565973675919404, \"returns_over_uniform_hodl\": -0.0010090176429797815, \"sharpe\": -1.1659506100639179, \"sterling\": -2.0705289747508657, \"ulcer\": -0.15210315617808398}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -0.04693236438793413, \"optuna_trial_number\": 171, \"price_ratio\": 8.218627841074607, \"shift_exponent\": 0.003018647200289077, \"step\": 171, \"test_objective\": [{\"annualised_returns\": -0.7173876108228171, \"annualised_returns_over_hodl\": -0.2209383781238019, \"annualised_returns_over_uniform_hodl\": -0.002503521574050205, \"calmar\": -1.2232328376347543, \"daily_log_sharpe\": -1.5605705819347857, \"daily_returns\": 0.02305268858647519, \"fee_revenue_over_value\": 0.01648904370436123, \"jax_sharpe\": -0.9651857237542543, \"return\": -0.3988623392160825, \"returns_over_hodl\": -0.09565973675919404, \"returns_over_uniform_hodl\": -0.0010090176429797815, \"sharpe\": -1.1659506100639179, \"sterling\": -2.0705289747508657, \"ulcer\": -0.15210315617808398}], \"train_objective\": [{\"annualised_returns\": -0.35424981105821585, \"annualised_returns_over_hodl\": -0.1867821355422552, \"annualised_returns_over_uniform_hodl\": -0.1867821355422553, \"calmar\": -0.4772445681821627, \"daily_log_sharpe\": -0.3541768447228564, \"daily_returns\": 0.008098592937996979, \"fee_revenue_over_value\": 0.0005727435000658088, \"jax_sharpe\": 0.1494397579491378, \"return\": -0.2331929067458256, \"returns_over_hodl\": -0.11796728193380779, \"returns_over_uniform_hodl\": -0.1179672819338079, \"sharpe\": 0.16908708280163348, \"sterling\": -1.1278843363964695, \"ulcer\": -0.1688825626814223}], \"train_return\": -0.2331929067458256, \"train_returns_over_hodl\": -0.11796728193380779, \"train_sharpe\": 0.1494397579491378, \"validation_return\": -0.20869473666723914, \"validation_returns_over_hodl\": -0.04693236438793413, \"validation_sharpe\": -1.6017773961490231}, {\"centeredness_margin\": 0.95488228170915, \"continuous_test_metrics\": [{\"annualised_returns\": -0.4846887211758202, \"annualised_returns_over_hodl\": -0.00035357014276882204, \"annualised_returns_over_uniform_hodl\": 0.8188204254486109, \"calmar\": -0.8361193921553034, \"daily_log_sharpe\": -0.6984938553798579, \"daily_returns\": 0.03101604251734431, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": -0.016120011306533986, \"return\": -0.23433365717196497, \"returns_over_hodl\": -0.00014241110774204646, \"returns_over_uniform_hodl\": 0.27241033440164286, \"sharpe\": -0.21065475636502215, \"sterling\": -1.2478535732505804, \"ulcer\": -0.17641031602982452}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -0.019702190470703873, \"optuna_trial_number\": 172, \"price_ratio\": 6.476274446856817, \"shift_exponent\": 0.00014309246459357332, \"step\": 172, \"test_objective\": [{\"annualised_returns\": -0.4846887211758202, \"annualised_returns_over_hodl\": -0.00035357014276882204, \"annualised_returns_over_uniform_hodl\": 0.8188204254486109, \"calmar\": -0.8361193921553034, \"daily_log_sharpe\": -0.6984938553798579, \"daily_returns\": 0.03101604251734431, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": -0.016120011306533986, \"return\": -0.23433365717196497, \"returns_over_hodl\": -0.00014241110774204646, \"returns_over_uniform_hodl\": 0.27241033440164286, \"sharpe\": -0.21065475636502215, \"sterling\": -1.2478535732505804, \"ulcer\": -0.17641031602982452}], \"train_objective\": [{\"annualised_returns\": -0.4739708691509984, \"annualised_returns_over_hodl\": -0.3375514343517233, \"annualised_returns_over_uniform_hodl\": -0.3375514343517231, \"calmar\": -0.6325707093212867, \"daily_log_sharpe\": -0.47215848436454805, \"daily_returns\": 0.008137336262178626, \"fee_revenue_over_value\": 0.00018466677997773726, \"jax_sharpe\": 0.09427809815089318, \"return\": -0.32295259067809245, \"returns_over_hodl\": -0.22121486361116827, \"returns_over_uniform_hodl\": -0.22121486361116816, \"sharpe\": 0.1073855242624297, \"sterling\": -1.4139720246372975, \"ulcer\": -0.1817791669738168}], \"train_return\": -0.32295259067809245, \"train_returns_over_hodl\": -0.22121486361116827, \"train_sharpe\": 0.09427809815089318, \"validation_return\": -0.33852701914641636, \"validation_returns_over_hodl\": -0.019702190470703873, \"validation_sharpe\": -2.238263771380773}, {\"centeredness_margin\": 0.9270056381835261, \"continuous_test_metrics\": [{\"annualised_returns\": -0.4816197987106462, \"annualised_returns_over_hodl\": 0.005599797930337269, \"annualised_returns_over_uniform_hodl\": 0.8296523615873119, \"calmar\": -0.8308252932490827, \"daily_log_sharpe\": -0.6866431845324515, \"daily_returns\": 0.031016042501792046, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": 0.007081164633648276, \"return\": -0.2325004666134699, \"returns_over_hodl\": 0.0022514902375756485, \"returns_over_uniform_hodl\": 0.2754567927361451, \"sharpe\": -0.19599235071939747, \"sterling\": -1.239952476782588, \"ulcer\": -0.1764103159976708}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -7.341678376349137e-10, \"optuna_trial_number\": 173, \"price_ratio\": 4.46046548076666, \"shift_exponent\": 0.00011311469252057019, \"step\": 173, \"test_objective\": [{\"annualised_returns\": -0.4816197987106462, \"annualised_returns_over_hodl\": 0.005599797930337269, \"annualised_returns_over_uniform_hodl\": 0.8296523615873119, \"calmar\": -0.8308252932490827, \"daily_log_sharpe\": -0.6866431845324515, \"daily_returns\": 0.031016042501792046, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": 0.007081164633648276, \"return\": -0.2325004666134699, \"returns_over_hodl\": 0.0022514902375756485, \"returns_over_uniform_hodl\": 0.2754567927361451, \"sharpe\": -0.19599235071939747, \"sterling\": -1.239952476782588, \"ulcer\": -0.1764103159976708}], \"train_objective\": [{\"annualised_returns\": -0.5210570163209347, \"annualised_returns_over_hodl\": -0.3968488169972135, \"annualised_returns_over_uniform_hodl\": -0.39684881699721364, \"calmar\": -0.6895654081018707, \"daily_log_sharpe\": -0.5217814403519752, \"daily_returns\": 0.008163487171033448, \"fee_revenue_over_value\": 0.00023063571056059827, \"jax_sharpe\": 0.09617074280653921, \"return\": -0.3604220728007802, \"returns_over_hodl\": -0.26431476377113183, \"returns_over_uniform_hodl\": -0.26431476377113194, \"sharpe\": 0.09023390176304313, \"sterling\": -1.5026426496101768, \"ulcer\": -0.18897173446434035}], \"train_return\": -0.3604220728007802, \"train_returns_over_hodl\": -0.26431476377113183, \"train_sharpe\": 0.0961707428065392, \"validation_return\": -0.3391427199636341, \"validation_returns_over_hodl\": -7.341678376349137e-10, \"validation_sharpe\": -2.2438531990293686}, {\"centeredness_margin\": 0.9005939608860125, \"continuous_test_metrics\": [{\"annualised_returns\": -0.7257083982539063, \"annualised_returns_over_hodl\": -0.2795151335368786, \"annualised_returns_over_uniform_hodl\": -0.031872213386915704, \"calmar\": -1.2327346107003825, \"daily_log_sharpe\": -1.5784262087797236, \"daily_returns\": 0.024107335003225486, \"fee_revenue_over_value\": 0.015210171298530858, \"jax_sharpe\": -0.9597127766318847, \"return\": -0.4060540445715376, \"returns_over_hodl\": -0.12368519855947568, \"returns_over_uniform_hodl\": -0.012960437869254382, \"sharpe\": -1.1811937482026238, \"sterling\": -2.0667467571622273, \"ulcer\": -0.15255111073905966}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -0.05243812842848039, \"optuna_trial_number\": 174, \"price_ratio\": 5.201802660238653, \"shift_exponent\": 0.0034223157270401273, \"step\": 174, \"test_objective\": [{\"annualised_returns\": -0.7257083982539063, \"annualised_returns_over_hodl\": -0.2795151335368786, \"annualised_returns_over_uniform_hodl\": -0.031872213386915704, \"calmar\": -1.2327346107003825, \"daily_log_sharpe\": -1.5784262087797236, \"daily_returns\": 0.024107335003225486, \"fee_revenue_over_value\": 0.015210171298530858, \"jax_sharpe\": -0.9597127766318847, \"return\": -0.4060540445715376, \"returns_over_hodl\": -0.12368519855947568, \"returns_over_uniform_hodl\": -0.012960437869254382, \"sharpe\": -1.1811937482026238, \"sterling\": -2.0667467571622273, \"ulcer\": -0.15255111073905966}], \"train_objective\": [{\"annualised_returns\": -0.3630719307839335, \"annualised_returns_over_hodl\": -0.19789216769740703, \"annualised_returns_over_uniform_hodl\": -0.19789216769740692, \"calmar\": -0.4869189311534548, \"daily_log_sharpe\": -0.35914977534584286, \"daily_returns\": 0.008207566904756899, \"fee_revenue_over_value\": 0.0008464262865503386, \"jax_sharpe\": 0.16094226458454927, \"return\": -0.23957027439184397, \"returns_over_hodl\": -0.12530295601456443, \"returns_over_uniform_hodl\": -0.12530295601456432, \"sharpe\": 0.17575012818414462, \"sterling\": -1.1436480753671516, \"ulcer\": -0.17048863007038434}], \"train_return\": -0.23957027439184397, \"train_returns_over_hodl\": -0.12530295601456443, \"train_sharpe\": 0.16094226458454927, \"validation_return\": -0.21721473286019577, \"validation_returns_over_hodl\": -0.05243812842848039, \"validation_sharpe\": -1.6540956819962396}, {\"centeredness_margin\": 0.41141946307707017, \"continuous_test_metrics\": [{\"annualised_returns\": -0.7424318756143709, \"annualised_returns_over_hodl\": -0.18992487265839886, \"annualised_returns_over_uniform_hodl\": -0.09089867653195871, \"calmar\": -1.2409156899980613, \"daily_log_sharpe\": -1.7000713422176907, \"daily_returns\": 0.020434365297450144, \"fee_revenue_over_value\": 0.049607324962657205, \"jax_sharpe\": -1.2162011134554525, \"return\": -0.42091279026594797, \"returns_over_hodl\": -0.08132969712053628, \"returns_over_uniform_hodl\": -0.03765320614215717, \"sharpe\": -1.325320336748373, \"sterling\": -2.2672340698334663, \"ulcer\": -0.1534745223171961}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -0.03101699600421684, \"optuna_trial_number\": 175, \"price_ratio\": 16.171058567368064, \"shift_exponent\": 73.50447327566185, \"step\": 175, \"test_objective\": [{\"annualised_returns\": -0.7424318756143709, \"annualised_returns_over_hodl\": -0.18992487265839886, \"annualised_returns_over_uniform_hodl\": -0.09089867653195871, \"calmar\": -1.2409156899980613, \"daily_log_sharpe\": -1.7000713422176907, \"daily_returns\": 0.020434365297450144, \"fee_revenue_over_value\": 0.049607324962657205, \"jax_sharpe\": -1.2162011134554525, \"return\": -0.42091279026594797, \"returns_over_hodl\": -0.08132969712053628, \"returns_over_uniform_hodl\": -0.03765320614215717, \"sharpe\": -1.325320336748373, \"sterling\": -2.2672340698334663, \"ulcer\": -0.1534745223171961}], \"train_objective\": [{\"annualised_returns\": -0.32854168173623977, \"annualised_returns_over_hodl\": -0.15440690687886027, \"annualised_returns_over_uniform_hodl\": -0.15440690687886016, \"calmar\": -0.4469830731698721, \"daily_log_sharpe\": -0.3329542890569601, \"daily_returns\": 0.008046122852962913, \"fee_revenue_over_value\": 0.025766834488295795, \"jax_sharpe\": 0.12509894168143484, \"return\": -0.2148013013744513, \"returns_over_hodl\": -0.09681202943546297, \"returns_over_uniform_hodl\": -0.09681202943546285, \"sharpe\": 0.16650000519856806, \"sterling\": -1.0701337297024736, \"ulcer\": -0.165457441332064}], \"train_return\": -0.2148013013744513, \"train_returns_over_hodl\": -0.09681202943546297, \"train_sharpe\": 0.12509894168143484, \"validation_return\": -0.18649086425822892, \"validation_returns_over_hodl\": -0.031016996004216812, \"validation_sharpe\": -1.4808918173758452}, {\"centeredness_margin\": 0.5612588397974551, \"continuous_test_metrics\": [{\"annualised_returns\": -0.5958021323362007, \"annualised_returns_over_hodl\": -0.13094111232280936, \"annualised_returns_over_uniform_hodl\": 0.42663933012908983, \"calmar\": -1.024149623792237, \"daily_log_sharpe\": -1.000842176510152, \"daily_returns\": 0.0285743495901564, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": -0.32943649386039947, \"return\": -0.30567863188186695, \"returns_over_hodl\": -0.054954301652955784, \"returns_over_uniform_hodl\": 0.1538468321936164, \"sharpe\": -0.5467025380975747, \"sterling\": -1.578223362060039, \"ulcer\": -0.16769359218470006}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -0.05578996035569006, \"optuna_trial_number\": 176, \"price_ratio\": 22.420897264524708, \"shift_exponent\": 1.2127084728119653e-05, \"step\": 176, \"test_objective\": [{\"annualised_returns\": -0.5958021323362007, \"annualised_returns_over_hodl\": -0.13094111232280936, \"annualised_returns_over_uniform_hodl\": 0.42663933012908983, \"calmar\": -1.024149623792237, \"daily_log_sharpe\": -1.000842176510152, \"daily_returns\": 0.0285743495901564, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": -0.32943649386039947, \"return\": -0.30567863188186695, \"returns_over_hodl\": -0.054954301652955784, \"returns_over_uniform_hodl\": 0.1538468321936164, \"sharpe\": -0.5467025380975747, \"sterling\": -1.578223362060039, \"ulcer\": -0.16769359218470006}], \"train_objective\": [{\"annualised_returns\": -0.3897006698546427, \"annualised_returns_over_hodl\": -0.23142675536167323, \"annualised_returns_over_uniform_hodl\": -0.23142675536167323, \"calmar\": -0.5279156659593692, \"daily_log_sharpe\": -0.39174696486026644, \"daily_returns\": 0.008057396142696073, \"fee_revenue_over_value\": 0.008666553872043223, \"jax_sharpe\": 0.11335034483758082, \"return\": -0.2590336179129109, \"returns_over_hodl\": -0.1476909932922168, \"returns_over_uniform_hodl\": -0.1476909932922168, \"sharpe\": 0.13864342992590825, \"sterling\": -1.226986696255497, \"ulcer\": -0.1712954748953189}], \"train_return\": -0.2590336179129109, \"train_returns_over_hodl\": -0.1476909932922168, \"train_sharpe\": 0.11335034483758082, \"validation_return\": -0.2706189519169795, \"validation_returns_over_hodl\": -0.05578996035569006, \"validation_sharpe\": -1.9145593604937454}, {\"centeredness_margin\": 0.7124356225277844, \"continuous_test_metrics\": [{\"annualised_returns\": -0.5871761367796575, \"annualised_returns_over_hodl\": -0.14219652095155522, \"annualised_returns_over_uniform_hodl\": 0.45708527135488386, \"calmar\": -1.0109959143689593, \"daily_log_sharpe\": -0.9668439498351925, \"daily_returns\": 0.029385967937648496, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": -0.29456754150201775, \"return\": -0.29974865878694246, \"returns_over_hodl\": -0.05990282226995758, \"returns_over_uniform_hodl\": 0.16370146289455123, \"sharpe\": -0.5067703879509393, \"sterling\": -1.5466379684326048, \"ulcer\": -0.1691778849251872}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -0.05821399686561357, \"optuna_trial_number\": 177, \"price_ratio\": 18.673970178301502, \"shift_exponent\": 0.00012058956670218138, \"step\": 177, \"test_objective\": [{\"annualised_returns\": -0.5871761367796575, \"annualised_returns_over_hodl\": -0.14219652095155522, \"annualised_returns_over_uniform_hodl\": 0.45708527135488386, \"calmar\": -1.0109959143689593, \"daily_log_sharpe\": -0.9668439498351925, \"daily_returns\": 0.029385967937648496, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": -0.29456754150201775, \"return\": -0.29974865878694246, \"returns_over_hodl\": -0.05990282226995758, \"returns_over_uniform_hodl\": 0.16370146289455123, \"sharpe\": -0.5067703879509393, \"sterling\": -1.5466379684326048, \"ulcer\": -0.1691778849251872}], \"train_objective\": [{\"annualised_returns\": -0.40051645719755047, \"annualised_returns_over_hodl\": -0.2450474892554445, \"annualised_returns_over_uniform_hodl\": -0.24504748925544462, \"calmar\": -0.5410109158943303, \"daily_log_sharpe\": -0.404863359179384, \"daily_returns\": 0.008074985496146285, \"fee_revenue_over_value\": 0.004045813585164046, \"jax_sharpe\": 0.14136976689878092, \"return\": -0.2670340068908331, \"returns_over_hodl\": -0.15689357487741928, \"returns_over_uniform_hodl\": -0.1568935748774194, \"sharpe\": 0.12922556135862273, \"sterling\": -1.2541551441534848, \"ulcer\": -0.17241599603326177}], \"train_return\": -0.2670340068908331, \"train_returns_over_hodl\": -0.15689357487741928, \"train_sharpe\": 0.14136976689878092, \"validation_return\": -0.2779561340907123, \"validation_returns_over_hodl\": -0.05821399686561357, \"validation_sharpe\": -1.9443479651896522}, {\"centeredness_margin\": 0.7753689961116937, \"continuous_test_metrics\": [{\"annualised_returns\": -0.5348068349794509, \"annualised_returns_over_hodl\": -0.09757712335660584, \"annualised_returns_over_uniform_hodl\": 0.6419256963462356, \"calmar\": -0.9256482932257469, \"daily_log_sharpe\": -0.816189748232116, \"daily_returns\": 0.031016042509701518, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": -0.1349356210018986, \"return\": -0.2652436149817694, \"returns_over_hodl\": -0.04050667183328471, \"returns_over_uniform_hodl\": 0.22104311665527332, \"sharpe\": -0.33839001143246217, \"sterling\": -1.381548705516291, \"ulcer\": -0.17510070708936937}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -0.06518925040264123, \"optuna_trial_number\": 178, \"price_ratio\": 9.703682512848097, \"shift_exponent\": 0.00034859508583268606, \"step\": 178, \"test_objective\": [{\"annualised_returns\": -0.5348068349794509, \"annualised_returns_over_hodl\": -0.09757712335660584, \"annualised_returns_over_uniform_hodl\": 0.6419256963462356, \"calmar\": -0.9256482932257469, \"daily_log_sharpe\": -0.816189748232116, \"daily_returns\": 0.031016042509701518, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": -0.1349356210018986, \"return\": -0.2652436149817694, \"returns_over_hodl\": -0.04050667183328471, \"returns_over_uniform_hodl\": 0.22104311665527332, \"sharpe\": -0.33839001143246217, \"sterling\": -1.381548705516291, \"ulcer\": -0.17510070708936937}], \"train_objective\": [{\"annualised_returns\": -0.4326307145969913, \"annualised_returns_over_hodl\": -0.28549019956082866, \"annualised_returns_over_uniform_hodl\": -0.28549019956082866, \"calmar\": -0.5807126844193832, \"daily_log_sharpe\": -0.4351182517860675, \"daily_returns\": 0.008142941960944713, \"fee_revenue_over_value\": 0.0008499290776298665, \"jax_sharpe\": 0.09937928505939247, \"return\": -0.29112987003196256, \"returns_over_hodl\": -0.18461024553356375, \"returns_over_uniform_hodl\": -0.18461024553356375, \"sharpe\": 0.11812845064150806, \"sterling\": -1.3258803401373467, \"ulcer\": -0.17645409623060448}], \"train_return\": -0.29112987003196256, \"train_returns_over_hodl\": -0.18461024553356375, \"train_sharpe\": 0.09937928505939246, \"validation_return\": -0.3164012399089349, \"validation_returns_over_hodl\": -0.06518925040264123, \"validation_sharpe\": -2.0911508240999432}, {\"centeredness_margin\": 0.7780001542606093, \"continuous_test_metrics\": [{\"annualised_returns\": -0.5051912126572551, \"annualised_returns_over_hodl\": -0.04012611735100069, \"annualised_returns_over_uniform_hodl\": 0.7464557173364421, \"calmar\": -0.8714875808102778, \"daily_log_sharpe\": -0.7454454644635375, \"daily_returns\": 0.03101604248718077, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": -0.06373318690144723, \"return\": -0.24675131002123896, \"returns_over_hodl\": -0.016358200663353917, \"returns_over_uniform_hodl\": 0.2517742571306101, \"sharpe\": -0.2618172989288002, \"sterling\": -1.3006382844559985, \"ulcer\": -0.17641031602709378}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -0.05176085318597867, \"optuna_trial_number\": 179, \"price_ratio\": 7.731296014840141, \"shift_exponent\": 0.00024946188217727874, \"step\": 179, \"test_objective\": [{\"annualised_returns\": -0.5051912126572551, \"annualised_returns_over_hodl\": -0.04012611735100069, \"annualised_returns_over_uniform_hodl\": 0.7464557173364421, \"calmar\": -0.8714875808102778, \"daily_log_sharpe\": -0.7454454644635375, \"daily_returns\": 0.03101604248718077, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": -0.06373318690144723, \"return\": -0.24675131002123896, \"returns_over_hodl\": -0.016358200663353917, \"returns_over_uniform_hodl\": 0.2517742571306101, \"sharpe\": -0.2618172989288002, \"sterling\": -1.3006382844559985, \"ulcer\": -0.17641031602709378}], \"train_objective\": [{\"annualised_returns\": -0.45305068076260435, \"annualised_returns_over_hodl\": -0.31120584248570626, \"annualised_returns_over_uniform_hodl\": -0.31120584248570626, \"calmar\": -0.6063967294592586, \"daily_log_sharpe\": -0.4533315764482347, \"daily_returns\": 0.00815674096451733, \"fee_revenue_over_value\": 0.0007095921326079882, \"jax_sharpe\": 0.09657718552954912, \"return\": -0.3067305353057297, \"returns_over_hodl\": -0.20255517238167842, \"returns_over_uniform_hodl\": -0.20255517238167842, \"sharpe\": 0.11288773663565395, \"sterling\": -1.3699446683331236, \"ulcer\": -0.17906082082938746}], \"train_return\": -0.3067305353057297, \"train_returns_over_hodl\": -0.20255517238167842, \"train_sharpe\": 0.09657718552954911, \"validation_return\": -0.33271598780035094, \"validation_returns_over_hodl\": -0.05176085318597867, \"validation_sharpe\": -2.187075648609896}, {\"centeredness_margin\": 0.8100182433733016, \"continuous_test_metrics\": [{\"annualised_returns\": -0.5154916013333809, \"annualised_returns_over_hodl\": -0.06010772291698485, \"annualised_returns_over_uniform_hodl\": 0.7100999104987862, \"calmar\": -0.8893874641917079, \"daily_log_sharpe\": -0.7699461651182177, \"daily_returns\": 0.031016042483567263, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": -0.08790580951453734, \"return\": -0.2531060622964866, \"returns_over_hodl\": -0.02465665509465842, \"returns_over_uniform_hodl\": 0.24121371396017133, \"sharpe\": -0.2883462347017513, \"sterling\": -1.3273950516686788, \"ulcer\": -0.17634174630373572}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -0.046956714064465666, \"optuna_trial_number\": 180, \"price_ratio\": 5.76632310278571, \"shift_exponent\": 0.0008107555443298912, \"step\": 180, \"test_objective\": [{\"annualised_returns\": -0.5154916013333809, \"annualised_returns_over_hodl\": -0.06010772291698485, \"annualised_returns_over_uniform_hodl\": 0.7100999104987862, \"calmar\": -0.8893874641917079, \"daily_log_sharpe\": -0.7699461651182177, \"daily_returns\": 0.031016042483567263, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": -0.08790580951453734, \"return\": -0.2531060622964866, \"returns_over_hodl\": -0.02465665509465842, \"returns_over_uniform_hodl\": 0.24121371396017133, \"sharpe\": -0.2883462347017513, \"sterling\": -1.3273950516686788, \"ulcer\": -0.17634174630373572}], \"train_objective\": [{\"annualised_returns\": -0.45969086769954903, \"annualised_returns_over_hodl\": -0.3195680833846387, \"annualised_returns_over_uniform_hodl\": -0.3195680833846387, \"calmar\": -0.6131765472863289, \"daily_log_sharpe\": -0.4561630984957738, \"daily_returns\": 0.00815180907882583, \"fee_revenue_over_value\": 0.0003726521320896839, \"jax_sharpe\": 0.1577791766637943, \"return\": -0.3118526722402052, \"returns_over_hodl\": -0.2084469962867621, \"returns_over_uniform_hodl\": -0.2084469962867621, \"sharpe\": 0.11930700299843375, \"sterling\": -1.3787259235108171, \"ulcer\": -0.18044093523567736}], \"train_return\": -0.3118526722402052, \"train_returns_over_hodl\": -0.2084469962867621, \"train_sharpe\": 0.15777917666379432, \"validation_return\": -0.3347523382536709, \"validation_returns_over_hodl\": -0.046956714064465666, \"validation_sharpe\": -2.205014850578569}, {\"centeredness_margin\": 0.7254928196194913, \"continuous_test_metrics\": [{\"annualised_returns\": -0.506210655457415, \"annualised_returns_over_hodl\": -0.042103722771923535, \"annualised_returns_over_uniform_hodl\": 0.7428575360745495, \"calmar\": -0.8732615459089533, \"daily_log_sharpe\": -0.747658312208542, \"daily_returns\": 0.03101604248878277, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": -0.06420765966043868, \"return\": -0.2473767039764998, \"returns_over_hodl\": -0.017174881338831605, \"returns_over_uniform_hodl\": 0.25073495621422537, \"sharpe\": -0.2641695338656896, \"sterling\": -1.303295494976214, \"ulcer\": -0.1764007444087743}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -0.05393973310672473, \"optuna_trial_number\": 181, \"price_ratio\": 8.142799880629992, \"shift_exponent\": 0.0001902295350100098, \"step\": 181, \"test_objective\": [{\"annualised_returns\": -0.506210655457415, \"annualised_returns_over_hodl\": -0.042103722771923535, \"annualised_returns_over_uniform_hodl\": 0.7428575360745495, \"calmar\": -0.8732615459089533, \"daily_log_sharpe\": -0.747658312208542, \"daily_returns\": 0.03101604248878277, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": -0.06420765966043868, \"return\": -0.2473767039764998, \"returns_over_hodl\": -0.017174881338831605, \"returns_over_uniform_hodl\": 0.25073495621422537, \"sharpe\": -0.2641695338656896, \"sterling\": -1.303295494976214, \"ulcer\": -0.1764007444087743}], \"train_objective\": [{\"annualised_returns\": -0.45053303827858204, \"annualised_returns_over_hodl\": -0.30803527919453644, \"annualised_returns_over_uniform_hodl\": -0.30803527919453655, \"calmar\": -0.6034434636166114, \"daily_log_sharpe\": -0.4512380881362636, \"daily_returns\": 0.00813684395041106, \"fee_revenue_over_value\": 0.0012640797473109308, \"jax_sharpe\": 0.09608850982131881, \"return\": -0.30479485988188026, \"returns_over_hodl\": -0.20032862926488704, \"returns_over_uniform_hodl\": -0.20032862926488715, \"sharpe\": 0.11291796473464037, \"sterling\": -1.365151580646645, \"ulcer\": -0.17867273760450547}], \"train_return\": -0.30479485988188026, \"train_returns_over_hodl\": -0.20032862926488704, \"train_sharpe\": 0.0960885098213188, \"validation_return\": -0.3316195963273637, \"validation_returns_over_hodl\": -0.05393973310672473, \"validation_sharpe\": -2.17762400501971}, {\"centeredness_margin\": 0.8053301466584002, \"continuous_test_metrics\": [{\"annualised_returns\": -0.4843375642505605, \"annualised_returns_over_hodl\": 0.00032763630893706264, \"annualised_returns_over_uniform_hodl\": 0.8200598537600916, \"calmar\": -0.8355136226219814, \"daily_log_sharpe\": -0.6921102585634482, \"daily_returns\": 0.031016042488797458, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": 0.0052491909441539485, \"return\": -0.23412356725537464, \"returns_over_hodl\": 0.0001319386257396804, \"returns_over_uniform_hodl\": 0.27275946896074554, \"sharpe\": -0.20172743758363954, \"sterling\": -1.2469495023704948, \"ulcer\": -0.1764103159708198}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -8.675959950465995e-10, \"optuna_trial_number\": 182, \"price_ratio\": 3.263098212691901, \"shift_exponent\": 0.000273973832507785, \"step\": 182, \"test_objective\": [{\"annualised_returns\": -0.4843375642505605, \"annualised_returns_over_hodl\": 0.00032763630893706264, \"annualised_returns_over_uniform_hodl\": 0.8200598537600916, \"calmar\": -0.8355136226219814, \"daily_log_sharpe\": -0.6921102585634482, \"daily_returns\": 0.031016042488797458, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": 0.0052491909441539485, \"return\": -0.23412356725537464, \"returns_over_hodl\": 0.0001319386257396804, \"returns_over_uniform_hodl\": 0.27275946896074554, \"sharpe\": -0.20172743758363954, \"sterling\": -1.2469495023704948, \"ulcer\": -0.1764103159708198}], \"train_objective\": [{\"annualised_returns\": -0.555532421335444, \"annualised_returns_over_hodl\": -0.4402650106310997, \"annualised_returns_over_uniform_hodl\": -0.4402650106310999, \"calmar\": -0.7264485728542116, \"daily_log_sharpe\": -0.5608696010039462, \"daily_returns\": 0.008305997068059791, \"fee_revenue_over_value\": 0.00033082388161788145, \"jax_sharpe\": 0.10860666696802028, \"return\": -0.3887819289256019, \"returns_over_hodl\": -0.2969361638622381, \"returns_over_uniform_hodl\": -0.2969361638622382, \"sharpe\": 0.0773126460647539, \"sterling\": -1.549524332548411, \"ulcer\": -0.19664064759167846}], \"train_return\": -0.3887819289256019, \"train_returns_over_hodl\": -0.2969361638622381, \"train_sharpe\": 0.1086066669680203, \"validation_return\": -0.3391427199142367, \"validation_returns_over_hodl\": -8.675959950465995e-10, \"validation_sharpe\": -2.243853199437246}, {\"centeredness_margin\": 0.8044459732247412, \"continuous_test_metrics\": [{\"annualised_returns\": -0.4845064586998169, \"annualised_returns_over_hodl\": 8.651523941693995e-12, \"annualised_returns_over_uniform_hodl\": 0.8194637312091673, \"calmar\": -0.8358049769696708, \"daily_log_sharpe\": -0.6924636109055882, \"daily_returns\": 0.03101604248630297, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": 0.005019267253817093, \"return\": -0.23422460263770162, \"returns_over_hodl\": 3.4843239404835913e-12, \"returns_over_uniform_hodl\": 0.27259156493072334, \"sharpe\": -0.20210854610921064, \"sterling\": -1.2473843297522438, \"ulcer\": -0.17641031596565626}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -9.0635732252764e-10, \"optuna_trial_number\": 183, \"price_ratio\": 3.2080426796644983, \"shift_exponent\": 8.45910679385002e-05, \"step\": 183, \"test_objective\": [{\"annualised_returns\": -0.4845064586998169, \"annualised_returns_over_hodl\": 8.651523941693995e-12, \"annualised_returns_over_uniform_hodl\": 0.8194637312091673, \"calmar\": -0.8358049769696708, \"daily_log_sharpe\": -0.6924636109055882, \"daily_returns\": 0.03101604248630297, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": 0.005019267253817093, \"return\": -0.23422460263770162, \"returns_over_hodl\": 3.4843239404835913e-12, \"returns_over_uniform_hodl\": 0.27259156493072334, \"sharpe\": -0.20210854610921064, \"sterling\": -1.2473843297522438, \"ulcer\": -0.17641031596565626}], \"train_objective\": [{\"annualised_returns\": -0.5608997587681934, \"annualised_returns_over_hodl\": -0.44702430355834977, \"annualised_returns_over_uniform_hodl\": -0.44702430355834977, \"calmar\": -0.7324791029745582, \"daily_log_sharpe\": -0.5667418504031644, \"daily_returns\": 0.008304389716126763, \"fee_revenue_over_value\": 0.00033353138016931804, \"jax_sharpe\": 0.11028853100354846, \"return\": -0.3932737798741268, \"returns_over_hodl\": -0.30210299074233626, \"returns_over_uniform_hodl\": -0.30210299074233626, \"sharpe\": 0.07582482796072518, \"sterling\": -1.5562599859276094, \"ulcer\": -0.19805061734784832}], \"train_return\": -0.3932737798741268, \"train_returns_over_hodl\": -0.30210299074233626, \"train_sharpe\": 0.11028853100354846, \"validation_return\": -0.3391427199123195, \"validation_returns_over_hodl\": -9.0635732252764e-10, \"validation_sharpe\": -2.2438531995946085}, {\"centeredness_margin\": 0.8094852012071492, \"continuous_test_metrics\": [{\"annualised_returns\": -0.4845064593759545, \"annualised_returns_over_hodl\": 9.511724741173566e-12, \"annualised_returns_over_uniform_hodl\": 0.8194637288227016, \"calmar\": -0.8358049780529752, \"daily_log_sharpe\": -0.692463612603305, \"daily_returns\": 0.031016042454176725, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": 0.005019265221337397, \"return\": -0.2342246030422176, \"returns_over_hodl\": 3.83071352416664e-12, \"returns_over_uniform_hodl\": 0.2725915642584851, \"sharpe\": -0.20210854810054613, \"sterling\": -1.2473843320180213, \"ulcer\": -0.17641031589924375}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -1.1759399010102811e-09, \"optuna_trial_number\": 184, \"price_ratio\": 2.0399060103568205, \"shift_exponent\": 0.0001584285047319246, \"step\": 184, \"test_objective\": [{\"annualised_returns\": -0.4845064593759545, \"annualised_returns_over_hodl\": 9.511724741173566e-12, \"annualised_returns_over_uniform_hodl\": 0.8194637288227016, \"calmar\": -0.8358049780529752, \"daily_log_sharpe\": -0.692463612603305, \"daily_returns\": 0.031016042454176725, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": 0.005019265221337397, \"return\": -0.2342246030422176, \"returns_over_hodl\": 3.83071352416664e-12, \"returns_over_uniform_hodl\": 0.2725915642584851, \"sharpe\": -0.20210854810054613, \"sterling\": -1.2473843320180213, \"ulcer\": -0.17641031589924375}], \"train_objective\": [{\"annualised_returns\": -0.6307157152760429, \"annualised_returns_over_hodl\": -0.5349462028138938, \"annualised_returns_over_uniform_hodl\": -0.5349462028138938, \"calmar\": -0.8054650739750064, \"daily_log_sharpe\": -0.6775817114470113, \"daily_returns\": 0.008559398412136786, \"fee_revenue_over_value\": 0.000333269100364752, \"jax_sharpe\": 0.08629694872755546, \"return\": -0.45382036164123973, \"returns_over_hodl\": -0.37174771176210075, \"returns_over_uniform_hodl\": -0.37174771176210086, \"sharpe\": -0.008529291432769068, \"sterling\": -1.7026144764735838, \"ulcer\": -0.20660666720229806}], \"train_return\": -0.45382036164123973, \"train_returns_over_hodl\": -0.37174771176210075, \"train_sharpe\": 0.08629694872755546, \"validation_return\": -0.33914271973597354, \"validation_returns_over_hodl\": -1.1759399010102811e-09, \"validation_sharpe\": -2.2438532000592732}, {\"centeredness_margin\": 0.7843411015785784, \"continuous_test_metrics\": [{\"annualised_returns\": -0.4845064587801956, \"annualised_returns_over_hodl\": 8.615774760301065e-12, \"annualised_returns_over_uniform_hodl\": 0.8194637309254669, \"calmar\": -0.8358049770984293, \"daily_log_sharpe\": -0.6924636111073657, \"daily_returns\": 0.031016042482486433, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": 0.005019267012291727, \"return\": -0.23422460268579015, \"returns_over_hodl\": 3.4698910411634643e-12, \"returns_over_uniform_hodl\": 0.2725915648508084, \"sharpe\": -0.20210854634586506, \"sterling\": -1.2473843300215302, \"ulcer\": -0.17641031595777293}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -9.456153637898979e-10, \"optuna_trial_number\": 185, \"price_ratio\": 3.0094916895425, \"shift_exponent\": 6.552360234737656e-05, \"step\": 185, \"test_objective\": [{\"annualised_returns\": -0.4845064587801956, \"annualised_returns_over_hodl\": 8.615774760301065e-12, \"annualised_returns_over_uniform_hodl\": 0.8194637309254669, \"calmar\": -0.8358049770984293, \"daily_log_sharpe\": -0.6924636111073657, \"daily_returns\": 0.031016042482486433, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": 0.005019267012291727, \"return\": -0.23422460268579015, \"returns_over_hodl\": 3.4698910411634643e-12, \"returns_over_uniform_hodl\": 0.2725915648508084, \"sharpe\": -0.20210854634586506, \"sterling\": -1.2473843300215302, \"ulcer\": -0.17641031595777293}], \"train_objective\": [{\"annualised_returns\": -0.5705262757624757, \"annualised_returns_over_hodl\": -0.4591473439016831, \"annualised_returns_over_uniform_hodl\": -0.4591473439016832, \"calmar\": -0.7423328825618072, \"daily_log_sharpe\": -0.578969687646307, \"daily_returns\": 0.008284949832091732, \"fee_revenue_over_value\": 0.0003461436822372381, \"jax_sharpe\": 0.11113556213271136, \"return\": -0.40138449826276257, \"returns_over_hodl\": -0.31143248058239237, \"returns_over_uniform_hodl\": -0.3114324805823925, \"sharpe\": 0.06968706962609299, \"sterling\": -1.5729920889433142, \"ulcer\": -0.19985439861070697}], \"train_return\": -0.40138449826276257, \"train_returns_over_hodl\": -0.31143248058239237, \"train_sharpe\": 0.11113556213271135, \"validation_return\": -0.33914271989615785, \"validation_returns_over_hodl\": -9.456153637898979e-10, \"validation_sharpe\": -2.2438531996965843}, {\"centeredness_margin\": 0.3765462566122979, \"continuous_test_metrics\": [{\"annualised_returns\": -0.48194785125703654, \"annualised_returns_over_hodl\": 0.004963412572067449, \"annualised_returns_over_uniform_hodl\": 0.8284944814932529, \"calmar\": -0.8313912060768478, \"daily_log_sharpe\": -0.6872789520003404, \"daily_returns\": 0.031016042501588237, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": 0.007573932685188083, \"return\": -0.2326961156175218, \"returns_over_hodl\": 0.001995998872666771, \"returns_over_uniform_hodl\": 0.27513165657598515, \"sharpe\": -0.19664167288264398, \"sterling\": -1.240797064713992, \"ulcer\": -0.1764103159972622}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -7.349186814664677e-10, \"optuna_trial_number\": 186, \"price_ratio\": 4.327288286493836, \"shift_exponent\": 0.00012603205236507342, \"step\": 186, \"test_objective\": [{\"annualised_returns\": -0.48194785125703654, \"annualised_returns_over_hodl\": 0.004963412572067449, \"annualised_returns_over_uniform_hodl\": 0.8284944814932529, \"calmar\": -0.8313912060768478, \"daily_log_sharpe\": -0.6872789520003404, \"daily_returns\": 0.031016042501588237, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": 0.007573932685188083, \"return\": -0.2326961156175218, \"returns_over_hodl\": 0.001995998872666771, \"returns_over_uniform_hodl\": 0.27513165657598515, \"sharpe\": -0.19664167288264398, \"sterling\": -1.240797064713992, \"ulcer\": -0.1764103159972622}], \"train_objective\": [{\"annualised_returns\": -0.5133534840737983, \"annualised_returns_over_hodl\": -0.3871474647559333, \"annualised_returns_over_uniform_hodl\": -0.3871474647559332, \"calmar\": -0.6817449891699825, \"daily_log_sharpe\": -0.5073894283697246, \"daily_returns\": 0.008243349823501455, \"fee_revenue_over_value\": 0.011522915986324229, \"jax_sharpe\": 0.1124809469729915, \"return\": -0.3541960473380934, \"returns_over_hodl\": -0.2571531735747009, \"returns_over_uniform_hodl\": -0.2571531735747008, \"sharpe\": 0.1058888750718139, \"sterling\": -1.4824293406340965, \"ulcer\": -0.18838067427187183}], \"train_return\": -0.3541960473380934, \"train_returns_over_hodl\": -0.2571531735747009, \"train_sharpe\": 0.11248094697299152, \"validation_return\": -0.33914271996771483, \"validation_returns_over_hodl\": -7.349186814664677e-10, \"validation_sharpe\": -2.243853199083003}, {\"centeredness_margin\": 0.43396931852464243, \"continuous_test_metrics\": [{\"annualised_returns\": -0.4788965663240162, \"annualised_returns_over_hodl\": 0.010882564642554549, \"annualised_returns_over_uniform_hodl\": 0.8392641649605044, \"calmar\": -0.8261275333102711, \"daily_log_sharpe\": -0.6841595207872669, \"daily_returns\": 0.03101604251421739, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": 0.0004746839396216612, \"return\": -0.23087919090299946, \"returns_over_hodl\": 0.004368658742037734, \"returns_over_uniform_hodl\": 0.27815108377850395, \"sharpe\": -0.1943452207562046, \"sterling\": -1.2329413767055004, \"ulcer\": -0.1764103160233741}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -6.871017088627696e-10, \"optuna_trial_number\": 187, \"price_ratio\": 5.949448033369349, \"shift_exponent\": 6.871579253323292e-05, \"step\": 187, \"test_objective\": [{\"annualised_returns\": -0.4788965663240162, \"annualised_returns_over_hodl\": 0.010882564642554549, \"annualised_returns_over_uniform_hodl\": 0.8392641649605044, \"calmar\": -0.8261275333102711, \"daily_log_sharpe\": -0.6841595207872669, \"daily_returns\": 0.03101604251421739, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": 0.0004746839396216612, \"return\": -0.23087919090299946, \"returns_over_hodl\": 0.004368658742037734, \"returns_over_uniform_hodl\": 0.27815108377850395, \"sharpe\": -0.1943452207562046, \"sterling\": -1.2329413767055004, \"ulcer\": -0.1764103160233741}], \"train_objective\": [{\"annualised_returns\": -0.47548759930904927, \"annualised_returns_over_hodl\": -0.3394615105409535, \"annualised_returns_over_uniform_hodl\": -0.3394615105409535, \"calmar\": -0.6360885311688318, \"daily_log_sharpe\": -0.46691150891666183, \"daily_returns\": 0.008189914040561365, \"fee_revenue_over_value\": 0.010717045639022876, \"jax_sharpe\": 0.15655077252847832, \"return\": -0.32413846696380866, \"returns_over_hodl\": -0.22257893769548776, \"returns_over_uniform_hodl\": -0.22257893769548776, \"sharpe\": 0.11900213054111305, \"sterling\": -1.4127138628696505, \"ulcer\": -0.18234704851790598}], \"train_return\": -0.32413846696380866, \"train_returns_over_hodl\": -0.22257893769548776, \"train_sharpe\": 0.1565507725284783, \"validation_return\": -0.33914272000020174, \"validation_returns_over_hodl\": -6.871017088627696e-10, \"validation_sharpe\": -2.2438531985317907}, {\"centeredness_margin\": 0.3775524118604432, \"continuous_test_metrics\": [{\"annualised_returns\": -0.4778161038821971, \"annualised_returns_over_hodl\": 0.012978541508461339, \"annualised_returns_over_uniform_hodl\": 0.843077718513213, \"calmar\": -0.8242636625744271, \"daily_log_sharpe\": -0.680230625508515, \"daily_returns\": 0.031016042509696005, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": 0.008182879685052023, \"return\": -0.23023734019463793, \"returns_over_hodl\": 0.005206829787446576, \"returns_over_uniform_hodl\": 0.27921773308614584, \"sharpe\": -0.18948750801314004, \"sterling\": -1.2301596714794845, \"ulcer\": -0.17641031601400564}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -6.345136638330473e-10, \"optuna_trial_number\": 188, \"price_ratio\": 5.203129290536814, \"shift_exponent\": 0.00011589333048215753, \"step\": 188, \"test_objective\": [{\"annualised_returns\": -0.4778161038821971, \"annualised_returns_over_hodl\": 0.012978541508461339, \"annualised_returns_over_uniform_hodl\": 0.843077718513213, \"calmar\": -0.8242636625744271, \"daily_log_sharpe\": -0.680230625508515, \"daily_returns\": 0.031016042509696005, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": 0.008182879685052023, \"return\": -0.23023734019463793, \"returns_over_hodl\": 0.005206829787446576, \"returns_over_uniform_hodl\": 0.27921773308614584, \"sharpe\": -0.18948750801314004, \"sterling\": -1.2301596714794845, \"ulcer\": -0.17641031601400564}], \"train_objective\": [{\"annualised_returns\": -0.48959746551348904, \"annualised_returns_over_hodl\": -0.35723060369656323, \"annualised_returns_over_uniform_hodl\": -0.3572306036965631, \"calmar\": -0.6534139514399186, \"daily_log_sharpe\": -0.4810098946775059, \"daily_returns\": 0.00819279581890689, \"fee_revenue_over_value\": 0.011112247150720449, \"jax_sharpe\": 0.10906305853977659, \"return\": -0.3352357922738912, \"returns_over_hodl\": -0.2353438222311509, \"returns_over_uniform_hodl\": -0.2353438222311508, \"sharpe\": 0.11517887963323352, \"sterling\": -1.4393903972523394, \"ulcer\": -0.18447214792025413}], \"train_return\": -0.3352357922738912, \"train_returns_over_hodl\": -0.2353438222311509, \"train_sharpe\": 0.10906305853977658, \"validation_return\": -0.33914271999079104, \"validation_returns_over_hodl\": -6.345136638330473e-10, \"validation_sharpe\": -2.2438531987508403}, {\"centeredness_margin\": 0.3962521875693334, \"continuous_test_metrics\": [{\"annualised_returns\": -0.4809382485245338, \"annualised_returns_over_hodl\": 0.006921929162948315, \"annualised_returns_over_uniform_hodl\": 0.8320579316774885, \"calmar\": -0.8296495731000181, \"daily_log_sharpe\": -0.685303655074013, \"daily_returns\": 0.031016042505304993, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": 0.007560553770725992, \"return\": -0.2320942295521624, \"returns_over_hodl\": 0.002781981316554827, \"returns_over_uniform_hodl\": 0.2761318912824853, \"sharpe\": -0.19460813409740887, \"sterling\": -1.2381977911932667, \"ulcer\": -0.17641031600494306}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -6.9340910791027e-10, \"optuna_trial_number\": 189, \"price_ratio\": 4.765963250462933, \"shift_exponent\": 5.4729968099249925e-05, \"step\": 189, \"test_objective\": [{\"annualised_returns\": -0.4809382485245338, \"annualised_returns_over_hodl\": 0.006921929162948315, \"annualised_returns_over_uniform_hodl\": 0.8320579316774885, \"calmar\": -0.8296495731000181, \"daily_log_sharpe\": -0.685303655074013, \"daily_returns\": 0.031016042505304993, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": 0.007560553770725992, \"return\": -0.2320942295521624, \"returns_over_hodl\": 0.002781981316554827, \"returns_over_uniform_hodl\": 0.2761318912824853, \"sharpe\": -0.19460813409740887, \"sterling\": -1.2381977911932667, \"ulcer\": -0.17641031600494306}], \"train_objective\": [{\"annualised_returns\": -0.5033241047622282, \"annualised_returns_over_hodl\": -0.37451708451716414, \"annualised_returns_over_uniform_hodl\": -0.37451708451716414, \"calmar\": -0.6700365578602088, \"daily_log_sharpe\": -0.4967820302910697, \"daily_returns\": 0.008241301845603554, \"fee_revenue_over_value\": 0.011258041406484523, \"jax_sharpe\": 0.16575150149516715, \"return\": -0.3461479806612975, \"returns_over_hodl\": -0.24789574991683272, \"returns_over_uniform_hodl\": -0.24789574991683272, \"sharpe\": 0.10845385104652087, \"sterling\": -1.4656432317444452, \"ulcer\": -0.1865540122197284}], \"train_return\": -0.3461479806612975, \"train_returns_over_hodl\": -0.24789574991683272, \"train_sharpe\": 0.16575150149516715, \"validation_return\": -0.33914271998126255, \"validation_returns_over_hodl\": -6.9340910791027e-10, \"validation_sharpe\": -2.2438531989604766}, {\"centeredness_margin\": 0.8220724534675814, \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -Infinity, \"optuna_trial_number\": 190, \"price_ratio\": 1.3260715058970125, \"shift_exponent\": 1.6446264021363146, \"step\": 190, \"test_objective\": -Infinity, \"train_objective\": -Infinity, \"train_return\": -Infinity, \"train_returns_over_hodl\": -Infinity, \"train_sharpe\": -Infinity, \"validation_return\": -Infinity, \"validation_returns_over_hodl\": -Infinity, \"validation_sharpe\": -Infinity}, {\"centeredness_margin\": 0.5165382573027755, \"continuous_test_metrics\": [{\"annualised_returns\": -0.4836877495308912, \"annualised_returns_over_hodl\": 0.001588204212972233, \"annualised_returns_over_uniform_hodl\": 0.8223534117190565, \"calmar\": -0.8343926482747346, \"daily_log_sharpe\": -0.6907540734908704, \"daily_returns\": 0.031016042493857067, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": 0.005835956305127495, \"return\": -0.23373502181393946, \"returns_over_hodl\": 0.0006393267959245996, \"returns_over_uniform_hodl\": 0.273405166972285, \"sharpe\": -0.20026658948094994, \"sterling\": -1.2452765212820744, \"ulcer\": -0.1764103159812736}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -8.154519282044248e-10, \"optuna_trial_number\": 191, \"price_ratio\": 3.522722133161309, \"shift_exponent\": 0.0003223486006254424, \"step\": 191, \"test_objective\": [{\"annualised_returns\": -0.4836877495308912, \"annualised_returns_over_hodl\": 0.001588204212972233, \"annualised_returns_over_uniform_hodl\": 0.8223534117190565, \"calmar\": -0.8343926482747346, \"daily_log_sharpe\": -0.6907540734908704, \"daily_returns\": 0.031016042493857067, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": 0.005835956305127495, \"return\": -0.23373502181393946, \"returns_over_hodl\": 0.0006393267959245996, \"returns_over_uniform_hodl\": 0.273405166972285, \"sharpe\": -0.20026658948094994, \"sterling\": -1.2452765212820744, \"ulcer\": -0.1764103159812736}], \"train_objective\": [{\"annualised_returns\": -0.5392457369956569, \"annualised_returns_over_hodl\": -0.41975456729758154, \"annualised_returns_over_uniform_hodl\": -0.41975456729758154, \"calmar\": -0.7094619813944792, \"daily_log_sharpe\": -0.5385607424291138, \"daily_returns\": 0.008294206144259174, \"fee_revenue_over_value\": 0.005502382358388466, \"jax_sharpe\": 0.11437643756587644, \"return\": -0.37528051328152845, \"returns_over_hodl\": -0.2814059341042615, \"returns_over_uniform_hodl\": -0.2814059341042615, \"sharpe\": 0.09162469641550897, \"sterling\": -1.5225993167853014, \"ulcer\": -0.19355878155365475}], \"train_return\": -0.37528051328152845, \"train_returns_over_hodl\": -0.2814059341042615, \"train_sharpe\": 0.11437643756587644, \"validation_return\": -0.3391427199355992, \"validation_returns_over_hodl\": -8.154519282044248e-10, \"validation_sharpe\": -2.243853199299823}, {\"centeredness_margin\": 0.7713052724827347, \"continuous_test_metrics\": [{\"annualised_returns\": -0.4856950969483882, \"annualised_returns_over_hodl\": -0.002305827029156604, \"annualised_returns_over_uniform_hodl\": 0.8152683650027379, \"calmar\": -0.8378554584826298, \"daily_log_sharpe\": -0.7007229082467313, \"daily_returns\": 0.031016042519463504, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": -0.018310145946122954, \"return\": -0.23493622504844291, \"returns_over_hodl\": -0.0009292840014945014, \"returns_over_uniform_hodl\": 0.27140896663826797, \"sharpe\": -0.2130784029177239, \"sterling\": -1.2504445387924863, \"ulcer\": -0.17641031603421}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -0.029359089207868894, \"optuna_trial_number\": 192, \"price_ratio\": 7.18378890603966, \"shift_exponent\": 1.0102212037262517e-05, \"step\": 192, \"test_objective\": [{\"annualised_returns\": -0.4856950969483882, \"annualised_returns_over_hodl\": -0.002305827029156604, \"annualised_returns_over_uniform_hodl\": 0.8152683650027379, \"calmar\": -0.8378554584826298, \"daily_log_sharpe\": -0.7007229082467313, \"daily_returns\": 0.031016042519463504, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": -0.018310145946122954, \"return\": -0.23493622504844291, \"returns_over_hodl\": -0.0009292840014945014, \"returns_over_uniform_hodl\": 0.27140896663826797, \"sharpe\": -0.2130784029177239, \"sterling\": -1.2504445387924863, \"ulcer\": -0.17641031603421}], \"train_objective\": [{\"annualised_returns\": -0.4684662102275351, \"annualised_returns_over_hodl\": -0.3306192072289673, \"annualised_returns_over_uniform_hodl\": -0.3306192072289673, \"calmar\": -0.6260304332274016, \"daily_log_sharpe\": -0.4681364083858732, \"daily_returns\": 0.008161407580808609, \"fee_revenue_over_value\": 0.0006941418810382449, \"jax_sharpe\": 0.09221989492464941, \"return\": -0.3186599367837506, \"returns_over_hodl\": -0.2162771664830997, \"returns_over_uniform_hodl\": -0.2162771664830997, \"sharpe\": 0.10661041269216867, \"sterling\": -1.4039225326397318, \"ulcer\": -0.18093022838633863}], \"train_return\": -0.3186599367837506, \"train_returns_over_hodl\": -0.2162771664830997, \"train_sharpe\": 0.09221989492464941, \"validation_return\": -0.33762197197392496, \"validation_returns_over_hodl\": -0.029359089207868894, \"validation_sharpe\": -2.230162695483419}, {\"centeredness_margin\": 0.46851107581396023, \"continuous_test_metrics\": [{\"annualised_returns\": -0.5130630954599789, \"annualised_returns_over_hodl\": -0.05539669264975744, \"annualised_returns_over_uniform_hodl\": 0.7186714599046986, \"calmar\": -0.8853164529787512, \"daily_log_sharpe\": -0.7631582115483148, \"daily_returns\": 0.03101604250027628, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": -0.080723550567569, \"return\": -0.2516005982758083, \"returns_over_hodl\": -0.02269072108317083, \"returns_over_uniform_hodl\": 0.24371554520288408, \"sharpe\": -0.2807469044273892, \"sterling\": -1.3215057257467724, \"ulcer\": -0.1762364420560411}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -0.06101756178832618, \"optuna_trial_number\": 193, \"price_ratio\": 9.875852508517784, \"shift_exponent\": 1.2720842040575807e-05, \"step\": 193, \"test_objective\": [{\"annualised_returns\": -0.5130630954599789, \"annualised_returns_over_hodl\": -0.05539669264975744, \"annualised_returns_over_uniform_hodl\": 0.7186714599046986, \"calmar\": -0.8853164529787512, \"daily_log_sharpe\": -0.7631582115483148, \"daily_returns\": 0.03101604250027628, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": -0.080723550567569, \"return\": -0.2516005982758083, \"returns_over_hodl\": -0.02269072108317083, \"returns_over_uniform_hodl\": 0.24371554520288408, \"sharpe\": -0.2807469044273892, \"sterling\": -1.3215057257467724, \"ulcer\": -0.1762364420560411}], \"train_objective\": [{\"annualised_returns\": -0.4312677223179705, \"annualised_returns_over_hodl\": -0.28377373135160644, \"annualised_returns_over_uniform_hodl\": -0.28377373135160644, \"calmar\": -0.58079116179584, \"daily_log_sharpe\": -0.4284166923522715, \"daily_returns\": 0.0081225212155976, \"fee_revenue_over_value\": 0.009800737657828398, \"jax_sharpe\": 0.10755831091148989, \"return\": -0.2900964782225689, \"returns_over_hodl\": -0.18342156927523823, \"returns_over_uniform_hodl\": -0.18342156927523823, \"sharpe\": 0.12768879994875446, \"sterling\": -1.3203946771990074, \"ulcer\": -0.17650086595137743}], \"train_return\": -0.2900964782225689, \"train_returns_over_hodl\": -0.18342156927523823, \"train_sharpe\": 0.10755831091148987, \"validation_return\": -0.32555809614157183, \"validation_returns_over_hodl\": -0.06101756178832618, \"validation_sharpe\": -2.1339664515345715}, {\"centeredness_margin\": 0.49257888728294147, \"continuous_test_metrics\": [{\"annualised_returns\": -0.6090695333012968, \"annualised_returns_over_hodl\": -0.2416384788081134, \"annualised_returns_over_uniform_hodl\": 0.3798112849075794, \"calmar\": -1.0487785833915506, \"daily_log_sharpe\": -1.0230041371808736, \"daily_returns\": 0.031016042516642445, \"fee_revenue_over_value\": 0.0008680688091856777, \"jax_sharpe\": -0.34793132008188293, \"return\": -0.3149487743297811, \"returns_over_hodl\": -0.10541494589785028, \"returns_over_uniform_hodl\": 0.13844139461288885, \"sharpe\": -0.5622408217867235, \"sterling\": -1.5959930552147792, \"ulcer\": -0.17092736417105986}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -0.0708897832795844, \"optuna_trial_number\": 194, \"price_ratio\": 5.216273442034524, \"shift_exponent\": 0.0020546929912983103, \"step\": 194, \"test_objective\": [{\"annualised_returns\": -0.6090695333012968, \"annualised_returns_over_hodl\": -0.2416384788081134, \"annualised_returns_over_uniform_hodl\": 0.3798112849075794, \"calmar\": -1.0487785833915506, \"daily_log_sharpe\": -1.0230041371808736, \"daily_returns\": 0.031016042516642445, \"fee_revenue_over_value\": 0.0008680688091856777, \"jax_sharpe\": -0.34793132008188293, \"return\": -0.3149487743297811, \"returns_over_hodl\": -0.10541494589785028, \"returns_over_uniform_hodl\": 0.13844139461288885, \"sharpe\": -0.5622408217867235, \"sterling\": -1.5959930552147792, \"ulcer\": -0.17092736417105986}], \"train_objective\": [{\"annualised_returns\": -0.4229427596843518, \"annualised_returns_over_hodl\": -0.2732897881015145, \"annualised_returns_over_uniform_hodl\": -0.2732897881015145, \"calmar\": -0.5656547242647312, \"daily_log_sharpe\": -0.41356106053342195, \"daily_returns\": 0.008167935667483821, \"fee_revenue_over_value\": 0.011492897942332004, \"jax_sharpe\": 0.14215700372686513, \"return\": -0.28380566004977825, \"returns_over_hodl\": -0.17618545017746212, \"returns_over_uniform_hodl\": -0.17618545017746212, \"sharpe\": 0.1520998565158859, \"sterling\": -1.2871877487853134, \"ulcer\": -0.17694181996130515}], \"train_return\": -0.28380566004977825, \"train_returns_over_hodl\": -0.17618545017746212, \"train_sharpe\": 0.14215700372686513, \"validation_return\": -0.3076466277872101, \"validation_returns_over_hodl\": -0.0708897832795844, \"validation_sharpe\": -2.070923113343384}, {\"centeredness_margin\": 0.4864604081971567, \"continuous_test_metrics\": [{\"annualised_returns\": -0.6829669005351022, \"annualised_returns_over_hodl\": -0.08043651232432569, \"annualised_returns_over_uniform_hodl\": 0.11898633029295169, \"calmar\": -1.2020978102572752, \"daily_log_sharpe\": -1.4639854036253772, \"daily_returns\": 0.022034648553859535, \"fee_revenue_over_value\": 0.060091712109585416, \"jax_sharpe\": -0.8761141537111713, \"return\": -0.37038371119246993, \"returns_over_hodl\": -0.0332081499688337, \"returns_over_uniform_hodl\": 0.046317733684476003, \"sharpe\": -1.0761371172650387, \"sterling\": -2.0142616723008264, \"ulcer\": -0.14708210615766001}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -0.0391114809092985, \"optuna_trial_number\": 195, \"price_ratio\": 4.192475626161238, \"shift_exponent\": 0.009676298117193007, \"step\": 195, \"test_objective\": [{\"annualised_returns\": -0.6829669005351022, \"annualised_returns_over_hodl\": -0.08043651232432569, \"annualised_returns_over_uniform_hodl\": 0.11898633029295169, \"calmar\": -1.2020978102572752, \"daily_log_sharpe\": -1.4639854036253772, \"daily_returns\": 0.022034648553859535, \"fee_revenue_over_value\": 0.060091712109585416, \"jax_sharpe\": -0.8761141537111713, \"return\": -0.37038371119246993, \"returns_over_hodl\": -0.0332081499688337, \"returns_over_uniform_hodl\": 0.046317733684476003, \"sharpe\": -1.0761371172650387, \"sterling\": -2.0142616723008264, \"ulcer\": -0.14708210615766001}], \"train_objective\": [{\"annualised_returns\": -0.38351912747375083, \"annualised_returns_over_hodl\": -0.22364210306094068, \"annualised_returns_over_uniform_hodl\": -0.223642103060941, \"calmar\": -0.5140644489129171, \"daily_log_sharpe\": -0.39428074827475385, \"daily_returns\": 0.008192021222669542, \"fee_revenue_over_value\": 0.018295514871518342, \"jax_sharpe\": 0.10670166349069962, \"return\": -0.2544861769043224, \"returns_over_hodl\": -0.14246022301330707, \"returns_over_uniform_hodl\": -0.1424602230133073, \"sharpe\": 0.13478293576834977, \"sterling\": -1.2032488586031227, \"ulcer\": -0.17178083856637286}], \"train_return\": -0.2544861769043224, \"train_returns_over_hodl\": -0.14246022301330707, \"train_sharpe\": 0.10670166349069962, \"validation_return\": -0.20522944109268737, \"validation_returns_over_hodl\": -0.0391114809092985, \"validation_sharpe\": -1.6214607886698766}, {\"centeredness_margin\": 0.8785712912965682, \"continuous_test_metrics\": [{\"annualised_returns\": -0.5995494911208266, \"annualised_returns_over_hodl\": -0.223170658068101, \"annualised_returns_over_uniform_hodl\": 0.4134128144694409, \"calmar\": -1.0368452980370717, \"daily_log_sharpe\": -0.9926253478919227, \"daily_returns\": 0.03101604250285671, \"fee_revenue_over_value\": 0.0001379671371312801, \"jax_sharpe\": -0.31835873186500285, \"return\": -0.30827831794087446, \"returns_over_hodl\": -0.0967042240466256, \"returns_over_uniform_hodl\": 0.1495265856023098, \"sharpe\": -0.5286752394748722, \"sterling\": -1.5629294857714469, \"ulcer\": -0.17225048924019767}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -0.071548711936817, \"optuna_trial_number\": 196, \"price_ratio\": 3.798539106938958, \"shift_exponent\": 0.002193543986545111, \"step\": 196, \"test_objective\": [{\"annualised_returns\": -0.5995494911208266, \"annualised_returns_over_hodl\": -0.223170658068101, \"annualised_returns_over_uniform_hodl\": 0.4134128144694409, \"calmar\": -1.0368452980370717, \"daily_log_sharpe\": -0.9926253478919227, \"daily_returns\": 0.03101604250285671, \"fee_revenue_over_value\": 0.0001379671371312801, \"jax_sharpe\": -0.31835873186500285, \"return\": -0.30827831794087446, \"returns_over_hodl\": -0.0967042240466256, \"returns_over_uniform_hodl\": 0.1495265856023098, \"sharpe\": -0.5286752394748722, \"sterling\": -1.5629294857714469, \"ulcer\": -0.17225048924019767}], \"train_objective\": [{\"annualised_returns\": -0.4432696856422642, \"annualised_returns_over_hodl\": -0.29888826194102325, \"annualised_returns_over_uniform_hodl\": -0.29888826194102325, \"calmar\": -0.5878985509949984, \"daily_log_sharpe\": -0.43092884994098934, \"daily_returns\": 0.00819871081548364, \"fee_revenue_over_value\": 0.0003025180770861104, \"jax_sharpe\": 0.21028023419658898, \"return\": -0.2992299039364058, \"returns_over_hodl\": -0.19392744536650286, \"returns_over_uniform_hodl\": -0.19392744536650286, \"sharpe\": 0.15111055029446294, \"sterling\": -1.3267860183048208, \"ulcer\": -0.18005508054239844}], \"train_return\": -0.2992299039364058, \"train_returns_over_hodl\": -0.19392744536650286, \"train_sharpe\": 0.21028023419658895, \"validation_return\": -0.32246593613751795, \"validation_returns_over_hodl\": -0.071548711936817, \"validation_sharpe\": -2.1253937198766946}, {\"centeredness_margin\": 0.22960185056736168, \"continuous_test_metrics\": [{\"annualised_returns\": -0.5477339366387293, \"annualised_returns_over_hodl\": -0.12265425902632543, \"annualised_returns_over_uniform_hodl\": 0.596298756851732, \"calmar\": -0.9460539611054577, \"daily_log_sharpe\": -0.8510828395701423, \"daily_returns\": 0.031016042508277733, \"fee_revenue_over_value\": 0.0012689348931054693, \"jax_sharpe\": -0.17308624017066024, \"return\": -0.2735359459635067, \"returns_over_hodl\": -0.051335344177086184, \"returns_over_uniform_hodl\": 0.20726263938044642, \"sharpe\": -0.37566097470849497, \"sterling\": -1.4122348297388312, \"ulcer\": -0.17585191514699905}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -0.0024592211031917444, \"optuna_trial_number\": 197, \"price_ratio\": 3.387491640217405, \"shift_exponent\": 0.002404104840171055, \"step\": 197, \"test_objective\": [{\"annualised_returns\": -0.5477339366387293, \"annualised_returns_over_hodl\": -0.12265425902632543, \"annualised_returns_over_uniform_hodl\": 0.596298756851732, \"calmar\": -0.9460539611054577, \"daily_log_sharpe\": -0.8510828395701423, \"daily_returns\": 0.031016042508277733, \"fee_revenue_over_value\": 0.0012689348931054693, \"jax_sharpe\": -0.17308624017066024, \"return\": -0.2735359459635067, \"returns_over_hodl\": -0.051335344177086184, \"returns_over_uniform_hodl\": 0.20726263938044642, \"sharpe\": -0.37566097470849497, \"sterling\": -1.4122348297388312, \"ulcer\": -0.17585191514699905}], \"train_objective\": [{\"annualised_returns\": -0.48897198844470524, \"annualised_returns_over_hodl\": -0.3564429165462468, \"annualised_returns_over_uniform_hodl\": -0.35644291654624716, \"calmar\": -0.646241149932852, \"daily_log_sharpe\": -0.47178152552051467, \"daily_returns\": 0.00830243660210045, \"fee_revenue_over_value\": 0.01552676632695238, \"jax_sharpe\": 0.14426480732592148, \"return\": -0.3347413253696613, \"returns_over_hodl\": -0.23477505338254645, \"returns_over_uniform_hodl\": -0.23477505338254667, \"sharpe\": 0.13896791871232003, \"sterling\": -1.419686984110718, \"ulcer\": -0.18621372733626043}], \"train_return\": -0.3347413253696613, \"train_returns_over_hodl\": -0.23477505338254645, \"train_sharpe\": 0.14426480732592148, \"validation_return\": -0.3390529408757066, \"validation_returns_over_hodl\": -0.0024592211031917444, \"validation_sharpe\": -2.243044630263254}, {\"centeredness_margin\": 0.8253678757292601, \"continuous_test_metrics\": [{\"annualised_returns\": -0.7461694680851304, \"annualised_returns_over_hodl\": -0.20959944557328491, \"annualised_returns_over_uniform_hodl\": -0.10409072143214393, \"calmar\": -1.256585553373014, \"daily_log_sharpe\": -1.74267843482949, \"daily_returns\": 0.02054406504115876, \"fee_revenue_over_value\": 0.017340560224009607, \"jax_sharpe\": -1.1781586498831105, \"return\": -0.42431184978524683, \"returns_over_hodl\": -0.09038163882377293, \"returns_over_uniform_hodl\": -0.04330187870052982, \"sharpe\": -1.3626717378849431, \"sterling\": -2.219116570504283, \"ulcer\": -0.14669174545227678}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -0.035394662650687136, \"optuna_trial_number\": 198, \"price_ratio\": 3.355269862661088, \"shift_exponent\": 0.006661174052407427, \"step\": 198, \"test_objective\": [{\"annualised_returns\": -0.7461694680851304, \"annualised_returns_over_hodl\": -0.20959944557328491, \"annualised_returns_over_uniform_hodl\": -0.10409072143214393, \"calmar\": -1.256585553373014, \"daily_log_sharpe\": -1.74267843482949, \"daily_returns\": 0.02054406504115876, \"fee_revenue_over_value\": 0.017340560224009607, \"jax_sharpe\": -1.1781586498831105, \"return\": -0.42431184978524683, \"returns_over_hodl\": -0.09038163882377293, \"returns_over_uniform_hodl\": -0.04330187870052982, \"sharpe\": -1.3626717378849431, \"sterling\": -2.219116570504283, \"ulcer\": -0.14669174545227678}], \"train_objective\": [{\"annualised_returns\": -0.3783695379140162, \"annualised_returns_over_hodl\": -0.21715702834270656, \"annualised_returns_over_uniform_hodl\": -0.2171570283427069, \"calmar\": -0.5081750184389876, \"daily_log_sharpe\": -0.3895079261912685, \"daily_returns\": 0.008307783278114206, \"fee_revenue_over_value\": 0.004378427779042857, \"jax_sharpe\": 0.18212055067333668, \"return\": -0.25071155220866104, \"returns_over_hodl\": -0.1381183976581647, \"returns_over_uniform_hodl\": -0.13811839765816492, \"sharpe\": 0.14054811114532323, \"sterling\": -1.1939331066376786, \"ulcer\": -0.171234258324948}], \"train_return\": -0.25071155220866104, \"train_returns_over_hodl\": -0.1381183976581647, \"train_sharpe\": 0.18212055067333668, \"validation_return\": -0.18541615181673965, \"validation_returns_over_hodl\": -0.035394662650687136, \"validation_sharpe\": -1.497651821071769}, {\"centeredness_margin\": 0.8733352960244001, \"continuous_test_metrics\": [{\"annualised_returns\": -0.7540945076799007, \"annualised_returns_over_hodl\": -0.2394709255573052, \"annualised_returns_over_uniform_hodl\": -0.13206259878042725, \"calmar\": -1.2646231901387488, \"daily_log_sharpe\": -1.7813943180307654, \"daily_returns\": 0.020699765536985694, \"fee_revenue_over_value\": 0.012685812061890284, \"jax_sharpe\": -1.2081379545591524, \"return\": -0.4316193000466061, \"returns_over_hodl\": -0.10438605989988803, \"returns_over_uniform_hodl\": -0.05544564774271643, \"sharpe\": -1.4015189719725265, \"sterling\": -2.2254419039573414, \"ulcer\": -0.14640994941686092}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -0.03613765396559565, \"optuna_trial_number\": 199, \"price_ratio\": 2.6831542951982588, \"shift_exponent\": 0.00690225648302622, \"step\": 199, \"test_objective\": [{\"annualised_returns\": -0.7540945076799007, \"annualised_returns_over_hodl\": -0.2394709255573052, \"annualised_returns_over_uniform_hodl\": -0.13206259878042725, \"calmar\": -1.2646231901387488, \"daily_log_sharpe\": -1.7813943180307654, \"daily_returns\": 0.020699765536985694, \"fee_revenue_over_value\": 0.012685812061890284, \"jax_sharpe\": -1.2081379545591524, \"return\": -0.4316193000466061, \"returns_over_hodl\": -0.10438605989988803, \"returns_over_uniform_hodl\": -0.05544564774271643, \"sharpe\": -1.4015189719725265, \"sterling\": -2.2254419039573414, \"ulcer\": -0.14640994941686092}], \"train_objective\": [{\"annualised_returns\": -0.38904376423614895, \"annualised_returns_over_hodl\": -0.23059948903892213, \"annualised_returns_over_uniform_hodl\": -0.23059948903892202, \"calmar\": -0.520855415179152, \"daily_log_sharpe\": -0.3984838716561736, \"daily_returns\": 0.00839722402132711, \"fee_revenue_over_value\": 0.003677129272673624, \"jax_sharpe\": 0.11990550617052735, \"return\": -0.2585495098380145, \"returns_over_hodl\": -0.14713413986076707, \"returns_over_uniform_hodl\": -0.14713413986076695, \"sharpe\": 0.14264085590324435, \"sterling\": -1.217650720921592, \"ulcer\": -0.17262042334798852}], \"train_return\": -0.2585495098380145, \"train_returns_over_hodl\": -0.14713413986076707, \"train_sharpe\": 0.11990550617052734, \"validation_return\": -0.18546363170774594, \"validation_returns_over_hodl\": -0.03613765396559565, \"validation_sharpe\": -1.5082411449561532}, {\"centeredness_margin\": 0.16965417438001773, \"continuous_test_metrics\": [{\"annualised_returns\": -0.6654868384456594, \"annualised_returns_over_hodl\": -0.27155284450934547, \"annualised_returns_over_uniform_hodl\": 0.18068320220876344, \"calmar\": -1.2088206564662247, \"daily_log_sharpe\": -1.326242388013269, \"daily_returns\": 0.028470690136637684, \"fee_revenue_over_value\": 0.0884487426145136, \"jax_sharpe\": -0.7070446857720679, \"return\": -0.35662643690372364, \"returns_over_hodl\": -0.11979771659489202, \"returns_over_uniform_hodl\": 0.06918003936392281, \"sharpe\": -0.9187238010909774, \"sterling\": -1.8838628711920562, \"ulcer\": -0.14973789491612893}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -0.0563532966197593, \"optuna_trial_number\": 200, \"price_ratio\": 1.8901224656976667, \"shift_exponent\": 0.013331315401277863, \"step\": 200, \"test_objective\": [{\"annualised_returns\": -0.6654868384456594, \"annualised_returns_over_hodl\": -0.27155284450934547, \"annualised_returns_over_uniform_hodl\": 0.18068320220876344, \"calmar\": -1.2088206564662247, \"daily_log_sharpe\": -1.326242388013269, \"daily_returns\": 0.028470690136637684, \"fee_revenue_over_value\": 0.0884487426145136, \"jax_sharpe\": -0.7070446857720679, \"return\": -0.35662643690372364, \"returns_over_hodl\": -0.11979771659489202, \"returns_over_uniform_hodl\": 0.06918003936392281, \"sharpe\": -0.9187238010909774, \"sterling\": -1.8838628711920562, \"ulcer\": -0.14973789491612893}], \"train_objective\": [{\"annualised_returns\": -0.5005700212948023, \"annualised_returns_over_hodl\": -0.37104876206945614, \"annualised_returns_over_uniform_hodl\": -0.37104876206945614, \"calmar\": -0.6498008910269861, \"daily_log_sharpe\": -0.5226765895587263, \"daily_returns\": 0.008525964736954056, \"fee_revenue_over_value\": 0.0246958802281404, \"jax_sharpe\": 0.14102249701587466, \"return\": -0.3439491746696457, \"returns_over_hodl\": -0.24536653645183149, \"returns_over_uniform_hodl\": -0.24536653645183149, \"sharpe\": 0.07200651576699443, \"sterling\": -1.4519026813397515, \"ulcer\": -0.1859999591818732}], \"train_return\": -0.3439491746696457, \"train_returns_over_hodl\": -0.24536653645183149, \"train_sharpe\": 0.14102249701587463, \"validation_return\": -0.2770850412143998, \"validation_returns_over_hodl\": -0.0563532966197593, \"validation_sharpe\": -2.0647744456573323}, {\"centeredness_margin\": 0.7759407898015677, \"continuous_test_metrics\": [{\"annualised_returns\": -0.48143179585388496, \"annualised_returns_over_hodl\": 0.005964504448898289, \"annualised_returns_over_uniform_hodl\": 0.8303159283477772, \"calmar\": -0.8305009754864325, \"daily_log_sharpe\": -0.6861567126775491, \"daily_returns\": 0.031016042454675857, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": 0.008470631871266092, \"return\": -0.2323883759212172, \"returns_over_hodl\": 0.0023978664304618036, \"returns_over_uniform_hodl\": 0.2756430688556961, \"sharpe\": -0.19539335908520492, \"sterling\": -1.2394684546535828, \"ulcer\": -0.17641031590027864}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -1.0605990530265785e-09, \"optuna_trial_number\": 201, \"price_ratio\": 1.7143299630788322, \"shift_exponent\": 0.0017544487699110312, \"step\": 201, \"test_objective\": [{\"annualised_returns\": -0.48143179585388496, \"annualised_returns_over_hodl\": 0.005964504448898289, \"annualised_returns_over_uniform_hodl\": 0.8303159283477772, \"calmar\": -0.8305009754864325, \"daily_log_sharpe\": -0.6861567126775491, \"daily_returns\": 0.031016042454675857, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": 0.008470631871266092, \"return\": -0.2323883759212172, \"returns_over_hodl\": 0.0023978664304618036, \"returns_over_uniform_hodl\": 0.2756430688556961, \"sharpe\": -0.19539335908520492, \"sterling\": -1.2394684546535828, \"ulcer\": -0.17641031590027864}], \"train_objective\": [{\"annualised_returns\": -0.6293717779090037, \"annualised_returns_over_hodl\": -0.5332537311827517, \"annualised_returns_over_uniform_hodl\": -0.5332537311827519, \"calmar\": -0.8013262271930308, \"daily_log_sharpe\": -0.6724696061873525, \"daily_returns\": 0.008804717561943053, \"fee_revenue_over_value\": 0.00035231123167358966, \"jax_sharpe\": 0.08816800916667049, \"return\": -0.45261443976385773, \"returns_over_hodl\": -0.37036057989981086, \"returns_over_uniform_hodl\": -0.37036057989981097, \"sharpe\": -0.001299578350499615, \"sterling\": -1.7006498625394668, \"ulcer\": -0.20628058381628248}], \"train_return\": -0.45261443976385773, \"train_returns_over_hodl\": -0.37036057989981086, \"train_sharpe\": 0.08816800916667049, \"validation_return\": -0.339142719665256, \"validation_returns_over_hodl\": -1.0605990530265785e-09, \"validation_sharpe\": -2.243853199312909}, {\"centeredness_margin\": 0.8902316176829624, \"continuous_test_metrics\": [{\"annualised_returns\": -0.4840143294532232, \"annualised_returns_over_hodl\": 0.0009546754944598224, \"annualised_returns_over_uniform_hodl\": 0.8212007293352461, \"calmar\": -0.8349560207424974, \"daily_log_sharpe\": -0.6914367057252242, \"daily_returns\": 0.031016042495771255, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": 0.005623597164089258, \"return\": -0.23393025808381018, \"returns_over_hodl\": 0.00038437437166471966, \"returns_over_uniform_hodl\": 0.2730807167078053, \"sharpe\": -0.20100309889208845, \"sterling\": -1.2461173178495513, \"ulcer\": -0.17641031598523543}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -8.119755978697185e-10, \"optuna_trial_number\": 202, \"price_ratio\": 4.02171996521883, \"shift_exponent\": 1.7415107528004904e-05, \"step\": 202, \"test_objective\": [{\"annualised_returns\": -0.4840143294532232, \"annualised_returns_over_hodl\": 0.0009546754944598224, \"annualised_returns_over_uniform_hodl\": 0.8212007293352461, \"calmar\": -0.8349560207424974, \"daily_log_sharpe\": -0.6914367057252242, \"daily_returns\": 0.031016042495771255, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": 0.005623597164089258, \"return\": -0.23393025808381018, \"returns_over_hodl\": 0.00038437437166471966, \"returns_over_uniform_hodl\": 0.2730807167078053, \"sharpe\": -0.20100309889208845, \"sterling\": -1.2461173178495513, \"ulcer\": -0.17641031598523543}], \"train_objective\": [{\"annualised_returns\": -0.5366440974872012, \"annualised_returns_over_hodl\": -0.41647822334695517, \"annualised_returns_over_uniform_hodl\": -0.4164782233469554, \"calmar\": -0.7076245690922314, \"daily_log_sharpe\": -0.5399853445797822, \"daily_returns\": 0.008249783880370053, \"fee_revenue_over_value\": 0.00027680640031787547, \"jax_sharpe\": 0.09927466620547609, \"return\": -0.37314128260367985, \"returns_over_hodl\": -0.2789452481430168, \"returns_over_uniform_hodl\": -0.2789452481430169, \"sharpe\": 0.08297749441042541, \"sterling\": -1.5261795430961937, \"ulcer\": -0.19213229498172243}], \"train_return\": -0.37314128260367985, \"train_returns_over_hodl\": -0.2789452481430168, \"train_sharpe\": 0.09927466620547609, \"validation_return\": -0.3391427199475783, \"validation_returns_over_hodl\": -8.119755978697185e-10, \"validation_sharpe\": -2.2438531992869595}, {\"centeredness_margin\": 0.6188638274666244, \"continuous_test_metrics\": [{\"annualised_returns\": -0.7627445046960287, \"annualised_returns_over_hodl\": -0.45771815633445045, \"annualised_returns_over_uniform_hodl\": -0.16259325452097717, \"calmar\": -1.3069195093677282, \"daily_log_sharpe\": -1.7439955954484458, \"daily_returns\": 0.02741607613757709, \"fee_revenue_over_value\": 0.039482904106328244, \"jax_sharpe\": -1.1227858567537867, \"return\": -0.43975761184368756, \"returns_over_hodl\": -0.21843989833677335, \"returns_over_uniform_hodl\": -0.06897017070521427, \"sharpe\": -1.3514185721224679, \"sterling\": -2.1894856432558467, \"ulcer\": -0.14805108227431688}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -0.06318434486532598, \"optuna_trial_number\": 203, \"price_ratio\": 1.689056209366159, \"shift_exponent\": 0.00744547806116632, \"step\": 203, \"test_objective\": [{\"annualised_returns\": -0.7627445046960287, \"annualised_returns_over_hodl\": -0.45771815633445045, \"annualised_returns_over_uniform_hodl\": -0.16259325452097717, \"calmar\": -1.3069195093677282, \"daily_log_sharpe\": -1.7439955954484458, \"daily_returns\": 0.02741607613757709, \"fee_revenue_over_value\": 0.039482904106328244, \"jax_sharpe\": -1.1227858567537867, \"return\": -0.43975761184368756, \"returns_over_hodl\": -0.21843989833677335, \"returns_over_uniform_hodl\": -0.06897017070521427, \"sharpe\": -1.3514185721224679, \"sterling\": -2.1894856432558467, \"ulcer\": -0.14805108227431688}], \"train_objective\": [{\"annualised_returns\": -0.4676378071123428, \"annualised_returns_over_hodl\": -0.32957596756170326, \"annualised_returns_over_uniform_hodl\": -0.3295759675617037, \"calmar\": -0.611136090182418, \"daily_log_sharpe\": -0.4777826132198265, \"daily_returns\": 0.008822019096381972, \"fee_revenue_over_value\": 0.0053110340759601315, \"jax_sharpe\": 0.16715165760253184, \"return\": -0.3180154449519904, \"returns_over_hodl\": -0.21553582894574486, \"returns_over_uniform_hodl\": -0.2155358289457452, \"sharpe\": 0.11364865993315999, \"sterling\": -1.3770061101534492, \"ulcer\": -0.18320075660529844}], \"train_return\": -0.3180154449519904, \"train_returns_over_hodl\": -0.21553582894574486, \"train_sharpe\": 0.1671516576025318, \"validation_return\": -0.251103195323366, \"validation_returns_over_hodl\": -0.06318434486532598, \"validation_sharpe\": -1.9146178758168342}, {\"centeredness_margin\": 0.8835836213028843, \"continuous_test_metrics\": [{\"annualised_returns\": -0.4782075934903445, \"annualised_returns_over_hodl\": 0.012219095340241015, \"annualised_returns_over_uniform_hodl\": 0.8416959337068066, \"calmar\": -0.8249390085995165, \"daily_log_sharpe\": -0.6810249968728335, \"daily_returns\": 0.031016042509081233, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": 0.009646384322599955, \"return\": -0.2304698136038149, \"returns_over_hodl\": 0.004903250693482519, \"returns_over_uniform_hodl\": 0.2788314008788064, \"sharpe\": -0.19033648737029488, \"sterling\": -1.2311675813175535, \"ulcer\": -0.17641031601275944}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -6.31204644108152e-10, \"optuna_trial_number\": 204, \"price_ratio\": 5.346286847182812, \"shift_exponent\": 7.721765601260488e-05, \"step\": 204, \"test_objective\": [{\"annualised_returns\": -0.4782075934903445, \"annualised_returns_over_hodl\": 0.012219095340241015, \"annualised_returns_over_uniform_hodl\": 0.8416959337068066, \"calmar\": -0.8249390085995165, \"daily_log_sharpe\": -0.6810249968728335, \"daily_returns\": 0.031016042509081233, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": 0.009646384322599955, \"return\": -0.2304698136038149, \"returns_over_hodl\": 0.004903250693482519, \"returns_over_uniform_hodl\": 0.2788314008788064, \"sharpe\": -0.19033648737029488, \"sterling\": -1.2311675813175535, \"ulcer\": -0.17641031601275944}], \"train_objective\": [{\"annualised_returns\": -0.49903699335114093, \"annualised_returns_over_hodl\": -0.369118161456638, \"annualised_returns_over_uniform_hodl\": -0.369118161456638, \"calmar\": -0.6635368838943865, \"daily_log_sharpe\": -0.49726761956392396, \"daily_returns\": 0.008154208911540265, \"fee_revenue_over_value\": 0.00027055021109506687, \"jax_sharpe\": 0.09314717031241977, \"return\": -0.34272729932222656, \"returns_over_hodl\": -0.24396105384309807, \"returns_over_uniform_hodl\": -0.24396105384309807, \"sharpe\": 0.09853150471750176, \"sterling\": -1.4635218627572584, \"ulcer\": -0.1852595474119261}], \"train_return\": -0.34272729932222656, \"train_returns_over_hodl\": -0.24396105384309807, \"train_sharpe\": 0.09314717031241977, \"validation_return\": -0.33914271998182643, \"validation_returns_over_hodl\": -6.31204644108152e-10, \"validation_sharpe\": -2.2438531987024257}, {\"centeredness_margin\": 0.7834451986337839, \"continuous_test_metrics\": [{\"annualised_returns\": -0.4792090234866755, \"annualised_returns_over_hodl\": 0.01027643287555402, \"annualised_returns_over_uniform_hodl\": 0.8381613296590547, \"calmar\": -0.8266665430153851, \"daily_log_sharpe\": -0.6821246429290091, \"daily_returns\": 0.031016042507108085, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": 0.008237685771504888, \"return\": -0.2310649548000504, \"returns_over_hodl\": 0.004126076115755994, \"returns_over_uniform_hodl\": 0.27784237502490594, \"sharpe\": -0.1913674285260727, \"sterling\": -1.2337458136216435, \"ulcer\": -0.1764103160086785}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -6.593737778004538e-10, \"optuna_trial_number\": 205, \"price_ratio\": 5.089732308441989, \"shift_exponent\": 6.686111202595295e-05, \"step\": 205, \"test_objective\": [{\"annualised_returns\": -0.4792090234866755, \"annualised_returns_over_hodl\": 0.01027643287555402, \"annualised_returns_over_uniform_hodl\": 0.8381613296590547, \"calmar\": -0.8266665430153851, \"daily_log_sharpe\": -0.6821246429290091, \"daily_returns\": 0.031016042507108085, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": 0.008237685771504888, \"return\": -0.2310649548000504, \"returns_over_hodl\": 0.004126076115755994, \"returns_over_uniform_hodl\": 0.27784237502490594, \"sharpe\": -0.1913674285260727, \"sterling\": -1.2337458136216435, \"ulcer\": -0.1764103160086785}], \"train_objective\": [{\"annualised_returns\": -0.5048745925258146, \"annualised_returns_over_hodl\": -0.37646967294774236, \"annualised_returns_over_uniform_hodl\": -0.37646967294774236, \"calmar\": -0.6705629336617657, \"daily_log_sharpe\": -0.5033768552188677, \"daily_returns\": 0.008178451543264142, \"fee_revenue_over_value\": 0.0004025029780683604, \"jax_sharpe\": 0.09446174953202083, \"return\": -0.34738796696244056, \"returns_over_hodl\": -0.2493220649538005, \"returns_over_uniform_hodl\": -0.2493220649538005, \"sharpe\": 0.09707690144891795, \"sterling\": -1.4737769292575036, \"ulcer\": -0.1862543446422267}], \"train_return\": -0.34738796696244056, \"train_returns_over_hodl\": -0.2493220649538005, \"train_sharpe\": 0.09446174953202083, \"validation_return\": -0.3391427199786933, \"validation_returns_over_hodl\": -6.593737778004538e-10, \"validation_sharpe\": -2.2438531988079675}, {\"centeredness_margin\": 0.850011425386539, \"continuous_test_metrics\": [{\"annualised_returns\": -0.4960890777225777, \"annualised_returns_over_hodl\": -0.022468991105665914, \"annualised_returns_over_uniform_hodl\": 0.7785822195394514, \"calmar\": -0.8557857797663411, \"daily_log_sharpe\": -0.7245231396514422, \"daily_returns\": 0.031016042524002703, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": -0.041319876959430055, \"return\": -0.24120125155071725, \"returns_over_hodl\": -0.009110568719064793, \"returns_over_uniform_hodl\": 0.2609975328048959, \"sharpe\": -0.23915809709101235, \"sterling\": -1.2772043719145203, \"ulcer\": -0.17641031604359197}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -0.046595005611452245, \"optuna_trial_number\": 206, \"price_ratio\": 8.094306966565824, \"shift_exponent\": 4.05591218491831e-05, \"step\": 206, \"test_objective\": [{\"annualised_returns\": -0.4960890777225777, \"annualised_returns_over_hodl\": -0.022468991105665914, \"annualised_returns_over_uniform_hodl\": 0.7785822195394514, \"calmar\": -0.8557857797663411, \"daily_log_sharpe\": -0.7245231396514422, \"daily_returns\": 0.031016042524002703, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": -0.041319876959430055, \"return\": -0.24120125155071725, \"returns_over_hodl\": -0.009110568719064793, \"returns_over_uniform_hodl\": 0.2609975328048959, \"sharpe\": -0.23915809709101235, \"sterling\": -1.2772043719145203, \"ulcer\": -0.17641031604359197}], \"train_objective\": [{\"annualised_returns\": -0.4568978177973817, \"annualised_returns_over_hodl\": -0.3160506890181167, \"annualised_returns_over_uniform_hodl\": -0.3160506890181167, \"calmar\": -0.6115690999315274, \"daily_log_sharpe\": -0.45812974559038244, \"daily_returns\": 0.008146692713766376, \"fee_revenue_over_value\": 0.00029489725871565173, \"jax_sharpe\": 0.09237378727865893, \"return\": -0.309695160503225, \"returns_over_hodl\": -0.20596528223068533, \"returns_over_uniform_hodl\": -0.20596528223068533, \"sharpe\": 0.10878184217669466, \"sterling\": -1.3800181132134457, \"ulcer\": -0.17937013541072624}], \"train_return\": -0.309695160503225, \"train_returns_over_hodl\": -0.20596528223068533, \"train_sharpe\": 0.09237378727865891, \"validation_return\": -0.3343712443690674, \"validation_returns_over_hodl\": -0.046595005611452245, \"validation_sharpe\": -2.2016040131193537}, {\"centeredness_margin\": 0.8085776248035765, \"continuous_test_metrics\": [{\"annualised_returns\": -0.4922728268859319, \"annualised_returns_over_hodl\": -0.015065889840472635, \"annualised_returns_over_uniform_hodl\": 0.7920518936093941, \"calmar\": -0.8492024882241282, \"daily_log_sharpe\": -0.7158631911688816, \"daily_returns\": 0.03101604252125508, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": -0.03368263464112556, \"return\": -0.2388920980842829, \"returns_over_hodl\": -0.006095123841396433, \"returns_over_uniform_hodl\": 0.26483496246591276, \"sharpe\": -0.22973104032286248, \"sterling\": -1.2673792393990737, \"ulcer\": -0.17641031603791116}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -0.03762786125517725, \"optuna_trial_number\": 207, \"price_ratio\": 7.265144472596018, \"shift_exponent\": 0.00012369814128218823, \"step\": 207, \"test_objective\": [{\"annualised_returns\": -0.4922728268859319, \"annualised_returns_over_hodl\": -0.015065889840472635, \"annualised_returns_over_uniform_hodl\": 0.7920518936093941, \"calmar\": -0.8492024882241282, \"daily_log_sharpe\": -0.7158631911688816, \"daily_returns\": 0.03101604252125508, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": -0.03368263464112556, \"return\": -0.2388920980842829, \"returns_over_hodl\": -0.006095123841396433, \"returns_over_uniform_hodl\": 0.26483496246591276, \"sharpe\": -0.22973104032286248, \"sterling\": -1.2673792393990737, \"ulcer\": -0.17641031603791116}], \"train_objective\": [{\"annualised_returns\": -0.4636267240762607, \"annualised_returns_over_hodl\": -0.3245246575712094, \"annualised_returns_over_uniform_hodl\": -0.3245246575712094, \"calmar\": -0.6197282454132255, \"daily_log_sharpe\": -0.46351665939993103, \"daily_returns\": 0.008146186326853672, \"fee_revenue_over_value\": 0.00041746905187776, \"jax_sharpe\": 0.13957216483817492, \"return\": -0.314900405898348, \"returns_over_hodl\": -0.21195270303632463, \"returns_over_uniform_hodl\": -0.21195270303632463, \"sharpe\": 0.1085714503378087, \"sterling\": -1.3933137825953164, \"ulcer\": -0.18032034222313803}], \"train_return\": -0.314900405898348, \"train_returns_over_hodl\": -0.21195270303632463, \"train_sharpe\": 0.1395721648381749, \"validation_return\": -0.3363796114656815, \"validation_returns_over_hodl\": -0.03762786125517725, \"validation_sharpe\": -2.219326859137363}, {\"centeredness_margin\": 0.6461257256674842, \"continuous_test_metrics\": [{\"annualised_returns\": -0.4779405559612204, \"annualised_returns_over_hodl\": 0.012737118272731163, \"annualised_returns_over_uniform_hodl\": 0.842638457832108, \"calmar\": -0.8244783508242802, \"daily_log_sharpe\": -0.6813214481956376, \"daily_returns\": 0.031016042511560867, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": 0.006181375125676649, \"return\": -0.23031123073684023, \"returns_over_hodl\": 0.005110338615125887, \"returns_over_uniform_hodl\": 0.27909493927341855, \"sharpe\": -0.190932400317387, \"sterling\": -1.2304800796308784, \"ulcer\": -0.17641031601788232}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -6.174458722085774e-10, \"optuna_trial_number\": 208, \"price_ratio\": 5.671239449405582, \"shift_exponent\": 6.892486064363522e-05, \"step\": 208, \"test_objective\": [{\"annualised_returns\": -0.4779405559612204, \"annualised_returns_over_hodl\": 0.012737118272731163, \"annualised_returns_over_uniform_hodl\": 0.842638457832108, \"calmar\": -0.8244783508242802, \"daily_log_sharpe\": -0.6813214481956376, \"daily_returns\": 0.031016042511560867, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": 0.006181375125676649, \"return\": -0.23031123073684023, \"returns_over_hodl\": 0.005110338615125887, \"returns_over_uniform_hodl\": 0.27909493927341855, \"sharpe\": -0.190932400317387, \"sterling\": -1.2304800796308784, \"ulcer\": -0.17641031601788232}], \"train_objective\": [{\"annualised_returns\": -0.4901605633607671, \"annualised_returns_over_hodl\": -0.3579397343119093, \"annualised_returns_over_uniform_hodl\": -0.3579397343119093, \"calmar\": -0.6529319088091845, \"daily_log_sharpe\": -0.4865051334069802, \"daily_returns\": 0.008153836596467155, \"fee_revenue_over_value\": 0.00216130077988876, \"jax_sharpe\": 0.09503220622461445, \"return\": -0.33568114935498206, \"returns_over_hodl\": -0.235856101682137, \"returns_over_uniform_hodl\": -0.235856101682137, \"sharpe\": 0.10433845082150044, \"sterling\": -1.4456512177874628, \"ulcer\": -0.18405083609025508}], \"train_return\": -0.33568114935498206, \"train_returns_over_hodl\": -0.235856101682137, \"train_sharpe\": 0.09503220622461445, \"validation_return\": -0.3391427199891778, \"validation_returns_over_hodl\": -6.174458722085774e-10, \"validation_sharpe\": -2.243853198606051}, {\"centeredness_margin\": 0.9797017870772639, \"continuous_test_metrics\": [{\"annualised_returns\": -0.5572327570777685, \"annualised_returns_over_hodl\": -0.14108091230504072, \"annualised_returns_over_uniform_hodl\": 0.5627721306315285, \"calmar\": -0.9623773457718438, \"daily_log_sharpe\": -0.8736338769504391, \"daily_returns\": 0.031016042523787687, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": -0.19266759710026848, \"return\": -0.27971978720070045, \"returns_over_hodl\": -0.05941061202648257, \"returns_over_uniform_hodl\": 0.19698612197804644, \"sharpe\": -0.401000277950148, \"sterling\": -1.4456704352089997, \"ulcer\": -0.17368015883963067}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -0.06593184208880462, \"optuna_trial_number\": 209, \"price_ratio\": 12.44446778869966, \"shift_exponent\": 0.000259486952875798, \"step\": 209, \"test_objective\": [{\"annualised_returns\": -0.5572327570777685, \"annualised_returns_over_hodl\": -0.14108091230504072, \"annualised_returns_over_uniform_hodl\": 0.5627721306315285, \"calmar\": -0.9623773457718438, \"daily_log_sharpe\": -0.8736338769504391, \"daily_returns\": 0.031016042523787687, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": -0.19266759710026848, \"return\": -0.27971978720070045, \"returns_over_hodl\": -0.05941061202648257, \"returns_over_uniform_hodl\": 0.19698612197804644, \"sharpe\": -0.401000277950148, \"sterling\": -1.4456704352089997, \"ulcer\": -0.17368015883963067}], \"train_objective\": [{\"annualised_returns\": -0.4207776814088712, \"annualised_returns_over_hodl\": -0.27056322237730546, \"annualised_returns_over_uniform_hodl\": -0.27056322237730535, \"calmar\": -0.5659774594775011, \"daily_log_sharpe\": -0.4255056181632396, \"daily_returns\": 0.008036142438919246, \"fee_revenue_over_value\": 9.042519032025276e-05, \"jax_sharpe\": 0.1393872271714123, \"return\": -0.2821754578489162, \"returns_over_hodl\": -0.1743102827580768, \"returns_over_uniform_hodl\": -0.1743102827580767, \"sharpe\": 0.11907810343926736, \"sterling\": -1.3011711798233503, \"ulcer\": -0.17480732018783046}], \"train_return\": -0.2821754578489162, \"train_returns_over_hodl\": -0.1743102827580768, \"train_sharpe\": 0.1393872271714123, \"validation_return\": -0.30217822775931447, \"validation_returns_over_hodl\": -0.06593184208880465, \"validation_sharpe\": -2.043246648562686}, {\"centeredness_margin\": 0.9883658933503594, \"continuous_test_metrics\": [{\"annualised_returns\": -0.5043279308701227, \"annualised_returns_over_hodl\": -0.038451446915880316, \"annualised_returns_over_uniform_hodl\": 0.7495027194337704, \"calmar\": -0.8699983610243869, \"daily_log_sharpe\": -0.7434082812010475, \"daily_returns\": 0.03101604248672855, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": -0.06131354392472921, \"return\": -0.24622231710583298, \"returns_over_hodl\": -0.015667406833820396, \"returns_over_uniform_hodl\": 0.2526533555246988, \"sharpe\": -0.25959525274866896, \"sterling\": -1.298415721212886, \"ulcer\": -0.17641031602703636}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -0.05157244130410965, \"optuna_trial_number\": 210, \"price_ratio\": 7.83449463104397, \"shift_exponent\": 0.00021366915753773122, \"step\": 210, \"test_objective\": [{\"annualised_returns\": -0.5043279308701227, \"annualised_returns_over_hodl\": -0.038451446915880316, \"annualised_returns_over_uniform_hodl\": 0.7495027194337704, \"calmar\": -0.8699983610243869, \"daily_log_sharpe\": -0.7434082812010475, \"daily_returns\": 0.03101604248672855, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": -0.06131354392472921, \"return\": -0.24622231710583298, \"returns_over_hodl\": -0.015667406833820396, \"returns_over_uniform_hodl\": 0.2526533555246988, \"sharpe\": -0.25959525274866896, \"sterling\": -1.298415721212886, \"ulcer\": -0.17641031602703636}], \"train_objective\": [{\"annualised_returns\": -0.45347966935478146, \"annualised_returns_over_hodl\": -0.31174608419648064, \"annualised_returns_over_uniform_hodl\": -0.311746084196481, \"calmar\": -0.6069428903267431, \"daily_log_sharpe\": -0.454266740624439, \"daily_returns\": 0.008067287394861362, \"fee_revenue_over_value\": 4.783220325651646e-05, \"jax_sharpe\": 0.09542516442197098, \"return\": -0.30706070954981945, \"returns_over_hodl\": -0.20293496084283358, \"returns_over_uniform_hodl\": -0.2029349608428338, \"sharpe\": 0.11157420381957475, \"sterling\": -1.3715101562996816, \"ulcer\": -0.17904504436598517}], \"train_return\": -0.30706070954981945, \"train_returns_over_hodl\": -0.20293496084283358, \"train_sharpe\": 0.09542516442197098, \"validation_return\": -0.3328231638542247, \"validation_returns_over_hodl\": -0.05157244130410965, \"validation_sharpe\": -2.1878437657755385}, {\"centeredness_margin\": 0.9868604543569424, \"continuous_test_metrics\": [{\"annualised_returns\": -0.4841128903265667, \"annualised_returns_over_hodl\": 0.0007634784417436258, \"annualised_returns_over_uniform_hodl\": 0.8208528531350676, \"calmar\": -0.835126044914731, \"daily_log_sharpe\": -0.6916443773210613, \"daily_returns\": 0.031016042494466385, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": 0.005467307474302851, \"return\": -0.23398919432104193, \"returns_over_hodl\": 0.0003074115690315793, \"returns_over_uniform_hodl\": 0.27298277446700214, \"sharpe\": -0.2012285829281753, \"sterling\": -1.246371067846216, \"ulcer\": -0.17641031598253548}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -8.081161295692141e-10, \"optuna_trial_number\": 211, \"price_ratio\": 3.82644613052936, \"shift_exponent\": 0.00011560950585779667, \"step\": 211, \"test_objective\": [{\"annualised_returns\": -0.4841128903265667, \"annualised_returns_over_hodl\": 0.0007634784417436258, \"annualised_returns_over_uniform_hodl\": 0.8208528531350676, \"calmar\": -0.835126044914731, \"daily_log_sharpe\": -0.6916443773210613, \"daily_returns\": 0.031016042494466385, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": 0.005467307474302851, \"return\": -0.23398919432104193, \"returns_over_hodl\": 0.0003074115690315793, \"returns_over_uniform_hodl\": 0.27298277446700214, \"sharpe\": -0.2012285829281753, \"sterling\": -1.246371067846216, \"ulcer\": -0.17641031598253548}], \"train_objective\": [{\"annualised_returns\": -0.5412562210718699, \"annualised_returns_over_hodl\": -0.42228644664501713, \"annualised_returns_over_uniform_hodl\": -0.42228644664501713, \"calmar\": -0.7124482984720955, \"daily_log_sharpe\": -0.5457469227398303, \"daily_returns\": 0.008166502099951893, \"fee_revenue_over_value\": 4.617041411140957e-05, \"jax_sharpe\": 0.09982208527505142, \"return\": -0.3769369113415395, \"returns_over_hodl\": -0.2833112337499273, \"returns_over_uniform_hodl\": -0.2833112337499273, \"sharpe\": 0.08012584648826039, \"sterling\": -1.5337130882797037, \"ulcer\": -0.19293715620468646}], \"train_return\": -0.3769369113415395, \"train_returns_over_hodl\": -0.2833112337499273, \"train_sharpe\": 0.0998220852750514, \"validation_return\": -0.33914271993748657, \"validation_returns_over_hodl\": -8.081161295692141e-10, \"validation_sharpe\": -2.2438531992760957}, {\"centeredness_margin\": 0.6516685187446334, \"continuous_test_metrics\": [{\"annualised_returns\": -0.4876668844720534, \"annualised_returns_over_hodl\": -0.006130875037356764, \"annualised_returns_over_uniform_hodl\": 0.808308828951305, \"calmar\": -0.8412569254659799, \"daily_log_sharpe\": -0.7052884419044523, \"daily_returns\": 0.03101604252053288, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": -0.023126910587525303, \"return\": -0.236118878410984, \"returns_over_hodl\": -0.002473670749112422, \"returns_over_uniform_hodl\": 0.2694435931115253, \"sharpe\": -0.21812438906989956, \"sterling\": -1.2555210060422455, \"ulcer\": -0.1764103160364211}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -0.032380553019814906, \"optuna_trial_number\": 212, \"price_ratio\": 7.295023983736121, \"shift_exponent\": 2.7941019322931685e-05, \"step\": 212, \"test_objective\": [{\"annualised_returns\": -0.4876668844720534, \"annualised_returns_over_hodl\": -0.006130875037356764, \"annualised_returns_over_uniform_hodl\": 0.808308828951305, \"calmar\": -0.8412569254659799, \"daily_log_sharpe\": -0.7052884419044523, \"daily_returns\": 0.03101604252053288, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": -0.023126910587525303, \"return\": -0.236118878410984, \"returns_over_hodl\": -0.002473670749112422, \"returns_over_uniform_hodl\": 0.2694435931115253, \"sharpe\": -0.21812438906989956, \"sterling\": -1.2555210060422455, \"ulcer\": -0.1764103160364211}], \"train_objective\": [{\"annualised_returns\": -0.4626704679595056, \"annualised_returns_over_hodl\": -0.3233204077382875, \"annualised_returns_over_uniform_hodl\": -0.3233204077382875, \"calmar\": -0.6193104223772213, \"daily_log_sharpe\": -0.4604013008536671, \"daily_returns\": 0.008114222843123993, \"fee_revenue_over_value\": 0.0037255549079135033, \"jax_sharpe\": 0.09724427324529483, \"return\": -0.31415912239070654, \"returns_over_hodl\": -0.21110002925062976, \"returns_over_uniform_hodl\": -0.21110002925062976, \"sharpe\": 0.11267129636848457, \"sterling\": -1.3902132764282313, \"ulcer\": -0.18030278474823916}], \"train_return\": -0.31415912239070654, \"train_returns_over_hodl\": -0.21110002925062976, \"train_sharpe\": 0.09724427324529485, \"validation_return\": -0.337135001110064, \"validation_returns_over_hodl\": -0.032380553019814906, \"validation_sharpe\": -2.2259260885397123}, {\"centeredness_margin\": 0.6347459461123989, \"continuous_test_metrics\": [{\"annualised_returns\": -0.47827030138837423, \"annualised_returns_over_hodl\": 0.01209744905842225, \"annualised_returns_over_uniform_hodl\": 0.841474602619247, \"calmar\": -0.8250471839581621, \"daily_log_sharpe\": -0.6808728612142273, \"daily_returns\": 0.031016042507893013, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": 0.008311443357659964, \"return\": -0.23050706033418433, \"returns_over_hodl\": 0.004854611477057569, \"returns_over_uniform_hodl\": 0.2787695029971915, \"sharpe\": -0.19011241779701263, \"sterling\": -1.2313290259972784, \"ulcer\": -0.17641031601027762}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -6.502882676784338e-10, \"optuna_trial_number\": 213, \"price_ratio\": 5.077081644988128, \"shift_exponent\": 0.00011494102425434309, \"step\": 213, \"test_objective\": [{\"annualised_returns\": -0.47827030138837423, \"annualised_returns_over_hodl\": 0.01209744905842225, \"annualised_returns_over_uniform_hodl\": 0.841474602619247, \"calmar\": -0.8250471839581621, \"daily_log_sharpe\": -0.6808728612142273, \"daily_returns\": 0.031016042507893013, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": 0.008311443357659964, \"return\": -0.23050706033418433, \"returns_over_hodl\": 0.004854611477057569, \"returns_over_uniform_hodl\": 0.2787695029971915, \"sharpe\": -0.19011241779701263, \"sterling\": -1.2313290259972784, \"ulcer\": -0.17641031601027762}], \"train_objective\": [{\"annualised_returns\": -0.5018027857434866, \"annualised_returns_over_hodl\": -0.372601229400486, \"annualised_returns_over_uniform_hodl\": -0.3726012294004858, \"calmar\": -0.6669928944755042, \"daily_log_sharpe\": -0.4988361993887865, \"daily_returns\": 0.008189220880569165, \"fee_revenue_over_value\": 0.0021538710915408312, \"jax_sharpe\": 0.09705921816474278, \"return\": -0.3449327990428527, \"returns_over_hodl\": -0.24649796688210335, \"returns_over_uniform_hodl\": -0.24649796688210313, \"sharpe\": 0.100756933814301, \"sterling\": -1.466942485288465, \"ulcer\": -0.1858741687753221}], \"train_return\": -0.3449327990428527, \"train_returns_over_hodl\": -0.24649796688210335, \"train_sharpe\": 0.09705921816474279, \"validation_return\": -0.3391427199813034, \"validation_returns_over_hodl\": -6.502882676784338e-10, \"validation_sharpe\": -2.2438531987814523}, {\"centeredness_margin\": 0.7057124306300946, \"continuous_test_metrics\": [{\"annualised_returns\": -0.47730501145513116, \"annualised_returns_over_hodl\": 0.013970003852715962, \"annualised_returns_over_uniform_hodl\": 0.844881648261772, \"calmar\": -0.8233819936059253, \"daily_log_sharpe\": -0.6797397940320754, \"daily_returns\": 0.03101604250930596, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": 0.008145052111972782, \"return\": -0.22993400113293472, \"returns_over_hodl\": 0.00560294993908772, \"returns_over_uniform_hodl\": 0.279721832241808, \"sharpe\": -0.18915870137457927, \"sterling\": -1.2288438381467406, \"ulcer\": -0.17641031601320845}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -6.291387411039295e-10, \"optuna_trial_number\": 214, \"price_ratio\": 5.132613020295665, \"shift_exponent\": 0.00019086209922192362, \"step\": 214, \"test_objective\": [{\"annualised_returns\": -0.47730501145513116, \"annualised_returns_over_hodl\": 0.013970003852715962, \"annualised_returns_over_uniform_hodl\": 0.844881648261772, \"calmar\": -0.8233819936059253, \"daily_log_sharpe\": -0.6797397940320754, \"daily_returns\": 0.03101604250930596, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": 0.008145052111972782, \"return\": -0.22993400113293472, \"returns_over_hodl\": 0.00560294993908772, \"returns_over_uniform_hodl\": 0.279721832241808, \"sharpe\": -0.18915870137457927, \"sterling\": -1.2288438381467406, \"ulcer\": -0.17641031601320845}], \"train_objective\": [{\"annualised_returns\": -0.4984621219267984, \"annualised_returns_over_hodl\": -0.368394203926238, \"annualised_returns_over_uniform_hodl\": -0.368394203926238, \"calmar\": -0.6626747608404352, \"daily_log_sharpe\": -0.4951238608824993, \"daily_returns\": 0.008165556662092954, \"fee_revenue_over_value\": 0.001165695994669221, \"jax_sharpe\": 0.152651776146354, \"return\": -0.3422694860995751, \"returns_over_hodl\": -0.24343444650639712, \"returns_over_uniform_hodl\": -0.24343444650639712, \"sharpe\": 0.1021354580163135, \"sterling\": -1.4606626948250894, \"ulcer\": -0.1853814583157508}], \"train_return\": -0.3422694860995751, \"train_returns_over_hodl\": -0.24343444650639712, \"train_sharpe\": 0.15265177614635397, \"validation_return\": -0.33914271998293777, \"validation_returns_over_hodl\": -6.291387411039295e-10, \"validation_sharpe\": -2.243853198700009}, {\"centeredness_margin\": 0.5718048984216457, \"continuous_test_metrics\": [{\"annualised_returns\": -0.4775590060984469, \"annualised_returns_over_hodl\": 0.013477282523486789, \"annualised_returns_over_uniform_hodl\": 0.8439851597426957, \"calmar\": -0.8238201515279943, \"daily_log_sharpe\": -0.6797260871584511, \"daily_returns\": 0.03101604251023443, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": 0.008305190644423144, \"return\": -0.23008472763564025, \"returns_over_hodl\": 0.0054061213194700475, \"returns_over_uniform_hodl\": 0.2794713498201804, \"sharpe\": -0.1889809189381326, \"sterling\": -1.2294977602044217, \"ulcer\": -0.17641031601513088}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -6.254206041944599e-10, \"optuna_trial_number\": 215, \"price_ratio\": 5.421441548048643, \"shift_exponent\": 5.022964720836585e-05, \"step\": 215, \"test_objective\": [{\"annualised_returns\": -0.4775590060984469, \"annualised_returns_over_hodl\": 0.013477282523486789, \"annualised_returns_over_uniform_hodl\": 0.8439851597426957, \"calmar\": -0.8238201515279943, \"daily_log_sharpe\": -0.6797260871584511, \"daily_returns\": 0.03101604251023443, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": 0.008305190644423144, \"return\": -0.23008472763564025, \"returns_over_hodl\": 0.0054061213194700475, \"returns_over_uniform_hodl\": 0.2794713498201804, \"sharpe\": -0.1889809189381326, \"sterling\": -1.2294977602044217, \"ulcer\": -0.17641031601513088}], \"train_objective\": [{\"annualised_returns\": -0.4908157637101511, \"annualised_returns_over_hodl\": -0.35876485312417195, \"annualised_returns_over_uniform_hodl\": -0.35876485312417195, \"calmar\": -0.6545905135727033, \"daily_log_sharpe\": -0.48478067085639565, \"daily_returns\": 0.008180682660738132, \"fee_revenue_over_value\": 0.0076016219137645166, \"jax_sharpe\": 0.10188239696384024, \"return\": -0.3361995941147381, \"returns_over_hodl\": -0.23645245146115912, \"returns_over_uniform_hodl\": -0.23645245146115912, \"sharpe\": 0.109339596518716, \"sterling\": -1.4445347864452092, \"ulcer\": -0.18435703154643526}], \"train_return\": -0.3361995941147381, \"train_returns_over_hodl\": -0.23645245146115912, \"train_sharpe\": 0.10188239696384024, \"validation_return\": -0.33914271999071666, \"validation_returns_over_hodl\": -6.254206041944599e-10, \"validation_sharpe\": -2.243853198712583}, {\"centeredness_margin\": 0.655859954798205, \"continuous_test_metrics\": [{\"annualised_returns\": -0.48361302287616725, \"annualised_returns_over_hodl\": 0.0017331655391596978, \"annualised_returns_over_uniform_hodl\": 0.8226171636909663, \"calmar\": -0.8342637397427877, \"daily_log_sharpe\": -0.6906057174788272, \"daily_returns\": 0.031016042495152288, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": 0.005739901982872359, \"return\": -0.2336903590354985, \"returns_over_hodl\": 0.0006976503752211816, \"returns_over_uniform_hodl\": 0.27347938909447134, \"sharpe\": -0.2001136456144398, \"sterling\": -1.245084133692951, \"ulcer\": -0.17641031598395243}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -8.046210364653916e-10, \"optuna_trial_number\": 216, \"price_ratio\": 3.674052539830011, \"shift_exponent\": 0.00023679733712829074, \"step\": 216, \"test_objective\": [{\"annualised_returns\": -0.48361302287616725, \"annualised_returns_over_hodl\": 0.0017331655391596978, \"annualised_returns_over_uniform_hodl\": 0.8226171636909663, \"calmar\": -0.8342637397427877, \"daily_log_sharpe\": -0.6906057174788272, \"daily_returns\": 0.031016042495152288, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": 0.005739901982872359, \"return\": -0.2336903590354985, \"returns_over_hodl\": 0.0006976503752211816, \"returns_over_uniform_hodl\": 0.27347938909447134, \"sharpe\": -0.2001136456144398, \"sterling\": -1.245084133692951, \"ulcer\": -0.17641031598395243}], \"train_objective\": [{\"annualised_returns\": -0.538629967579261, \"annualised_returns_over_hodl\": -0.4189791053645072, \"annualised_returns_over_uniform_hodl\": -0.41897910536450733, \"calmar\": -0.7089715117416084, \"daily_log_sharpe\": -0.53961759418881, \"daily_returns\": 0.008291696144877127, \"fee_revenue_over_value\": 0.001234756048255067, \"jax_sharpe\": 0.10819748702774933, \"return\": -0.37477376139137597, \"returns_over_hodl\": -0.28082303424458543, \"returns_over_uniform_hodl\": -0.28082303424458555, \"sharpe\": 0.08802195130842756, \"sterling\": -1.524540310968823, \"ulcer\": -0.19305467861617623}], \"train_return\": -0.37477376139137597, \"train_returns_over_hodl\": -0.28082303424458543, \"train_sharpe\": 0.10819748702774933, \"validation_return\": -0.33914271994272804, \"validation_returns_over_hodl\": -8.046210364653916e-10, \"validation_sharpe\": -2.2438531992808533}, {\"centeredness_margin\": 0.5991891171192396, \"continuous_test_metrics\": [{\"annualised_returns\": -0.48331065865291, \"annualised_returns_over_hodl\": 0.0023197183200116545, \"annualised_returns_over_uniform_hodl\": 0.8236843753895731, \"calmar\": -0.8337421410076645, \"daily_log_sharpe\": -0.6899778991632544, \"daily_returns\": 0.031016042497661378, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": 0.006365064128939939, \"return\": -0.2335096807727971, \"returns_over_hodl\": 0.0009335919210575749, \"returns_over_uniform_hodl\": 0.2737796463681734, \"sharpe\": -0.19943961179696496, \"sterling\": -1.2443056816033635, \"ulcer\": -0.1764103159891398}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -7.8520379087621e-10, \"optuna_trial_number\": 217, \"price_ratio\": 4.086823464815285, \"shift_exponent\": 0.00010395610705327472, \"step\": 217, \"test_objective\": [{\"annualised_returns\": -0.48331065865291, \"annualised_returns_over_hodl\": 0.0023197183200116545, \"annualised_returns_over_uniform_hodl\": 0.8236843753895731, \"calmar\": -0.8337421410076645, \"daily_log_sharpe\": -0.6899778991632544, \"daily_returns\": 0.031016042497661378, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": 0.006365064128939939, \"return\": -0.2335096807727971, \"returns_over_hodl\": 0.0009335919210575749, \"returns_over_uniform_hodl\": 0.2737796463681734, \"sharpe\": -0.19943961179696496, \"sterling\": -1.2443056816033635, \"ulcer\": -0.1764103159891398}], \"train_objective\": [{\"annualised_returns\": -0.5308348535593762, \"annualised_returns_over_hodl\": -0.4091624207006692, \"annualised_returns_over_uniform_hodl\": -0.4091624207006692, \"calmar\": -0.7010819440269016, \"daily_log_sharpe\": -0.5322119703582087, \"daily_returns\": 0.00825620861132888, \"fee_revenue_over_value\": 0.002262722427976059, \"jax_sharpe\": 0.1012069352915478, \"return\": -0.36838151883990056, \"returns_over_hodl\": -0.27347025005438064, \"returns_over_uniform_hodl\": -0.27347025005438064, \"sharpe\": 0.08781198909498628, \"sterling\": -1.5170005151028165, \"ulcer\": -0.19096769359597188}], \"train_return\": -0.36838151883990056, \"train_returns_over_hodl\": -0.27347025005438064, \"train_sharpe\": 0.1012069352915478, \"validation_return\": -0.3391427199517264, \"validation_returns_over_hodl\": -7.8520379087621e-10, \"validation_sharpe\": -2.2438531991978845}, {\"centeredness_margin\": 0.5864167530165778, \"continuous_test_metrics\": [{\"annualised_returns\": -0.4845064586848534, \"annualised_returns_over_hodl\": 8.662626171940246e-12, \"annualised_returns_over_uniform_hodl\": 0.8194637312619826, \"calmar\": -0.8358049769456964, \"daily_log_sharpe\": -0.6924636108680314, \"daily_returns\": 0.031016042487014447, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": 0.005019267298775492, \"return\": -0.23422460262874933, \"returns_over_hodl\": 3.488764832582092e-12, \"returns_over_uniform_hodl\": 0.2725915649456008, \"sharpe\": -0.20210854606515666, \"sterling\": -1.2473843297021074, \"ulcer\": -0.17641031596712556}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -9.049304638963918e-10, \"optuna_trial_number\": 218, \"price_ratio\": 3.253639018757714, \"shift_exponent\": 3.159618207808599e-05, \"step\": 218, \"test_objective\": [{\"annualised_returns\": -0.4845064586848534, \"annualised_returns_over_hodl\": 8.662626171940246e-12, \"annualised_returns_over_uniform_hodl\": 0.8194637312619826, \"calmar\": -0.8358049769456964, \"daily_log_sharpe\": -0.6924636108680314, \"daily_returns\": 0.031016042487014447, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": 0.005019267298775492, \"return\": -0.23422460262874933, \"returns_over_hodl\": 3.488764832582092e-12, \"returns_over_uniform_hodl\": 0.2725915649456008, \"sharpe\": -0.20210854606515666, \"sterling\": -1.2473843297021074, \"ulcer\": -0.17641031596712556}], \"train_objective\": [{\"annualised_returns\": -0.5591665192143901, \"annualised_returns_over_hodl\": -0.4448415688217986, \"annualised_returns_over_uniform_hodl\": -0.4448415688217986, \"calmar\": -0.7308215544687182, \"daily_log_sharpe\": -0.5640994811862097, \"daily_returns\": 0.008287391268343748, \"fee_revenue_over_value\": 0.0016543956006078674, \"jax_sharpe\": 0.145531250410198, \"return\": -0.39182090924064505, \"returns_over_hodl\": -0.30043180193211005, \"returns_over_uniform_hodl\": -0.30043180193211005, \"sharpe\": 0.07785838334896951, \"sterling\": -1.5528183935000617, \"ulcer\": -0.19782447793096727}], \"train_return\": -0.39182090924064505, \"train_returns_over_hodl\": -0.30043180193211005, \"train_sharpe\": 0.145531250410198, \"validation_return\": -0.33914271991921396, \"validation_returns_over_hodl\": -9.049304638963918e-10, \"validation_sharpe\": -2.24385319961011}, {\"centeredness_margin\": 0.5914589789122393, \"continuous_test_metrics\": [{\"annualised_returns\": -0.5533864191476087, \"annualised_returns_over_hodl\": -0.1336194455364098, \"annualised_returns_over_uniform_hodl\": 0.576347998806809, \"calmar\": -0.9561502296098134, \"daily_log_sharpe\": -0.8635249198923356, \"daily_returns\": 0.03101604252280185, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": -0.18233086767705967, \"return\": -0.27720632167579495, \"returns_over_hodl\": -0.05612836303483548, \"returns_over_uniform_hodl\": 0.20116308435729802, \"sharpe\": -0.3900408640459493, \"sterling\": -1.4345409577431334, \"ulcer\": -0.1739159958943923}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -0.06591266001810059, \"optuna_trial_number\": 219, \"price_ratio\": 12.429566322053649, \"shift_exponent\": 0.00022094285796474316, \"step\": 219, \"test_objective\": [{\"annualised_returns\": -0.5533864191476087, \"annualised_returns_over_hodl\": -0.1336194455364098, \"annualised_returns_over_uniform_hodl\": 0.576347998806809, \"calmar\": -0.9561502296098134, \"daily_log_sharpe\": -0.8635249198923356, \"daily_returns\": 0.03101604252280185, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": -0.18233086767705967, \"return\": -0.27720632167579495, \"returns_over_hodl\": -0.05612836303483548, \"returns_over_uniform_hodl\": 0.20116308435729802, \"sharpe\": -0.3900408640459493, \"sterling\": -1.4345409577431334, \"ulcer\": -0.1739159958943923}], \"train_objective\": [{\"annualised_returns\": -0.4123476639412631, \"annualised_returns_over_hodl\": -0.25994697956430035, \"annualised_returns_over_uniform_hodl\": -0.25994697956430046, \"calmar\": -0.5564677874565586, \"daily_log_sharpe\": -0.4117849468006829, \"daily_returns\": 0.008093596725647812, \"fee_revenue_over_value\": 0.009196722088219679, \"jax_sharpe\": 0.11132129456326298, \"return\": -0.27585072854291526, \"returns_over_hodl\": -0.16703515681064907, \"returns_over_uniform_hodl\": -0.16703515681064918, \"sharpe\": 0.13293206716855738, \"sterling\": -1.2779333468819298, \"ulcer\": -0.1741553986831705}], \"train_return\": -0.27585072854291526, \"train_returns_over_hodl\": -0.16703515681064907, \"train_sharpe\": 0.11132129456326298, \"validation_return\": -0.30369096018167263, \"validation_returns_over_hodl\": -0.06591266001810059, \"validation_sharpe\": -2.0472699014690976}, {\"centeredness_margin\": 0.5831014157545296, \"continuous_test_metrics\": [{\"annualised_returns\": -0.5301486386901456, \"annualised_returns_over_hodl\": -0.0885407419563593, \"annualised_returns_over_uniform_hodl\": 0.6583670647091975, \"calmar\": -0.9172415532558108, \"daily_log_sharpe\": -0.8042877488940924, \"daily_returns\": 0.03101604251108656, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": -0.12241017980636726, \"return\": -0.2622893019069351, \"returns_over_hodl\": -0.036648735078637085, \"returns_over_uniform_hodl\": 0.22595269446640254, \"sharpe\": -0.3253886810001932, \"sterling\": -1.3688897612981588, \"ulcer\": -0.17526064020996307}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -0.06523772018191443, \"optuna_trial_number\": 220, \"price_ratio\": 11.446693388353683, \"shift_exponent\": 6.554683093552369e-05, \"step\": 220, \"test_objective\": [{\"annualised_returns\": -0.5301486386901456, \"annualised_returns_over_hodl\": -0.0885407419563593, \"annualised_returns_over_uniform_hodl\": 0.6583670647091975, \"calmar\": -0.9172415532558108, \"daily_log_sharpe\": -0.8042877488940924, \"daily_returns\": 0.03101604251108656, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": -0.12241017980636726, \"return\": -0.2622893019069351, \"returns_over_hodl\": -0.036648735078637085, \"returns_over_uniform_hodl\": 0.22595269446640254, \"sharpe\": -0.3253886810001932, \"sterling\": -1.3688897612981588, \"ulcer\": -0.17526064020996307}], \"train_objective\": [{\"annualised_returns\": -0.42090084462014143, \"annualised_returns_over_hodl\": -0.27071832651101546, \"annualised_returns_over_uniform_hodl\": -0.27071832651101546, \"calmar\": -0.5675364976422438, \"daily_log_sharpe\": -0.41970746102007034, \"daily_returns\": 0.008066749648211174, \"fee_revenue_over_value\": 0.00931810275852594, \"jax_sharpe\": 0.14550559171753738, \"return\": -0.2822681296686884, \"returns_over_hodl\": -0.1744168800728393, \"returns_over_uniform_hodl\": -0.1744168800728393, \"sharpe\": 0.12964631996728793, \"sterling\": -1.297780612593025, \"ulcer\": -0.17515583496579223}], \"train_return\": -0.2822681296686884, \"train_returns_over_hodl\": -0.1744168800728393, \"train_sharpe\": 0.14550559171753738, \"validation_return\": -0.31467829690713545, \"validation_returns_over_hodl\": -0.06523772018191443, \"validation_sharpe\": -2.083874861513186}, {\"centeredness_margin\": 0.5400921034849493, \"continuous_test_metrics\": [{\"annualised_returns\": -0.4783044053531166, \"annualised_returns_over_hodl\": 0.012031290943327555, \"annualised_returns_over_uniform_hodl\": 0.8413542307387714, \"calmar\": -0.8251060156157513, \"daily_log_sharpe\": -0.6823845055192919, \"daily_returns\": 0.03101604251334836, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": 0.0029575165515036065, \"return\": -0.23052731825399486, \"returns_over_hodl\": 0.004828157259579191, \"returns_over_uniform_hodl\": 0.27873583769798915, \"sharpe\": -0.19218986269718102, \"sterling\": -1.2314168282942972, \"ulcer\": -0.1764103160215657}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -6.948717157229112e-10, \"optuna_trial_number\": 221, \"price_ratio\": 5.944958917639811, \"shift_exponent\": 2.0278482877540384e-05, \"step\": 221, \"test_objective\": [{\"annualised_returns\": -0.4783044053531166, \"annualised_returns_over_hodl\": 0.012031290943327555, \"annualised_returns_over_uniform_hodl\": 0.8413542307387714, \"calmar\": -0.8251060156157513, \"daily_log_sharpe\": -0.6823845055192919, \"daily_returns\": 0.03101604251334836, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": 0.0029575165515036065, \"return\": -0.23052731825399486, \"returns_over_hodl\": 0.004828157259579191, \"returns_over_uniform_hodl\": 0.27873583769798915, \"sharpe\": -0.19218986269718102, \"sterling\": -1.2314168282942972, \"ulcer\": -0.1764103160215657}], \"train_objective\": [{\"annualised_returns\": -0.4778471193681869, \"annualised_returns_over_hodl\": -0.3424329442261399, \"annualised_returns_over_uniform_hodl\": -0.3424329442261401, \"calmar\": -0.6390944714290172, \"daily_log_sharpe\": -0.46984430834923196, \"daily_returns\": 0.008171642603854804, \"fee_revenue_over_value\": 0.010103320545721751, \"jax_sharpe\": 0.16140715354268648, \"return\": -0.32598597180520517, \"returns_over_hodl\": -0.22470406112122687, \"returns_over_uniform_hodl\": -0.22470406112122698, \"sharpe\": 0.11682290033867919, \"sterling\": -1.4181962724120385, \"ulcer\": -0.1825781525891573}], \"train_return\": -0.32598597180520517, \"train_returns_over_hodl\": -0.22470406112122687, \"train_sharpe\": 0.16140715354268645, \"validation_return\": -0.33914271999869317, \"validation_returns_over_hodl\": -6.948717157229112e-10, \"validation_sharpe\": -2.243853198577666}, {\"centeredness_margin\": 0.6288003955415052, \"continuous_test_metrics\": [{\"annualised_returns\": -0.48203967153195926, \"annualised_returns_over_hodl\": 0.004785291439497463, \"annualised_returns_over_uniform_hodl\": 0.8281703966180283, \"calmar\": -0.8315496022634042, \"daily_log_sharpe\": -0.6874583872106249, \"daily_returns\": 0.03101604250246919, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": 0.007506457655679906, \"return\": -0.23275089005340077, \"returns_over_hodl\": 0.0019244707886931778, \"returns_over_uniform_hodl\": 0.2750406305580255, \"sharpe\": -0.1968254904517872, \"sterling\": -1.2410334606575568, \"ulcer\": -0.17641031599906565}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -7.211417019092892e-10, \"optuna_trial_number\": 222, \"price_ratio\": 4.62802147636548, \"shift_exponent\": 1.2266751101388446e-05, \"step\": 222, \"test_objective\": [{\"annualised_returns\": -0.48203967153195926, \"annualised_returns_over_hodl\": 0.004785291439497463, \"annualised_returns_over_uniform_hodl\": 0.8281703966180283, \"calmar\": -0.8315496022634042, \"daily_log_sharpe\": -0.6874583872106249, \"daily_returns\": 0.03101604250246919, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": 0.007506457655679906, \"return\": -0.23275089005340077, \"returns_over_hodl\": 0.0019244707886931778, \"returns_over_uniform_hodl\": 0.2750406305580255, \"sharpe\": -0.1968254904517872, \"sterling\": -1.2410334606575568, \"ulcer\": -0.17641031599906565}], \"train_objective\": [{\"annualised_returns\": -0.5177438303386602, \"annualised_returns_over_hodl\": -0.3926763954088095, \"annualised_returns_over_uniform_hodl\": -0.3926763954088097, \"calmar\": -0.6861698425100873, \"daily_log_sharpe\": -0.5175448149436775, \"daily_returns\": 0.00823226358857661, \"fee_revenue_over_value\": 0.0019183498271818516, \"jax_sharpe\": 0.0968898159513512, \"return\": -0.3577395568080286, \"returns_over_hodl\": -0.2612291547657408, \"returns_over_uniform_hodl\": -0.26122915476574093, \"sharpe\": 0.09259689068654488, \"sterling\": -1.4964947450077986, \"ulcer\": -0.18843548003137114}], \"train_return\": -0.3577395568080286, \"train_returns_over_hodl\": -0.2612291547657408, \"train_sharpe\": 0.09688981595135117, \"validation_return\": -0.3391427199682796, \"validation_returns_over_hodl\": -7.211417019092892e-10, \"validation_sharpe\": -2.2438531990272628}, {\"centeredness_margin\": 0.6784271628578113, \"continuous_test_metrics\": [{\"annualised_returns\": -0.5023862543512125, \"annualised_returns_over_hodl\": -0.03468481100589449, \"annualised_returns_over_uniform_hodl\": 0.7563559769837822, \"calmar\": -0.866648837047527, \"daily_log_sharpe\": -0.7387854622043774, \"daily_returns\": 0.031016042488985356, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": -0.05641490086014438, \"return\": -0.24503452388757452, \"returns_over_hodl\": -0.014116308167603453, \"returns_over_uniform_hodl\": 0.2546272706382482, \"sharpe\": -0.2545584882546545, \"sterling\": -1.2934167754494408, \"ulcer\": -0.1764103160306354}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -0.05359103128562337, \"optuna_trial_number\": 223, \"price_ratio\": 8.695806736471882, \"shift_exponent\": 3.756371891614529e-05, \"step\": 223, \"test_objective\": [{\"annualised_returns\": -0.5023862543512125, \"annualised_returns_over_hodl\": -0.03468481100589449, \"annualised_returns_over_uniform_hodl\": 0.7563559769837822, \"calmar\": -0.866648837047527, \"daily_log_sharpe\": -0.7387854622043774, \"daily_returns\": 0.031016042488985356, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": -0.05641490086014438, \"return\": -0.24503452388757452, \"returns_over_hodl\": -0.014116308167603453, \"returns_over_uniform_hodl\": 0.2546272706382482, \"sharpe\": -0.2545584882546545, \"sterling\": -1.2934167754494408, \"ulcer\": -0.1764103160306354}], \"train_objective\": [{\"annualised_returns\": -0.4481231713515317, \"annualised_returns_over_hodl\": -0.30500044177659524, \"annualised_returns_over_uniform_hodl\": -0.30500044177659524, \"calmar\": -0.6009852849776239, \"daily_log_sharpe\": -0.448401926682855, \"daily_returns\": 0.008127034774623688, \"fee_revenue_over_value\": 0.0029705561381639396, \"jax_sharpe\": 0.09685032371239276, \"return\": -0.3029453084368646, \"returns_over_hodl\": -0.19820115169901442, \"returns_over_uniform_hodl\": -0.19820115169901442, \"sharpe\": 0.11435355733204658, \"sterling\": -1.3601204710292762, \"ulcer\": -0.17835195004958357}], \"train_return\": -0.3029453084368646, \"train_returns_over_hodl\": -0.19820115169901442, \"train_sharpe\": 0.09685032371239276, \"validation_return\": -0.33167335968005096, \"validation_returns_over_hodl\": -0.05359103128562337, \"validation_sharpe\": -2.1780066488634042}, {\"centeredness_margin\": 0.04780394482680761, \"continuous_test_metrics\": [{\"annualised_returns\": -0.6190791359033783, \"annualised_returns_over_hodl\": 0.07579478732643774, \"annualised_returns_over_uniform_hodl\": 0.34448182403330785, \"calmar\": -1.0862542540962579, \"daily_log_sharpe\": -1.181736876047044, \"daily_returns\": 0.02265536351192496, \"fee_revenue_over_value\": 0.04609690616606313, \"jax_sharpe\": -0.5926596367548266, \"return\": -0.32206773495629526, \"returns_over_hodl\": 0.029861075315158025, \"returns_over_uniform_hodl\": 0.12661086404794597, \"sharpe\": -0.7721152965637316, \"sterling\": -1.7863004790243566, \"ulcer\": -0.15454361551316478}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -0.03444247785021326, \"optuna_trial_number\": 224, \"price_ratio\": 126.36177174603807, \"shift_exponent\": 72.45973024903616, \"step\": 224, \"test_objective\": [{\"annualised_returns\": -0.6190791359033783, \"annualised_returns_over_hodl\": 0.07579478732643774, \"annualised_returns_over_uniform_hodl\": 0.34448182403330785, \"calmar\": -1.0862542540962579, \"daily_log_sharpe\": -1.181736876047044, \"daily_returns\": 0.02265536351192496, \"fee_revenue_over_value\": 0.04609690616606313, \"jax_sharpe\": -0.5926596367548266, \"return\": -0.32206773495629526, \"returns_over_hodl\": 0.029861075315158025, \"returns_over_uniform_hodl\": 0.12661086404794597, \"sharpe\": -0.7721152965637316, \"sterling\": -1.7863004790243566, \"ulcer\": -0.15454361551316478}], \"train_objective\": [{\"annualised_returns\": -0.3233835051022478, \"annualised_returns_over_hodl\": -0.1479110181302017, \"annualised_returns_over_uniform_hodl\": -0.1479110181302019, \"calmar\": -0.4428517237242792, \"daily_log_sharpe\": -0.3161109360083249, \"daily_returns\": 0.008010939163826222, \"fee_revenue_over_value\": 0.020961643900464032, \"jax_sharpe\": 0.16030863834697584, \"return\": -0.21114469077712905, \"returns_over_hodl\": -0.09260595177598452, \"returns_over_uniform_hodl\": -0.09260595177598463, \"sharpe\": 0.1916239511278529, \"sterling\": -1.0495769121498963, \"ulcer\": -0.1659967655827674}], \"train_return\": -0.21114469077712905, \"train_returns_over_hodl\": -0.09260595177598452, \"train_sharpe\": 0.16030863834697584, \"validation_return\": -0.2068004622232117, \"validation_returns_over_hodl\": -0.03444247785021326, \"validation_sharpe\": -1.5726065689893793}, {\"centeredness_margin\": 0.7410451135811986, \"continuous_test_metrics\": [{\"annualised_returns\": -0.4845064586220297, \"annualised_returns_over_hodl\": 8.629541525806417e-12, \"annualised_returns_over_uniform_hodl\": 0.8194637314837223, \"calmar\": -0.8358049768450517, \"daily_log_sharpe\": -0.6924636107102967, \"daily_returns\": 0.031016042489996558, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": 0.005019267487593824, \"return\": -0.2342246025911635, \"returns_over_hodl\": 3.47544215628659e-12, \"returns_over_uniform_hodl\": 0.27259156500806214, \"sharpe\": -0.20210854588015684, \"sterling\": -1.247384329491607, \"ulcer\": -0.17641031597329193}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -8.71001826219242e-10, \"optuna_trial_number\": 225, \"price_ratio\": 3.4946451747708167, \"shift_exponent\": 1.1253461785937945e-05, \"step\": 225, \"test_objective\": [{\"annualised_returns\": -0.4845064586220297, \"annualised_returns_over_hodl\": 8.629541525806417e-12, \"annualised_returns_over_uniform_hodl\": 0.8194637314837223, \"calmar\": -0.8358049768450517, \"daily_log_sharpe\": -0.6924636107102967, \"daily_returns\": 0.031016042489996558, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": 0.005019267487593824, \"return\": -0.2342246025911635, \"returns_over_hodl\": 3.47544215628659e-12, \"returns_over_uniform_hodl\": 0.27259156500806214, \"sharpe\": -0.20210854588015684, \"sterling\": -1.247384329491607, \"ulcer\": -0.17641031597329193}], \"train_objective\": [{\"annualised_returns\": -0.5530402991784718, \"annualised_returns_over_hodl\": -0.4371265860620228, \"annualised_returns_over_uniform_hodl\": -0.4371265860620229, \"calmar\": -0.7247622246028825, \"daily_log_sharpe\": -0.5583320613245443, \"daily_returns\": 0.008289801900431295, \"fee_revenue_over_value\": 0.0004306387446128717, \"jax_sharpe\": 0.10570063870479177, \"return\": -0.3867035554613326, \"returns_over_hodl\": -0.2945454799314642, \"returns_over_uniform_hodl\": -0.2945454799314643, \"sharpe\": 0.07746986632267393, \"sterling\": -1.5462639704547563, \"ulcer\": -0.1961321997757623}], \"train_return\": -0.3867035554613326, \"train_returns_over_hodl\": -0.2945454799314642, \"train_sharpe\": 0.10570063870479177, \"validation_return\": -0.339142719929718, \"validation_returns_over_hodl\": -8.71001826219242e-10, \"validation_sharpe\": -2.243853199509357}, {\"centeredness_margin\": 0.7268760196594849, \"continuous_test_metrics\": [{\"annualised_returns\": -0.4773626718177373, \"annualised_returns_over_hodl\": 0.013858149136522835, \"annualised_returns_over_uniform_hodl\": 0.8446781327371637, \"calmar\": -0.8234814616292959, \"daily_log_sharpe\": -0.6797111270957915, \"daily_returns\": 0.031016042510296674, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": 0.0070537332564266685, \"return\": -0.2299682143338192, \"returns_over_hodl\": 0.005558272069343673, \"returns_over_uniform_hodl\": 0.27966497558252557, \"sharpe\": -0.18907184043426778, \"sterling\": -1.2289922876542891, \"ulcer\": -0.17641031601527446}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -6.174710742712364e-10, \"optuna_trial_number\": 226, \"price_ratio\": 5.474894262952156, \"shift_exponent\": 7.573792442880977e-05, \"step\": 226, \"test_objective\": [{\"annualised_returns\": -0.4773626718177373, \"annualised_returns_over_hodl\": 0.013858149136522835, \"annualised_returns_over_uniform_hodl\": 0.8446781327371637, \"calmar\": -0.8234814616292959, \"daily_log_sharpe\": -0.6797111270957915, \"daily_returns\": 0.031016042510296674, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": 0.0070537332564266685, \"return\": -0.2299682143338192, \"returns_over_hodl\": 0.005558272069343673, \"returns_over_uniform_hodl\": 0.27966497558252557, \"sharpe\": -0.18907184043426778, \"sterling\": -1.2289922876542891, \"ulcer\": -0.17641031601527446}], \"train_objective\": [{\"annualised_returns\": -0.4955926347252725, \"annualised_returns_over_hodl\": -0.3647805491506386, \"annualised_returns_over_uniform_hodl\": -0.3647805491506386, \"calmar\": -0.6594067612560699, \"daily_log_sharpe\": -0.4932592547659276, \"daily_returns\": 0.008215210509709771, \"fee_revenue_over_value\": 0.0009183833297837273, \"jax_sharpe\": 0.09333274872837516, \"return\": -0.33998737391877676, \"returns_over_hodl\": -0.24080940870031853, \"returns_over_uniform_hodl\": -0.24080940870031853, \"sharpe\": 0.10047357111317813, \"sterling\": -1.4567957518151589, \"ulcer\": -0.18476356993097212}], \"train_return\": -0.33998737391877676, \"train_returns_over_hodl\": -0.24080940870031853, \"train_sharpe\": 0.09333274872837517, \"validation_return\": -0.33914271998618783, \"validation_returns_over_hodl\": -6.174710742712364e-10, \"validation_sharpe\": -2.243853198663474}, {\"centeredness_margin\": 0.8554188834701749, \"continuous_test_metrics\": [{\"annualised_returns\": -0.48417750583943486, \"annualised_returns_over_hodl\": 0.0006381305999441089, \"annualised_returns_over_uniform_hodl\": 0.8206247890125942, \"calmar\": -0.8352375111067691, \"daily_log_sharpe\": -0.6971248938466613, \"daily_returns\": 0.031016042517719982, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": -0.014500097324460889, \"return\": -0.23402783597251653, \"returns_over_hodl\": 0.0002569503702840592, \"returns_over_uniform_hodl\": 0.2729185584581162, \"sharpe\": -0.2090674347521691, \"sterling\": -1.2465374233869428, \"ulcer\": -0.17641031603060842}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -0.021258649666217266, \"optuna_trial_number\": 227, \"price_ratio\": 6.765586536624855, \"shift_exponent\": 5.628664871305023e-05, \"step\": 227, \"test_objective\": [{\"annualised_returns\": -0.48417750583943486, \"annualised_returns_over_hodl\": 0.0006381305999441089, \"annualised_returns_over_uniform_hodl\": 0.8206247890125942, \"calmar\": -0.8352375111067691, \"daily_log_sharpe\": -0.6971248938466613, \"daily_returns\": 0.031016042517719982, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": -0.014500097324460889, \"return\": -0.23402783597251653, \"returns_over_hodl\": 0.0002569503702840592, \"returns_over_uniform_hodl\": 0.2729185584581162, \"sharpe\": -0.2090674347521691, \"sterling\": -1.2465374233869428, \"ulcer\": -0.17641031603060842}], \"train_objective\": [{\"annualised_returns\": -0.4731859088055794, \"annualised_returns_over_hodl\": -0.3365629038228639, \"annualised_returns_over_uniform_hodl\": -0.3365629038228638, \"calmar\": -0.6317846958068715, \"daily_log_sharpe\": -0.4722518093177954, \"daily_returns\": 0.00816534417162387, \"fee_revenue_over_value\": 0.0002795002598106211, \"jax_sharpe\": 0.09200866132671905, \"return\": -0.32233938623022895, \"returns_over_hodl\": -0.22050951491181814, \"returns_over_uniform_hodl\": -0.22050951491181803, \"sharpe\": 0.1058271779319791, \"sterling\": -1.4133830537218195, \"ulcer\": -0.18154995375859342}], \"train_return\": -0.32233938623022895, \"train_returns_over_hodl\": -0.22050951491181814, \"train_sharpe\": 0.09200866132671905, \"validation_return\": -0.338434929458184, \"validation_returns_over_hodl\": -0.021258649666217266, \"validation_sharpe\": -2.237443905334695}, {\"centeredness_margin\": 0.6792377411572134, \"continuous_test_metrics\": [{\"annualised_returns\": -0.4816968017508315, \"annualised_returns_over_hodl\": 0.005450420096484887, \"annualised_returns_over_uniform_hodl\": 0.8293805749836305, \"calmar\": -0.8309581287334231, \"daily_log_sharpe\": -0.6914048088757466, \"daily_returns\": 0.031016042514467292, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": -0.008945271711294764, \"return\": -0.23254638422307616, \"returns_over_hodl\": 0.0021915277866724203, \"returns_over_uniform_hodl\": 0.27538048529290804, \"sharpe\": -0.20269069656356123, \"sterling\": -1.2401507248109689, \"ulcer\": -0.17641031602388083}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -0.002739919446937633, \"optuna_trial_number\": 228, \"price_ratio\": 5.800785399535986, \"shift_exponent\": 0.00023214784456696214, \"step\": 228, \"test_objective\": [{\"annualised_returns\": -0.4816968017508315, \"annualised_returns_over_hodl\": 0.005450420096484887, \"annualised_returns_over_uniform_hodl\": 0.8293805749836305, \"calmar\": -0.8309581287334231, \"daily_log_sharpe\": -0.6914048088757466, \"daily_returns\": 0.031016042514467292, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": -0.008945271711294764, \"return\": -0.23254638422307616, \"returns_over_hodl\": 0.0021915277866724203, \"returns_over_uniform_hodl\": 0.27538048529290804, \"sharpe\": -0.20269069656356123, \"sterling\": -1.2401507248109689, \"ulcer\": -0.17641031602388083}], \"train_objective\": [{\"annualised_returns\": -0.48177467122363127, \"annualised_returns_over_hodl\": -0.34737906021205334, \"annualised_returns_over_uniform_hodl\": -0.34737906021205334, \"calmar\": -0.642175865310124, \"daily_log_sharpe\": -0.4779246749938891, \"daily_returns\": 0.008183673730125718, \"fee_revenue_over_value\": 0.0015366690073627452, \"jax_sharpe\": 0.09733940720481671, \"return\": -0.32906853647943823, \"returns_over_hodl\": -0.22824983283114952, \"returns_over_uniform_hodl\": -0.22824983283114952, \"sharpe\": 0.10837325622170808, \"sterling\": -1.4283251604372698, \"ulcer\": -0.18295185671356243}], \"train_return\": -0.32906853647943823, \"train_returns_over_hodl\": -0.22824983283114952, \"train_sharpe\": 0.0973394072048167, \"validation_return\": -0.3390816049260005, \"validation_returns_over_hodl\": -0.002739919446937633, \"validation_sharpe\": -2.243318988982911}, {\"centeredness_margin\": 0.6183598866481758, \"continuous_test_metrics\": [{\"annualised_returns\": -0.4822630927928242, \"annualised_returns_over_hodl\": 0.004351878642654627, \"annualised_returns_over_uniform_hodl\": 0.8273818185887087, \"calmar\": -0.831935019078034, \"daily_log_sharpe\": -0.6929498517242009, \"daily_returns\": 0.031016042513659213, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": -0.010271831785282935, \"return\": -0.23288419392566895, \"returns_over_hodl\": 0.001750393619357471, \"returns_over_uniform_hodl\": 0.27481910165541823, \"sharpe\": -0.2044526455084633, \"sterling\": -1.2416086698512632, \"ulcer\": -0.17641031602222262}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": 3.205844177389672e-05, \"optuna_trial_number\": 229, \"price_ratio\": 5.486279745168204, \"shift_exponent\": 0.00033826317189649573, \"step\": 229, \"test_objective\": [{\"annualised_returns\": -0.4822630927928242, \"annualised_returns_over_hodl\": 0.004351878642654627, \"annualised_returns_over_uniform_hodl\": 0.8273818185887087, \"calmar\": -0.831935019078034, \"daily_log_sharpe\": -0.6929498517242009, \"daily_returns\": 0.031016042513659213, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": -0.010271831785282935, \"return\": -0.23288419392566895, \"returns_over_hodl\": 0.001750393619357471, \"returns_over_uniform_hodl\": 0.27481910165541823, \"sharpe\": -0.2044526455084633, \"sterling\": -1.2416086698512632, \"ulcer\": -0.17641031602222262}], \"train_objective\": [{\"annualised_returns\": -0.48214441756566817, \"annualised_returns_over_hodl\": -0.3478446958956527, \"annualised_returns_over_uniform_hodl\": -0.3478446958956529, \"calmar\": -0.6425753521639027, \"daily_log_sharpe\": -0.4760509769884628, \"daily_returns\": 0.008217218232380382, \"fee_revenue_over_value\": 0.0037806721710416783, \"jax_sharpe\": 0.10279400401501318, \"return\": -0.3293592062212669, \"returns_over_hodl\": -0.2285841805761042, \"returns_over_uniform_hodl\": -0.2285841805761043, \"sharpe\": 0.11283685850142751, \"sterling\": -1.4266610158337205, \"ulcer\": -0.18326210466807727}], \"train_return\": -0.3293592062212669, \"train_returns_over_hodl\": -0.2285841805761042, \"train_sharpe\": 0.10279400401501317, \"validation_return\": -0.33912153347148655, \"validation_returns_over_hodl\": 3.205844177389672e-05, \"validation_sharpe\": -2.243657174878682}, {\"centeredness_margin\": 0.6260733168344809, \"continuous_test_metrics\": [{\"annualised_returns\": -0.5488079558774734, \"annualised_returns_over_hodl\": -0.12473773680703992, \"annualised_returns_over_uniform_hodl\": 0.5925079449502153, \"calmar\": -0.950308100867008, \"daily_log_sharpe\": -0.8518500602740295, \"daily_returns\": 0.031016042502870732, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": -0.17273069766442706, \"return\": -0.27423123099501046, \"returns_over_hodl\": -0.05224329329568722, \"returns_over_uniform_hodl\": 0.20610719109970788, \"sharpe\": -0.37692090750225204, \"sterling\": -1.4196376151106909, \"ulcer\": -0.17460646686350706}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -0.0654352816653132, \"optuna_trial_number\": 230, \"price_ratio\": 6.6435373380548395, \"shift_exponent\": 0.001039534339244824, \"step\": 230, \"test_objective\": [{\"annualised_returns\": -0.5488079558774734, \"annualised_returns_over_hodl\": -0.12473773680703992, \"annualised_returns_over_uniform_hodl\": 0.5925079449502153, \"calmar\": -0.950308100867008, \"daily_log_sharpe\": -0.8518500602740295, \"daily_returns\": 0.031016042502870732, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": -0.17273069766442706, \"return\": -0.27423123099501046, \"returns_over_hodl\": -0.05224329329568722, \"returns_over_uniform_hodl\": 0.20610719109970788, \"sharpe\": -0.37692090750225204, \"sterling\": -1.4196376151106909, \"ulcer\": -0.17460646686350706}], \"train_objective\": [{\"annualised_returns\": -0.4353585358647386, \"annualised_returns_over_hodl\": -0.2889254490179217, \"annualised_returns_over_uniform_hodl\": -0.2889254490179215, \"calmar\": -0.5829950931018173, \"daily_log_sharpe\": -0.4313517102015571, \"daily_returns\": 0.008148775390815566, \"fee_revenue_over_value\": 0.004978426357641314, \"jax_sharpe\": 0.11744484603304085, \"return\": -0.2932009801464289, \"returns_over_hodl\": -0.18699257467442332, \"returns_over_uniform_hodl\": -0.1869925746744232, \"sharpe\": 0.13170402840510215, \"sterling\": -1.3240539358488603, \"ulcer\": -0.1775692346134915}], \"train_return\": -0.2932009801464289, \"train_returns_over_hodl\": -0.18699257467442332, \"train_sharpe\": 0.11744484603304085, \"validation_return\": -0.32136953299920323, \"validation_returns_over_hodl\": -0.0654352816653132, \"validation_sharpe\": -2.1156679281811437}, {\"centeredness_margin\": 0.5968188924813708, \"continuous_test_metrics\": [{\"annualised_returns\": -0.4814572229313765, \"annualised_returns_over_hodl\": 0.005915176799357047, \"annualised_returns_over_uniform_hodl\": 0.830226182033653, \"calmar\": -0.8305448390367772, \"daily_log_sharpe\": -0.6863123714974899, \"daily_returns\": 0.031016042502221487, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": 0.007572275240306895, \"return\": -0.23240353458368368, \"returns_over_hodl\": 0.0023780704654794427, \"returns_over_uniform_hodl\": 0.2756178776755447, \"sharpe\": -0.19563969457651076, \"sterling\": -1.2395339171826674, \"ulcer\": -0.1764103159985615}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -7.191013340346331e-10, \"optuna_trial_number\": 231, \"price_ratio\": 4.492343517540381, \"shift_exponent\": 0.00011652269207156692, \"step\": 231, \"test_objective\": [{\"annualised_returns\": -0.4814572229313765, \"annualised_returns_over_hodl\": 0.005915176799357047, \"annualised_returns_over_uniform_hodl\": 0.830226182033653, \"calmar\": -0.8305448390367772, \"daily_log_sharpe\": -0.6863123714974899, \"daily_returns\": 0.031016042502221487, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": 0.007572275240306895, \"return\": -0.23240353458368368, \"returns_over_hodl\": 0.0023780704654794427, \"returns_over_uniform_hodl\": 0.2756178776755447, \"sharpe\": -0.19563969457651076, \"sterling\": -1.2395339171826674, \"ulcer\": -0.1764103159985615}], \"train_objective\": [{\"annualised_returns\": -0.5165851272026283, \"annualised_returns_over_hodl\": -0.39121719631609453, \"annualised_returns_over_uniform_hodl\": -0.39121719631609475, \"calmar\": -0.6846872425458396, \"daily_log_sharpe\": -0.5148312764387527, \"daily_returns\": 0.008231211418985265, \"fee_revenue_over_value\": 0.0032773145545472475, \"jax_sharpe\": 0.1641518024552651, \"return\": -0.35680312469751896, \"returns_over_hodl\": -0.2601520080270323, \"returns_over_uniform_hodl\": -0.2601520080270324, \"sharpe\": 0.09619394019462937, \"sterling\": -1.4925814984014956, \"ulcer\": -0.18841820448913}], \"train_return\": -0.35680312469751896, \"train_returns_over_hodl\": -0.2601520080270323, \"train_sharpe\": 0.16415180245526506, \"validation_return\": -0.3391427199642152, \"validation_returns_over_hodl\": -7.191013340346331e-10, \"validation_sharpe\": -2.243853199001204}, {\"centeredness_margin\": 0.6508692673053688, \"continuous_test_metrics\": [{\"annualised_returns\": -0.4963065023904969, \"annualised_returns_over_hodl\": -0.022890770692204354, \"annualised_returns_over_uniform_hodl\": 0.7778148068255082, \"calmar\": -0.8561608520707528, \"daily_log_sharpe\": -0.7251150147810956, \"daily_returns\": 0.031016042523279427, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": -0.043177051295405326, \"return\": -0.24133312581112476, \"returns_over_hodl\": -0.009282778810637438, \"returns_over_uniform_hodl\": 0.26077837967983575, \"sharpe\": -0.2398174975440673, \"sterling\": -1.277764142842136, \"ulcer\": -0.1764103160420974}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -0.043834368527048695, \"optuna_trial_number\": 232, \"price_ratio\": 7.588195942421599, \"shift_exponent\": 0.00013550220823648339, \"step\": 232, \"test_objective\": [{\"annualised_returns\": -0.4963065023904969, \"annualised_returns_over_hodl\": -0.022890770692204354, \"annualised_returns_over_uniform_hodl\": 0.7778148068255082, \"calmar\": -0.8561608520707528, \"daily_log_sharpe\": -0.7251150147810956, \"daily_returns\": 0.031016042523279427, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": -0.043177051295405326, \"return\": -0.24133312581112476, \"returns_over_hodl\": -0.009282778810637438, \"returns_over_uniform_hodl\": 0.26077837967983575, \"sharpe\": -0.2398174975440673, \"sterling\": -1.277764142842136, \"ulcer\": -0.1764103160420974}], \"train_objective\": [{\"annualised_returns\": -0.4554750350898181, \"annualised_returns_over_hodl\": -0.31425892443973025, \"annualised_returns_over_uniform_hodl\": -0.31425892443973014, \"calmar\": -0.6101071890546297, \"daily_log_sharpe\": -0.45379914121511, \"daily_returns\": 0.008142006136168833, \"fee_revenue_over_value\": 0.003947799651162878, \"jax_sharpe\": 0.14969146990086046, \"return\": -0.3085977977958806, \"returns_over_hodl\": -0.20470302237422544, \"returns_over_uniform_hodl\": -0.20470302237422533, \"sharpe\": 0.11514902021504986, \"sterling\": -1.3745043276442717, \"ulcer\": -0.17941968349057247}], \"train_return\": -0.3085977977958806, \"train_returns_over_hodl\": -0.20470302237422544, \"train_sharpe\": 0.14969146990086046, \"validation_return\": -0.33512012818350434, \"validation_returns_over_hodl\": -0.043834368527048695, \"validation_sharpe\": -2.2082806602923046}, {\"centeredness_margin\": 0.5945492958427877, \"continuous_test_metrics\": [{\"annualised_returns\": -0.495906502405528, \"annualised_returns_over_hodl\": -0.02211481528834014, \"annualised_returns_over_uniform_hodl\": 0.7792266294902501, \"calmar\": -0.8554708249518908, \"daily_log_sharpe\": -0.724269636832202, \"daily_returns\": 0.031016042522568534, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": -0.04248441968688004, \"return\": -0.2410905406631525, \"returns_over_hodl\": -0.00896599511981866, \"returns_over_uniform_hodl\": 0.2611815159181512, \"sharpe\": -0.23893383895788575, \"sterling\": -1.2767343224581127, \"ulcer\": -0.17641031604062524}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -0.04008302679856002, \"optuna_trial_number\": 233, \"price_ratio\": 7.100226291009001, \"shift_exponent\": 0.0002263581241860364, \"step\": 233, \"test_objective\": [{\"annualised_returns\": -0.495906502405528, \"annualised_returns_over_hodl\": -0.02211481528834014, \"annualised_returns_over_uniform_hodl\": 0.7792266294902501, \"calmar\": -0.8554708249518908, \"daily_log_sharpe\": -0.724269636832202, \"daily_returns\": 0.031016042522568534, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": -0.04248441968688004, \"return\": -0.2410905406631525, \"returns_over_hodl\": -0.00896599511981866, \"returns_over_uniform_hodl\": 0.2611815159181512, \"sharpe\": -0.23893383895788575, \"sterling\": -1.2767343224581127, \"ulcer\": -0.17641031604062524}], \"train_objective\": [{\"annualised_returns\": -0.45303515006335016, \"annualised_returns_over_hodl\": -0.3111862840832508, \"annualised_returns_over_uniform_hodl\": -0.3111862840832508, \"calmar\": -0.6074329569575732, \"daily_log_sharpe\": -0.447620392169805, \"daily_returns\": 0.008143555429385574, \"fee_revenue_over_value\": 0.009156309789819173, \"jax_sharpe\": 0.15542321181082952, \"return\": -0.306718583896872, \"returns_over_hodl\": -0.2025414250732196, \"returns_over_uniform_hodl\": -0.2025414250732196, \"sharpe\": 0.12336150258247902, \"sterling\": -1.366179138055635, \"ulcer\": -0.17938037462638876}], \"train_return\": -0.306718583896872, \"train_returns_over_hodl\": -0.2025414250732196, \"train_sharpe\": 0.1554232118108295, \"validation_return\": -0.33589916898696026, \"validation_returns_over_hodl\": -0.04008302679856002, \"validation_sharpe\": -2.2151503498280443}, {\"centeredness_margin\": 0.6290814353010518, \"continuous_test_metrics\": [{\"annualised_returns\": -0.47774863790393896, \"annualised_returns_over_hodl\": 0.013109417962063663, \"annualised_returns_over_uniform_hodl\": 0.84331584351517, \"calmar\": -0.8241472792046931, \"daily_log_sharpe\": -0.6815983135061001, \"daily_returns\": 0.031016042510239136, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": 0.003194265846636713, \"return\": -0.23019728822751373, \"returns_over_hodl\": 0.005259132277613654, \"returns_over_uniform_hodl\": 0.2792842928054795, \"sharpe\": -0.19147866738779784, \"sterling\": -1.2299859768922663, \"ulcer\": -0.17641031601515814}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -6.13516570879824e-10, \"optuna_trial_number\": 234, \"price_ratio\": 5.156794625439818, \"shift_exponent\": 0.0002623630404861513, \"step\": 234, \"test_objective\": [{\"annualised_returns\": -0.47774863790393896, \"annualised_returns_over_hodl\": 0.013109417962063663, \"annualised_returns_over_uniform_hodl\": 0.84331584351517, \"calmar\": -0.8241472792046931, \"daily_log_sharpe\": -0.6815983135061001, \"daily_returns\": 0.031016042510239136, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": 0.003194265846636713, \"return\": -0.23019728822751373, \"returns_over_hodl\": 0.005259132277613654, \"returns_over_uniform_hodl\": 0.2792842928054795, \"sharpe\": -0.19147866738779784, \"sterling\": -1.2299859768922663, \"ulcer\": -0.17641031601515814}], \"train_objective\": [{\"annualised_returns\": -0.49459697684663395, \"annualised_returns_over_hodl\": -0.3635266791748142, \"annualised_returns_over_uniform_hodl\": -0.3635266791748142, \"calmar\": -0.6578979320756421, \"daily_log_sharpe\": -0.4901847746991442, \"daily_returns\": 0.008156136887930735, \"fee_revenue_over_value\": 0.002576767315135773, \"jax_sharpe\": 0.09907015798785748, \"return\": -0.3391967167529579, \"returns_over_hodl\": -0.23989994203632847, \"returns_over_uniform_hodl\": -0.23989994203632847, \"sharpe\": 0.1054415096904601, \"sterling\": -1.4525534923418786, \"ulcer\": -0.18483355199720564}], \"train_return\": -0.3391967167529579, \"train_returns_over_hodl\": -0.23989994203632847, \"train_sharpe\": 0.09907015798785748, \"validation_return\": -0.339142719982942, \"validation_returns_over_hodl\": -6.13516570879824e-10, \"validation_sharpe\": -2.243853198633748}, {\"centeredness_margin\": 0.5804778639115357, \"continuous_test_metrics\": [{\"annualised_returns\": -0.48016569569375045, \"annualised_returns_over_hodl\": 0.008420595590070912, \"annualised_returns_over_uniform_hodl\": 0.834784700770479, \"calmar\": -0.8283168672310942, \"daily_log_sharpe\": -0.6838032926798384, \"daily_returns\": 0.031016042503724247, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": 0.007957479177932042, \"return\": -0.23163413553235457, \"returns_over_hodl\": 0.0033828024466473483, \"returns_over_uniform_hodl\": 0.2768964911517151, \"sharpe\": -0.19307246023902264, \"sterling\": -1.2362088147741364, \"ulcer\": -0.17641031600167162}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -7.000457991068743e-10, \"optuna_trial_number\": 235, \"price_ratio\": 4.500054850398453, \"shift_exponent\": 0.00020812711863362008, \"step\": 235, \"test_objective\": [{\"annualised_returns\": -0.48016569569375045, \"annualised_returns_over_hodl\": 0.008420595590070912, \"annualised_returns_over_uniform_hodl\": 0.834784700770479, \"calmar\": -0.8283168672310942, \"daily_log_sharpe\": -0.6838032926798384, \"daily_returns\": 0.031016042503724247, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": 0.007957479177932042, \"return\": -0.23163413553235457, \"returns_over_hodl\": 0.0033828024466473483, \"returns_over_uniform_hodl\": 0.2768964911517151, \"sharpe\": -0.19307246023902264, \"sterling\": -1.2362088147741364, \"ulcer\": -0.17641031600167162}], \"train_objective\": [{\"annualised_returns\": -0.5128638182187102, \"annualised_returns_over_hodl\": -0.38653080985161903, \"annualised_returns_over_uniform_hodl\": -0.38653080985161903, \"calmar\": -0.6800255035112946, \"daily_log_sharpe\": -0.509807272412575, \"daily_returns\": 0.00822786035705872, \"fee_revenue_over_value\": 0.004228987285081424, \"jax_sharpe\": 0.16272160389165397, \"return\": -0.35380161126900667, \"returns_over_hodl\": -0.2566994668716973, \"returns_over_uniform_hodl\": -0.2566994668716973, \"sharpe\": 0.09953922832639013, \"sterling\": -1.485075254442577, \"ulcer\": -0.1878728610621944}], \"train_return\": -0.35380161126900667, \"train_returns_over_hodl\": -0.2566994668716973, \"train_sharpe\": 0.16272160389165397, \"validation_return\": -0.33914271996821377, \"validation_returns_over_hodl\": -7.000457991068743e-10, \"validation_sharpe\": -2.243853198938411}, {\"centeredness_margin\": 0.041043318621631186, \"continuous_test_metrics\": [{\"annualised_returns\": -0.7174781080864491, \"annualised_returns_over_hodl\": -0.37586655725312745, \"annualised_returns_over_uniform_hodl\": -0.002822936805784715, \"calmar\": -1.1806618321259885, \"daily_log_sharpe\": -1.4151605968375915, \"daily_returns\": 0.028192308058865356, \"fee_revenue_over_value\": 0.06580021004806122, \"jax_sharpe\": -0.9374324171472223, \"return\": -0.39893987142225806, \"returns_over_hodl\": -0.1729143471158633, \"returns_over_uniform_hodl\": -0.0011378632965870494, \"sharpe\": -1.0141443432785695, \"sterling\": -2.0041283087299475, \"ulcer\": -0.16995412121290573}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -0.04386337929389361, \"optuna_trial_number\": 236, \"price_ratio\": 5.3021881080306175, \"shift_exponent\": 105.8027316163273, \"step\": 236, \"test_objective\": [{\"annualised_returns\": -0.7174781080864491, \"annualised_returns_over_hodl\": -0.37586655725312745, \"annualised_returns_over_uniform_hodl\": -0.002822936805784715, \"calmar\": -1.1806618321259885, \"daily_log_sharpe\": -1.4151605968375915, \"daily_returns\": 0.028192308058865356, \"fee_revenue_over_value\": 0.06580021004806122, \"jax_sharpe\": -0.9374324171472223, \"return\": -0.39893987142225806, \"returns_over_hodl\": -0.1729143471158633, \"returns_over_uniform_hodl\": -0.0011378632965870494, \"sharpe\": -1.0141443432785695, \"sterling\": -2.0041283087299475, \"ulcer\": -0.16995412121290573}], \"train_objective\": [{\"annualised_returns\": -0.44821539876047933, \"annualised_returns_over_hodl\": -0.3051165872734658, \"annualised_returns_over_uniform_hodl\": -0.3051165872734658, \"calmar\": -0.5990547455485098, \"daily_log_sharpe\": -0.4541313094756832, \"daily_returns\": 0.008144070611036268, \"fee_revenue_over_value\": 0.03249080682214888, \"jax_sharpe\": 0.14207680345334556, \"return\": -0.3030160336867963, \"returns_over_hodl\": -0.19828250460378272, \"returns_over_uniform_hodl\": -0.19828250460378272, \"sharpe\": 0.10934937077062848, \"sterling\": -1.3557195156937432, \"ulcer\": -0.17850318524727365}], \"train_return\": -0.3030160336867963, \"train_returns_over_hodl\": -0.19828250460378272, \"train_sharpe\": 0.14207680345334553, \"validation_return\": -0.29056210269855853, \"validation_returns_over_hodl\": -0.04386337929389361, \"validation_sharpe\": -2.15463587356322}, {\"centeredness_margin\": 0.7102396742095805, \"continuous_test_metrics\": [{\"annualised_returns\": -0.5144677354194847, \"annualised_returns_over_hodl\": -0.058121537512859645, \"annualised_returns_over_uniform_hodl\": 0.7137137033918208, \"calmar\": -0.8877817019609117, \"daily_log_sharpe\": -0.7665692485446393, \"daily_returns\": 0.031016042496545732, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": -0.08411519641936067, \"return\": -0.2524708055585836, \"returns_over_hodl\": -0.02382709509932046, \"returns_over_uniform_hodl\": 0.24226940518374085, \"sharpe\": -0.28444710900095455, \"sterling\": -1.325184644848984, \"ulcer\": -0.1762188747458014}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -0.06110458970995214, \"optuna_trial_number\": 237, \"price_ratio\": 9.23508419103416, \"shift_exponent\": 0.00013337011142073557, \"step\": 237, \"test_objective\": [{\"annualised_returns\": -0.5144677354194847, \"annualised_returns_over_hodl\": -0.058121537512859645, \"annualised_returns_over_uniform_hodl\": 0.7137137033918208, \"calmar\": -0.8877817019609117, \"daily_log_sharpe\": -0.7665692485446393, \"daily_returns\": 0.031016042496545732, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": -0.08411519641936067, \"return\": -0.2524708055585836, \"returns_over_hodl\": -0.02382709509932046, \"returns_over_uniform_hodl\": 0.24226940518374085, \"sharpe\": -0.28444710900095455, \"sterling\": -1.325184644848984, \"ulcer\": -0.1762188747458014}], \"train_objective\": [{\"annualised_returns\": -0.4420263565102922, \"annualised_returns_over_hodl\": -0.2973224901010224, \"annualised_returns_over_uniform_hodl\": -0.2973224901010224, \"calmar\": -0.5930236203931508, \"daily_log_sharpe\": -0.4435880336413037, \"daily_returns\": 0.008115200117262023, \"fee_revenue_over_value\": 0.001682467085268841, \"jax_sharpe\": 0.09659777261230747, \"return\": -0.29828016999018836, \"returns_over_hodl\": -0.19283499796820502, \"returns_over_uniform_hodl\": -0.19283499796820502, \"sharpe\": 0.11495572622914697, \"sterling\": -1.347205270718289, \"ulcer\": -0.17754889018995237}], \"train_return\": -0.29828016999018836, \"train_returns_over_hodl\": -0.19283499796820502, \"train_sharpe\": 0.09659777261230747, \"validation_return\": -0.32647981539349025, \"validation_returns_over_hodl\": -0.06110458970995214, \"validation_sharpe\": -2.139951880583053}, {\"centeredness_margin\": 0.6751801524094572, \"continuous_test_metrics\": [{\"annualised_returns\": -0.4800132419932003, \"annualised_returns_over_hodl\": 0.008716338697333903, \"annualised_returns_over_uniform_hodl\": 0.8353227947650992, \"calmar\": -0.8280538742961312, \"daily_log_sharpe\": -0.6835755109470609, \"daily_returns\": 0.031016042505550373, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": 0.00748573084573575, \"return\": -0.2315433899515319, \"returns_over_hodl\": 0.003501303978607595, \"returns_over_uniform_hodl\": 0.2770472952401035, \"sharpe\": -0.19285954068246594, \"sterling\": -1.2358163149621166, \"ulcer\": -0.17641031600544232}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -6.775119354429648e-10, \"optuna_trial_number\": 238, \"price_ratio\": 4.8226958216139, \"shift_exponent\": 0.00012384464267591605, \"step\": 238, \"test_objective\": [{\"annualised_returns\": -0.4800132419932003, \"annualised_returns_over_hodl\": 0.008716338697333903, \"annualised_returns_over_uniform_hodl\": 0.8353227947650992, \"calmar\": -0.8280538742961312, \"daily_log_sharpe\": -0.6835755109470609, \"daily_returns\": 0.031016042505550373, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": 0.00748573084573575, \"return\": -0.2315433899515319, \"returns_over_hodl\": 0.003501303978607595, \"returns_over_uniform_hodl\": 0.2770472952401035, \"sharpe\": -0.19285954068246594, \"sterling\": -1.2358163149621166, \"ulcer\": -0.17641031600544232}], \"train_objective\": [{\"annualised_returns\": -0.5091704531754015, \"annualised_returns_over_hodl\": -0.3818796142583133, \"annualised_returns_over_uniform_hodl\": -0.3818796142583134, \"calmar\": -0.675672091862396, \"daily_log_sharpe\": -0.5074732478775782, \"daily_returns\": 0.008201531655765617, \"fee_revenue_over_value\": 0.001314686898283619, \"jax_sharpe\": 0.09681123559533844, \"return\": -0.3508315308113411, \"returns_over_hodl\": -0.25328308201819716, \"returns_over_uniform_hodl\": -0.25328308201819727, \"sharpe\": 0.09697895066201774, \"sterling\": -1.48076904753388, \"ulcer\": -0.18698901816412322}], \"train_return\": -0.3508315308113411, \"train_returns_over_hodl\": -0.25328308201819716, \"train_sharpe\": 0.09681123559533843, \"validation_return\": -0.33914271997364165, \"validation_returns_over_hodl\": -6.775119354429648e-10, \"validation_sharpe\": -2.2438531988686523}, {\"centeredness_margin\": 0.36268983520177756, \"continuous_test_metrics\": [{\"annualised_returns\": -0.48425484079538417, \"annualised_returns_over_hodl\": 0.0004881103098766104, \"annualised_returns_over_uniform_hodl\": 0.8203518308933717, \"calmar\": -0.8353709190817674, \"daily_log_sharpe\": -0.6919372292308036, \"daily_returns\": 0.031016042495639193, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": 0.005343151020266028, \"return\": -0.2340740879618618, \"returns_over_hodl\": 0.00019655183670552745, \"returns_over_uniform_hodl\": 0.27284169533127134, \"sharpe\": -0.20154080758747778, \"sterling\": -1.246736526520568, \"ulcer\": -0.17641031598495807}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -8.117285732467394e-10, \"optuna_trial_number\": 239, \"price_ratio\": 3.87976032767896, \"shift_exponent\": 1.3286280298576363e-05, \"step\": 239, \"test_objective\": [{\"annualised_returns\": -0.48425484079538417, \"annualised_returns_over_hodl\": 0.0004881103098766104, \"annualised_returns_over_uniform_hodl\": 0.8203518308933717, \"calmar\": -0.8353709190817674, \"daily_log_sharpe\": -0.6919372292308036, \"daily_returns\": 0.031016042495639193, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": 0.005343151020266028, \"return\": -0.2340740879618618, \"returns_over_hodl\": 0.00019655183670552745, \"returns_over_uniform_hodl\": 0.27284169533127134, \"sharpe\": -0.20154080758747778, \"sterling\": -1.246736526520568, \"ulcer\": -0.17641031598495807}], \"train_objective\": [{\"annualised_returns\": -0.5304546611742285, \"annualised_returns_over_hodl\": -0.40868362991620266, \"annualised_returns_over_uniform_hodl\": -0.40868362991620266, \"calmar\": -0.7016883768946423, \"daily_log_sharpe\": -0.5277560433857674, \"daily_returns\": 0.008221910014296258, \"fee_revenue_over_value\": 0.011824298593847841, \"jax_sharpe\": 0.1587423651857537, \"return\": -0.3680708205099308, \"returns_over_hodl\": -0.2731128640900453, \"returns_over_uniform_hodl\": -0.2731128640900453, \"sharpe\": 0.09672030006405574, \"sterling\": -1.5090593003085986, \"ulcer\": -0.19181576688606553}], \"train_return\": -0.3680708205099308, \"train_returns_over_hodl\": -0.2731128640900453, \"train_sharpe\": 0.15874236518575371, \"validation_return\": -0.3391427199528019, \"validation_returns_over_hodl\": -8.117285732467394e-10, \"validation_sharpe\": -2.2438531993504958}, {\"centeredness_margin\": 0.7150181168382204, \"continuous_test_metrics\": [{\"annualised_returns\": -0.4809349718822745, \"annualised_returns_over_hodl\": 0.006928285564664849, \"annualised_returns_over_uniform_hodl\": 0.8320694967724378, \"calmar\": -0.8296439206654839, \"daily_log_sharpe\": -0.6852889189921826, \"daily_returns\": 0.031016042503334194, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": 0.007891495792619864, \"return\": -0.23209227728373283, \"returns_over_hodl\": 0.0027845307498810534, \"returns_over_uniform_hodl\": 0.2761351356284705, \"sharpe\": -0.19458579192259554, \"sterling\": -1.2381893553435033, \"ulcer\": -0.17641031600087378}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -7.043314820265323e-10, \"optuna_trial_number\": 240, \"price_ratio\": 4.666225974521557, \"shift_exponent\": 8.748315305366611e-05, \"step\": 240, \"test_objective\": [{\"annualised_returns\": -0.4809349718822745, \"annualised_returns_over_hodl\": 0.006928285564664849, \"annualised_returns_over_uniform_hodl\": 0.8320694967724378, \"calmar\": -0.8296439206654839, \"daily_log_sharpe\": -0.6852889189921826, \"daily_returns\": 0.031016042503334194, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": 0.007891495792619864, \"return\": -0.23209227728373283, \"returns_over_hodl\": 0.0027845307498810534, \"returns_over_uniform_hodl\": 0.2761351356284705, \"sharpe\": -0.19458579192259554, \"sterling\": -1.2381893553435033, \"ulcer\": -0.17641031600087378}], \"train_objective\": [{\"annualised_returns\": -0.5156247612848769, \"annualised_returns_over_hodl\": -0.39000777085388694, \"annualised_returns_over_uniform_hodl\": -0.3900077708538867, \"calmar\": -0.6833186148262529, \"daily_log_sharpe\": -0.5153903867794329, \"daily_returns\": 0.008256477766706169, \"fee_revenue_over_value\": 0.0008808877968974272, \"jax_sharpe\": 0.09593038228161217, \"return\": -0.356027651438223, \"returns_over_hodl\": -0.2592600069061487, \"returns_over_uniform_hodl\": -0.2592600069061486, \"sharpe\": 0.09295458949240366, \"sterling\": -1.493052615770381, \"ulcer\": -0.18799288294037841}], \"train_return\": -0.356027651438223, \"train_returns_over_hodl\": -0.2592600069061487, \"train_sharpe\": 0.09593038228161217, \"validation_return\": -0.3391427199667433, \"validation_returns_over_hodl\": -7.043314820265323e-10, \"validation_sharpe\": -2.2438531989520993}, {\"centeredness_margin\": 0.6568621501938541, \"continuous_test_metrics\": [{\"annualised_returns\": -0.47870512486359484, \"annualised_returns_over_hodl\": 0.011253939812425395, \"annualised_returns_over_uniform_hodl\": 0.8399398684678803, \"calmar\": -0.8257972838480077, \"daily_log_sharpe\": -0.6838912199471641, \"daily_returns\": 0.03101604251228078, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": 0.0003060673874215351, \"return\": -0.23076540684870717, \"returns_over_hodl\": 0.004517245493655064, \"returns_over_uniform_hodl\": 0.278340173984607, \"sharpe\": -0.1940887961393583, \"sterling\": -1.2324485009953907, \"ulcer\": -0.1764103160193777}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -7.016698333472959e-10, \"optuna_trial_number\": 241, \"price_ratio\": 5.565521095713487, \"shift_exponent\": 0.00018176504719883001, \"step\": 241, \"test_objective\": [{\"annualised_returns\": -0.47870512486359484, \"annualised_returns_over_hodl\": 0.011253939812425395, \"annualised_returns_over_uniform_hodl\": 0.8399398684678803, \"calmar\": -0.8257972838480077, \"daily_log_sharpe\": -0.6838912199471641, \"daily_returns\": 0.03101604251228078, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": 0.0003060673874215351, \"return\": -0.23076540684870717, \"returns_over_hodl\": 0.004517245493655064, \"returns_over_uniform_hodl\": 0.278340173984607, \"sharpe\": -0.1940887961393583, \"sterling\": -1.2324485009953907, \"ulcer\": -0.1764103160193777}], \"train_objective\": [{\"annualised_returns\": -0.4886025262862773, \"annualised_returns_over_hodl\": -0.3559776387459155, \"annualised_returns_over_uniform_hodl\": -0.35597763874591526, \"calmar\": -0.6507387588286054, \"daily_log_sharpe\": -0.4846190349014001, \"daily_returns\": 0.008173569075187273, \"fee_revenue_over_value\": 0.0018986519147141244, \"jax_sharpe\": 0.09663762810240953, \"return\": -0.33444936112366563, \"returns_over_hodl\": -0.2344392166124658, \"returns_over_uniform_hodl\": -0.2344392166124657, \"sharpe\": 0.10591003821781647, \"sterling\": -1.4420085653378554, \"ulcer\": -0.18388374293077137}], \"train_return\": -0.33444936112366563, \"train_returns_over_hodl\": -0.2344392166124658, \"train_sharpe\": 0.09663762810240953, \"validation_return\": -0.33914271999111667, \"validation_returns_over_hodl\": -7.016698333472959e-10, \"validation_sharpe\": -2.24385319857544}, {\"centeredness_margin\": 0.6098095682832957, \"continuous_test_metrics\": [{\"annualised_returns\": -0.6869999323732682, \"annualised_returns_over_hodl\": 0.02255462159866406, \"annualised_returns_over_uniform_hodl\": 0.1047515153661791, \"calmar\": -1.191297576399815, \"daily_log_sharpe\": -1.4937566655990975, \"daily_returns\": 0.019658563078488872, \"fee_revenue_over_value\": 0.047974070889356894, \"jax_sharpe\": -0.9498227349953756, \"return\": -0.3736217598749223, \"returns_over_hodl\": 0.009023140653873662, \"returns_over_uniform_hodl\": 0.040936634403514915, \"sharpe\": -1.108655638500299, \"sterling\": -2.087049789532441, \"ulcer\": -0.14706085512648645}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -0.028951237846129213, \"optuna_trial_number\": 242, \"price_ratio\": 18.51268662385883, \"shift_exponent\": 0.01509425861664544, \"step\": 242, \"test_objective\": [{\"annualised_returns\": -0.6869999323732682, \"annualised_returns_over_hodl\": 0.02255462159866406, \"annualised_returns_over_uniform_hodl\": 0.1047515153661791, \"calmar\": -1.191297576399815, \"daily_log_sharpe\": -1.4937566655990975, \"daily_returns\": 0.019658563078488872, \"fee_revenue_over_value\": 0.047974070889356894, \"jax_sharpe\": -0.9498227349953756, \"return\": -0.3736217598749223, \"returns_over_hodl\": 0.009023140653873662, \"returns_over_uniform_hodl\": 0.040936634403514915, \"sharpe\": -1.108655638500299, \"sterling\": -2.087049789532441, \"ulcer\": -0.14706085512648645}], \"train_objective\": [{\"annualised_returns\": -0.32253392556868776, \"annualised_returns_over_hodl\": -0.1468411101908782, \"annualised_returns_over_uniform_hodl\": -0.14684111019087798, \"calmar\": -0.43929348792629047, \"daily_log_sharpe\": -0.3258724359931737, \"daily_returns\": 0.0080435656476077, \"fee_revenue_over_value\": 0.01835978220979808, \"jax_sharpe\": 0.1350615280676205, \"return\": -0.21054347928656514, \"returns_over_hodl\": -0.09191439817688551, \"returns_over_uniform_hodl\": -0.0919143981768854, \"sharpe\": 0.17309995133246933, \"sterling\": -1.051944620775497, \"ulcer\": -0.16524388450045444}], \"train_return\": -0.21054347928656514, \"train_returns_over_hodl\": -0.09191439817688551, \"train_sharpe\": 0.1350615280676205, \"validation_return\": -0.17737953734867384, \"validation_returns_over_hodl\": -0.028951237846129185, \"validation_sharpe\": -1.406061574740041}, {\"centeredness_margin\": 0.43497061759904987, \"continuous_test_metrics\": [{\"annualised_returns\": -0.5997650686695848, \"annualised_returns_over_hodl\": -0.16879765792388934, \"annualised_returns_over_uniform_hodl\": 0.4126519212674833, \"calmar\": -1.0309746541101399, \"daily_log_sharpe\": -1.0045769483450848, \"daily_returns\": 0.029413146013553805, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": -0.3347857311637233, \"return\": -0.3084283134721312, \"returns_over_hodl\": -0.07175445280985204, \"returns_over_uniform_hodl\": 0.1492773179338629, \"sharpe\": -0.5476495378980174, \"sterling\": -1.5845635913029343, \"ulcer\": -0.16836011906806853}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -0.05873508219059764, \"optuna_trial_number\": 243, \"price_ratio\": 14.525272616662843, \"shift_exponent\": 0.0006469182812628212, \"step\": 243, \"test_objective\": [{\"annualised_returns\": -0.5997650686695848, \"annualised_returns_over_hodl\": -0.16879765792388934, \"annualised_returns_over_uniform_hodl\": 0.4126519212674833, \"calmar\": -1.0309746541101399, \"daily_log_sharpe\": -1.0045769483450848, \"daily_returns\": 0.029413146013553805, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": -0.3347857311637233, \"return\": -0.3084283134721312, \"returns_over_hodl\": -0.07175445280985204, \"returns_over_uniform_hodl\": 0.1492773179338629, \"sharpe\": -0.5476495378980174, \"sterling\": -1.5845635913029343, \"ulcer\": -0.16836011906806853}], \"train_objective\": [{\"annualised_returns\": -0.39509964152356447, \"annualised_returns_over_hodl\": -0.23822588649017284, \"annualised_returns_over_uniform_hodl\": -0.23822588649017273, \"calmar\": -0.5339808364484478, \"daily_log_sharpe\": -0.39450205837292296, \"daily_returns\": 0.008056727210052479, \"fee_revenue_over_value\": 0.010351904536847915, \"jax_sharpe\": 0.11991058852613463, \"return\": -0.2630201875749, \"returns_over_hodl\": -0.15227661189911068, \"returns_over_uniform_hodl\": -0.15227661189911057, \"sharpe\": 0.14234416945153858, \"sterling\": -1.235640411390412, \"ulcer\": -0.17240136731745698}], \"train_return\": -0.2630201875749, \"train_returns_over_hodl\": -0.15227661189911068, \"train_sharpe\": 0.11991058852613463, \"validation_return\": -0.2782142168094862, \"validation_returns_over_hodl\": -0.05873508219059764, \"validation_sharpe\": -1.9471496822300691}, {\"centeredness_margin\": 0.7379569306285163, \"continuous_test_metrics\": [{\"annualised_returns\": -0.4780568153342146, \"annualised_returns_over_hodl\": 0.012511588141143504, \"annualised_returns_over_uniform_hodl\": 0.842228113772234, \"calmar\": -0.8246789060928653, \"daily_log_sharpe\": -0.6805245975161899, \"daily_returns\": 0.03101604250937569, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": 0.008206987075714324, \"return\": -0.23038026655444643, \"returns_over_hodl\": 0.005020187129326281, \"returns_over_uniform_hodl\": 0.27898021320691613, \"sharpe\": -0.1897694434970991, \"sterling\": -1.2307793953114006, \"ulcer\": -0.176410316013342}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -6.295122201294134e-10, \"optuna_trial_number\": 244, \"price_ratio\": 5.470560534922868, \"shift_exponent\": 2.102529085575066e-05, \"step\": 244, \"test_objective\": [{\"annualised_returns\": -0.4780568153342146, \"annualised_returns_over_hodl\": 0.012511588141143504, \"annualised_returns_over_uniform_hodl\": 0.842228113772234, \"calmar\": -0.8246789060928653, \"daily_log_sharpe\": -0.6805245975161899, \"daily_returns\": 0.03101604250937569, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": 0.008206987075714324, \"return\": -0.23038026655444643, \"returns_over_hodl\": 0.005020187129326281, \"returns_over_uniform_hodl\": 0.27898021320691613, \"sharpe\": -0.1897694434970991, \"sterling\": -1.2307793953114006, \"ulcer\": -0.176410316013342}], \"train_objective\": [{\"annualised_returns\": -0.49834856053458054, \"annualised_returns_over_hodl\": -0.36825119172981, \"annualised_returns_over_uniform_hodl\": -0.36825119172981, \"calmar\": -0.6629550103516737, \"daily_log_sharpe\": -0.4969517447804167, \"daily_returns\": 0.008174608808164374, \"fee_revenue_over_value\": 0.0008209971559901181, \"jax_sharpe\": 0.09194379091021508, \"return\": -0.3421790730379981, \"returns_over_hodl\": -0.24333044736612697, \"returns_over_uniform_hodl\": -0.24333044736612697, \"sharpe\": 0.09785405458387224, \"sterling\": -1.4629033561117912, \"ulcer\": -0.18508484500513261}], \"train_return\": -0.3421790730379981, \"train_returns_over_hodl\": -0.24333044736612697, \"train_sharpe\": 0.09194379091021508, \"validation_return\": -0.33914271998394174, \"validation_returns_over_hodl\": -6.295122201294134e-10, \"validation_sharpe\": -2.243853198704844}, {\"centeredness_margin\": 0.7962671088785892, \"continuous_test_metrics\": [{\"annualised_returns\": -0.48450645865469477, \"annualised_returns_over_hodl\": 8.622436098448816e-12, \"annualised_returns_over_uniform_hodl\": 0.819463731368429, \"calmar\": -0.8358049768973772, \"daily_log_sharpe\": -0.6924636107922995, \"daily_returns\": 0.031016042488448307, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": 0.005019267389435134, \"return\": -0.2342246026107062, \"returns_over_hodl\": 3.4725555764225646e-12, \"returns_over_uniform_hodl\": 0.27259156497558545, \"sharpe\": -0.2021085459763297, \"sterling\": -1.2473843296010458, \"ulcer\": -0.17641031597008697}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -8.776713800173752e-10, \"optuna_trial_number\": 245, \"price_ratio\": 3.312898756515967, \"shift_exponent\": 0.00015981401330211628, \"step\": 245, \"test_objective\": [{\"annualised_returns\": -0.48450645865469477, \"annualised_returns_over_hodl\": 8.622436098448816e-12, \"annualised_returns_over_uniform_hodl\": 0.819463731368429, \"calmar\": -0.8358049768973772, \"daily_log_sharpe\": -0.6924636107922995, \"daily_returns\": 0.031016042488448307, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": 0.005019267389435134, \"return\": -0.2342246026107062, \"returns_over_hodl\": 3.4725555764225646e-12, \"returns_over_uniform_hodl\": 0.27259156497558545, \"sharpe\": -0.2021085459763297, \"sterling\": -1.2473843296010458, \"ulcer\": -0.17641031597008697}], \"train_objective\": [{\"annualised_returns\": -0.5561175859209557, \"annualised_returns_over_hodl\": -0.4410019307323021, \"annualised_returns_over_uniform_hodl\": -0.44100193073230176, \"calmar\": -0.7273809928616902, \"daily_log_sharpe\": -0.5616298927925246, \"daily_returns\": 0.0082819461289043, \"fee_revenue_over_value\": 0.00033562043699374654, \"jax_sharpe\": 0.10795059479426121, \"return\": -0.38927060608012687, \"returns_over_hodl\": -0.29749827295415265, \"returns_over_uniform_hodl\": -0.29749827295415243, \"sharpe\": 0.07684283067275181, \"sterling\": -1.5502758084865058, \"ulcer\": -0.19682692855337863}], \"train_return\": -0.38927060608012687, \"train_returns_over_hodl\": -0.29749827295415265, \"train_sharpe\": 0.10795059479426121, \"validation_return\": -0.33914271991701217, \"validation_returns_over_hodl\": -8.776713800173752e-10, \"validation_sharpe\": -2.2438531994917605}, {\"centeredness_margin\": 0.7949046017221958, \"continuous_test_metrics\": [{\"annualised_returns\": -0.48404031315861384, \"annualised_returns_over_hodl\": 0.0009042700792496472, \"annualised_returns_over_uniform_hodl\": 0.8211090183713394, \"calmar\": -0.8350008443876489, \"daily_log_sharpe\": -0.6914903863069981, \"daily_returns\": 0.03101604249390435, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": 0.005620333546151924, \"return\": -0.23394579485254363, \"returns_over_hodl\": 0.0003640854655060366, \"returns_over_uniform_hodl\": 0.2730548971778173, \"sharpe\": -0.20106041760441679, \"sterling\": -1.2461842142495563, \"ulcer\": -0.17641031598136975}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -8.209706248152315e-10, \"optuna_trial_number\": 246, \"price_ratio\": 3.639970873241518, \"shift_exponent\": 0.00017201685499046, \"step\": 246, \"test_objective\": [{\"annualised_returns\": -0.48404031315861384, \"annualised_returns_over_hodl\": 0.0009042700792496472, \"annualised_returns_over_uniform_hodl\": 0.8211090183713394, \"calmar\": -0.8350008443876489, \"daily_log_sharpe\": -0.6914903863069981, \"daily_returns\": 0.03101604249390435, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": 0.005620333546151924, \"return\": -0.23394579485254363, \"returns_over_hodl\": 0.0003640854655060366, \"returns_over_uniform_hodl\": 0.2730548971778173, \"sharpe\": -0.20106041760441679, \"sterling\": -1.2461842142495563, \"ulcer\": -0.17641031598136975}], \"train_objective\": [{\"annualised_returns\": -0.5418555693095175, \"annualised_returns_over_hodl\": -0.42304122876081485, \"annualised_returns_over_uniform_hodl\": -0.42304122876081485, \"calmar\": -0.7126520558799935, \"daily_log_sharpe\": -0.543722945312106, \"daily_returns\": 0.008276346779836628, \"fee_revenue_over_value\": 0.00033739226258117815, \"jax_sharpe\": 0.1072910121795509, \"return\": -0.37743125402776323, \"returns_over_hodl\": -0.28387985971468666, \"returns_over_uniform_hodl\": -0.28387985971468666, \"sharpe\": 0.08582199186564217, \"sterling\": -1.5291817889858785, \"ulcer\": -0.19379766026585626}], \"train_return\": -0.37743125402776323, \"train_returns_over_hodl\": -0.28387985971468666, \"train_sharpe\": 0.1072910121795509, \"validation_return\": -0.33914271993976564, \"validation_returns_over_hodl\": -8.209706248152315e-10, \"validation_sharpe\": -2.2438531993382376}, {\"centeredness_margin\": 0.768093052715269, \"continuous_test_metrics\": [{\"annualised_returns\": -0.47981822169273647, \"annualised_returns_over_hodl\": 0.009094656563907844, \"annualised_returns_over_uniform_hodl\": 0.8360111299916635, \"calmar\": -0.827717451080496, \"daily_log_sharpe\": -0.6831140784889712, \"daily_returns\": 0.03101604250007064, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": 0.008145599957834197, \"return\": -0.23142733045775554, \"returns_over_hodl\": 0.003652862207615959, \"returns_over_uniform_hodl\": 0.2772401668488267, \"sharpe\": -0.19235442536327058, \"sterling\": -1.2353142253992482, \"ulcer\": -0.17641031599411597}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -7.406910640384012e-10, \"optuna_trial_number\": 247, \"price_ratio\": 4.001400265905781, \"shift_exponent\": 0.0004105286681581222, \"step\": 247, \"test_objective\": [{\"annualised_returns\": -0.47981822169273647, \"annualised_returns_over_hodl\": 0.009094656563907844, \"annualised_returns_over_uniform_hodl\": 0.8360111299916635, \"calmar\": -0.827717451080496, \"daily_log_sharpe\": -0.6831140784889712, \"daily_returns\": 0.03101604250007064, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": 0.008145599957834197, \"return\": -0.23142733045775554, \"returns_over_hodl\": 0.003652862207615959, \"returns_over_uniform_hodl\": 0.2772401668488267, \"sharpe\": -0.19235442536327058, \"sterling\": -1.2353142253992482, \"ulcer\": -0.17641031599411597}], \"train_objective\": [{\"annualised_returns\": -0.5251268016929296, \"annualised_returns_over_hodl\": -0.40197405308028544, \"annualised_returns_over_uniform_hodl\": -0.40197405308028566, \"calmar\": -0.6933668778028377, \"daily_log_sharpe\": -0.5247869802970299, \"daily_returns\": 0.008261302533775007, \"fee_revenue_over_value\": 0.0004111766265284624, \"jax_sharpe\": 0.10270795997141549, \"return\": -0.3637271673782192, \"returns_over_hodl\": -0.26811650423395517, \"returns_over_uniform_hodl\": -0.2681165042339553, \"sharpe\": 0.09226098867639237, \"sterling\": -1.5070535743450582, \"ulcer\": -0.18982869603091881}], \"train_return\": -0.3637271673782192, \"train_returns_over_hodl\": -0.26811650423395517, \"train_sharpe\": 0.10270795997141548, \"validation_return\": -0.3391427199489171, \"validation_returns_over_hodl\": -7.406910640384012e-10, \"validation_sharpe\": -2.243853199002273}, {\"centeredness_margin\": 0.7884232126662793, \"continuous_test_metrics\": [{\"annualised_returns\": -0.5076011009291472, \"annualised_returns_over_hodl\": -0.04480103191491602, \"annualised_returns_over_uniform_hodl\": 0.7379498798124358, \"calmar\": -0.8756611461395771, \"daily_log_sharpe\": -0.7510670947063374, \"daily_returns\": 0.031016042486632776, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": -0.0693029492721779, \"return\": -0.24823094007275326, \"returns_over_hodl\": -0.01829039912823427, \"returns_over_uniform_hodl\": 0.24931535765530577, \"sharpe\": -0.2678904606215858, \"sterling\": -1.3068776558590756, \"ulcer\": -0.17640007162154003}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -0.05148420112950847, \"optuna_trial_number\": 248, \"price_ratio\": 7.3253546826940745, \"shift_exponent\": 0.00036014921917371475, \"step\": 248, \"test_objective\": [{\"annualised_returns\": -0.5076011009291472, \"annualised_returns_over_hodl\": -0.04480103191491602, \"annualised_returns_over_uniform_hodl\": 0.7379498798124358, \"calmar\": -0.8756611461395771, \"daily_log_sharpe\": -0.7510670947063374, \"daily_returns\": 0.031016042486632776, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": -0.0693029492721779, \"return\": -0.24823094007275326, \"returns_over_hodl\": -0.01829039912823427, \"returns_over_uniform_hodl\": 0.24931535765530577, \"sharpe\": -0.2678904606215858, \"sterling\": -1.3068776558590756, \"ulcer\": -0.17640007162154003}], \"train_objective\": [{\"annualised_returns\": -0.45434473665225583, \"annualised_returns_over_hodl\": -0.3128354964681477, \"annualised_returns_over_uniform_hodl\": -0.3128354964681477, \"calmar\": -0.6077203944412785, \"daily_log_sharpe\": -0.4541603178827276, \"daily_returns\": 0.008163663865096059, \"fee_revenue_over_value\": 0.0006225594204844971, \"jax_sharpe\": 0.14599258295919645, \"return\": -0.30772682484529756, \"returns_over_hodl\": -0.20370117113194652, \"returns_over_uniform_hodl\": -0.20370117113194652, \"sharpe\": 0.11332898362218098, \"sterling\": -1.3722225029573485, \"ulcer\": -0.17925894126498848}], \"train_return\": -0.30772682484529756, \"train_returns_over_hodl\": -0.20370117113194652, \"train_sharpe\": 0.14599258295919643, \"validation_return\": -0.33296877465204433, \"validation_returns_over_hodl\": -0.05148420112950847, \"validation_sharpe\": -2.1891270677708503}, {\"centeredness_margin\": 0.7795138191105523, \"continuous_test_metrics\": [{\"annualised_returns\": -0.4860568681041699, \"annualised_returns_over_hodl\": -0.003007622716128977, \"annualised_returns_over_uniform_hodl\": 0.8139914731618001, \"calmar\": -0.838479538214428, \"daily_log_sharpe\": -0.7017609860669612, \"daily_returns\": 0.031016042518921854, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": -0.019777844531480775, \"return\": -0.235153007729897, \"returns_over_hodl\": -0.0012123731099742407, \"returns_over_uniform_hodl\": 0.2710487098151946, \"sharpe\": -0.21427500084510298, \"sterling\": -1.251375937035147, \"ulcer\": -0.1764103160330978}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -0.02575938112080045, \"optuna_trial_number\": 249, \"price_ratio\": 6.856697069644643, \"shift_exponent\": 9.05222562944031e-05, \"step\": 249, \"test_objective\": [{\"annualised_returns\": -0.4860568681041699, \"annualised_returns_over_hodl\": -0.003007622716128977, \"annualised_returns_over_uniform_hodl\": 0.8139914731618001, \"calmar\": -0.838479538214428, \"daily_log_sharpe\": -0.7017609860669612, \"daily_returns\": 0.031016042518921854, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": -0.019777844531480775, \"return\": -0.235153007729897, \"returns_over_hodl\": -0.0012123731099742407, \"returns_over_uniform_hodl\": 0.2710487098151946, \"sharpe\": -0.21427500084510298, \"sterling\": -1.251375937035147, \"ulcer\": -0.1764103160330978}], \"train_objective\": [{\"annualised_returns\": -0.46943681567541473, \"annualised_returns_over_hodl\": -0.33184152772235975, \"annualised_returns_over_uniform_hodl\": -0.33184152772235975, \"calmar\": -0.6271380800169556, \"daily_log_sharpe\": -0.4680456137389809, \"daily_returns\": 0.008165599753757594, \"fee_revenue_over_value\": 0.0006279940602235935, \"jax_sharpe\": 0.09417097104903834, \"return\": -0.3194155633050857, \"returns_over_hodl\": -0.2171463385608221, \"returns_over_uniform_hodl\": -0.2171463385608221, \"sharpe\": 0.10845698878833544, \"sterling\": -1.4048385448566738, \"ulcer\": -0.1811449148534954}], \"train_return\": -0.3194155633050857, \"train_returns_over_hodl\": -0.2171463385608221, \"train_sharpe\": 0.09417097104903834, \"validation_return\": -0.33786646799535736, \"validation_returns_over_hodl\": -0.02575938112080045, \"validation_sharpe\": -2.2322731644821543}, {\"centeredness_margin\": 0.7092450440887017, \"continuous_test_metrics\": [{\"annualised_returns\": -0.4823815397126203, \"annualised_returns_over_hodl\": 0.004122105345200078, \"annualised_returns_over_uniform_hodl\": 0.8269637534581575, \"calmar\": -0.8321393479933946, \"daily_log_sharpe\": -0.6881500896549367, \"daily_returns\": 0.03101604250094827, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": 0.00656973425676276, \"return\": -0.23295487899201928, \"returns_over_hodl\": 0.0016580886091643876, \"returns_over_uniform_hodl\": 0.27470163481133625, \"sharpe\": -0.19755558125335002, \"sterling\": -1.2419136176653622, \"ulcer\": -0.176410315995937}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -7.352941588933959e-10, \"optuna_trial_number\": 250, \"price_ratio\": 4.429782139994521, \"shift_exponent\": 7.168231306151621e-05, \"step\": 250, \"test_objective\": [{\"annualised_returns\": -0.4823815397126203, \"annualised_returns_over_hodl\": 0.004122105345200078, \"annualised_returns_over_uniform_hodl\": 0.8269637534581575, \"calmar\": -0.8321393479933946, \"daily_log_sharpe\": -0.6881500896549367, \"daily_returns\": 0.03101604250094827, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": 0.00656973425676276, \"return\": -0.23295487899201928, \"returns_over_hodl\": 0.0016580886091643876, \"returns_over_uniform_hodl\": 0.27470163481133625, \"sharpe\": -0.19755558125335002, \"sterling\": -1.2419136176653622, \"ulcer\": -0.176410315995937}], \"train_objective\": [{\"annualised_returns\": -0.5229129594690535, \"annualised_returns_over_hodl\": -0.39918607705429787, \"annualised_returns_over_uniform_hodl\": -0.39918607705429787, \"calmar\": -0.6920345158701955, \"daily_log_sharpe\": -0.5239468734763245, \"daily_returns\": 0.00821302495926796, \"fee_revenue_over_value\": 0.0008818857321347565, \"jax_sharpe\": 0.15174566764502084, \"return\": -0.3619279200290456, \"returns_over_hodl\": -0.2660468897979046, \"returns_over_uniform_hodl\": -0.2660468897979046, \"sharpe\": 0.08934705948863468, \"sterling\": -1.5061232986403448, \"ulcer\": -0.18924226407034606}], \"train_return\": -0.3619279200290456, \"train_returns_over_hodl\": -0.2660468897979046, \"train_sharpe\": 0.15174566764502084, \"validation_return\": -0.3391427199608571, \"validation_returns_over_hodl\": -7.352941588933959e-10, \"validation_sharpe\": -2.2438531990597803}, {\"centeredness_margin\": 0.8295097258908273, \"continuous_test_metrics\": [{\"annualised_returns\": -0.5329594712881132, \"annualised_returns_over_hodl\": -0.0939934438626413, \"annualised_returns_over_uniform_hodl\": 0.6484460714148821, \"calmar\": -0.9223308617303654, \"daily_log_sharpe\": -0.8114151400564118, \"daily_returns\": 0.031016042514362584, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": -0.12966484297033457, \"return\": -0.26406987848020913, \"returns_over_hodl\": -0.038973929374316874, \"returns_over_uniform_hodl\": 0.22299367183957663, \"sharpe\": -0.33327436104798935, \"sterling\": -1.3769418812127754, \"ulcer\": -0.17502901917503388}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -0.06557777479117388, \"optuna_trial_number\": 251, \"price_ratio\": 12.28554819539487, \"shift_exponent\": 1.0616849170621303e-05, \"step\": 251, \"test_objective\": [{\"annualised_returns\": -0.5329594712881132, \"annualised_returns_over_hodl\": -0.0939934438626413, \"annualised_returns_over_uniform_hodl\": 0.6484460714148821, \"calmar\": -0.9223308617303654, \"daily_log_sharpe\": -0.8114151400564118, \"daily_returns\": 0.031016042514362584, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": -0.12966484297033457, \"return\": -0.26406987848020913, \"returns_over_hodl\": -0.038973929374316874, \"returns_over_uniform_hodl\": 0.22299367183957663, \"sharpe\": -0.33327436104798935, \"sterling\": -1.3769418812127754, \"ulcer\": -0.17502901917503388}], \"train_objective\": [{\"annualised_returns\": -0.42827123533368805, \"annualised_returns_over_hodl\": -0.28000014090136305, \"annualised_returns_over_uniform_hodl\": -0.28000014090136305, \"calmar\": -0.5758184355450781, \"daily_log_sharpe\": -0.43278376584967765, \"daily_returns\": 0.008106078590954916, \"fee_revenue_over_value\": 0.0004590036432059013, \"jax_sharpe\": 0.1315363547308587, \"return\": -0.2878280201458844, \"returns_over_hodl\": -0.1808122373313903, \"returns_over_uniform_hodl\": -0.1808122373313903, \"sharpe\": 0.11537472744215066, \"sterling\": -1.3189813205382892, \"ulcer\": -0.17562896883921972}], \"train_return\": -0.2878280201458844, \"train_returns_over_hodl\": -0.1808122373313903, \"train_sharpe\": 0.1315363547308587, \"validation_return\": -0.31179102986623375, \"validation_returns_over_hodl\": -0.06557777479117388, \"validation_sharpe\": -2.0734436543713612}, {\"centeredness_margin\": 0.7930107859745488, \"continuous_test_metrics\": [{\"annualised_returns\": -0.47795909003945514, \"annualised_returns_over_hodl\": 0.012701164314915525, \"annualised_returns_over_uniform_hodl\": 0.8425730407503453, \"calmar\": -0.8245103233563301, \"daily_log_sharpe\": -0.6803867012642449, \"daily_returns\": 0.031016042509448217, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": 0.008451296536153468, \"return\": -0.230322235816946, \"returns_over_hodl\": 0.005095967491016262, \"returns_over_uniform_hodl\": 0.27907665065751086, \"sharpe\": -0.18962405084050807, \"sterling\": -1.2305277965883605, \"ulcer\": -0.1764103160134861}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -6.299203381132656e-10, \"optuna_trial_number\": 252, \"price_ratio\": 5.471476252066088, \"shift_exponent\": 1.5730285387783846e-05, \"step\": 252, \"test_objective\": [{\"annualised_returns\": -0.47795909003945514, \"annualised_returns_over_hodl\": 0.012701164314915525, \"annualised_returns_over_uniform_hodl\": 0.8425730407503453, \"calmar\": -0.8245103233563301, \"daily_log_sharpe\": -0.6803867012642449, \"daily_returns\": 0.031016042509448217, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": 0.008451296536153468, \"return\": -0.230322235816946, \"returns_over_hodl\": 0.005095967491016262, \"returns_over_uniform_hodl\": 0.27907665065751086, \"sharpe\": -0.18962405084050807, \"sterling\": -1.2305277965883605, \"ulcer\": -0.1764103160134861}], \"train_objective\": [{\"annualised_returns\": -0.4981737640942395, \"annualised_returns_over_hodl\": -0.3680310638996319, \"annualised_returns_over_uniform_hodl\": -0.3680310638996319, \"calmar\": -0.662698953027682, \"daily_log_sharpe\": -0.4965455146218365, \"daily_returns\": 0.008194117384250863, \"fee_revenue_over_value\": 0.0003759574345422976, \"jax_sharpe\": 0.09232493742201539, \"return\": -0.342039922780379, \"returns_over_hodl\": -0.24317038744880615, \"returns_over_uniform_hodl\": -0.24317038744880615, \"sharpe\": 0.09838514402224857, \"sterling\": -1.462274898797708, \"ulcer\": -0.18510708380708896}], \"train_return\": -0.342039922780379, \"train_returns_over_hodl\": -0.24317038744880615, \"train_sharpe\": 0.09232493742201539, \"validation_return\": -0.3391427199849897, \"validation_returns_over_hodl\": -6.299203381132656e-10, \"validation_sharpe\": -2.2438531987098704}, {\"centeredness_margin\": 0.7993377451728808, \"continuous_test_metrics\": [{\"annualised_returns\": -0.5313589458433018, \"annualised_returns_over_hodl\": -0.09088860291734602, \"annualised_returns_over_uniform_hodl\": 0.6540952168733616, \"calmar\": -0.9195340801969946, \"daily_log_sharpe\": -0.8073513942328725, \"daily_returns\": 0.031016042512254257, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": -0.12548541990636383, \"return\": -0.2630552124079564, \"returns_over_hodl\": -0.03764891151340577, \"returns_over_uniform_hodl\": 0.22467987838162262, \"sharpe\": -0.32879981609911063, \"sterling\": -1.3724238459551223, \"ulcer\": -0.17513458396864826}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -0.0653145969666793, \"optuna_trial_number\": 253, \"price_ratio\": 12.075179299910323, \"shift_exponent\": 1.2646601770404958e-05, \"step\": 253, \"test_objective\": [{\"annualised_returns\": -0.5313589458433018, \"annualised_returns_over_hodl\": -0.09088860291734602, \"annualised_returns_over_uniform_hodl\": 0.6540952168733616, \"calmar\": -0.9195340801969946, \"daily_log_sharpe\": -0.8073513942328725, \"daily_returns\": 0.031016042512254257, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": -0.12548541990636383, \"return\": -0.2630552124079564, \"returns_over_hodl\": -0.03764891151340577, \"returns_over_uniform_hodl\": 0.22467987838162262, \"sharpe\": -0.32879981609911063, \"sterling\": -1.3724238459551223, \"ulcer\": -0.17513458396864826}], \"train_objective\": [{\"annualised_returns\": -0.4290125187935513, \"annualised_returns_over_hodl\": -0.28093366746087634, \"annualised_returns_over_uniform_hodl\": -0.28093366746087634, \"calmar\": -0.5767277907176358, \"daily_log_sharpe\": -0.43334250140303804, \"daily_returns\": 0.00811766831947053, \"fee_revenue_over_value\": 0.0006698500348802377, \"jax_sharpe\": 0.13187648555178874, \"return\": -0.2883887650633744, \"returns_over_hodl\": -0.18145724357620296, \"returns_over_uniform_hodl\": -0.18145724357620296, \"sharpe\": 0.11539240425176024, \"sterling\": -1.3203997586464613, \"ulcer\": -0.1757485979442395}], \"train_return\": -0.2883887650633744, \"train_returns_over_hodl\": -0.18145724357620296, \"train_sharpe\": 0.13187648555178874, \"validation_return\": -0.31295513354897875, \"validation_returns_over_hodl\": -0.0653145969666793, \"validation_sharpe\": -2.0777775912636236}, {\"centeredness_margin\": 0.4248663721840819, \"continuous_test_metrics\": [{\"annualised_returns\": -0.5138973217223093, \"annualised_returns_over_hodl\": -0.057014998279048656, \"annualised_returns_over_uniform_hodl\": 0.7157270109323526, \"calmar\": -0.8866393141926385, \"daily_log_sharpe\": -0.7658440592689495, \"daily_returns\": 0.031016042486006354, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": -0.08494107071958222, \"return\": -0.25211723985657997, \"returns_over_hodl\": -0.023365385589416032, \"returns_over_uniform_hodl\": 0.24285697267620443, \"sharpe\": -0.28383294799625375, \"sterling\": -1.3232832245362527, \"ulcer\": -0.17634363348508314}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -0.05033891816308189, \"optuna_trial_number\": 254, \"price_ratio\": 6.488266614676768, \"shift_exponent\": 0.0006527563922275591, \"step\": 254, \"test_objective\": [{\"annualised_returns\": -0.5138973217223093, \"annualised_returns_over_hodl\": -0.057014998279048656, \"annualised_returns_over_uniform_hodl\": 0.7157270109323526, \"calmar\": -0.8866393141926385, \"daily_log_sharpe\": -0.7658440592689495, \"daily_returns\": 0.031016042486006354, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": -0.08494107071958222, \"return\": -0.25211723985657997, \"returns_over_hodl\": -0.023365385589416032, \"returns_over_uniform_hodl\": 0.24285697267620443, \"sharpe\": -0.28383294799625375, \"sterling\": -1.3232832245362527, \"ulcer\": -0.17634363348508314}], \"train_objective\": [{\"annualised_returns\": -0.4467105338040097, \"annualised_returns_over_hodl\": -0.30322145338554063, \"annualised_returns_over_uniform_hodl\": -0.30322145338554063, \"calmar\": -0.5988079455349242, \"daily_log_sharpe\": -0.43901390233177506, \"daily_returns\": 0.008158049800910784, \"fee_revenue_over_value\": 0.010632242996730538, \"jax_sharpe\": 0.1180943140615466, \"return\": -0.3018625968091173, \"returns_over_hodl\": -0.1969557444925537, \"returns_over_uniform_hodl\": -0.1969557444925537, \"sharpe\": 0.13220935919569077, \"sterling\": -1.3484736045725643, \"ulcer\": -0.17900602349148995}], \"train_return\": -0.3018625968091173, \"train_returns_over_hodl\": -0.1969557444925537, \"train_sharpe\": 0.11809431406154658, \"validation_return\": -0.3337059415271574, \"validation_returns_over_hodl\": -0.05033891816308189, \"validation_sharpe\": -2.195754195353558}, {\"centeredness_margin\": 0.3890418123947821, \"continuous_test_metrics\": [{\"annualised_returns\": -0.4771216414202978, \"annualised_returns_over_hodl\": 0.014325721330297414, \"annualised_returns_over_uniform_hodl\": 0.8455288632141247, \"calmar\": -0.8230656679045637, \"daily_log_sharpe\": -0.6799740366142203, \"daily_returns\": 0.031016042508015332, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": 0.005319585976050029, \"return\": -0.22982521215278495, \"returns_over_hodl\": 0.005745013782914032, \"returns_over_uniform_hodl\": 0.27990262146404365, \"sharpe\": -0.18957928967543156, \"sterling\": -1.2283717427270822, \"ulcer\": -0.17641031601054308}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -6.434770494223585e-10, \"optuna_trial_number\": 255, \"price_ratio\": 4.703736298420496, \"shift_exponent\": 0.00038637141365479606, \"step\": 255, \"test_objective\": [{\"annualised_returns\": -0.4771216414202978, \"annualised_returns_over_hodl\": 0.014325721330297414, \"annualised_returns_over_uniform_hodl\": 0.8455288632141247, \"calmar\": -0.8230656679045637, \"daily_log_sharpe\": -0.6799740366142203, \"daily_returns\": 0.031016042508015332, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": 0.005319585976050029, \"return\": -0.22982521215278495, \"returns_over_hodl\": 0.005745013782914032, \"returns_over_uniform_hodl\": 0.27990262146404365, \"sharpe\": -0.18957928967543156, \"sterling\": -1.2283717427270822, \"ulcer\": -0.17641031601054308}], \"train_objective\": [{\"annualised_returns\": -0.4943014552430949, \"annualised_returns_over_hodl\": -0.3631545175379357, \"annualised_returns_over_uniform_hodl\": -0.3631545175379357, \"calmar\": -0.6581919261795399, \"daily_log_sharpe\": -0.48454999886277655, \"daily_returns\": 0.008196711581537147, \"fee_revenue_over_value\": 0.0113056742401533, \"jax_sharpe\": 0.11405955381019472, \"return\": -0.3389621592938781, \"returns_over_hodl\": -0.23963013838558644, \"returns_over_uniform_hodl\": -0.23963013838558644, \"sharpe\": 0.11706033532671828, \"sterling\": -1.4461419108212301, \"ulcer\": -0.18533417395927637}], \"train_return\": -0.3389621592938781, \"train_returns_over_hodl\": -0.23963013838558644, \"train_sharpe\": 0.11405955381019472, \"validation_return\": -0.339142719978178, \"validation_returns_over_hodl\": -6.434770494223585e-10, \"validation_sharpe\": -2.24385319874305}, {\"centeredness_margin\": 0.39515142015734045, \"continuous_test_metrics\": [{\"annualised_returns\": -0.5316571610777168, \"annualised_returns_over_hodl\": -0.09146710698715677, \"annualised_returns_over_uniform_hodl\": 0.6530426492665129, \"calmar\": -0.9188274167206856, \"daily_log_sharpe\": -0.8078156049173786, \"daily_returns\": 0.03101604250153133, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": -0.1262156597071113, \"return\": -0.2632441113015175, \"returns_over_hodl\": -0.03789558804095927, \"returns_over_uniform_hodl\": 0.2243659597843437, \"sharpe\": -0.32891307866512626, \"sterling\": -1.3718619396013727, \"ulcer\": -0.1755593014784838}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -0.0634987395706138, \"optuna_trial_number\": 256, \"price_ratio\": 8.18985882440989, \"shift_exponent\": 0.0005681681422568099, \"step\": 256, \"test_objective\": [{\"annualised_returns\": -0.5316571610777168, \"annualised_returns_over_hodl\": -0.09146710698715677, \"annualised_returns_over_uniform_hodl\": 0.6530426492665129, \"calmar\": -0.9188274167206856, \"daily_log_sharpe\": -0.8078156049173786, \"daily_returns\": 0.03101604250153133, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": -0.1262156597071113, \"return\": -0.2632441113015175, \"returns_over_hodl\": -0.03789558804095927, \"returns_over_uniform_hodl\": 0.2243659597843437, \"sharpe\": -0.32891307866512626, \"sterling\": -1.3718619396013727, \"ulcer\": -0.1755593014784838}], \"train_objective\": [{\"annualised_returns\": -0.4296787956096376, \"annualised_returns_over_hodl\": -0.2817727352905016, \"annualised_returns_over_uniform_hodl\": -0.2817727352905016, \"calmar\": -0.5776693995327792, \"daily_log_sharpe\": -0.4245399534777701, \"daily_returns\": 0.008122620739744301, \"fee_revenue_over_value\": 0.01091328706290148, \"jax_sharpe\": 0.1594330028014584, \"return\": -0.28889301521141253, \"returns_over_hodl\": -0.18203726576505874, \"returns_over_uniform_hodl\": -0.18203726576505874, \"sharpe\": 0.13457639349950976, \"sterling\": -1.3124550224415195, \"ulcer\": -0.17679079561463582}], \"train_return\": -0.28889301521141253, \"train_returns_over_hodl\": -0.18203726576505874, \"train_sharpe\": 0.1594330028014584, \"validation_return\": -0.3231648727960892, \"validation_returns_over_hodl\": -0.0634987395706138, \"validation_sharpe\": -2.1220393144042604}, {\"centeredness_margin\": 0.7892198225120061, \"continuous_test_metrics\": [{\"annualised_returns\": -0.4802851981619132, \"annualised_returns_over_hodl\": 0.00818877398394613, \"annualised_returns_over_uniform_hodl\": 0.8343629100220173, \"calmar\": -0.8285230170763568, \"daily_log_sharpe\": -0.6840936062402195, \"daily_returns\": 0.03101604250714592, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": 0.007896687104271062, \"return\": -0.23170527854273293, \"returns_over_hodl\": 0.0032898991569434433, \"returns_over_uniform_hodl\": 0.276778263280693, \"sharpe\": -0.19338999017936898, \"sterling\": -1.236516479882983, \"ulcer\": -0.17641031600873663}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -6.626433846079749e-10, \"optuna_trial_number\": 257, \"price_ratio\": 5.153598090300894, \"shift_exponent\": 1.007100999371729e-05, \"step\": 257, \"test_objective\": [{\"annualised_returns\": -0.4802851981619132, \"annualised_returns_over_hodl\": 0.00818877398394613, \"annualised_returns_over_uniform_hodl\": 0.8343629100220173, \"calmar\": -0.8285230170763568, \"daily_log_sharpe\": -0.6840936062402195, \"daily_returns\": 0.03101604250714592, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": 0.007896687104271062, \"return\": -0.23170527854273293, \"returns_over_hodl\": 0.0032898991569434433, \"returns_over_uniform_hodl\": 0.276778263280693, \"sharpe\": -0.19338999017936898, \"sterling\": -1.236516479882983, \"ulcer\": -0.17641031600873663}], \"train_objective\": [{\"annualised_returns\": -0.5052758060005494, \"annualised_returns_over_hodl\": -0.37697493639280644, \"annualised_returns_over_uniform_hodl\": -0.37697493639280666, \"calmar\": -0.6711602421682331, \"daily_log_sharpe\": -0.5041319276031876, \"daily_returns\": 0.008177997808578408, \"fee_revenue_over_value\": 0.00037117749263929377, \"jax_sharpe\": 0.1507491751147456, \"return\": -0.34770908156608427, \"returns_over_hodl\": -0.24969143241160596, \"returns_over_uniform_hodl\": -0.24969143241160618, \"sharpe\": 0.09622691218674678, \"sterling\": -1.4748622195419194, \"ulcer\": -0.18629132798706372}], \"train_return\": -0.34770908156608427, \"train_returns_over_hodl\": -0.24969143241160596, \"train_sharpe\": 0.1507491751147456, \"validation_return\": -0.33914271998123313, \"validation_returns_over_hodl\": -6.626433846079749e-10, \"validation_sharpe\": -2.243853198832315}, {\"centeredness_margin\": 0.7347046529649214, \"continuous_test_metrics\": [{\"annualised_returns\": -0.4845064586917579, \"annualised_returns_over_hodl\": 8.701706022407052e-12, \"annualised_returns_over_uniform_hodl\": 0.8194637312376125, \"calmar\": -0.8358049769567655, \"daily_log_sharpe\": -0.6924636108853707, \"daily_returns\": 0.031016042486682882, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": 0.005019267278000956, \"return\": -0.23422460263288014, \"returns_over_hodl\": 3.504529999531769e-12, \"returns_over_uniform_hodl\": 0.27259156493873604, \"sharpe\": -0.2021085460855065, \"sterling\": -1.247384329725262, \"ulcer\": -0.1764103159664443}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -9.083717111835199e-10, \"optuna_trial_number\": 258, \"price_ratio\": 3.2516920037179236, \"shift_exponent\": 1.4995058497808109e-05, \"step\": 258, \"test_objective\": [{\"annualised_returns\": -0.4845064586917579, \"annualised_returns_over_hodl\": 8.701706022407052e-12, \"annualised_returns_over_uniform_hodl\": 0.8194637312376125, \"calmar\": -0.8358049769567655, \"daily_log_sharpe\": -0.6924636108853707, \"daily_returns\": 0.031016042486682882, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": 0.005019267278000956, \"return\": -0.23422460263288014, \"returns_over_hodl\": 3.504529999531769e-12, \"returns_over_uniform_hodl\": 0.27259156493873604, \"sharpe\": -0.2021085460855065, \"sterling\": -1.247384329725262, \"ulcer\": -0.1764103159664443}], \"train_objective\": [{\"annualised_returns\": -0.5609472344578044, \"annualised_returns_over_hodl\": -0.4470840915066622, \"annualised_returns_over_uniform_hodl\": -0.44708409150666206, \"calmar\": -0.7327050255658646, \"daily_log_sharpe\": -0.5670099181317247, \"daily_returns\": 0.008315521345217859, \"fee_revenue_over_value\": 0.00041173183608587483, \"jax_sharpe\": 0.10966995680827607, \"return\": -0.39331360756474276, \"returns_over_hodl\": -0.30214880321136195, \"returns_over_uniform_hodl\": -0.30214880321136184, \"sharpe\": 0.07531035130033403, \"sterling\": -1.55667601484001, \"ulcer\": -0.1980264229224528}], \"train_return\": -0.39331360756474276, \"train_returns_over_hodl\": -0.30214880321136195, \"train_sharpe\": 0.10966995680827607, \"validation_return\": -0.3391427199178597, \"validation_returns_over_hodl\": -9.083717111835199e-10, \"validation_sharpe\": -2.243853199623636}, {\"centeredness_margin\": 0.26982624638284003, \"continuous_test_metrics\": [{\"annualised_returns\": -0.5816888180078515, \"annualised_returns_over_hodl\": -0.13736348087134642, \"annualised_returns_over_uniform_hodl\": 0.4764530746094162, \"calmar\": -1.002658231531044, \"daily_log_sharpe\": -0.9495918501465688, \"daily_returns\": 0.029568918630223234, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": -0.27793748503641186, \"return\": -0.29601481136480334, \"returns_over_hodl\": -0.057773219457645575, \"returns_over_uniform_hodl\": 0.16990649736094898, \"sharpe\": -0.48751370586589954, \"sterling\": -1.5294368417940172, \"ulcer\": -0.16965593096955572}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -0.058798877898577157, \"optuna_trial_number\": 259, \"price_ratio\": 18.401609643537327, \"shift_exponent\": 2.5749340061597974e-05, \"step\": 259, \"test_objective\": [{\"annualised_returns\": -0.5816888180078515, \"annualised_returns_over_hodl\": -0.13736348087134642, \"annualised_returns_over_uniform_hodl\": 0.4764530746094162, \"calmar\": -1.002658231531044, \"daily_log_sharpe\": -0.9495918501465688, \"daily_returns\": 0.029568918630223234, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": -0.27793748503641186, \"return\": -0.29601481136480334, \"returns_over_hodl\": -0.057773219457645575, \"returns_over_uniform_hodl\": 0.16990649736094898, \"sharpe\": -0.48751370586589954, \"sterling\": -1.5294368417940172, \"ulcer\": -0.16965593096955572}], \"train_objective\": [{\"annualised_returns\": -0.38021865736664273, \"annualised_returns_over_hodl\": -0.21948569505956284, \"annualised_returns_over_uniform_hodl\": -0.21948569505956284, \"calmar\": -0.5164353276338488, \"daily_log_sharpe\": -0.3728635266329355, \"daily_returns\": 0.00807106892732859, \"fee_revenue_over_value\": 0.017633280863123043, \"jax_sharpe\": 0.16651414694154731, \"return\": -0.25206552923770786, \"returns_over_hodl\": -0.1396758324414833, \"returns_over_uniform_hodl\": -0.1396758324414833, \"sharpe\": 0.1614915079859958, \"sterling\": -1.193601452373618, \"ulcer\": -0.17156174220376483}], \"train_return\": -0.25206552923770786, \"train_returns_over_hodl\": -0.1396758324414833, \"train_sharpe\": 0.16651414694154731, \"validation_return\": -0.2797543817577056, \"validation_returns_over_hodl\": -0.058798877898577184, \"validation_sharpe\": -1.9510210336355085}, {\"centeredness_margin\": 0.4471879582502144, \"continuous_test_metrics\": [{\"annualised_returns\": -0.4841779073708534, \"annualised_returns_over_hodl\": 0.0006373525889078469, \"annualised_returns_over_uniform_hodl\": 0.8206233717846476, \"calmar\": -0.8352382037177956, \"daily_log_sharpe\": -0.6917763347716601, \"daily_returns\": 0.031016042495335596, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": 0.005456340269693354, \"return\": -0.23402807610712906, \"returns_over_hodl\": 0.00025663715510071405, \"returns_over_uniform_hodl\": 0.2729181593942609, \"sharpe\": -0.20136726107899497, \"sterling\": -1.2465384575155847, \"ulcer\": -0.17641031598432805}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -8.152215569268151e-10, \"optuna_trial_number\": 260, \"price_ratio\": 3.7461656189242647, \"shift_exponent\": 9.683796801320924e-05, \"step\": 260, \"test_objective\": [{\"annualised_returns\": -0.4841779073708534, \"annualised_returns_over_hodl\": 0.0006373525889078469, \"annualised_returns_over_uniform_hodl\": 0.8206233717846476, \"calmar\": -0.8352382037177956, \"daily_log_sharpe\": -0.6917763347716601, \"daily_returns\": 0.031016042495335596, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": 0.005456340269693354, \"return\": -0.23402807610712906, \"returns_over_hodl\": 0.00025663715510071405, \"returns_over_uniform_hodl\": 0.2729181593942609, \"sharpe\": -0.20136726107899497, \"sterling\": -1.2465384575155847, \"ulcer\": -0.17641031598432805}], \"train_objective\": [{\"annualised_returns\": -0.5305879342462467, \"annualised_returns_over_hodl\": -0.40885146578349685, \"annualised_returns_over_uniform_hodl\": -0.40885146578349685, \"calmar\": -0.701312019497519, \"daily_log_sharpe\": -0.5262815062487838, \"daily_returns\": 0.00817526837257625, \"fee_revenue_over_value\": 0.011374568353225445, \"jax_sharpe\": 0.11895411832555688, \"return\": -0.3681797218019047, \"returns_over_hodl\": -0.2732381296273698, \"returns_over_uniform_hodl\": -0.2732381296273698, \"sharpe\": 0.10029605179933182, \"sterling\": -1.5061726455240472, \"ulcer\": -0.19223968058626667}], \"train_return\": -0.3681797218019047, \"train_returns_over_hodl\": -0.2732381296273698, \"train_sharpe\": 0.11895411832555687, \"validation_return\": -0.33914271995174483, \"validation_returns_over_hodl\": -8.152215569268151e-10, \"validation_sharpe\": -2.2438531993591466}, {\"centeredness_margin\": 0.39221477953022776, \"continuous_test_metrics\": [{\"annualised_returns\": -0.48850824435126083, \"annualised_returns_over_hodl\": -0.007763019443704389, \"annualised_returns_over_uniform_hodl\": 0.8053392014730416, \"calmar\": -0.8427083282444482, \"daily_log_sharpe\": -0.707228896414087, \"daily_returns\": 0.031016042521582278, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": -0.02491176514926113, \"return\": -0.23662434294084222, \"returns_over_hodl\": -0.0031337396733449596, \"returns_over_uniform_hodl\": 0.2686035949876826, \"sharpe\": -0.22025992791805193, \"sterling\": -1.257687129854692, \"ulcer\": -0.17641031603858914}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -0.033553576961997345, \"optuna_trial_number\": 261, \"price_ratio\": 7.315207363645149, \"shift_exponent\": 3.9075105291110874e-05, \"step\": 261, \"test_objective\": [{\"annualised_returns\": -0.48850824435126083, \"annualised_returns_over_hodl\": -0.007763019443704389, \"annualised_returns_over_uniform_hodl\": 0.8053392014730416, \"calmar\": -0.8427083282444482, \"daily_log_sharpe\": -0.707228896414087, \"daily_returns\": 0.031016042521582278, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": -0.02491176514926113, \"return\": -0.23662434294084222, \"returns_over_hodl\": -0.0031337396733449596, \"returns_over_uniform_hodl\": 0.2686035949876826, \"sharpe\": -0.22025992791805193, \"sterling\": -1.257687129854692, \"ulcer\": -0.17641031603858914}], \"train_objective\": [{\"annualised_returns\": -0.454188611284298, \"annualised_returns_over_hodl\": -0.31263888183219024, \"annualised_returns_over_uniform_hodl\": -0.31263888183219024, \"calmar\": -0.6095490474901755, \"daily_log_sharpe\": -0.44799287856425046, \"daily_returns\": 0.008132746002346224, \"fee_revenue_over_value\": 0.010492469426451146, \"jax_sharpe\": 0.10763213429458982, \"return\": -0.30760657521415935, \"returns_over_hodl\": -0.20356285197719126, \"returns_over_uniform_hodl\": -0.20356285197719126, \"sharpe\": 0.12380716824114826, \"sterling\": -1.3689355956592266, \"ulcer\": -0.17951352756886474}], \"train_return\": -0.30760657521415935, \"train_returns_over_hodl\": -0.20356285197719126, \"train_sharpe\": 0.1076321342945898, \"validation_return\": -0.33693681664033537, \"validation_returns_over_hodl\": -0.033553576961997345, \"validation_sharpe\": -2.224206785994679}, {\"centeredness_margin\": 0.5183974306621923, \"continuous_test_metrics\": [{\"annualised_returns\": -0.4845064592031507, \"annualised_returns_over_hodl\": 9.273026790879157e-12, \"annualised_returns_over_uniform_hodl\": 0.8194637294326217, \"calmar\": -0.8358049777761068, \"daily_log_sharpe\": -0.692463612169406, \"daily_returns\": 0.03101604246238792, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": 0.005019265740800878, \"return\": -0.23422460293883351, \"returns_over_hodl\": 3.7345682102341016e-12, \"returns_over_uniform_hodl\": 0.2725915644302921, \"sharpe\": -0.2021085475916012, \"sterling\": -1.2473843314389363, \"ulcer\": -0.17641031591621878}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -1.0940190975361475e-09, \"optuna_trial_number\": 262, \"price_ratio\": 2.2223204416240088, \"shift_exponent\": 0.0003745087424037454, \"step\": 262, \"test_objective\": [{\"annualised_returns\": -0.4845064592031507, \"annualised_returns_over_hodl\": 9.273026790879157e-12, \"annualised_returns_over_uniform_hodl\": 0.8194637294326217, \"calmar\": -0.8358049777761068, \"daily_log_sharpe\": -0.692463612169406, \"daily_returns\": 0.03101604246238792, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": 0.005019265740800878, \"return\": -0.23422460293883351, \"returns_over_hodl\": 3.7345682102341016e-12, \"returns_over_uniform_hodl\": 0.2725915644302921, \"sharpe\": -0.2021085475916012, \"sterling\": -1.2473843314389363, \"ulcer\": -0.17641031591621878}], \"train_objective\": [{\"annualised_returns\": -0.614110808679018, \"annualised_returns_over_hodl\": -0.5140350100436967, \"annualised_returns_over_uniform_hodl\": -0.5140350100436968, \"calmar\": -0.7878188783219554, \"daily_log_sharpe\": -0.6459740658541722, \"daily_returns\": 0.008436007937355267, \"fee_revenue_over_value\": 0.0014551736139971868, \"jax_sharpe\": 0.09373101712299967, \"return\": -0.4390390762570945, \"returns_over_hodl\": -0.3547452903726991, \"returns_over_uniform_hodl\": -0.3547452903726992, \"sharpe\": 0.020717835732457973, \"sterling\": -1.66825208386662, \"ulcer\": -0.20522126784827552}], \"train_return\": -0.4390390762570945, \"train_returns_over_hodl\": -0.3547452903726991, \"train_sharpe\": 0.09373101712299967, \"validation_return\": -0.33914271977244237, \"validation_returns_over_hodl\": -1.0940190975361475e-09, \"validation_sharpe\": -2.243853199852858}, {\"centeredness_margin\": 0.29468812140643236, \"continuous_test_metrics\": [{\"annualised_returns\": -0.4873398270203332, \"annualised_returns_over_hodl\": -0.005496419934385766, \"annualised_returns_over_uniform_hodl\": 0.8094631968022106, \"calmar\": -0.8406927292066089, \"daily_log_sharpe\": -0.7049358732528181, \"daily_returns\": 0.03101604251783277, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": -0.02359596740221767, \"return\": -0.2359225256357539, \"returns_over_hodl\": -0.0022172603058584484, \"returns_over_uniform_hodl\": 0.269769898822533, \"sharpe\": -0.2178332954794194, \"sterling\": -1.2546789800362397, \"ulcer\": -0.17641031603084364}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -0.017058764901418222, \"optuna_trial_number\": 263, \"price_ratio\": 6.021148218341983, \"shift_exponent\": 0.000347233323753335, \"step\": 263, \"test_objective\": [{\"annualised_returns\": -0.4873398270203332, \"annualised_returns_over_hodl\": -0.005496419934385766, \"annualised_returns_over_uniform_hodl\": 0.8094631968022106, \"calmar\": -0.8406927292066089, \"daily_log_sharpe\": -0.7049358732528181, \"daily_returns\": 0.03101604251783277, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": -0.02359596740221767, \"return\": -0.2359225256357539, \"returns_over_hodl\": -0.0022172603058584484, \"returns_over_uniform_hodl\": 0.269769898822533, \"sharpe\": -0.2178332954794194, \"sterling\": -1.2546789800362397, \"ulcer\": -0.17641031603084364}], \"train_objective\": [{\"annualised_returns\": -0.46377110593108484, \"annualised_returns_over_hodl\": -0.32470648315276884, \"annualised_returns_over_uniform_hodl\": -0.32470648315276873, \"calmar\": -0.6207628567641373, \"daily_log_sharpe\": -0.4536676697691227, \"daily_returns\": 0.008194436767202338, \"fee_revenue_over_value\": 0.012228153496974205, \"jax_sharpe\": 0.11520930968224494, \"return\": -0.31501237487083866, \"returns_over_hodl\": -0.21208149722460723, \"returns_over_uniform_hodl\": -0.21208149722460712, \"sharpe\": 0.12803398055678353, \"sterling\": -1.384659145122694, \"ulcer\": -0.1812857981389941}], \"train_return\": -0.31501237487083866, \"train_returns_over_hodl\": -0.21208149722460723, \"train_sharpe\": 0.11520930968224495, \"validation_return\": -0.3386897697010083, \"validation_returns_over_hodl\": -0.017058764901418222, \"validation_sharpe\": -2.239766580300449}, {\"centeredness_margin\": 0.4964910013210541, \"continuous_test_metrics\": [{\"annualised_returns\": -0.484005320466388, \"annualised_returns_over_hodl\": 0.0009721519365892828, \"annualised_returns_over_uniform_hodl\": 0.8212325270659431, \"calmar\": -0.8349404796298522, \"daily_log_sharpe\": -0.6914179726918392, \"daily_returns\": 0.0310160424954788, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": 0.0056649878429897915, \"return\": -0.23392487133437845, \"returns_over_hodl\": 0.00039140874905929657, \"returns_over_uniform_hodl\": 0.2730896685909725, \"sharpe\": -0.2009829833731595, \"sterling\": -1.2460941237601055, \"ulcer\": -0.1764103159846277}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -8.097271742002476e-10, \"optuna_trial_number\": 264, \"price_ratio\": 3.7641582162931866, \"shift_exponent\": 0.00012322346554919446, \"step\": 264, \"test_objective\": [{\"annualised_returns\": -0.484005320466388, \"annualised_returns_over_hodl\": 0.0009721519365892828, \"annualised_returns_over_uniform_hodl\": 0.8212325270659431, \"calmar\": -0.8349404796298522, \"daily_log_sharpe\": -0.6914179726918392, \"daily_returns\": 0.0310160424954788, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": 0.0056649878429897915, \"return\": -0.23392487133437845, \"returns_over_hodl\": 0.00039140874905929657, \"returns_over_uniform_hodl\": 0.2730896685909725, \"sharpe\": -0.2009829833731595, \"sterling\": -1.2460941237601055, \"ulcer\": -0.1764103159846277}], \"train_objective\": [{\"annualised_returns\": -0.5321545173009622, \"annualised_returns_over_hodl\": -0.41082432362863197, \"annualised_returns_over_uniform_hodl\": -0.41082432362863197, \"calmar\": -0.7027529393100839, \"daily_log_sharpe\": -0.529360150994046, \"daily_returns\": 0.008203239352482135, \"fee_revenue_over_value\": 0.009424467434702656, \"jax_sharpe\": 0.16039601569695874, \"return\": -0.3694607341958416, \"returns_over_hodl\": -0.274711635615565, \"returns_over_uniform_hodl\": -0.274711635615565, \"sharpe\": 0.09681801522262565, \"sterling\": -1.510722320659362, \"ulcer\": -0.19225232227832592}], \"train_return\": -0.3694607341958416, \"train_returns_over_hodl\": -0.274711635615565, \"train_sharpe\": 0.16039601569695874, \"validation_return\": -0.339142719949706, \"validation_returns_over_hodl\": -8.097271742002476e-10, \"validation_sharpe\": -2.2438531993275417}, {\"centeredness_margin\": 0.21946700262775906, \"continuous_test_metrics\": [{\"annualised_returns\": -0.4845064588384106, \"annualised_returns_over_hodl\": 8.728795464207906e-12, \"annualised_returns_over_uniform_hodl\": 0.819463730719993, \"calmar\": -0.8358049771917075, \"daily_log_sharpe\": -0.6924636112535583, \"daily_returns\": 0.031016042479719928, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": 0.005019266837269413, \"return\": -0.23422460272061874, \"returns_over_hodl\": 3.5154101851730957e-12, \"returns_over_uniform_hodl\": 0.2725915647929289, \"sharpe\": -0.20210854651734272, \"sterling\": -1.2473843302166365, \"ulcer\": -0.17641031595204984}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -9.65925672780088e-10, \"optuna_trial_number\": 265, \"price_ratio\": 2.8426700776507894, \"shift_exponent\": 0.00016907661158620254, \"step\": 265, \"test_objective\": [{\"annualised_returns\": -0.4845064588384106, \"annualised_returns_over_hodl\": 8.728795464207906e-12, \"annualised_returns_over_uniform_hodl\": 0.819463730719993, \"calmar\": -0.8358049771917075, \"daily_log_sharpe\": -0.6924636112535583, \"daily_returns\": 0.031016042479719928, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": 0.005019266837269413, \"return\": -0.23422460272061874, \"returns_over_hodl\": 3.5154101851730957e-12, \"returns_over_uniform_hodl\": 0.2725915647929289, \"sharpe\": -0.20210854651734272, \"sterling\": -1.2473843302166365, \"ulcer\": -0.17641031595204984}], \"train_objective\": [{\"annualised_returns\": -0.5684027907437461, \"annualised_returns_over_hodl\": -0.45647315815352363, \"annualised_returns_over_uniform_hodl\": -0.45647315815352396, \"calmar\": -0.7406246541076779, \"daily_log_sharpe\": -0.5725659593952208, \"daily_returns\": 0.008369573116100574, \"fee_revenue_over_value\": 0.013030333330426322, \"jax_sharpe\": 0.123060599449494, \"return\": -0.39958928918320835, \"returns_over_hodl\": -0.30936751123369144, \"returns_over_uniform_hodl\": -0.30936751123369166, \"sharpe\": 0.07886161521300755, \"sterling\": -1.567509947688789, \"ulcer\": -0.19980798234543967}], \"train_return\": -0.39958928918320835, \"train_returns_over_hodl\": -0.30936751123369144, \"train_sharpe\": 0.123060599449494, \"validation_return\": -0.3391427198790472, \"validation_returns_over_hodl\": -9.65925672780088e-10, \"validation_sharpe\": -2.2438531997155153}, {\"centeredness_margin\": 0.18053786855721599, \"continuous_test_metrics\": [{\"annualised_returns\": -0.4819750862266692, \"annualised_returns_over_hodl\": 0.004910579726822295, \"annualised_returns_over_uniform_hodl\": 0.8283983541211384, \"calmar\": -0.8314381882439171, \"daily_log_sharpe\": -0.6873258664170099, \"daily_returns\": 0.031016042502577872, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": 0.007662550752418083, \"return\": -0.23271236175222298, \"returns_over_hodl\": 0.0019747835826398052, \"returns_over_uniform_hodl\": 0.27510465819753804, \"sharpe\": -0.19668453358236543, \"sterling\": -1.2408671825094533, \"ulcer\": -0.176410315999305}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -7.318361472385959e-10, \"optuna_trial_number\": 266, \"price_ratio\": 4.393799647566545, \"shift_exponent\": 8.981167823547998e-05, \"step\": 266, \"test_objective\": [{\"annualised_returns\": -0.4819750862266692, \"annualised_returns_over_hodl\": 0.004910579726822295, \"annualised_returns_over_uniform_hodl\": 0.8283983541211384, \"calmar\": -0.8314381882439171, \"daily_log_sharpe\": -0.6873258664170099, \"daily_returns\": 0.031016042502577872, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": 0.007662550752418083, \"return\": -0.23271236175222298, \"returns_over_hodl\": 0.0019747835826398052, \"returns_over_uniform_hodl\": 0.27510465819753804, \"sharpe\": -0.19668453358236543, \"sterling\": -1.2408671825094533, \"ulcer\": -0.176410315999305}], \"train_objective\": [{\"annualised_returns\": -0.507667136298049, \"annualised_returns_over_hodl\": -0.37998642992551857, \"annualised_returns_over_uniform_hodl\": -0.3799864299255187, \"calmar\": -0.6746863215661975, \"daily_log_sharpe\": -0.4984504959590548, \"daily_returns\": 0.008217368690528432, \"fee_revenue_over_value\": 0.014593743804464576, \"jax_sharpe\": 0.11894716974386173, \"return\": -0.3496251295584283, \"returns_over_hodl\": -0.25189539874616407, \"returns_over_uniform_hodl\": -0.2518953987461642, \"sharpe\": 0.11385851281985436, \"sterling\": -1.4673573766010841, \"ulcer\": -0.18817123883457168}], \"train_return\": -0.3496251295584283, \"train_returns_over_hodl\": -0.25189539874616407, \"train_sharpe\": 0.11894716974386173, \"validation_return\": -0.33914271997656276, \"validation_returns_over_hodl\": -7.318361472385959e-10, \"validation_sharpe\": -2.2438531991020523}, {\"centeredness_margin\": 0.19933744640887158, \"continuous_test_metrics\": [{\"annualised_returns\": -0.47880505583214883, \"annualised_returns_over_hodl\": 0.01106008533359848, \"annualised_returns_over_uniform_hodl\": 0.8395871564388409, \"calmar\": -0.8259696714939756, \"daily_log_sharpe\": -0.6815314158946092, \"daily_returns\": 0.031016042501169544, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": 0.007924359689234568, \"return\": -0.23082479813487466, \"returns_over_hodl\": 0.004439688620340165, \"returns_over_uniform_hodl\": 0.27824147552802625, \"sharpe\": -0.19077205565448913, \"sterling\": -1.2327057785174915, \"ulcer\": -0.17641031599638565}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -7.251007572151025e-10, \"optuna_trial_number\": 267, \"price_ratio\": 3.978062563408231, \"shift_exponent\": 0.0005436457296209508, \"step\": 267, \"test_objective\": [{\"annualised_returns\": -0.47880505583214883, \"annualised_returns_over_hodl\": 0.01106008533359848, \"annualised_returns_over_uniform_hodl\": 0.8395871564388409, \"calmar\": -0.8259696714939756, \"daily_log_sharpe\": -0.6815314158946092, \"daily_returns\": 0.031016042501169544, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": 0.007924359689234568, \"return\": -0.23082479813487466, \"returns_over_hodl\": 0.004439688620340165, \"returns_over_uniform_hodl\": 0.27824147552802625, \"sharpe\": -0.19077205565448913, \"sterling\": -1.2327057785174915, \"ulcer\": -0.17641031599638565}], \"train_objective\": [{\"annualised_returns\": -0.514216196502455, \"annualised_returns_over_hodl\": -0.38823391145140074, \"annualised_returns_over_uniform_hodl\": -0.38823391145140074, \"calmar\": -0.6813446719259842, \"daily_log_sharpe\": -0.5058471204233913, \"daily_returns\": 0.008240751341640307, \"fee_revenue_over_value\": 0.01381545414545823, \"jax_sharpe\": 0.12100482702982912, \"return\": -0.35489135978433006, \"returns_over_hodl\": -0.25795296837609816, \"returns_over_uniform_hodl\": -0.25795296837609816, \"sharpe\": 0.11124439815346349, \"sterling\": -1.4793482792240835, \"ulcer\": -0.18903697224046978}], \"train_return\": -0.35489135978433006, \"train_returns_over_hodl\": -0.25795296837609816, \"train_sharpe\": 0.1210048270298291, \"validation_return\": -0.3391427199567296, \"validation_returns_over_hodl\": -7.251007572151025e-10, \"validation_sharpe\": -2.2438531990034547}, {\"centeredness_margin\": 0.7978905379538548, \"continuous_test_metrics\": [{\"annualised_returns\": -0.6907101840533476, \"annualised_returns_over_hodl\": -0.10026285175611227, \"annualised_returns_over_uniform_hodl\": 0.0916559713395051, \"calmar\": -1.1872513189356582, \"daily_log_sharpe\": -1.470606413440749, \"daily_returns\": 0.02189511743136738, \"fee_revenue_over_value\": 0.0344227096197748, \"jax_sharpe\": -0.8934880081293006, \"return\": -0.3766227351561303, \"returns_over_hodl\": -0.0416577446548585, \"returns_over_uniform_hodl\": 0.0359495117529498, \"sharpe\": -1.0772090022083298, \"sterling\": -2.024607555575826, \"ulcer\": -0.15134577485592463}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -0.041116171745504665, \"optuna_trial_number\": 268, \"price_ratio\": 13.141116481256512, \"shift_exponent\": 0.003228840860198983, \"step\": 268, \"test_objective\": [{\"annualised_returns\": -0.6907101840533476, \"annualised_returns_over_hodl\": -0.10026285175611227, \"annualised_returns_over_uniform_hodl\": 0.0916559713395051, \"calmar\": -1.1872513189356582, \"daily_log_sharpe\": -1.470606413440749, \"daily_returns\": 0.02189511743136738, \"fee_revenue_over_value\": 0.0344227096197748, \"jax_sharpe\": -0.8934880081293006, \"return\": -0.3766227351561303, \"returns_over_hodl\": -0.0416577446548585, \"returns_over_uniform_hodl\": 0.0359495117529498, \"sharpe\": -1.0772090022083298, \"sterling\": -2.024607555575826, \"ulcer\": -0.15134577485592463}], \"train_objective\": [{\"annualised_returns\": -0.34959918302806436, \"annualised_returns_over_hodl\": -0.1809254221260892, \"annualised_returns_over_uniform_hodl\": -0.18092542212608909, \"calmar\": -0.47251809698721564, \"daily_log_sharpe\": -0.3535870491599306, \"daily_returns\": 0.008068514586262078, \"fee_revenue_over_value\": 0.002698169413754467, \"jax_sharpe\": 0.1380450668708727, \"return\": -0.2298448260522271, \"returns_over_hodl\": -0.11411609597001193, \"returns_over_uniform_hodl\": -0.11411609597001182, \"sharpe\": 0.16137473813842704, \"sterling\": -1.1217835407314054, \"ulcer\": -0.16788515003232754}], \"train_return\": -0.2298448260522271, \"train_returns_over_hodl\": -0.11411609597001193, \"train_sharpe\": 0.13804506687087267, \"validation_return\": -0.1991297692391294, \"validation_returns_over_hodl\": -0.04111617174550464, \"validation_sharpe\": -1.547373883469809}, {\"centeredness_margin\": 0.8101298814580846, \"continuous_test_metrics\": [{\"annualised_returns\": -0.66442437423554, \"annualised_returns_over_hodl\": -0.22417293651047332, \"annualised_returns_over_uniform_hodl\": 0.18443322998048783, \"calmar\": -1.1292345262869277, \"daily_log_sharpe\": -1.2548410153204659, \"daily_returns\": 0.026915285291949767, \"fee_revenue_over_value\": 0.00014133809736354422, \"jax_sharpe\": -0.6004521000376446, \"return\": -0.35580424101157526, \"returns_over_hodl\": -0.09717377484381917, \"returns_over_uniform_hodl\": 0.07054639242341376, \"sharpe\": -0.8258265749097589, \"sterling\": -1.8056837941230601, \"ulcer\": -0.16138138675261243}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -0.054977465202171394, \"optuna_trial_number\": 269, \"price_ratio\": 9.882122368648204, \"shift_exponent\": 0.001778922045218454, \"step\": 269, \"test_objective\": [{\"annualised_returns\": -0.66442437423554, \"annualised_returns_over_hodl\": -0.22417293651047332, \"annualised_returns_over_uniform_hodl\": 0.18443322998048783, \"calmar\": -1.1292345262869277, \"daily_log_sharpe\": -1.2548410153204659, \"daily_returns\": 0.026915285291949767, \"fee_revenue_over_value\": 0.00014133809736354422, \"jax_sharpe\": -0.6004521000376446, \"return\": -0.35580424101157526, \"returns_over_hodl\": -0.09717377484381917, \"returns_over_uniform_hodl\": 0.07054639242341376, \"sharpe\": -0.8258265749097589, \"sterling\": -1.8056837941230601, \"ulcer\": -0.16138138675261243}], \"train_objective\": [{\"annualised_returns\": -0.3867592057162551, \"annualised_returns_over_hodl\": -0.2277224572817632, \"annualised_returns_over_uniform_hodl\": -0.2277224572817632, \"calmar\": -0.5204617282451137, \"daily_log_sharpe\": -0.3885684467457367, \"daily_returns\": 0.008078874291194029, \"fee_revenue_over_value\": 0.0007787428380282336, \"jax_sharpe\": 0.12565901293519183, \"return\": -0.2568674892861662, \"returns_over_hodl\": -0.1451993675141865, \"returns_over_uniform_hodl\": -0.1451993675141865, \"sharpe\": 0.1452414574555028, \"sterling\": -1.2133885138227722, \"ulcer\": -0.17170233898666912}], \"train_return\": -0.2568674892861662, \"train_returns_over_hodl\": -0.1451993675141865, \"train_sharpe\": 0.12565901293519183, \"validation_return\": -0.2518378686228625, \"validation_returns_over_hodl\": -0.054977465202171394, \"validation_sharpe\": -1.8335011685296045}, {\"centeredness_margin\": 0.6017806984742382, \"continuous_test_metrics\": [{\"annualised_returns\": -0.5055402086813471, \"annualised_returns_over_hodl\": -0.04080313086490761, \"annualised_returns_over_uniform_hodl\": 0.7452239160483607, \"calmar\": -0.8721089237307523, \"daily_log_sharpe\": -0.7458566554151856, \"daily_returns\": 0.03101604249227535, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": -0.06323569690658339, \"return\": -0.24696532066777055, \"returns_over_hodl\": -0.016637669930864685, \"returns_over_uniform_hodl\": 0.2514186069693234, \"sharpe\": -0.2621550776695939, \"sterling\": -1.3015808145381023, \"ulcer\": -0.17639737503898206}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -0.05667442762248531, \"optuna_trial_number\": 270, \"price_ratio\": 9.0931668121521, \"shift_exponent\": 1.4150340474897877e-05, \"step\": 270, \"test_objective\": [{\"annualised_returns\": -0.5055402086813471, \"annualised_returns_over_hodl\": -0.04080313086490761, \"annualised_returns_over_uniform_hodl\": 0.7452239160483607, \"calmar\": -0.8721089237307523, \"daily_log_sharpe\": -0.7458566554151856, \"daily_returns\": 0.03101604249227535, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": -0.06323569690658339, \"return\": -0.24696532066777055, \"returns_over_hodl\": -0.016637669930864685, \"returns_over_uniform_hodl\": 0.2514186069693234, \"sharpe\": -0.2621550776695939, \"sterling\": -1.3015808145381023, \"ulcer\": -0.17639737503898206}], \"train_objective\": [{\"annualised_returns\": -0.438438445457456, \"annualised_returns_over_hodl\": -0.2928040967436265, \"annualised_returns_over_uniform_hodl\": -0.2928040967436264, \"calmar\": -0.589741214417648, \"daily_log_sharpe\": -0.4356260014109888, \"daily_returns\": 0.00808566949516825, \"fee_revenue_over_value\": 0.009128024878225519, \"jax_sharpe\": 0.10584413574571375, \"return\": -0.29554414557210396, \"returns_over_hodl\": -0.1896878399992601, \"returns_over_uniform_hodl\": -0.18968783999925998, \"sharpe\": 0.12440548719563575, \"sterling\": -1.3365608586056668, \"ulcer\": -0.17733831709788053}], \"train_return\": -0.29554414557210396, \"train_returns_over_hodl\": -0.1896878399992601, \"train_sharpe\": 0.10584413574571375, \"validation_return\": -0.3298605137337215, \"validation_returns_over_hodl\": -0.05667442762248531, \"validation_sharpe\": -2.1636084850424964}, {\"centeredness_margin\": 0.7787851678337303, \"continuous_test_metrics\": [{\"annualised_returns\": -0.7020966565513902, \"annualised_returns_over_hodl\": -0.19066716852863042, \"annualised_returns_over_uniform_hodl\": 0.051466769968824044, \"calmar\": -1.1838826291101878, \"daily_log_sharpe\": -1.4713818878730414, \"daily_returns\": 0.023335850931214062, \"fee_revenue_over_value\": 0.014380998449004572, \"jax_sharpe\": -0.8780348351124436, \"return\": -0.3859690498935887, \"returns_over_hodl\": -0.08166881659209546, \"returns_over_uniform_hodl\": 0.020417488474262724, \"sharpe\": -1.0686645375750126, \"sterling\": -2.0122511514821926, \"ulcer\": -0.15390848723351216}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -0.045775150250245256, \"optuna_trial_number\": 271, \"price_ratio\": 11.551589608113153, \"shift_exponent\": 0.002644290673894816, \"step\": 271, \"test_objective\": [{\"annualised_returns\": -0.7020966565513902, \"annualised_returns_over_hodl\": -0.19066716852863042, \"annualised_returns_over_uniform_hodl\": 0.051466769968824044, \"calmar\": -1.1838826291101878, \"daily_log_sharpe\": -1.4713818878730414, \"daily_returns\": 0.023335850931214062, \"fee_revenue_over_value\": 0.014380998449004572, \"jax_sharpe\": -0.8780348351124436, \"return\": -0.3859690498935887, \"returns_over_hodl\": -0.08166881659209546, \"returns_over_uniform_hodl\": 0.020417488474262724, \"sharpe\": -1.0686645375750126, \"sterling\": -2.0122511514821926, \"ulcer\": -0.15390848723351216}], \"train_objective\": [{\"annualised_returns\": -0.3597751428561934, \"annualised_returns_over_hodl\": -0.1937403968050747, \"annualised_returns_over_uniform_hodl\": -0.19374039680507438, \"calmar\": -0.4854453158433941, \"daily_log_sharpe\": -0.3622638663668811, \"daily_returns\": 0.008072963303995137, \"fee_revenue_over_value\": 0.0018499409423464337, \"jax_sharpe\": 0.13753061178066645, \"return\": -0.23718304315255756, \"returns_over_hodl\": -0.12255700324865515, \"returns_over_uniform_hodl\": -0.12255700324865493, \"sharpe\": 0.15897819485080292, \"sterling\": -1.1462666127587442, \"ulcer\": -0.16900241387597856}], \"train_return\": -0.23718304315255756, \"train_returns_over_hodl\": -0.12255700324865515, \"train_sharpe\": 0.13753061178066645, \"validation_return\": -0.2145236927468217, \"validation_returns_over_hodl\": -0.045775150250245256, \"validation_sharpe\": -1.637155701143052}, {\"centeredness_margin\": 0.5552728503785758, \"continuous_test_metrics\": [{\"annualised_returns\": -0.4782477214268386, \"annualised_returns_over_hodl\": 0.01214125141434308, \"annualised_returns_over_uniform_hodl\": 0.8415542998759047, \"calmar\": -0.825008232014189, \"daily_log_sharpe\": -0.6825165162359584, \"daily_returns\": 0.031016042513986514, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": 0.0022393252170819175, \"return\": -0.23049364816312035, \"returns_over_hodl\": 0.004872125873473188, \"returns_over_uniform_hodl\": 0.2787917917986107, \"sharpe\": -0.19248599408046704, \"sterling\": -1.231270892643956, \"ulcer\": -0.17641031602288737}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -6.883873471252855e-10, \"optuna_trial_number\": 272, \"price_ratio\": 6.069829618088707, \"shift_exponent\": 1.2730945636754133e-05, \"step\": 272, \"test_objective\": [{\"annualised_returns\": -0.4782477214268386, \"annualised_returns_over_hodl\": 0.01214125141434308, \"annualised_returns_over_uniform_hodl\": 0.8415542998759047, \"calmar\": -0.825008232014189, \"daily_log_sharpe\": -0.6825165162359584, \"daily_returns\": 0.031016042513986514, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": 0.0022393252170819175, \"return\": -0.23049364816312035, \"returns_over_hodl\": 0.004872125873473188, \"returns_over_uniform_hodl\": 0.2787917917986107, \"sharpe\": -0.19248599408046704, \"sterling\": -1.231270892643956, \"ulcer\": -0.17641031602288737}], \"train_objective\": [{\"annualised_returns\": -0.47551024870879144, \"annualised_returns_over_hodl\": -0.3394900337946899, \"annualised_returns_over_uniform_hodl\": -0.3394900337946901, \"calmar\": -0.6361821049366067, \"daily_log_sharpe\": -0.46739515834768464, \"daily_returns\": 0.008167299313755521, \"fee_revenue_over_value\": 0.009801544686965228, \"jax_sharpe\": 0.1050654148357138, \"return\": -0.3241561859096248, \"returns_over_hodl\": -0.22259931921005238, \"returns_over_uniform_hodl\": -0.2225993192100525, \"sharpe\": 0.11784439084819512, \"sterling\": -1.4133824277875418, \"ulcer\": -0.18228263679002815}], \"train_return\": -0.3241561859096248, \"train_returns_over_hodl\": -0.22259931921005238, \"train_sharpe\": 0.1050654148357138, \"validation_return\": -0.33914271999843204, \"validation_returns_over_hodl\": -6.883873471252855e-10, \"validation_sharpe\": -2.2438531985299686}, {\"centeredness_margin\": 0.8447240464021621, \"continuous_test_metrics\": [{\"annualised_returns\": -0.4773080234142483, \"annualised_returns_over_hodl\": 0.013964160944731852, \"annualised_returns_over_uniform_hodl\": 0.8448710173810072, \"calmar\": -0.8233871894415143, \"daily_log_sharpe\": -0.6795254789829267, \"daily_returns\": 0.031016042510347612, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": 0.008076937032970329, \"return\": -0.22993578824526228, \"returns_over_hodl\": 0.005600616192778629, \"returns_over_uniform_hodl\": 0.27971886235784327, \"sharpe\": -0.1888442918589426, \"sterling\": -1.2288515925712646, \"ulcer\": -0.1764103160153608}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -6.159391885418586e-10, \"optuna_trial_number\": 273, \"price_ratio\": 5.553974644531817, \"shift_exponent\": 4.713670019726867e-05, \"step\": 273, \"test_objective\": [{\"annualised_returns\": -0.4773080234142483, \"annualised_returns_over_hodl\": 0.013964160944731852, \"annualised_returns_over_uniform_hodl\": 0.8448710173810072, \"calmar\": -0.8233871894415143, \"daily_log_sharpe\": -0.6795254789829267, \"daily_returns\": 0.031016042510347612, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": 0.008076937032970329, \"return\": -0.22993578824526228, \"returns_over_hodl\": 0.005600616192778629, \"returns_over_uniform_hodl\": 0.27971886235784327, \"sharpe\": -0.1888442918589426, \"sterling\": -1.2288515925712646, \"ulcer\": -0.1764103160153608}], \"train_objective\": [{\"annualised_returns\": -0.4954424194469106, \"annualised_returns_over_hodl\": -0.3645913773161268, \"annualised_returns_over_uniform_hodl\": -0.3645913773161268, \"calmar\": -0.6592620289759162, \"daily_log_sharpe\": -0.49358338015317244, \"daily_returns\": 0.008175187206578989, \"fee_revenue_over_value\": 0.00029325409879172936, \"jax_sharpe\": 0.09233741776307004, \"return\": -0.3398680479255355, \"returns_over_hodl\": -0.24067215197554315, \"returns_over_uniform_hodl\": -0.24067215197554315, \"sharpe\": 0.09950588992999879, \"sterling\": -1.4570329443515448, \"ulcer\": -0.18469379935793726}], \"train_return\": -0.3398680479255355, \"train_returns_over_hodl\": -0.24067215197554315, \"train_sharpe\": 0.09233741776307004, \"validation_return\": -0.3391427199856937, \"validation_returns_over_hodl\": -6.159391885418586e-10, \"validation_sharpe\": -2.2438531986549037}, {\"centeredness_margin\": 0.8442843810920726, \"continuous_test_metrics\": [{\"annualised_returns\": -0.4786543685755007, \"annualised_returns_over_hodl\": 0.011352401321196082, \"annualised_returns_over_uniform_hodl\": 0.8401190156693856, \"calmar\": -0.8257097258230287, \"daily_log_sharpe\": -0.6835135635452673, \"daily_returns\": 0.03101604251304145, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": 0.0012727882397381358, \"return\": -0.2307352438166017, \"returns_over_hodl\": 0.004556634356979838, \"returns_over_uniform_hodl\": 0.2783902999358485, \"sharpe\": -0.19355988358571916, \"sterling\": -1.2323178263474597, \"ulcer\": -0.17641031602093343}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -6.917086903257541e-10, \"optuna_trial_number\": 274, \"price_ratio\": 6.0218125572188175, \"shift_exponent\": 4.001230635138788e-05, \"step\": 274, \"test_objective\": [{\"annualised_returns\": -0.4786543685755007, \"annualised_returns_over_hodl\": 0.011352401321196082, \"annualised_returns_over_uniform_hodl\": 0.8401190156693856, \"calmar\": -0.8257097258230287, \"daily_log_sharpe\": -0.6835135635452673, \"daily_returns\": 0.03101604251304145, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": 0.0012727882397381358, \"return\": -0.2307352438166017, \"returns_over_hodl\": 0.004556634356979838, \"returns_over_uniform_hodl\": 0.2783902999358485, \"sharpe\": -0.19355988358571916, \"sterling\": -1.2323178263474597, \"ulcer\": -0.17641031602093343}], \"train_objective\": [{\"annualised_returns\": -0.48619285715361893, \"annualised_returns_over_hodl\": -0.35294305041800234, \"annualised_returns_over_uniform_hodl\": -0.35294305041800234, \"calmar\": -0.6479247719552306, \"daily_log_sharpe\": -0.4837662795329922, \"daily_returns\": 0.008178078052526315, \"fee_revenue_over_value\": 0.00029344721817486436, \"jax_sharpe\": 0.09176054642860003, \"return\": -0.3325471698872545, \"returns_over_hodl\": -0.23225118924379173, \"returns_over_uniform_hodl\": -0.23225118924379173, \"sharpe\": 0.10308350908000301, \"sterling\": -1.4393089453106334, \"ulcer\": -0.18334048971747152}], \"train_return\": -0.3325471698872545, \"train_returns_over_hodl\": -0.23225118924379173, \"train_sharpe\": 0.0917605464286, \"validation_return\": -0.33914271998992895, \"validation_returns_over_hodl\": -6.917086903257541e-10, \"validation_sharpe\": -2.2438531985093273}, {\"centeredness_margin\": 0.8422100604827228, \"continuous_test_metrics\": [{\"annualised_returns\": -0.47890361179740537, \"annualised_returns_over_hodl\": 0.010868897218075713, \"annualised_returns_over_uniform_hodl\": 0.8392392975620326, \"calmar\": -0.8261396872275828, \"daily_log_sharpe\": -0.684213313303635, \"daily_returns\": 0.031016042514014727, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": 0.0005733875134367312, \"return\": -0.23088337888665267, \"returns_over_hodl\": 0.004363189800245859, \"returns_over_uniform_hodl\": 0.27814412404502376, \"sharpe\": -0.19441544358809612, \"sterling\": -1.23295951563711, \"ulcer\": -0.17641031602294482}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": 3.192834509180287e-05, \"optuna_trial_number\": 275, \"price_ratio\": 6.209102089282052, \"shift_exponent\": 1.1279668884996343e-05, \"step\": 275, \"test_objective\": [{\"annualised_returns\": -0.47890361179740537, \"annualised_returns_over_hodl\": 0.010868897218075713, \"annualised_returns_over_uniform_hodl\": 0.8392392975620326, \"calmar\": -0.8261396872275828, \"daily_log_sharpe\": -0.684213313303635, \"daily_returns\": 0.031016042514014727, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": 0.0005733875134367312, \"return\": -0.23088337888665267, \"returns_over_hodl\": 0.004363189800245859, \"returns_over_uniform_hodl\": 0.27814412404502376, \"sharpe\": -0.19441544358809612, \"sterling\": -1.23295951563711, \"ulcer\": -0.17641031602294482}], \"train_objective\": [{\"annualised_returns\": -0.48407482137533997, \"annualised_returns_over_hodl\": -0.35027572710636756, \"annualised_returns_over_uniform_hodl\": -0.35027572710636756, \"calmar\": -0.6453469149663132, \"daily_log_sharpe\": -0.48223490856704954, \"daily_returns\": 0.008152285285687148, \"fee_revenue_over_value\": 0.0002958529997266491, \"jax_sharpe\": 0.14127525423459739, \"return\": -0.3308780861905749, \"returns_over_hodl\": -0.23033129773181504, \"returns_over_uniform_hodl\": -0.23033129773181504, \"sharpe\": 0.10279348872362233, \"sterling\": -1.4355616479533553, \"ulcer\": -0.18299998896800534}], \"train_return\": -0.3308780861905749, \"train_returns_over_hodl\": -0.23033129773181504, \"train_sharpe\": 0.14127525423459739, \"validation_return\": -0.3391216194508476, \"validation_returns_over_hodl\": 3.192834509180287e-05, \"validation_sharpe\": -2.24366168714102}, {\"centeredness_margin\": 0.8639797580094176, \"continuous_test_metrics\": [{\"annualised_returns\": -0.48402634431328284, \"annualised_returns_over_hodl\": 0.0009313680223925758, \"annualised_returns_over_uniform_hodl\": 0.8211583222042895, \"calmar\": -0.8349767471880022, \"daily_log_sharpe\": -0.6914636999395776, \"daily_returns\": 0.031016042495361152, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": 0.005405924934527012, \"return\": -0.23393744222441293, \"returns_over_hodl\": 0.0003749928540550673, \"returns_over_uniform_hodl\": 0.27306877785894246, \"sharpe\": -0.2010339358568189, \"sterling\": -1.2461482507238857, \"ulcer\": -0.1764103159843794}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -7.989747752290555e-10, \"optuna_trial_number\": 276, \"price_ratio\": 3.97096363420228, \"shift_exponent\": 5.886393972007376e-05, \"step\": 276, \"test_objective\": [{\"annualised_returns\": -0.48402634431328284, \"annualised_returns_over_hodl\": 0.0009313680223925758, \"annualised_returns_over_uniform_hodl\": 0.8211583222042895, \"calmar\": -0.8349767471880022, \"daily_log_sharpe\": -0.6914636999395776, \"daily_returns\": 0.031016042495361152, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": 0.005405924934527012, \"return\": -0.23393744222441293, \"returns_over_hodl\": 0.0003749928540550673, \"returns_over_uniform_hodl\": 0.27306877785894246, \"sharpe\": -0.2010339358568189, \"sterling\": -1.2461482507238857, \"ulcer\": -0.1764103159843794}], \"train_objective\": [{\"annualised_returns\": -0.5375809016466008, \"annualised_returns_over_hodl\": -0.41765797658740034, \"annualised_returns_over_uniform_hodl\": -0.4176579765874001, \"calmar\": -0.7086583794004767, \"daily_log_sharpe\": -0.5413104271174344, \"daily_returns\": 0.008240361200376231, \"fee_revenue_over_value\": 0.00028954161053137777, \"jax_sharpe\": 0.09890884076166298, \"return\": -0.3739110365462892, \"returns_over_hodl\": -0.27983067052397137, \"returns_over_uniform_hodl\": -0.27983067052397126, \"sharpe\": 0.0820599567213204, \"sterling\": -1.5281489655304201, \"ulcer\": -0.19221984583672994}], \"train_return\": -0.3739110365462892, \"train_returns_over_hodl\": -0.27983067052397137, \"train_sharpe\": 0.098908840761663, \"validation_return\": -0.3391427199412761, \"validation_returns_over_hodl\": -7.989747752290555e-10, \"validation_sharpe\": -2.243853199249375}, {\"centeredness_margin\": 0.8595655684036217, \"continuous_test_metrics\": [{\"annualised_returns\": -0.4822447238184565, \"annualised_returns_over_hodl\": 0.0043875129179342665, \"annualised_returns_over_uniform_hodl\": 0.8274466529269964, \"calmar\": -0.831903331322778, \"daily_log_sharpe\": -0.6878769278884345, \"daily_returns\": 0.03101604250116073, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": 0.007373241029851826, \"return\": -0.23287323281578298, \"returns_over_hodl\": 0.0017647075653193234, \"returns_over_uniform_hodl\": 0.2748373172001215, \"sharpe\": -0.19727101330818517, \"sterling\": -1.241561378197765, \"ulcer\": -0.17641031599637083}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -7.323115447377404e-10, \"optuna_trial_number\": 277, \"price_ratio\": 4.501524933486749, \"shift_exponent\": 4.070892752855823e-05, \"step\": 277, \"test_objective\": [{\"annualised_returns\": -0.4822447238184565, \"annualised_returns_over_hodl\": 0.0043875129179342665, \"annualised_returns_over_uniform_hodl\": 0.8274466529269964, \"calmar\": -0.831903331322778, \"daily_log_sharpe\": -0.6878769278884345, \"daily_returns\": 0.03101604250116073, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": 0.007373241029851826, \"return\": -0.23287323281578298, \"returns_over_hodl\": 0.0017647075653193234, \"returns_over_uniform_hodl\": 0.2748373172001215, \"sharpe\": -0.19727101330818517, \"sterling\": -1.241561378197765, \"ulcer\": -0.17641031599637083}], \"train_objective\": [{\"annualised_returns\": -0.5232174352926926, \"annualised_returns_over_hodl\": -0.3995695150823758, \"annualised_returns_over_uniform_hodl\": -0.3995695150823756, \"calmar\": -0.6924059797011962, \"daily_log_sharpe\": -0.5252179953917642, \"daily_returns\": 0.00821582280115372, \"fee_revenue_over_value\": 0.00028444972963519497, \"jax_sharpe\": 0.15046878515150414, \"return\": -0.3621751806329149, \"returns_over_hodl\": -0.2663313054539673, \"returns_over_uniform_hodl\": -0.2663313054539672, \"sharpe\": 0.08717954430953744, \"sterling\": -1.5078998314009804, \"ulcer\": -0.1891305053553858}], \"train_return\": -0.3621751806329149, \"train_returns_over_hodl\": -0.2663313054539673, \"train_sharpe\": 0.15046878515150414, \"validation_return\": -0.3391427199614342, \"validation_returns_over_hodl\": -7.323115447377404e-10, \"validation_sharpe\": -2.2438531990498523}, {\"centeredness_margin\": 0.9164921167488949, \"continuous_test_metrics\": [{\"annualised_returns\": -0.48389344233570597, \"annualised_returns_over_hodl\": 0.0011891834001505064, \"annualised_returns_over_uniform_hodl\": 0.8216274072822465, \"calmar\": -0.8347474822615253, \"daily_log_sharpe\": -0.6911857763434857, \"daily_returns\": 0.031016042486604816, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": 0.00582714247966017, \"return\": -0.23385798064971108, \"returns_over_hodl\": 0.000478759161119946, \"returns_over_uniform_hodl\": 0.2732008298026978, \"sharpe\": -0.20073402666537554, \"sterling\": -1.2458060879743587, \"ulcer\": -0.17641031596628465}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -8.744813762007198e-10, \"optuna_trial_number\": 278, \"price_ratio\": 2.9913611528108603, \"shift_exponent\": 0.0005369860649930745, \"step\": 278, \"test_objective\": [{\"annualised_returns\": -0.48389344233570597, \"annualised_returns_over_hodl\": 0.0011891834001505064, \"annualised_returns_over_uniform_hodl\": 0.8216274072822465, \"calmar\": -0.8347474822615253, \"daily_log_sharpe\": -0.6911857763434857, \"daily_returns\": 0.031016042486604816, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": 0.00582714247966017, \"return\": -0.23385798064971108, \"returns_over_hodl\": 0.000478759161119946, \"returns_over_uniform_hodl\": 0.2732008298026978, \"sharpe\": -0.20073402666537554, \"sterling\": -1.2458060879743587, \"ulcer\": -0.17641031596628465}], \"train_objective\": [{\"annualised_returns\": -0.5608464253403711, \"annualised_returns_over_hodl\": -0.44695713873668863, \"annualised_returns_over_uniform_hodl\": -0.44695713873668863, \"calmar\": -0.7311310039728275, \"daily_log_sharpe\": -0.5671079995365849, \"daily_returns\": 0.008299737352675461, \"fee_revenue_over_value\": 0.00024301863145063466, \"jax_sharpe\": 0.11089260038529787, \"return\": -0.39322904010338444, \"returns_over_hodl\": -0.3020515280707716, \"returns_over_uniform_hodl\": -0.3020515280707716, \"sharpe\": 0.07508606591998863, \"sterling\": -1.557681796523701, \"ulcer\": -0.1976221560889571}], \"train_return\": -0.39322904010338444, \"train_returns_over_hodl\": -0.3020515280707716, \"train_sharpe\": 0.11089260038529787, \"validation_return\": -0.33914271989459166, \"validation_returns_over_hodl\": -8.744813762007198e-10, \"validation_sharpe\": -2.243853199392276}, {\"centeredness_margin\": 0.8802692448381051, \"continuous_test_metrics\": [{\"annualised_returns\": -0.48450645859508745, \"annualised_returns_over_hodl\": 8.5149665096651e-12, \"annualised_returns_over_uniform_hodl\": 0.8194637315788169, \"calmar\": -0.8358049768018686, \"daily_log_sharpe\": -0.6924636106426261, \"daily_returns\": 0.031016042491279497, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": 0.005019267568638411, \"return\": -0.23422460257504463, \"returns_over_hodl\": 3.4292568784621835e-12, \"returns_over_uniform_hodl\": 0.27259156503484916, \"sharpe\": -0.20210854580076237, \"sterling\": -1.2473843294012794, \"ulcer\": -0.17641031597594511}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -8.584525312826941e-10, \"optuna_trial_number\": 279, \"price_ratio\": 3.5591092477429958, \"shift_exponent\": 1.9555203273011648e-05, \"step\": 279, \"test_objective\": [{\"annualised_returns\": -0.48450645859508745, \"annualised_returns_over_hodl\": 8.5149665096651e-12, \"annualised_returns_over_uniform_hodl\": 0.8194637315788169, \"calmar\": -0.8358049768018686, \"daily_log_sharpe\": -0.6924636106426261, \"daily_returns\": 0.031016042491279497, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": 0.005019267568638411, \"return\": -0.23422460257504463, \"returns_over_hodl\": 3.4292568784621835e-12, \"returns_over_uniform_hodl\": 0.27259156503484916, \"sharpe\": -0.20210854580076237, \"sterling\": -1.2473843294012794, \"ulcer\": -0.17641031597594511}], \"train_objective\": [{\"annualised_returns\": -0.548794100367715, \"annualised_returns_over_hodl\": -0.43177918580093233, \"annualised_returns_over_uniform_hodl\": -0.43177918580093233, \"calmar\": -0.7202849614785471, \"daily_log_sharpe\": -0.5523126752255535, \"daily_returns\": 0.008284465159362861, \"fee_revenue_over_value\": 0.00028252108200466357, \"jax_sharpe\": 0.10748644156169458, \"return\": -0.3831727787443733, \"returns_over_hodl\": -0.29048414480305784, \"returns_over_uniform_hodl\": -0.29048414480305784, \"sharpe\": 0.08161926852282454, \"sterling\": -1.5387244689780264, \"ulcer\": -0.19542178995977028}], \"train_return\": -0.3831727787443733, \"train_returns_over_hodl\": -0.29048414480305784, \"train_sharpe\": 0.10748644156169458, \"validation_return\": -0.3391427199355723, \"validation_returns_over_hodl\": -8.584525312826941e-10, \"validation_sharpe\": -2.2438531994811677}, {\"centeredness_margin\": 0.8990103096926962, \"continuous_test_metrics\": [{\"annualised_returns\": -0.4794850719418977, \"annualised_returns_over_hodl\": 0.00974092934123294, \"annualised_returns_over_uniform_hodl\": 0.8371870009583184, \"calmar\": -0.8271427452780398, \"daily_log_sharpe\": -0.6856398305681084, \"daily_returns\": 0.03101604251455458, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": -0.001261656803325306, \"return\": -0.23122912816617514, \"returns_over_hodl\": 0.003911687587745849, \"returns_over_uniform_hodl\": 0.2775695461489245, \"sharpe\": -0.19602968344628438, \"sterling\": -1.2344565142419066, \"ulcer\": -0.1764103160240588}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -0.0011755794790607732, \"optuna_trial_number\": 280, \"price_ratio\": 6.2935813886469525, \"shift_exponent\": 1.224936388956621e-05, \"step\": 280, \"test_objective\": [{\"annualised_returns\": -0.4794850719418977, \"annualised_returns_over_hodl\": 0.00974092934123294, \"annualised_returns_over_uniform_hodl\": 0.8371870009583184, \"calmar\": -0.8271427452780398, \"daily_log_sharpe\": -0.6856398305681084, \"daily_returns\": 0.03101604251455458, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": -0.001261656803325306, \"return\": -0.23122912816617514, \"returns_over_hodl\": 0.003911687587745849, \"returns_over_uniform_hodl\": 0.2775695461489245, \"sharpe\": -0.19602968344628438, \"sterling\": -1.2344565142419066, \"ulcer\": -0.1764103160240588}], \"train_objective\": [{\"annualised_returns\": -0.4824767106992095, \"annualised_returns_over_hodl\": -0.3482631653240168, \"annualised_returns_over_uniform_hodl\": -0.3482631653240168, \"calmar\": -0.6433444201624815, \"daily_log_sharpe\": -0.48072644952298105, \"daily_returns\": 0.00816951939779535, \"fee_revenue_over_value\": 0.00025559333290279076, \"jax_sharpe\": 0.14075051135925165, \"return\": -0.3296205023396199, \"returns_over_hodl\": -0.22888474081807197, \"returns_over_uniform_hodl\": -0.22888474081807197, \"sharpe\": 0.10324653267221275, \"sterling\": -1.432369920652963, \"ulcer\": -0.18277885950780282}], \"train_return\": -0.3296205023396199, \"train_returns_over_hodl\": -0.22888474081807197, \"train_sharpe\": 0.14075051135925162, \"validation_return\": -0.3390844992497283, \"validation_returns_over_hodl\": -0.0011755794790607732, \"validation_sharpe\": -2.243330018952427}, {\"centeredness_margin\": 0.8099695225194588, \"continuous_test_metrics\": [{\"annualised_returns\": -0.47817967144055074, \"annualised_returns_over_hodl\": 0.012273260887430082, \"annualised_returns_over_uniform_hodl\": 0.8417944861673721, \"calmar\": -0.824890841184193, \"daily_log_sharpe\": -0.6817672910726913, \"daily_returns\": 0.031016042511898087, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": 0.004565820182924709, \"return\": -0.2304532295506324, \"returns_over_hodl\": 0.00492490719691574, \"returns_over_uniform_hodl\": 0.27885896082166317, \"sharpe\": -0.19140859825933385, \"sterling\": -1.2310956945403928, \"ulcer\": -0.17641031601856536}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -7.031821791514403e-10, \"optuna_trial_number\": 281, \"price_ratio\": 5.8383633300639834, \"shift_exponent\": 2.139054065320904e-05, \"step\": 281, \"test_objective\": [{\"annualised_returns\": -0.47817967144055074, \"annualised_returns_over_hodl\": 0.012273260887430082, \"annualised_returns_over_uniform_hodl\": 0.8417944861673721, \"calmar\": -0.824890841184193, \"daily_log_sharpe\": -0.6817672910726913, \"daily_returns\": 0.031016042511898087, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": 0.004565820182924709, \"return\": -0.2304532295506324, \"returns_over_hodl\": 0.00492490719691574, \"returns_over_uniform_hodl\": 0.27885896082166317, \"sharpe\": -0.19140859825933385, \"sterling\": -1.2310956945403928, \"ulcer\": -0.17641031601856536}], \"train_objective\": [{\"annualised_returns\": -0.4904009396828859, \"annualised_returns_over_hodl\": -0.35824244939072336, \"annualised_returns_over_uniform_hodl\": -0.35824244939072336, \"calmar\": -0.6531261189307246, \"daily_log_sharpe\": -0.48811334003300094, \"daily_returns\": 0.008131324288436659, \"fee_revenue_over_value\": 0.000327887787608496, \"jax_sharpe\": 0.09165737542905454, \"return\": -0.335871323116533, \"returns_over_hodl\": -0.2360748522404995, \"returns_over_uniform_hodl\": -0.2360748522404995, \"sharpe\": 0.10147196787184791, \"sterling\": -1.447448574736885, \"ulcer\": -0.18395053257531305}], \"train_return\": -0.335871323116533, \"train_returns_over_hodl\": -0.2360748522404995, \"train_sharpe\": 0.09165737542905454, \"validation_return\": -0.33914271999207035, \"validation_returns_over_hodl\": -7.031821791514403e-10, \"validation_sharpe\": -2.2438531986110393}, {\"centeredness_margin\": 0.9354069516980528, \"continuous_test_metrics\": [{\"annualised_returns\": -0.4828498951276644, \"annualised_returns_over_hodl\": 0.00321354807356955, \"annualised_returns_over_uniform_hodl\": 0.8253106664207548, \"calmar\": -0.8329472927637098, \"daily_log_sharpe\": -0.6890612010135223, \"daily_returns\": 0.031016042500505794, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": 0.006249813885908175, \"return\": -0.23323447213167925, \"returns_over_hodl\": 0.0012929774617198575, \"returns_over_uniform_hodl\": 0.2742369974354564, \"sharpe\": -0.19848649207175195, \"sterling\": -1.2431194224651374, \"ulcer\": -0.1764103159950142}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -7.540005286799101e-10, \"optuna_trial_number\": 282, \"price_ratio\": 4.435602547883993, \"shift_exponent\": 2.427422960331554e-05, \"step\": 282, \"test_objective\": [{\"annualised_returns\": -0.4828498951276644, \"annualised_returns_over_hodl\": 0.00321354807356955, \"annualised_returns_over_uniform_hodl\": 0.8253106664207548, \"calmar\": -0.8329472927637098, \"daily_log_sharpe\": -0.6890612010135223, \"daily_returns\": 0.031016042500505794, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": 0.006249813885908175, \"return\": -0.23323447213167925, \"returns_over_hodl\": 0.0012929774617198575, \"returns_over_uniform_hodl\": 0.2742369974354564, \"sharpe\": -0.19848649207175195, \"sterling\": -1.2431194224651374, \"ulcer\": -0.1764103159950142}], \"train_objective\": [{\"annualised_returns\": -0.5249357301968647, \"annualised_returns_over_hodl\": -0.4017334294511339, \"annualised_returns_over_uniform_hodl\": -0.4017334294511339, \"calmar\": -0.6943374578168819, \"daily_log_sharpe\": -0.5268432461522753, \"daily_returns\": 0.008210694607262162, \"fee_revenue_over_value\": 0.00022014120211615108, \"jax_sharpe\": 0.09559544909700561, \"return\": -0.36357174883475063, \"returns_over_hodl\": -0.26793773144802324, \"returns_over_uniform_hodl\": -0.26793773144802324, \"sharpe\": 0.08719829396031659, \"sterling\": -1.5103414586142025, \"ulcer\": -0.18953197941897126}], \"train_return\": -0.36357174883475063, \"train_returns_over_hodl\": -0.26793773144802324, \"train_sharpe\": 0.0955954490970056, \"validation_return\": -0.33914271996252987, \"validation_returns_over_hodl\": -7.540005286799101e-10, \"validation_sharpe\": -2.243853199110114}, {\"centeredness_margin\": 0.35759643633462684, \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -Infinity, \"optuna_trial_number\": 283, \"price_ratio\": 1.2104848640468606, \"shift_exponent\": 1.07273190147014, \"step\": 283, \"test_objective\": -Infinity, \"train_objective\": -Infinity, \"train_return\": -Infinity, \"train_returns_over_hodl\": -Infinity, \"train_sharpe\": -Infinity, \"validation_return\": -Infinity, \"validation_returns_over_hodl\": -Infinity, \"validation_sharpe\": -Infinity}, {\"centeredness_margin\": 0.9589544581178904, \"continuous_test_metrics\": [{\"annualised_returns\": -0.5837016395106238, \"annualised_returns_over_hodl\": -0.13628059181877128, \"annualised_returns_over_uniform_hodl\": 0.46934870679822116, \"calmar\": -1.0056764105484848, \"daily_log_sharpe\": -0.9565444301004643, \"daily_returns\": 0.02942103581590274, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": -0.28355559631206856, \"return\": -0.29738101973489073, \"returns_over_hodl\": -0.05729703934180408, \"returns_over_uniform_hodl\": 0.16763608588821222, \"sharpe\": -0.49552514718035545, \"sterling\": -1.5362466345494807, \"ulcer\": -0.1693928600906099}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -0.0584446037493116, \"optuna_trial_number\": 284, \"price_ratio\": 19.79491748785731, \"shift_exponent\": 1.4713660156056705e-05, \"step\": 284, \"test_objective\": [{\"annualised_returns\": -0.5837016395106238, \"annualised_returns_over_hodl\": -0.13628059181877128, \"annualised_returns_over_uniform_hodl\": 0.46934870679822116, \"calmar\": -1.0056764105484848, \"daily_log_sharpe\": -0.9565444301004643, \"daily_returns\": 0.02942103581590274, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": -0.28355559631206856, \"return\": -0.29738101973489073, \"returns_over_hodl\": -0.05729703934180408, \"returns_over_uniform_hodl\": 0.16763608588821222, \"sharpe\": -0.49552514718035545, \"sterling\": -1.5362466345494807, \"ulcer\": -0.1693928600906099}], \"train_objective\": [{\"annualised_returns\": -0.40474393706415834, \"annualised_returns_over_hodl\": -0.2503713160355723, \"annualised_returns_over_uniform_hodl\": -0.2503713160355723, \"calmar\": -0.5460243408023623, \"daily_log_sharpe\": -0.41156412269892023, \"daily_returns\": 0.00805057000418768, \"fee_revenue_over_value\": 0.00017814004255361498, \"jax_sharpe\": 0.09850387841582041, \"return\": -0.2701764455615715, \"returns_over_hodl\": -0.1605082176558852, \"returns_over_uniform_hodl\": -0.1605082176558852, \"sharpe\": 0.12228729310129738, \"sterling\": -1.2662424934313943, \"ulcer\": -0.1726970108463709}], \"train_return\": -0.2701764455615715, \"train_returns_over_hodl\": -0.1605082176558852, \"train_sharpe\": 0.09850387841582042, \"validation_return\": -0.27851272030344476, \"validation_returns_over_hodl\": -0.0584446037493116, \"validation_sharpe\": -1.9488431906722066}, {\"centeredness_margin\": 0.6578681561088392, \"continuous_test_metrics\": [{\"annualised_returns\": -0.6864888912630034, \"annualised_returns_over_hodl\": -0.275408545962449, \"annualised_returns_over_uniform_hodl\": 0.10655526398917536, \"calmar\": -1.1623157342407577, \"daily_log_sharpe\": -1.3485148855441, \"daily_returns\": 0.02694592671326915, \"fee_revenue_over_value\": 0.014506936778245164, \"jax_sharpe\": -0.7128373057785036, \"return\": -0.37321008073051376, \"returns_over_hodl\": -0.12167702531093072, \"returns_over_uniform_hodl\": 0.04162077678839271, \"sharpe\": -0.9270850947592081, \"sterling\": -1.8866224526017819, \"ulcer\": -0.1585704861874595}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -0.0584932582328207, \"optuna_trial_number\": 285, \"price_ratio\": 5.68324841199483, \"shift_exponent\": 0.0029178137690691955, \"step\": 285, \"test_objective\": [{\"annualised_returns\": -0.6864888912630034, \"annualised_returns_over_hodl\": -0.275408545962449, \"annualised_returns_over_uniform_hodl\": 0.10655526398917536, \"calmar\": -1.1623157342407577, \"daily_log_sharpe\": -1.3485148855441, \"daily_returns\": 0.02694592671326915, \"fee_revenue_over_value\": 0.014506936778245164, \"jax_sharpe\": -0.7128373057785036, \"return\": -0.37321008073051376, \"returns_over_hodl\": -0.12167702531093072, \"returns_over_uniform_hodl\": 0.04162077678839271, \"sharpe\": -0.9270850947592081, \"sterling\": -1.8866224526017819, \"ulcer\": -0.1585704861874595}], \"train_objective\": [{\"annualised_returns\": -0.3888691468911627, \"annualised_returns_over_hodl\": -0.23037958675036085, \"annualised_returns_over_uniform_hodl\": -0.23037958675036097, \"calmar\": -0.5208965984304647, \"daily_log_sharpe\": -0.3855797307922698, \"daily_returns\": 0.008194080143598851, \"fee_revenue_over_value\": 0.00400164902537531, \"jax_sharpe\": 0.1928264109670907, \"return\": -0.25842085960255534, \"returns_over_hodl\": -0.14698615777001656, \"returns_over_uniform_hodl\": -0.14698615777001667, \"sharpe\": 0.15888280793385162, \"sterling\": -1.2093081659801717, \"ulcer\": -0.1728906347969606}], \"train_return\": -0.25842085960255534, \"train_returns_over_hodl\": -0.14698615777001656, \"train_sharpe\": 0.1928264109670907, \"validation_return\": -0.24958615405098417, \"validation_returns_over_hodl\": -0.0584932582328207, \"validation_sharpe\": -1.8285791790872592}, {\"centeredness_margin\": 0.9021335232933602, \"continuous_test_metrics\": [{\"annualised_returns\": -0.504363593396874, \"annualised_returns_over_hodl\": -0.03852062830891001, \"annualised_returns_over_uniform_hodl\": 0.7493768465201671, \"calmar\": -0.8700598813127829, \"daily_log_sharpe\": -0.743288843595819, \"daily_returns\": 0.03101604249161691, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": -0.06108876425685927, \"return\": -0.24624415912019515, \"returns_over_hodl\": -0.015695929603810388, \"returns_over_uniform_hodl\": 0.25261705772336684, \"sharpe\": -0.25941028984209413, \"sterling\": -1.2985075361893936, \"ulcer\": -0.17641031603026802}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -0.054454227696781654, \"optuna_trial_number\": 286, \"price_ratio\": 8.672871862650675, \"shift_exponent\": 7.083072295049438e-05, \"step\": 286, \"test_objective\": [{\"annualised_returns\": -0.504363593396874, \"annualised_returns_over_hodl\": -0.03852062830891001, \"annualised_returns_over_uniform_hodl\": 0.7493768465201671, \"calmar\": -0.8700598813127829, \"daily_log_sharpe\": -0.743288843595819, \"daily_returns\": 0.03101604249161691, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": -0.06108876425685927, \"return\": -0.24624415912019515, \"returns_over_hodl\": -0.015695929603810388, \"returns_over_uniform_hodl\": 0.25261705772336684, \"sharpe\": -0.25941028984209413, \"sterling\": -1.2985075361893936, \"ulcer\": -0.17641031603026802}], \"train_objective\": [{\"annualised_returns\": -0.4503078160455207, \"annualised_returns_over_hodl\": -0.30775164823858037, \"annualised_returns_over_uniform_hodl\": -0.30775164823858037, \"calmar\": -0.6032892971847542, \"daily_log_sharpe\": -0.4521616822113677, \"daily_returns\": 0.008098845511274684, \"fee_revenue_over_value\": 0.00024748013546331836, \"jax_sharpe\": 0.13546471278867048, \"return\": -0.3046218689981761, \"returns_over_hodl\": -0.20012964359991803, \"returns_over_uniform_hodl\": -0.20012964359991803, \"sharpe\": 0.11051317592411171, \"sterling\": -1.3659709906152986, \"ulcer\": -0.17851700014036073}], \"train_return\": -0.3046218689981761, \"train_returns_over_hodl\": -0.20012964359991803, \"train_sharpe\": 0.13546471278867048, \"validation_return\": -0.33116340150226053, \"validation_returns_over_hodl\": -0.054454227696781654, \"validation_sharpe\": -2.1738053248512323}, {\"centeredness_margin\": 0.986225652641314, \"continuous_test_metrics\": [{\"annualised_returns\": -0.6031869303445119, \"annualised_returns_over_hodl\": -0.12901717521775213, \"annualised_returns_over_uniform_hodl\": 0.40057426614295655, \"calmar\": -1.0353114438235846, \"daily_log_sharpe\": -1.0286357141171878, \"daily_returns\": 0.028095387523240867, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": -0.35858500938257365, \"return\": -0.3108156825652961, \"returns_over_hodl\": -0.054112266896272576, \"returns_over_uniform_hodl\": 0.14530990688774814, \"sharpe\": -0.5785518237281619, \"sterling\": -1.604279885895609, \"ulcer\": -0.16672020648984576}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -0.054378162431223176, \"optuna_trial_number\": 287, \"price_ratio\": 24.266851695161325, \"shift_exponent\": 2.8807555097352738e-05, \"step\": 287, \"test_objective\": [{\"annualised_returns\": -0.6031869303445119, \"annualised_returns_over_hodl\": -0.12901717521775213, \"annualised_returns_over_uniform_hodl\": 0.40057426614295655, \"calmar\": -1.0353114438235846, \"daily_log_sharpe\": -1.0286357141171878, \"daily_returns\": 0.028095387523240867, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": -0.35858500938257365, \"return\": -0.3108156825652961, \"returns_over_hodl\": -0.054112266896272576, \"returns_over_uniform_hodl\": 0.14530990688774814, \"sharpe\": -0.5785518237281619, \"sterling\": -1.604279885895609, \"ulcer\": -0.16672020648984576}], \"train_objective\": [{\"annualised_returns\": -0.3965142981065619, \"annualised_returns_over_hodl\": -0.2400074175296779, \"annualised_returns_over_uniform_hodl\": -0.24000741752967814, \"calmar\": -0.535610118908336, \"daily_log_sharpe\": -0.4039045909816805, \"daily_returns\": 0.008009802876522806, \"fee_revenue_over_value\": 7.131137104866962e-05, \"jax_sharpe\": 0.100277723726153, \"return\": -0.264067070203273, \"returns_over_hodl\": -0.1534808061982027, \"returns_over_uniform_hodl\": -0.1534808061982028, \"sharpe\": 0.12526049886170892, \"sterling\": -1.247335317417333, \"ulcer\": -0.1717141588064256}], \"train_return\": -0.264067070203273, \"train_returns_over_hodl\": -0.1534808061982027, \"train_sharpe\": 0.10027772372615298, \"validation_return\": -0.2660616425148543, \"validation_returns_over_hodl\": -0.054378162431223176, \"validation_sharpe\": -1.895029845535905}, {\"centeredness_margin\": 0.8732503061683304, \"continuous_test_metrics\": [{\"annualised_returns\": -0.4826588536207711, \"annualised_returns_over_hodl\": 0.003584146774107566, \"annualised_returns_over_uniform_hodl\": 0.8259849582694379, \"calmar\": -0.8326177332744951, \"daily_log_sharpe\": -0.6940372044488555, \"daily_returns\": 0.031016042513291958, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": -0.011936173253216923, \"return\": -0.2331204082327284, \"returns_over_hodl\": 0.0014419294102721025, \"returns_over_uniform_hodl\": 0.27442655269691785, \"sharpe\": -0.2057198947415131, \"sterling\": -1.2426275761984569, \"ulcer\": -0.17641031602144086}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": 2.5258769953184768e-05, \"optuna_trial_number\": 288, \"price_ratio\": 5.347477907323994, \"shift_exponent\": 0.00038910931427129324, \"step\": 288, \"test_objective\": [{\"annualised_returns\": -0.4826588536207711, \"annualised_returns_over_hodl\": 0.003584146774107566, \"annualised_returns_over_uniform_hodl\": 0.8259849582694379, \"calmar\": -0.8326177332744951, \"daily_log_sharpe\": -0.6940372044488555, \"daily_returns\": 0.031016042513291958, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": -0.011936173253216923, \"return\": -0.2331204082327284, \"returns_over_hodl\": 0.0014419294102721025, \"returns_over_uniform_hodl\": 0.27442655269691785, \"sharpe\": -0.2057198947415131, \"sterling\": -1.2426275761984569, \"ulcer\": -0.17641031602144086}], \"train_objective\": [{\"annualised_returns\": -0.485666719845517, \"annualised_returns_over_hodl\": -0.3522804656206101, \"annualised_returns_over_uniform_hodl\": -0.3522804656206099, \"calmar\": -0.6463613461424708, \"daily_log_sharpe\": -0.4808998602111604, \"daily_returns\": 0.008183880890685832, \"fee_revenue_over_value\": 0.00027404140662003564, \"jax_sharpe\": 0.09947287597039578, \"return\": -0.3321323039833568, \"returns_over_hodl\": -0.23177398278069694, \"returns_over_uniform_hodl\": -0.23177398278069683, \"sharpe\": 0.10906244326693967, \"sterling\": -1.4346291084304998, \"ulcer\": -0.18362692901151542}], \"train_return\": -0.3321323039833568, \"train_returns_over_hodl\": -0.23177398278069694, \"train_sharpe\": 0.0994728759703958, \"validation_return\": -0.3391260270836637, \"validation_returns_over_hodl\": 2.5258769953184768e-05, \"validation_sharpe\": -2.2436960148241454}, {\"centeredness_margin\": 0.806050850038852, \"continuous_test_metrics\": [{\"annualised_returns\": -0.493848863072636, \"annualised_returns_over_hodl\": -0.018123224235592983, \"annualised_returns_over_uniform_hodl\": 0.7864891843782584, \"calmar\": -0.8519212575078146, \"daily_log_sharpe\": -0.7199744268160798, \"daily_returns\": 0.031016042518826618, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": -0.037806141387424774, \"return\": -0.23984447199448045, \"returns_over_hodl\": -0.007338796441192286, \"returns_over_uniform_hodl\": 0.26325227515452587, \"sharpe\": -0.23436318947177634, \"sterling\": -1.2714368250010375, \"ulcer\": -0.17641031603289992}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -0.02768403011442233, \"optuna_trial_number\": 289, \"price_ratio\": 5.994380432924245, \"shift_exponent\": 0.0004431429332381018, \"step\": 289, \"test_objective\": [{\"annualised_returns\": -0.493848863072636, \"annualised_returns_over_hodl\": -0.018123224235592983, \"annualised_returns_over_uniform_hodl\": 0.7864891843782584, \"calmar\": -0.8519212575078146, \"daily_log_sharpe\": -0.7199744268160798, \"daily_returns\": 0.031016042518826618, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": -0.037806141387424774, \"return\": -0.23984447199448045, \"returns_over_hodl\": -0.007338796441192286, \"returns_over_uniform_hodl\": 0.26325227515452587, \"sharpe\": -0.23436318947177634, \"sterling\": -1.2714368250010375, \"ulcer\": -0.17641031603289992}], \"train_objective\": [{\"annualised_returns\": -0.47004555282664806, \"annualised_returns_over_hodl\": -0.3326081336554578, \"annualised_returns_over_uniform_hodl\": -0.3326081336554578, \"calmar\": -0.6268935878709747, \"daily_log_sharpe\": -0.4668741371822108, \"daily_returns\": 0.008135058636131615, \"fee_revenue_over_value\": 0.00037302349000847335, \"jax_sharpe\": 0.1505535549326212, \"return\": -0.31988974869018294, \"returns_over_hodl\": -0.21769177825193087, \"returns_over_uniform_hodl\": -0.21769177825193087, \"sharpe\": 0.1127951150434889, \"sterling\": -1.402956183912372, \"ulcer\": -0.1815492254096335}], \"train_return\": -0.31988974869018294, \"train_returns_over_hodl\": -0.21769177825193087, \"train_sharpe\": 0.15055355493262124, \"validation_return\": -0.33771873994175494, \"validation_returns_over_hodl\": -0.02768403011442233, \"validation_sharpe\": -2.2309789700143665}, {\"centeredness_margin\": 0.7874339865090063, \"continuous_test_metrics\": [{\"annualised_returns\": -0.5439528158679295, \"annualised_returns_over_hodl\": -0.11531930682843938, \"annualised_returns_over_uniform_hodl\": 0.6096444373590766, \"calmar\": -0.9406637148712531, \"daily_log_sharpe\": -0.8392117400710872, \"daily_returns\": 0.03101604251883548, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": -0.1577261522526103, \"return\": -0.271095988459654, \"returns_over_hodl\": -0.04814908704636933, \"returns_over_uniform_hodl\": 0.21131744363360982, \"sharpe\": -0.3636178430640457, \"sterling\": -1.4077292526590335, \"ulcer\": -0.17444167786077253}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -0.0660294545139769, \"optuna_trial_number\": 290, \"price_ratio\": 12.648571284100509, \"shift_exponent\": 9.192799042728159e-05, \"step\": 290, \"test_objective\": [{\"annualised_returns\": -0.5439528158679295, \"annualised_returns_over_hodl\": -0.11531930682843938, \"annualised_returns_over_uniform_hodl\": 0.6096444373590766, \"calmar\": -0.9406637148712531, \"daily_log_sharpe\": -0.8392117400710872, \"daily_returns\": 0.03101604251883548, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": -0.1577261522526103, \"return\": -0.271095988459654, \"returns_over_hodl\": -0.04814908704636933, \"returns_over_uniform_hodl\": 0.21131744363360982, \"sharpe\": -0.3636178430640457, \"sterling\": -1.4077292526590335, \"ulcer\": -0.17444167786077253}], \"train_objective\": [{\"annualised_returns\": -0.4243674418231753, \"annualised_returns_over_hodl\": -0.27508394470619735, \"annualised_returns_over_uniform_hodl\": -0.27508394470619735, \"calmar\": -0.570815308750851, \"daily_log_sharpe\": -0.42910405175106275, \"daily_returns\": 0.008117820369078052, \"fee_revenue_over_value\": 0.000835509339041887, \"jax_sharpe\": 0.0967690666928747, \"return\": -0.2848796898134308, \"returns_over_hodl\": -0.17742087092415626, \"returns_over_uniform_hodl\": -0.17742087092415626, \"sharpe\": 0.11696998711521449, \"sterling\": -1.3100669307644344, \"ulcer\": -0.17515981517673906}], \"train_return\": -0.2848796898134308, \"train_returns_over_hodl\": -0.17742087092415626, \"train_sharpe\": 0.09676906669287468, \"validation_return\": -0.307040685344109, \"validation_returns_over_hodl\": -0.0660294545139769, \"validation_sharpe\": -2.058281234846709}, {\"centeredness_margin\": 0.7849105108997017, \"continuous_test_metrics\": [{\"annualised_returns\": -0.5655475477047075, \"annualised_returns_over_hodl\": -0.15721067912067854, \"annualised_returns_over_uniform_hodl\": 0.5334246048794973, \"calmar\": -0.9766770570522484, \"daily_log_sharpe\": -0.8970808756741607, \"daily_returns\": 0.031076080037665446, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": -0.2194682423959886, \"return\": -0.2851981777237381, \"returns_over_hodl\": -0.0665646555490268, \"returns_over_uniform_hodl\": 0.18788194653309476, \"sharpe\": -0.4270943277454801, \"sterling\": -1.4739412852248546, \"ulcer\": -0.1723214921059751}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -0.06386515609483878, \"optuna_trial_number\": 291, \"price_ratio\": 14.633914973081454, \"shift_exponent\": 0.00017195186516624888, \"step\": 291, \"test_objective\": [{\"annualised_returns\": -0.5655475477047075, \"annualised_returns_over_hodl\": -0.15721067912067854, \"annualised_returns_over_uniform_hodl\": 0.5334246048794973, \"calmar\": -0.9766770570522484, \"daily_log_sharpe\": -0.8970808756741607, \"daily_returns\": 0.031076080037665446, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": -0.2194682423959886, \"return\": -0.2851981777237381, \"returns_over_hodl\": -0.0665646555490268, \"returns_over_uniform_hodl\": 0.18788194653309476, \"sharpe\": -0.4270943277454801, \"sterling\": -1.4739412852248546, \"ulcer\": -0.1723214921059751}], \"train_objective\": [{\"annualised_returns\": -0.4138153796059346, \"annualised_returns_over_hodl\": -0.2617953299308893, \"annualised_returns_over_uniform_hodl\": -0.2617953299308893, \"calmar\": -0.5573821431000638, \"daily_log_sharpe\": -0.41889550091730976, \"daily_returns\": 0.008101800865811512, \"fee_revenue_over_value\": 0.000978558488917278, \"jax_sharpe\": 0.09929110513048393, \"return\": -0.27694932542493034, \"returns_over_hodl\": -0.16829883629722064, \"returns_over_uniform_hodl\": -0.16829883629722064, \"sharpe\": 0.1215510157952427, \"sterling\": -1.2856017184523132, \"ulcer\": -0.17393835484039444}], \"train_return\": -0.27694932542493034, \"train_returns_over_hodl\": -0.16829883629722064, \"train_sharpe\": 0.09929110513048392, \"validation_return\": -0.2934670781597133, \"validation_returns_over_hodl\": -0.06386515609483878, \"validation_sharpe\": -2.0061044841742954}, {\"centeredness_margin\": 0.8072371276439604, \"continuous_test_metrics\": [{\"annualised_returns\": -0.4925031335915979, \"annualised_returns_over_hodl\": -0.0155126588847182, \"annualised_returns_over_uniform_hodl\": 0.7912390130115945, \"calmar\": -0.8495997828867285, \"daily_log_sharpe\": -0.7172008832305963, \"daily_returns\": 0.03101604251409071, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": -0.03664761024893438, \"return\": -0.23903115854546497, \"returns_over_hodl\": -0.006276718042329654, \"returns_over_uniform_hodl\": 0.2646038670683257, \"sharpe\": -0.2313855173079509, \"sterling\": -1.2679721758768572, \"ulcer\": -0.1764103160230961}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -0.004889973785796697, \"optuna_trial_number\": 292, \"price_ratio\": 4.816570110873592, \"shift_exponent\": 0.0007481738953860484, \"step\": 292, \"test_objective\": [{\"annualised_returns\": -0.4925031335915979, \"annualised_returns_over_hodl\": -0.0155126588847182, \"annualised_returns_over_uniform_hodl\": 0.7912390130115945, \"calmar\": -0.8495997828867285, \"daily_log_sharpe\": -0.7172008832305963, \"daily_returns\": 0.03101604251409071, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": -0.03664761024893438, \"return\": -0.23903115854546497, \"returns_over_hodl\": -0.006276718042329654, \"returns_over_uniform_hodl\": 0.2646038670683257, \"sharpe\": -0.2313855173079509, \"sterling\": -1.2679721758768572, \"ulcer\": -0.1764103160230961}], \"train_objective\": [{\"annualised_returns\": -0.48369962391304766, \"annualised_returns_over_hodl\": -0.3498032266189337, \"annualised_returns_over_uniform_hodl\": -0.3498032266189338, \"calmar\": -0.6428279352339415, \"daily_log_sharpe\": -0.4769599615044544, \"daily_returns\": 0.00821140955062452, \"fee_revenue_over_value\": 0.0003544535265668568, \"jax_sharpe\": 0.16460431919041252, \"return\": -0.33058269881687197, \"returns_over_hodl\": -0.22999152345168394, \"returns_over_uniform_hodl\": -0.22999152345168405, \"sharpe\": 0.11532280496729401, \"sterling\": -1.427027858411868, \"ulcer\": -0.18372495509577993}], \"train_return\": -0.33058269881687197, \"train_returns_over_hodl\": -0.22999152345168394, \"train_sharpe\": 0.16460431919041252, \"validation_return\": -0.3390421946418448, \"validation_returns_over_hodl\": -0.004889973785796697, \"validation_sharpe\": -2.242982155033056}, {\"centeredness_margin\": 0.8556082142892611, \"continuous_test_metrics\": [{\"annualised_returns\": -0.48402998694035304, \"annualised_returns_over_hodl\": 0.0009243019061615509, \"annualised_returns_over_uniform_hodl\": 0.8211454653451635, \"calmar\": -0.8349830309551564, \"daily_log_sharpe\": -0.6914697897822952, \"daily_returns\": 0.03101604249109848, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": 0.005610544721029271, \"return\": -0.2339396203090559, \"returns_over_hodl\": 0.00037214863764267747, \"returns_over_uniform_hodl\": 0.27306515824390387, \"sharpe\": -0.20103910817727177, \"sterling\": -1.2461576289218665, \"ulcer\": -0.17641031597557472}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -8.402578632882296e-10, \"optuna_trial_number\": 293, \"price_ratio\": 3.397520155910415, \"shift_exponent\": 0.00030297270081653955, \"step\": 293, \"test_objective\": [{\"annualised_returns\": -0.48402998694035304, \"annualised_returns_over_hodl\": 0.0009243019061615509, \"annualised_returns_over_uniform_hodl\": 0.8211454653451635, \"calmar\": -0.8349830309551564, \"daily_log_sharpe\": -0.6914697897822952, \"daily_returns\": 0.03101604249109848, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": 0.005610544721029271, \"return\": -0.2339396203090559, \"returns_over_hodl\": 0.00037214863764267747, \"returns_over_uniform_hodl\": 0.27306515824390387, \"sharpe\": -0.20103910817727177, \"sterling\": -1.2461576289218665, \"ulcer\": -0.17641031597557472}], \"train_objective\": [{\"annualised_returns\": -0.5486044344124545, \"annualised_returns_over_hodl\": -0.43154033222297206, \"annualised_returns_over_uniform_hodl\": -0.43154033222297217, \"calmar\": -0.7191586874253656, \"daily_log_sharpe\": -0.5520192070507722, \"daily_returns\": 0.008301123629257197, \"fee_revenue_over_value\": 0.000296843012271059, \"jax_sharpe\": 0.10873600359601515, \"return\": -0.3830153739996828, \"returns_over_hodl\": -0.2903030873558624, \"returns_over_uniform_hodl\": -0.2903030873558625, \"sharpe\": 0.08196303682302505, \"sterling\": -1.5391871189978403, \"ulcer\": -0.19515736821544044}], \"train_return\": -0.3830153739996828, \"train_returns_over_hodl\": -0.2903030873558624, \"train_sharpe\": 0.10873600359601515, \"validation_return\": -0.3391427199215553, \"validation_returns_over_hodl\": -8.402578632882296e-10, \"validation_sharpe\": -2.2438531993534045}, {\"centeredness_margin\": 0.5835307449493479, \"continuous_test_metrics\": [{\"annualised_returns\": -0.7003274318914157, \"annualised_returns_over_hodl\": -0.014660229940216207, \"annualised_returns_over_uniform_hodl\": 0.05771134888840712, \"calmar\": -1.2048318754332052, \"daily_log_sharpe\": -1.54632490916359, \"daily_returns\": 0.0195358195169493, \"fee_revenue_over_value\": 0.04854682113910453, \"jax_sharpe\": -1.0120423004944978, \"return\": -0.38450298973175445, \"returns_over_hodl\": -0.005930281515634128, \"returns_over_uniform_hodl\": 0.02285383704599475, \"sharpe\": -1.1640626680650628, \"sterling\": -2.137591926635461, \"ulcer\": -0.14831507320737614}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -0.025178925650236827, \"optuna_trial_number\": 294, \"price_ratio\": 20.4824101707187, \"shift_exponent\": 2.8808541136804178, \"step\": 294, \"test_objective\": [{\"annualised_returns\": -0.7003274318914157, \"annualised_returns_over_hodl\": -0.014660229940216207, \"annualised_returns_over_uniform_hodl\": 0.05771134888840712, \"calmar\": -1.2048318754332052, \"daily_log_sharpe\": -1.54632490916359, \"daily_returns\": 0.0195358195169493, \"fee_revenue_over_value\": 0.04854682113910453, \"jax_sharpe\": -1.0120423004944978, \"return\": -0.38450298973175445, \"returns_over_hodl\": -0.005930281515634128, \"returns_over_uniform_hodl\": 0.02285383704599475, \"sharpe\": -1.1640626680650628, \"sterling\": -2.137591926635461, \"ulcer\": -0.14831507320737614}], \"train_objective\": [{\"annualised_returns\": -0.30977990968816405, \"annualised_returns_over_hodl\": -0.13077949110778309, \"annualised_returns_over_uniform_hodl\": -0.13077949110778309, \"calmar\": -0.4232823310804096, \"daily_log_sharpe\": -0.3091638888588355, \"daily_returns\": 0.008052367837567045, \"fee_revenue_over_value\": 0.024948916580707754, \"jax_sharpe\": 0.14444803206523502, \"return\": -0.20155330716111408, \"returns_over_hodl\": -0.08157330192796752, \"returns_over_uniform_hodl\": -0.08157330192796752, \"sharpe\": 0.18622207145741868, \"sterling\": -1.0163393194334824, \"ulcer\": -0.1642887544708887}], \"train_return\": -0.20155330716111408, \"train_returns_over_hodl\": -0.08157330192796752, \"train_sharpe\": 0.14444803206523502, \"validation_return\": -0.17527740951425252, \"validation_returns_over_hodl\": -0.025178925650236827, \"validation_sharpe\": -1.383426234347543}, {\"centeredness_margin\": 0.7272879702836812, \"continuous_test_metrics\": [{\"annualised_returns\": -0.4845064594697247, \"annualised_returns_over_hodl\": 8.979039733958416e-12, \"annualised_returns_over_uniform_hodl\": 0.8194637284917352, \"calmar\": -0.8358049782031398, \"daily_log_sharpe\": -0.6924636128385677, \"daily_returns\": 0.0310160424497392, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": 0.00501926493990866, \"return\": -0.23422460309831794, \"returns_over_hodl\": 3.61621843580906e-12, \"returns_over_uniform_hodl\": 0.2725915641652559, \"sharpe\": -0.20210854837639383, \"sterling\": -1.2473843323319629, \"ulcer\": -0.17641031589007655}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -1.1966441171296083e-09, \"optuna_trial_number\": 295, \"price_ratio\": 1.8695262239296329, \"shift_exponent\": 0.0004037958026572084, \"step\": 295, \"test_objective\": [{\"annualised_returns\": -0.4845064594697247, \"annualised_returns_over_hodl\": 8.979039733958416e-12, \"annualised_returns_over_uniform_hodl\": 0.8194637284917352, \"calmar\": -0.8358049782031398, \"daily_log_sharpe\": -0.6924636128385677, \"daily_returns\": 0.0310160424497392, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": 0.00501926493990866, \"return\": -0.23422460309831794, \"returns_over_hodl\": 3.61621843580906e-12, \"returns_over_uniform_hodl\": 0.2725915641652559, \"sharpe\": -0.20210854837639383, \"sterling\": -1.2473843323319629, \"ulcer\": -0.17641031589007655}], \"train_objective\": [{\"annualised_returns\": -0.6382443198954653, \"annualised_returns_over_hodl\": -0.5444272620157289, \"annualised_returns_over_uniform_hodl\": -0.5444272620157291, \"calmar\": -0.8128734901090786, \"daily_log_sharpe\": -0.6919253242000254, \"daily_returns\": 0.008656857489044491, \"fee_revenue_over_value\": 0.00036565527386044067, \"jax_sharpe\": 0.0774805212945728, \"return\": -0.4606079749689944, \"returns_over_hodl\": -0.37955527781793175, \"returns_over_uniform_hodl\": -0.37955527781793186, \"sharpe\": -0.021517549698550773, \"sterling\": -1.718872872756836, \"ulcer\": -0.20716925599381705}], \"train_return\": -0.4606079749689944, \"train_returns_over_hodl\": -0.37955527781793175, \"train_sharpe\": 0.0774805212945728, \"validation_return\": -0.3391427197006681, \"validation_returns_over_hodl\": -1.1966441171296083e-09, \"validation_sharpe\": -2.2438532000079134}, {\"centeredness_margin\": 0.697132348456329, \"continuous_test_metrics\": [{\"annualised_returns\": -0.4845064594154853, \"annualised_returns_over_hodl\": 9.086065233532281e-12, \"annualised_returns_over_uniform_hodl\": 0.8194637286831747, \"calmar\": -0.8358049781162397, \"daily_log_sharpe\": -0.6924636127024276, \"daily_returns\": 0.03101604245231376, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": 0.005019265102832431, \"return\": -0.2342246030658679, \"returns_over_hodl\": 3.659295089164516e-12, \"returns_over_uniform_hodl\": 0.27259156421918207, \"sharpe\": -0.20210854821673993, \"sterling\": -1.2473843321502494, \"ulcer\": -0.17641031589538606}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -1.1862612003810113e-09, \"optuna_trial_number\": 296, \"price_ratio\": 1.9435595354250004, \"shift_exponent\": 0.0004499646862692158, \"step\": 296, \"test_objective\": [{\"annualised_returns\": -0.4845064594154853, \"annualised_returns_over_hodl\": 9.086065233532281e-12, \"annualised_returns_over_uniform_hodl\": 0.8194637286831747, \"calmar\": -0.8358049781162397, \"daily_log_sharpe\": -0.6924636127024276, \"daily_returns\": 0.03101604245231376, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": 0.005019265102832431, \"return\": -0.2342246030658679, \"returns_over_hodl\": 3.659295089164516e-12, \"returns_over_uniform_hodl\": 0.27259156421918207, \"sharpe\": -0.20210854821673993, \"sterling\": -1.2473843321502494, \"ulcer\": -0.17641031589538606}], \"train_objective\": [{\"annualised_returns\": -0.6337385693051285, \"annualised_returns_over_hodl\": -0.5387529982902202, \"annualised_returns_over_uniform_hodl\": -0.5387529982902204, \"calmar\": -0.8082458760584755, \"daily_log_sharpe\": -0.683123954949436, \"daily_returns\": 0.008532343716933706, \"fee_revenue_over_value\": 0.00039191687762726694, \"jax_sharpe\": 0.08313386552660786, \"return\": -0.4565391026086415, \"returns_over_hodl\": -0.3748749891520615, \"returns_over_uniform_hodl\": -0.37487498915206163, \"sharpe\": -0.01333301239488639, \"sterling\": -1.7093784572892512, \"ulcer\": -0.20679159917384834}], \"train_return\": -0.4565391026086415, \"train_returns_over_hodl\": -0.3748749891520615, \"train_sharpe\": 0.08313386552660786, \"validation_return\": -0.33914271971560683, \"validation_returns_over_hodl\": -1.1862612003810113e-09, \"validation_sharpe\": -2.243853199988938}, {\"centeredness_margin\": 0.6181011743808479, \"continuous_test_metrics\": [{\"annualised_returns\": -0.48450645930833613, \"annualised_returns_over_hodl\": 9.418466007105053e-12, \"annualised_returns_over_uniform_hodl\": 0.8194637290613647, \"calmar\": -0.8358049779446363, \"daily_log_sharpe\": -0.6924636124335201, \"daily_returns\": 0.03101604245738988, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": 0.005019265424603584, \"return\": -0.2342246030017633, \"returns_over_hodl\": 3.79318798593431e-12, \"returns_over_uniform_hodl\": 0.2725915643257135, \"sharpe\": -0.20210854790139546, \"sterling\": -1.247384331791425, \"ulcer\": -0.17641031590588607}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -1.1485703499403144e-09, \"optuna_trial_number\": 297, \"price_ratio\": 2.133999133744774, \"shift_exponent\": 0.00016334433871541433, \"step\": 297, \"test_objective\": [{\"annualised_returns\": -0.48450645930833613, \"annualised_returns_over_hodl\": 9.418466007105053e-12, \"annualised_returns_over_uniform_hodl\": 0.8194637290613647, \"calmar\": -0.8358049779446363, \"daily_log_sharpe\": -0.6924636124335201, \"daily_returns\": 0.03101604245738988, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": 0.005019265424603584, \"return\": -0.2342246030017633, \"returns_over_hodl\": 3.79318798593431e-12, \"returns_over_uniform_hodl\": 0.2725915643257135, \"sharpe\": -0.20210854790139546, \"sterling\": -1.247384331791425, \"ulcer\": -0.17641031590588607}], \"train_objective\": [{\"annualised_returns\": -0.6245075824752842, \"annualised_returns_over_hodl\": -0.5271280641823338, \"annualised_returns_over_uniform_hodl\": -0.5271280641823338, \"calmar\": -0.7990254801110108, \"daily_log_sharpe\": -0.6655643206504457, \"daily_returns\": 0.008566961546556647, \"fee_revenue_over_value\": 0.0005533000194008123, \"jax_sharpe\": 0.08567017669273498, \"return\": -0.44826406089920257, \"returns_over_hodl\": -0.36535648365661477, \"returns_over_uniform_hodl\": -0.36535648365661477, \"sharpe\": 0.0026791686785764437, \"sterling\": -1.6892668257118135, \"ulcer\": -0.20613393188729406}], \"train_return\": -0.44826406089920257, \"train_returns_over_hodl\": -0.36535648365661477, \"train_sharpe\": 0.08567017669273498, \"validation_return\": -0.33914271975334065, \"validation_returns_over_hodl\": -1.1485703499403144e-09, \"validation_sharpe\": -2.243853200009935}, {\"centeredness_margin\": 0.8775239459287922, \"continuous_test_metrics\": [{\"annualised_returns\": -0.5137505136852769, \"annualised_returns_over_hodl\": -0.0567302071441681, \"annualised_returns_over_uniform_hodl\": 0.7162451782369457, \"calmar\": -0.8864369974254629, \"daily_log_sharpe\": -0.7652537528939541, \"daily_returns\": 0.031016042489600007, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": -0.0838826575882093, \"return\": -0.2520262823399908, \"returns_over_hodl\": -0.023246607305069, \"returns_over_uniform_hodl\": 0.24300812896664836, \"sharpe\": -0.283132349258778, \"sterling\": -1.32301518881851, \"ulcer\": -0.1763118322106732}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -0.05535303262295377, \"optuna_trial_number\": 298, \"price_ratio\": 7.3616219744034055, \"shift_exponent\": 0.00043732873607464157, \"step\": 298, \"test_objective\": [{\"annualised_returns\": -0.5137505136852769, \"annualised_returns_over_hodl\": -0.0567302071441681, \"annualised_returns_over_uniform_hodl\": 0.7162451782369457, \"calmar\": -0.8864369974254629, \"daily_log_sharpe\": -0.7652537528939541, \"daily_returns\": 0.031016042489600007, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": -0.0838826575882093, \"return\": -0.2520262823399908, \"returns_over_hodl\": -0.023246607305069, \"returns_over_uniform_hodl\": 0.24300812896664836, \"sharpe\": -0.283132349258778, \"sterling\": -1.32301518881851, \"ulcer\": -0.1763118322106732}], \"train_objective\": [{\"annualised_returns\": -0.4507255167394929, \"annualised_returns_over_hodl\": -0.30827767466822964, \"annualised_returns_over_uniform_hodl\": -0.30827767466822975, \"calmar\": -0.6030604790815908, \"daily_log_sharpe\": -0.4505468701466387, \"daily_returns\": 0.00814115332312443, \"fee_revenue_over_value\": 0.00027615848578058837, \"jax_sharpe\": 0.09979500736067708, \"return\": -0.30494272268901834, \"returns_over_hodl\": -0.20049871093839822, \"returns_over_uniform_hodl\": -0.20049871093839833, \"sharpe\": 0.11532984278785552, \"sterling\": -1.3637360085472694, \"ulcer\": -0.1788813359820888}], \"train_return\": -0.30494272268901834, \"train_returns_over_hodl\": -0.20049871093839822, \"train_sharpe\": 0.09979500736067708, \"validation_return\": -0.33117997350992645, \"validation_returns_over_hodl\": -0.05535303262295377, \"validation_sharpe\": -2.174194013524311}, {\"centeredness_margin\": 0.8441533342992725, \"continuous_test_metrics\": [{\"annualised_returns\": -0.48727072411837224, \"annualised_returns_over_hodl\": -0.005362367906364018, \"annualised_returns_over_uniform_hodl\": 0.8097070994193465, \"calmar\": -0.8405735220149091, \"daily_log_sharpe\": -0.7049050824132691, \"daily_returns\": 0.03101604251531491, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": -0.0238897681389424, \"return\": -0.23588104847304092, \"returns_over_hodl\": -0.002163096650944052, \"returns_over_uniform_hodl\": 0.26983882698030337, \"sharpe\": -0.2178569548245265, \"sterling\": -1.2545010711408955, \"ulcer\": -0.17641031602562302}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -0.01046551038498933, \"optuna_trial_number\": 299, \"price_ratio\": 5.478656635179979, \"shift_exponent\": 0.00046081740725351783, \"step\": 299, \"test_objective\": [{\"annualised_returns\": -0.48727072411837224, \"annualised_returns_over_hodl\": -0.005362367906364018, \"annualised_returns_over_uniform_hodl\": 0.8097070994193465, \"calmar\": -0.8405735220149091, \"daily_log_sharpe\": -0.7049050824132691, \"daily_returns\": 0.03101604251531491, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": -0.0238897681389424, \"return\": -0.23588104847304092, \"returns_over_hodl\": -0.002163096650944052, \"returns_over_uniform_hodl\": 0.26983882698030337, \"sharpe\": -0.2178569548245265, \"sterling\": -1.2545010711408955, \"ulcer\": -0.17641031602562302}], \"train_objective\": [{\"annualised_returns\": -0.47964782792713956, \"annualised_returns_over_hodl\": -0.3447006452565057, \"annualised_returns_over_uniform_hodl\": -0.3447006452565059, \"calmar\": -0.6387606466537913, \"daily_log_sharpe\": -0.4752610329114031, \"daily_returns\": 0.00818783688330391, \"fee_revenue_over_value\": 0.0003054634612867263, \"jax_sharpe\": 0.10093436105423233, \"return\": -0.32739813471142565, \"returns_over_hodl\": -0.22632842518551832, \"returns_over_uniform_hodl\": -0.22632842518551854, \"sharpe\": 0.11128313646417771, \"sterling\": -1.4219756714609817, \"ulcer\": -0.18288876925805958}], \"train_return\": -0.32739813471142565, \"train_returns_over_hodl\": -0.22632842518551832, \"train_sharpe\": 0.10093436105423233, \"validation_return\": -0.33892850538032915, \"validation_returns_over_hodl\": -0.01046551038498933, \"validation_sharpe\": -2.2419537575386084}]" \ No newline at end of file diff --git a/results/run_121c8f61d369929f8544d6b51b304bf44992034ba79b905bf4b1ac4e67ac3155.json b/results/run_121c8f61d369929f8544d6b51b304bf44992034ba79b905bf4b1ac4e67ac3155.json new file mode 100644 index 0000000..ed60cc2 --- /dev/null +++ b/results/run_121c8f61d369929f8544d6b51b304bf44992034ba79b905bf4b1ac4e67ac3155.json @@ -0,0 +1 @@ +"[{\"alphabetic\": true, \"arb_fees\": 0.0, \"arb_frequency\": 4, \"arb_quality\": 1.0, \"bout_offset\": 10080, \"checkpoint_fused\": \"scan\", \"chunk_period\": 1440, \"do_arb\": true, \"do_trades\": false, \"endDateString\": \"2025-10-05 00:00:00\", \"endTestDateString\": \"2026-03-01 00:00:00\", \"ensemble_init_method\": \"gaussian\", \"ensemble_init_scale\": 0.5, \"ensemble_init_seed\": 42, \"evaluation_starts\": [525600, 530329, 532521, 535058, 539787, 542689, 544517, 544583, 546843, 549246, 553975, 557830, 558704, 563434, 565112, 567593, 568163, 568213, 569167, 571491, 572892, 574200, 577621, 578220, 580626, 582351, 587080, 591809, 593868, 596538, 601268, 605786, 605997, 606071, 606566, 608057, 610726, 611893, 615288, 615456], \"fees\": 0.0025, \"freq\": \"minute\", \"gas_cost\": 1.0, \"initial_arc_length_speed\": 0.0001, \"initial_centeredness_margin\": 0.2, \"initial_daily_price_shift_base\": 0.999991935483871, \"initial_k_per_day\": 20, \"initial_log_amplitude\": 0.0, \"initial_memory_length\": 10.0, \"initial_memory_length_delta\": 0.0, \"initial_pool_value\": 1000000.0, \"initial_pre_exp_scaling\": 0.5, \"initial_price_ratio\": 4.0, \"initial_raw_exponents\": 0.0, \"initial_raw_width\": 0.0, \"initial_shift_exponent\": 1.0, \"initial_weights_logits\": 1.0, \"learnable_bounds_settings\": {\"freeze_bounds\": false, \"max_weights_per_asset\": null, \"min_weights_per_asset\": null}, \"max_memory_days\": 365, \"maximum_change\": 0.0003, \"minimum_weight\": null, \"n_ensemble_members\": 1, \"noise_arrays_path\": \"results/mm_noise/_sim_arrays/0x9d1fcf346ea1b0_2025-01-01_2026-03-01_mm.npz\", \"noise_model\": \"mm_observed\", \"noise_trader_ratio\": 0.0, \"numeraire\": null, \"optimisation_settings\": {\"base_lr\": 0.1, \"batch_size\": 8, \"bfgs_settings\": {\"compute_dtype\": \"float32\", \"maxiter\": 100, \"n_evaluation_points\": 20, \"tol\": 1e-06}, \"checkpoint_interval\": 10, \"clip_norm\": 10.0, \"cma_es_settings\": {\"compute_dtype\": \"float32\", \"memory_budget\": null, \"n_evaluation_points\": 20, \"n_generations\": 300, \"population_size\": null, \"sigma0\": 0.5, \"tol\": 1e-08}, \"decay_lr_plateau\": 100, \"decay_lr_ratio\": 0.8, \"early_stopping\": true, \"early_stopping_metric\": \"daily_log_sharpe\", \"early_stopping_patience\": 200, \"force_scalar\": false, \"include_flipped_training_data\": false, \"initial_random_key\": 0, \"lr_decay_ratio\": 1000, \"lr_schedule_type\": \"constant\", \"max_mc_version\": 9, \"method\": \"optuna\", \"min_lr\": 1e-06, \"n_cycles\": 5, \"n_iterations\": 1000, \"n_parameter_sets\": 1, \"noise_scale\": 0.1, \"optimiser\": \"adamw\", \"optuna_settings\": {\"early_stopping\": {\"enabled\": false, \"min_improvement\": 0.001, \"patience\": 100}, \"expand_around\": false, \"make_scalar\": true, \"min_train_returns_over_hodl\": -0.5, \"multi_objective\": false, \"n_jobs\": 4, \"n_startup_trials\": 10, \"n_trials\": 300, \"overfitting_penalty\": 1.0, \"parameter_config\": {\"centeredness_margin\": {\"high\": 0.99, \"low\": 0.01, \"scalar\": true}, \"k_per_day\": {\"high\": 1000, \"log_scale\": true, \"low\": 0.1, \"scalar\": true}, \"log_amplitude\": {\"high\": 10, \"log_scale\": false, \"low\": -10, \"scalar\": true}, \"log_k\": {\"high\": 10.0, \"log_scale\": false, \"low\": -10.0, \"scalar\": true}, \"logit_lamb\": {\"high\": 4.602848117654388, \"log_scale\": false, \"low\": -5.955817303419269, \"scalar\": true}, \"memory_days_1\": {\"high\": 200, \"log_scale\": true, \"low\": 0.5, \"scalar\": true}, \"memory_days_2\": {\"high\": 200, \"log_scale\": true, \"low\": 0.5, \"scalar\": true}, \"memory_length\": {\"high\": 200, \"log_scale\": true, \"low\": 1, \"scalar\": true}, \"memory_length_delta\": {\"high\": 100, \"log_scale\": true, \"low\": 0.1, \"scalar\": true}, \"price_ratio\": {\"high\": 200.0, \"log_scale\": true, \"low\": 1.01, \"scalar\": true}, \"raw_exponents\": {\"high\": 10, \"log_scale\": false, \"low\": 0, \"scalar\": true}, \"raw_pre_exp_scaling\": {\"high\": 10, \"log_scale\": false, \"low\": -10, \"scalar\": true}, \"raw_width\": {\"high\": 10, \"log_scale\": false, \"low\": -10, \"scalar\": true}, \"shift_exponent\": {\"high\": 125.0, \"log_scale\": true, \"low\": 1e-05, \"scalar\": true}, \"weights_logits\": {\"high\": 10, \"log_scale\": false, \"low\": -10, \"scalar\": true}}, \"storage\": {\"type\": \"sqlite\", \"url\": null}, \"study_name\": null, \"timeout\": 7200}, \"parameter_init_method\": \"gaussian\", \"sample_method\": \"uniform\", \"swa_freq\": 10, \"swa_start_frac\": 0.75, \"track_checkpoints\": false, \"train_on_hessian_trace\": false, \"training_data_kind\": \"historic\", \"use_gradient_clipping\": true, \"use_plateau_decay\": false, \"use_swa\": false, \"val_fraction\": 0.2, \"warmup_steps\": 100, \"weight_decay\": 0.01}, \"price_noise_sigma\": 0.0, \"protocol_fee_split\": 0.25, \"reclamm_arc_length_speed\": null, \"reclamm_centeredness_scaling\": false, \"reclamm_interpolation_method\": \"geometric\", \"reclamm_learn_arc_length_speed\": false, \"reclamm_learn_fees\": false, \"reclamm_use_shift_exponent\": true, \"return_val\": \"daily_log_sharpe_excess\", \"rule\": \"reclamm\", \"startDateString\": \"2025-01-01 00:00:00\", \"ste_max_change\": false, \"ste_min_max_weight\": false, \"ste_temperature\": 10.0, \"subsidary_pools\": [], \"tokens\": [\"AAVE\", \"ETH\"], \"training_method\": \"optuna\", \"turnover_penalty\": 0.0, \"use_alt_lamb\": false, \"use_fused_reserves\": true, \"use_pre_exp_scaling\": true, \"weight_calculation_method\": \"auto\", \"weight_interpolation_method\": \"linear\", \"weight_interpolation_period\": 1440}, {\"centeredness_margin\": 0.13235250461126638, \"continuous_test_metrics\": [{\"annualised_returns\": -0.8646966234510169, \"annualised_returns_over_hodl\": 0.24393414602935004, \"annualised_returns_over_uniform_hodl\": 0.18852496398140128, \"calmar\": -1.3217273694020815, \"daily_log_sharpe\": -2.325795747619819, \"daily_returns\": 0.007317130713028265, \"fee_revenue_over_value\": 0.047207076261797876, \"jax_sharpe\": -1.2094237284411964, \"return\": -0.553167112468475, \"returns_over_hodl\": 0.09188900869861327, \"returns_over_uniform_hodl\": 0.07203430451500359, \"sharpe\": -1.9243094702173051, \"sterling\": -2.2579362825386378, \"ulcer\": -0.16346035954295146}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": 0.6476250394611855, \"optuna_trial_number\": 0, \"price_ratio\": 4.935282761756684, \"shift_exponent\": 0.17671509769829652, \"step\": 0, \"test_objective\": [{\"annualised_returns\": -0.8646966234510169, \"annualised_returns_over_hodl\": 0.24393414602935004, \"annualised_returns_over_uniform_hodl\": 0.18852496398140128, \"calmar\": -1.3217273694020815, \"daily_log_sharpe\": -2.325795747619819, \"daily_returns\": 0.007317130713028265, \"fee_revenue_over_value\": 0.047207076261797876, \"jax_sharpe\": -1.2094237284411964, \"return\": -0.553167112468475, \"returns_over_hodl\": 0.09188900869861327, \"returns_over_uniform_hodl\": 0.07203430451500359, \"sharpe\": -1.9243094702173051, \"sterling\": -2.2579362825386378, \"ulcer\": -0.16346035954295146}], \"train_objective\": [{\"annualised_returns\": 0.43341278627997326, \"annualised_returns_over_hodl\": 0.18541194521839488, \"annualised_returns_over_uniform_hodl\": 0.18541194521839488, \"calmar\": 0.6798245508925048, \"daily_log_sharpe\": 0.44434058150972844, \"daily_returns\": 0.020517239393085085, \"fee_revenue_over_value\": 0.080954371905103, \"jax_sharpe\": 0.8492681693185304, \"return\": 0.244332213993401, \"returns_over_hodl\": 0.10878573845318806, \"returns_over_uniform_hodl\": 0.10878573845318806, \"sharpe\": 0.8691559885230011, \"sterling\": 1.6619061134234105, \"ulcer\": -0.13246961887617462}], \"train_return\": 0.244332213993401, \"train_returns_over_hodl\": 0.10878573845318806, \"train_sharpe\": 0.8492681693185306, \"validation_return\": -0.008359316480445078, \"validation_returns_over_hodl\": 0.009828243621375687, \"validation_sharpe\": 0.23741483713738745}, {\"centeredness_margin\": 0.8784252356498521, \"continuous_test_metrics\": [{\"annualised_returns\": -0.8748203736308126, \"annualised_returns_over_hodl\": 0.10205811953301369, \"annualised_returns_over_uniform_hodl\": 0.09959643814049302, \"calmar\": -1.3394501329150026, \"daily_log_sharpe\": -2.597190528883401, \"daily_returns\": 0.006606373625689645, \"fee_revenue_over_value\": 0.021318881271428173, \"jax_sharpe\": -1.7479972214009454, \"return\": -0.5669453893037153, \"returns_over_hodl\": 0.03991381541867822, \"returns_over_uniform_hodl\": 0.03897768349034858, \"sharpe\": -2.223727728971175, \"sterling\": -2.5197733307442203, \"ulcer\": -0.1560815699085417}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": 0.5551812794014997, \"optuna_trial_number\": 1, \"price_ratio\": 118.57575031321454, \"shift_exponent\": 0.013087534791885708, \"step\": 1, \"test_objective\": [{\"annualised_returns\": -0.8748203736308126, \"annualised_returns_over_hodl\": 0.10205811953301369, \"annualised_returns_over_uniform_hodl\": 0.09959643814049302, \"calmar\": -1.3394501329150026, \"daily_log_sharpe\": -2.597190528883401, \"daily_returns\": 0.006606373625689645, \"fee_revenue_over_value\": 0.021318881271428173, \"jax_sharpe\": -1.7479972214009454, \"return\": -0.5669453893037153, \"returns_over_hodl\": 0.03991381541867822, \"returns_over_uniform_hodl\": 0.03897768349034858, \"sharpe\": -2.223727728971175, \"sterling\": -2.5197733307442203, \"ulcer\": -0.1560815699085417}], \"train_objective\": [{\"annualised_returns\": 0.2885429330991327, \"annualised_returns_over_hodl\": 0.06560664132663696, \"annualised_returns_over_uniform_hodl\": 0.06560664132663696, \"calmar\": 0.44817893964912586, \"daily_log_sharpe\": 0.32114110494455, \"daily_returns\": 0.020425335198199654, \"fee_revenue_over_value\": 0.031601392039177, \"jax_sharpe\": 0.7277980335340922, \"return\": 0.16638893939636334, \"returns_over_hodl\": 0.03933291041445797, \"returns_over_uniform_hodl\": 0.03933291041445797, \"sharpe\": 0.7458902471105417, \"sterling\": 1.098429396905951, \"ulcer\": -0.13452023342959052}], \"train_return\": 0.16638893939636334, \"train_returns_over_hodl\": 0.03933291041445797, \"train_sharpe\": 0.7277980335340922, \"validation_return\": 0.0017658332551577782, \"validation_returns_over_hodl\": 0.004693213963668619, \"validation_sharpe\": 0.3305653434858744}, {\"centeredness_margin\": 0.7552725303684831, \"continuous_test_metrics\": [{\"annualised_returns\": -0.8851343207689347, \"annualised_returns_over_hodl\": 0.01638203260521265, \"annualised_returns_over_uniform_hodl\": 0.008997193956776561, \"calmar\": -1.3356331580346357, \"daily_log_sharpe\": -2.672438575728665, \"daily_returns\": 0.00652740187836668, \"fee_revenue_over_value\": 5.411812685340088e-05, \"jax_sharpe\": -1.3695842766283601, \"return\": -0.5816853437794627, \"returns_over_hodl\": 0.006565665730905446, \"returns_over_uniform_hodl\": 0.0036138208787901416, \"sharpe\": -2.2972144737979807, \"sterling\": -2.409330688494538, \"ulcer\": -0.15880168738100758}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -0.4394136581571326, \"optuna_trial_number\": 2, \"price_ratio\": 170.0280682670717, \"shift_exponent\": 0.0006084808930288774, \"step\": 2, \"test_objective\": [{\"annualised_returns\": -0.8851343207689347, \"annualised_returns_over_hodl\": 0.01638203260521265, \"annualised_returns_over_uniform_hodl\": 0.008997193956776561, \"calmar\": -1.3356331580346357, \"daily_log_sharpe\": -2.672438575728665, \"daily_returns\": 0.00652740187836668, \"fee_revenue_over_value\": 5.411812685340088e-05, \"jax_sharpe\": -1.3695842766283601, \"return\": -0.5816853437794627, \"returns_over_hodl\": 0.006565665730905446, \"returns_over_uniform_hodl\": 0.0036138208787901416, \"sharpe\": -2.2972144737979807, \"sterling\": -2.409330688494538, \"ulcer\": -0.15880168738100758}], \"train_objective\": [{\"annualised_returns\": 0.2909873986715428, \"annualised_returns_over_hodl\": 0.06762817951643485, \"annualised_returns_over_uniform_hodl\": 0.06762817951643485, \"calmar\": 0.45234468635811, \"daily_log_sharpe\": 0.32600673423487536, \"daily_returns\": 0.020420823227302452, \"fee_revenue_over_value\": 0.028647356094450505, \"jax_sharpe\": 0.7299011436855869, \"return\": 0.1677318349096073, \"returns_over_hodl\": 0.04052952284365707, \"returns_over_uniform_hodl\": 0.04052952284365707, \"sharpe\": 0.7503534719290295, \"sterling\": 1.1092186541935365, \"ulcer\": -0.13425609133669422}], \"train_return\": 0.1677318349096073, \"train_returns_over_hodl\": 0.04052952284365707, \"train_sharpe\": 0.7299011436855868, \"validation_return\": -0.005461616622667198, \"validation_returns_over_hodl\": -0.0005952760096641363, \"validation_sharpe\": 0.2557766514060868}, {\"centeredness_margin\": 0.15603917260691053, \"continuous_test_metrics\": [{\"annualised_returns\": -0.8654691011785979, \"annualised_returns_over_hodl\": 0.23740187258467804, \"annualised_returns_over_uniform_hodl\": 0.18173940484188322, \"calmar\": -1.2617796380626654, \"daily_log_sharpe\": -2.335509705564638, \"daily_returns\": 0.007319024104269883, \"fee_revenue_over_value\": 0.046953791746366234, \"jax_sharpe\": -0.7174600926196413, \"return\": -0.5541962820867392, \"returns_over_hodl\": 0.08957614353162335, \"returns_over_uniform_hodl\": 0.06956513725643632, \"sharpe\": -1.9344307159137337, \"sterling\": -2.1410454622492248, \"ulcer\": -0.16349096771145646}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": 0.6484979210714252, \"optuna_trial_number\": 3, \"price_ratio\": 4.870748630232988, \"shift_exponent\": 0.038819067718878435, \"step\": 3, \"test_objective\": [{\"annualised_returns\": -0.8654691011785979, \"annualised_returns_over_hodl\": 0.23740187258467804, \"annualised_returns_over_uniform_hodl\": 0.18173940484188322, \"calmar\": -1.2617796380626654, \"daily_log_sharpe\": -2.335509705564638, \"daily_returns\": 0.007319024104269883, \"fee_revenue_over_value\": 0.046953791746366234, \"jax_sharpe\": -0.7174600926196413, \"return\": -0.5541962820867392, \"returns_over_hodl\": 0.08957614353162335, \"returns_over_uniform_hodl\": 0.06956513725643632, \"sharpe\": -1.9344307159137337, \"sterling\": -2.1410454622492248, \"ulcer\": -0.16349096771145646}], \"train_objective\": [{\"annualised_returns\": 0.4352128180941073, \"annualised_returns_over_hodl\": 0.1869005458745847, \"annualised_returns_over_uniform_hodl\": 0.1869005458745847, \"calmar\": 0.6827766295155451, \"daily_log_sharpe\": 0.44580856981122785, \"daily_returns\": 0.020524031742917073, \"fee_revenue_over_value\": 0.08148470866658147, \"jax_sharpe\": 0.850702986240742, \"return\": 0.2452806623724344, \"returns_over_hodl\": 0.10963087130798743, \"returns_over_uniform_hodl\": 0.10963087130798743, \"sharpe\": 0.8706309238417507, \"sterling\": 1.6689139569520526, \"ulcer\": -0.13244895182184713}], \"train_return\": 0.2452806623724344, \"train_returns_over_hodl\": 0.10963087130798743, \"train_sharpe\": 0.8507029862407421, \"validation_return\": -0.00850355027790739, \"validation_returns_over_hodl\": 0.009933306008981013, \"validation_sharpe\": 0.2361101810828208}, {\"centeredness_margin\": 0.09580269446524757, \"continuous_test_metrics\": [{\"annualised_returns\": -0.8701114901651317, \"annualised_returns_over_hodl\": 0.1674930806843049, \"annualised_returns_over_uniform_hodl\": 0.14095997018372142, \"calmar\": -1.3300954529769715, \"daily_log_sharpe\": -2.461648871189412, \"daily_returns\": 0.006936213964880072, \"fee_revenue_over_value\": 0.03255398671306927, \"jax_sharpe\": -1.4926026176027365, \"return\": -0.560456960654425, \"returns_over_hodl\": 0.06435341165787123, \"returns_over_uniform_hodl\": 0.054544617546755214, \"sharpe\": -2.075060526726854, \"sterling\": -2.385713116328406, \"ulcer\": -0.1598403561361086}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": 0.6007866266834672, \"optuna_trial_number\": 4, \"price_ratio\": 15.204064110414503, \"shift_exponent\": 0.0002823719711165304, \"step\": 4, \"test_objective\": [{\"annualised_returns\": -0.8701114901651317, \"annualised_returns_over_hodl\": 0.1674930806843049, \"annualised_returns_over_uniform_hodl\": 0.14095997018372142, \"calmar\": -1.3300954529769715, \"daily_log_sharpe\": -2.461648871189412, \"daily_returns\": 0.006936213964880072, \"fee_revenue_over_value\": 0.03255398671306927, \"jax_sharpe\": -1.4926026176027365, \"return\": -0.560456960654425, \"returns_over_hodl\": 0.06435341165787123, \"returns_over_uniform_hodl\": 0.054544617546755214, \"sharpe\": -2.075060526726854, \"sterling\": -2.385713116328406, \"ulcer\": -0.1598403561361086}], \"train_objective\": [{\"annualised_returns\": 0.3560741012977975, \"annualised_returns_over_hodl\": 0.12145395497102207, \"annualised_returns_over_uniform_hodl\": 0.12145395497102207, \"calmar\": 0.5553721594479342, \"daily_log_sharpe\": 0.38056585860915704, \"daily_returns\": 0.020460794095813757, \"fee_revenue_over_value\": 0.05464577688654572, \"jax_sharpe\": 0.785936348611586, \"return\": 0.2031287206687098, \"returns_over_hodl\": 0.07207058693729951, \"returns_over_uniform_hodl\": 0.07207058693729951, \"sharpe\": 0.8051590009031039, \"sterling\": 1.3605139782492506, \"ulcer\": -0.1335682242005651}], \"train_return\": 0.2031287206687098, \"train_returns_over_hodl\": 0.07207058693729951, \"train_sharpe\": 0.785936348611586, \"validation_return\": -0.0036583440791315702, \"validation_returns_over_hodl\": 0.0067019264761101205, \"validation_sharpe\": 0.2790377878415566}, {\"centeredness_margin\": 0.7812539088724472, \"continuous_test_metrics\": [{\"annualised_returns\": -0.8759923077398151, \"annualised_returns_over_hodl\": 0.092409559196168, \"annualised_returns_over_uniform_hodl\": 0.08930199479238832, \"calmar\": -1.3369592435582625, \"daily_log_sharpe\": -2.5991343953762907, \"daily_returns\": 0.006623278556775218, \"fee_revenue_over_value\": 0.02235177618321869, \"jax_sharpe\": -1.8046449705536007, \"return\": -0.5685827833364662, \"returns_over_hodl\": 0.03623746342156253, \"returns_over_uniform_hodl\": 0.03504927396164437, \"sharpe\": -2.226372920196868, \"sterling\": -2.549943078076696, \"ulcer\": -0.15721635892285682}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": 0.5645998920927358, \"optuna_trial_number\": 5, \"price_ratio\": 164.17642773520734, \"shift_exponent\": 32.59205811937928, \"step\": 5, \"test_objective\": [{\"annualised_returns\": -0.8759923077398151, \"annualised_returns_over_hodl\": 0.092409559196168, \"annualised_returns_over_uniform_hodl\": 0.08930199479238832, \"calmar\": -1.3369592435582625, \"daily_log_sharpe\": -2.5991343953762907, \"daily_returns\": 0.006623278556775218, \"fee_revenue_over_value\": 0.02235177618321869, \"jax_sharpe\": -1.8046449705536007, \"return\": -0.5685827833364662, \"returns_over_hodl\": 0.03623746342156253, \"returns_over_uniform_hodl\": 0.03504927396164437, \"sharpe\": -2.226372920196868, \"sterling\": -2.549943078076696, \"ulcer\": -0.15721635892285682}], \"train_objective\": [{\"annualised_returns\": 0.2967979497385351, \"annualised_returns_over_hodl\": 0.07243342243671758, \"annualised_returns_over_uniform_hodl\": 0.07243342243671758, \"calmar\": 0.4605646332519919, \"daily_log_sharpe\": 0.32874178885637567, \"daily_returns\": 0.0204241935927149, \"fee_revenue_over_value\": 0.03755085103280756, \"jax_sharpe\": 0.7351491985435734, \"return\": 0.17091992526663025, \"returns_over_hodl\": 0.04337033101450949, \"returns_over_uniform_hodl\": 0.04337033101450949, \"sharpe\": 0.7534777253085261, \"sterling\": 1.130240052232847, \"ulcer\": -0.13456905890454418}], \"train_return\": 0.17091992526663025, \"train_returns_over_hodl\": 0.04337033101450949, \"train_sharpe\": 0.7351491985435733, \"validation_return\": 0.0016679917914037556, \"validation_returns_over_hodl\": 0.005198158467133451, \"validation_sharpe\": 0.32956044580809274}, {\"centeredness_margin\": 0.5439003087077028, \"continuous_test_metrics\": [{\"annualised_returns\": -0.8744414085719108, \"annualised_returns_over_hodl\": 0.11600473528472577, \"annualised_returns_over_uniform_hodl\": 0.10292532352731376, \"calmar\": -1.3352446749423885, \"daily_log_sharpe\": -2.562126336900154, \"daily_returns\": 0.0067486345188958324, \"fee_revenue_over_value\": 0.02325323967931821, \"jax_sharpe\": -1.2181611206297522, \"return\": -0.5664178699142477, \"returns_over_hodl\": 0.0451940196493259, \"returns_over_uniform_hodl\": 0.040243299557532364, \"sharpe\": -2.184460243563584, \"sterling\": -2.366231482840909, \"ulcer\": -0.15769083778622986}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": 0.5770998692493124, \"optuna_trial_number\": 6, \"price_ratio\": 71.97838445153303, \"shift_exponent\": 0.012418784499942844, \"step\": 6, \"test_objective\": [{\"annualised_returns\": -0.8744414085719108, \"annualised_returns_over_hodl\": 0.11600473528472577, \"annualised_returns_over_uniform_hodl\": 0.10292532352731376, \"calmar\": -1.3352446749423885, \"daily_log_sharpe\": -2.562126336900154, \"daily_returns\": 0.0067486345188958324, \"fee_revenue_over_value\": 0.02325323967931821, \"jax_sharpe\": -1.2181611206297522, \"return\": -0.5664178699142477, \"returns_over_hodl\": 0.0451940196493259, \"returns_over_uniform_hodl\": 0.040243299557532364, \"sharpe\": -2.184460243563584, \"sterling\": -2.366231482840909, \"ulcer\": -0.15769083778622986}], \"train_objective\": [{\"annualised_returns\": 0.319186491612802, \"annualised_returns_over_hodl\": 0.09094842748467324, \"annualised_returns_over_uniform_hodl\": 0.09094842748467324, \"calmar\": 0.49656527023400177, \"daily_log_sharpe\": 0.348867358493186, \"daily_returns\": 0.02043159865356909, \"fee_revenue_over_value\": 0.04133751834301185, \"jax_sharpe\": 0.7545306001158647, \"return\": 0.18315178711895186, \"returns_over_hodl\": 0.0542697627129447, \"returns_over_uniform_hodl\": 0.0542697627129447, \"sharpe\": 0.7733828737247608, \"sterling\": 1.2173536638853082, \"ulcer\": -0.1340981562703708}], \"train_return\": 0.18315178711895186, \"train_returns_over_hodl\": 0.0542697627129447, \"train_sharpe\": 0.7545306001158647, \"validation_return\": -0.0012421441764625119, \"validation_returns_over_hodl\": 0.005054534263150945, \"validation_sharpe\": 0.30130999131905906}, {\"centeredness_margin\": 0.38220678315441536, \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -Infinity, \"optuna_trial_number\": 7, \"price_ratio\": 1.1991151558010766, \"shift_exponent\": 42.82360157562692, \"step\": 7, \"test_objective\": -Infinity, \"train_objective\": -Infinity, \"train_return\": -Infinity, \"train_returns_over_hodl\": -Infinity, \"train_sharpe\": -Infinity, \"validation_return\": -Infinity, \"validation_returns_over_hodl\": -Infinity, \"validation_sharpe\": -Infinity}, {\"centeredness_margin\": 0.724026640628958, \"continuous_test_metrics\": [{\"annualised_returns\": -0.8657061652570974, \"annualised_returns_over_hodl\": 0.19353764380027094, \"annualised_returns_over_uniform_hodl\": 0.17965699875161079, \"calmar\": -1.337954630133538, \"daily_log_sharpe\": -2.504201184482734, \"daily_returns\": 0.006872771999842159, \"fee_revenue_over_value\": 0.04034790923371779, \"jax_sharpe\": -0.8272695772817232, \"return\": -0.55451282930953, \"returns_over_hodl\": 0.073852944135115, \"returns_over_uniform_hodl\": 0.06880568223130279, \"sharpe\": -2.1282900007026657, \"sterling\": -2.2942160138287013, \"ulcer\": -0.15557982002558296}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": 0.6043370324009216, \"optuna_trial_number\": 8, \"price_ratio\": 6.614890678691679, \"shift_exponent\": 0.5949102237584457, \"step\": 8, \"test_objective\": [{\"annualised_returns\": -0.8657061652570974, \"annualised_returns_over_hodl\": 0.19353764380027094, \"annualised_returns_over_uniform_hodl\": 0.17965699875161079, \"calmar\": -1.337954630133538, \"daily_log_sharpe\": -2.504201184482734, \"daily_returns\": 0.006872771999842159, \"fee_revenue_over_value\": 0.04034790923371779, \"jax_sharpe\": -0.8272695772817232, \"return\": -0.55451282930953, \"returns_over_hodl\": 0.073852944135115, \"returns_over_uniform_hodl\": 0.06880568223130279, \"sharpe\": -2.1282900007026657, \"sterling\": -2.2942160138287013, \"ulcer\": -0.15557982002558296}], \"train_objective\": [{\"annualised_returns\": 0.3572608575383598, \"annualised_returns_over_hodl\": 0.1224353854682878, \"annualised_returns_over_uniform_hodl\": 0.1224353854682878, \"calmar\": 0.55558684469779, \"daily_log_sharpe\": 0.37996470236170266, \"daily_returns\": 0.020501416833901647, \"fee_revenue_over_value\": 0.06958331718673763, \"jax_sharpe\": 0.78704036288258, \"return\": 0.20376785344625747, \"returns_over_hodl\": 0.07264009827900852, \"returns_over_uniform_hodl\": 0.07264009827900852, \"sharpe\": 0.8054962083054551, \"sterling\": 1.3623026354525627, \"ulcer\": -0.13391183981370125}], \"train_return\": 0.20376785344625747, \"train_returns_over_hodl\": 0.07264009827900852, \"train_sharpe\": 0.7870403628825798, \"validation_return\": 0.003293934397936793, \"validation_returns_over_hodl\": 0.01055953228033446, \"validation_sharpe\": 0.348209941057061}, {\"centeredness_margin\": 0.23195833297785196, \"continuous_test_metrics\": [{\"annualised_returns\": -0.8684100147378317, \"annualised_returns_over_hodl\": 0.1888485560828319, \"annualised_returns_over_uniform_hodl\": 0.1559059831549119, \"calmar\": -1.3283613517463406, \"daily_log_sharpe\": -2.4271387524148356, \"daily_returns\": 0.007019000515745882, \"fee_revenue_over_value\": 0.035938144415981255, \"jax_sharpe\": -0.8030049343651188, \"return\": -0.5581470865460509, \"returns_over_hodl\": 0.0721518444370568, \"returns_over_uniform_hodl\": 0.06008643049827689, \"sharpe\": -2.0372255311554945, \"sterling\": -2.222520592012645, \"ulcer\": -0.16047541108656999}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": 0.6121846158654891, \"optuna_trial_number\": 9, \"price_ratio\": 10.47486441201008, \"shift_exponent\": 0.014977206421137538, \"step\": 9, \"test_objective\": [{\"annualised_returns\": -0.8684100147378317, \"annualised_returns_over_hodl\": 0.1888485560828319, \"annualised_returns_over_uniform_hodl\": 0.1559059831549119, \"calmar\": -1.3283613517463406, \"daily_log_sharpe\": -2.4271387524148356, \"daily_returns\": 0.007019000515745882, \"fee_revenue_over_value\": 0.035938144415981255, \"jax_sharpe\": -0.8030049343651188, \"return\": -0.5581470865460509, \"returns_over_hodl\": 0.0721518444370568, \"returns_over_uniform_hodl\": 0.06008643049827689, \"sharpe\": -2.0372255311554945, \"sterling\": -2.222520592012645, \"ulcer\": -0.16047541108656999}], \"train_objective\": [{\"annualised_returns\": 0.3749448536380282, \"annualised_returns_over_hodl\": 0.13705979820995573, \"annualised_returns_over_uniform_hodl\": 0.13705979820995529, \"calmar\": 0.5856931372128104, \"daily_log_sharpe\": 0.39649741734812194, \"daily_returns\": 0.020483124856927798, \"fee_revenue_over_value\": 0.060579375094152114, \"jax_sharpe\": 0.8016983488929708, \"return\": 0.2132657857588549, \"returns_over_hodl\": 0.0811034103869579, \"returns_over_uniform_hodl\": 0.08110341038695768, \"sharpe\": 0.821131951919415, \"sterling\": 1.4339949386587323, \"ulcer\": -0.13328955596361303}], \"train_return\": 0.2132657857588549, \"train_returns_over_hodl\": 0.0811034103869579, \"train_sharpe\": 0.8016983488929708, \"validation_return\": -0.004713670995182828, \"validation_returns_over_hodl\": 0.007404217817432723, \"validation_sharpe\": 0.26949614535332705}, {\"centeredness_margin\": 0.3991491796166288, \"continuous_test_metrics\": [{\"annualised_returns\": -0.888916136928488, \"annualised_returns_over_hodl\": 0.04184897612401173, \"annualised_returns_over_uniform_hodl\": -0.02422284111892037, \"calmar\": -1.2674937414850653, \"daily_log_sharpe\": -2.4354192344415684, \"daily_returns\": 0.007258633835794435, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": -0.7783752881469628, \"return\": -0.5872875685124542, \"returns_over_hodl\": 0.0166481015882749, \"returns_over_uniform_hodl\": -0.009826946940578507, \"sharpe\": -2.0205456718700603, \"sterling\": -2.1188429489249825, \"ulcer\": -0.1715050823942645}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -0.26459622953859163, \"optuna_trial_number\": 10, \"price_ratio\": 3.209819152865184, \"shift_exponent\": 6.813085479689946e-05, \"step\": 10, \"test_objective\": [{\"annualised_returns\": -0.888916136928488, \"annualised_returns_over_hodl\": 0.04184897612401173, \"annualised_returns_over_uniform_hodl\": -0.02422284111892037, \"calmar\": -1.2674937414850653, \"daily_log_sharpe\": -2.4354192344415684, \"daily_returns\": 0.007258633835794435, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": -0.7783752881469628, \"return\": -0.5872875685124542, \"returns_over_hodl\": 0.0166481015882749, \"returns_over_uniform_hodl\": -0.009826946940578507, \"sharpe\": -2.0205456718700603, \"sterling\": -2.1188429489249825, \"ulcer\": -0.1715050823942645}], \"train_objective\": [{\"annualised_returns\": 0.46830913841250954, \"annualised_returns_over_hodl\": 0.21427073108831274, \"annualised_returns_over_uniform_hodl\": 0.2142707310883123, \"calmar\": 0.7382037454429755, \"daily_log_sharpe\": 0.47689756139447864, \"daily_returns\": 0.02055835216747387, \"fee_revenue_over_value\": 0.08738506858348004, \"jax_sharpe\": 0.8767902216264937, \"return\": 0.26263688685111153, \"returns_over_hodl\": 0.1250964631804259, \"returns_over_uniform_hodl\": 0.12509646318042567, \"sharpe\": 0.9016898916159418, \"sterling\": 1.800847111038084, \"ulcer\": -0.13155800184741626}], \"train_return\": 0.26263688685111153, \"train_returns_over_hodl\": 0.1250964631804259, \"train_sharpe\": 0.8767902216264937, \"validation_return\": -0.025702257043823007, \"validation_returns_over_hodl\": -0.000976770160141971, \"validation_sharpe\": 0.06763710609496576}, {\"centeredness_margin\": 0.08198735589815682, \"continuous_test_metrics\": [{\"annualised_returns\": -0.866595978054957, \"annualised_returns_over_hodl\": 0.28128634662696217, \"annualised_returns_over_uniform_hodl\": 0.17184075092025308, \"calmar\": -1.3115232033874, \"daily_log_sharpe\": -2.2782729290451686, \"daily_returns\": 0.008038631589649561, \"fee_revenue_over_value\": 0.06823867077291279, \"jax_sharpe\": -1.0315800539776958, \"return\": -0.5557039663118674, \"returns_over_hodl\": 0.10497689137148591, \"returns_over_uniform_hodl\": 0.0659479254199431, \"sharpe\": -1.8746941874034373, \"sterling\": -2.2038109539951742, \"ulcer\": -0.16949558583951063}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": 0.7362169873365216, \"optuna_trial_number\": 11, \"price_ratio\": 2.387742290883742, \"shift_exponent\": 25.15043267091571, \"step\": 11, \"test_objective\": [{\"annualised_returns\": -0.866595978054957, \"annualised_returns_over_hodl\": 0.28128634662696217, \"annualised_returns_over_uniform_hodl\": 0.17184075092025308, \"calmar\": -1.3115232033874, \"daily_log_sharpe\": -2.2782729290451686, \"daily_returns\": 0.008038631589649561, \"fee_revenue_over_value\": 0.06823867077291279, \"jax_sharpe\": -1.0315800539776958, \"return\": -0.5557039663118674, \"returns_over_hodl\": 0.10497689137148591, \"returns_over_uniform_hodl\": 0.0659479254199431, \"sharpe\": -1.8746941874034373, \"sterling\": -2.2038109539951742, \"ulcer\": -0.16949558583951063}], \"train_objective\": [{\"annualised_returns\": 0.5955759006395667, \"annualised_returns_over_hodl\": 0.31951852964098726, \"annualised_returns_over_uniform_hodl\": 0.31951852964098726, \"calmar\": 0.9455304604074337, \"daily_log_sharpe\": 0.5674245745882762, \"daily_returns\": 0.020654732872476902, \"fee_revenue_over_value\": 0.13287473882017176, \"jax_sharpe\": 0.972111451146546, \"return\": 0.3279922309762868, \"returns_over_hodl\": 0.18333257784721124, \"returns_over_uniform_hodl\": 0.18333257784721124, \"sharpe\": 0.9928944860316317, \"sterling\": 2.299304797816705, \"ulcer\": -0.1303202208206242}], \"train_return\": 0.3279922309762868, \"train_returns_over_hodl\": 0.18333257784721124, \"train_sharpe\": 0.9721114511465462, \"validation_return\": -0.017186505310098243, \"validation_returns_over_hodl\": 0.016445560496407108, \"validation_sharpe\": 0.16630120288444136}, {\"centeredness_margin\": 0.40269970983299674, \"continuous_test_metrics\": [{\"annualised_returns\": -0.8736961570676808, \"annualised_returns_over_hodl\": 0.12082489095406257, \"annualised_returns_over_uniform_hodl\": 0.10947172347544365, \"calmar\": -1.3352423665310789, \"daily_log_sharpe\": -2.5493580153402777, \"daily_returns\": 0.006713876805591612, \"fee_revenue_over_value\": 0.02365322137738926, \"jax_sharpe\": -1.2441627963380344, \"return\": -0.5653832468205329, \"returns_over_hodl\": 0.047009765763260436, \"returns_over_uniform_hodl\": 0.04272555070702366, \"sharpe\": -2.1708919480866915, \"sterling\": -2.372049999231164, \"ulcer\": -0.1576656660611783}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": 0.5734934696512447, \"optuna_trial_number\": 12, \"price_ratio\": 108.10118028311598, \"shift_exponent\": 0.014818318290438778, \"step\": 12, \"test_objective\": [{\"annualised_returns\": -0.8736961570676808, \"annualised_returns_over_hodl\": 0.12082489095406257, \"annualised_returns_over_uniform_hodl\": 0.10947172347544365, \"calmar\": -1.3352423665310789, \"daily_log_sharpe\": -2.5493580153402777, \"daily_returns\": 0.006713876805591612, \"fee_revenue_over_value\": 0.02365322137738926, \"jax_sharpe\": -1.2441627963380344, \"return\": -0.5653832468205329, \"returns_over_hodl\": 0.047009765763260436, \"returns_over_uniform_hodl\": 0.04272555070702366, \"sharpe\": -2.1708919480866915, \"sterling\": -2.372049999231164, \"ulcer\": -0.1576656660611783}], \"train_objective\": [{\"annualised_returns\": 0.313664688895382, \"annualised_returns_over_hodl\": 0.08638197533423853, \"annualised_returns_over_uniform_hodl\": 0.08638197533423853, \"calmar\": 0.48778738960796547, \"daily_log_sharpe\": 0.3440361809433633, \"daily_returns\": 0.020427745888878458, \"fee_revenue_over_value\": 0.0393837319206614, \"jax_sharpe\": 0.7497600717203895, \"return\": 0.18014260597556975, \"returns_over_hodl\": 0.05158837497847757, \"returns_over_uniform_hodl\": 0.05158837497847757, \"sharpe\": 0.7685410262439347, \"sterling\": 1.195971765226021, \"ulcer\": -0.1341824203161388}], \"train_return\": 0.18014260597556975, \"train_returns_over_hodl\": 0.05158837497847757, \"train_sharpe\": 0.7497600717203895, \"validation_return\": -0.000894017593113805, \"validation_returns_over_hodl\": 0.004769460550292148, \"validation_sharpe\": 0.3045584172952568}, {\"centeredness_margin\": 0.02659083897279535, \"continuous_test_metrics\": [{\"annualised_returns\": -0.8739329024353362, \"annualised_returns_over_hodl\": 0.1183996253805577, \"annualised_returns_over_uniform_hodl\": 0.10739211698858342, \"calmar\": -1.335179806873396, \"daily_log_sharpe\": -2.5514025220834085, \"daily_returns\": 0.006713509130007837, \"fee_revenue_over_value\": 0.023492144180094543, \"jax_sharpe\": -1.6652756032069786, \"return\": -0.5657115212698159, \"returns_over_hodl\": 0.04609675572315197, \"returns_over_uniform_hodl\": 0.0419379599079861, \"sharpe\": -2.173093787316785, \"sterling\": -2.470755064413366, \"ulcer\": -0.1577502239432248}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": 0.5730535412914822, \"optuna_trial_number\": 13, \"price_ratio\": 116.18946878761216, \"shift_exponent\": 0.47822751541153596, \"step\": 13, \"test_objective\": [{\"annualised_returns\": -0.8739329024353362, \"annualised_returns_over_hodl\": 0.1183996253805577, \"annualised_returns_over_uniform_hodl\": 0.10739211698858342, \"calmar\": -1.335179806873396, \"daily_log_sharpe\": -2.5514025220834085, \"daily_returns\": 0.006713509130007837, \"fee_revenue_over_value\": 0.023492144180094543, \"jax_sharpe\": -1.6652756032069786, \"return\": -0.5657115212698159, \"returns_over_hodl\": 0.04609675572315197, \"returns_over_uniform_hodl\": 0.0419379599079861, \"sharpe\": -2.173093787316785, \"sterling\": -2.470755064413366, \"ulcer\": -0.1577502239432248}], \"train_objective\": [{\"annualised_returns\": 0.3129906587035358, \"annualised_returns_over_hodl\": 0.08582456197187716, \"annualised_returns_over_uniform_hodl\": 0.08582456197187716, \"calmar\": 0.4867233287814372, \"daily_log_sharpe\": 0.34345885264317133, \"daily_returns\": 0.020429187707407568, \"fee_revenue_over_value\": 0.039077518384066506, \"jax_sharpe\": 0.7491768406637555, \"return\": 0.17977494407683814, \"returns_over_hodl\": 0.051260762894420164, \"returns_over_uniform_hodl\": 0.051260762894420164, \"sharpe\": 0.7679586770571294, \"sterling\": 1.1933755863130078, \"ulcer\": -0.13418649037410854}], \"train_return\": 0.17977494407683814, \"train_returns_over_hodl\": 0.051260762894420164, \"train_sharpe\": 0.7491768406637553, \"validation_return\": -0.0007850642229482174, \"validation_returns_over_hodl\": 0.0047276363644293, \"validation_sharpe\": 0.30563812141641583}, {\"centeredness_margin\": 0.02694675843730157, \"continuous_test_metrics\": [{\"annualised_returns\": -0.8119037695134027, \"annualised_returns_over_hodl\": 0.8576525493762581, \"annualised_returns_over_uniform_hodl\": 0.6522652373216073, \"calmar\": -1.2109068484037442, \"daily_log_sharpe\": -1.892623155474046, \"daily_returns\": 0.009151258662358741, \"fee_revenue_over_value\": 0.14772348594026974, \"jax_sharpe\": -0.6293107871215412, \"return\": -0.4897706726520794, \"returns_over_hodl\": 0.2832822061995981, \"returns_over_uniform_hodl\": 0.2241340271713892, \"sharpe\": -1.4690736857112128, \"sterling\": -2.036822907736319, \"ulcer\": -0.1633139513544522}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": 0.9298918590977725, \"optuna_trial_number\": 14, \"price_ratio\": 1.2739076683719754, \"shift_exponent\": 0.034018682515179685, \"step\": 14, \"test_objective\": [{\"annualised_returns\": -0.8119037695134027, \"annualised_returns_over_hodl\": 0.8576525493762581, \"annualised_returns_over_uniform_hodl\": 0.6522652373216073, \"calmar\": -1.2109068484037442, \"daily_log_sharpe\": -1.892623155474046, \"daily_returns\": 0.009151258662358741, \"fee_revenue_over_value\": 0.14772348594026974, \"jax_sharpe\": -0.6293107871215412, \"return\": -0.4897706726520794, \"returns_over_hodl\": 0.2832822061995981, \"returns_over_uniform_hodl\": 0.2241340271713892, \"sharpe\": -1.4690736857112128, \"sterling\": -2.036822907736319, \"ulcer\": -0.1633139513544522}], \"train_objective\": [{\"annualised_returns\": 1.1488766352035826, \"annualised_returns_over_hodl\": 0.7770903514694198, \"annualised_returns_over_uniform_hodl\": 0.777090351469419, \"calmar\": 1.892702047648492, \"daily_log_sharpe\": 0.8858778586343705, \"daily_returns\": 0.02130185689445037, \"fee_revenue_over_value\": 0.34215459994735536, \"jax_sharpe\": 1.306872359062124, \"return\": 0.5910824895339419, \"returns_over_hodl\": 0.41776412541481034, \"returns_over_uniform_hodl\": 0.4177641254148099, \"sharpe\": 1.3251536830124422, \"sterling\": 4.488558471216452, \"ulcer\": -0.12419776159730075}], \"train_return\": 0.5910824895339419, \"train_returns_over_hodl\": 0.41776412541481034, \"train_sharpe\": 1.306872359062124, \"validation_return\": -0.008757340337449904, \"validation_returns_over_hodl\": 0.05058311777850588, \"validation_sharpe\": 0.2611171505213468}, {\"centeredness_margin\": 0.02699676926973743, \"continuous_test_metrics\": [{\"annualised_returns\": -0.8692571674745933, \"annualised_returns_over_hodl\": 0.19187960961445216, \"annualised_returns_over_uniform_hodl\": 0.14846446763898835, \"calmar\": -1.2914136763659299, \"daily_log_sharpe\": -2.3926193861698506, \"daily_returns\": 0.007181520339260564, \"fee_revenue_over_value\": 0.04140762124113983, \"jax_sharpe\": -0.7678113495940083, \"return\": -0.5592949124825941, \"returns_over_hodl\": 0.07325190208941468, \"returns_over_uniform_hodl\": 0.057332584902031414, \"sharpe\": -1.9984975541464285, \"sterling\": -2.183720630072505, \"ulcer\": -0.16332556175288793}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": 0.6309174930557369, \"optuna_trial_number\": 15, \"price_ratio\": 6.568626478522216, \"shift_exponent\": 8.345727079743247, \"step\": 15, \"test_objective\": [{\"annualised_returns\": -0.8692571674745933, \"annualised_returns_over_hodl\": 0.19187960961445216, \"annualised_returns_over_uniform_hodl\": 0.14846446763898835, \"calmar\": -1.2914136763659299, \"daily_log_sharpe\": -2.3926193861698506, \"daily_returns\": 0.007181520339260564, \"fee_revenue_over_value\": 0.04140762124113983, \"jax_sharpe\": -0.7678113495940083, \"return\": -0.5592949124825941, \"returns_over_hodl\": 0.07325190208941468, \"returns_over_uniform_hodl\": 0.057332584902031414, \"sharpe\": -1.9984975541464285, \"sterling\": -2.183720630072505, \"ulcer\": -0.16332556175288793}], \"train_objective\": [{\"annualised_returns\": 0.4053564435219279, \"annualised_returns_over_hodl\": 0.1622097496171946, \"annualised_returns_over_uniform_hodl\": 0.1622097496171946, \"calmar\": 0.634563993418187, \"daily_log_sharpe\": 0.4216311781344459, \"daily_returns\": 0.02049388594557409, \"fee_revenue_over_value\": 0.07130492267195693, \"jax_sharpe\": 0.8266734689691561, \"return\": 0.22948812380432893, \"returns_over_hodl\": 0.09555863132145581, \"returns_over_uniform_hodl\": 0.09555863132145581, \"sharpe\": 0.8463570229590432, \"sterling\": 1.5525083023372965, \"ulcer\": -0.13285771203252178}], \"train_return\": 0.22948812380432893, \"train_returns_over_hodl\": 0.09555863132145581, \"train_sharpe\": 0.8266734689691561, \"validation_return\": -0.006713124417746297, \"validation_returns_over_hodl\": 0.008589613430862508, \"validation_sharpe\": 0.25162746785801504}, {\"centeredness_margin\": 0.016349049176074812, \"continuous_test_metrics\": [{\"annualised_returns\": -0.8649191579183825, \"annualised_returns_over_hodl\": 0.2667587901273194, \"annualised_returns_over_uniform_hodl\": 0.1865701881542472, \"calmar\": -1.3143480403188106, \"daily_log_sharpe\": -2.27133429508701, \"daily_returns\": 0.0076415254355330165, \"fee_revenue_over_value\": 0.057769552017724476, \"jax_sharpe\": -1.1832254161471045, \"return\": -0.5534632337659522, \"returns_over_hodl\": 0.09991399637772869, \"returns_over_uniform_hodl\": 0.07132385504261407, \"sharpe\": -1.8645655133806507, \"sterling\": -2.2485204102928016, \"ulcer\": -0.1676363380165542}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": 0.6875117885525461, \"optuna_trial_number\": 16, \"price_ratio\": 3.2038690330082717, \"shift_exponent\": 23.68370559303359, \"step\": 16, \"test_objective\": [{\"annualised_returns\": -0.8649191579183825, \"annualised_returns_over_hodl\": 0.2667587901273194, \"annualised_returns_over_uniform_hodl\": 0.1865701881542472, \"calmar\": -1.3143480403188106, \"daily_log_sharpe\": -2.27133429508701, \"daily_returns\": 0.0076415254355330165, \"fee_revenue_over_value\": 0.057769552017724476, \"jax_sharpe\": -1.1832254161471045, \"return\": -0.5534632337659522, \"returns_over_hodl\": 0.09991399637772869, \"returns_over_uniform_hodl\": 0.07132385504261407, \"sharpe\": -1.8645655133806507, \"sterling\": -2.2485204102928016, \"ulcer\": -0.1676363380165542}], \"train_objective\": [{\"annualised_returns\": 0.5032141951579747, \"annualised_returns_over_hodl\": 0.24313671554906402, \"annualised_returns_over_uniform_hodl\": 0.24313671554906402, \"calmar\": 0.7933030504980857, \"daily_log_sharpe\": 0.4989751225559454, \"daily_returns\": 0.020576533336536525, \"fee_revenue_over_value\": 0.10429911996227009, \"jax_sharpe\": 0.9037056564822654, \"return\": 0.280775922783294, \"returns_over_hodl\": 0.1412595939944612, \"returns_over_uniform_hodl\": 0.1412595939944612, \"sharpe\": 0.9240411460072803, \"sterling\": 1.935216646724327, \"ulcer\": -0.13153387931267144}], \"train_return\": 0.280775922783294, \"train_returns_over_hodl\": 0.1412595939944612, \"train_sharpe\": 0.9037056564822655, \"validation_return\": -0.012515972450897306, \"validation_returns_over_hodl\": 0.012724452118760388, \"validation_sharpe\": 0.20252125315339686}, {\"centeredness_margin\": 0.18053180909343572, \"continuous_test_metrics\": [{\"annualised_returns\": -0.8692670121521191, \"annualised_returns_over_hodl\": 0.17869890742201955, \"annualised_returns_over_uniform_hodl\": 0.1483779905288065, \"calmar\": -1.3288812863952038, \"daily_log_sharpe\": -2.4413528758398915, \"daily_returns\": 0.006987268514229978, \"fee_revenue_over_value\": 0.03462942139302528, \"jax_sharpe\": -1.5367374151607598, \"return\": -0.5593082773253635, \"returns_over_hodl\": 0.06845599666936653, \"returns_over_uniform_hodl\": 0.057300520185393555, \"sharpe\": -2.0527302244502907, \"sterling\": -2.4044853486085915, \"ulcer\": -0.16034108686915546}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": 0.608177867686877, \"optuna_trial_number\": 17, \"price_ratio\": 11.932218660270468, \"shift_exponent\": 121.10731082626482, \"step\": 17, \"test_objective\": [{\"annualised_returns\": -0.8692670121521191, \"annualised_returns_over_hodl\": 0.17869890742201955, \"annualised_returns_over_uniform_hodl\": 0.1483779905288065, \"calmar\": -1.3288812863952038, \"daily_log_sharpe\": -2.4413528758398915, \"daily_returns\": 0.006987268514229978, \"fee_revenue_over_value\": 0.03462942139302528, \"jax_sharpe\": -1.5367374151607598, \"return\": -0.5593082773253635, \"returns_over_hodl\": 0.06845599666936653, \"returns_over_uniform_hodl\": 0.057300520185393555, \"sharpe\": -2.0527302244502907, \"sterling\": -2.4044853486085915, \"ulcer\": -0.16034108686915546}], \"train_objective\": [{\"annualised_returns\": 0.3684324194819395, \"annualised_returns_over_hodl\": 0.1316741079782453, \"annualised_returns_over_uniform_hodl\": 0.13167410797824575, \"calmar\": 0.5752647480199599, \"daily_log_sharpe\": 0.39102637126251694, \"daily_returns\": 0.02046462248804261, \"fee_revenue_over_value\": 0.058307402668839645, \"jax_sharpe\": 0.7962836319736621, \"return\": 0.2097736241592396, \"returns_over_hodl\": 0.07799165378813022, \"returns_over_uniform_hodl\": 0.07799165378813044, \"sharpe\": 0.8156494739082251, \"sterling\": 1.4086681188874013, \"ulcer\": -0.13337632658750667}], \"train_return\": 0.2097736241592396, \"train_returns_over_hodl\": 0.07799165378813022, \"train_sharpe\": 0.7962836319736623, \"validation_return\": -0.0042578442339993305, \"validation_returns_over_hodl\": 0.007157075243756861, \"validation_sharpe\": 0.27367838491246355}, {\"centeredness_margin\": 0.4100571766749234, \"continuous_test_metrics\": [{\"annualised_returns\": -0.8459204355906277, \"annualised_returns_over_hodl\": 0.4128295539057387, \"annualised_returns_over_uniform_hodl\": 0.35345778805174577, \"calmar\": -1.2173980574292529, \"daily_log_sharpe\": -2.285812204926095, \"daily_returns\": 0.007547681249054753, \"fee_revenue_over_value\": 0.08040340303939891, \"jax_sharpe\": -0.5832945831263424, \"return\": -0.5291590729321026, \"returns_over_hodl\": 0.14933551733936445, \"returns_over_uniform_hodl\": 0.12963400830880234, \"sharpe\": -1.8984247126853744, \"sterling\": -2.142839754918196, \"ulcer\": -0.15677300658645543}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": 0.7233717408456007, \"optuna_trial_number\": 18, \"price_ratio\": 1.9846688006330264, \"shift_exponent\": 0.06768833626969345, \"step\": 18, \"test_objective\": [{\"annualised_returns\": -0.8459204355906277, \"annualised_returns_over_hodl\": 0.4128295539057387, \"annualised_returns_over_uniform_hodl\": 0.35345778805174577, \"calmar\": -1.2173980574292529, \"daily_log_sharpe\": -2.285812204926095, \"daily_returns\": 0.007547681249054753, \"fee_revenue_over_value\": 0.08040340303939891, \"jax_sharpe\": -0.5832945831263424, \"return\": -0.5291590729321026, \"returns_over_hodl\": 0.14933551733936445, \"returns_over_uniform_hodl\": 0.12963400830880234, \"sharpe\": -1.8984247126853744, \"sterling\": -2.142839754918196, \"ulcer\": -0.15677300658645543}], \"train_objective\": [{\"annualised_returns\": 0.5769428777949543, \"annualised_returns_over_hodl\": 0.3041092853068035, \"annualised_returns_over_uniform_hodl\": 0.3041092853068035, \"calmar\": 0.9124584193872238, \"daily_log_sharpe\": 0.5489764882554304, \"daily_returns\": 0.020710897820936068, \"fee_revenue_over_value\": 0.1523957170125912, \"jax_sharpe\": 0.9582304613146999, \"return\": 0.31855515453280403, \"returns_over_hodl\": 0.1749234925116756, \"returns_over_uniform_hodl\": 0.1749234925116756, \"sharpe\": 0.9774719126885413, \"sterling\": 2.214962054342166, \"ulcer\": -0.1308452907093733}], \"train_return\": 0.31855515453280403, \"train_returns_over_hodl\": 0.1749234925116756, \"train_sharpe\": 0.9582304613146998, \"validation_return\": 0.0019203981726019226, \"validation_returns_over_hodl\": 0.024074924510851137, \"validation_sharpe\": 0.3409600506947561}, {\"centeredness_margin\": 0.042232491395153046, \"continuous_test_metrics\": [{\"annualised_returns\": -0.8256595226870288, \"annualised_returns_over_hodl\": 0.47869244044921366, \"annualised_returns_over_uniform_hodl\": 0.5314326575130597, \"calmar\": -1.2026040134972742, \"daily_log_sharpe\": -2.1694906547013177, \"daily_returns\": 0.007534425789173967, \"fee_revenue_over_value\": 0.16633877718509896, \"jax_sharpe\": -0.5422586603532837, \"return\": -0.5051399729203936, \"returns_over_hodl\": 0.17062080020123593, \"returns_over_uniform_hodl\": 0.18726024821781584, \"sharpe\": -1.7908784170764183, \"sterling\": -2.117640036519665, \"ulcer\": -0.15825676934554958}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -0.6567233801321879, \"optuna_trial_number\": 19, \"price_ratio\": 1.2259752562917055, \"shift_exponent\": 53.67079069327052, \"step\": 19, \"test_objective\": [{\"annualised_returns\": -0.8256595226870288, \"annualised_returns_over_hodl\": 0.47869244044921366, \"annualised_returns_over_uniform_hodl\": 0.5314326575130597, \"calmar\": -1.2026040134972742, \"daily_log_sharpe\": -2.1694906547013177, \"daily_returns\": 0.007534425789173967, \"fee_revenue_over_value\": 0.16633877718509896, \"jax_sharpe\": -0.5422586603532837, \"return\": -0.5051399729203936, \"returns_over_hodl\": 0.17062080020123593, \"returns_over_uniform_hodl\": 0.18726024821781584, \"sharpe\": -1.7908784170764183, \"sterling\": -2.117640036519665, \"ulcer\": -0.15825676934554958}], \"train_objective\": [{\"annualised_returns\": -0.22626014282193951, \"annualised_returns_over_hodl\": -0.3601281654736821, \"annualised_returns_over_uniform_hodl\": -0.3601281654736821, \"calmar\": -0.29412960204703065, \"daily_log_sharpe\": -0.2700261475448093, \"daily_returns\": 0.02135278183815969, \"fee_revenue_over_value\": 0.252451288922389, \"jax_sharpe\": 0.16347607352720728, \"return\": -0.14421706262483547, \"returns_over_hodl\": -0.23743843846335377, \"returns_over_uniform_hodl\": -0.23743843846335377, \"sharpe\": 0.19009291960125263, \"sterling\": -0.7618629017149704, \"ulcer\": -0.1562937970485228}], \"train_return\": -0.14421706262483547, \"train_returns_over_hodl\": -0.23743843846335377, \"train_sharpe\": 0.16347607352720728, \"validation_return\": -0.03565336894089577, \"validation_returns_over_hodl\": -0.0212279992708011, \"validation_sharpe\": -0.02647633372347897}, {\"centeredness_margin\": 0.12465889766873929, \"continuous_test_metrics\": [{\"annualised_returns\": -0.899591298462157, \"annualised_returns_over_hodl\": 0.0022168521334777758, \"annualised_returns_over_uniform_hodl\": -0.11799504622502388, \"calmar\": -1.2631634162994956, \"daily_log_sharpe\": -2.373537879980639, \"daily_returns\": 0.007841752014486876, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": -0.8300085044699255, \"return\": -0.603744259737123, \"returns_over_hodl\": 0.0008922198406757786, \"returns_over_uniform_hodl\": -0.049309576854231096, \"sharpe\": -1.932143703129486, \"sterling\": -2.083091026100836, \"ulcer\": -0.18108046885974666}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -0.008199229601653202, \"optuna_trial_number\": 20, \"price_ratio\": 1.0687703465571325, \"shift_exponent\": 0.0002291020828299918, \"step\": 20, \"test_objective\": [{\"annualised_returns\": -0.899591298462157, \"annualised_returns_over_hodl\": 0.0022168521334777758, \"annualised_returns_over_uniform_hodl\": -0.11799504622502388, \"calmar\": -1.2631634162994956, \"daily_log_sharpe\": -2.373537879980639, \"daily_returns\": 0.007841752014486876, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": -0.8300085044699255, \"return\": -0.603744259737123, \"returns_over_hodl\": 0.0008922198406757786, \"returns_over_uniform_hodl\": -0.049309576854231096, \"sharpe\": -1.932143703129486, \"sterling\": -2.083091026100836, \"ulcer\": -0.18108046885974666}], \"train_objective\": [{\"annualised_returns\": 1.1080838524511702, \"annualised_returns_over_hodl\": 0.743355301512943, \"annualised_returns_over_uniform_hodl\": 0.7433553015129419, \"calmar\": 1.9517068503586714, \"daily_log_sharpe\": 0.8674707299961831, \"daily_returns\": 0.022520226364074667, \"fee_revenue_over_value\": 0.20354649817151055, \"jax_sharpe\": 1.2806938526493903, \"return\": 0.5726760002972819, \"returns_over_hodl\": 0.40136267527867453, \"returns_over_uniform_hodl\": 0.4013626752786741, \"sharpe\": 1.3132811806073916, \"sterling\": 4.386212265952762, \"ulcer\": -0.12047049122744406}], \"train_return\": 0.5726760002972819, \"train_returns_over_hodl\": 0.40136267527867453, \"train_sharpe\": 1.2806938526493903, \"validation_return\": -0.06990342233333091, \"validation_returns_over_hodl\": -2.362412487855181e-10, \"validation_sharpe\": -0.2859676620552539}, {\"centeredness_margin\": 0.04601904104852746, \"continuous_test_metrics\": [{\"annualised_returns\": -0.8267663915087367, \"annualised_returns_over_hodl\": 0.7291095201353797, \"annualised_returns_over_uniform_hodl\": 0.5217097573164311, \"calmar\": -1.1612398590432695, \"daily_log_sharpe\": -1.9236588648948532, \"daily_returns\": 0.007889695011191591, \"fee_revenue_over_value\": 0.11405290244870035, \"jax_sharpe\": -0.4936198236769597, \"return\": -0.5064077069232815, \"returns_over_hodl\": 0.24675212408558123, \"returns_over_uniform_hodl\": 0.18421872110998794, \"sharpe\": -1.4878647834220957, \"sterling\": -1.9833593662320612, \"ulcer\": -0.17006585337074537}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": 0.9004433387967652, \"optuna_trial_number\": 21, \"price_ratio\": 1.2691021097853632, \"shift_exponent\": 0.004870380860310229, \"step\": 21, \"test_objective\": [{\"annualised_returns\": -0.8267663915087367, \"annualised_returns_over_hodl\": 0.7291095201353797, \"annualised_returns_over_uniform_hodl\": 0.5217097573164311, \"calmar\": -1.1612398590432695, \"daily_log_sharpe\": -1.9236588648948532, \"daily_returns\": 0.007889695011191591, \"fee_revenue_over_value\": 0.11405290244870035, \"jax_sharpe\": -0.4936198236769597, \"return\": -0.5064077069232815, \"returns_over_hodl\": 0.24675212408558123, \"returns_over_uniform_hodl\": 0.18421872110998794, \"sharpe\": -1.4878647834220957, \"sterling\": -1.9833593662320612, \"ulcer\": -0.17006585337074537}], \"train_objective\": [{\"annualised_returns\": 1.0905002204253034, \"annualised_returns_over_hodl\": 0.7288138884299171, \"annualised_returns_over_uniform_hodl\": 0.7288138884299171, \"calmar\": 1.8319616329946966, \"daily_log_sharpe\": 0.878558623962482, \"daily_returns\": 0.021302537040854042, \"fee_revenue_over_value\": 0.26477218884289555, \"jax_sharpe\": 1.2785554424756347, \"return\": 0.564698822468463, \"returns_over_hodl\": 0.3942544601973388, \"returns_over_uniform_hodl\": 0.3942544601973388, \"sharpe\": 1.312376377263689, \"sterling\": 4.305561305697051, \"ulcer\": -0.12231300941417535}], \"train_return\": 0.564698822468463, \"train_returns_over_hodl\": 0.3942544601973388, \"train_sharpe\": 1.278555442475635, \"validation_return\": -0.049765033525494884, \"validation_returns_over_hodl\": 0.02165193268445531, \"validation_sharpe\": -0.10471039813044837}, {\"centeredness_margin\": 0.11140116089680648, \"continuous_test_metrics\": [{\"annualised_returns\": -0.7296576939963171, \"annualised_returns_over_hodl\": 1.5980784839794522, \"annualised_returns_over_uniform_hodl\": 1.3747269853931154, \"calmar\": -1.0298913420214713, \"daily_log_sharpe\": -1.6302162620200742, \"daily_returns\": 0.010451973001284408, \"fee_revenue_over_value\": 0.297360618053012, \"jax_sharpe\": -0.2462611549527415, \"return\": -0.40951307380584456, \"returns_over_hodl\": 0.46891325985644583, \"returns_over_uniform_hodl\": 0.41668677241912433, \"sharpe\": -1.2310028571178913, \"sterling\": -1.8988346110439183, \"ulcer\": -0.146253312288071}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": 0.9908740175993178, \"optuna_trial_number\": 22, \"price_ratio\": 1.0387053296389324, \"shift_exponent\": 0.08961844828121159, \"step\": 22, \"test_objective\": [{\"annualised_returns\": -0.7296576939963171, \"annualised_returns_over_hodl\": 1.5980784839794522, \"annualised_returns_over_uniform_hodl\": 1.3747269853931154, \"calmar\": -1.0298913420214713, \"daily_log_sharpe\": -1.6302162620200742, \"daily_returns\": 0.010451973001284408, \"fee_revenue_over_value\": 0.297360618053012, \"jax_sharpe\": -0.2462611549527415, \"return\": -0.40951307380584456, \"returns_over_hodl\": 0.46891325985644583, \"returns_over_uniform_hodl\": 0.41668677241912433, \"sharpe\": -1.2310028571178913, \"sterling\": -1.8988346110439183, \"ulcer\": -0.146253312288071}], \"train_objective\": [{\"annualised_returns\": 0.858578272671046, \"annualised_returns_over_hodl\": 0.5370177429945895, \"annualised_returns_over_uniform_hodl\": 0.5370177429945886, \"calmar\": 1.2546787846825573, \"daily_log_sharpe\": 0.7107402478346403, \"daily_returns\": 0.022787983040246474, \"fee_revenue_over_value\": 0.7030153345729149, \"jax_sharpe\": 1.1419855230592326, \"return\": 0.4568856110891244, \"returns_over_hodl\": 0.2981854604158358, \"returns_over_uniform_hodl\": 0.2981854604158354, \"sharpe\": 1.1466292481032145, \"sterling\": 3.1647882451813762, \"ulcer\": -0.1402339252026803}], \"train_return\": 0.4568856110891244, \"train_returns_over_hodl\": 0.2981854604158358, \"train_sharpe\": 1.1419855230592326, \"validation_return\": 0.06778152347837874, \"validation_returns_over_hodl\": 0.09886927656015065, \"validation_sharpe\": 0.9925813847676771}, {\"centeredness_margin\": 0.016527398461459564, \"continuous_test_metrics\": [{\"annualised_returns\": -0.8995208600494092, \"annualised_returns_over_hodl\": 0.002919923811598446, \"annualised_returns_over_uniform_hodl\": -0.11737630474118721, \"calmar\": -1.2630645105171412, \"daily_log_sharpe\": -2.372805477873441, \"daily_returns\": 0.007841752006524247, \"fee_revenue_over_value\": 8.106402138559186e-06, \"jax_sharpe\": -0.8295515446264659, \"return\": -0.6036323300635111, \"returns_over_hodl\": 0.001174939934709185, \"returns_over_uniform_hodl\": -0.049041036974662555, \"sharpe\": -1.9313993837416972, \"sterling\": -2.0830517507608914, \"ulcer\": -0.18106008768150353}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": 0.8216328977739992, \"optuna_trial_number\": 23, \"price_ratio\": 1.7739863635540913, \"shift_exponent\": 5.241300888728884e-05, \"step\": 23, \"test_objective\": [{\"annualised_returns\": -0.8995208600494092, \"annualised_returns_over_hodl\": 0.002919923811598446, \"annualised_returns_over_uniform_hodl\": -0.11737630474118721, \"calmar\": -1.2630645105171412, \"daily_log_sharpe\": -2.372805477873441, \"daily_returns\": 0.007841752006524247, \"fee_revenue_over_value\": 8.106402138559186e-06, \"jax_sharpe\": -0.8295515446264659, \"return\": -0.6036323300635111, \"returns_over_hodl\": 0.001174939934709185, \"returns_over_uniform_hodl\": -0.049041036974662555, \"sharpe\": -1.9313993837416972, \"sterling\": -2.0830517507608914, \"ulcer\": -0.18106008768150353}], \"train_objective\": [{\"annualised_returns\": 0.7729809215199934, \"annualised_returns_over_hodl\": 0.46622995352827257, \"annualised_returns_over_uniform_hodl\": 0.46622995352827257, \"calmar\": 1.242281936905435, \"daily_log_sharpe\": 0.6878074946183105, \"daily_returns\": 0.020793063818042263, \"fee_revenue_over_value\": 0.18918367353234, \"jax_sharpe\": 1.0932215368028466, \"return\": 0.41577276883064185, \"returns_over_hodl\": 0.2615510852458913, \"returns_over_uniform_hodl\": 0.2615510852458913, \"sharpe\": 1.1143497948842251, \"sterling\": 3.0045103725312376, \"ulcer\": -0.1281826728505678}], \"train_return\": 0.41577276883064185, \"train_returns_over_hodl\": 0.2615510852458913, \"train_sharpe\": 1.0932215368028466, \"validation_return\": -0.030247329714988114, \"validation_returns_over_hodl\": 0.019295551292808355, \"validation_sharpe\": 0.06486838328225439}, {\"centeredness_margin\": 0.46661196576866754, \"continuous_test_metrics\": [{\"annualised_returns\": -0.8244562858474165, \"annualised_returns_over_hodl\": 0.603011260094185, \"annualised_returns_over_uniform_hodl\": 0.5420020686980318, \"calmar\": -1.1796401667735166, \"daily_log_sharpe\": -2.1784696916676696, \"daily_returns\": 0.007953753572271884, \"fee_revenue_over_value\": 0.12344478018016361, \"jax_sharpe\": -0.5444182216172054, \"return\": -0.5037673067234136, \"returns_over_hodl\": 0.2093046125016056, \"returns_over_uniform_hodl\": 0.19055352696446315, \"sharpe\": -1.7967207097267526, \"sterling\": -2.123040379630345, \"ulcer\": -0.1516668818491867}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": 0.7822068232605153, \"optuna_trial_number\": 24, \"price_ratio\": 1.4254139635398622, \"shift_exponent\": 0.20553942115350385, \"step\": 24, \"test_objective\": [{\"annualised_returns\": -0.8244562858474165, \"annualised_returns_over_hodl\": 0.603011260094185, \"annualised_returns_over_uniform_hodl\": 0.5420020686980318, \"calmar\": -1.1796401667735166, \"daily_log_sharpe\": -2.1784696916676696, \"daily_returns\": 0.007953753572271884, \"fee_revenue_over_value\": 0.12344478018016361, \"jax_sharpe\": -0.5444182216172054, \"return\": -0.5037673067234136, \"returns_over_hodl\": 0.2093046125016056, \"returns_over_uniform_hodl\": 0.19055352696446315, \"sharpe\": -1.7967207097267526, \"sterling\": -2.123040379630345, \"ulcer\": -0.1516668818491867}], \"train_objective\": [{\"annualised_returns\": 0.6104817188069387, \"annualised_returns_over_hodl\": 0.3318454288273074, \"annualised_returns_over_uniform_hodl\": 0.3318454288273074, \"calmar\": 0.9438768563843352, \"daily_log_sharpe\": 0.5712692226508688, \"daily_returns\": 0.021014658091837536, \"fee_revenue_over_value\": 0.243085229336525, \"jax_sharpe\": 0.9822704311859735, \"return\": 0.3355104528973494, \"returns_over_hodl\": 0.1900318315921956, \"returns_over_uniform_hodl\": 0.1900318315921956, \"sharpe\": 0.9980518534777664, \"sterling\": 2.337262662056282, \"ulcer\": -0.13315863155359015}], \"train_return\": 0.3355104528973494, \"train_returns_over_hodl\": 0.1900318315921956, \"train_sharpe\": 0.9822704311859735, \"validation_return\": 0.02082005465263781, \"validation_returns_over_hodl\": 0.03805627100462572, \"validation_sharpe\": 0.531100382498902}, {\"centeredness_margin\": 0.05326740743941078, \"continuous_test_metrics\": [{\"annualised_returns\": -0.8315229103566008, \"annualised_returns_over_hodl\": 0.6494616869528365, \"annualised_returns_over_uniform_hodl\": 0.47992778899808886, \"calmar\": -1.1707433627006085, \"daily_log_sharpe\": -2.043978539198794, \"daily_returns\": 0.008677047098275763, \"fee_revenue_over_value\": 0.11499492037697584, \"jax_sharpe\": -0.5212198394456465, \"return\": -0.5119113079816882, \"returns_over_hodl\": 0.2232970758625834, \"returns_over_uniform_hodl\": 0.1710145696305143, \"sharpe\": -1.6314863183889232, \"sterling\": -2.0444298522089226, \"ulcer\": -0.16339932174674143}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": 0.8926152539366818, \"optuna_trial_number\": 25, \"price_ratio\": 1.4860495837536987, \"shift_exponent\": 0.4063536609070128, \"step\": 25, \"test_objective\": [{\"annualised_returns\": -0.8315229103566008, \"annualised_returns_over_hodl\": 0.6494616869528365, \"annualised_returns_over_uniform_hodl\": 0.47992778899808886, \"calmar\": -1.1707433627006085, \"daily_log_sharpe\": -2.043978539198794, \"daily_returns\": 0.008677047098275763, \"fee_revenue_over_value\": 0.11499492037697584, \"jax_sharpe\": -0.5212198394456465, \"return\": -0.5119113079816882, \"returns_over_hodl\": 0.2232970758625834, \"returns_over_uniform_hodl\": 0.1710145696305143, \"sharpe\": -1.6314863183889232, \"sterling\": -2.0444298522089226, \"ulcer\": -0.16339932174674143}], \"train_objective\": [{\"annualised_returns\": 0.9643732548153205, \"annualised_returns_over_hodl\": 0.6245086854351525, \"annualised_returns_over_uniform_hodl\": 0.6245086854351525, \"calmar\": 1.5654387832164331, \"daily_log_sharpe\": 0.7967306550535282, \"daily_returns\": 0.020927220915135276, \"fee_revenue_over_value\": 0.25555326002984446, \"jax_sharpe\": 1.2078615109450777, \"return\": 0.5066853608344766, \"returns_over_hodl\": 0.34256046869354995, \"returns_over_uniform_hodl\": 0.34256046869354995, \"sharpe\": 1.2289581581405784, \"sterling\": 3.7571849001110853, \"ulcer\": -0.12636681706710062}], \"train_return\": 0.5066853608344766, \"train_returns_over_hodl\": 0.34256046869354995, \"train_sharpe\": 1.2078615109450774, \"validation_return\": -0.016826082677632415, \"validation_returns_over_hodl\": 0.039259106140544064, \"validation_sharpe\": 0.1809329460670704}, {\"centeredness_margin\": 0.012334865207660901, \"continuous_test_metrics\": [{\"annualised_returns\": -0.6518418664077392, \"annualised_returns_over_hodl\": 2.4750967115767466, \"annualised_returns_over_uniform_hodl\": 2.0582727773816476, \"calmar\": -0.9192726528208334, \"daily_log_sharpe\": -1.3406244421560478, \"daily_returns\": 0.008175601144019223, \"fee_revenue_over_value\": 0.3898781300205302, \"jax_sharpe\": -0.04392192110900617, \"return\": -0.3461831885711216, \"returns_over_hodl\": 0.6514591288972915, \"returns_over_uniform_hodl\": 0.5686268183895122, \"sharpe\": -0.9154634040370185, \"sterling\": -1.6906423668724355, \"ulcer\": -0.14716204614276174}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": 0.7539365146606524, \"optuna_trial_number\": 26, \"price_ratio\": 1.0157636291418122, \"shift_exponent\": 0.009370771745546755, \"step\": 26, \"test_objective\": [{\"annualised_returns\": -0.6518418664077392, \"annualised_returns_over_hodl\": 2.4750967115767466, \"annualised_returns_over_uniform_hodl\": 2.0582727773816476, \"calmar\": -0.9192726528208334, \"daily_log_sharpe\": -1.3406244421560478, \"daily_returns\": 0.008175601144019223, \"fee_revenue_over_value\": 0.3898781300205302, \"jax_sharpe\": -0.04392192110900617, \"return\": -0.3461831885711216, \"returns_over_hodl\": 0.6514591288972915, \"returns_over_uniform_hodl\": 0.5686268183895122, \"sharpe\": -0.9154634040370185, \"sterling\": -1.6906423668724355, \"ulcer\": -0.14716204614276174}], \"train_objective\": [{\"annualised_returns\": 0.7294752483112206, \"annualised_returns_over_hodl\": 0.43025138182856915, \"annualised_returns_over_uniform_hodl\": 0.43025138182856826, \"calmar\": 1.1886408816444793, \"daily_log_sharpe\": 0.6408811531107116, \"daily_returns\": 0.019645352222955742, \"fee_revenue_over_value\": 0.38756372357426666, \"jax_sharpe\": 1.056574535727035, \"return\": 0.3945782584830466, \"returns_over_hodl\": 0.2426653162023551, \"returns_over_uniform_hodl\": 0.24266531620235465, \"sharpe\": 1.0881995503794282, \"sterling\": 2.734037727238344, \"ulcer\": -0.12724732164943417}], \"train_return\": 0.3945782584830466, \"train_returns_over_hodl\": 0.2426653162023551, \"train_sharpe\": 1.0565745357270349, \"validation_return\": -0.02019600067434435, \"validation_returns_over_hodl\": 0.05344329007961934, \"validation_sharpe\": 0.18032661961007732}, {\"centeredness_margin\": 0.1600033486361988, \"continuous_test_metrics\": [{\"annualised_returns\": -0.8543411073190311, \"annualised_returns_over_hodl\": 0.38808001742836296, \"annualised_returns_over_uniform_hodl\": 0.27948935638384453, \"calmar\": -1.3156084248107263, \"daily_log_sharpe\": -2.2087830301263827, \"daily_returns\": 0.00795013755992806, \"fee_revenue_over_value\": 0.07432107694536386, \"jax_sharpe\": -1.0384037666646773, \"return\": -0.53969661773044, \"returns_over_hodl\": 0.1411840858324631, \"returns_over_uniform_hodl\": 0.10435249966339355, \"sharpe\": -1.7995475598479795, \"sterling\": -2.1994701138202664, \"ulcer\": -0.1649489685388369}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": 0.7692687219476105, \"optuna_trial_number\": 27, \"price_ratio\": 2.0785200438370217, \"shift_exponent\": 0.01843236217526346, \"step\": 27, \"test_objective\": [{\"annualised_returns\": -0.8543411073190311, \"annualised_returns_over_hodl\": 0.38808001742836296, \"annualised_returns_over_uniform_hodl\": 0.27948935638384453, \"calmar\": -1.3156084248107263, \"daily_log_sharpe\": -2.2087830301263827, \"daily_returns\": 0.00795013755992806, \"fee_revenue_over_value\": 0.07432107694536386, \"jax_sharpe\": -1.0384037666646773, \"return\": -0.53969661773044, \"returns_over_hodl\": 0.1411840858324631, \"returns_over_uniform_hodl\": 0.10435249966339355, \"sharpe\": -1.7995475598479795, \"sterling\": -2.1994701138202664, \"ulcer\": -0.1649489685388369}], \"train_objective\": [{\"annualised_returns\": 0.6614821652188019, \"annualised_returns_over_hodl\": 0.37402207115027153, \"annualised_returns_over_uniform_hodl\": 0.37402207115027153, \"calmar\": 1.0549961518743505, \"daily_log_sharpe\": 0.6137701546443016, \"daily_returns\": 0.02069661171972846, \"fee_revenue_over_value\": 0.15382311343266938, \"jax_sharpe\": 1.018596813407417, \"return\": 0.3610298335137352, \"returns_over_hodl\": 0.21277135803328928, \"returns_over_uniform_hodl\": 0.21277135803328928, \"sharpe\": 1.0395873385292007, \"sterling\": 2.5602389719187566, \"ulcer\": -0.12951017351846023}], \"train_return\": 0.3610298335137352, \"train_returns_over_hodl\": 0.21277135803328928, \"train_sharpe\": 1.018596813407417, \"validation_return\": -0.01930935711922488, \"validation_returns_over_hodl\": 0.020434764798620275, \"validation_sharpe\": 0.14591172124122007}, {\"centeredness_margin\": 0.19733001158644076, \"continuous_test_metrics\": [{\"annualised_returns\": -0.7568601300150904, \"annualised_returns_over_hodl\": 1.2954675185008373, \"annualised_returns_over_uniform_hodl\": 1.1357767454653294, \"calmar\": -1.0702056958199349, \"daily_log_sharpe\": -1.7752150833876001, \"daily_returns\": 0.009591802730490435, \"fee_revenue_over_value\": 0.24574364949287528, \"jax_sharpe\": -0.3280705080439207, \"return\": -0.434202460226244, \"returns_over_hodl\": 0.3974505926614693, \"returns_over_uniform_hodl\": 0.35745239209785096, \"sharpe\": -1.3833857602477526, \"sterling\": -1.9697134301538162, \"ulcer\": -0.14691788635235078}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": 0.9094867858837962, \"optuna_trial_number\": 28, \"price_ratio\": 1.0756389354355298, \"shift_exponent\": 0.19413382437276208, \"step\": 28, \"test_objective\": [{\"annualised_returns\": -0.7568601300150904, \"annualised_returns_over_hodl\": 1.2954675185008373, \"annualised_returns_over_uniform_hodl\": 1.1357767454653294, \"calmar\": -1.0702056958199349, \"daily_log_sharpe\": -1.7752150833876001, \"daily_returns\": 0.009591802730490435, \"fee_revenue_over_value\": 0.24574364949287528, \"jax_sharpe\": -0.3280705080439207, \"return\": -0.434202460226244, \"returns_over_hodl\": 0.3974505926614693, \"returns_over_uniform_hodl\": 0.35745239209785096, \"sharpe\": -1.3833857602477526, \"sterling\": -1.9697134301538162, \"ulcer\": -0.14691788635235078}], \"train_objective\": [{\"annualised_returns\": 0.682959993486203, \"annualised_returns_over_hodl\": 0.39178392902486237, \"annualised_returns_over_uniform_hodl\": 0.3917839290248628, \"calmar\": 0.9907040544349991, \"daily_log_sharpe\": 0.6054271883051489, \"daily_returns\": 0.022430667870884095, \"fee_revenue_over_value\": 0.5554539110313543, \"jax_sharpe\": 1.0314189405579768, \"return\": 0.37168450865340685, \"returns_over_hodl\": 0.22226540770094672, \"returns_over_uniform_hodl\": 0.22226540770094694, \"sharpe\": 1.0376987849029184, \"sterling\": 2.543805486748177, \"ulcer\": -0.14094441419400297}], \"train_return\": 0.37168450865340685, \"train_returns_over_hodl\": 0.22226540770094672, \"train_sharpe\": 1.0314189405579768, \"validation_return\": 0.048986115057697344, \"validation_returns_over_hodl\": 0.07928990729777063, \"validation_sharpe\": 0.812470896872789}, {\"centeredness_margin\": 0.10460218843798565, \"continuous_test_metrics\": [{\"annualised_returns\": -0.797640475393943, \"annualised_returns_over_hodl\": 0.9530702079146895, \"annualised_returns_over_uniform_hodl\": 0.7775561322125406, \"calmar\": -1.1248214609294214, \"daily_log_sharpe\": -1.9114185123766132, \"daily_returns\": 0.008950523167597873, \"fee_revenue_over_value\": 0.16149806066765848, \"jax_sharpe\": -0.4381102431690381, \"return\": -0.47452779450314797, \"returns_over_hodl\": 0.3094323806900945, \"returns_over_uniform_hodl\": 0.26070449620170155, \"sharpe\": -1.5078897450387476, \"sterling\": -2.014058316338636, \"ulcer\": -0.15597689769069228}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": 0.8673787919976772, \"optuna_trial_number\": 29, \"price_ratio\": 1.2373792492070719, \"shift_exponent\": 0.2057538035952214, \"step\": 29, \"test_objective\": [{\"annualised_returns\": -0.797640475393943, \"annualised_returns_over_hodl\": 0.9530702079146895, \"annualised_returns_over_uniform_hodl\": 0.7775561322125406, \"calmar\": -1.1248214609294214, \"daily_log_sharpe\": -1.9114185123766132, \"daily_returns\": 0.008950523167597873, \"fee_revenue_over_value\": 0.16149806066765848, \"jax_sharpe\": -0.4381102431690381, \"return\": -0.47452779450314797, \"returns_over_hodl\": 0.3094323806900945, \"returns_over_uniform_hodl\": 0.26070449620170155, \"sharpe\": -1.5078897450387476, \"sterling\": -2.014058316338636, \"ulcer\": -0.15597689769069228}], \"train_objective\": [{\"annualised_returns\": 0.8402381471506477, \"annualised_returns_over_hodl\": 0.5218507205731497, \"annualised_returns_over_uniform_hodl\": 0.5218507205731497, \"calmar\": 1.321672622923014, \"daily_log_sharpe\": 0.7109320951138693, \"daily_returns\": 0.021335610160972438, \"fee_revenue_over_value\": 0.3594375316176219, \"jax_sharpe\": 1.1325133555632056, \"return\": 0.4481404595618379, \"returns_over_hodl\": 0.2903929278549777, \"returns_over_uniform_hodl\": 0.2903929278549777, \"sharpe\": 1.147162472724594, \"sterling\": 3.237621321992066, \"ulcer\": -0.13005359462102717}], \"train_return\": 0.4481404595618379, \"train_returns_over_hodl\": 0.2903929278549777, \"train_sharpe\": 1.1325133555632056, \"validation_return\": 0.00926454378012398, \"validation_returns_over_hodl\": 0.055394736249717136, \"validation_sharpe\": 0.424803395105981}, {\"centeredness_margin\": 0.9103668546221256, \"continuous_test_metrics\": [{\"annualised_returns\": -0.8995912983876561, \"annualised_returns_over_hodl\": 0.0022168521306837885, \"annualised_returns_over_uniform_hodl\": -0.11799504557059592, \"calmar\": -1.263163418435762, \"daily_log_sharpe\": -2.3735378822818505, \"daily_returns\": 0.007841752006355557, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": -0.8300085117106778, \"return\": -0.6037442596187128, \"returns_over_hodl\": 0.0008922198395520109, \"returns_over_uniform_hodl\": -0.049309576570143454, \"sharpe\": -1.9321437059969275, \"sterling\": -2.0830910283908874, \"ulcer\": -0.18108046871575353}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": 0.5042176396694923, \"optuna_trial_number\": 30, \"price_ratio\": 1.6141768400739163, \"shift_exponent\": 0.0006546301279910199, \"step\": 30, \"test_objective\": [{\"annualised_returns\": -0.8995912983876561, \"annualised_returns_over_hodl\": 0.0022168521306837885, \"annualised_returns_over_uniform_hodl\": -0.11799504557059592, \"calmar\": -1.263163418435762, \"daily_log_sharpe\": -2.3735378822818505, \"daily_returns\": 0.007841752006355557, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": -0.8300085117106778, \"return\": -0.6037442596187128, \"returns_over_hodl\": 0.0008922198395520109, \"returns_over_uniform_hodl\": -0.049309576570143454, \"sharpe\": -1.9321437059969275, \"sterling\": -2.0830910283908874, \"ulcer\": -0.18108046871575353}], \"train_objective\": [{\"annualised_returns\": 0.2804104935167726, \"annualised_returns_over_hodl\": 0.05888122969575904, \"annualised_returns_over_uniform_hodl\": 0.05888122969575904, \"calmar\": 0.4428926442863942, \"daily_log_sharpe\": 0.3150677553499671, \"daily_returns\": 0.02051709935549667, \"fee_revenue_over_value\": 0.014171387214610073, \"jax_sharpe\": 0.7197216935951416, \"return\": 0.1619140679818749, \"returns_over_hodl\": 0.035345491660849326, \"returns_over_uniform_hodl\": 0.035345491660849326, \"sharpe\": 0.7422951618674205, \"sterling\": 1.073229720710388, \"ulcer\": -0.13175893347382314}], \"train_return\": 0.1619140679818749, \"train_returns_over_hodl\": 0.035345491660849326, \"train_sharpe\": 0.7197216935951417, \"validation_return\": -0.06425497655468537, \"validation_returns_over_hodl\": 0.0033002796670562606, \"validation_sharpe\": -0.2413390224551247}, {\"centeredness_margin\": 0.34863305634337916, \"continuous_test_metrics\": [{\"annualised_returns\": -0.8721885429224807, \"annualised_returns_over_hodl\": 0.21296949285926314, \"annualised_returns_over_uniform_hodl\": 0.12271483013932771, \"calmar\": -1.2341002752647967, \"daily_log_sharpe\": -2.3286798095487233, \"daily_returns\": 0.00744750483909391, \"fee_revenue_over_value\": 0.02820969943855697, \"jax_sharpe\": -0.7229312506059263, \"return\": -0.5633013391082302, \"returns_over_hodl\": 0.08086018035311349, \"returns_over_uniform_hodl\": 0.047720430333616504, \"sharpe\": -1.9154018634255063, \"sterling\": -2.0966369376202514, \"ulcer\": -0.16937750661744033}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": 0.7256540605716353, \"optuna_trial_number\": 31, \"price_ratio\": 1.9530470539423486, \"shift_exponent\": 0.002227253208125497, \"step\": 31, \"test_objective\": [{\"annualised_returns\": -0.8721885429224807, \"annualised_returns_over_hodl\": 0.21296949285926314, \"annualised_returns_over_uniform_hodl\": 0.12271483013932771, \"calmar\": -1.2341002752647967, \"daily_log_sharpe\": -2.3286798095487233, \"daily_returns\": 0.00744750483909391, \"fee_revenue_over_value\": 0.02820969943855697, \"jax_sharpe\": -0.7229312506059263, \"return\": -0.5633013391082302, \"returns_over_hodl\": 0.08086018035311349, \"returns_over_uniform_hodl\": 0.047720430333616504, \"sharpe\": -1.9154018634255063, \"sterling\": -2.0966369376202514, \"ulcer\": -0.16937750661744033}], \"train_objective\": [{\"annualised_returns\": 0.5890900603118925, \"annualised_returns_over_hodl\": 0.3141548321263601, \"annualised_returns_over_uniform_hodl\": 0.3141548321263601, \"calmar\": 0.941467016942952, \"daily_log_sharpe\": 0.5693164294379366, \"daily_returns\": 0.02071493222098789, \"fee_revenue_over_value\": 0.12182202189619348, \"jax_sharpe\": 0.9674022730913144, \"return\": 0.3247122815099659, \"returns_over_hodl\": 0.180409916880786, \"returns_over_uniform_hodl\": 0.180409916880786, \"sharpe\": 0.995120078827093, \"sterling\": 2.2802987111061284, \"ulcer\": -0.12928435221540358}], \"train_return\": 0.3247122815099659, \"train_returns_over_hodl\": 0.180409916880786, \"train_sharpe\": 0.9674022730913145, \"validation_return\": -0.030675039451485198, \"validation_returns_over_hodl\": 0.009166644171284988, \"validation_sharpe\": 0.030930367669977386}, {\"centeredness_margin\": 0.9754109684317449, \"continuous_test_metrics\": [{\"annualised_returns\": -0.8776810140001022, \"annualised_returns_over_hodl\": 0.07475994878596404, \"annualised_returns_over_uniform_hodl\": 0.07446814808157542, \"calmar\": -1.3357986071516874, \"daily_log_sharpe\": -2.6139901968517436, \"daily_returns\": 0.006633269524235165, \"fee_revenue_over_value\": 0.027266267500697976, \"jax_sharpe\": -1.2808121119590294, \"return\": -0.5709585336944588, \"returns_over_hodl\": 0.029461986989348432, \"returns_over_uniform_hodl\": 0.02934941176752104, \"sharpe\": -2.241860989757871, \"sterling\": -2.4155402643068755, \"ulcer\": -0.15771698924288263}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": 0.5557290655136816, \"optuna_trial_number\": 32, \"price_ratio\": 29.387167018447194, \"shift_exponent\": 11.55775662569705, \"step\": 32, \"test_objective\": [{\"annualised_returns\": -0.8776810140001022, \"annualised_returns_over_hodl\": 0.07475994878596404, \"annualised_returns_over_uniform_hodl\": 0.07446814808157542, \"calmar\": -1.3357986071516874, \"daily_log_sharpe\": -2.6139901968517436, \"daily_returns\": 0.006633269524235165, \"fee_revenue_over_value\": 0.027266267500697976, \"jax_sharpe\": -1.2808121119590294, \"return\": -0.5709585336944588, \"returns_over_hodl\": 0.029461986989348432, \"returns_over_uniform_hodl\": 0.02934941176752104, \"sharpe\": -2.241860989757871, \"sterling\": -2.4155402643068755, \"ulcer\": -0.15771698924288263}], \"train_objective\": [{\"annualised_returns\": 0.27666079545491784, \"annualised_returns_over_hodl\": 0.05578028283939607, \"annualised_returns_over_uniform_hodl\": 0.05578028283939607, \"calmar\": 0.42705645702008543, \"daily_log_sharpe\": 0.3098886870320539, \"daily_returns\": 0.020485734986637708, \"fee_revenue_over_value\": 0.04582428069293049, \"jax_sharpe\": 0.7172785202056795, \"return\": 0.15984703826094582, \"returns_over_hodl\": 0.033503625759001565, \"returns_over_uniform_hodl\": 0.033503625759001565, \"sharpe\": 0.7345013446855931, \"sterling\": 1.0510858783590404, \"ulcer\": -0.13526875754067397}], \"train_return\": 0.15984703826094582, \"train_returns_over_hodl\": 0.033503625759001565, \"train_sharpe\": 0.7172785202056794, \"validation_return\": 0.0018360202893343835, \"validation_returns_over_hodl\": 0.0037963725822527383, \"validation_sharpe\": 0.3309834246769537}, {\"centeredness_margin\": 0.34284168893653005, \"continuous_test_metrics\": [{\"annualised_returns\": -0.8072641774760368, \"annualised_returns_over_hodl\": 0.7881170382075446, \"annualised_returns_over_uniform_hodl\": 0.6930201031626848, \"calmar\": -1.3282280853252688, \"daily_log_sharpe\": -2.0295735180563907, \"daily_returns\": 0.008449574140622003, \"fee_revenue_over_value\": 0.1526957242226009, \"jax_sharpe\": -1.0622890589641318, \"return\": -0.4847389311864405, \"returns_over_hodl\": 0.26371573209017773, \"returns_over_uniform_hodl\": 0.23620610067612846, \"sharpe\": -1.6385558413334749, \"sterling\": -2.2808157749205695, \"ulcer\": -0.1519086412247069}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": 0.8429838821123861, \"optuna_trial_number\": 33, \"price_ratio\": 1.2663874278804863, \"shift_exponent\": 0.05411369015142587, \"step\": 33, \"test_objective\": [{\"annualised_returns\": -0.8072641774760368, \"annualised_returns_over_hodl\": 0.7881170382075446, \"annualised_returns_over_uniform_hodl\": 0.6930201031626848, \"calmar\": -1.3282280853252688, \"daily_log_sharpe\": -2.0295735180563907, \"daily_returns\": 0.008449574140622003, \"fee_revenue_over_value\": 0.1526957242226009, \"jax_sharpe\": -1.0622890589641318, \"return\": -0.4847389311864405, \"returns_over_hodl\": 0.26371573209017773, \"returns_over_uniform_hodl\": 0.23620610067612846, \"sharpe\": -1.6385558413334749, \"sterling\": -2.2808157749205695, \"ulcer\": -0.1519086412247069}], \"train_objective\": [{\"annualised_returns\": 0.7629089116788395, \"annualised_returns_over_hodl\": 0.45790054493617705, \"annualised_returns_over_uniform_hodl\": 0.4579005449361766, \"calmar\": 1.197403187064924, \"daily_log_sharpe\": 0.6716780522447084, \"daily_returns\": 0.021302076906850993, \"fee_revenue_over_value\": 0.30874756805699943, \"jax_sharpe\": 1.0854440335789786, \"return\": 0.4108843678595733, \"returns_over_hodl\": 0.2571951831647532, \"returns_over_uniform_hodl\": 0.257195183164753, \"sharpe\": 1.1004557528880325, \"sterling\": 2.93797993430883, \"ulcer\": -0.13083982186555257}], \"train_return\": 0.4108843678595733, \"train_returns_over_hodl\": 0.2571951831647532, \"train_sharpe\": 1.0854440335789786, \"validation_return\": 0.025844576371621075, \"validation_returns_over_hodl\": 0.05051003993434433, \"validation_sharpe\": 0.5828647898547503}, {\"centeredness_margin\": 0.19881524068707082, \"continuous_test_metrics\": [{\"annualised_returns\": -0.81984744441909, \"annualised_returns_over_hodl\": 0.7119267353571237, \"annualised_returns_over_uniform_hodl\": 0.5824868166201556, \"calmar\": -1.1585284395315938, \"daily_log_sharpe\": -2.0169614877531465, \"daily_returns\": 0.00837668742609159, \"fee_revenue_over_value\": 0.12897234134063357, \"jax_sharpe\": -0.5109101059893029, \"return\": -0.49856084167861825, \"returns_over_hodl\": 0.24174754758174433, \"returns_over_uniform_hodl\": 0.20304479448085666, \"sharpe\": -1.6097885632398288, \"sterling\": -2.040318995780865, \"ulcer\": -0.15912775506286378}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": 0.814618954171446, \"optuna_trial_number\": 34, \"price_ratio\": 1.3589787512547185, \"shift_exponent\": 0.02194170221482421, \"step\": 34, \"test_objective\": [{\"annualised_returns\": -0.81984744441909, \"annualised_returns_over_hodl\": 0.7119267353571237, \"annualised_returns_over_uniform_hodl\": 0.5824868166201556, \"calmar\": -1.1585284395315938, \"daily_log_sharpe\": -2.0169614877531465, \"daily_returns\": 0.00837668742609159, \"fee_revenue_over_value\": 0.12897234134063357, \"jax_sharpe\": -0.5109101059893029, \"return\": -0.49856084167861825, \"returns_over_hodl\": 0.24174754758174433, \"returns_over_uniform_hodl\": 0.20304479448085666, \"sharpe\": -1.6097885632398288, \"sterling\": -2.040318995780865, \"ulcer\": -0.15912775506286378}], \"train_objective\": [{\"annualised_returns\": 0.8494750320891125, \"annualised_returns_over_hodl\": 0.5294894927729432, \"annualised_returns_over_uniform_hodl\": 0.5294894927729434, \"calmar\": 1.3781093205377053, \"daily_log_sharpe\": 0.7203686028330245, \"daily_returns\": 0.02111342140893369, \"fee_revenue_over_value\": 0.26077946830987675, \"jax_sharpe\": 1.138597693820723, \"return\": 0.45254915695077624, \"returns_over_hodl\": 0.29432138099236194, \"returns_over_uniform_hodl\": 0.29432138099236216, \"sharpe\": 1.1558090756481314, \"sterling\": 3.2837110997018857, \"ulcer\": -0.1274150358909959}], \"train_return\": 0.45254915695077624, \"train_returns_over_hodl\": 0.29432138099236194, \"train_sharpe\": 1.138597693820723, \"validation_return\": 0.003043850074410104, \"validation_returns_over_hodl\": 0.042718753301566714, \"validation_sharpe\": 0.36091353093407913}, {\"centeredness_margin\": 0.056738126563906394, \"continuous_test_metrics\": [{\"annualised_returns\": -0.8620070968142298, \"annualised_returns_over_hodl\": 0.2762285056487286, \"annualised_returns_over_uniform_hodl\": 0.2121501655886726, \"calmar\": -1.3189935079335808, \"daily_log_sharpe\": -2.2744735603459048, \"daily_returns\": 0.00741005684402992, \"fee_revenue_over_value\": 0.051316127405936225, \"jax_sharpe\": -1.1231941027529864, \"return\": -0.549610994518967, \"returns_over_hodl\": 0.10321812835236877, \"returns_over_uniform_hodl\": 0.08056608572259227, \"sharpe\": -1.8676554771789755, \"sterling\": -2.2220691218697097, \"ulcer\": -0.16442452583100303}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": 0.6596128356226972, \"optuna_trial_number\": 35, \"price_ratio\": 4.207948245533922, \"shift_exponent\": 0.002663152932055363, \"step\": 35, \"test_objective\": [{\"annualised_returns\": -0.8620070968142298, \"annualised_returns_over_hodl\": 0.2762285056487286, \"annualised_returns_over_uniform_hodl\": 0.2121501655886726, \"calmar\": -1.3189935079335808, \"daily_log_sharpe\": -2.2744735603459048, \"daily_returns\": 0.00741005684402992, \"fee_revenue_over_value\": 0.051316127405936225, \"jax_sharpe\": -1.1231941027529864, \"return\": -0.549610994518967, \"returns_over_hodl\": 0.10321812835236877, \"returns_over_uniform_hodl\": 0.08056608572259227, \"sharpe\": -1.8676554771789755, \"sterling\": -2.2220691218697097, \"ulcer\": -0.16442452583100303}], \"train_objective\": [{\"annualised_returns\": 0.4541381485688807, \"annualised_returns_over_hodl\": 0.2025515244529359, \"annualised_returns_over_uniform_hodl\": 0.2025515244529359, \"calmar\": 0.713425418156947, \"daily_log_sharpe\": 0.460849426553222, \"daily_returns\": 0.020538471387358505, \"fee_revenue_over_value\": 0.08797453142760561, \"jax_sharpe\": 0.8656902810632758, \"return\": 0.25522441625800996, \"returns_over_hodl\": 0.11849144115503107, \"returns_over_uniform_hodl\": 0.11849144115503107, \"sharpe\": 0.885737817717333, \"sterling\": 1.7429069299087478, \"ulcer\": -0.13219268483706625}], \"train_return\": 0.25522441625800996, \"train_returns_over_hodl\": 0.11849144115503107, \"train_sharpe\": 0.8656902810632758, \"validation_return\": -0.009698140187002324, \"validation_returns_over_hodl\": 0.010674181533087745, \"validation_sharpe\": 0.22583484381319383}, {\"centeredness_margin\": 0.17469164237662468, \"continuous_test_metrics\": [{\"annualised_returns\": -0.6915118066471356, \"annualised_returns_over_hodl\": 1.9862327342520305, \"annualised_returns_over_uniform_hodl\": 1.70980612786604, \"calmar\": -0.9762060778171199, \"daily_log_sharpe\": -1.4591698826069535, \"daily_returns\": 0.011050359381188367, \"fee_revenue_over_value\": 0.32866627205572885, \"jax_sharpe\": -0.1441386092023207, \"return\": -0.37727393484420846, \"returns_over_hodl\": 0.5536395396938076, \"returns_over_uniform_hodl\": 0.4940343980736073, \"sharpe\": -1.0491034080100696, \"sterling\": -1.7905980197949505, \"ulcer\": -0.14391652092024845}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": 0.773639637366359, \"optuna_trial_number\": 36, \"price_ratio\": 1.0415728273755485, \"shift_exponent\": 0.01603391183300729, \"step\": 36, \"test_objective\": [{\"annualised_returns\": -0.6915118066471356, \"annualised_returns_over_hodl\": 1.9862327342520305, \"annualised_returns_over_uniform_hodl\": 1.70980612786604, \"calmar\": -0.9762060778171199, \"daily_log_sharpe\": -1.4591698826069535, \"daily_returns\": 0.011050359381188367, \"fee_revenue_over_value\": 0.32866627205572885, \"jax_sharpe\": -0.1441386092023207, \"return\": -0.37727393484420846, \"returns_over_hodl\": 0.5536395396938076, \"returns_over_uniform_hodl\": 0.4940343980736073, \"sharpe\": -1.0491034080100696, \"sterling\": -1.7905980197949505, \"ulcer\": -0.14391652092024845}], \"train_objective\": [{\"annualised_returns\": 0.6842904545314499, \"annualised_returns_over_hodl\": 0.392884201347518, \"annualised_returns_over_uniform_hodl\": 0.39288420134751756, \"calmar\": 1.0960728069645036, \"daily_log_sharpe\": 0.6186808101422571, \"daily_returns\": 0.021446019124589502, \"fee_revenue_over_value\": 0.4140330808586392, \"jax_sharpe\": 1.0292093063018064, \"return\": 0.37234275841640097, \"returns_over_hodl\": 0.2228519535938689, \"returns_over_uniform_hodl\": 0.22285195359386867, \"sharpe\": 1.0602567983092375, \"sterling\": 2.5894128628991626, \"ulcer\": -0.13103179295646084}], \"train_return\": 0.37234275841640097, \"train_returns_over_hodl\": 0.2228519535938689, \"train_sharpe\": 1.0292093063018062, \"validation_return\": 0.06620187187094984, \"validation_returns_over_hodl\": 0.14633458175865455, \"validation_sharpe\": 0.9700793400859876}, {\"centeredness_margin\": 0.158140526678858, \"continuous_test_metrics\": [{\"annualised_returns\": -0.8322461631879738, \"annualised_returns_over_hodl\": 0.6079293271112103, \"annualised_returns_over_uniform_hodl\": 0.47357462866105937, \"calmar\": -1.3114584046417197, \"daily_log_sharpe\": -2.0614405320060873, \"daily_returns\": 0.008302422325499605, \"fee_revenue_over_value\": 0.10962623785366166, \"jax_sharpe\": -0.9104636039397872, \"return\": -0.5127562523860177, \"returns_over_hodl\": 0.21079747071311772, \"returns_over_uniform_hodl\": 0.16898739255352413, \"sharpe\": -1.6499326538784165, \"sterling\": -2.1585877180356183, \"ulcer\": -0.16223804969557437}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": 0.8632248008859916, \"optuna_trial_number\": 37, \"price_ratio\": 1.4772569104698698, \"shift_exponent\": 0.015748631214100816, \"step\": 37, \"test_objective\": [{\"annualised_returns\": -0.8322461631879738, \"annualised_returns_over_hodl\": 0.6079293271112103, \"annualised_returns_over_uniform_hodl\": 0.47357462866105937, \"calmar\": -1.3114584046417197, \"daily_log_sharpe\": -2.0614405320060873, \"daily_returns\": 0.008302422325499605, \"fee_revenue_over_value\": 0.10962623785366166, \"jax_sharpe\": -0.9104636039397872, \"return\": -0.5127562523860177, \"returns_over_hodl\": 0.21079747071311772, \"returns_over_uniform_hodl\": 0.16898739255352413, \"sharpe\": -1.6499326538784165, \"sterling\": -2.1585877180356183, \"ulcer\": -0.16223804969557437}], \"train_objective\": [{\"annualised_returns\": 0.9110608740485684, \"annualised_returns_over_hodl\": 0.5804201063504428, \"annualised_returns_over_uniform_hodl\": 0.5804201063504419, \"calmar\": 1.4819133625769714, \"daily_log_sharpe\": 0.7656844461069874, \"daily_returns\": 0.020972767133717006, \"fee_revenue_over_value\": 0.23725387999563735, \"jax_sharpe\": 1.177154612002215, \"return\": 0.4817255744630622, \"returns_over_hodl\": 0.32031957928135246, \"returns_over_uniform_hodl\": 0.320319579281352, \"sharpe\": 1.1972452351162441, \"sterling\": 3.549336736933798, \"ulcer\": -0.12637304237351188}], \"train_return\": 0.4817255744630622, \"train_returns_over_hodl\": 0.32031957928135246, \"train_sharpe\": 1.177154612002215, \"validation_return\": -0.01032550662359144, \"validation_returns_over_hodl\": 0.03615145630521632, \"validation_sharpe\": 0.23358565022152356}, {\"centeredness_margin\": 0.21095749587360796, \"continuous_test_metrics\": [{\"annualised_returns\": -0.7801797910311321, \"annualised_returns_over_hodl\": 1.0775909371959318, \"annualised_returns_over_uniform_hodl\": 0.930933378092931, \"calmar\": -1.103270364394258, \"daily_log_sharpe\": -1.8861142557297315, \"daily_returns\": 0.009119883191085176, \"fee_revenue_over_value\": 0.20033778514583422, \"jax_sharpe\": -0.3232713450065769, \"return\": -0.45671746507924127, \"returns_over_hodl\": 0.3424355366763987, \"returns_over_uniform_hodl\": 0.3034347531946895, \"sharpe\": -1.495477470203065, \"sterling\": -2.020157650532965, \"ulcer\": -0.1493575090338054}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": 0.9342852047340188, \"optuna_trial_number\": 38, \"price_ratio\": 1.1397865625195078, \"shift_exponent\": 0.4890191843617376, \"step\": 38, \"test_objective\": [{\"annualised_returns\": -0.7801797910311321, \"annualised_returns_over_hodl\": 1.0775909371959318, \"annualised_returns_over_uniform_hodl\": 0.930933378092931, \"calmar\": -1.103270364394258, \"daily_log_sharpe\": -1.8861142557297315, \"daily_returns\": 0.009119883191085176, \"fee_revenue_over_value\": 0.20033778514583422, \"jax_sharpe\": -0.3232713450065769, \"return\": -0.45671746507924127, \"returns_over_hodl\": 0.3424355366763987, \"returns_over_uniform_hodl\": 0.3034347531946895, \"sharpe\": -1.495477470203065, \"sterling\": -2.020157650532965, \"ulcer\": -0.1493575090338054}], \"train_objective\": [{\"annualised_returns\": 0.778066362657166, \"annualised_returns_over_hodl\": 0.4704355408708798, \"annualised_returns_over_uniform_hodl\": 0.47043554087087935, \"calmar\": 1.1609168768595437, \"daily_log_sharpe\": 0.6757640427577909, \"daily_returns\": 0.021882354608269355, \"fee_revenue_over_value\": 0.44883367091587784, \"jax_sharpe\": 1.094058652203018, \"return\": 0.41823681709099736, \"returns_over_hodl\": 0.26374672202135896, \"returns_over_uniform_hodl\": 0.26374672202135874, \"sharpe\": 1.105812512264446, \"sterling\": 2.9495215900059284, \"ulcer\": -0.13696994003539864}], \"train_return\": 0.41823681709099736, \"train_returns_over_hodl\": 0.26374672202135896, \"train_sharpe\": 1.0940586522030182, \"validation_return\": 0.03261543258932087, \"validation_returns_over_hodl\": 0.06514899863794477, \"validation_sharpe\": 0.6506803638212268}, {\"centeredness_margin\": 0.01857886741771317, \"continuous_test_metrics\": [{\"annualised_returns\": -0.7042503751998967, \"annualised_returns_over_hodl\": 1.9188659428479533, \"annualised_returns_over_uniform_hodl\": 1.597908648907973, \"calmar\": -1.1055474194519757, \"daily_log_sharpe\": -1.4753089001740498, \"daily_returns\": 0.010774709671393306, \"fee_revenue_over_value\": 0.2926748832290991, \"jax_sharpe\": -0.41412340734054187, \"return\": -0.3877607549223031, \"returns_over_hodl\": 0.5394278313042526, \"returns_over_uniform_hodl\": 0.46887458736428234, \"sharpe\": -1.0591912580379463, \"sterling\": -1.902900249330155, \"ulcer\": -0.14702629294863795}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": 1.081891624838662, \"optuna_trial_number\": 39, \"price_ratio\": 1.0528456221492624, \"shift_exponent\": 0.031158196496607195, \"step\": 39, \"test_objective\": [{\"annualised_returns\": -0.7042503751998967, \"annualised_returns_over_hodl\": 1.9188659428479533, \"annualised_returns_over_uniform_hodl\": 1.597908648907973, \"calmar\": -1.1055474194519757, \"daily_log_sharpe\": -1.4753089001740498, \"daily_returns\": 0.010774709671393306, \"fee_revenue_over_value\": 0.2926748832290991, \"jax_sharpe\": -0.41412340734054187, \"return\": -0.3877607549223031, \"returns_over_hodl\": 0.5394278313042526, \"returns_over_uniform_hodl\": 0.46887458736428234, \"sharpe\": -1.0591912580379463, \"sterling\": -1.902900249330155, \"ulcer\": -0.14702629294863795}], \"train_objective\": [{\"annualised_returns\": 1.3547402607591184, \"annualised_returns_over_hodl\": 0.9473366358302724, \"annualised_returns_over_uniform_hodl\": 0.9473366358302695, \"calmar\": 2.192330465592307, \"daily_log_sharpe\": 0.9747661457793294, \"daily_returns\": 0.02231430175898761, \"fee_revenue_over_value\": 0.6566197156803059, \"jax_sharpe\": 1.4040918008067806, \"return\": 0.681955707734037, \"returns_over_hodl\": 0.4987384240904418, \"returns_over_uniform_hodl\": 0.49873842409044045, \"sharpe\": 1.4146616736067483, \"sterling\": 5.17948074170945, \"ulcer\": -0.12890739822641475}], \"train_return\": 0.681955707734037, \"train_returns_over_hodl\": 0.4987384240904418, \"train_sharpe\": 1.4040918008067806, \"validation_return\": 0.06529023120515398, \"validation_returns_over_hodl\": 0.11344018637957065, \"validation_sharpe\": 0.9621069450981972}, {\"centeredness_margin\": 0.02582015053390169, \"continuous_test_metrics\": [{\"annualised_returns\": -0.8584236953316762, \"annualised_returns_over_hodl\": 0.3697988987706311, \"annualised_returns_over_uniform_hodl\": 0.24362729665968663, \"calmar\": -1.2096559412448438, \"daily_log_sharpe\": -2.1242344113417984, \"daily_returns\": 0.008154341214277548, \"fee_revenue_over_value\": 0.07103357146123598, \"jax_sharpe\": -0.6297707635675709, \"return\": -0.5449367218026143, \"returns_over_hodl\": 0.13510717098263303, \"returns_over_uniform_hodl\": 0.09178052593148389, \"sharpe\": -1.6938734237582265, \"sterling\": -2.04578088585315, \"ulcer\": -0.17100368980119884}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": 0.7502174571279185, \"optuna_trial_number\": 40, \"price_ratio\": 2.2392828694765954, \"shift_exponent\": 0.010402546677873978, \"step\": 40, \"test_objective\": [{\"annualised_returns\": -0.8584236953316762, \"annualised_returns_over_hodl\": 0.3697988987706311, \"annualised_returns_over_uniform_hodl\": 0.24362729665968663, \"calmar\": -1.2096559412448438, \"daily_log_sharpe\": -2.1242344113417984, \"daily_returns\": 0.008154341214277548, \"fee_revenue_over_value\": 0.07103357146123598, \"jax_sharpe\": -0.6297707635675709, \"return\": -0.5449367218026143, \"returns_over_hodl\": 0.13510717098263303, \"returns_over_uniform_hodl\": 0.09178052593148389, \"sharpe\": -1.6938734237582265, \"sterling\": -2.04578088585315, \"ulcer\": -0.17100368980119884}], \"train_objective\": [{\"annualised_returns\": 0.6223914020186134, \"annualised_returns_over_hodl\": 0.34169456710625745, \"annualised_returns_over_uniform_hodl\": 0.34169456710625745, \"calmar\": 0.9897940011891356, \"daily_log_sharpe\": 0.5864821381193329, \"daily_returns\": 0.020672945750437464, \"fee_revenue_over_value\": 0.14171529111279121, \"jax_sharpe\": 0.9912527171194898, \"return\": 0.3414978496656531, \"returns_over_hodl\": 0.19536701465062545, \"returns_over_uniform_hodl\": 0.19536701465062545, \"sharpe\": 1.0121097127244316, \"sterling\": 2.4053353882577078, \"ulcer\": -0.12999120466998676}], \"train_return\": 0.3414978496656531, \"train_returns_over_hodl\": 0.19536701465062545, \"train_sharpe\": 0.9912527171194899, \"validation_return\": -0.018765930898336336, \"validation_returns_over_hodl\": 0.017426776352561735, \"validation_sharpe\": 0.15428465234127353}, {\"centeredness_margin\": 0.2849133996138875, \"continuous_test_metrics\": [{\"annualised_returns\": -0.8728065293463768, \"annualised_returns_over_hodl\": 0.13208553073921148, \"annualised_returns_over_uniform_hodl\": 0.11728634556683626, \"calmar\": -1.334093565943, \"daily_log_sharpe\": -2.5286938464129682, \"daily_returns\": 0.006766800040104945, \"fee_revenue_over_value\": 0.025721844853322767, \"jax_sharpe\": -1.1529861642398391, \"return\": -0.564152950927285, \"returns_over_hodl\": 0.051233537467483714, \"returns_over_uniform_hodl\": 0.04567725690204405, \"sharpe\": -2.1484267836253195, \"sterling\": -2.345887564320988, \"ulcer\": -0.1581300557257739}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": 0.579820942365112, \"optuna_trial_number\": 41, \"price_ratio\": 55.04563104589619, \"shift_exponent\": 4.7774066367975575e-05, \"step\": 41, \"test_objective\": [{\"annualised_returns\": -0.8728065293463768, \"annualised_returns_over_hodl\": 0.13208553073921148, \"annualised_returns_over_uniform_hodl\": 0.11728634556683626, \"calmar\": -1.334093565943, \"daily_log_sharpe\": -2.5286938464129682, \"daily_returns\": 0.006766800040104945, \"fee_revenue_over_value\": 0.025721844853322767, \"jax_sharpe\": -1.1529861642398391, \"return\": -0.564152950927285, \"returns_over_hodl\": 0.051233537467483714, \"returns_over_uniform_hodl\": 0.04567725690204405, \"sharpe\": -2.1484267836253195, \"sterling\": -2.345887564320988, \"ulcer\": -0.1581300557257739}], \"train_objective\": [{\"annualised_returns\": 0.3234200220193435, \"annualised_returns_over_hodl\": 0.09444949679450132, \"annualised_returns_over_uniform_hodl\": 0.09444949679450132, \"calmar\": 0.5033104823470398, \"daily_log_sharpe\": 0.3525468190023254, \"daily_returns\": 0.02042728646605609, \"fee_revenue_over_value\": 0.04286234434425639, \"jax_sharpe\": 0.7581760516386005, \"return\": 0.1854555565903402, \"returns_over_hodl\": 0.05632258004406698, \"returns_over_uniform_hodl\": 0.05632258004406698, \"sharpe\": 0.7770758406730276, \"sterling\": 1.233763628672687, \"ulcer\": -0.1340352017661839}], \"train_return\": 0.1854555565903402, \"train_returns_over_hodl\": 0.05632258004406698, \"train_sharpe\": 0.7581760516386004, \"validation_return\": -0.0014876809631858556, \"validation_returns_over_hodl\": 0.005185001154583535, \"validation_sharpe\": 0.299057082866298}, {\"centeredness_margin\": 0.16044996575830076, \"continuous_test_metrics\": [{\"annualised_returns\": -0.863977643259731, \"annualised_returns_over_hodl\": 0.2696194904470086, \"annualised_returns_over_uniform_hodl\": 0.19484059281304367, \"calmar\": -1.3198766902537769, \"daily_log_sharpe\": -2.313433045794787, \"daily_returns\": 0.007568867116619673, \"fee_revenue_over_value\": 0.05484651482915625, \"jax_sharpe\": -1.220834371019268, \"return\": -0.5522123659876216, \"returns_over_hodl\": 0.10091369007186546, \"returns_over_uniform_hodl\": 0.07432491697471799, \"sharpe\": -1.9111515512541142, \"sterling\": -2.266925700859084, \"ulcer\": -0.16439744294232406}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": 0.6780685028196969, \"optuna_trial_number\": 42, \"price_ratio\": 3.4720048675052677, \"shift_exponent\": 0.18381164717344617, \"step\": 42, \"test_objective\": [{\"annualised_returns\": -0.863977643259731, \"annualised_returns_over_hodl\": 0.2696194904470086, \"annualised_returns_over_uniform_hodl\": 0.19484059281304367, \"calmar\": -1.3198766902537769, \"daily_log_sharpe\": -2.313433045794787, \"daily_returns\": 0.007568867116619673, \"fee_revenue_over_value\": 0.05484651482915625, \"jax_sharpe\": -1.220834371019268, \"return\": -0.5522123659876216, \"returns_over_hodl\": 0.10091369007186546, \"returns_over_uniform_hodl\": 0.07432491697471799, \"sharpe\": -1.9111515512541142, \"sterling\": -2.266925700859084, \"ulcer\": -0.16439744294232406}], \"train_objective\": [{\"annualised_returns\": 0.4865709681657093, \"annualised_returns_over_hodl\": 0.2293730040261508, \"annualised_returns_over_uniform_hodl\": 0.22937300402615102, \"calmar\": 0.7661747719621381, \"daily_log_sharpe\": 0.48617415587675983, \"daily_returns\": 0.020560404705969086, \"fee_revenue_over_value\": 0.09876619587269184, \"jax_sharpe\": 0.8909476033283769, \"return\": 0.27214785069976566, \"returns_over_hodl\": 0.13357138728488782, \"returns_over_uniform_hodl\": 0.13357138728488804, \"sharpe\": 0.9111767776979174, \"sterling\": 1.8698583570482326, \"ulcer\": -0.13176744880689942}], \"train_return\": 0.27214785069976566, \"train_returns_over_hodl\": 0.13357138728488782, \"train_sharpe\": 0.8909476033283769, \"validation_return\": -0.011609015601315109, \"validation_returns_over_hodl\": 0.012048358747667853, \"validation_sharpe\": 0.20984962190090417}, {\"centeredness_margin\": 0.24257084311279747, \"continuous_test_metrics\": [{\"annualised_returns\": -0.8455260806990775, \"annualised_returns_over_hodl\": 0.44904957159546965, \"annualised_returns_over_uniform_hodl\": 0.3569218600152855, \"calmar\": -1.297180332771124, \"daily_log_sharpe\": -2.2257433962218816, \"daily_returns\": 0.00790587339995613, \"fee_revenue_over_value\": 0.08598049918063899, \"jax_sharpe\": -0.8965913035341537, \"return\": -0.5286741115034146, \"returns_over_hodl\": 0.16111253772003664, \"returns_over_uniform_hodl\": 0.13079751999835665, \"sharpe\": -1.83054596798548, \"sterling\": -2.181706399702556, \"ulcer\": -0.16008301764424326}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": 0.7850727823577227, \"optuna_trial_number\": 43, \"price_ratio\": 1.8762403814925317, \"shift_exponent\": 0.6425144653073326, \"step\": 43, \"test_objective\": [{\"annualised_returns\": -0.8455260806990775, \"annualised_returns_over_hodl\": 0.44904957159546965, \"annualised_returns_over_uniform_hodl\": 0.3569218600152855, \"calmar\": -1.297180332771124, \"daily_log_sharpe\": -2.2257433962218816, \"daily_returns\": 0.00790587339995613, \"fee_revenue_over_value\": 0.08598049918063899, \"jax_sharpe\": -0.8965913035341537, \"return\": -0.5286741115034146, \"returns_over_hodl\": 0.16111253772003664, \"returns_over_uniform_hodl\": 0.13079751999835665, \"sharpe\": -1.83054596798548, \"sterling\": -2.181706399702556, \"ulcer\": -0.16008301764424326}], \"train_objective\": [{\"annualised_returns\": 0.698790795648248, \"annualised_returns_over_hodl\": 0.40487577679188247, \"annualised_returns_over_uniform_hodl\": 0.40487577679188247, \"calmar\": 1.1140848823614709, \"daily_log_sharpe\": 0.6350991473707663, \"daily_returns\": 0.020743987679901748, \"fee_revenue_over_value\": 0.17317442484029008, \"jax_sharpe\": 1.043111829411682, \"return\": 0.37950365153153176, \"returns_over_hodl\": 0.2292328027524413, \"returns_over_uniform_hodl\": 0.2292328027524413, \"sharpe\": 1.0639099220486192, \"sterling\": 2.6991055311062584, \"ulcer\": -0.12936184836427683}], \"train_return\": 0.37950365153153176, \"train_returns_over_hodl\": 0.2292328027524413, \"train_sharpe\": 1.043111829411682, \"validation_return\": -0.007581142814736319, \"validation_returns_over_hodl\": 0.027123725990594094, \"validation_sharpe\": 0.25264731568428844}, {\"centeredness_margin\": 0.1931959939203841, \"continuous_test_metrics\": [{\"annualised_returns\": -0.7915183237629408, \"annualised_returns_over_hodl\": 0.9782082660671527, \"annualised_returns_over_uniform_hodl\": 0.8313340218137752, \"calmar\": -1.3116955274801285, \"daily_log_sharpe\": -1.9283244018274919, \"daily_returns\": 0.008816479600269834, \"fee_revenue_over_value\": 0.16974437326969777, \"jax_sharpe\": -0.8904716947434885, \"return\": -0.4681821815493634, \"returns_over_hodl\": 0.31619411719015234, \"returns_over_uniform_hodl\": 0.27592878912967445, \"sharpe\": -1.5343650931111694, \"sterling\": -2.1946825651743, \"ulcer\": -0.15201838755143413}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": 0.8626341852242974, \"optuna_trial_number\": 44, \"price_ratio\": 1.2147075016526026, \"shift_exponent\": 0.494541867571596, \"step\": 44, \"test_objective\": [{\"annualised_returns\": -0.7915183237629408, \"annualised_returns_over_hodl\": 0.9782082660671527, \"annualised_returns_over_uniform_hodl\": 0.8313340218137752, \"calmar\": -1.3116955274801285, \"daily_log_sharpe\": -1.9283244018274919, \"daily_returns\": 0.008816479600269834, \"fee_revenue_over_value\": 0.16974437326969777, \"jax_sharpe\": -0.8904716947434885, \"return\": -0.4681821815493634, \"returns_over_hodl\": 0.31619411719015234, \"returns_over_uniform_hodl\": 0.27592878912967445, \"sharpe\": -1.5343650931111694, \"sterling\": -2.1946825651743, \"ulcer\": -0.15201838755143413}], \"train_objective\": [{\"annualised_returns\": 0.7479218857410128, \"annualised_returns_over_hodl\": 0.4455064880810651, \"annualised_returns_over_uniform_hodl\": 0.4455064880810651, \"calmar\": 1.146744038983602, \"daily_log_sharpe\": 0.6591338352957066, \"daily_returns\": 0.02149606593005452, \"fee_revenue_over_value\": 0.3691410308266545, \"jax_sharpe\": 1.0754141984701857, \"return\": 0.4035901274462834, \"returns_over_hodl\": 0.2506955123757528, \"returns_over_uniform_hodl\": 0.2506955123757528, \"sharpe\": 1.0890175290195934, \"sterling\": 2.858518273873777, \"ulcer\": -0.13341836031373042}], \"train_return\": 0.4035901274462834, \"train_returns_over_hodl\": 0.2506955123757528, \"train_sharpe\": 1.0754141984701857, \"validation_return\": 0.022065843252080608, \"validation_returns_over_hodl\": 0.05914217823316603, \"validation_sharpe\": 0.5465831824665555}, {\"centeredness_margin\": 0.27546859682731173, \"continuous_test_metrics\": [{\"annualised_returns\": -0.822199641935005, \"annualised_returns_over_hodl\": 0.658995704406039, \"annualised_returns_over_uniform_hodl\": 0.5618247641334746, \"calmar\": -1.233346372276301, \"daily_log_sharpe\": -2.097550866222166, \"daily_returns\": 0.008210422103103332, \"fee_revenue_over_value\": 0.12385871742581459, \"jax_sharpe\": -0.7335161314321637, \"return\": -0.5012079757070003, \"returns_over_hodl\": 0.22613983510753277, \"returns_over_uniform_hodl\": 0.19669383293290132, \"sharpe\": -1.7063132364119444, \"sterling\": -2.140317782280454, \"ulcer\": -0.15656103923589978}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": 0.7761651086001401, \"optuna_trial_number\": 45, \"price_ratio\": 1.421093811938935, \"shift_exponent\": 2.4086668481645916, \"step\": 45, \"test_objective\": [{\"annualised_returns\": -0.822199641935005, \"annualised_returns_over_hodl\": 0.658995704406039, \"annualised_returns_over_uniform_hodl\": 0.5618247641334746, \"calmar\": -1.233346372276301, \"daily_log_sharpe\": -2.097550866222166, \"daily_returns\": 0.008210422103103332, \"fee_revenue_over_value\": 0.12385871742581459, \"jax_sharpe\": -0.7335161314321637, \"return\": -0.5012079757070003, \"returns_over_hodl\": 0.22613983510753277, \"returns_over_uniform_hodl\": 0.19669383293290132, \"sharpe\": -1.7063132364119444, \"sterling\": -2.140317782280454, \"ulcer\": -0.15656103923589978}], \"train_objective\": [{\"annualised_returns\": 0.6329784198298065, \"annualised_returns_over_hodl\": 0.35044987994966936, \"annualised_returns_over_uniform_hodl\": 0.3504498799496698, \"calmar\": 0.9821550563482253, \"daily_log_sharpe\": 0.5833709760620421, \"daily_returns\": 0.02099954050798942, \"fee_revenue_over_value\": 0.2533905975445141, \"jax_sharpe\": 0.9976988949202014, \"return\": 0.3468058156631051, \"returns_over_hodl\": 0.2000967780787386, \"returns_over_uniform_hodl\": 0.20009677807873882, \"sharpe\": 1.0138955710008444, \"sterling\": 2.4100866511636, \"ulcer\": -0.13315374442921898}], \"train_return\": 0.3468058156631051, \"train_returns_over_hodl\": 0.2000967780787386, \"train_sharpe\": 0.9976988949202013, \"validation_return\": 0.009483412005182323, \"validation_returns_over_hodl\": 0.04100102072902412, \"validation_sharpe\": 0.42040228500445886}, {\"centeredness_margin\": 0.8403641770722874, \"continuous_test_metrics\": [{\"annualised_returns\": -0.8309721930154529, \"annualised_returns_over_hodl\": 0.4893771480108149, \"annualised_returns_over_uniform_hodl\": 0.4847653719523821, \"calmar\": -1.1854157480756249, \"daily_log_sharpe\": -2.211285536951668, \"daily_returns\": 0.008251626620176582, \"fee_revenue_over_value\": 0.1866302765183631, \"jax_sharpe\": -0.5835969869146299, \"return\": -0.5112693809680754, \"returns_over_hodl\": 0.17402009544949926, \"returns_over_uniform_hodl\": 0.17255467063648422, \"sharpe\": -1.833629810307028, \"sterling\": -2.1462166161149248, \"ulcer\": -0.15555236165694922}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": 0.6777580826113744, \"optuna_trial_number\": 46, \"price_ratio\": 1.137106485768301, \"shift_exponent\": 1.7097300813007557, \"step\": 46, \"test_objective\": [{\"annualised_returns\": -0.8309721930154529, \"annualised_returns_over_hodl\": 0.4893771480108149, \"annualised_returns_over_uniform_hodl\": 0.4847653719523821, \"calmar\": -1.1854157480756249, \"daily_log_sharpe\": -2.211285536951668, \"daily_returns\": 0.008251626620176582, \"fee_revenue_over_value\": 0.1866302765183631, \"jax_sharpe\": -0.5835969869146299, \"return\": -0.5112693809680754, \"returns_over_hodl\": 0.17402009544949926, \"returns_over_uniform_hodl\": 0.17255467063648422, \"sharpe\": -1.833629810307028, \"sterling\": -2.1462166161149248, \"ulcer\": -0.15555236165694922}], \"train_objective\": [{\"annualised_returns\": 0.14670262507259046, \"annualised_returns_over_hodl\": -0.05169327189966966, \"annualised_returns_over_uniform_hodl\": -0.05169327189967032, \"calmar\": 0.20418849230294325, \"daily_log_sharpe\": 0.16743920733914244, \"daily_returns\": 0.019145920828308627, \"fee_revenue_over_value\": 0.3533504002666885, \"jax_sharpe\": 0.5945463315303283, \"return\": 0.08666043826834757, \"returns_over_hodl\": -0.03171067746734013, \"returns_over_uniform_hodl\": -0.031710677467340576, \"sharpe\": 0.5924270586830775, \"sterling\": 0.5312177775977257, \"ulcer\": -0.14874137222254427}], \"train_return\": 0.08666043826834757, \"train_returns_over_hodl\": -0.03171067746734013, \"train_sharpe\": 0.5945463315303283, \"validation_return\": 0.01781930162565004, \"validation_returns_over_hodl\": 0.018639271856124573, \"validation_sharpe\": 0.4987001638697106}, {\"centeredness_margin\": 0.09193426892417977, \"continuous_test_metrics\": [{\"annualised_returns\": -0.7833935294217366, \"annualised_returns_over_hodl\": 1.1026620897313029, \"annualised_returns_over_uniform_hodl\": 0.9027034225488717, \"calmar\": -1.1043970357287163, \"daily_log_sharpe\": -1.8469497283750316, \"daily_returns\": 0.009198423581258022, \"fee_revenue_over_value\": 0.18028184093522578, \"jax_sharpe\": -0.39437410500998965, \"return\": -0.4599303700449945, \"returns_over_hodl\": 0.3489364179273009, \"returns_over_uniform_hodl\": 0.29572640307869436, \"sharpe\": -1.444192864540487, \"sterling\": -1.9929536509808552, \"ulcer\": -0.15403735273722735}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": 0.8854173975423749, \"optuna_trial_number\": 47, \"price_ratio\": 1.1847168654953142, \"shift_exponent\": 0.2322274891475192, \"step\": 47, \"test_objective\": [{\"annualised_returns\": -0.7833935294217366, \"annualised_returns_over_hodl\": 1.1026620897313029, \"annualised_returns_over_uniform_hodl\": 0.9027034225488717, \"calmar\": -1.1043970357287163, \"daily_log_sharpe\": -1.8469497283750316, \"daily_returns\": 0.009198423581258022, \"fee_revenue_over_value\": 0.18028184093522578, \"jax_sharpe\": -0.39437410500998965, \"return\": -0.4599303700449945, \"returns_over_hodl\": 0.3489364179273009, \"returns_over_uniform_hodl\": 0.29572640307869436, \"sharpe\": -1.444192864540487, \"sterling\": -1.9929536509808552, \"ulcer\": -0.15403735273722735}], \"train_objective\": [{\"annualised_returns\": 0.830933686225823, \"annualised_returns_over_hodl\": 0.5141560639957317, \"annualised_returns_over_uniform_hodl\": 0.5141560639957312, \"calmar\": 1.2882222235948633, \"daily_log_sharpe\": 0.7050972225992744, \"daily_returns\": 0.021553333935245497, \"fee_revenue_over_value\": 0.406393666857311, \"jax_sharpe\": 1.1263822606457872, \"return\": 0.443690710133283, \"returns_over_hodl\": 0.2864278945217671, \"returns_over_uniform_hodl\": 0.28642789452176687, \"sharpe\": 1.1400271338257753, \"sterling\": 3.1957506004513796, \"ulcer\": -0.13115584566694288}], \"train_return\": 0.443690710133283, \"train_returns_over_hodl\": 0.2864278945217671, \"train_sharpe\": 1.1263822606457874, \"validation_return\": 0.016674953674566284, \"validation_returns_over_hodl\": 0.06380107885903352, \"validation_sharpe\": 0.4967552095970503}, {\"centeredness_margin\": 0.23926639049833862, \"continuous_test_metrics\": [{\"annualised_returns\": -0.8021588472200369, \"annualised_returns_over_hodl\": 0.8638776009876121, \"annualised_returns_over_uniform_hodl\": 0.7378660827190693, \"calmar\": -1.1516102466613025, \"daily_log_sharpe\": -1.9915099198666868, \"daily_returns\": 0.008572716127393045, \"fee_revenue_over_value\": 0.15319724419397981, \"jax_sharpe\": -0.5156015440882655, \"return\": -0.47928499476159003, \"returns_over_hodl\": 0.28501237712022065, \"returns_over_uniform_hodl\": 0.24929109756251178, \"sharpe\": -1.5989923327127773, \"sterling\": -2.0658914914865596, \"ulcer\": -0.15284664366385484}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": 0.8273483247259702, \"optuna_trial_number\": 48, \"price_ratio\": 1.2730250798458862, \"shift_exponent\": 0.4821135060673662, \"step\": 48, \"test_objective\": [{\"annualised_returns\": -0.8021588472200369, \"annualised_returns_over_hodl\": 0.8638776009876121, \"annualised_returns_over_uniform_hodl\": 0.7378660827190693, \"calmar\": -1.1516102466613025, \"daily_log_sharpe\": -1.9915099198666868, \"daily_returns\": 0.008572716127393045, \"fee_revenue_over_value\": 0.15319724419397981, \"jax_sharpe\": -0.5156015440882655, \"return\": -0.47928499476159003, \"returns_over_hodl\": 0.28501237712022065, \"returns_over_uniform_hodl\": 0.24929109756251178, \"sharpe\": -1.5989923327127773, \"sterling\": -2.0658914914865596, \"ulcer\": -0.15284664366385484}], \"train_objective\": [{\"annualised_returns\": 0.6974926201995257, \"annualised_returns_over_hodl\": 0.40380220419741875, \"annualised_returns_over_uniform_hodl\": 0.40380220419741786, \"calmar\": 1.0736976216198757, \"daily_log_sharpe\": 0.6259820667946395, \"daily_returns\": 0.021291082116062125, \"fee_revenue_over_value\": 0.3226754637605212, \"jax_sharpe\": 1.0418147679890382, \"return\": 0.3788635378802141, \"returns_over_hodl\": 0.22866241738462034, \"returns_over_uniform_hodl\": 0.2286624173846199, \"sharpe\": 1.05634280686521, \"sterling\": 2.6629578413579145, \"ulcer\": -0.13322731788687847}], \"train_return\": 0.3788635378802141, \"train_returns_over_hodl\": 0.22866241738462034, \"train_sharpe\": 1.0418147679890382, \"validation_return\": 0.018631733213843038, \"validation_returns_over_hodl\": 0.05203928740473174, \"validation_sharpe\": 0.5118857762991496}, {\"centeredness_margin\": 0.4519974392464918, \"continuous_test_metrics\": [{\"annualised_returns\": -0.8728941515441012, \"annualised_returns_over_hodl\": 0.13951632309681328, \"annualised_returns_over_uniform_hodl\": 0.116516659162472, \"calmar\": -1.332528253508627, \"daily_log_sharpe\": -2.5205021807348933, \"daily_returns\": 0.006896400468676594, \"fee_revenue_over_value\": 0.0301047094606953, \"jax_sharpe\": -0.9802628189283626, \"return\": -0.5642738979608093, \"returns_over_hodl\": 0.05400703867781176, \"returns_over_uniform_hodl\": 0.0453870827170515, \"sharpe\": -2.139881491901584, \"sterling\": -2.3030443989308416, \"ulcer\": -0.15909728354699115}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": 0.5948400768715593, \"optuna_trial_number\": 49, \"price_ratio\": 19.739359059562407, \"shift_exponent\": 2.6220659348785227, \"step\": 49, \"test_objective\": [{\"annualised_returns\": -0.8728941515441012, \"annualised_returns_over_hodl\": 0.13951632309681328, \"annualised_returns_over_uniform_hodl\": 0.116516659162472, \"calmar\": -1.332528253508627, \"daily_log_sharpe\": -2.5205021807348933, \"daily_returns\": 0.006896400468676594, \"fee_revenue_over_value\": 0.0301047094606953, \"jax_sharpe\": -0.9802628189283626, \"return\": -0.5642738979608093, \"returns_over_hodl\": 0.05400703867781176, \"returns_over_uniform_hodl\": 0.0453870827170515, \"sharpe\": -2.139881491901584, \"sterling\": -2.3030443989308416, \"ulcer\": -0.15909728354699115}], \"train_objective\": [{\"annualised_returns\": 0.34671015495223423, \"annualised_returns_over_hodl\": 0.11371010479844057, \"annualised_returns_over_uniform_hodl\": 0.11371010479844057, \"calmar\": 0.5404231019812497, \"daily_log_sharpe\": 0.37259570870253117, \"daily_returns\": 0.02044503371227234, \"fee_revenue_over_value\": 0.05139739043349218, \"jax_sharpe\": 0.778040703006976, \"return\": 0.19807799577972318, \"returns_over_hodl\": 0.06757004306100911, \"returns_over_uniform_hodl\": 0.06757004306100911, \"sharpe\": 0.7971667098114903, \"sterling\": 1.3241636600352722, \"ulcer\": -0.13370064678894036}], \"train_return\": 0.19807799577972318, \"train_returns_over_hodl\": 0.06757004306100911, \"train_sharpe\": 0.7780407030069759, \"validation_return\": -0.0030841003148117663, \"validation_returns_over_hodl\": 0.006283694439987997, \"validation_sharpe\": 0.2842638920861129}, {\"centeredness_margin\": 0.41153685405470664, \"continuous_test_metrics\": [{\"annualised_returns\": -0.8802185092509662, \"annualised_returns_over_hodl\": 0.0632295517982484, \"annualised_returns_over_uniform_hodl\": 0.052178412758198345, \"calmar\": -1.3312737308108322, \"daily_log_sharpe\": -2.5920380478382823, \"daily_returns\": 0.006720766407548637, \"fee_revenue_over_value\": 0.023205816694213507, \"jax_sharpe\": -1.284763989603983, \"return\": -0.5745655256619571, \"returns_over_hodl\": 0.024999646822249755, \"returns_over_uniform_hodl\": 0.020695574431084385, \"sharpe\": -2.2170596564580016, \"sterling\": -2.396144175293168, \"ulcer\": -0.1608350171808036}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": 0.5739437356845608, \"optuna_trial_number\": 50, \"price_ratio\": 100.71745837894906, \"shift_exponent\": 62.4970621932647, \"step\": 50, \"test_objective\": [{\"annualised_returns\": -0.8802185092509662, \"annualised_returns_over_hodl\": 0.0632295517982484, \"annualised_returns_over_uniform_hodl\": 0.052178412758198345, \"calmar\": -1.3312737308108322, \"daily_log_sharpe\": -2.5920380478382823, \"daily_returns\": 0.006720766407548637, \"fee_revenue_over_value\": 0.023205816694213507, \"jax_sharpe\": -1.284763989603983, \"return\": -0.5745655256619571, \"returns_over_hodl\": 0.024999646822249755, \"returns_over_uniform_hodl\": 0.020695574431084385, \"sharpe\": -2.2170596564580016, \"sterling\": -2.396144175293168, \"ulcer\": -0.1608350171808036}], \"train_objective\": [{\"annualised_returns\": 0.31430914102784446, \"annualised_returns_over_hodl\": 0.08691492806303702, \"annualised_returns_over_uniform_hodl\": 0.08691492806303702, \"calmar\": 0.4887849941737867, \"daily_log_sharpe\": 0.3446007291960334, \"daily_returns\": 0.020428357829204793, \"fee_revenue_over_value\": 0.03969428126145669, \"jax_sharpe\": 0.7503165236495989, \"return\": 0.18049406468858553, \"returns_over_hodl\": 0.05190154890764598, \"returns_over_uniform_hodl\": 0.05190154890764598, \"sharpe\": 0.7691095005668228, \"sterling\": 1.1984480825514239, \"ulcer\": -0.13417459229141362}], \"train_return\": 0.18049406468858553, \"train_returns_over_hodl\": 0.05190154890764598, \"train_sharpe\": 0.7503165236495989, \"validation_return\": -0.0009351079035446741, \"validation_returns_over_hodl\": 0.004824700672773741, \"validation_sharpe\": 0.3041917958296601}, {\"centeredness_margin\": 0.8457876258250251, \"continuous_test_metrics\": [{\"annualised_returns\": -0.8857490880763925, \"annualised_returns_over_hodl\": 0.021541644360393297, \"annualised_returns_over_uniform_hodl\": 0.0035969865813989532, \"calmar\": -1.3328678337779378, \"daily_log_sharpe\": -2.6446987641848225, \"daily_returns\": 0.006660905738350313, \"fee_revenue_over_value\": 0.0001334496190468823, \"jax_sharpe\": -1.7623963289523044, \"return\": -0.5825884556167609, \"returns_over_hodl\": 0.008620455847383912, \"returns_over_uniform_hodl\": 0.0014470894285922853, \"sharpe\": -2.2650772005413584, \"sterling\": -2.483257400559358, \"ulcer\": -0.1605901511525613}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": 0.27160537592985606, \"optuna_trial_number\": 51, \"price_ratio\": 17.432031312880927, \"shift_exponent\": 0.0012664482304648476, \"step\": 51, \"test_objective\": [{\"annualised_returns\": -0.8857490880763925, \"annualised_returns_over_hodl\": 0.021541644360393297, \"annualised_returns_over_uniform_hodl\": 0.0035969865813989532, \"calmar\": -1.3328678337779378, \"daily_log_sharpe\": -2.6446987641848225, \"daily_returns\": 0.006660905738350313, \"fee_revenue_over_value\": 0.0001334496190468823, \"jax_sharpe\": -1.7623963289523044, \"return\": -0.5825884556167609, \"returns_over_hodl\": 0.008620455847383912, \"returns_over_uniform_hodl\": 0.0014470894285922853, \"sharpe\": -2.2650772005413584, \"sterling\": -2.483257400559358, \"ulcer\": -0.1605901511525613}], \"train_objective\": [{\"annualised_returns\": 0.2812318676232768, \"annualised_returns_over_hodl\": 0.05956049437559363, \"annualised_returns_over_uniform_hodl\": 0.05956049437559363, \"calmar\": 0.43796390547117303, \"daily_log_sharpe\": 0.31700015389432745, \"daily_returns\": 0.020443442607782688, \"fee_revenue_over_value\": 0.02587044782259912, \"jax_sharpe\": 0.7211647507783788, \"return\": 0.16236653466835826, \"returns_over_hodl\": 0.035748670653931836, \"returns_over_uniform_hodl\": 0.035748670653931836, \"sharpe\": 0.741535740657307, \"sterling\": 1.0717174908490883, \"ulcer\": -0.1341564522247013}], \"train_return\": 0.16236653466835826, \"train_returns_over_hodl\": 0.035748670653931836, \"train_sharpe\": 0.7211647507783788, \"validation_return\": -0.009839334383919929, \"validation_returns_over_hodl\": 0.0005198743191174859, \"validation_sharpe\": 0.2132767351961361}, {\"centeredness_margin\": 0.7906466393327929, \"continuous_test_metrics\": [{\"annualised_returns\": -0.8859350231289381, \"annualised_returns_over_hodl\": 0.022510115493638017, \"annualised_returns_over_uniform_hodl\": 0.0019637054522356756, \"calmar\": -1.3309110197875087, \"daily_log_sharpe\": -2.6261399878652374, \"daily_returns\": 0.006691654434598502, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": -1.0437884688926804, \"return\": -0.5828621712920379, \"returns_over_hodl\": 0.009005453324536283, \"returns_over_uniform_hodl\": 0.0007903951659979924, \"sharpe\": -2.2438874608160013, \"sterling\": -2.303722698146365, \"ulcer\": -0.161417187277738}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -0.3174209928029361, \"optuna_trial_number\": 52, \"price_ratio\": 17.112253388660662, \"shift_exponent\": 0.00047759157659975257, \"step\": 52, \"test_objective\": [{\"annualised_returns\": -0.8859350231289381, \"annualised_returns_over_hodl\": 0.022510115493638017, \"annualised_returns_over_uniform_hodl\": 0.0019637054522356756, \"calmar\": -1.3309110197875087, \"daily_log_sharpe\": -2.6261399878652374, \"daily_returns\": 0.006691654434598502, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": -1.0437884688926804, \"return\": -0.5828621712920379, \"returns_over_hodl\": 0.009005453324536283, \"returns_over_uniform_hodl\": 0.0007903951659979924, \"sharpe\": -2.2438874608160013, \"sterling\": -2.303722698146365, \"ulcer\": -0.161417187277738}], \"train_objective\": [{\"annualised_returns\": 0.29827939801313175, \"annualised_returns_over_hodl\": 0.07365855904617091, \"annualised_returns_over_uniform_hodl\": 0.07365855904617091, \"calmar\": 0.4649414559689653, \"daily_log_sharpe\": 0.3326468873561907, \"daily_returns\": 0.020457385470307697, \"fee_revenue_over_value\": 0.029532045327719563, \"jax_sharpe\": 0.7362088062347949, \"return\": 0.1717318569425479, \"returns_over_hodl\": 0.04409381808069002, \"returns_over_uniform_hodl\": 0.04409381808069002, \"sharpe\": 0.7570637287518548, \"sterling\": 1.1378461280231968, \"ulcer\": -0.13393167686575017}], \"train_return\": 0.1717318569425479, \"train_returns_over_hodl\": 0.04409381808069002, \"train_sharpe\": 0.7362088062347949, \"validation_return\": -0.010571741735323714, \"validation_returns_over_hodl\": -0.000586746451530562, \"validation_sharpe\": 0.2059765996537938}, {\"centeredness_margin\": 0.06307607394050202, \"continuous_test_metrics\": [{\"annualised_returns\": -0.8507163447390056, \"annualised_returns_over_hodl\": 0.45595049277488275, \"annualised_returns_over_uniform_hodl\": 0.31132980948078615, \"calmar\": -1.1980023752858058, \"daily_log_sharpe\": -2.14192746312601, \"daily_returns\": 0.008372885342130551, \"fee_revenue_over_value\": 0.08685997236888254, \"jax_sharpe\": -0.6178929510195271, \"return\": -0.5351171814246031, \"returns_over_hodl\": 0.1633363802431549, \"returns_over_uniform_hodl\": 0.11533940987566416, \"sharpe\": -1.7255881716977322, \"sterling\": -2.0656313887720765, \"ulcer\": -0.16711015309459187}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": 0.8041978020833995, \"optuna_trial_number\": 53, \"price_ratio\": 1.8564373798492282, \"shift_exponent\": 0.08510439883026971, \"step\": 53, \"test_objective\": [{\"annualised_returns\": -0.8507163447390056, \"annualised_returns_over_hodl\": 0.45595049277488275, \"annualised_returns_over_uniform_hodl\": 0.31132980948078615, \"calmar\": -1.1980023752858058, \"daily_log_sharpe\": -2.14192746312601, \"daily_returns\": 0.008372885342130551, \"fee_revenue_over_value\": 0.08685997236888254, \"jax_sharpe\": -0.6178929510195271, \"return\": -0.5351171814246031, \"returns_over_hodl\": 0.1633363802431549, \"returns_over_uniform_hodl\": 0.11533940987566416, \"sharpe\": -1.7255881716977322, \"sterling\": -2.0656313887720765, \"ulcer\": -0.16711015309459187}], \"train_objective\": [{\"annualised_returns\": 0.734619855980716, \"annualised_returns_over_hodl\": 0.4345058990501631, \"annualised_returns_over_uniform_hodl\": 0.4345058990501631, \"calmar\": 1.1775383236426296, \"daily_log_sharpe\": 0.6628590427617602, \"daily_returns\": 0.020744318934510005, \"fee_revenue_over_value\": 0.17731931247334548, \"jax_sharpe\": 1.068093805875531, \"return\": 0.3970953721670687, \"returns_over_hodl\": 0.2449082379265719, \"returns_over_uniform_hodl\": 0.2449082379265719, \"sharpe\": 1.0891486461843007, \"sterling\": 2.8514930486219137, \"ulcer\": -0.12862882360504715}], \"train_return\": 0.3970953721670687, \"train_returns_over_hodl\": 0.2449082379265719, \"train_sharpe\": 1.068093805875531, \"validation_return\": -0.02364033946718913, \"validation_returns_over_hodl\": 0.022879957498631587, \"validation_sharpe\": 0.11706360000828096}, {\"centeredness_margin\": 0.011244705355748126, \"continuous_test_metrics\": [{\"annualised_returns\": -0.8577466468227134, \"annualised_returns_over_hodl\": 0.3863655383392872, \"annualised_returns_over_uniform_hodl\": 0.2495745913632843, \"calmar\": -1.2072891049689776, \"daily_log_sharpe\": -2.121152161683285, \"daily_returns\": 0.008272413598785862, \"fee_revenue_over_value\": 0.07700466016196014, \"jax_sharpe\": -0.6272783835633781, \"return\": -0.5440615278008465, \"returns_over_hodl\": 0.14061620647529915, \"returns_over_uniform_hodl\": 0.09388027735798277, \"sharpe\": -1.6911102981480124, \"sterling\": -2.045435494061739, \"ulcer\": -0.17111919736536294}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": 0.7639334516254601, \"optuna_trial_number\": 54, \"price_ratio\": 2.1200228109441652, \"shift_exponent\": 0.03434597633669791, \"step\": 54, \"test_objective\": [{\"annualised_returns\": -0.8577466468227134, \"annualised_returns_over_hodl\": 0.3863655383392872, \"annualised_returns_over_uniform_hodl\": 0.2495745913632843, \"calmar\": -1.2072891049689776, \"daily_log_sharpe\": -2.121152161683285, \"daily_returns\": 0.008272413598785862, \"fee_revenue_over_value\": 0.07700466016196014, \"jax_sharpe\": -0.6272783835633781, \"return\": -0.5440615278008465, \"returns_over_hodl\": 0.14061620647529915, \"returns_over_uniform_hodl\": 0.09388027735798277, \"sharpe\": -1.6911102981480124, \"sterling\": -2.045435494061739, \"ulcer\": -0.17111919736536294}], \"train_objective\": [{\"annualised_returns\": 0.6506606596459339, \"annualised_returns_over_hodl\": 0.3650728402698795, \"annualised_returns_over_uniform_hodl\": 0.36507284026987996, \"calmar\": 1.0370516954943672, \"daily_log_sharpe\": 0.6062480585689604, \"daily_returns\": 0.02068627174954128, \"fee_revenue_over_value\": 0.1503932737965556, \"jax_sharpe\": 1.0110876421597947, \"return\": 0.3556410323024586, \"returns_over_hodl\": 0.2079695648600277, \"returns_over_uniform_hodl\": 0.20796956486002793, \"sharpe\": 1.0320214244576875, \"sterling\": 2.5173996291665666, \"ulcer\": -0.12962785534523952}], \"train_return\": 0.3556410323024586, \"train_returns_over_hodl\": 0.2079695648600277, \"train_sharpe\": 1.0110876421597945, \"validation_return\": -0.020138808812999898, \"validation_returns_over_hodl\": 0.018554066470642727, \"validation_sharpe\": 0.1444723867051089}, {\"centeredness_margin\": 0.10691982200490634, \"continuous_test_metrics\": [{\"annualised_returns\": -0.7477656666552908, \"annualised_returns_over_hodl\": 1.517645339423808, \"annualised_returns_over_uniform_hodl\": 1.2156638629403558, \"calmar\": -1.0499760370610904, \"daily_log_sharpe\": -1.6433171549734347, \"daily_returns\": 0.007841752010151698, \"fee_revenue_over_value\": 0.24072703133964643, \"jax_sharpe\": -0.24460243542216098, \"return\": -0.4257725810014853, \"returns_over_hodl\": 0.45042632220700196, \"returns_over_uniform_hodl\": 0.3776772232685919, \"sharpe\": -1.1967472798613206, \"sterling\": -1.8237538322374012, \"ulcer\": -0.16678828752510957}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -0.011384158036214065, \"optuna_trial_number\": 55, \"price_ratio\": 1.0200457362642572, \"shift_exponent\": 0.006334600464066974, \"step\": 55, \"test_objective\": [{\"annualised_returns\": -0.7477656666552908, \"annualised_returns_over_hodl\": 1.517645339423808, \"annualised_returns_over_uniform_hodl\": 1.2156638629403558, \"calmar\": -1.0499760370610904, \"daily_log_sharpe\": -1.6433171549734347, \"daily_returns\": 0.007841752010151698, \"fee_revenue_over_value\": 0.24072703133964643, \"jax_sharpe\": -0.24460243542216098, \"return\": -0.4257725810014853, \"returns_over_hodl\": 0.45042632220700196, \"returns_over_uniform_hodl\": 0.3776772232685919, \"sharpe\": -1.1967472798613206, \"sterling\": -1.8237538322374012, \"ulcer\": -0.16678828752510957}], \"train_objective\": [{\"annualised_returns\": 0.67401661582282, \"annualised_returns_over_hodl\": 0.38438788315848593, \"annualised_returns_over_uniform_hodl\": 0.3843878831584888, \"calmar\": 1.1257698730197168, \"daily_log_sharpe\": 0.6039717530921431, \"daily_returns\": 0.0197683672150923, \"fee_revenue_over_value\": 0.29669584682314337, \"jax_sharpe\": 1.0214336365936445, \"return\": 0.3672544264540556, \"returns_over_hodl\": 0.21831789922404754, \"returns_over_uniform_hodl\": 0.2183178992240491, \"sharpe\": 1.0517635514375392, \"sterling\": 2.5908477241341257, \"ulcer\": -0.12403130696645517}], \"train_return\": 0.3672544264540556, \"train_returns_over_hodl\": 0.21831789922404754, \"train_sharpe\": 1.0214336365936445, \"validation_return\": -0.06990342221285917, \"validation_returns_over_hodl\": -3.282851768204864e-10, \"validation_sharpe\": -0.28596766143373553}, {\"centeredness_margin\": 0.018014990138067934, \"continuous_test_metrics\": [{\"annualised_returns\": -0.7958698569136634, \"annualised_returns_over_hodl\": 1.0181858698124557, \"annualised_returns_over_uniform_hodl\": 0.7931095080348887, \"calmar\": -1.2005181771091566, \"daily_log_sharpe\": -1.8434554818928102, \"daily_returns\": 0.00931053132151618, \"fee_revenue_over_value\": 0.16757533302261823, \"jax_sharpe\": -0.6123702335318976, \"return\": -0.4726808982853189, \"returns_over_hodl\": 0.3268425621878943, \"returns_over_uniform_hodl\": 0.2651355400161568, \"sharpe\": -1.4274343325666585, \"sterling\": -2.0393596550755326, \"ulcer\": -0.15951378868042657}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": 0.9245684258156684, \"optuna_trial_number\": 56, \"price_ratio\": 1.2153338446062443, \"shift_exponent\": 0.08496031663124214, \"step\": 56, \"test_objective\": [{\"annualised_returns\": -0.7958698569136634, \"annualised_returns_over_hodl\": 1.0181858698124557, \"annualised_returns_over_uniform_hodl\": 0.7931095080348887, \"calmar\": -1.2005181771091566, \"daily_log_sharpe\": -1.8434554818928102, \"daily_returns\": 0.00931053132151618, \"fee_revenue_over_value\": 0.16757533302261823, \"jax_sharpe\": -0.6123702335318976, \"return\": -0.4726808982853189, \"returns_over_hodl\": 0.3268425621878943, \"returns_over_uniform_hodl\": 0.2651355400161568, \"sharpe\": -1.4274343325666585, \"sterling\": -2.0393596550755326, \"ulcer\": -0.15951378868042657}], \"train_objective\": [{\"annualised_returns\": 1.0884633233318795, \"annualised_returns_over_hodl\": 0.7271294035635623, \"annualised_returns_over_uniform_hodl\": 0.7271294035635636, \"calmar\": 1.768691891541065, \"daily_log_sharpe\": 0.84904439510837, \"daily_returns\": 0.021455615113633458, \"fee_revenue_over_value\": 0.39450223818849334, \"jax_sharpe\": 1.2736058971942448, \"return\": 0.5637730413653865, \"returns_over_hodl\": 0.3934295254472149, \"returns_over_uniform_hodl\": 0.39342952544721554, \"sharpe\": 1.2898255282341262, \"sterling\": 4.22803306985713, \"ulcer\": -0.12598572076217615}], \"train_return\": 0.5637730413653865, \"train_returns_over_hodl\": 0.3934295254472149, \"train_sharpe\": 1.2736058971942448, \"validation_return\": 0.0007684636144951185, \"validation_returns_over_hodl\": 0.058105666273751355, \"validation_sharpe\": 0.3503298472303882}, {\"centeredness_margin\": 0.06611546766061385, \"continuous_test_metrics\": [{\"annualised_returns\": -0.8684059550690963, \"annualised_returns_over_hodl\": 0.2982829530704021, \"annualised_returns_over_uniform_hodl\": 0.15594164388829923, \"calmar\": -1.271711953529634, \"daily_log_sharpe\": -2.183190442291089, \"daily_returns\": 0.00789241080583908, \"fee_revenue_over_value\": 0.04963782220648787, \"jax_sharpe\": -0.8090564905376668, \"return\": -0.5581415966528074, \"returns_over_hodl\": 0.11085692607356945, \"returns_over_uniform_hodl\": 0.06009960176218643, \"sharpe\": -1.7495680156525713, \"sterling\": -2.0833538439724526, \"ulcer\": -0.17474593197537844}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": 0.796609885905522, \"optuna_trial_number\": 57, \"price_ratio\": 1.8979537366716661, \"shift_exponent\": 0.0033361382211921256, \"step\": 57, \"test_objective\": [{\"annualised_returns\": -0.8684059550690963, \"annualised_returns_over_hodl\": 0.2982829530704021, \"annualised_returns_over_uniform_hodl\": 0.15594164388829923, \"calmar\": -1.271711953529634, \"daily_log_sharpe\": -2.183190442291089, \"daily_returns\": 0.00789241080583908, \"fee_revenue_over_value\": 0.04963782220648787, \"jax_sharpe\": -0.8090564905376668, \"return\": -0.5581415966528074, \"returns_over_hodl\": 0.11085692607356945, \"returns_over_uniform_hodl\": 0.06009960176218643, \"sharpe\": -1.7495680156525713, \"sterling\": -2.0833538439724526, \"ulcer\": -0.17474593197537844}], \"train_objective\": [{\"annualised_returns\": 0.7186628916439113, \"annualised_returns_over_hodl\": 0.4213097169626836, \"annualised_returns_over_uniform_hodl\": 0.4213097169626836, \"calmar\": 1.1507644976295544, \"daily_log_sharpe\": 0.652398051080856, \"daily_returns\": 0.020732103906503173, \"fee_revenue_over_value\": 0.1721167926502539, \"jax_sharpe\": 1.0574637007514, \"return\": 0.38927846415522405, \"returns_over_hodl\": 0.23794283429491792, \"returns_over_uniform_hodl\": 0.23794283429491792, \"sharpe\": 1.0785559502672644, \"sterling\": 2.787631106324301, \"ulcer\": -0.12882796361170093}], \"train_return\": 0.38927846415522405, \"train_returns_over_hodl\": 0.23794283429491792, \"train_sharpe\": 1.0574637007514, \"validation_return\": -0.027011453138911512, \"validation_returns_over_hodl\": 0.01780060090229485, \"validation_sharpe\": 0.08650811278131473}, {\"centeredness_margin\": 0.053379159227170014, \"continuous_test_metrics\": [{\"annualised_returns\": -0.7016507605917697, \"annualised_returns_over_hodl\": 1.902186926585888, \"annualised_returns_over_uniform_hodl\": 1.6207440498957029, \"calmar\": -0.9897869002612434, \"daily_log_sharpe\": -1.5188214127435715, \"daily_returns\": 0.01086657721372197, \"fee_revenue_over_value\": 0.3282845898027947, \"jax_sharpe\": -0.11570074440730574, \"return\": -0.38559907110290603, \"returns_over_hodl\": 0.5358790375430225, \"returns_over_uniform_hodl\": 0.47406086454882645, \"sharpe\": -1.1181640247472149, \"sterling\": -1.8372956458982208, \"ulcer\": -0.14444893977477175}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": 0.9875626112778862, \"optuna_trial_number\": 58, \"price_ratio\": 1.022173167493758, \"shift_exponent\": 0.2111960811802437, \"step\": 58, \"test_objective\": [{\"annualised_returns\": -0.7016507605917697, \"annualised_returns_over_hodl\": 1.902186926585888, \"annualised_returns_over_uniform_hodl\": 1.6207440498957029, \"calmar\": -0.9897869002612434, \"daily_log_sharpe\": -1.5188214127435715, \"daily_returns\": 0.01086657721372197, \"fee_revenue_over_value\": 0.3282845898027947, \"jax_sharpe\": -0.11570074440730574, \"return\": -0.38559907110290603, \"returns_over_hodl\": 0.5358790375430225, \"returns_over_uniform_hodl\": 0.47406086454882645, \"sharpe\": -1.1181640247472149, \"sterling\": -1.8372956458982208, \"ulcer\": -0.14444893977477175}], \"train_objective\": [{\"annualised_returns\": 0.5646661753148476, \"annualised_returns_over_hodl\": 0.2939566273236289, \"annualised_returns_over_uniform_hodl\": 0.2939566273236285, \"calmar\": 0.7804091404882396, \"daily_log_sharpe\": 0.5055675122099849, \"daily_returns\": 0.026241572534196594, \"fee_revenue_over_value\": 0.7905052987627585, \"jax_sharpe\": 0.9480329589395816, \"return\": 0.31231341458923634, \"returns_over_hodl\": 0.16936167215957654, \"returns_over_uniform_hodl\": 0.16936167215957632, \"sharpe\": 0.9457178844849595, \"sterling\": 2.00463027861359, \"ulcer\": -0.15067083479526963}], \"train_return\": 0.31231341458923634, \"train_returns_over_hodl\": 0.16936167215957654, \"train_sharpe\": 0.9480329589395815, \"validation_return\": 0.06440666785754834, \"validation_returns_over_hodl\": 0.10156225825784393, \"validation_sharpe\": 0.957789140880876}, {\"centeredness_margin\": 0.5753228617347833, \"continuous_test_metrics\": [{\"annualised_returns\": -0.8857264109712688, \"annualised_returns_over_hodl\": 0.01864618995193479, \"annualised_returns_over_uniform_hodl\": 0.003796185642337635, \"calmar\": -1.3324327038499293, \"daily_log_sharpe\": -2.6443207144975234, \"daily_returns\": 0.006614470070437684, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": -1.2036035174134692, \"return\": -0.5825550907747572, \"returns_over_hodl\": 0.007468120592066141, \"returns_over_uniform_hodl\": 0.0015271378229491095, \"sharpe\": -2.2648802037069733, \"sterling\": -2.351890973979892, \"ulcer\": -0.16044674016600588}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": 0.5829040644964513, \"optuna_trial_number\": 59, \"price_ratio\": 42.0460577526336, \"shift_exponent\": 1.2579296539340236e-05, \"step\": 59, \"test_objective\": [{\"annualised_returns\": -0.8857264109712688, \"annualised_returns_over_hodl\": 0.01864618995193479, \"annualised_returns_over_uniform_hodl\": 0.003796185642337635, \"calmar\": -1.3324327038499293, \"daily_log_sharpe\": -2.6443207144975234, \"daily_returns\": 0.006614470070437684, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": -1.2036035174134692, \"return\": -0.5825550907747572, \"returns_over_hodl\": 0.007468120592066141, \"returns_over_uniform_hodl\": 0.0015271378229491095, \"sharpe\": -2.2648802037069733, \"sterling\": -2.351890973979892, \"ulcer\": -0.16044674016600588}], \"train_objective\": [{\"annualised_returns\": 0.32800476939787404, \"annualised_returns_over_hodl\": 0.09824101753461112, \"annualised_returns_over_uniform_hodl\": 0.09824101753461112, \"calmar\": 0.5105908479665796, \"daily_log_sharpe\": 0.3565177629060136, \"daily_returns\": 0.020434767179735462, \"fee_revenue_over_value\": 0.04462616400710469, \"jax_sharpe\": 0.7621127777929546, \"return\": 0.18794718538872313, \"returns_over_hodl\": 0.058542792979244584, \"returns_over_uniform_hodl\": 0.058542792979244584, \"sharpe\": 0.7810481772196168, \"sterling\": 1.2515218886634607, \"ulcer\": -0.13396953562263827}], \"train_return\": 0.18794718538872313, \"train_returns_over_hodl\": 0.058542792979244584, \"train_sharpe\": 0.7621127777929545, \"validation_return\": -0.00299863691662694, \"validation_returns_over_hodl\": 0.004270335729812391, \"validation_sharpe\": 0.28359744976538914}, {\"centeredness_margin\": 0.05445667832309034, \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -Infinity, \"optuna_trial_number\": 60, \"price_ratio\": 1.0149167979603617, \"shift_exponent\": 3.571480601268919, \"step\": 60, \"test_objective\": -Infinity, \"train_objective\": -Infinity, \"train_return\": -Infinity, \"train_returns_over_hodl\": -Infinity, \"train_sharpe\": -Infinity, \"validation_return\": -Infinity, \"validation_returns_over_hodl\": -Infinity, \"validation_sharpe\": -Infinity}, {\"centeredness_margin\": 0.11810580569157454, \"continuous_test_metrics\": [{\"annualised_returns\": -0.7426995684777256, \"annualised_returns_over_hodl\": 1.4818242391807481, \"annualised_returns_over_uniform_hodl\": 1.2601652220903756, \"calmar\": -1.0484077115151529, \"daily_log_sharpe\": -1.6993186943639593, \"daily_returns\": 0.010006036523674306, \"fee_revenue_over_value\": 0.26221695689218605, \"jax_sharpe\": -0.28662360003590565, \"return\": -0.42115525351181315, \"returns_over_hodl\": 0.44207956295220296, \"returns_over_uniform_hodl\": 0.38875504140201955, \"sharpe\": -1.30403939731567, \"sterling\": -1.936694555038689, \"ulcer\": -0.14657099604136115}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": 0.9472076866324731, \"optuna_trial_number\": 61, \"price_ratio\": 1.0600474506745834, \"shift_exponent\": 0.26611224065017264, \"step\": 61, \"test_objective\": [{\"annualised_returns\": -0.7426995684777256, \"annualised_returns_over_hodl\": 1.4818242391807481, \"annualised_returns_over_uniform_hodl\": 1.2601652220903756, \"calmar\": -1.0484077115151529, \"daily_log_sharpe\": -1.6993186943639593, \"daily_returns\": 0.010006036523674306, \"fee_revenue_over_value\": 0.26221695689218605, \"jax_sharpe\": -0.28662360003590565, \"return\": -0.42115525351181315, \"returns_over_hodl\": 0.44207956295220296, \"returns_over_uniform_hodl\": 0.38875504140201955, \"sharpe\": -1.30403939731567, \"sterling\": -1.936694555038689, \"ulcer\": -0.14657099604136115}], \"train_objective\": [{\"annualised_returns\": 0.7038091239346358, \"annualised_returns_over_hodl\": 0.4090258627633827, \"annualised_returns_over_uniform_hodl\": 0.40902586276338404, \"calmar\": 1.008093050428493, \"daily_log_sharpe\": 0.6139572511968436, \"daily_returns\": 0.02271003345873298, \"fee_revenue_over_value\": 0.6144946364615612, \"jax_sharpe\": 1.0441780290032905, \"return\": 0.3819763195291441, \"returns_over_hodl\": 0.23143612030771266, \"returns_over_uniform_hodl\": 0.23143612030771332, \"sharpe\": 1.0496612879222535, \"sterling\": 2.5876242782980983, \"ulcer\": -0.14295894764221695}], \"train_return\": 0.3819763195291441, \"train_returns_over_hodl\": 0.23143612030771266, \"train_sharpe\": 1.0441780290032905, \"validation_return\": 0.05122811105866698, \"validation_returns_over_hodl\": 0.08866098087868535, \"validation_sharpe\": 0.8323232357803009}, {\"centeredness_margin\": 0.06674569286617138, \"continuous_test_metrics\": [{\"annualised_returns\": -0.8737011361637718, \"annualised_returns_over_hodl\": 0.1205893235160087, \"annualised_returns_over_uniform_hodl\": 0.10942798635555051, \"calmar\": -1.3352856971610585, \"daily_log_sharpe\": -2.549852392166975, \"daily_returns\": 0.00671071349545037, \"fee_revenue_over_value\": 0.023567001350562174, \"jax_sharpe\": -1.2484300218179207, \"return\": -0.5653901471199756, \"returns_over_hodl\": 0.046921136344095116, \"returns_over_uniform_hodl\": 0.042708995619159085, \"sharpe\": -2.1714593763080474, \"sterling\": -2.3734395970628617, \"ulcer\": -0.1576409124859587}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": 0.5729920772932008, \"optuna_trial_number\": 62, \"price_ratio\": 113.20873745701007, \"shift_exponent\": 0.00078560939373428, \"step\": 62, \"test_objective\": [{\"annualised_returns\": -0.8737011361637718, \"annualised_returns_over_hodl\": 0.1205893235160087, \"annualised_returns_over_uniform_hodl\": 0.10942798635555051, \"calmar\": -1.3352856971610585, \"daily_log_sharpe\": -2.549852392166975, \"daily_returns\": 0.00671071349545037, \"fee_revenue_over_value\": 0.023567001350562174, \"jax_sharpe\": -1.2484300218179207, \"return\": -0.5653901471199756, \"returns_over_hodl\": 0.046921136344095116, \"returns_over_uniform_hodl\": 0.042708995619159085, \"sharpe\": -2.1714593763080474, \"sterling\": -2.3734395970628617, \"ulcer\": -0.1576409124859587}], \"train_objective\": [{\"annualised_returns\": 0.3128541865281895, \"annualised_returns_over_hodl\": 0.08571170142787055, \"annualised_returns_over_uniform_hodl\": 0.08571170142787055, \"calmar\": 0.48647997679758437, \"daily_log_sharpe\": 0.3433256682603009, \"daily_returns\": 0.020427479183893562, \"fee_revenue_over_value\": 0.039182492257799, \"jax_sharpe\": 0.7490553711711013, \"return\": 0.17970049384668463, \"returns_over_hodl\": 0.05119442260965501, \"returns_over_uniform_hodl\": 0.05119442260965501, \"sharpe\": 0.7678316860995514, \"sterling\": 1.1928074792807775, \"ulcer\": -0.13419438359340546}], \"train_return\": 0.17970049384668463, \"train_returns_over_hodl\": 0.05119442260965501, \"train_sharpe\": 0.7490553711711013, \"validation_return\": -0.000808825984897954, \"validation_returns_over_hodl\": 0.004793670013918838, \"validation_sharpe\": 0.30541248908885815}, {\"centeredness_margin\": 0.03143752858584085, \"continuous_test_metrics\": [{\"annualised_returns\": -0.7958283271243707, \"annualised_returns_over_hodl\": 1.0059608972405485, \"annualised_returns_over_uniform_hodl\": 0.7934743118748382, \"calmar\": -1.1201754484000583, \"daily_log_sharpe\": -1.8564305341583447, \"daily_returns\": 0.009231669913618049, \"fee_revenue_over_value\": 0.16726641020020222, \"jax_sharpe\": -0.39296916993957837, \"return\": -0.4726376944675038, \"returns_over_hodl\": 0.3235997934104806, \"returns_over_uniform_hodl\": 0.26523919392363804, \"sharpe\": -1.443314486172317, \"sterling\": -1.9868895365027786, \"ulcer\": -0.15866835006839386}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": 0.9139933815690239, \"optuna_trial_number\": 63, \"price_ratio\": 1.2166713162312732, \"shift_exponent\": 0.11544817644928855, \"step\": 63, \"test_objective\": [{\"annualised_returns\": -0.7958283271243707, \"annualised_returns_over_hodl\": 1.0059608972405485, \"annualised_returns_over_uniform_hodl\": 0.7934743118748382, \"calmar\": -1.1201754484000583, \"daily_log_sharpe\": -1.8564305341583447, \"daily_returns\": 0.009231669913618049, \"fee_revenue_over_value\": 0.16726641020020222, \"jax_sharpe\": -0.39296916993957837, \"return\": -0.4726376944675038, \"returns_over_hodl\": 0.3235997934104806, \"returns_over_uniform_hodl\": 0.26523919392363804, \"sharpe\": -1.443314486172317, \"sterling\": -1.9868895365027786, \"ulcer\": -0.15866835006839386}], \"train_objective\": [{\"annualised_returns\": 1.0307952455317557, \"annualised_returns_over_hodl\": 0.6794387251097593, \"annualised_returns_over_uniform_hodl\": 0.6794387251097593, \"calmar\": 1.6592219547678395, \"daily_log_sharpe\": 0.8170753843702775, \"daily_returns\": 0.02144568807736047, \"fee_revenue_over_value\": 0.39147837602982355, \"jax_sharpe\": 1.2422199493984134, \"return\": 0.5374135054716911, \"returns_over_hodl\": 0.36994136276646605, \"returns_over_uniform_hodl\": 0.36994136276646605, \"sharpe\": 1.2578542614286359, \"sterling\": 3.9927992151197436, \"ulcer\": -0.12732195188161807}], \"train_return\": 0.5374135054716911, \"train_returns_over_hodl\": 0.36994136276646605, \"train_sharpe\": 1.2422199493984134, \"validation_return\": 0.0026593246707093954, \"validation_returns_over_hodl\": 0.05824860301756085, \"validation_sharpe\": 0.36685956154325927}, {\"centeredness_margin\": 0.04440686209986388, \"continuous_test_metrics\": [{\"annualised_returns\": -0.7726585168818312, \"annualised_returns_over_hodl\": 1.2271199470600727, \"annualised_returns_over_uniform_hodl\": 0.9970013677868603, \"calmar\": -1.210745026344658, \"daily_log_sharpe\": -1.7791456607059644, \"daily_returns\": 0.009472236161973418, \"fee_revenue_over_value\": 0.1950687409363068, \"jax_sharpe\": -0.6405752110851066, \"return\": -0.44930621470954124, \"returns_over_hodl\": 0.3805416186136519, \"returns_over_uniform_hodl\": 0.32121570633705954, \"sharpe\": -1.3726454352126731, \"sterling\": -2.056966912473273, \"ulcer\": -0.15419122893177295}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": 0.8897116439332011, \"optuna_trial_number\": 64, \"price_ratio\": 1.1523566429789984, \"shift_exponent\": 0.9651442959387527, \"step\": 64, \"test_objective\": [{\"annualised_returns\": -0.7726585168818312, \"annualised_returns_over_hodl\": 1.2271199470600727, \"annualised_returns_over_uniform_hodl\": 0.9970013677868603, \"calmar\": -1.210745026344658, \"daily_log_sharpe\": -1.7791456607059644, \"daily_returns\": 0.009472236161973418, \"fee_revenue_over_value\": 0.1950687409363068, \"jax_sharpe\": -0.6405752110851066, \"return\": -0.44930621470954124, \"returns_over_hodl\": 0.3805416186136519, \"returns_over_uniform_hodl\": 0.32121570633705954, \"sharpe\": -1.3726454352126731, \"sterling\": -2.056966912473273, \"ulcer\": -0.15419122893177295}], \"train_objective\": [{\"annualised_returns\": 0.7738795535812759, \"annualised_returns_over_hodl\": 0.46697310943562687, \"annualised_returns_over_uniform_hodl\": 0.46697310943562687, \"calmar\": 1.1685647452787034, \"daily_log_sharpe\": 0.6691636979274589, \"daily_returns\": 0.021821214936162195, \"fee_revenue_over_value\": 0.4438243117294144, \"jax_sharpe\": 1.0909428175541038, \"return\": 0.41620838470729127, \"returns_over_hodl\": 0.26193924900637366, \"returns_over_uniform_hodl\": 0.26193924900637366, \"sharpe\": 1.1025994605452216, \"sterling\": 2.92988488683784, \"ulcer\": -0.134825861850749}], \"train_return\": 0.41620838470729127, \"train_returns_over_hodl\": 0.26193924900637366, \"train_sharpe\": 1.0909428175541038, \"validation_return\": 0.01884668275359558, \"validation_returns_over_hodl\": 0.07083838538964926, \"validation_sharpe\": 0.5190690100958026}, {\"centeredness_margin\": 0.07349611565567525, \"continuous_test_metrics\": [{\"annualised_returns\": -0.7326261873334196, \"annualised_returns_over_hodl\": 1.601163146460931, \"annualised_returns_over_uniform_hodl\": 1.3486512988393384, \"calmar\": -1.0332121860414867, \"daily_log_sharpe\": -1.6486370810553284, \"daily_returns\": 0.010206532163192781, \"fee_revenue_over_value\": 0.2722022859420115, \"jax_sharpe\": -0.25792016042521904, \"return\": -0.41213297492712997, \"returns_over_hodl\": 0.4696153939167782, \"returns_over_uniform_hodl\": 0.4104011476255436, \"sharpe\": -1.2510983875196655, \"sterling\": -1.9143215998223355, \"ulcer\": -0.14624459859020755}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": 0.9683643555201216, \"optuna_trial_number\": 65, \"price_ratio\": 1.0516584224403311, \"shift_exponent\": 0.254610185586096, \"step\": 65, \"test_objective\": [{\"annualised_returns\": -0.7326261873334196, \"annualised_returns_over_hodl\": 1.601163146460931, \"annualised_returns_over_uniform_hodl\": 1.3486512988393384, \"calmar\": -1.0332121860414867, \"daily_log_sharpe\": -1.6486370810553284, \"daily_returns\": 0.010206532163192781, \"fee_revenue_over_value\": 0.2722022859420115, \"jax_sharpe\": -0.25792016042521904, \"return\": -0.41213297492712997, \"returns_over_hodl\": 0.4696153939167782, \"returns_over_uniform_hodl\": 0.4104011476255436, \"sharpe\": -1.2510983875196655, \"sterling\": -1.9143215998223355, \"ulcer\": -0.14624459859020755}], \"train_objective\": [{\"annualised_returns\": 0.733830930491562, \"annualised_returns_over_hodl\": 0.43385346891442045, \"annualised_returns_over_uniform_hodl\": 0.4338534689144182, \"calmar\": 1.04680357119644, \"daily_log_sharpe\": 0.6313671110862101, \"daily_returns\": 0.023111220467020986, \"fee_revenue_over_value\": 0.6537231605625283, \"jax_sharpe\": 1.063263783415668, \"return\": 0.3967095634227571, \"returns_over_hodl\": 0.24456445575276708, \"returns_over_uniform_hodl\": 0.24456445575276597, \"sharpe\": 1.068480519004511, \"sterling\": 2.685739000418113, \"ulcer\": -0.14341274301878945}], \"train_return\": 0.3967095634227571, \"train_returns_over_hodl\": 0.24456445575276708, \"train_sharpe\": 1.063263783415668, \"validation_return\": 0.05302227011112093, \"validation_returns_over_hodl\": 0.09161004241108106, \"validation_sharpe\": 0.8485794270920874}, {\"centeredness_margin\": 0.02306640758066153, \"continuous_test_metrics\": [{\"annualised_returns\": -0.8164128550211678, \"annualised_returns_over_hodl\": 0.8126214324794374, \"annualised_returns_over_uniform_hodl\": 0.6126567602281674, \"calmar\": -1.1485562887390601, \"daily_log_sharpe\": -1.9524106358233435, \"daily_returns\": 0.009015388753535039, \"fee_revenue_over_value\": 0.13813002064096805, \"jax_sharpe\": -0.4867470072095288, \"return\": -0.49473241503148735, \"returns_over_hodl\": 0.2706620038991516, \"returns_over_uniform_hodl\": 0.21222989435279538, \"sharpe\": -1.5383284439173694, \"sterling\": -2.017461722482126, \"ulcer\": -0.16185381764467882}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": 0.9275510228344868, \"optuna_trial_number\": 66, \"price_ratio\": 1.333030397604985, \"shift_exponent\": 0.4530684547878721, \"step\": 66, \"test_objective\": [{\"annualised_returns\": -0.8164128550211678, \"annualised_returns_over_hodl\": 0.8126214324794374, \"annualised_returns_over_uniform_hodl\": 0.6126567602281674, \"calmar\": -1.1485562887390601, \"daily_log_sharpe\": -1.9524106358233435, \"daily_returns\": 0.009015388753535039, \"fee_revenue_over_value\": 0.13813002064096805, \"jax_sharpe\": -0.4867470072095288, \"return\": -0.49473241503148735, \"returns_over_hodl\": 0.2706620038991516, \"returns_over_uniform_hodl\": 0.21222989435279538, \"sharpe\": -1.5383284439173694, \"sterling\": -2.017461722482126, \"ulcer\": -0.16185381764467882}], \"train_objective\": [{\"annualised_returns\": 1.0483271426529353, \"annualised_returns_over_hodl\": 0.6939373541639353, \"annualised_returns_over_uniform_hodl\": 0.6939373541639349, \"calmar\": 1.690340119380979, \"daily_log_sharpe\": 0.8338646935766464, \"daily_returns\": 0.021142687009921786, \"fee_revenue_over_value\": 0.3190526599127229, \"jax_sharpe\": 1.2528417738578228, \"return\": 0.545457926823506, \"returns_over_hodl\": 0.3771094964599395, \"returns_over_uniform_hodl\": 0.3771094964599393, \"sharpe\": 1.2721235809240827, \"sterling\": 4.084078552374818, \"ulcer\": -0.12583941156939454}], \"train_return\": 0.545457926823506, \"train_returns_over_hodl\": 0.3771094964599395, \"train_sharpe\": 1.2528417738578226, \"validation_return\": -0.011242083349937704, \"validation_returns_over_hodl\": 0.04844814132968556, \"validation_sharpe\": 0.23666413535631708}, {\"centeredness_margin\": 0.02858859460554732, \"continuous_test_metrics\": [{\"annualised_returns\": -0.806770192387734, \"annualised_returns_over_hodl\": 0.902121658344631, \"annualised_returns_over_uniform_hodl\": 0.6973593415782917, \"calmar\": -1.13511657865801, \"daily_log_sharpe\": -1.880484912032606, \"daily_returns\": 0.009135404078003647, \"fee_revenue_over_value\": 0.15414285812531378, \"jax_sharpe\": -0.44428402494171454, \"return\": -0.4842074730444672, \"returns_over_hodl\": 0.2955668330647909, \"returns_over_uniform_hodl\": 0.23748116653521678, \"sharpe\": -1.4602933274122933, \"sterling\": -1.9860296256085204, \"ulcer\": -0.16181850208399695}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": 0.9188020872645816, \"optuna_trial_number\": 67, \"price_ratio\": 1.2546879430836384, \"shift_exponent\": 0.05286634041997705, \"step\": 67, \"test_objective\": [{\"annualised_returns\": -0.806770192387734, \"annualised_returns_over_hodl\": 0.902121658344631, \"annualised_returns_over_uniform_hodl\": 0.6973593415782917, \"calmar\": -1.13511657865801, \"daily_log_sharpe\": -1.880484912032606, \"daily_returns\": 0.009135404078003647, \"fee_revenue_over_value\": 0.15414285812531378, \"jax_sharpe\": -0.44428402494171454, \"return\": -0.4842074730444672, \"returns_over_hodl\": 0.2955668330647909, \"returns_over_uniform_hodl\": 0.23748116653521678, \"sharpe\": -1.4602933274122933, \"sterling\": -1.9860296256085204, \"ulcer\": -0.16181850208399695}], \"train_objective\": [{\"annualised_returns\": 1.1086952170021553, \"annualised_returns_over_hodl\": 0.7438608912834237, \"annualised_returns_over_uniform_hodl\": 0.7438608912834233, \"calmar\": 1.816443083398431, \"daily_log_sharpe\": 0.8611009586110463, \"daily_returns\": 0.021332934659964463, \"fee_revenue_over_value\": 0.3562103261900768, \"jax_sharpe\": 1.2847318354048936, \"return\": 0.5729528872060139, \"returns_over_hodl\": 0.40160940059215045, \"returns_over_uniform_hodl\": 0.4016094005921502, \"sharpe\": 1.302015704799296, \"sterling\": 4.315904309555729, \"ulcer\": -0.12489094294582334}], \"train_return\": 0.5729528872060139, \"train_returns_over_hodl\": 0.40160940059215045, \"train_sharpe\": 1.2847318354048936, \"validation_return\": -0.004389998244827331, \"validation_returns_over_hodl\": 0.05244092745460294, \"validation_sharpe\": 0.3011293187616894}, {\"centeredness_margin\": 0.010204139861070244, \"continuous_test_metrics\": [{\"annualised_returns\": -0.8176858246292905, \"annualised_returns_over_hodl\": 0.8064833863282594, \"annualised_returns_over_uniform_hodl\": 0.6014748060431883, \"calmar\": -1.150004899387856, \"daily_log_sharpe\": -1.9503347096575125, \"daily_returns\": 0.009034415526445355, \"fee_revenue_over_value\": 0.1375446093151847, \"jax_sharpe\": -0.4737180144424775, \"return\": -0.4961463235192748, \"returns_over_hodl\": 0.26892734035432064, \"returns_over_uniform_hodl\": 0.20883766776283474, \"sharpe\": -1.534341845028742, \"sterling\": -2.01534677762362, \"ulcer\": -0.1625253480465198}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": 0.9448540750411492, \"optuna_trial_number\": 68, \"price_ratio\": 1.3350402527212561, \"shift_exponent\": 0.358526205287904, \"step\": 68, \"test_objective\": [{\"annualised_returns\": -0.8176858246292905, \"annualised_returns_over_hodl\": 0.8064833863282594, \"annualised_returns_over_uniform_hodl\": 0.6014748060431883, \"calmar\": -1.150004899387856, \"daily_log_sharpe\": -1.9503347096575125, \"daily_returns\": 0.009034415526445355, \"fee_revenue_over_value\": 0.1375446093151847, \"jax_sharpe\": -0.4737180144424775, \"return\": -0.4961463235192748, \"returns_over_hodl\": 0.26892734035432064, \"returns_over_uniform_hodl\": 0.20883766776283474, \"sharpe\": -1.534341845028742, \"sterling\": -2.01534677762362, \"ulcer\": -0.1625253480465198}], \"train_objective\": [{\"annualised_returns\": 1.1087002290044863, \"annualised_returns_over_hodl\": 0.7438650361379211, \"annualised_returns_over_uniform_hodl\": 0.7438650361379202, \"calmar\": 1.7936200381877763, \"daily_log_sharpe\": 0.86574194645199, \"daily_returns\": 0.021144326431624054, \"fee_revenue_over_value\": 0.3212862835739203, \"jax_sharpe\": 1.2848611763256867, \"return\": 0.5729551570107303, \"returns_over_hodl\": 0.4016114231445451, \"returns_over_uniform_hodl\": 0.4016114231445447, \"sharpe\": 1.3047945835001893, \"sterling\": 4.325725439728763, \"ulcer\": -0.12535995058758706}], \"train_return\": 0.5729551570107303, \"train_returns_over_hodl\": 0.4016114231445451, \"train_sharpe\": 1.2848611763256867, \"validation_return\": -0.013452116659141589, \"validation_returns_over_hodl\": 0.0477966775831391, \"validation_sharpe\": 0.2178511379016401}, {\"centeredness_margin\": 0.0112593194077317, \"continuous_test_metrics\": [{\"annualised_returns\": -0.7233938601875773, \"annualised_returns_over_hodl\": 1.7291407376927093, \"annualised_returns_over_uniform_hodl\": 1.429749432295781, \"calmar\": -1.0186144199404679, \"daily_log_sharpe\": -1.5677294271151103, \"daily_returns\": 0.01023383355069124, \"fee_revenue_over_value\": 0.25973025538019245, \"jax_sharpe\": -0.2213617421948667, \"return\": -0.4040406434846867, \"returns_over_hodl\": 0.4983184759227006, \"returns_over_uniform_hodl\": 0.42981613956521514, \"sharpe\": -1.15835406499717, \"sterling\": -1.8707975458416732, \"ulcer\": -0.14805086598877437}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": 1.068432589124584, \"optuna_trial_number\": 69, \"price_ratio\": 1.0689836644012805, \"shift_exponent\": 0.09733128620342002, \"step\": 69, \"test_objective\": [{\"annualised_returns\": -0.7233938601875773, \"annualised_returns_over_hodl\": 1.7291407376927093, \"annualised_returns_over_uniform_hodl\": 1.429749432295781, \"calmar\": -1.0186144199404679, \"daily_log_sharpe\": -1.5677294271151103, \"daily_returns\": 0.01023383355069124, \"fee_revenue_over_value\": 0.25973025538019245, \"jax_sharpe\": -0.2213617421948667, \"return\": -0.4040406434846867, \"returns_over_hodl\": 0.4983184759227006, \"returns_over_uniform_hodl\": 0.42981613956521514, \"sharpe\": -1.15835406499717, \"sterling\": -1.8707975458416732, \"ulcer\": -0.14805086598877437}], \"train_objective\": [{\"annualised_returns\": 1.1006879808192123, \"annualised_returns_over_hodl\": 0.7372390210794644, \"annualised_returns_over_uniform_hodl\": 0.7372390210794617, \"calmar\": 1.6687744548797967, \"daily_log_sharpe\": 0.8577516568752205, \"daily_returns\": 0.02273244229793952, \"fee_revenue_over_value\": 0.6344188177012159, \"jax_sharpe\": 1.2791463260164961, \"return\": 0.5693239080377366, \"returns_over_hodl\": 0.3983757301127737, \"returns_over_uniform_hodl\": 0.3983757301127724, \"sharpe\": 1.291895313584133, \"sterling\": 4.155110780136445, \"ulcer\": -0.13424899246524707}], \"train_return\": 0.5693239080377366, \"train_returns_over_hodl\": 0.3983757301127737, \"train_sharpe\": 1.279146326016496, \"validation_return\": 0.04678216169597227, \"validation_returns_over_hodl\": 0.09690716912368558, \"validation_sharpe\": 0.7860325592285593}, {\"centeredness_margin\": 0.0260817977798621, \"continuous_test_metrics\": [{\"annualised_returns\": -0.8588744716517273, \"annualised_returns_over_hodl\": 0.32181269879688634, \"annualised_returns_over_uniform_hodl\": 0.239667610484682, \"calmar\": -1.2265236797770749, \"daily_log_sharpe\": -2.1994995239074004, \"daily_returns\": 0.007626027584149626, \"fee_revenue_over_value\": 0.059293742484481125, \"jax_sharpe\": -0.6461329366786339, \"return\": -0.5455208096609, \"returns_over_hodl\": 0.11892173426498687, \"returns_over_uniform_hodl\": 0.09037919170026365, \"sharpe\": -1.783352612084795, \"sterling\": -2.082002154489994, \"ulcer\": -0.16655993306128594}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": 0.6849414384564674, \"optuna_trial_number\": 70, \"price_ratio\": 3.2686220068766363, \"shift_exponent\": 0.7523655521245305, \"step\": 70, \"test_objective\": [{\"annualised_returns\": -0.8588744716517273, \"annualised_returns_over_hodl\": 0.32181269879688634, \"annualised_returns_over_uniform_hodl\": 0.239667610484682, \"calmar\": -1.2265236797770749, \"daily_log_sharpe\": -2.1994995239074004, \"daily_returns\": 0.007626027584149626, \"fee_revenue_over_value\": 0.059293742484481125, \"jax_sharpe\": -0.6461329366786339, \"return\": -0.5455208096609, \"returns_over_hodl\": 0.11892173426498687, \"returns_over_uniform_hodl\": 0.09037919170026365, \"sharpe\": -1.783352612084795, \"sterling\": -2.082002154489994, \"ulcer\": -0.16655993306128594}], \"train_objective\": [{\"annualised_returns\": 0.4986107110177409, \"annualised_returns_over_hodl\": 0.2393296997740626, \"annualised_returns_over_uniform_hodl\": 0.2393296997740626, \"calmar\": 0.7857499367769226, \"daily_log_sharpe\": 0.49544530055751007, \"daily_returns\": 0.02056889228261817, \"fee_revenue_over_value\": 0.102850204374457, \"jax_sharpe\": 0.9001892981938768, \"return\": 0.2783931862422715, \"returns_over_hodl\": 0.13913641156338175, \"returns_over_uniform_hodl\": 0.13913641156338175, \"sharpe\": 0.920494391935395, \"sterling\": 1.9172367851511818, \"ulcer\": -0.13159586065630327}], \"train_return\": 0.2783931862422715, \"train_returns_over_hodl\": 0.13913641156338175, \"train_sharpe\": 0.9001892981938768, \"validation_return\": -0.012266663165267144, \"validation_returns_over_hodl\": 0.012577807948186637, \"validation_sharpe\": 0.20454792780677142}, {\"centeredness_margin\": 0.13159793255868035, \"continuous_test_metrics\": [{\"annualised_returns\": -0.8650024317700594, \"annualised_returns_over_hodl\": 0.23636246412658646, \"annualised_returns_over_uniform_hodl\": 0.18583869826767252, \"calmar\": -1.271548177950149, \"daily_log_sharpe\": -2.342329622824356, \"daily_returns\": 0.007252615260167188, \"fee_revenue_over_value\": 0.044803892080420266, \"jax_sharpe\": -0.726173479411077, \"return\": -0.5535741192014905, \"returns_over_hodl\": 0.0892074508994436, \"returns_over_uniform_hodl\": 0.07105782048230047, \"sharpe\": -1.9433877756002897, \"sterling\": -2.1535484526944613, \"ulcer\": -0.16278347230512288}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": 0.6397317308965654, \"optuna_trial_number\": 71, \"price_ratio\": 5.582380816689645, \"shift_exponent\": 0.6034499954919226, \"step\": 71, \"test_objective\": [{\"annualised_returns\": -0.8650024317700594, \"annualised_returns_over_hodl\": 0.23636246412658646, \"annualised_returns_over_uniform_hodl\": 0.18583869826767252, \"calmar\": -1.271548177950149, \"daily_log_sharpe\": -2.342329622824356, \"daily_returns\": 0.007252615260167188, \"fee_revenue_over_value\": 0.044803892080420266, \"jax_sharpe\": -0.726173479411077, \"return\": -0.5535741192014905, \"returns_over_hodl\": 0.0892074508994436, \"returns_over_uniform_hodl\": 0.07105782048230047, \"sharpe\": -1.9433877756002897, \"sterling\": -2.1535484526944613, \"ulcer\": -0.16278347230512288}], \"train_objective\": [{\"annualised_returns\": 0.4201573838217443, \"annualised_returns_over_hodl\": 0.17444991630176876, \"annualised_returns_over_uniform_hodl\": 0.17444991630176876, \"calmar\": 0.6584224820227047, \"daily_log_sharpe\": 0.4336759646133175, \"daily_returns\": 0.02051796766950355, \"fee_revenue_over_value\": 0.07640764058562688, \"jax_sharpe\": 0.8386459429979544, \"return\": 0.23733338912968893, \"returns_over_hodl\": 0.10254930327330047, \"returns_over_uniform_hodl\": 0.10254930327330047, \"sharpe\": 0.8584445239634553, \"sterling\": 1.610223409310457, \"ulcer\": -0.13265452544270895}], \"train_return\": 0.23733338912968893, \"train_returns_over_hodl\": 0.10254930327330047, \"train_sharpe\": 0.8386459429979543, \"validation_return\": -0.0075926346625282415, \"validation_returns_over_hodl\": 0.009298756887555992, \"validation_sharpe\": 0.24398227202710984}, {\"centeredness_margin\": 0.09754814275097279, \"continuous_test_metrics\": [{\"annualised_returns\": -0.8632850413310592, \"annualised_returns_over_hodl\": 0.2593889203819293, \"annualised_returns_over_uniform_hodl\": 0.20092451106640752, \"calmar\": -1.3206009695956764, \"daily_log_sharpe\": -2.301597167739193, \"daily_returns\": 0.007345885466109462, \"fee_revenue_over_value\": 0.048701032552880205, \"jax_sharpe\": -1.308491237086744, \"return\": -0.5512954937508088, \"returns_over_hodl\": 0.09733231129590747, \"returns_over_uniform_hodl\": 0.0765246621549589, \"sharpe\": -1.8979271551759396, \"sterling\": -2.292894642278004, \"ulcer\": -0.16381304283141973}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": 0.6513387310291936, \"optuna_trial_number\": 72, \"price_ratio\": 4.6626283528213675, \"shift_exponent\": 0.28168736757269597, \"step\": 72, \"test_objective\": [{\"annualised_returns\": -0.8632850413310592, \"annualised_returns_over_hodl\": 0.2593889203819293, \"annualised_returns_over_uniform_hodl\": 0.20092451106640752, \"calmar\": -1.3206009695956764, \"daily_log_sharpe\": -2.301597167739193, \"daily_returns\": 0.007345885466109462, \"fee_revenue_over_value\": 0.048701032552880205, \"jax_sharpe\": -1.308491237086744, \"return\": -0.5512954937508088, \"returns_over_hodl\": 0.09733231129590747, \"returns_over_uniform_hodl\": 0.0765246621549589, \"sharpe\": -1.8979271551759396, \"sterling\": -2.292894642278004, \"ulcer\": -0.16381304283141973}], \"train_objective\": [{\"annualised_returns\": 0.4399132203805902, \"annualised_returns_over_hodl\": 0.19078771157525432, \"annualised_returns_over_uniform_hodl\": 0.19078771157525432, \"calmar\": 0.690328605744161, \"daily_log_sharpe\": 0.44953722080127706, \"daily_returns\": 0.02051388692315839, \"fee_revenue_over_value\": 0.08328873371242297, \"jax_sharpe\": 0.8544427891737252, \"return\": 0.2477551337249988, \"returns_over_hodl\": 0.11183579577677483, \"returns_over_uniform_hodl\": 0.11183579577677483, \"sharpe\": 0.8743732112439062, \"sterling\": 1.687209682590388, \"ulcer\": -0.1323945494784543}], \"train_return\": 0.2477551337249988, \"train_returns_over_hodl\": 0.11183579577677483, \"train_sharpe\": 0.8544427891737251, \"validation_return\": -0.008756907684087212, \"validation_returns_over_hodl\": 0.010218317206176497, \"validation_sharpe\": 0.23403976018018793}, {\"centeredness_margin\": 0.047485621636512175, \"continuous_test_metrics\": [{\"annualised_returns\": -0.8589142230630269, \"annualised_returns_over_hodl\": 0.35226452186057444, \"annualised_returns_over_uniform_hodl\": 0.2393184281812677, \"calmar\": -1.2130306567303037, \"daily_log_sharpe\": -2.1898989844218133, \"daily_returns\": 0.008008144536868883, \"fee_revenue_over_value\": 0.07008346074905179, \"jax_sharpe\": -0.6263311246560145, \"return\": -0.5455723704942215, \"returns_over_hodl\": 0.12923280252982816, \"returns_over_uniform_hodl\": 0.09025548777507719, \"sharpe\": -1.7723814313794848, \"sterling\": -2.0744525984686146, \"ulcer\": -0.1681762762961617}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": 0.7327972734862772, \"optuna_trial_number\": 73, \"price_ratio\": 2.4316554520523574, \"shift_exponent\": 0.4016386215693828, \"step\": 73, \"test_objective\": [{\"annualised_returns\": -0.8589142230630269, \"annualised_returns_over_hodl\": 0.35226452186057444, \"annualised_returns_over_uniform_hodl\": 0.2393184281812677, \"calmar\": -1.2130306567303037, \"daily_log_sharpe\": -2.1898989844218133, \"daily_returns\": 0.008008144536868883, \"fee_revenue_over_value\": 0.07008346074905179, \"jax_sharpe\": -0.6263311246560145, \"return\": -0.5455723704942215, \"returns_over_hodl\": 0.12923280252982816, \"returns_over_uniform_hodl\": 0.09025548777507719, \"sharpe\": -1.7723814313794848, \"sterling\": -2.0744525984686146, \"ulcer\": -0.1681762762961617}], \"train_objective\": [{\"annualised_returns\": 0.5886326212527841, \"annualised_returns_over_hodl\": 0.31377653654391047, \"annualised_returns_over_uniform_hodl\": 0.3137765365439109, \"calmar\": 0.9339423173984626, \"daily_log_sharpe\": 0.5624148410784332, \"daily_returns\": 0.020636475370811605, \"fee_revenue_over_value\": 0.13058236013424496, \"jax_sharpe\": 0.9671025916192911, \"return\": 0.324480751981266, \"returns_over_hodl\": 0.1802036081180889, \"returns_over_uniform_hodl\": 0.1802036081180891, \"sharpe\": 0.9878620482417093, \"sterling\": 2.2718913324716072, \"ulcer\": -0.1304172649346789}], \"train_return\": 0.324480751981266, \"train_returns_over_hodl\": 0.1802036081180889, \"train_sharpe\": 0.9671025916192911, \"validation_return\": -0.016815471450565944, \"validation_returns_over_hodl\": 0.01608440452595472, \"validation_sharpe\": 0.16906804866292707}, {\"centeredness_margin\": 0.8656782681691958, \"continuous_test_metrics\": [{\"annualised_returns\": -0.8855118066591465, \"annualised_returns_over_hodl\": 0.01422793647570364, \"annualised_returns_over_uniform_hodl\": 0.0056813018075134725, \"calmar\": -1.3347198127855875, \"daily_log_sharpe\": -2.668152852294835, \"daily_returns\": 0.006537214267921668, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": -1.7799497523605199, \"return\": -0.5822395388917941, \"returns_over_hodl\": 0.005705964149067988, \"returns_over_uniform_hodl\": 0.002284204844715365, \"sharpe\": -2.2921662398760145, \"sterling\": -2.4960779269268274, \"ulcer\": -0.15926538753626335}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -0.6518983215553561, \"optuna_trial_number\": 74, \"price_ratio\": 180.80544138433476, \"shift_exponent\": 3.324888862755109e-05, \"step\": 74, \"test_objective\": [{\"annualised_returns\": -0.8855118066591465, \"annualised_returns_over_hodl\": 0.01422793647570364, \"annualised_returns_over_uniform_hodl\": 0.0056813018075134725, \"calmar\": -1.3347198127855875, \"daily_log_sharpe\": -2.668152852294835, \"daily_returns\": 0.006537214267921668, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": -1.7799497523605199, \"return\": -0.5822395388917941, \"returns_over_hodl\": 0.005705964149067988, \"returns_over_uniform_hodl\": 0.002284204844715365, \"sharpe\": -2.2921662398760145, \"sterling\": -2.4960779269268274, \"ulcer\": -0.15926538753626335}], \"train_objective\": [{\"annualised_returns\": 0.26585578402893684, \"annualised_returns_over_hodl\": 0.04684469238340494, \"annualised_returns_over_uniform_hodl\": 0.04684469238340494, \"calmar\": 0.4131079662455968, \"daily_log_sharpe\": 0.302886969760485, \"daily_returns\": 0.02041805117902083, \"fee_revenue_over_value\": 0.017700772816201745, \"jax_sharpe\": 0.7074666869994083, \"return\": 0.15387736933223595, \"returns_over_hodl\": 0.028184239427116564, \"returns_over_uniform_hodl\": 0.028184239427116564, \"sharpe\": 0.7272064816666758, \"sterling\": 1.0123930476118055, \"ulcer\": -0.1345026185078521}], \"train_return\": 0.15387736933223595, \"train_returns_over_hodl\": 0.028184239427116564, \"train_sharpe\": 0.7074666869994083, \"validation_return\": -0.005923629681483078, \"validation_returns_over_hodl\": -0.0009235433569868556, \"validation_sharpe\": 0.25111269403060493}, {\"centeredness_margin\": 0.8819596751813354, \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -Infinity, \"optuna_trial_number\": 75, \"price_ratio\": 7.4033976843763405, \"shift_exponent\": 98.56454623393381, \"step\": 75, \"test_objective\": -Infinity, \"train_objective\": -Infinity, \"train_return\": -Infinity, \"train_returns_over_hodl\": -Infinity, \"train_sharpe\": -Infinity, \"validation_return\": -Infinity, \"validation_returns_over_hodl\": -Infinity, \"validation_sharpe\": -Infinity}, {\"centeredness_margin\": 0.1703618557170538, \"continuous_test_metrics\": [{\"annualised_returns\": -0.8134893104118466, \"annualised_returns_over_hodl\": 0.7779219998084925, \"annualised_returns_over_uniform_hodl\": 0.6383376104782967, \"calmar\": -1.1496450542287913, \"daily_log_sharpe\": -2.0190448740225526, \"daily_returns\": 0.008584242096342799, \"fee_revenue_over_value\": 0.13670938288160586, \"jax_sharpe\": -0.44941515105097485, \"return\": -0.4915072014241003, \"returns_over_hodl\": 0.26080899460410256, \"returns_over_uniform_hodl\": 0.21996777516458632, \"sharpe\": -1.6204775260665674, \"sterling\": -2.0518458521663776, \"ulcer\": -0.15653318137779854}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": 0.840313094995357, \"optuna_trial_number\": 76, \"price_ratio\": 1.343706402861736, \"shift_exponent\": 0.2601596689879469, \"step\": 76, \"test_objective\": [{\"annualised_returns\": -0.8134893104118466, \"annualised_returns_over_hodl\": 0.7779219998084925, \"annualised_returns_over_uniform_hodl\": 0.6383376104782967, \"calmar\": -1.1496450542287913, \"daily_log_sharpe\": -2.0190448740225526, \"daily_returns\": 0.008584242096342799, \"fee_revenue_over_value\": 0.13670938288160586, \"jax_sharpe\": -0.44941515105097485, \"return\": -0.4915072014241003, \"returns_over_hodl\": 0.26080899460410256, \"returns_over_uniform_hodl\": 0.21996777516458632, \"sharpe\": -1.6204775260665674, \"sterling\": -2.0518458521663776, \"ulcer\": -0.15653318137779854}], \"train_objective\": [{\"annualised_returns\": 0.7937690525535654, \"annualised_returns_over_hodl\": 0.4834214416200697, \"annualised_returns_over_uniform_hodl\": 0.4834214416200697, \"calmar\": 1.25523753783968, \"daily_log_sharpe\": 0.6856287893441362, \"daily_returns\": 0.021127105807465008, \"fee_revenue_over_value\": 0.29386081090851207, \"jax_sharpe\": 1.1040359387407659, \"return\": 0.4258278238002824, \"returns_over_hodl\": 0.2705108320275973, \"returns_over_uniform_hodl\": 0.2705108320275973, \"sharpe\": 1.1205587354233157, \"sterling\": 3.0528830434420877, \"ulcer\": -0.13010778500164255}], \"train_return\": 0.4258278238002824, \"train_returns_over_hodl\": 0.2705108320275973, \"train_sharpe\": 1.1040359387407659, \"validation_return\": 0.00545882555010202, \"validation_returns_over_hodl\": 0.04511725989795523, \"validation_sharpe\": 0.38490946750397165}, {\"centeredness_margin\": 0.1073697358517352, \"continuous_test_metrics\": [{\"annualised_returns\": -0.7988123229454223, \"annualised_returns_over_hodl\": 0.9393525119704065, \"annualised_returns_over_uniform_hodl\": 0.7672624491985811, \"calmar\": -1.1267403635103126, \"daily_log_sharpe\": -1.922361510632204, \"daily_returns\": 0.00891575189574234, \"fee_revenue_over_value\": 0.16025919554119464, \"jax_sharpe\": -0.40357068780360184, \"return\": -0.4757554382399316, \"returns_over_hodl\": 0.30572060094165177, \"returns_over_uniform_hodl\": 0.2577591530179768, \"sharpe\": -1.5205946797881278, \"sterling\": -2.019646548306182, \"ulcer\": -0.15627630539144588}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": 0.8482522759007192, \"optuna_trial_number\": 77, \"price_ratio\": 1.2444541306463432, \"shift_exponent\": 1.126386797898276, \"step\": 77, \"test_objective\": [{\"annualised_returns\": -0.7988123229454223, \"annualised_returns_over_hodl\": 0.9393525119704065, \"annualised_returns_over_uniform_hodl\": 0.7672624491985811, \"calmar\": -1.1267403635103126, \"daily_log_sharpe\": -1.922361510632204, \"daily_returns\": 0.00891575189574234, \"fee_revenue_over_value\": 0.16025919554119464, \"jax_sharpe\": -0.40357068780360184, \"return\": -0.4757554382399316, \"returns_over_hodl\": 0.30572060094165177, \"returns_over_uniform_hodl\": 0.2577591530179768, \"sharpe\": -1.5205946797881278, \"sterling\": -2.019646548306182, \"ulcer\": -0.15627630539144588}], \"train_objective\": [{\"annualised_returns\": 0.7564800516110419, \"annualised_returns_over_hodl\": 0.45258396928438316, \"annualised_returns_over_uniform_hodl\": 0.4525839692843836, \"calmar\": 1.1698337299003734, \"daily_log_sharpe\": 0.6603478938919921, \"daily_returns\": 0.02135413357892627, \"fee_revenue_over_value\": 0.3510065318905236, \"jax_sharpe\": 1.0802809052896123, \"return\": 0.4077584138789343, \"returns_over_hodl\": 0.2544097427865195, \"returns_over_uniform_hodl\": 0.25440974278651973, \"sharpe\": 1.0943471266106244, \"sterling\": 2.887058605763902, \"ulcer\": -0.13268778307210385}], \"train_return\": 0.4077584138789343, \"train_returns_over_hodl\": 0.2544097427865195, \"train_sharpe\": 1.0802809052896123, \"validation_return\": 0.00900437426692946, \"validation_returns_over_hodl\": 0.05570231623229582, \"validation_sharpe\": 0.4216025523044014}, {\"centeredness_margin\": 0.8517633244839415, \"continuous_test_metrics\": [{\"annualised_returns\": -0.8855432819053148, \"annualised_returns_over_hodl\": 0.014519143422073055, \"annualised_returns_over_uniform_hodl\": 0.0054048185683470695, \"calmar\": -1.3344471063581818, \"daily_log_sharpe\": -2.665850093452464, \"daily_returns\": 0.006545320142001202, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": -1.7735818373018988, \"return\": -0.5822857976366806, \"returns_over_hodl\": 0.005822248794211005, \"returns_over_uniform_hodl\": 0.002173221605151099, \"sharpe\": -2.2895347256074556, \"sterling\": -2.4924141776563404, \"ulcer\": -0.1593930928073339}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -0.5970400797180357, \"optuna_trial_number\": 78, \"price_ratio\": 150.32111912035708, \"shift_exponent\": 1.7244305743547473e-05, \"step\": 78, \"test_objective\": [{\"annualised_returns\": -0.8855432819053148, \"annualised_returns_over_hodl\": 0.014519143422073055, \"annualised_returns_over_uniform_hodl\": 0.0054048185683470695, \"calmar\": -1.3344471063581818, \"daily_log_sharpe\": -2.665850093452464, \"daily_returns\": 0.006545320142001202, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": -1.7735818373018988, \"return\": -0.5822857976366806, \"returns_over_hodl\": 0.005822248794211005, \"returns_over_uniform_hodl\": 0.002173221605151099, \"sharpe\": -2.2895347256074556, \"sterling\": -2.4924141776563404, \"ulcer\": -0.1593930928073339}], \"train_objective\": [{\"annualised_returns\": 0.26920416492888033, \"annualised_returns_over_hodl\": 0.0496137556664491, \"annualised_returns_over_uniform_hodl\": 0.049613755666449544, \"calmar\": 0.41841875468791334, \"daily_log_sharpe\": 0.3060006536934911, \"daily_returns\": 0.020418623941938868, \"fee_revenue_over_value\": 0.018933957839094507, \"jax_sharpe\": 0.7104747673099734, \"return\": 0.15572945199176913, \"returns_over_hodl\": 0.029834572687184036, \"returns_over_uniform_hodl\": 0.029834572687184258, \"sharpe\": 0.7303158974600797, \"sterling\": 1.025361044035124, \"ulcer\": -0.13444905502515211}], \"train_return\": 0.15572945199176913, \"train_returns_over_hodl\": 0.029834572687184036, \"train_sharpe\": 0.7104747673099734, \"validation_return\": -0.006110338573980023, \"validation_returns_over_hodl\": -0.0008595117569496491, \"validation_sharpe\": 0.24929942390938323}, {\"centeredness_margin\": 0.060015520817103075, \"continuous_test_metrics\": [{\"annualised_returns\": -0.7562465700966392, \"annualised_returns_over_hodl\": 1.378961854683682, \"annualised_returns_over_uniform_hodl\": 1.141166346956271, \"calmar\": -1.0656146815030327, \"daily_log_sharpe\": -1.7191425563543061, \"daily_returns\": 0.009628822357524868, \"fee_revenue_over_value\": 0.21436233565716373, \"jax_sharpe\": -0.31343879228280075, \"return\": -0.4336278712233844, \"returns_over_hodl\": 0.417703703343727, \"returns_over_uniform_hodl\": 0.3588309368273257, \"sharpe\": -1.3146107332021493, \"sterling\": -1.9428362732670823, \"ulcer\": -0.15107742849727698}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": 0.9602712294150111, \"optuna_trial_number\": 79, \"price_ratio\": 1.1181304087642856, \"shift_exponent\": 0.218047077695014, \"step\": 79, \"test_objective\": [{\"annualised_returns\": -0.7562465700966392, \"annualised_returns_over_hodl\": 1.378961854683682, \"annualised_returns_over_uniform_hodl\": 1.141166346956271, \"calmar\": -1.0656146815030327, \"daily_log_sharpe\": -1.7191425563543061, \"daily_returns\": 0.009628822357524868, \"fee_revenue_over_value\": 0.21436233565716373, \"jax_sharpe\": -0.31343879228280075, \"return\": -0.4336278712233844, \"returns_over_hodl\": 0.417703703343727, \"returns_over_uniform_hodl\": 0.3588309368273257, \"sharpe\": -1.3146107332021493, \"sterling\": -1.9428362732670823, \"ulcer\": -0.15107742849727698}], \"train_objective\": [{\"annualised_returns\": 0.9103110214407966, \"annualised_returns_over_hodl\": 0.5797999889307335, \"annualised_returns_over_uniform_hodl\": 0.5797999889307335, \"calmar\": 1.3890130907557525, \"daily_log_sharpe\": 0.7550964856001391, \"daily_returns\": 0.022039397616269347, \"fee_revenue_over_value\": 0.5049759275714946, \"jax_sharpe\": 1.1745225416204965, \"return\": 0.4813725716454831, \"returns_over_hodl\": 0.32000502944862763, \"returns_over_uniform_hodl\": 0.32000502944862763, \"sharpe\": 1.1871230943244409, \"sterling\": 3.477623816888407, \"ulcer\": -0.1327393803430916}], \"train_return\": 0.4813725716454831, \"train_returns_over_hodl\": 0.32000502944862763, \"train_sharpe\": 1.1745225416204965, \"validation_return\": 0.030365428831728547, \"validation_returns_over_hodl\": 0.07989782300418047, \"validation_sharpe\": 0.6292016230490227}, {\"centeredness_margin\": 0.1400423301485942, \"continuous_test_metrics\": [{\"annualised_returns\": -0.7436524904862709, \"annualised_returns_over_hodl\": 1.4601489551027766, \"annualised_returns_over_uniform_hodl\": 1.251794613575123, \"calmar\": -1.0503442023135676, \"daily_log_sharpe\": -1.704823616706742, \"daily_returns\": 0.010016403459294764, \"fee_revenue_over_value\": 0.2715761703386477, \"jax_sharpe\": -0.2911591642411357, \"return\": -0.42201958904341663, \"returns_over_hodl\": 0.436993966201364, \"returns_over_uniform_hodl\": 0.38668134144316246, \"sharpe\": -1.3100791437593684, \"sterling\": -1.9325599799755302, \"ulcer\": -0.147650244329027}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": 0.8927573680232369, \"optuna_trial_number\": 80, \"price_ratio\": 1.0498479244484404, \"shift_exponent\": 0.975895188158939, \"step\": 80, \"test_objective\": [{\"annualised_returns\": -0.7436524904862709, \"annualised_returns_over_hodl\": 1.4601489551027766, \"annualised_returns_over_uniform_hodl\": 1.251794613575123, \"calmar\": -1.0503442023135676, \"daily_log_sharpe\": -1.704823616706742, \"daily_returns\": 0.010016403459294764, \"fee_revenue_over_value\": 0.2715761703386477, \"jax_sharpe\": -0.2911591642411357, \"return\": -0.42201958904341663, \"returns_over_hodl\": 0.436993966201364, \"returns_over_uniform_hodl\": 0.38668134144316246, \"sharpe\": -1.3100791437593684, \"sterling\": -1.9325599799755302, \"ulcer\": -0.147650244329027}], \"train_objective\": [{\"annualised_returns\": 0.4278284781838235, \"annualised_returns_over_hodl\": 0.18079380201057926, \"annualised_returns_over_uniform_hodl\": 0.18079380201057837, \"calmar\": 0.5889858843250872, \"daily_log_sharpe\": 0.4089303153993429, \"daily_returns\": 0.02275784039552996, \"fee_revenue_over_value\": 0.612223005853979, \"jax_sharpe\": 0.845251269811301, \"return\": 0.24138682398882483, \"returns_over_hodl\": 0.10616119301867322, \"returns_over_uniform_hodl\": 0.10616119301867277, \"sharpe\": 0.8459372810398968, \"sterling\": 1.5262641276670275, \"ulcer\": -0.1502950942748843}], \"train_return\": 0.24138682398882483, \"train_returns_over_hodl\": 0.10616119301867322, \"train_sharpe\": 0.845251269811301, \"validation_return\": 0.03005497854552197, \"validation_returns_over_hodl\": 0.06244538919407283, \"validation_sharpe\": 0.6255682827094617}, {\"centeredness_margin\": 0.245736562640092, \"continuous_test_metrics\": [{\"annualised_returns\": -0.803142656733956, \"annualised_returns_over_hodl\": 0.8503448208607245, \"annualised_returns_over_uniform_hodl\": 0.7292241537671185, \"calmar\": -1.3196720861682845, \"daily_log_sharpe\": -1.9895994792852456, \"daily_returns\": 0.008641143684904632, \"fee_revenue_over_value\": 0.15961375414055204, \"jax_sharpe\": -1.0077484585356025, \"return\": -0.4803293856141677, \"returns_over_hodl\": 0.2812466951005088, \"returns_over_uniform_hodl\": 0.2467854117624575, \"sharpe\": -1.600110096109307, \"sterling\": -2.2569222177028894, \"ulcer\": -0.15381397410428826}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": 0.824511486119726, \"optuna_trial_number\": 81, \"price_ratio\": 1.2443417749348746, \"shift_exponent\": 5.368785795889879, \"step\": 81, \"test_objective\": [{\"annualised_returns\": -0.803142656733956, \"annualised_returns_over_hodl\": 0.8503448208607245, \"annualised_returns_over_uniform_hodl\": 0.7292241537671185, \"calmar\": -1.3196720861682845, \"daily_log_sharpe\": -1.9895994792852456, \"daily_returns\": 0.008641143684904632, \"fee_revenue_over_value\": 0.15961375414055204, \"jax_sharpe\": -1.0077484585356025, \"return\": -0.4803293856141677, \"returns_over_hodl\": 0.2812466951005088, \"returns_over_uniform_hodl\": 0.2467854117624575, \"sharpe\": -1.600110096109307, \"sterling\": -2.2569222177028894, \"ulcer\": -0.15381397410428826}], \"train_objective\": [{\"annualised_returns\": 0.658051634304728, \"annualised_returns_over_hodl\": 0.3711850709763447, \"annualised_returns_over_uniform_hodl\": 0.3711850709763451, \"calmar\": 0.9986427319419272, \"daily_log_sharpe\": 0.6001426688000948, \"daily_returns\": 0.02132769007818951, \"fee_revenue_over_value\": 0.338191365156682, \"jax_sharpe\": 1.0150257358818968, \"return\": 0.35932302378118464, \"returns_over_hodl\": 0.21125047296061883, \"returns_over_uniform_hodl\": 0.21125047296061905, \"sharpe\": 1.028464464773798, \"sterling\": 2.50375879354219, \"ulcer\": -0.134809820862409}], \"train_return\": 0.35932302378118464, \"train_returns_over_hodl\": 0.21125047296061883, \"train_sharpe\": 1.0150257358818968, \"validation_return\": 0.02263492208208273, \"validation_returns_over_hodl\": 0.05516409934860911, \"validation_sharpe\": 0.5512994204881818}, {\"centeredness_margin\": 0.1405832335653132, \"continuous_test_metrics\": [{\"annualised_returns\": -0.7498849607171193, \"annualised_returns_over_hodl\": 1.396164238087954, \"annualised_returns_over_uniform_hodl\": 1.1970476690008858, \"calmar\": -1.2845172993662104, \"daily_log_sharpe\": -1.7337750995140013, \"daily_returns\": 0.009789257084166253, \"fee_revenue_over_value\": 0.24719742245355855, \"jax_sharpe\": -0.7515186549826954, \"return\": -0.427720570393772, \"returns_over_hodl\": 0.42182348123168856, \"returns_over_uniform_hodl\": 0.3730036383297137, \"sharpe\": -1.339471795359999, \"sterling\": -2.1238871655459883, \"ulcer\": -0.14706468369935413}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": 0.9450731405440955, \"optuna_trial_number\": 82, \"price_ratio\": 1.0741409098198815, \"shift_exponent\": 0.3842799880478476, \"step\": 82, \"test_objective\": [{\"annualised_returns\": -0.7498849607171193, \"annualised_returns_over_hodl\": 1.396164238087954, \"annualised_returns_over_uniform_hodl\": 1.1970476690008858, \"calmar\": -1.2845172993662104, \"daily_log_sharpe\": -1.7337750995140013, \"daily_returns\": 0.009789257084166253, \"fee_revenue_over_value\": 0.24719742245355855, \"jax_sharpe\": -0.7515186549826954, \"return\": -0.427720570393772, \"returns_over_hodl\": 0.42182348123168856, \"returns_over_uniform_hodl\": 0.3730036383297137, \"sharpe\": -1.339471795359999, \"sterling\": -2.1238871655459883, \"ulcer\": -0.14706468369935413}], \"train_objective\": [{\"annualised_returns\": 0.7059154268885124, \"annualised_returns_over_hodl\": 0.4107677452871594, \"annualised_returns_over_uniform_hodl\": 0.4107677452871572, \"calmar\": 1.0190914073771282, \"daily_log_sharpe\": 0.6179686412860373, \"daily_returns\": 0.02238329287150136, \"fee_revenue_over_value\": 0.5716957302289911, \"jax_sharpe\": 1.045885462931185, \"return\": 0.3830133000796607, \"returns_over_hodl\": 0.23236014142726424, \"returns_over_uniform_hodl\": 0.23236014142726313, \"sharpe\": 1.052720319283362, \"sterling\": 2.6141854626492322, \"ulcer\": -0.14190965838311964}], \"train_return\": 0.3830133000796607, \"train_returns_over_hodl\": 0.23236014142726424, \"train_sharpe\": 1.045885462931185, \"validation_return\": 0.041983289630076914, \"validation_returns_over_hodl\": 0.07696219092147016, \"validation_sharpe\": 0.7425947326424764}, {\"centeredness_margin\": 0.23712018327012258, \"continuous_test_metrics\": [{\"annualised_returns\": -0.7627000315801261, \"annualised_returns_over_hodl\": 1.2358005950555602, \"annualised_returns_over_uniform_hodl\": 1.084478182382345, \"calmar\": -1.0784962713194786, \"daily_log_sharpe\": -1.778858753089402, \"daily_returns\": 0.009543733566285694, \"fee_revenue_over_value\": 0.237620336211534, \"jax_sharpe\": -0.2586370717660638, \"return\": -0.4397153201233086, \"returns_over_hodl\": 0.38270620890260254, \"returns_over_uniform_hodl\": 0.344226026961016, \"sharpe\": -1.3823796558932087, \"sterling\": -1.9731605632550986, \"ulcer\": -0.14809191817674658}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": 0.9467649388097572, \"optuna_trial_number\": 83, \"price_ratio\": 1.0881329518192249, \"shift_exponent\": 0.05921995039737794, \"step\": 83, \"test_objective\": [{\"annualised_returns\": -0.7627000315801261, \"annualised_returns_over_hodl\": 1.2358005950555602, \"annualised_returns_over_uniform_hodl\": 1.084478182382345, \"calmar\": -1.0784962713194786, \"daily_log_sharpe\": -1.778858753089402, \"daily_returns\": 0.009543733566285694, \"fee_revenue_over_value\": 0.237620336211534, \"jax_sharpe\": -0.2586370717660638, \"return\": -0.4397153201233086, \"returns_over_hodl\": 0.38270620890260254, \"returns_over_uniform_hodl\": 0.344226026961016, \"sharpe\": -1.3823796558932087, \"sterling\": -1.9731605632550986, \"ulcer\": -0.14809191817674658}], \"train_objective\": [{\"annualised_returns\": 0.8672901527971213, \"annualised_returns_over_hodl\": 0.5442223436969174, \"annualised_returns_over_uniform_hodl\": 0.5442223436969174, \"calmar\": 1.3216555877654146, \"daily_log_sharpe\": 0.7305621049763954, \"daily_returns\": 0.02225856603728981, \"fee_revenue_over_value\": 0.515662671923414, \"jax_sharpe\": 1.1496621232933721, \"return\": 0.46102782775129025, \"returns_over_hodl\": 0.30187646086486564, \"returns_over_uniform_hodl\": 0.30187646086486564, \"sharpe\": 1.1599576478532139, \"sterling\": 3.326871863123523, \"ulcer\": -0.1336059410627106}], \"train_return\": 0.46102782775129025, \"train_returns_over_hodl\": 0.30187646086486564, \"train_sharpe\": 1.1496621232933721, \"validation_return\": 0.05456812406429168, \"validation_returns_over_hodl\": 0.0802617883374539, \"validation_sharpe\": 0.8679770778123409}, {\"centeredness_margin\": 0.6859063700968128, \"continuous_test_metrics\": [{\"annualised_returns\": -0.8916282497259334, \"annualised_returns_over_hodl\": 0.036834455972157176, \"annualised_returns_over_uniform_hodl\": -0.04804644291743354, \"calmar\": -1.294271734563816, \"daily_log_sharpe\": -2.3711222475278158, \"daily_returns\": 0.0074933453496556154, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": -0.9078373702173362, \"return\": -0.5913756848953824, \"returns_over_hodl\": 0.014674574610475943, \"returns_over_uniform_hodl\": -0.019635090265837984, \"sharpe\": -1.9417186499699413, \"sterling\": -2.111896127281009, \"ulcer\": -0.17598776136629316}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -0.4428920959150493, \"optuna_trial_number\": 84, \"price_ratio\": 2.582679765859417, \"shift_exponent\": 2.9095398904364064e-05, \"step\": 84, \"test_objective\": [{\"annualised_returns\": -0.8916282497259334, \"annualised_returns_over_hodl\": 0.036834455972157176, \"annualised_returns_over_uniform_hodl\": -0.04804644291743354, \"calmar\": -1.294271734563816, \"daily_log_sharpe\": -2.3711222475278158, \"daily_returns\": 0.0074933453496556154, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": -0.9078373702173362, \"return\": -0.5913756848953824, \"returns_over_hodl\": 0.014674574610475943, \"returns_over_uniform_hodl\": -0.019635090265837984, \"sharpe\": -1.9417186499699413, \"sterling\": -2.111896127281009, \"ulcer\": -0.17598776136629316}], \"train_objective\": [{\"annualised_returns\": 0.39186461540772966, \"annualised_returns_over_hodl\": 0.15105220005262754, \"annualised_returns_over_uniform_hodl\": 0.1510522000526271, \"calmar\": 0.6189900993558389, \"daily_log_sharpe\": 0.4144198348208424, \"daily_returns\": 0.020636974306132632, \"fee_revenue_over_value\": 0.04957337948316621, \"jax_sharpe\": 0.8154993271760242, \"return\": 0.22230842711936116, \"returns_over_hodl\": 0.08916102688657324, \"returns_over_uniform_hodl\": 0.08916102688657301, \"sharpe\": 0.8393096805884145, \"sterling\": 1.504414595160884, \"ulcer\": -0.13180127808043032}], \"train_return\": 0.22230842711936116, \"train_returns_over_hodl\": 0.08916102688657324, \"train_sharpe\": 0.8154993271760242, \"validation_return\": -0.0333327805005359, \"validation_returns_over_hodl\": -0.0020195244899925413, \"validation_sharpe\": 0.0009578061117543533}, {\"centeredness_margin\": 0.9198775631874696, \"continuous_test_metrics\": [{\"annualised_returns\": -0.8747205586592653, \"annualised_returns_over_hodl\": 0.10166806350598612, \"annualised_returns_over_uniform_hodl\": 0.10047322768180988, \"calmar\": -1.3390516848456513, \"daily_log_sharpe\": -2.5993510683868535, \"daily_returns\": 0.006599669780965709, \"fee_revenue_over_value\": 0.022001931064165554, \"jax_sharpe\": -1.7563654139051927, \"return\": -0.5668063544325013, \"returns_over_hodl\": 0.039765567648390565, \"returns_over_uniform_hodl\": 0.0393112537257192, \"sharpe\": -2.2264095475631454, \"sterling\": -2.525587989491241, \"ulcer\": -0.15593889676256711}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": 0.5619687987538099, \"optuna_trial_number\": 85, \"price_ratio\": 153.7067818627155, \"shift_exponent\": 0.12779788568047684, \"step\": 85, \"test_objective\": [{\"annualised_returns\": -0.8747205586592653, \"annualised_returns_over_hodl\": 0.10166806350598612, \"annualised_returns_over_uniform_hodl\": 0.10047322768180988, \"calmar\": -1.3390516848456513, \"daily_log_sharpe\": -2.5993510683868535, \"daily_returns\": 0.006599669780965709, \"fee_revenue_over_value\": 0.022001931064165554, \"jax_sharpe\": -1.7563654139051927, \"return\": -0.5668063544325013, \"returns_over_hodl\": 0.039765567648390565, \"returns_over_uniform_hodl\": 0.0393112537257192, \"sharpe\": -2.2264095475631454, \"sterling\": -2.525587989491241, \"ulcer\": -0.15593889676256711}], \"train_objective\": [{\"annualised_returns\": 0.2935516587891449, \"annualised_returns_over_hodl\": 0.06974878608779256, \"annualised_returns_over_uniform_hodl\": 0.06974878608779256, \"calmar\": 0.4552904995611229, \"daily_log_sharpe\": 0.32593321228597844, \"daily_returns\": 0.020419364382264573, \"fee_revenue_over_value\": 0.03638743050645653, \"jax_sharpe\": 0.7322549072542793, \"return\": 0.1691394677618372, \"returns_over_hodl\": 0.041783820702363306, \"returns_over_uniform_hodl\": 0.041783820702363306, \"sharpe\": 0.7504385113115428, \"sterling\": 1.117773987130635, \"ulcer\": -0.13461141078334732}], \"train_return\": 0.1691394677618372, \"train_returns_over_hodl\": 0.041783820702363306, \"train_sharpe\": 0.7322549072542793, \"validation_return\": 0.0023302492702332867, \"validation_returns_over_hodl\": 0.004737262674952003, \"validation_sharpe\": 0.3363065305793628}, {\"centeredness_margin\": 0.30985704727052604, \"continuous_test_metrics\": [{\"annualised_returns\": -0.8190447109417754, \"annualised_returns_over_hodl\": 0.6947472693479455, \"annualised_returns_over_uniform_hodl\": 0.5895381467609553, \"calmar\": -1.1611991361794005, \"daily_log_sharpe\": -2.048803891903609, \"daily_returns\": 0.008036787979564647, \"fee_revenue_over_value\": 0.12155031774195357, \"jax_sharpe\": -0.5129348502576028, \"return\": -0.49766218315126765, \"returns_over_hodl\": 0.23671385432718495, \"returns_over_uniform_hodl\": 0.2052008416211799, \"sharpe\": -1.6482369291931112, \"sterling\": -2.056029344734151, \"ulcer\": -0.15667278627468567}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": 0.7181029763189843, \"optuna_trial_number\": 86, \"price_ratio\": 1.3924069882654377, \"shift_exponent\": 0.011589546354683433, \"step\": 86, \"test_objective\": [{\"annualised_returns\": -0.8190447109417754, \"annualised_returns_over_hodl\": 0.6947472693479455, \"annualised_returns_over_uniform_hodl\": 0.5895381467609553, \"calmar\": -1.1611991361794005, \"daily_log_sharpe\": -2.048803891903609, \"daily_returns\": 0.008036787979564647, \"fee_revenue_over_value\": 0.12155031774195357, \"jax_sharpe\": -0.5129348502576028, \"return\": -0.49766218315126765, \"returns_over_hodl\": 0.23671385432718495, \"returns_over_uniform_hodl\": 0.2052008416211799, \"sharpe\": -1.6482369291931112, \"sterling\": -2.056029344734151, \"ulcer\": -0.15667278627468567}], \"train_objective\": [{\"annualised_returns\": 0.6526362926688047, \"annualised_returns_over_hodl\": 0.3667066606230225, \"annualised_returns_over_uniform_hodl\": 0.3667066606230225, \"calmar\": 1.060381019873463, \"daily_log_sharpe\": 0.6077605576580621, \"daily_returns\": 0.021052953011146576, \"fee_revenue_over_value\": 0.1842745878137002, \"jax_sharpe\": 1.0113373769779481, \"return\": 0.3566258744999016, \"returns_over_hodl\": 0.2088471271146024, \"returns_over_uniform_hodl\": 0.2088471271146024, \"sharpe\": 1.039605113514264, \"sterling\": 2.5234602682068403, \"ulcer\": -0.12787835108240644}], \"train_return\": 0.3566258744999016, \"train_returns_over_hodl\": 0.2088471271146024, \"train_sharpe\": 1.0113373769779481, \"validation_return\": 0.0033042715704501013, \"validation_returns_over_hodl\": 0.04336751027087837, \"validation_sharpe\": 0.3588410412527338}, {\"centeredness_margin\": 0.3298080438839717, \"continuous_test_metrics\": [{\"annualised_returns\": -0.8527060313740574, \"annualised_returns_over_hodl\": 0.36633224897335226, \"annualised_returns_over_uniform_hodl\": 0.293852106436155, \"calmar\": -1.2202995739294975, \"daily_log_sharpe\": -2.2772629029557763, \"daily_returns\": 0.007474723920011355, \"fee_revenue_over_value\": 0.06865611278838736, \"jax_sharpe\": -0.6089931616367227, \"return\": -0.5376225741899592, \"returns_over_hodl\": 0.13394935113852147, \"returns_over_uniform_hodl\": 0.1093285116949525, \"sharpe\": -1.8812701219681576, \"sterling\": -2.122863377850228, \"ulcer\": -0.16044389041469}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": 0.7436440654012698, \"optuna_trial_number\": 87, \"price_ratio\": 2.1741406064640487, \"shift_exponent\": 0.010440830871357314, \"step\": 87, \"test_objective\": [{\"annualised_returns\": -0.8527060313740574, \"annualised_returns_over_hodl\": 0.36633224897335226, \"annualised_returns_over_uniform_hodl\": 0.293852106436155, \"calmar\": -1.2202995739294975, \"daily_log_sharpe\": -2.2772629029557763, \"daily_returns\": 0.007474723920011355, \"fee_revenue_over_value\": 0.06865611278838736, \"jax_sharpe\": -0.6089931616367227, \"return\": -0.5376225741899592, \"returns_over_hodl\": 0.13394935113852147, \"returns_over_uniform_hodl\": 0.1093285116949525, \"sharpe\": -1.8812701219681576, \"sterling\": -2.122863377850228, \"ulcer\": -0.16044389041469}], \"train_objective\": [{\"annualised_returns\": 0.6152399161102604, \"annualised_returns_over_hodl\": 0.33578039018320704, \"annualised_returns_over_uniform_hodl\": 0.33578039018320704, \"calmar\": 0.9791508750643848, \"daily_log_sharpe\": 0.5807207505877635, \"daily_returns\": 0.02068105375667817, \"fee_revenue_over_value\": 0.13808620609977473, \"jax_sharpe\": 0.9861719121686251, \"return\": 0.33790463777959423, \"returns_over_hodl\": 0.19216521528410713, \"returns_over_uniform_hodl\": 0.19216521528410713, \"sharpe\": 1.0066555809642526, \"sterling\": 2.378282571012472, \"ulcer\": -0.12985375245008016}], \"train_return\": 0.33790463777959423, \"train_returns_over_hodl\": 0.19216521528410713, \"train_sharpe\": 0.9861719121686251, \"validation_return\": -0.009628922190090705, \"validation_returns_over_hodl\": 0.019740348694118115, \"validation_sharpe\": 0.22826944338404564}, {\"centeredness_margin\": 0.4051535520386289, \"continuous_test_metrics\": [{\"annualised_returns\": -0.7713819828897104, \"annualised_returns_over_hodl\": 1.1832656355927504, \"annualised_returns_over_uniform_hodl\": 1.0082146320504992, \"calmar\": -1.0905528936823057, \"daily_log_sharpe\": -1.8041729177915784, \"daily_returns\": 0.009073471823959185, \"fee_revenue_over_value\": 0.230314721344573, \"jax_sharpe\": -0.3505803803105133, \"return\": -0.44806296143108615, \"returns_over_hodl\": 0.36952840911916707, \"returns_over_uniform_hodl\": 0.32419849968306313, \"sharpe\": -1.3951789448429746, \"sterling\": -1.9569885901057082, \"ulcer\": -0.1499430432931093}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": 0.6190590904358978, \"optuna_trial_number\": 88, \"price_ratio\": 1.0626706117880955, \"shift_exponent\": 0.009218436678545263, \"step\": 88, \"test_objective\": [{\"annualised_returns\": -0.7713819828897104, \"annualised_returns_over_hodl\": 1.1832656355927504, \"annualised_returns_over_uniform_hodl\": 1.0082146320504992, \"calmar\": -1.0905528936823057, \"daily_log_sharpe\": -1.8041729177915784, \"daily_returns\": 0.009073471823959185, \"fee_revenue_over_value\": 0.230314721344573, \"jax_sharpe\": -0.3505803803105133, \"return\": -0.44806296143108615, \"returns_over_hodl\": 0.36952840911916707, \"returns_over_uniform_hodl\": 0.32419849968306313, \"sharpe\": -1.3951789448429746, \"sterling\": -1.9569885901057082, \"ulcer\": -0.1499430432931093}], \"train_objective\": [{\"annualised_returns\": 0.5097620631390518, \"annualised_returns_over_hodl\": 0.24855170904903434, \"annualised_returns_over_uniform_hodl\": 0.24855170904903523, \"calmar\": 0.8188857391944153, \"daily_log_sharpe\": 0.4932377484677421, \"daily_returns\": 0.021999611232565736, \"fee_revenue_over_value\": 0.26140285071926805, \"jax_sharpe\": 0.90787689399006, \"return\": 0.28416012814388725, \"returns_over_hodl\": 0.1442751541460221, \"returns_over_uniform_hodl\": 0.14427515414602254, \"sharpe\": 0.935866850971183, \"sterling\": 1.9307468553181406, \"ulcer\": -0.130443344910259}], \"train_return\": 0.28416012814388725, \"train_returns_over_hodl\": 0.1442751541460221, \"train_sharpe\": 0.90787689399006, \"validation_return\": -0.027843432108009547, \"validation_returns_over_hodl\": 0.0452210992876414, \"validation_sharpe\": 0.10119744821228935}, {\"centeredness_margin\": 0.20116352753189054, \"continuous_test_metrics\": [{\"annualised_returns\": -0.7398791262635274, \"annualised_returns_over_hodl\": 1.4588642631173285, \"annualised_returns_over_uniform_hodl\": 1.28494040558203, \"calmar\": -1.0455022434942443, \"daily_log_sharpe\": -1.6648054924153706, \"daily_returns\": 0.01044370730914941, \"fee_revenue_over_value\": 0.2945393038976957, \"jax_sharpe\": -0.2709599042888132, \"return\": -0.418608156543273, \"returns_over_hodl\": 0.4366917047839569, \"returns_over_uniform_hodl\": 0.39486599563881697, \"sharpe\": -1.264887411292368, \"sterling\": -1.9128176006988589, \"ulcer\": -0.14664582984113564}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": 0.9052769478572558, \"optuna_trial_number\": 89, \"price_ratio\": 1.0457223020648465, \"shift_exponent\": 0.03990575152263854, \"step\": 89, \"test_objective\": [{\"annualised_returns\": -0.7398791262635274, \"annualised_returns_over_hodl\": 1.4588642631173285, \"annualised_returns_over_uniform_hodl\": 1.28494040558203, \"calmar\": -1.0455022434942443, \"daily_log_sharpe\": -1.6648054924153706, \"daily_returns\": 0.01044370730914941, \"fee_revenue_over_value\": 0.2945393038976957, \"jax_sharpe\": -0.2709599042888132, \"return\": -0.418608156543273, \"returns_over_hodl\": 0.4366917047839569, \"returns_over_uniform_hodl\": 0.39486599563881697, \"sharpe\": -1.264887411292368, \"sterling\": -1.9128176006988589, \"ulcer\": -0.14664582984113564}], \"train_objective\": [{\"annualised_returns\": 0.8817261818669038, \"annualised_returns_over_hodl\": 0.5561607339949783, \"annualised_returns_over_uniform_hodl\": 0.5561607339949779, \"calmar\": 1.3574055483867906, \"daily_log_sharpe\": 0.7242196212575023, \"daily_returns\": 0.02225692170116298, \"fee_revenue_over_value\": 0.6094150175148424, \"jax_sharpe\": 1.1556981816277727, \"return\": 0.4678750159816818, \"returns_over_hodl\": 0.30797777735654286, \"returns_over_uniform_hodl\": 0.30797777735654264, \"sharpe\": 1.160363328600594, \"sterling\": 3.311656978199565, \"ulcer\": -0.13533477451965492}], \"train_return\": 0.4678750159816818, \"train_returns_over_hodl\": 0.30797777735654286, \"train_sharpe\": 1.1556981816277725, \"validation_return\": 0.06770179674114751, \"validation_returns_over_hodl\": 0.08957835336771303, \"validation_sharpe\": 0.9945800249304153}, {\"centeredness_margin\": 0.29780688453965576, \"continuous_test_metrics\": [{\"annualised_returns\": -0.8292778046380214, \"annualised_returns_over_hodl\": 0.5911610744205402, \"annualised_returns_over_uniform_hodl\": 0.49964913122448196, \"calmar\": -1.3205289971699028, \"daily_log_sharpe\": -2.132892428743661, \"daily_returns\": 0.008078722789368723, \"fee_revenue_over_value\": 0.1110830326635044, \"jax_sharpe\": -1.104884574217253, \"return\": -0.509302166786203, \"returns_over_hodl\": 0.20569626635609084, \"returns_over_uniform_hodl\": 0.17727437938251245, \"sharpe\": -1.7371957877618556, \"sterling\": -2.2654836673176577, \"ulcer\": -0.15711977184358844}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": 0.7866251174380109, \"optuna_trial_number\": 90, \"price_ratio\": 1.5093004618061054, \"shift_exponent\": 0.052073476790505425, \"step\": 90, \"test_objective\": [{\"annualised_returns\": -0.8292778046380214, \"annualised_returns_over_hodl\": 0.5911610744205402, \"annualised_returns_over_uniform_hodl\": 0.49964913122448196, \"calmar\": -1.3205289971699028, \"daily_log_sharpe\": -2.132892428743661, \"daily_returns\": 0.008078722789368723, \"fee_revenue_over_value\": 0.1110830326635044, \"jax_sharpe\": -1.104884574217253, \"return\": -0.509302166786203, \"returns_over_hodl\": 0.20569626635609084, \"returns_over_uniform_hodl\": 0.17727437938251245, \"sharpe\": -1.7371957877618556, \"sterling\": -2.2654836673176577, \"ulcer\": -0.15711977184358844}], \"train_objective\": [{\"annualised_returns\": 0.7163576844363198, \"annualised_returns_over_hodl\": 0.4194033434558764, \"annualised_returns_over_uniform_hodl\": 0.4194033434558764, \"calmar\": 1.1436487956917873, \"daily_log_sharpe\": 0.6411035175006874, \"daily_returns\": 0.020915846619693708, \"fee_revenue_over_value\": 0.22213290829779334, \"jax_sharpe\": 1.0545976508423125, \"return\": 0.38814684943789235, \"returns_over_hodl\": 0.23693448761234315, \"returns_over_uniform_hodl\": 0.23693448761234315, \"sharpe\": 1.072586371377205, \"sterling\": 2.760982164774596, \"ulcer\": -0.12917855511936688}], \"train_return\": 0.38814684943789235, \"train_returns_over_hodl\": 0.23693448761234315, \"train_sharpe\": 1.0545976508423125, \"validation_return\": 0.005330610452076945, \"validation_returns_over_hodl\": 0.03520049982768958, \"validation_sharpe\": 0.37891619511019975}, {\"centeredness_margin\": 0.22923936886849533, \"continuous_test_metrics\": [{\"annualised_returns\": -0.7832981226263155, \"annualised_returns_over_hodl\": 1.0433589330421578, \"annualised_returns_over_uniform_hodl\": 0.903541490016097, \"calmar\": -1.31798026888244, \"daily_log_sharpe\": -1.905095121269586, \"daily_returns\": 0.008956349539365046, \"fee_revenue_over_value\": 0.1874093239199499, \"jax_sharpe\": -0.9219341883251861, \"return\": -0.4598345794343096, \"returns_over_hodl\": 0.3334831528214366, \"returns_over_uniform_hodl\": 0.2959562223770722, \"sharpe\": -1.514913147026117, \"sterling\": -2.2237524917987312, \"ulcer\": -0.14914992222872397}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": 0.924941615848381, \"optuna_trial_number\": 91, \"price_ratio\": 1.1678074394320859, \"shift_exponent\": 0.19365221760942983, \"step\": 91, \"test_objective\": [{\"annualised_returns\": -0.7832981226263155, \"annualised_returns_over_hodl\": 1.0433589330421578, \"annualised_returns_over_uniform_hodl\": 0.903541490016097, \"calmar\": -1.31798026888244, \"daily_log_sharpe\": -1.905095121269586, \"daily_returns\": 0.008956349539365046, \"fee_revenue_over_value\": 0.1874093239199499, \"jax_sharpe\": -0.9219341883251861, \"return\": -0.4598345794343096, \"returns_over_hodl\": 0.3334831528214366, \"returns_over_uniform_hodl\": 0.2959562223770722, \"sharpe\": -1.514913147026117, \"sterling\": -2.2237524917987312, \"ulcer\": -0.14914992222872397}], \"train_objective\": [{\"annualised_returns\": 0.830009525397486, \"annualised_returns_over_hodl\": 0.5133917961618604, \"annualised_returns_over_uniform_hodl\": 0.5133917961618604, \"calmar\": 1.2713647405557618, \"daily_log_sharpe\": 0.7124763827975248, \"daily_returns\": 0.02173965703524043, \"fee_revenue_over_value\": 0.41441961097615426, \"jax_sharpe\": 1.127383295011751, \"return\": 0.4432482565636868, \"returns_over_hodl\": 0.2860336379057453, \"returns_over_uniform_hodl\": 0.2860336379057453, \"sharpe\": 1.141019424021638, \"sterling\": 3.1853326681783702, \"ulcer\": -0.1332038057642646}], \"train_return\": 0.4432482565636868, \"train_returns_over_hodl\": 0.2860336379057453, \"train_sharpe\": 1.127383295011751, \"validation_return\": 0.03296815609928183, \"validation_returns_over_hodl\": 0.06487304535661464, \"validation_sharpe\": 0.6541617115980978}, {\"centeredness_margin\": 0.06397298124321928, \"continuous_test_metrics\": [{\"annualised_returns\": -0.7502706863979062, \"annualised_returns_over_hodl\": 1.4505750932744026, \"annualised_returns_over_uniform_hodl\": 1.1936593973068836, \"calmar\": -1.0565245185350498, \"daily_log_sharpe\": -1.643087480407123, \"daily_returns\": 0.009665797929225277, \"fee_revenue_over_value\": 0.22685879253613397, \"jax_sharpe\": -0.2790631963113601, \"return\": -0.4280761766830007, \"returns_over_hodl\": 0.4347391633092712, \"returns_over_uniform_hodl\": 0.37215047341819507, \"sharpe\": -1.2247050448949082, \"sterling\": -1.8950479282407502, \"ulcer\": -0.1533022729799725}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": 0.9615459709968625, \"optuna_trial_number\": 92, \"price_ratio\": 1.104452418968596, \"shift_exponent\": 0.02314481351471341, \"step\": 92, \"test_objective\": [{\"annualised_returns\": -0.7502706863979062, \"annualised_returns_over_hodl\": 1.4505750932744026, \"annualised_returns_over_uniform_hodl\": 1.1936593973068836, \"calmar\": -1.0565245185350498, \"daily_log_sharpe\": -1.643087480407123, \"daily_returns\": 0.009665797929225277, \"fee_revenue_over_value\": 0.22685879253613397, \"jax_sharpe\": -0.2790631963113601, \"return\": -0.4280761766830007, \"returns_over_hodl\": 0.4347391633092712, \"returns_over_uniform_hodl\": 0.37215047341819507, \"sharpe\": -1.2247050448949082, \"sterling\": -1.8950479282407502, \"ulcer\": -0.1533022729799725}], \"train_objective\": [{\"annualised_returns\": 1.24320179785229, \"annualised_returns_over_hodl\": 0.8550959166553054, \"annualised_returns_over_uniform_hodl\": 0.8550959166553058, \"calmar\": 2.074182342547301, \"daily_log_sharpe\": 0.9220597064209274, \"daily_returns\": 0.022249656164452465, \"fee_revenue_over_value\": 0.49863749433747684, \"jax_sharpe\": 1.3533856707548328, \"return\": 0.6331259331011412, \"returns_over_hodl\": 0.45522773047021925, \"returns_over_uniform_hodl\": 0.45522773047021947, \"sharpe\": 1.364320967756572, \"sterling\": 4.818635145475297, \"ulcer\": -0.1260543361191191}], \"train_return\": 0.6331259331011412, \"train_returns_over_hodl\": 0.45522773047021925, \"train_sharpe\": 1.3533856707548328, \"validation_return\": 0.033076146755480984, \"validation_returns_over_hodl\": 0.08586755189788642, \"validation_sharpe\": 0.6553501605541944}, {\"centeredness_margin\": 0.1596956596087773, \"continuous_test_metrics\": [{\"annualised_returns\": -0.7625550510187775, \"annualised_returns_over_hodl\": 1.2782071235628236, \"annualised_returns_over_uniform_hodl\": 1.0857517131755134, \"calmar\": -1.2594971872810587, \"daily_log_sharpe\": -1.730733196322905, \"daily_returns\": 0.009371180056268314, \"fee_revenue_over_value\": 0.22039169597912056, \"jax_sharpe\": -0.7020617492070852, \"return\": -0.4395774836846249, \"returns_over_hodl\": 0.39320912177131007, \"returns_over_uniform_hodl\": 0.3445567219362613, \"sharpe\": -1.3220461406977373, \"sterling\": -2.0831714406879787, \"ulcer\": -0.15087408671462385}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": 0.9078386183160618, \"optuna_trial_number\": 93, \"price_ratio\": 1.111897876608862, \"shift_exponent\": 0.023090035914399017, \"step\": 93, \"test_objective\": [{\"annualised_returns\": -0.7625550510187775, \"annualised_returns_over_hodl\": 1.2782071235628236, \"annualised_returns_over_uniform_hodl\": 1.0857517131755134, \"calmar\": -1.2594971872810587, \"daily_log_sharpe\": -1.730733196322905, \"daily_returns\": 0.009371180056268314, \"fee_revenue_over_value\": 0.22039169597912056, \"jax_sharpe\": -0.7020617492070852, \"return\": -0.4395774836846249, \"returns_over_hodl\": 0.39320912177131007, \"returns_over_uniform_hodl\": 0.3445567219362613, \"sharpe\": -1.3220461406977373, \"sterling\": -2.0831714406879787, \"ulcer\": -0.15087408671462385}], \"train_objective\": [{\"annualised_returns\": 1.0489462665498475, \"annualised_returns_over_hodl\": 0.6944493608029125, \"annualised_returns_over_uniform_hodl\": 0.6944493608029125, \"calmar\": 1.7203311452100871, \"daily_log_sharpe\": 0.8238456302392476, \"daily_returns\": 0.02221801313839634, \"fee_revenue_over_value\": 0.45375223475681087, \"jax_sharpe\": 1.2517419324526273, \"return\": 0.5457415130806917, \"returns_over_hodl\": 0.37736219135448046, \"returns_over_uniform_hodl\": 0.37736219135448046, \"sharpe\": 1.2629029746863716, \"sterling\": 4.0436813669002305, \"ulcer\": -0.1284519611315639}], \"train_return\": 0.5457415130806917, \"train_returns_over_hodl\": 0.37736219135448046, \"train_sharpe\": 1.251741932452627, \"validation_return\": 0.04101904115589039, \"validation_returns_over_hodl\": 0.08549483670233626, \"validation_sharpe\": 0.7326588830402182}, {\"centeredness_margin\": 0.01939650110245826, \"continuous_test_metrics\": [{\"annualised_returns\": -0.8115100001006423, \"annualised_returns_over_hodl\": 0.8786288607289949, \"annualised_returns_over_uniform_hodl\": 0.6557241663524618, \"calmar\": -1.1408731828274103, \"daily_log_sharpe\": -1.8719934854722193, \"daily_returns\": 0.008953158119314082, \"fee_revenue_over_value\": 0.14392937871074868, \"jax_sharpe\": -0.4489540441884839, \"return\": -0.48934076177578834, \"returns_over_hodl\": 0.2890985629610321, \"returns_over_uniform_hodl\": 0.22516546245766422, \"sharpe\": -1.4444532610865723, \"sterling\": -1.9785155098130356, \"ulcer\": -0.1644380238178989}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": 0.9473322090535252, \"optuna_trial_number\": 94, \"price_ratio\": 1.268444239983156, \"shift_exponent\": 0.01467478570097948, \"step\": 94, \"test_objective\": [{\"annualised_returns\": -0.8115100001006423, \"annualised_returns_over_hodl\": 0.8786288607289949, \"annualised_returns_over_uniform_hodl\": 0.6557241663524618, \"calmar\": -1.1408731828274103, \"daily_log_sharpe\": -1.8719934854722193, \"daily_returns\": 0.008953158119314082, \"fee_revenue_over_value\": 0.14392937871074868, \"jax_sharpe\": -0.4489540441884839, \"return\": -0.48934076177578834, \"returns_over_hodl\": 0.2890985629610321, \"returns_over_uniform_hodl\": 0.22516546245766422, \"sharpe\": -1.4444532610865723, \"sterling\": -1.9785155098130356, \"ulcer\": -0.1644380238178989}], \"train_objective\": [{\"annualised_returns\": 1.21099700429968, \"annualised_returns_over_hodl\": 0.8284630113708276, \"annualised_returns_over_uniform_hodl\": 0.8284630113708267, \"calmar\": 2.0168319842132014, \"daily_log_sharpe\": 0.9205647316076634, \"daily_returns\": 0.02129329284820839, \"fee_revenue_over_value\": 0.3318489855449508, \"jax_sharpe\": 1.3396220706073076, \"return\": 0.6188508492503799, \"returns_over_hodl\": 0.44250764719105207, \"returns_over_uniform_hodl\": 0.4425076471910516, \"sharpe\": 1.3585751226719103, \"sterling\": 4.7620134189043615, \"ulcer\": -0.12286496112489909}], \"train_return\": 0.6188508492503799, \"train_returns_over_hodl\": 0.44250764719105207, \"train_sharpe\": 1.3396220706073076, \"validation_return\": -0.016806225888696402, \"validation_returns_over_hodl\": 0.04961325252435311, \"validation_sharpe\": 0.1867473124051763}, {\"centeredness_margin\": 0.06222817339532571, \"continuous_test_metrics\": [{\"annualised_returns\": -0.8214425550418005, \"annualised_returns_over_hodl\": 0.7426017687496529, \"annualised_returns_over_uniform_hodl\": 0.5684751278969464, \"calmar\": -1.156825938747316, \"daily_log_sharpe\": -1.9992631592838273, \"daily_returns\": 0.00880908392225064, \"fee_revenue_over_value\": 0.13047967413302894, \"jax_sharpe\": -0.5104407859758401, \"return\": -0.5003536889610332, \"returns_over_hodl\": 0.2506610314559825, \"returns_over_uniform_hodl\": 0.19874342400626377, \"sharpe\": -1.5888796368778577, \"sterling\": -2.034401643101139, \"ulcer\": -0.16132643973422087}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": 0.9101924726608446, \"optuna_trial_number\": 95, \"price_ratio\": 1.3710656632560094, \"shift_exponent\": 0.1917918802234161, \"step\": 95, \"test_objective\": [{\"annualised_returns\": -0.8214425550418005, \"annualised_returns_over_hodl\": 0.7426017687496529, \"annualised_returns_over_uniform_hodl\": 0.5684751278969464, \"calmar\": -1.156825938747316, \"daily_log_sharpe\": -1.9992631592838273, \"daily_returns\": 0.00880908392225064, \"fee_revenue_over_value\": 0.13047967413302894, \"jax_sharpe\": -0.5104407859758401, \"return\": -0.5003536889610332, \"returns_over_hodl\": 0.2506610314559825, \"returns_over_uniform_hodl\": 0.19874342400626377, \"sharpe\": -1.5888796368778577, \"sterling\": -2.034401643101139, \"ulcer\": -0.16132643973422087}], \"train_objective\": [{\"annualised_returns\": 1.0159973223598482, \"annualised_returns_over_hodl\": 0.6672010535469657, \"annualised_returns_over_uniform_hodl\": 0.6672010535469657, \"calmar\": 1.637277719861035, \"daily_log_sharpe\": 0.8162643462200853, \"daily_returns\": 0.021069587862713752, \"fee_revenue_over_value\": 0.29713040964980364, \"jax_sharpe\": 1.2344940956005241, \"return\": 0.530602297054112, \"returns_over_hodl\": 0.3638721067670516, \"returns_over_uniform_hodl\": 0.3638721067670516, \"sharpe\": 1.2542828678461386, \"sterling\": 3.9487717400991613, \"ulcer\": -0.12636344688417972}], \"train_return\": 0.530602297054112, \"train_returns_over_hodl\": 0.3638721067670516, \"train_sharpe\": 1.2344940956005241, \"validation_return\": -0.009254653974915206, \"validation_returns_over_hodl\": 0.043662405963052375, \"validation_sharpe\": 0.2513041815970133}, {\"centeredness_margin\": 0.10257510657489449, \"continuous_test_metrics\": [{\"annualised_returns\": -0.8162994272630286, \"annualised_returns_over_hodl\": 0.7820344982626954, \"annualised_returns_over_uniform_hodl\": 0.6136531265095957, \"calmar\": -1.2708858527146691, \"daily_log_sharpe\": -1.9488266950130473, \"daily_returns\": 0.008689963914460534, \"fee_revenue_over_value\": 0.1380838473659638, \"jax_sharpe\": -0.7730085382162616, \"return\": -0.4946067134258545, \"returns_over_hodl\": 0.2619827160298678, \"returns_over_uniform_hodl\": 0.21253147563101193, \"sharpe\": -1.5323448269035738, \"sterling\": -2.0984880909140364, \"ulcer\": -0.16162496745269417}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": 0.8683948119440761, \"optuna_trial_number\": 96, \"price_ratio\": 1.3094000598269118, \"shift_exponent\": 0.022900219993330466, \"step\": 96, \"test_objective\": [{\"annualised_returns\": -0.8162994272630286, \"annualised_returns_over_hodl\": 0.7820344982626954, \"annualised_returns_over_uniform_hodl\": 0.6136531265095957, \"calmar\": -1.2708858527146691, \"daily_log_sharpe\": -1.9488266950130473, \"daily_returns\": 0.008689963914460534, \"fee_revenue_over_value\": 0.1380838473659638, \"jax_sharpe\": -0.7730085382162616, \"return\": -0.4946067134258545, \"returns_over_hodl\": 0.2619827160298678, \"returns_over_uniform_hodl\": 0.21253147563101193, \"sharpe\": -1.5323448269035738, \"sterling\": -2.0984880909140364, \"ulcer\": -0.16162496745269417}], \"train_objective\": [{\"annualised_returns\": 0.9916133831934657, \"annualised_returns_over_hodl\": 0.6470358833768828, \"annualised_returns_over_uniform_hodl\": 0.6470358833768823, \"calmar\": 1.6218782603181563, \"daily_log_sharpe\": 0.8011972277635757, \"daily_returns\": 0.02118475207769528, \"fee_revenue_over_value\": 0.29836229050025703, \"jax_sharpe\": 1.2214895584305703, \"return\": 0.5193358162859896, \"returns_over_hodl\": 0.3538328961303996, \"returns_over_uniform_hodl\": 0.35383289613039937, \"sharpe\": 1.2390789597071283, \"sterling\": 3.8512111918347856, \"ulcer\": -0.12572736089773037}], \"train_return\": 0.5193358162859896, \"train_returns_over_hodl\": 0.3538328961303996, \"train_sharpe\": 1.2214895584305703, \"validation_return\": -0.003849246803173334, \"validation_returns_over_hodl\": 0.046730329646061275, \"validation_sharpe\": 0.30049971876788606}, {\"centeredness_margin\": 0.03160301481474666, \"continuous_test_metrics\": [{\"annualised_returns\": -0.7309736574743191, \"annualised_returns_over_hodl\": 1.6379684580095866, \"annualised_returns_over_uniform_hodl\": 1.3631673666667714, \"calmar\": -1.1763464682468894, \"daily_log_sharpe\": -1.606105628744042, \"daily_returns\": 0.010119018932235893, \"fee_revenue_over_value\": 0.2534424321411314, \"jax_sharpe\": -0.5574733630868792, \"return\": -0.4106723716481747, \"returns_over_hodl\": 0.47795496616590105, \"returns_over_uniform_hodl\": 0.4139054036103198, \"sharpe\": -1.1992539502089659, \"sterling\": -2.0078937704788054, \"ulcer\": -0.148017022143969}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": 1.0375376225358195, \"optuna_trial_number\": 97, \"price_ratio\": 1.0736070684847732, \"shift_exponent\": 0.1112322277819404, \"step\": 97, \"test_objective\": [{\"annualised_returns\": -0.7309736574743191, \"annualised_returns_over_hodl\": 1.6379684580095866, \"annualised_returns_over_uniform_hodl\": 1.3631673666667714, \"calmar\": -1.1763464682468894, \"daily_log_sharpe\": -1.606105628744042, \"daily_returns\": 0.010119018932235893, \"fee_revenue_over_value\": 0.2534424321411314, \"jax_sharpe\": -0.5574733630868792, \"return\": -0.4106723716481747, \"returns_over_hodl\": 0.47795496616590105, \"returns_over_uniform_hodl\": 0.4139054036103198, \"sharpe\": -1.1992539502089659, \"sterling\": -2.0078937704788054, \"ulcer\": -0.148017022143969}], \"train_objective\": [{\"annualised_returns\": 1.0181260892840496, \"annualised_returns_over_hodl\": 0.6689615134540428, \"annualised_returns_over_uniform_hodl\": 0.6689615134540394, \"calmar\": 1.5304446042483388, \"daily_log_sharpe\": 0.8124259598285299, \"daily_returns\": 0.022790922681925115, \"fee_revenue_over_value\": 0.6097680349046782, \"jax_sharpe\": 1.2347418423306966, \"return\": 0.5315833354481152, \"returns_over_hodl\": 0.3647462795706764, \"returns_over_uniform_hodl\": 0.36474627957067485, \"sharpe\": 1.2463623680237712, \"sterling\": 3.831297533997656, \"ulcer\": -0.13559178766672939}], \"train_return\": 0.5315833354481152, \"train_returns_over_hodl\": 0.3647462795706764, \"train_sharpe\": 1.2347418423306966, \"validation_return\": 0.04575686397993639, \"validation_returns_over_hodl\": 0.09342502845903655, \"validation_sharpe\": 0.7767254542542859}, {\"centeredness_margin\": 0.15228894107324734, \"continuous_test_metrics\": [{\"annualised_returns\": -0.7353375835720153, \"annualised_returns_over_hodl\": 1.5470868228854648, \"annualised_returns_over_uniform_hodl\": 1.324833991400233, \"calmar\": -1.0374329308016057, \"daily_log_sharpe\": -1.623450447210521, \"daily_returns\": 0.009925271059243488, \"fee_revenue_over_value\": 0.2674198089540152, \"jax_sharpe\": -0.2518096821445721, \"return\": -0.4145411939527759, \"returns_over_hodl\": 0.4572335977855251, \"returns_over_uniform_hodl\": 0.4046233871242064, \"sharpe\": -1.216184554223328, \"sterling\": -1.9006044291676354, \"ulcer\": -0.1466082680673828}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": 0.9145567096109254, \"optuna_trial_number\": 98, \"price_ratio\": 1.0696896113088652, \"shift_exponent\": 0.02292203626698439, \"step\": 98, \"test_objective\": [{\"annualised_returns\": -0.7353375835720153, \"annualised_returns_over_hodl\": 1.5470868228854648, \"annualised_returns_over_uniform_hodl\": 1.324833991400233, \"calmar\": -1.0374329308016057, \"daily_log_sharpe\": -1.623450447210521, \"daily_returns\": 0.009925271059243488, \"fee_revenue_over_value\": 0.2674198089540152, \"jax_sharpe\": -0.2518096821445721, \"return\": -0.4145411939527759, \"returns_over_hodl\": 0.4572335977855251, \"returns_over_uniform_hodl\": 0.4046233871242064, \"sharpe\": -1.216184554223328, \"sterling\": -1.9006044291676354, \"ulcer\": -0.1466082680673828}], \"train_objective\": [{\"annualised_returns\": 0.9742282121655734, \"annualised_returns_over_hodl\": 0.6326585947107102, \"annualised_returns_over_uniform_hodl\": 0.6326585947107111, \"calmar\": 1.5652639732050948, \"daily_log_sharpe\": 0.7755712350626751, \"daily_returns\": 0.0223900793209912, \"fee_revenue_over_value\": 0.5028261854851139, \"jax_sharpe\": 1.2086257418499555, \"return\": 0.5112699626287331, \"returns_over_hodl\": 0.34664566477606895, \"returns_over_uniform_hodl\": 0.3466456647760694, \"sharpe\": 1.215264734840063, \"sterling\": 3.725656602129677, \"ulcer\": -0.13030480990306195}], \"train_return\": 0.5112699626287331, \"train_returns_over_hodl\": 0.34664566477606895, \"train_sharpe\": 1.2086257418499553, \"validation_return\": 0.05846773551568907, \"validation_returns_over_hodl\": 0.10455149223998217, \"validation_sharpe\": 0.9022929247684529}, {\"centeredness_margin\": 0.0825965089862998, \"continuous_test_metrics\": [{\"annualised_returns\": -0.8602163057358828, \"annualised_returns_over_hodl\": 0.3455432372609881, \"annualised_returns_over_uniform_hodl\": 0.2278807405098393, \"calmar\": -1.2135913113864045, \"daily_log_sharpe\": -2.191197771881775, \"daily_returns\": 0.008074999732108939, \"fee_revenue_over_value\": 0.06717210670539009, \"jax_sharpe\": -0.6318848483487698, \"return\": -0.5472660985193203, \"returns_over_hodl\": 0.12696897990923373, \"returns_over_uniform_hodl\": 0.08619192263452735, \"sharpe\": -1.7717627842239816, \"sterling\": -2.071961725191382, \"ulcer\": -0.16869709912838873}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": 0.7402081747450714, \"optuna_trial_number\": 99, \"price_ratio\": 2.340872413041636, \"shift_exponent\": 0.01392182698766476, \"step\": 99, \"test_objective\": [{\"annualised_returns\": -0.8602163057358828, \"annualised_returns_over_hodl\": 0.3455432372609881, \"annualised_returns_over_uniform_hodl\": 0.2278807405098393, \"calmar\": -1.2135913113864045, \"daily_log_sharpe\": -2.191197771881775, \"daily_returns\": 0.008074999732108939, \"fee_revenue_over_value\": 0.06717210670539009, \"jax_sharpe\": -0.6318848483487698, \"return\": -0.5472660985193203, \"returns_over_hodl\": 0.12696897990923373, \"returns_over_uniform_hodl\": 0.08619192263452735, \"sharpe\": -1.7717627842239816, \"sterling\": -2.071961725191382, \"ulcer\": -0.16869709912838873}], \"train_objective\": [{\"annualised_returns\": 0.6030813743931671, \"annualised_returns_over_hodl\": 0.32572544946701343, \"annualised_returns_over_uniform_hodl\": 0.32572544946701343, \"calmar\": 0.9578321421645121, \"daily_log_sharpe\": 0.5727928355918054, \"daily_returns\": 0.0206593264482489, \"fee_revenue_over_value\": 0.13546647254992067, \"jax_sharpe\": 0.9775034289399169, \"return\": 0.3317812875767914, \"returns_over_hodl\": 0.18670888834820532, \"returns_over_uniform_hodl\": 0.18670888834820532, \"sharpe\": 0.998307571675756, \"sterling\": 2.3290783209909023, \"ulcer\": -0.13022299514298308}], \"train_return\": 0.3317812875767914, \"train_returns_over_hodl\": 0.18670888834820532, \"train_sharpe\": 0.9775034289399169, \"validation_return\": -0.017700239065255086, \"validation_returns_over_hodl\": 0.01648999982548749, \"validation_sharpe\": 0.1622617039238211}, {\"centeredness_margin\": 0.015723329475678893, \"continuous_test_metrics\": [{\"annualised_returns\": -0.8915899671112463, \"annualised_returns_over_hodl\": 0.08208113661476357, \"annualised_returns_over_uniform_hodl\": -0.04771016274170814, \"calmar\": -1.2519282464208037, \"daily_log_sharpe\": -2.2943527031394435, \"daily_returns\": 0.007841752012942267, \"fee_revenue_over_value\": 0.01121328580160979, \"jax_sharpe\": -0.7801459594339297, \"return\": -0.5913175567695717, \"returns_over_hodl\": 0.03228051035965884, \"returns_over_uniform_hodl\": -0.019495630197734037, \"sharpe\": -1.8515223263952447, \"sterling\": -2.0646063277420654, \"ulcer\": -0.1810732946428275}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": 0.48062804025397804, \"optuna_trial_number\": 100, \"price_ratio\": 1.2744841026099998, \"shift_exponent\": 0.0018125375488733523, \"step\": 100, \"test_objective\": [{\"annualised_returns\": -0.8915899671112463, \"annualised_returns_over_hodl\": 0.08208113661476357, \"annualised_returns_over_uniform_hodl\": -0.04771016274170814, \"calmar\": -1.2519282464208037, \"daily_log_sharpe\": -2.2943527031394435, \"daily_returns\": 0.007841752012942267, \"fee_revenue_over_value\": 0.01121328580160979, \"jax_sharpe\": -0.7801459594339297, \"return\": -0.5913175567695717, \"returns_over_hodl\": 0.03228051035965884, \"returns_over_uniform_hodl\": -0.019495630197734037, \"sharpe\": -1.8515223263952447, \"sterling\": -2.0646063277420654, \"ulcer\": -0.1810732946428275}], \"train_objective\": [{\"annualised_returns\": 1.196112635299989, \"annualised_returns_over_hodl\": 0.8161538503404835, \"annualised_returns_over_uniform_hodl\": 0.8161538503404839, \"calmar\": 2.017074266751919, \"daily_log_sharpe\": 0.9388268821925396, \"daily_returns\": 0.021299550469307014, \"fee_revenue_over_value\": 0.2842562715130182, \"jax_sharpe\": 1.3363069034846138, \"return\": 0.6122256280340712, \"returns_over_hodl\": 0.4366041186026808, \"returns_over_uniform_hodl\": 0.436604118602681, \"sharpe\": 1.3710045646817344, \"sterling\": 4.754854636682199, \"ulcer\": -0.12124395199247237}], \"train_return\": 0.6122256280340712, \"train_returns_over_hodl\": 0.4366041186026808, \"train_sharpe\": 1.3363069034846138, \"validation_return\": -0.06890672942266962, \"validation_returns_over_hodl\": 0.00107160120808647, \"validation_sharpe\": -0.2784494577155494}, {\"centeredness_margin\": 0.021695368994933982, \"continuous_test_metrics\": [{\"annualised_returns\": -0.7517592664178437, \"annualised_returns_over_hodl\": 1.4713810903868798, \"annualised_returns_over_uniform_hodl\": 1.180583489227538, \"calmar\": -1.129365395445251, \"daily_log_sharpe\": -1.624274095507041, \"daily_returns\": 0.009760243358419074, \"fee_revenue_over_value\": 0.21751708767213052, \"jax_sharpe\": -0.4443795047117982, \"return\": -0.4294516054868768, \"returns_over_hodl\": 0.4396326489901863, \"returns_over_uniform_hodl\": 0.3688505666693458, \"sharpe\": -1.1995711128689837, \"sterling\": -1.934652662096515, \"ulcer\": -0.15622223838071625}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": 0.9345639129634664, \"optuna_trial_number\": 101, \"price_ratio\": 1.119699367652366, \"shift_exponent\": 0.018300321732517954, \"step\": 101, \"test_objective\": [{\"annualised_returns\": -0.7517592664178437, \"annualised_returns_over_hodl\": 1.4713810903868798, \"annualised_returns_over_uniform_hodl\": 1.180583489227538, \"calmar\": -1.129365395445251, \"daily_log_sharpe\": -1.624274095507041, \"daily_returns\": 0.009760243358419074, \"fee_revenue_over_value\": 0.21751708767213052, \"jax_sharpe\": -0.4443795047117982, \"return\": -0.4294516054868768, \"returns_over_hodl\": 0.4396326489901863, \"returns_over_uniform_hodl\": 0.3688505666693458, \"sharpe\": -1.1995711128689837, \"sterling\": -1.934652662096515, \"ulcer\": -0.15622223838071625}], \"train_objective\": [{\"annualised_returns\": 1.2702768476635131, \"annualised_returns_over_hodl\": 0.8774865969748962, \"annualised_returns_over_uniform_hodl\": 0.8774865969748957, \"calmar\": 2.1489794180822614, \"daily_log_sharpe\": 0.933490088935573, \"daily_returns\": 0.02207947426945642, \"fee_revenue_over_value\": 0.46788011633861626, \"jax_sharpe\": 1.3670498410387082, \"return\": 0.6450650152066151, \"returns_over_hodl\": 0.46586627524138446, \"returns_over_uniform_hodl\": 0.46586627524138424, \"sharpe\": 1.3777447956230382, \"sterling\": 4.959399698215886, \"ulcer\": -0.12405890749650506}], \"train_return\": 0.6450650152066151, \"train_returns_over_hodl\": 0.46586627524138446, \"train_sharpe\": 1.3670498410387082, \"validation_return\": 0.01712328802656926, \"validation_returns_over_hodl\": 0.08282616540997201, \"validation_sharpe\": 0.5041396010762645}, {\"centeredness_margin\": 0.010475922925839685, \"continuous_test_metrics\": [{\"annualised_returns\": -0.8995912984283367, \"annualised_returns_over_hodl\": 0.00221685213208489, \"annualised_returns_over_uniform_hodl\": -0.11799504592794163, \"calmar\": -1.2631634172696495, \"daily_log_sharpe\": -2.373537881025797, \"daily_returns\": 0.007841752010793483, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": -0.8300085077582041, \"return\": -0.6037442596833699, \"returns_over_hodl\": 0.0008922198401155601, \"returns_over_uniform_hodl\": -0.04930957672526748, \"sharpe\": -1.9321437044317877, \"sterling\": -2.0830910271407705, \"ulcer\": -0.1810804687943533}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": 0.915567091219203, \"optuna_trial_number\": 102, \"price_ratio\": 1.4660224889939188, \"shift_exponent\": 0.0004020609637782151, \"step\": 102, \"test_objective\": [{\"annualised_returns\": -0.8995912984283367, \"annualised_returns_over_hodl\": 0.00221685213208489, \"annualised_returns_over_uniform_hodl\": -0.11799504592794163, \"calmar\": -1.2631634172696495, \"daily_log_sharpe\": -2.373537881025797, \"daily_returns\": 0.007841752010793483, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": -0.8300085077582041, \"return\": -0.6037442596833699, \"returns_over_hodl\": 0.0008922198401155601, \"returns_over_uniform_hodl\": -0.04930957672526748, \"sharpe\": -1.9321437044317877, \"sterling\": -2.0830910271407705, \"ulcer\": -0.1810804687943533}], \"train_objective\": [{\"annualised_returns\": 1.015189238096934, \"annualised_returns_over_hodl\": 0.6665327793783729, \"annualised_returns_over_uniform_hodl\": 0.6665327793783724, \"calmar\": 1.6642972810163135, \"daily_log_sharpe\": 0.8325595560018597, \"daily_returns\": 0.02095991172960185, \"fee_revenue_over_value\": 0.255245575072028, \"jax_sharpe\": 1.2400670961443123, \"return\": 0.5302297863206709, \"returns_over_hodl\": 0.3635401740371782, \"returns_over_uniform_hodl\": 0.363540174037178, \"sharpe\": 1.2610391390941882, \"sterling\": 3.9869231648193266, \"ulcer\": -0.12510205209505013}], \"train_return\": 0.5302297863206709, \"train_returns_over_hodl\": 0.3635401740371782, \"train_sharpe\": 1.2400670961443123, \"validation_return\": -0.061590364861781044, \"validation_returns_over_hodl\": 0.007588880009304244, \"validation_sharpe\": -0.21401257408805235}, {\"centeredness_margin\": 0.02095896952195995, \"continuous_test_metrics\": [{\"annualised_returns\": -0.8545573954201917, \"annualised_returns_over_hodl\": 0.451717102784541, \"annualised_returns_over_uniform_hodl\": 0.2775894495655655, \"calmar\": -1.1999285414217344, \"daily_log_sharpe\": -2.0612800314257265, \"daily_returns\": 0.00784175201170116, \"fee_revenue_over_value\": 0.06988216086448976, \"jax_sharpe\": -0.5953611676813573, \"return\": -0.5399720120004303, \"returns_over_hodl\": 0.16197290604536296, \"returns_over_uniform_hodl\": 0.10369177814325758, \"sharpe\": -1.6197062840005945, \"sterling\": -2.0008847772219696, \"ulcer\": -0.17700447340560305}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": 0.9237598400268123, \"optuna_trial_number\": 103, \"price_ratio\": 1.3345807867022086, \"shift_exponent\": 0.0031363462559582268, \"step\": 103, \"test_objective\": [{\"annualised_returns\": -0.8545573954201917, \"annualised_returns_over_hodl\": 0.451717102784541, \"annualised_returns_over_uniform_hodl\": 0.2775894495655655, \"calmar\": -1.1999285414217344, \"daily_log_sharpe\": -2.0612800314257265, \"daily_returns\": 0.00784175201170116, \"fee_revenue_over_value\": 0.06988216086448976, \"jax_sharpe\": -0.5953611676813573, \"return\": -0.5399720120004303, \"returns_over_hodl\": 0.16197290604536296, \"returns_over_uniform_hodl\": 0.10369177814325758, \"sharpe\": -1.6197062840005945, \"sterling\": -2.0008847772219696, \"ulcer\": -0.17700447340560305}], \"train_objective\": [{\"annualised_returns\": 1.0771453178376644, \"annualised_returns_over_hodl\": 0.7177695743243477, \"annualised_returns_over_uniform_hodl\": 0.7177695743243477, \"calmar\": 1.7903984228224787, \"daily_log_sharpe\": 0.8763418897898799, \"daily_returns\": 0.021083128223772087, \"fee_revenue_over_value\": 0.2584084361881196, \"jax_sharpe\": 1.2731957331861083, \"return\": 0.5586224682816563, \"returns_over_hodl\": 0.3888400099497642, \"returns_over_uniform_hodl\": 0.3888400099497642, \"sharpe\": 1.3071052630719044, \"sterling\": 4.257458648438606, \"ulcer\": -0.12293317048476086}], \"train_return\": 0.5586224682816563, \"train_returns_over_hodl\": 0.3888400099497642, \"train_sharpe\": 1.2731957331861083, \"validation_return\": -0.06040656262612942, \"validation_returns_over_hodl\": 0.010210616409279005, \"validation_sharpe\": -0.2032862153109134}, {\"centeredness_margin\": 0.018858128981984433, \"continuous_test_metrics\": [{\"annualised_returns\": -0.8545584367327558, \"annualised_returns_over_hodl\": 0.4517067086945574, \"annualised_returns_over_uniform_hodl\": 0.27758030252127663, \"calmar\": -1.2245618751195353, \"daily_log_sharpe\": -2.0820929280326936, \"daily_returns\": 0.008063836299094792, \"fee_revenue_over_value\": 0.07604298979370876, \"jax_sharpe\": -0.6723250832279772, \"return\": -0.5399733384693155, \"returns_over_hodl\": 0.16196955543576808, \"returns_over_uniform_hodl\": 0.10368859570036126, \"sharpe\": -1.6480399552877802, \"sterling\": -2.0508539588394994, \"ulcer\": -0.17250096067395382}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": 0.8239918722749995, \"optuna_trial_number\": 104, \"price_ratio\": 1.7633433757612877, \"shift_exponent\": 0.0063766594846073445, \"step\": 104, \"test_objective\": [{\"annualised_returns\": -0.8545584367327558, \"annualised_returns_over_hodl\": 0.4517067086945574, \"annualised_returns_over_uniform_hodl\": 0.27758030252127663, \"calmar\": -1.2245618751195353, \"daily_log_sharpe\": -2.0820929280326936, \"daily_returns\": 0.008063836299094792, \"fee_revenue_over_value\": 0.07604298979370876, \"jax_sharpe\": -0.6723250832279772, \"return\": -0.5399733384693155, \"returns_over_hodl\": 0.16196955543576808, \"returns_over_uniform_hodl\": 0.10368859570036126, \"sharpe\": -1.6480399552877802, \"sterling\": -2.0508539588394994, \"ulcer\": -0.17250096067395382}], \"train_objective\": [{\"annualised_returns\": 0.7788166708991133, \"annualised_returns_over_hodl\": 0.47105603509355864, \"annualised_returns_over_uniform_hodl\": 0.47105603509355776, \"calmar\": 1.252188730630428, \"daily_log_sharpe\": 0.6915551686247732, \"daily_returns\": 0.02078909513522838, \"fee_revenue_over_value\": 0.19088651154718092, \"jax_sharpe\": 1.0970001338339996, \"return\": 0.41860012934177915, \"returns_over_hodl\": 0.264070458269398, \"returns_over_uniform_hodl\": 0.26407045826939757, \"sharpe\": 1.118117964311649, \"sterling\": 3.028040462111735, \"ulcer\": -0.12810717718891165}], \"train_return\": 0.41860012934177915, \"train_returns_over_hodl\": 0.264070458269398, \"train_sharpe\": 1.0970001338339996, \"validation_return\": -0.02940465900768796, \"validation_returns_over_hodl\": 0.02068675360053529, \"validation_sharpe\": 0.07281361396503115}, {\"centeredness_margin\": 0.018622480360004082, \"continuous_test_metrics\": [{\"annualised_returns\": -0.7986120705054103, \"annualised_returns_over_hodl\": 0.9991681316793206, \"annualised_returns_over_uniform_hodl\": 0.7690214963866326, \"calmar\": -1.123277546129604, \"daily_log_sharpe\": -1.8198831807784017, \"daily_returns\": 0.009211179025721732, \"fee_revenue_over_value\": 0.16356967121441313, \"jax_sharpe\": -0.4094556626511077, \"return\": -0.47554534902837775, \"returns_over_hodl\": 0.32179285635493704, \"returns_over_uniform_hodl\": 0.2582631956882431, \"sharpe\": -1.3945065343824996, \"sterling\": -1.9604159400360843, \"ulcer\": -0.1620569199181671}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": 0.9203916424490899, \"optuna_trial_number\": 105, \"price_ratio\": 1.2175734727905632, \"shift_exponent\": 0.023539592003022115, \"step\": 105, \"test_objective\": [{\"annualised_returns\": -0.7986120705054103, \"annualised_returns_over_hodl\": 0.9991681316793206, \"annualised_returns_over_uniform_hodl\": 0.7690214963866326, \"calmar\": -1.123277546129604, \"daily_log_sharpe\": -1.8198831807784017, \"daily_returns\": 0.009211179025721732, \"fee_revenue_over_value\": 0.16356967121441313, \"jax_sharpe\": -0.4094556626511077, \"return\": -0.47554534902837775, \"returns_over_hodl\": 0.32179285635493704, \"returns_over_uniform_hodl\": 0.2582631956882431, \"sharpe\": -1.3945065343824996, \"sterling\": -1.9604159400360843, \"ulcer\": -0.1620569199181671}], \"train_objective\": [{\"annualised_returns\": 1.1857800110437484, \"annualised_returns_over_hodl\": 0.8076089173413945, \"annualised_returns_over_uniform_hodl\": 0.8076089173413941, \"calmar\": 1.9729453756620006, \"daily_log_sharpe\": 0.8972901673013913, \"daily_returns\": 0.021460855909401902, \"fee_revenue_over_value\": 0.37259719197353847, \"jax_sharpe\": 1.3243312500809035, \"return\": 0.6076160681415461, \"returns_over_hodl\": 0.4324966831349655, \"returns_over_uniform_hodl\": 0.43249668313496525, \"sharpe\": 1.340366046201841, \"sterling\": 4.626345958644165, \"ulcer\": -0.12382742837856735}], \"train_return\": 0.6076160681415461, \"train_returns_over_hodl\": 0.4324966831349655, \"train_sharpe\": 1.3243312500809035, \"validation_return\": -0.0039004767741689372, \"validation_returns_over_hodl\": 0.05789557165824877, \"validation_sharpe\": 0.30732343856588656}, {\"centeredness_margin\": 0.10957384830015324, \"continuous_test_metrics\": [{\"annualised_returns\": -0.7266008597297602, \"annualised_returns_over_hodl\": 1.6546114995148642, \"annualised_returns_over_uniform_hodl\": 1.4015786717975653, \"calmar\": -1.0244703252101073, \"daily_log_sharpe\": -1.5772374834669223, \"daily_returns\": 0.010052553227054437, \"fee_revenue_over_value\": 0.27372711123101867, \"jax_sharpe\": -0.22632778803458486, \"return\": -0.40683310084108826, \"returns_over_hodl\": 0.4817032325245112, \"returns_over_uniform_hodl\": 0.42311652061707616, \"sharpe\": -1.1668279812783426, \"sterling\": -1.8765162387189056, \"ulcer\": -0.1466006279371494}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": 0.9633792456030256, \"optuna_trial_number\": 106, \"price_ratio\": 1.065506099667731, \"shift_exponent\": 0.023985041266505414, \"step\": 106, \"test_objective\": [{\"annualised_returns\": -0.7266008597297602, \"annualised_returns_over_hodl\": 1.6546114995148642, \"annualised_returns_over_uniform_hodl\": 1.4015786717975653, \"calmar\": -1.0244703252101073, \"daily_log_sharpe\": -1.5772374834669223, \"daily_returns\": 0.010052553227054437, \"fee_revenue_over_value\": 0.27372711123101867, \"jax_sharpe\": -0.22632778803458486, \"return\": -0.40683310084108826, \"returns_over_hodl\": 0.4817032325245112, \"returns_over_uniform_hodl\": 0.42311652061707616, \"sharpe\": -1.1668279812783426, \"sterling\": -1.8765162387189056, \"ulcer\": -0.1466006279371494}], \"train_objective\": [{\"annualised_returns\": 1.0865896544694542, \"annualised_returns_over_hodl\": 0.7255799061179131, \"annualised_returns_over_uniform_hodl\": 0.725579906117914, \"calmar\": 1.7495731098219867, \"daily_log_sharpe\": 0.8363720789821915, \"daily_returns\": 0.022313048223315864, \"fee_revenue_over_value\": 0.5440219478619631, \"jax_sharpe\": 1.2698793964584436, \"return\": 0.5629211351189491, \"returns_over_hodl\": 0.39267041828441207, \"returns_over_uniform_hodl\": 0.3926704182844125, \"sharpe\": 1.2772342482347019, \"sterling\": 4.153424697340195, \"ulcer\": -0.1297997457731357}], \"train_return\": 0.5629211351189491, \"train_returns_over_hodl\": 0.39267041828441207, \"train_sharpe\": 1.2698793964584436, \"validation_return\": 0.06076632104618063, \"validation_returns_over_hodl\": 0.10883559727859105, \"validation_sharpe\": 0.9228499315026769}, {\"centeredness_margin\": 0.1121367129151915, \"continuous_test_metrics\": [{\"annualised_returns\": -0.7417241180750316, \"annualised_returns_over_hodl\": 1.4858615323690785, \"annualised_returns_over_uniform_hodl\": 1.2687337233672666, \"calmar\": -1.0468714297272124, \"daily_log_sharpe\": -1.6812144553925126, \"daily_returns\": 0.009940801557407805, \"fee_revenue_over_value\": 0.2577776597854335, \"jax_sharpe\": -0.27963158338275984, \"return\": -0.42027246184816425, \"returns_over_hodl\": 0.4430238848499033, \"returns_over_uniform_hodl\": 0.39087302101717203, \"sharpe\": -1.282795444574742, \"sterling\": -1.9332272169135, \"ulcer\": -0.14686864150570442}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": 0.9807610942617657, \"optuna_trial_number\": 107, \"price_ratio\": 1.0666226674493209, \"shift_exponent\": 0.12293812565152992, \"step\": 107, \"test_objective\": [{\"annualised_returns\": -0.7417241180750316, \"annualised_returns_over_hodl\": 1.4858615323690785, \"annualised_returns_over_uniform_hodl\": 1.2687337233672666, \"calmar\": -1.0468714297272124, \"daily_log_sharpe\": -1.6812144553925126, \"daily_returns\": 0.009940801557407805, \"fee_revenue_over_value\": 0.2577776597854335, \"jax_sharpe\": -0.27963158338275984, \"return\": -0.42027246184816425, \"returns_over_hodl\": 0.4430238848499033, \"returns_over_uniform_hodl\": 0.39087302101717203, \"sharpe\": -1.282795444574742, \"sterling\": -1.9332272169135, \"ulcer\": -0.14686864150570442}], \"train_objective\": [{\"annualised_returns\": 0.8640205190067205, \"annualised_returns_over_hodl\": 0.5415184031511584, \"annualised_returns_over_uniform_hodl\": 0.5415184031511597, \"calmar\": 1.2709836731465558, \"daily_log_sharpe\": 0.7201011230479694, \"daily_returns\": 0.022434543469963292, \"fee_revenue_over_value\": 0.602900346617336, \"jax_sharpe\": 1.1459676680441333, \"return\": 0.4594741147388719, \"returns_over_hodl\": 0.30049199551835626, \"returns_over_uniform_hodl\": 0.3004919955183569, \"sharpe\": 1.1542464984004697, \"sterling\": 3.229243154177214, \"ulcer\": -0.1387586811856247}], \"train_return\": 0.4594741147388719, \"train_returns_over_hodl\": 0.30049199551835626, \"train_sharpe\": 1.145967668044133, \"validation_return\": 0.05452959723180628, \"validation_returns_over_hodl\": 0.09042778158989462, \"validation_sharpe\": 0.8640726077634779}, {\"centeredness_margin\": 0.03944757606525074, \"continuous_test_metrics\": [{\"annualised_returns\": -0.8555157715637782, \"annualised_returns_over_hodl\": 0.4185228760876891, \"annualised_returns_over_uniform_hodl\": 0.26917093111769597, \"calmar\": -1.20354781560001, \"daily_log_sharpe\": -2.135292243560746, \"daily_returns\": 0.008412122151847532, \"fee_revenue_over_value\": 0.0806387382813662, \"jax_sharpe\": -0.627308765603854, \"return\": -0.5411952410653276, \"returns_over_hodl\": 0.15119856261892162, \"returns_over_uniform_hodl\": 0.10075702656959007, \"sharpe\": -1.7113598315831335, \"sterling\": -2.05523269104644, \"ulcer\": -0.16966322201302067}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": 0.7822954543794909, \"optuna_trial_number\": 108, \"price_ratio\": 1.9867181641193634, \"shift_exponent\": 0.04273750570650403, \"step\": 108, \"test_objective\": [{\"annualised_returns\": -0.8555157715637782, \"annualised_returns_over_hodl\": 0.4185228760876891, \"annualised_returns_over_uniform_hodl\": 0.26917093111769597, \"calmar\": -1.20354781560001, \"daily_log_sharpe\": -2.135292243560746, \"daily_returns\": 0.008412122151847532, \"fee_revenue_over_value\": 0.0806387382813662, \"jax_sharpe\": -0.627308765603854, \"return\": -0.5411952410653276, \"returns_over_hodl\": 0.15119856261892162, \"returns_over_uniform_hodl\": 0.10075702656959007, \"sharpe\": -1.7113598315831335, \"sterling\": -2.05523269104644, \"ulcer\": -0.16966322201302067}], \"train_objective\": [{\"annualised_returns\": 0.6888188328290845, \"annualised_returns_over_hodl\": 0.39662910566109955, \"annualised_returns_over_uniform_hodl\": 0.39662910566109955, \"calmar\": 1.10089147456946, \"daily_log_sharpe\": 0.632352910862593, \"daily_returns\": 0.020717305409464076, \"fee_revenue_over_value\": 0.16235892478834388, \"jax_sharpe\": 1.037353662846968, \"return\": 0.37458165839949653, \"returns_over_hodl\": 0.2248469677413465, \"returns_over_uniform_hodl\": 0.2248469677413465, \"sharpe\": 1.0583584070367555, \"sterling\": 2.668990446799516, \"ulcer\": -0.12916699212198587}], \"train_return\": 0.37458165839949653, \"train_returns_over_hodl\": 0.2248469677413465, \"train_sharpe\": 1.037353662846968, \"validation_return\": -0.022396141157257188, \"validation_returns_over_hodl\": 0.01972148193766987, \"validation_sharpe\": 0.12761599775058827}, {\"centeredness_margin\": 0.05068021918972483, \"continuous_test_metrics\": [{\"annualised_returns\": -0.7435914485142812, \"annualised_returns_over_hodl\": 1.5105608731064373, \"annualised_returns_over_uniform_hodl\": 1.2523308153271473, \"calmar\": -1.0474267956350658, \"daily_log_sharpe\": -1.6512645958698202, \"daily_returns\": 0.009905910324528684, \"fee_revenue_over_value\": 0.2359255325059036, \"jax_sharpe\": -0.2384024839481809, \"return\": -0.42196416424647, \"returns_over_hodl\": 0.44878120811348277, \"returns_over_uniform_hodl\": 0.38681431572796887, \"sharpe\": -1.24300518827113, \"sterling\": -1.9104764651677582, \"ulcer\": -0.15018225374872404}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": 1.03122343864465, \"optuna_trial_number\": 109, \"price_ratio\": 1.091477005042227, \"shift_exponent\": 0.08258459727191572, \"step\": 109, \"test_objective\": [{\"annualised_returns\": -0.7435914485142812, \"annualised_returns_over_hodl\": 1.5105608731064373, \"annualised_returns_over_uniform_hodl\": 1.2523308153271473, \"calmar\": -1.0474267956350658, \"daily_log_sharpe\": -1.6512645958698202, \"daily_returns\": 0.009905910324528684, \"fee_revenue_over_value\": 0.2359255325059036, \"jax_sharpe\": -0.2384024839481809, \"return\": -0.42196416424647, \"returns_over_hodl\": 0.44878120811348277, \"returns_over_uniform_hodl\": 0.38681431572796887, \"sharpe\": -1.24300518827113, \"sterling\": -1.9104764651677582, \"ulcer\": -0.15018225374872404}], \"train_objective\": [{\"annualised_returns\": 1.07465579942621, \"annualised_returns_over_hodl\": 0.7157107780787535, \"annualised_returns_over_uniform_hodl\": 0.7157107780787535, \"calmar\": 1.6611140616789561, \"daily_log_sharpe\": 0.8473635265560577, \"daily_returns\": 0.022427876500322564, \"fee_revenue_over_value\": 0.5625547715819593, \"jax_sharpe\": 1.266746516274946, \"return\": 0.5574880656665662, \"returns_over_hodl\": 0.38782917905819736, \"returns_over_uniform_hodl\": 0.38782917905819736, \"sharpe\": 1.2801966823434712, \"sterling\": 4.112060434090974, \"ulcer\": -0.13149232030535224}], \"train_return\": 0.5574880656665662, \"train_returns_over_hodl\": 0.38782917905819736, \"train_sharpe\": 1.2667465162749463, \"validation_return\": 0.04016816014760227, \"validation_returns_over_hodl\": 0.08643195351034372, \"validation_sharpe\": 0.7233663482120035}, {\"centeredness_margin\": 0.17983881628726606, \"continuous_test_metrics\": [{\"annualised_returns\": -0.8718760931699223, \"annualised_returns_over_hodl\": 0.14481497737409166, \"annualised_returns_over_uniform_hodl\": 0.1254594351918923, \"calmar\": -1.3324744158589712, \"daily_log_sharpe\": -2.502961232539424, \"daily_returns\": 0.0068372079165635, \"fee_revenue_over_value\": 0.028419173893844754, \"jax_sharpe\": -1.5721905067327386, \"return\": -0.5628717058203252, \"returns_over_hodl\": 0.055978140090348294, \"returns_over_uniform_hodl\": 0.04875119963428287, \"sharpe\": -2.1202926958872874, \"sterling\": -2.423972367953119, \"ulcer\": -0.1588839829167359}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": 0.5877666138017219, \"optuna_trial_number\": 110, \"price_ratio\": 29.184558624678623, \"shift_exponent\": 0.9145995184960993, \"step\": 110, \"test_objective\": [{\"annualised_returns\": -0.8718760931699223, \"annualised_returns_over_hodl\": 0.14481497737409166, \"annualised_returns_over_uniform_hodl\": 0.1254594351918923, \"calmar\": -1.3324744158589712, \"daily_log_sharpe\": -2.502961232539424, \"daily_returns\": 0.0068372079165635, \"fee_revenue_over_value\": 0.028419173893844754, \"jax_sharpe\": -1.5721905067327386, \"return\": -0.5628717058203252, \"returns_over_hodl\": 0.055978140090348294, \"returns_over_uniform_hodl\": 0.04875119963428287, \"sharpe\": -2.1202926958872874, \"sterling\": -2.423972367953119, \"ulcer\": -0.1588839829167359}], \"train_objective\": [{\"annualised_returns\": 0.3357777330147369, \"annualised_returns_over_hodl\": 0.10466914766528301, \"annualised_returns_over_uniform_hodl\": 0.10466914766528346, \"calmar\": 0.5229653492535221, \"daily_log_sharpe\": 0.3632237956537943, \"daily_returns\": 0.02043818127128681, \"fee_revenue_over_value\": 0.047499233987145784, \"jax_sharpe\": 0.7687546486814475, \"return\": 0.19216377550398533, \"returns_over_hodl\": 0.06230006529939214, \"returns_over_uniform_hodl\": 0.06230006529939236, \"sharpe\": 0.7877786521823866, \"sterling\": 1.2816725622651088, \"ulcer\": -0.1338595539514677}], \"train_return\": 0.19216377550398533, \"train_returns_over_hodl\": 0.06230006529939214, \"train_sharpe\": 0.7687546486814475, \"validation_return\": -0.00240573193324356, \"validation_returns_over_hodl\": 0.005790235627927132, \"validation_sharpe\": 0.29045942627472443}, {\"centeredness_margin\": 0.03591483413235894, \"continuous_test_metrics\": [{\"annualised_returns\": -0.6965366047776138, \"annualised_returns_over_hodl\": 1.9746996379748452, \"annualised_returns_over_uniform_hodl\": 1.665667554466296, \"calmar\": -1.159241044178638, \"daily_log_sharpe\": -1.5041444923934564, \"daily_returns\": 0.010951966069145901, \"fee_revenue_over_value\": 0.33518932972834475, \"jax_sharpe\": -0.5258108298142368, \"return\": -0.381379050078653, \"returns_over_hodl\": 0.551220201500392, \"returns_over_uniform_hodl\": 0.48418547137614865, \"sharpe\": -1.1042028290609605, \"sterling\": -1.9645894100203718, \"ulcer\": -0.14445309319052518}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": 0.9822404175567545, \"optuna_trial_number\": 111, \"price_ratio\": 1.0155514747788428, \"shift_exponent\": 0.7436871426418874, \"step\": 111, \"test_objective\": [{\"annualised_returns\": -0.6965366047776138, \"annualised_returns_over_hodl\": 1.9746996379748452, \"annualised_returns_over_uniform_hodl\": 1.665667554466296, \"calmar\": -1.159241044178638, \"daily_log_sharpe\": -1.5041444923934564, \"daily_returns\": 0.010951966069145901, \"fee_revenue_over_value\": 0.33518932972834475, \"jax_sharpe\": -0.5258108298142368, \"return\": -0.381379050078653, \"returns_over_hodl\": 0.551220201500392, \"returns_over_uniform_hodl\": 0.48418547137614865, \"sharpe\": -1.1042028290609605, \"sterling\": -1.9645894100203718, \"ulcer\": -0.14445309319052518}], \"train_objective\": [{\"annualised_returns\": 0.19706355395011044, \"annualised_returns_over_hodl\": -0.010045501463183948, \"annualised_returns_over_uniform_hodl\": -0.010045501463188167, \"calmar\": 0.2587221136862817, \"daily_log_sharpe\": 0.19895791332353688, \"daily_returns\": 0.013648804631536194, \"fee_revenue_over_value\": 0.763280041227821, \"jax_sharpe\": 0.648859752518092, \"return\": 0.11538969497771356, \"returns_over_hodl\": -0.006110930263594216, \"returns_over_uniform_hodl\": -0.00611093026359677, \"sharpe\": 0.6413749784350455, \"sterling\": 0.6663808371656694, \"ulcer\": -0.1613958391456782}], \"train_return\": 0.11538969497771356, \"train_returns_over_hodl\": -0.006110930263594216, \"train_sharpe\": 0.648859752518092, \"validation_return\": 0.03811606535210821, \"validation_returns_over_hodl\": 0.07733112991696345, \"validation_sharpe\": 0.7038455703249781}, {\"centeredness_margin\": 0.08071274546162745, \"continuous_test_metrics\": [{\"annualised_returns\": -0.8089343464202084, \"annualised_returns_over_hodl\": 0.8583405888286557, \"annualised_returns_over_uniform_hodl\": 0.6783490909910428, \"calmar\": -1.1395273871002431, \"daily_log_sharpe\": -1.9332000145615276, \"daily_returns\": 0.008949103272184886, \"fee_revenue_over_value\": 0.14982064458093827, \"jax_sharpe\": -0.4653870292921635, \"return\": -0.4865418465253064, \"returns_over_hodl\": 0.2834736077341371, \"returns_over_uniform_hodl\": 0.2318805750818107, \"sharpe\": -1.522153705832703, \"sterling\": -2.013543855349277, \"ulcer\": -0.1597695207617035}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": 0.8832208287465658, \"optuna_trial_number\": 112, \"price_ratio\": 1.2742063892330435, \"shift_exponent\": 0.07452605248593822, \"step\": 112, \"test_objective\": [{\"annualised_returns\": -0.8089343464202084, \"annualised_returns_over_hodl\": 0.8583405888286557, \"annualised_returns_over_uniform_hodl\": 0.6783490909910428, \"calmar\": -1.1395273871002431, \"daily_log_sharpe\": -1.9332000145615276, \"daily_returns\": 0.008949103272184886, \"fee_revenue_over_value\": 0.14982064458093827, \"jax_sharpe\": -0.4653870292921635, \"return\": -0.4865418465253064, \"returns_over_hodl\": 0.2834736077341371, \"returns_over_uniform_hodl\": 0.2318805750818107, \"sharpe\": -1.522153705832703, \"sterling\": -2.013543855349277, \"ulcer\": -0.1597695207617035}], \"train_objective\": [{\"annualised_returns\": 0.9775355710413789, \"annualised_returns_over_hodl\": 0.6353937333644402, \"annualised_returns_over_uniform_hodl\": 0.6353937333644402, \"calmar\": 1.5818111027776973, \"daily_log_sharpe\": 0.79258142561755, \"daily_returns\": 0.021299590189675307, \"fee_revenue_over_value\": 0.3386117607150791, \"jax_sharpe\": 1.2134858409492355, \"return\": 0.5128065551524206, \"returns_over_hodl\": 0.3480148745874996, \"returns_over_uniform_hodl\": 0.3480148745874996, \"sharpe\": 1.2304374411670178, \"sterling\": 3.7911727452575867, \"ulcer\": -0.12650226901576025}], \"train_return\": 0.5128065551524206, \"train_returns_over_hodl\": 0.3480148745874996, \"train_sharpe\": 1.2134858409492353, \"validation_return\": 0.001004084956231166, \"validation_returns_over_hodl\": 0.051033286853283455, \"validation_sharpe\": 0.3476226689138839}, {\"centeredness_margin\": 0.07058610330628663, \"continuous_test_metrics\": [{\"annualised_returns\": -0.7761862564111635, \"annualised_returns_over_hodl\": 1.1816049973595022, \"annualised_returns_over_uniform_hodl\": 0.9660131795836089, \"calmar\": -1.0933103731940985, \"daily_log_sharpe\": -1.7743649773153434, \"daily_returns\": 0.009431013860008192, \"fee_revenue_over_value\": 0.19218698146711552, \"jax_sharpe\": -0.3600859842028352, \"return\": -0.45276381664281373, \"returns_over_hodl\": 0.36910878485341736, \"returns_over_uniform_hodl\": 0.3129202831771787, \"sharpe\": -1.3620290496647955, \"sterling\": -1.954896846402866, \"ulcer\": -0.15567438245251194}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": 0.9123778183844533, \"optuna_trial_number\": 113, \"price_ratio\": 1.1588832784699188, \"shift_exponent\": 0.06724224473066362, \"step\": 113, \"test_objective\": [{\"annualised_returns\": -0.7761862564111635, \"annualised_returns_over_hodl\": 1.1816049973595022, \"annualised_returns_over_uniform_hodl\": 0.9660131795836089, \"calmar\": -1.0933103731940985, \"daily_log_sharpe\": -1.7743649773153434, \"daily_returns\": 0.009431013860008192, \"fee_revenue_over_value\": 0.19218698146711552, \"jax_sharpe\": -0.3600859842028352, \"return\": -0.45276381664281373, \"returns_over_hodl\": 0.36910878485341736, \"returns_over_uniform_hodl\": 0.3129202831771787, \"sharpe\": -1.3620290496647955, \"sterling\": -1.954896846402866, \"ulcer\": -0.15567438245251194}], \"train_objective\": [{\"annualised_returns\": 0.9717029922726688, \"annualised_returns_over_hodl\": 0.630570273848778, \"annualised_returns_over_uniform_hodl\": 0.6305702738487766, \"calmar\": 1.5349336605747763, \"daily_log_sharpe\": 0.7853365913103242, \"daily_returns\": 0.021779835800798033, \"fee_revenue_over_value\": 0.43743275878197174, \"jax_sharpe\": 1.209035032799965, \"return\": 0.5100960693851635, \"returns_over_hodl\": 0.345599645013583, \"returns_over_uniform_hodl\": 0.3455996450135823, \"sharpe\": 1.2229305056680355, \"sterling\": 3.750047787921814, \"ulcer\": -0.1291795221104969}], \"train_return\": 0.5100960693851635, \"train_returns_over_hodl\": 0.345599645013583, \"train_sharpe\": 1.209035032799965, \"validation_return\": 0.019440833680561598, \"validation_returns_over_hodl\": 0.06842362963741122, \"validation_sharpe\": 0.5243643701743025}, {\"centeredness_margin\": 0.023288488730437137, \"continuous_test_metrics\": [{\"annualised_returns\": -0.7035755454580037, \"annualised_returns_over_hodl\": 1.9135185988836407, \"annualised_returns_over_uniform_hodl\": 1.6038364536319496, \"calmar\": -0.9916821337009213, \"daily_log_sharpe\": -1.514446967473784, \"daily_returns\": 0.010742864782542683, \"fee_revenue_over_value\": 0.30987409687483947, \"jax_sharpe\": -0.1258137328445614, \"return\": -0.38719851986186027, \"returns_over_hodl\": 0.5382913992127203, \"returns_over_uniform_hodl\": 0.4702234927130482, \"sharpe\": -1.1106718253829164, \"sterling\": -1.8400744952296828, \"ulcer\": -0.14450567264847727}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": 1.0049638128996428, \"optuna_trial_number\": 114, \"price_ratio\": 1.0333255026012722, \"shift_exponent\": 0.14636345537165296, \"step\": 114, \"test_objective\": [{\"annualised_returns\": -0.7035755454580037, \"annualised_returns_over_hodl\": 1.9135185988836407, \"annualised_returns_over_uniform_hodl\": 1.6038364536319496, \"calmar\": -0.9916821337009213, \"daily_log_sharpe\": -1.514446967473784, \"daily_returns\": 0.010742864782542683, \"fee_revenue_over_value\": 0.30987409687483947, \"jax_sharpe\": -0.1258137328445614, \"return\": -0.38719851986186027, \"returns_over_hodl\": 0.5382913992127203, \"returns_over_uniform_hodl\": 0.4702234927130482, \"sharpe\": -1.1106718253829164, \"sterling\": -1.8400744952296828, \"ulcer\": -0.14450567264847727}], \"train_objective\": [{\"annualised_returns\": 0.8206165200942697, \"annualised_returns_over_hodl\": 0.505623914645452, \"annualised_returns_over_uniform_hodl\": 0.5056239146454482, \"calmar\": 1.1698976866445312, \"daily_log_sharpe\": 0.6814898949188148, \"daily_returns\": 0.023300441986367427, \"fee_revenue_over_value\": 0.7584171587655798, \"jax_sharpe\": 1.1173960206238749, \"return\": 0.4387462460655691, \"returns_over_hodl\": 0.2820220363587136, \"returns_over_uniform_hodl\": 0.2820220363587116, \"sharpe\": 1.121122436175744, \"sterling\": 2.966542902786318, \"ulcer\": -0.1451038334680179}], \"train_return\": 0.4387462460655691, \"train_returns_over_hodl\": 0.2820220363587136, \"train_sharpe\": 1.1173960206238749, \"validation_return\": 0.06911619270347136, \"validation_returns_over_hodl\": 0.11070686557214104, \"validation_sharpe\": 1.0009473502858022}, {\"centeredness_margin\": 0.014780940582999145, \"continuous_test_metrics\": [{\"annualised_returns\": -0.8105765351847124, \"annualised_returns_over_hodl\": 0.8737284353420209, \"annualised_returns_over_uniform_hodl\": 0.6639238608751039, \"calmar\": -1.1401887941929252, \"daily_log_sharpe\": -1.9116001134677467, \"daily_returns\": 0.009142161415266596, \"fee_revenue_over_value\": 0.14798339589500697, \"jax_sharpe\": -0.4618073744379928, \"return\": -0.48832375879560463, \"returns_over_hodl\": 0.28774324878627167, \"returns_over_uniform_hodl\": 0.2276054397130851, \"sharpe\": -1.4948957684732191, \"sterling\": -2.00232331092467, \"ulcer\": -0.1616099897465404}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": 0.9258692146657048, \"optuna_trial_number\": 115, \"price_ratio\": 1.2862096170070025, \"shift_exponent\": 0.14425664507208066, \"step\": 115, \"test_objective\": [{\"annualised_returns\": -0.8105765351847124, \"annualised_returns_over_hodl\": 0.8737284353420209, \"annualised_returns_over_uniform_hodl\": 0.6639238608751039, \"calmar\": -1.1401887941929252, \"daily_log_sharpe\": -1.9116001134677467, \"daily_returns\": 0.009142161415266596, \"fee_revenue_over_value\": 0.14798339589500697, \"jax_sharpe\": -0.4618073744379928, \"return\": -0.48832375879560463, \"returns_over_hodl\": 0.28774324878627167, \"returns_over_uniform_hodl\": 0.2276054397130851, \"sharpe\": -1.4948957684732191, \"sterling\": -2.00232331092467, \"ulcer\": -0.1616099897465404}], \"train_objective\": [{\"annualised_returns\": 1.0818984898437196, \"annualised_returns_over_hodl\": 0.7217003798310277, \"annualised_returns_over_uniform_hodl\": 0.7217003798310286, \"calmar\": 1.7539009385364455, \"daily_log_sharpe\": 0.8485621656195091, \"daily_returns\": 0.021206865734973622, \"fee_revenue_over_value\": 0.3471258145458539, \"jax_sharpe\": 1.2702140209503627, \"return\": 0.560786870916059, \"returns_over_hodl\": 0.39076864182661164, \"returns_over_uniform_hodl\": 0.3907686418266121, \"sharpe\": 1.2887688944353133, \"sterling\": 4.215926533659609, \"ulcer\": -0.12541183705114173}], \"train_return\": 0.560786870916059, \"train_returns_over_hodl\": 0.39076864182661164, \"train_sharpe\": 1.2702140209503627, \"validation_return\": -0.008212299766538811, \"validation_returns_over_hodl\": 0.05141424453568444, \"validation_sharpe\": 0.2664792650967592}, {\"centeredness_margin\": 0.6122990216104199, \"continuous_test_metrics\": [{\"annualised_returns\": -0.8739360838669192, \"annualised_returns_over_hodl\": 0.11804671253922061, \"annualised_returns_over_uniform_hodl\": 0.10736417082083682, \"calmar\": -1.3364298272406865, \"daily_log_sharpe\": -2.5680060620884597, \"daily_returns\": 0.006718082408997465, \"fee_revenue_over_value\": 0.024224715151880103, \"jax_sharpe\": -1.7360206088680699, \"return\": -0.5657159351936384, \"returns_over_hodl\": 0.0459638004238081, \"returns_over_uniform_hodl\": 0.04192737009267211, \"sharpe\": -2.1919663261491906, \"sterling\": -2.512133252565713, \"ulcer\": -0.15712081338998904}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": 0.5762899644060042, \"optuna_trial_number\": 116, \"price_ratio\": 78.61999398725781, \"shift_exponent\": 0.3968697090240375, \"step\": 116, \"test_objective\": [{\"annualised_returns\": -0.8739360838669192, \"annualised_returns_over_hodl\": 0.11804671253922061, \"annualised_returns_over_uniform_hodl\": 0.10736417082083682, \"calmar\": -1.3364298272406865, \"daily_log_sharpe\": -2.5680060620884597, \"daily_returns\": 0.006718082408997465, \"fee_revenue_over_value\": 0.024224715151880103, \"jax_sharpe\": -1.7360206088680699, \"return\": -0.5657159351936384, \"returns_over_hodl\": 0.0459638004238081, \"returns_over_uniform_hodl\": 0.04192737009267211, \"sharpe\": -2.1919663261491906, \"sterling\": -2.512133252565713, \"ulcer\": -0.15712081338998904}], \"train_objective\": [{\"annualised_returns\": 0.31784645000661316, \"annualised_returns_over_hodl\": 0.08984023217466208, \"annualised_returns_over_uniform_hodl\": 0.08984023217466208, \"calmar\": 0.49442992418545956, \"daily_log_sharpe\": 0.3476928891118545, \"daily_returns\": 0.020426015250422515, \"fee_revenue_over_value\": 0.04087906208856635, \"jax_sharpe\": 0.7533734710491311, \"return\": 0.18242196877463202, \"returns_over_hodl\": 0.0536194442829121, \"returns_over_uniform_hodl\": 0.0536194442829121, \"sharpe\": 0.7722045195441644, \"sterling\": 1.2121851188017123, \"ulcer\": -0.13411799878062947}], \"train_return\": 0.18242196877463202, \"train_returns_over_hodl\": 0.0536194442829121, \"train_sharpe\": 0.7533734710491311, \"validation_return\": -0.0010024336619984808, \"validation_returns_over_hodl\": 0.0051227110737246395, \"validation_sharpe\": 0.3035609319918244}, {\"centeredness_margin\": 0.026628408428241428, \"continuous_test_metrics\": [{\"annualised_returns\": -0.6812836221693612, \"annualised_returns_over_hodl\": 2.118757416423767, \"annualised_returns_over_uniform_hodl\": 1.7996520200980108, \"calmar\": -0.9612572153137396, \"daily_log_sharpe\": -1.4391976239367257, \"daily_returns\": 0.01193285428016492, \"fee_revenue_over_value\": 0.3824722099233352, \"jax_sharpe\": -0.059620674673406954, \"return\": -0.36903951549922176, \"returns_over_hodl\": 0.5810480638463058, \"returns_over_uniform_hodl\": 0.5137902850324982, \"sharpe\": -1.0368469114600691, \"sterling\": -1.7755356949081043, \"ulcer\": -0.14460933727406805}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": 1.1095049782847202, \"optuna_trial_number\": 117, \"price_ratio\": 1.012011484075616, \"shift_exponent\": 0.08827350039359076, \"step\": 117, \"test_objective\": [{\"annualised_returns\": -0.6812836221693612, \"annualised_returns_over_hodl\": 2.118757416423767, \"annualised_returns_over_uniform_hodl\": 1.7996520200980108, \"calmar\": -0.9612572153137396, \"daily_log_sharpe\": -1.4391976239367257, \"daily_returns\": 0.01193285428016492, \"fee_revenue_over_value\": 0.3824722099233352, \"jax_sharpe\": -0.059620674673406954, \"return\": -0.36903951549922176, \"returns_over_hodl\": 0.5810480638463058, \"returns_over_uniform_hodl\": 0.5137902850324982, \"sharpe\": -1.0368469114600691, \"sterling\": -1.7755356949081043, \"ulcer\": -0.14460933727406805}], \"train_objective\": [{\"annualised_returns\": 0.9229913612555147, \"annualised_returns_over_hodl\": 0.5902864492369895, \"annualised_returns_over_uniform_hodl\": 0.5902864492369828, \"calmar\": 1.308197455873218, \"daily_log_sharpe\": 0.7358915533118723, \"daily_returns\": 0.025072891839124804, \"fee_revenue_over_value\": 0.9279689004918033, \"jax_sharpe\": 1.178055744386258, \"return\": 0.4873347054847539, \"returns_over_hodl\": 0.3253177014966502, \"returns_over_uniform_hodl\": 0.32531770149664685, \"sharpe\": 1.1752973455449238, \"sterling\": 3.325269295402659, \"ulcer\": -0.14433139752920432}], \"train_return\": 0.4873347054847539, \"train_returns_over_hodl\": 0.3253177014966502, \"train_sharpe\": 1.178055744386258, \"validation_return\": 0.09508127576414394, \"validation_returns_over_hodl\": 0.12088221033612756, \"validation_sharpe\": 1.244777212183136}, {\"centeredness_margin\": 0.0679775556560621, \"continuous_test_metrics\": [{\"annualised_returns\": -0.7556429176143087, \"annualised_returns_over_hodl\": 1.3848977718609303, \"annualised_returns_over_uniform_hodl\": 1.146468920056206, \"calmar\": -1.0650001099992163, \"daily_log_sharpe\": -1.7244431715491384, \"daily_returns\": 0.009624408289983306, \"fee_revenue_over_value\": 0.21562945136427852, \"jax_sharpe\": -0.27204820449737865, \"return\": -0.43306340278138133, \"returns_over_hodl\": 0.41912729340489263, \"returns_over_uniform_hodl\": 0.3601852004693471, \"sharpe\": -1.321958962616684, \"sterling\": -1.9481280912127723, \"ulcer\": -0.1503653944384496}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": 0.9631872780366966, \"optuna_trial_number\": 118, \"price_ratio\": 1.1157714761928759, \"shift_exponent\": 0.2451506967938159, \"step\": 118, \"test_objective\": [{\"annualised_returns\": -0.7556429176143087, \"annualised_returns_over_hodl\": 1.3848977718609303, \"annualised_returns_over_uniform_hodl\": 1.146468920056206, \"calmar\": -1.0650001099992163, \"daily_log_sharpe\": -1.7244431715491384, \"daily_returns\": 0.009624408289983306, \"fee_revenue_over_value\": 0.21562945136427852, \"jax_sharpe\": -0.27204820449737865, \"return\": -0.43306340278138133, \"returns_over_hodl\": 0.41912729340489263, \"returns_over_uniform_hodl\": 0.3601852004693471, \"sharpe\": -1.321958962616684, \"sterling\": -1.9481280912127723, \"ulcer\": -0.1503653944384496}], \"train_objective\": [{\"annualised_returns\": 0.8833080584223976, \"annualised_returns_over_hodl\": 0.557468923361427, \"annualised_returns_over_uniform_hodl\": 0.5574689233614265, \"calmar\": 1.3383592698699667, \"daily_log_sharpe\": 0.7382894044475772, \"daily_returns\": 0.02213681020568353, \"fee_revenue_over_value\": 0.5054897436733692, \"jax_sharpe\": 1.158326509515274, \"return\": 0.468624062992288, \"returns_over_hodl\": 0.3086452298531117, \"returns_over_uniform_hodl\": 0.3086452298531115, \"sharpe\": 1.1703286159570543, \"sterling\": 3.359991973510659, \"ulcer\": -0.13387435569779974}], \"train_return\": 0.468624062992288, \"train_returns_over_hodl\": 0.3086452298531117, \"train_sharpe\": 1.1583265095152737, \"validation_return\": 0.032075093447548886, \"validation_returns_over_hodl\": 0.0793283842100525, \"validation_sharpe\": 0.6455864240110075}, {\"centeredness_margin\": 0.032765535626176415, \"continuous_test_metrics\": [{\"annualised_returns\": -0.7628603387709507, \"annualised_returns_over_hodl\": 1.3288051406499513, \"annualised_returns_over_uniform_hodl\": 1.0830700201985142, \"calmar\": -1.0742794732782188, \"daily_log_sharpe\": -1.7370613657047247, \"daily_returns\": 0.00959546365120631, \"fee_revenue_over_value\": 0.2069383500039925, \"jax_sharpe\": -0.3304764001507628, \"return\": -0.439867786516432, \"returns_over_hodl\": 0.4055891826427742, \"returns_over_uniform_hodl\": 0.3438602320334827, \"sharpe\": -1.3308000287799546, \"sterling\": -1.9470348578743741, \"ulcer\": -0.15288307596299597}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": 0.9049284618689797, \"optuna_trial_number\": 119, \"price_ratio\": 1.1306808149273806, \"shift_exponent\": 1.0761460617870857, \"step\": 119, \"test_objective\": [{\"annualised_returns\": -0.7628603387709507, \"annualised_returns_over_hodl\": 1.3288051406499513, \"annualised_returns_over_uniform_hodl\": 1.0830700201985142, \"calmar\": -1.0742794732782188, \"daily_log_sharpe\": -1.7370613657047247, \"daily_returns\": 0.00959546365120631, \"fee_revenue_over_value\": 0.2069383500039925, \"jax_sharpe\": -0.3304764001507628, \"return\": -0.439867786516432, \"returns_over_hodl\": 0.4055891826427742, \"returns_over_uniform_hodl\": 0.3438602320334827, \"sharpe\": -1.3308000287799546, \"sterling\": -1.9470348578743741, \"ulcer\": -0.15288307596299597}], \"train_objective\": [{\"annualised_returns\": 0.7690289207132357, \"annualised_returns_over_hodl\": 0.4629617051850998, \"annualised_returns_over_uniform_hodl\": 0.4629617051850994, \"calmar\": 1.150951193108674, \"daily_log_sharpe\": 0.6653827190326234, \"daily_returns\": 0.021862640620900962, \"fee_revenue_over_value\": 0.4751986683692172, \"jax_sharpe\": 1.0875192538767453, \"return\": 0.4138559884041546, \"returns_over_hodl\": 0.2598431018177245, \"returns_over_uniform_hodl\": 0.2598431018177243, \"sharpe\": 1.0983313160152977, \"sterling\": 2.8937873189022048, \"ulcer\": -0.136288898708265}], \"train_return\": 0.4138559884041546, \"train_returns_over_hodl\": 0.2598431018177245, \"train_sharpe\": 1.0875192538767453, \"validation_return\": 0.023361404141166275, \"validation_returns_over_hodl\": 0.07564921311241335, \"validation_sharpe\": 0.5624517388393827}, {\"centeredness_margin\": 0.06932453262155064, \"continuous_test_metrics\": [{\"annualised_returns\": -0.753705386370959, \"annualised_returns_over_hodl\": 1.399645809080151, \"annualised_returns_over_uniform_hodl\": 1.1634884823904947, \"calmar\": -1.0623869363512901, \"daily_log_sharpe\": -1.7154858700518265, \"daily_returns\": 0.009622182314179209, \"fee_revenue_over_value\": 0.21763259683542158, \"jax_sharpe\": -0.30896450032582756, \"return\": -0.431257245985723, \"returns_over_hodl\": 0.42265512710456044, \"returns_over_uniform_hodl\": 0.3645185029148661, \"sharpe\": -1.3128112842447714, \"sterling\": -1.9424736997466492, \"ulcer\": -0.15016636973842856}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": 0.9636853173156178, \"optuna_trial_number\": 120, \"price_ratio\": 1.1129639963699522, \"shift_exponent\": 0.3609191523834695, \"step\": 120, \"test_objective\": [{\"annualised_returns\": -0.753705386370959, \"annualised_returns_over_hodl\": 1.399645809080151, \"annualised_returns_over_uniform_hodl\": 1.1634884823904947, \"calmar\": -1.0623869363512901, \"daily_log_sharpe\": -1.7154858700518265, \"daily_returns\": 0.009622182314179209, \"fee_revenue_over_value\": 0.21763259683542158, \"jax_sharpe\": -0.30896450032582756, \"return\": -0.431257245985723, \"returns_over_hodl\": 0.42265512710456044, \"returns_over_uniform_hodl\": 0.3645185029148661, \"sharpe\": -1.3128112842447714, \"sterling\": -1.9424736997466492, \"ulcer\": -0.15016636973842856}], \"train_objective\": [{\"annualised_returns\": 0.85090831897103, \"annualised_returns_over_hodl\": 0.5306748005970481, \"annualised_returns_over_uniform_hodl\": 0.5306748005970485, \"calmar\": 1.2781967348571386, \"daily_log_sharpe\": 0.7190168480103478, \"daily_returns\": 0.02220684904697109, \"fee_revenue_over_value\": 0.5080112872775701, \"jax_sharpe\": 1.1389317302692923, \"return\": 0.453232478200859, \"returns_over_hodl\": 0.2949302673077312, \"returns_over_uniform_hodl\": 0.2949302673077314, \"sharpe\": 1.150373867373063, \"sterling\": 3.2186466767343616, \"ulcer\": -0.13541953192244413}], \"train_return\": 0.453232478200859, \"train_returns_over_hodl\": 0.2949302673077312, \"train_sharpe\": 1.1389317302692923, \"validation_return\": 0.03313273769022995, \"validation_returns_over_hodl\": 0.08063831810338118, \"validation_sharpe\": 0.6557944251572944}, {\"centeredness_margin\": 0.03973191595370215, \"continuous_test_metrics\": [{\"annualised_returns\": -0.753727715706023, \"annualised_returns_over_hodl\": 1.4214733045379169, \"annualised_returns_over_uniform_hodl\": 1.1632923381934348, \"calmar\": -1.0615471608864124, \"daily_log_sharpe\": -1.7012301255426983, \"daily_returns\": 0.009744163782655163, \"fee_revenue_over_value\": 0.21709533286337057, \"jax_sharpe\": -0.2692842421013772, \"return\": -0.43127801285027756, \"returns_over_hodl\": 0.42785273115608136, \"returns_over_uniform_hodl\": 0.364468679386158, \"sharpe\": -1.2949581709519584, \"sterling\": -1.9332984329265865, \"ulcer\": -0.15149168935019816}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": 0.9646715046461296, \"optuna_trial_number\": 121, \"price_ratio\": 1.1143087071850128, \"shift_exponent\": 0.22532018570099552, \"step\": 121, \"test_objective\": [{\"annualised_returns\": -0.753727715706023, \"annualised_returns_over_hodl\": 1.4214733045379169, \"annualised_returns_over_uniform_hodl\": 1.1632923381934348, \"calmar\": -1.0615471608864124, \"daily_log_sharpe\": -1.7012301255426983, \"daily_returns\": 0.009744163782655163, \"fee_revenue_over_value\": 0.21709533286337057, \"jax_sharpe\": -0.2692842421013772, \"return\": -0.43127801285027756, \"returns_over_hodl\": 0.42785273115608136, \"returns_over_uniform_hodl\": 0.364468679386158, \"sharpe\": -1.2949581709519584, \"sterling\": -1.9332984329265865, \"ulcer\": -0.15149168935019816}], \"train_objective\": [{\"annualised_returns\": 0.9184481704488701, \"annualised_returns_over_hodl\": 0.5865292951895578, \"annualised_returns_over_uniform_hodl\": 0.5865292951895569, \"calmar\": 1.4002243524140114, \"daily_log_sharpe\": 0.7587067249887361, \"daily_returns\": 0.022135099471188014, \"fee_revenue_over_value\": 0.5150427459478868, \"jax_sharpe\": 1.1788962206660663, \"return\": 0.4852003359320818, \"returns_over_hodl\": 0.3234158311649318, \"returns_over_uniform_hodl\": 0.32341583116493133, \"sharpe\": 1.191501183485186, \"sterling\": 3.4996225406990034, \"ulcer\": -0.13285104668792105}], \"train_return\": 0.4852003359320818, \"train_returns_over_hodl\": 0.3234158311649318, \"train_sharpe\": 1.1788962206660665, \"validation_return\": 0.028847331419351807, \"validation_returns_over_hodl\": 0.0809500739461797, \"validation_sharpe\": 0.6147396452488396}, {\"centeredness_margin\": 0.015154105441566916, \"continuous_test_metrics\": [{\"annualised_returns\": -0.8573168462354427, \"annualised_returns_over_hodl\": 0.380896840218671, \"annualised_returns_over_uniform_hodl\": 0.2533500235848156, \"calmar\": -1.3017374748252286, \"daily_log_sharpe\": -2.1495792757006953, \"daily_returns\": 0.008157583473894415, \"fee_revenue_over_value\": 0.0753926290706703, \"jax_sharpe\": -0.8767504764853247, \"return\": -0.5435072317942862, \"returns_over_hodl\": 0.13880202586221202, \"returns_over_uniform_hodl\": 0.09521013545587476, \"sharpe\": -1.7268587307103547, \"sterling\": -2.1256932541807227, \"ulcer\": -0.16961060756366353}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": 0.750312758614248, \"optuna_trial_number\": 122, \"price_ratio\": 2.233555489731384, \"shift_exponent\": 1.6122773194058546, \"step\": 122, \"test_objective\": [{\"annualised_returns\": -0.8573168462354427, \"annualised_returns_over_hodl\": 0.380896840218671, \"annualised_returns_over_uniform_hodl\": 0.2533500235848156, \"calmar\": -1.3017374748252286, \"daily_log_sharpe\": -2.1495792757006953, \"daily_returns\": 0.008157583473894415, \"fee_revenue_over_value\": 0.0753926290706703, \"jax_sharpe\": -0.8767504764853247, \"return\": -0.5435072317942862, \"returns_over_hodl\": 0.13880202586221202, \"returns_over_uniform_hodl\": 0.09521013545587476, \"sharpe\": -1.7268587307103547, \"sterling\": -2.1256932541807227, \"ulcer\": -0.16961060756366353}], \"train_objective\": [{\"annualised_returns\": 0.6229593880863824, \"annualised_returns_over_hodl\": 0.3421642834893508, \"annualised_returns_over_uniform_hodl\": 0.3421642834893508, \"calmar\": 0.9907325943077163, \"daily_log_sharpe\": 0.5868863000084479, \"daily_returns\": 0.02067466055786922, \"fee_revenue_over_value\": 0.1420917265962384, \"jax_sharpe\": 0.9916506339423689, \"return\": 0.3417829631088727, \"returns_over_hodl\": 0.19562107037314713, \"returns_over_uniform_hodl\": 0.19562107037314713, \"sharpe\": 1.012501615602757, \"sterling\": 2.407488924994401, \"ulcer\": -0.12998088020163256}], \"train_return\": 0.3417829631088727, \"train_returns_over_hodl\": 0.19562107037314713, \"train_sharpe\": 0.9916506339423687, \"validation_return\": -0.01880321174896915, \"validation_returns_over_hodl\": 0.017293592712759986, \"validation_sharpe\": 0.1540712447275031}, {\"centeredness_margin\": 0.07826327145890072, \"continuous_test_metrics\": [{\"annualised_returns\": -0.71255646656703, \"annualised_returns_over_hodl\": 1.7860129248933068, \"annualised_returns_over_uniform_hodl\": 1.5249467081586654, \"calmar\": -1.0056689889227128, \"daily_log_sharpe\": -1.5697112850634063, \"daily_returns\": 0.010664138127669016, \"fee_revenue_over_value\": 0.3160519792934388, \"jax_sharpe\": -0.13618660341158303, \"return\": -0.3947446602678928, \"returns_over_hodl\": 0.5108158625703592, \"returns_over_uniform_hodl\": 0.4521189135567458, \"sharpe\": -1.17169893611697, \"sterling\": -1.8636401396111233, \"ulcer\": -0.14487844325738833}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": 0.9845408905101678, \"optuna_trial_number\": 123, \"price_ratio\": 1.025183649272547, \"shift_exponent\": 0.2999356022419367, \"step\": 123, \"test_objective\": [{\"annualised_returns\": -0.71255646656703, \"annualised_returns_over_hodl\": 1.7860129248933068, \"annualised_returns_over_uniform_hodl\": 1.5249467081586654, \"calmar\": -1.0056689889227128, \"daily_log_sharpe\": -1.5697112850634063, \"daily_returns\": 0.010664138127669016, \"fee_revenue_over_value\": 0.3160519792934388, \"jax_sharpe\": -0.13618660341158303, \"return\": -0.3947446602678928, \"returns_over_hodl\": 0.5108158625703592, \"returns_over_uniform_hodl\": 0.4521189135567458, \"sharpe\": -1.17169893611697, \"sterling\": -1.8636401396111233, \"ulcer\": -0.14487844325738833}], \"train_objective\": [{\"annualised_returns\": 0.5197278472593858, \"annualised_returns_over_hodl\": 0.25679327048394573, \"annualised_returns_over_uniform_hodl\": 0.2567932704839453, \"calmar\": 0.7160748951283045, \"daily_log_sharpe\": 0.4733392524418077, \"daily_returns\": 0.026239469643297826, \"fee_revenue_over_value\": 0.7579339121033261, \"jax_sharpe\": 0.915345060239933, \"return\": 0.2892998059583427, \"returns_over_hodl\": 0.14885496120785535, \"returns_over_uniform_hodl\": 0.14885496120785513, \"sharpe\": 0.9135037213170331, \"sterling\": 1.8398626763205974, \"ulcer\": -0.15123949949338683}], \"train_return\": 0.2892998059583427, \"train_returns_over_hodl\": 0.14885496120785535, \"train_sharpe\": 0.9153450602399328, \"validation_return\": 0.05806342312021351, \"validation_returns_over_hodl\": 0.09297190187731097, \"validation_sharpe\": 0.8972264192411621}, {\"centeredness_margin\": 0.039009446341847914, \"continuous_test_metrics\": [{\"annualised_returns\": -0.6823446608425684, \"annualised_returns_over_hodl\": 2.0978897724345913, \"annualised_returns_over_uniform_hodl\": 1.7903316987355962, \"calmar\": -0.9628642187706329, \"daily_log_sharpe\": -1.4427808774375257, \"daily_returns\": 0.01194472985603386, \"fee_revenue_over_value\": 0.3886138226089568, \"jax_sharpe\": -0.13272165696541294, \"return\": -0.3698863211118295, \"returns_over_hodl\": 0.5767790326312219, \"returns_over_uniform_hodl\": 0.5117586425744276, \"sharpe\": -1.041014604618684, \"sterling\": -1.7784447454119146, \"ulcer\": -0.1447204456379214}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": 1.1043638608506239, \"optuna_trial_number\": 124, \"price_ratio\": 1.0113059983143375, \"shift_exponent\": 0.08215298695069997, \"step\": 124, \"test_objective\": [{\"annualised_returns\": -0.6823446608425684, \"annualised_returns_over_hodl\": 2.0978897724345913, \"annualised_returns_over_uniform_hodl\": 1.7903316987355962, \"calmar\": -0.9628642187706329, \"daily_log_sharpe\": -1.4427808774375257, \"daily_returns\": 0.01194472985603386, \"fee_revenue_over_value\": 0.3886138226089568, \"jax_sharpe\": -0.13272165696541294, \"return\": -0.3698863211118295, \"returns_over_hodl\": 0.5767790326312219, \"returns_over_uniform_hodl\": 0.5117586425744276, \"sharpe\": -1.041014604618684, \"sterling\": -1.7784447454119146, \"ulcer\": -0.1447204456379214}], \"train_objective\": [{\"annualised_returns\": 0.9314555865245804, \"annualised_returns_over_hodl\": 0.597286242902143, \"annualised_returns_over_uniform_hodl\": 0.5972862429021479, \"calmar\": 1.3182077067838829, \"daily_log_sharpe\": 0.7385780726648343, \"daily_returns\": 0.023853462988798438, \"fee_revenue_over_value\": 0.926273885653016, \"jax_sharpe\": 1.1829277355277057, \"return\": 0.49130588221693294, \"returns_over_hodl\": 0.3288562935832271, \"returns_over_uniform_hodl\": 0.32885629358322954, \"sharpe\": 1.1791850452505939, \"sterling\": 3.3531872890970074, \"ulcer\": -0.14433304220815996}], \"train_return\": 0.49130588221693294, \"train_returns_over_hodl\": 0.3288562935832271, \"train_sharpe\": 1.1829277355277057, \"validation_return\": 0.0965047815445228, \"validation_returns_over_hodl\": 0.11849620843401998, \"validation_sharpe\": 1.2588998168384662}, {\"centeredness_margin\": 0.5251225483911309, \"continuous_test_metrics\": [{\"annualised_returns\": -0.8759282786697126, \"annualised_returns_over_hodl\": 0.11059028488014144, \"annualised_returns_over_uniform_hodl\": 0.08986443565808133, \"calmar\": -1.3321852823501743, \"daily_log_sharpe\": -2.5725336417625475, \"daily_returns\": 0.0069391201021270724, \"fee_revenue_over_value\": 0.036848550747603424, \"jax_sharpe\": -1.6233252763610717, \"return\": -0.5684930855948682, \"returns_over_hodl\": 0.04314880884822525, \"returns_over_uniform_hodl\": 0.03526447534613042, \"sharpe\": -2.2043892735130224, \"sterling\": -2.4798787761796444, \"ulcer\": -0.15993480537970772}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": 0.6154486164090841, \"optuna_trial_number\": 125, \"price_ratio\": 8.006587133820602, \"shift_exponent\": 46.90127890520154, \"step\": 125, \"test_objective\": [{\"annualised_returns\": -0.8759282786697126, \"annualised_returns_over_hodl\": 0.11059028488014144, \"annualised_returns_over_uniform_hodl\": 0.08986443565808133, \"calmar\": -1.3321852823501743, \"daily_log_sharpe\": -2.5725336417625475, \"daily_returns\": 0.0069391201021270724, \"fee_revenue_over_value\": 0.036848550747603424, \"jax_sharpe\": -1.6233252763610717, \"return\": -0.5684930855948682, \"returns_over_hodl\": 0.04314880884822525, \"returns_over_uniform_hodl\": 0.03526447534613042, \"sharpe\": -2.2043892735130224, \"sterling\": -2.4798787761796444, \"ulcer\": -0.15993480537970772}], \"train_objective\": [{\"annualised_returns\": 0.38219994065851104, \"annualised_returns_over_hodl\": 0.1430596517762115, \"annualised_returns_over_uniform_hodl\": 0.1430596517762115, \"calmar\": 0.597663078240888, \"daily_log_sharpe\": 0.40223630967026947, \"daily_returns\": 0.020496715929645526, \"fee_revenue_over_value\": 0.06599930055830557, \"jax_sharpe\": 0.8076756779396248, \"return\": 0.21714853667752432, \"returns_over_hodl\": 0.08456320898107283, \"returns_over_uniform_hodl\": 0.08456320898107283, \"sharpe\": 0.827040945242031, \"sterling\": 1.4628985668929035, \"ulcer\": -0.13305916698451492}], \"train_return\": 0.21714853667752432, \"train_returns_over_hodl\": 0.08456320898107283, \"train_sharpe\": 0.8076756779396248, \"validation_return\": 0.0014911267758663804, \"validation_returns_over_hodl\": 0.007925701279062336, \"validation_sharpe\": 0.33067546283611265}, {\"centeredness_margin\": 0.023219078568483577, \"continuous_test_metrics\": [{\"annualised_returns\": -0.8590000679146146, \"annualised_returns_over_hodl\": 0.3277217146539322, \"annualised_returns_over_uniform_hodl\": 0.2385643542494591, \"calmar\": -1.2223526219311984, \"daily_log_sharpe\": -2.1906488137753755, \"daily_returns\": 0.007719162745402177, \"fee_revenue_over_value\": 0.061900589333246596, \"jax_sharpe\": -0.6466448036967316, \"return\": -0.54568374790033, \"returns_over_hodl\": 0.12093355172361253, \"returns_over_uniform_hodl\": 0.08998827288685352, \"sharpe\": -1.7736892062351766, \"sterling\": -2.0793736366106432, \"ulcer\": -0.1675037550736891}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": 0.6978801008324002, \"optuna_trial_number\": 126, \"price_ratio\": 3.0022959460145526, \"shift_exponent\": 4.257518860943232, \"step\": 126, \"test_objective\": [{\"annualised_returns\": -0.8590000679146146, \"annualised_returns_over_hodl\": 0.3277217146539322, \"annualised_returns_over_uniform_hodl\": 0.2385643542494591, \"calmar\": -1.2223526219311984, \"daily_log_sharpe\": -2.1906488137753755, \"daily_returns\": 0.007719162745402177, \"fee_revenue_over_value\": 0.061900589333246596, \"jax_sharpe\": -0.6466448036967316, \"return\": -0.54568374790033, \"returns_over_hodl\": 0.12093355172361253, \"returns_over_uniform_hodl\": 0.08998827288685352, \"sharpe\": -1.7736892062351766, \"sterling\": -2.0793736366106432, \"ulcer\": -0.1675037550736891}], \"train_objective\": [{\"annualised_returns\": 0.5224081229835755, \"annualised_returns_over_hodl\": 0.25900982030849296, \"annualised_returns_over_uniform_hodl\": 0.25900982030849296, \"calmar\": 0.8248663716602754, \"daily_log_sharpe\": 0.5136111028714202, \"daily_returns\": 0.020589136063448296, \"fee_revenue_over_value\": 0.10937415018520633, \"jax_sharpe\": 0.9182475433728876, \"return\": 0.2906798493268732, \"returns_over_hodl\": 0.1500846749356406, \"returns_over_uniform_hodl\": 0.1500846749356406, \"sharpe\": 0.9387392287104417, \"sterling\": 2.0108115207676156, \"ulcer\": -0.13126995489846427}], \"train_return\": 0.2906798493268732, \"train_returns_over_hodl\": 0.1500846749356406, \"train_sharpe\": 0.9182475433728875, \"validation_return\": -0.013090238250100472, \"validation_returns_over_hodl\": 0.013600765069085918, \"validation_sharpe\": 0.19845089670618885}, {\"centeredness_margin\": 0.03707262358570389, \"continuous_test_metrics\": [{\"annualised_returns\": -0.8035097953625506, \"annualised_returns_over_hodl\": 0.9273434113732508, \"annualised_returns_over_uniform_hodl\": 0.7259991535013746, \"calmar\": -1.1311413863542126, \"daily_log_sharpe\": -1.9087651017205327, \"daily_returns\": 0.009073988573892232, \"fee_revenue_over_value\": 0.1552725245920771, \"jax_sharpe\": -0.4499194646332216, \"return\": -0.48071993062282536, \"returns_over_hodl\": 0.3024582444321795, \"returns_over_uniform_hodl\": 0.24584842243508054, \"sharpe\": -1.4995516065137366, \"sterling\": -2.008026565713956, \"ulcer\": -0.159032285552082}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": 0.8908519795380159, \"optuna_trial_number\": 127, \"price_ratio\": 1.2607386721171547, \"shift_exponent\": 1.0624913791922215, \"step\": 127, \"test_objective\": [{\"annualised_returns\": -0.8035097953625506, \"annualised_returns_over_hodl\": 0.9273434113732508, \"annualised_returns_over_uniform_hodl\": 0.7259991535013746, \"calmar\": -1.1311413863542126, \"daily_log_sharpe\": -1.9087651017205327, \"daily_returns\": 0.009073988573892232, \"fee_revenue_over_value\": 0.1552725245920771, \"jax_sharpe\": -0.4499194646332216, \"return\": -0.48071993062282536, \"returns_over_hodl\": 0.3024582444321795, \"returns_over_uniform_hodl\": 0.24584842243508054, \"sharpe\": -1.4995516065137366, \"sterling\": -2.008026565713956, \"ulcer\": -0.159032285552082}], \"train_objective\": [{\"annualised_returns\": 0.9152634085955684, \"annualised_returns_over_hodl\": 0.5838955425261831, \"annualised_returns_over_uniform_hodl\": 0.5838955425261831, \"calmar\": 1.4480040731080608, \"daily_log_sharpe\": 0.7544836631569479, \"daily_returns\": 0.02129913236117693, \"fee_revenue_over_value\": 0.35408691095205924, \"jax_sharpe\": 1.177212317979668, \"return\": 0.48370296577774075, \"returns_over_hodl\": 0.32208157118705105, \"returns_over_uniform_hodl\": 0.32208157118705105, \"sharpe\": 1.193255183449207, \"sterling\": 3.5183181954411777, \"ulcer\": -0.1297608838294456}], \"train_return\": 0.48370296577774075, \"train_returns_over_hodl\": 0.32208157118705105, \"train_sharpe\": 1.177212317979668, \"validation_return\": -0.0014612247805124712, \"validation_returns_over_hodl\": 0.053724731819305704, \"validation_sharpe\": 0.3261485904479172}, {\"centeredness_margin\": 0.012408770757869576, \"continuous_test_metrics\": [{\"annualised_returns\": -0.8305970057905354, \"annualised_returns_over_hodl\": 0.6774413131189849, \"annualised_returns_over_uniform_hodl\": 0.4880610722841452, \"calmar\": -1.2319776941646636, \"daily_log_sharpe\": -1.9995951100528921, \"daily_returns\": 0.00885567545049196, \"fee_revenue_over_value\": 0.11928413528403543, \"jax_sharpe\": -0.6934399755441467, \"return\": -0.5108327704250881, \"returns_over_hodl\": 0.2316121899313448, \"returns_over_uniform_hodl\": 0.1736021796557532, \"sharpe\": -1.5789589264926915, \"sterling\": -2.072496650195602, \"ulcer\": -0.165283104751846}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": 0.9245228322505192, \"optuna_trial_number\": 128, \"price_ratio\": 1.4470204552753896, \"shift_exponent\": 0.1078470866238068, \"step\": 128, \"test_objective\": [{\"annualised_returns\": -0.8305970057905354, \"annualised_returns_over_hodl\": 0.6774413131189849, \"annualised_returns_over_uniform_hodl\": 0.4880610722841452, \"calmar\": -1.2319776941646636, \"daily_log_sharpe\": -1.9995951100528921, \"daily_returns\": 0.00885567545049196, \"fee_revenue_over_value\": 0.11928413528403543, \"jax_sharpe\": -0.6934399755441467, \"return\": -0.5108327704250881, \"returns_over_hodl\": 0.2316121899313448, \"returns_over_uniform_hodl\": 0.1736021796557532, \"sharpe\": -1.5789589264926915, \"sterling\": -2.072496650195602, \"ulcer\": -0.165283104751846}], \"train_objective\": [{\"annualised_returns\": 1.0410387229298448, \"annualised_returns_over_hodl\": 0.6879099349276816, \"annualised_returns_over_uniform_hodl\": 0.6879099349276807, \"calmar\": 1.7034944238491778, \"daily_log_sharpe\": 0.8435500623504106, \"daily_returns\": 0.020983720539263527, \"fee_revenue_over_value\": 0.27164587626044034, \"jax_sharpe\": 1.2530693072204562, \"return\": 0.5421169710276175, \"returns_over_hodl\": 0.3741324746504704, \"returns_over_uniform_hodl\": 0.37413247465046995, \"sharpe\": 1.2741955045647921, \"sterling\": 4.078627854429233, \"ulcer\": -0.125270613945672}], \"train_return\": 0.5421169710276175, \"train_returns_over_hodl\": 0.3741324746504704, \"train_sharpe\": 1.253069307220456, \"validation_return\": -0.021385992900810558, \"validation_returns_over_hodl\": 0.04030628849119955, \"validation_sharpe\": 0.1451921911806744}, {\"centeredness_margin\": 0.3013789713896564, \"continuous_test_metrics\": [{\"annualised_returns\": -0.8733605429384275, \"annualised_returns_over_hodl\": 0.14471418541717007, \"annualised_returns_over_uniform_hodl\": 0.11241980785483197, \"calmar\": -1.3250281204608456, \"daily_log_sharpe\": -2.4669732440490764, \"daily_returns\": 0.0070302902774464, \"fee_revenue_over_value\": 0.02681460837312232, \"jax_sharpe\": -1.4547346888462243, \"return\": -0.5649185087168556, \"returns_over_hodl\": 0.055940696340067486, \"returns_over_uniform_hodl\": 0.04384054291923456, \"sharpe\": -2.077051643618199, \"sterling\": -2.353209263438213, \"ulcer\": -0.16165974990664334}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": 0.6134631626936624, \"optuna_trial_number\": 129, \"price_ratio\": 10.068813519862221, \"shift_exponent\": 2.7799272814966635e-05, \"step\": 129, \"test_objective\": [{\"annualised_returns\": -0.8733605429384275, \"annualised_returns_over_hodl\": 0.14471418541717007, \"annualised_returns_over_uniform_hodl\": 0.11241980785483197, \"calmar\": -1.3250281204608456, \"daily_log_sharpe\": -2.4669732440490764, \"daily_returns\": 0.0070302902774464, \"fee_revenue_over_value\": 0.02681460837312232, \"jax_sharpe\": -1.4547346888462243, \"return\": -0.5649185087168556, \"returns_over_hodl\": 0.055940696340067486, \"returns_over_uniform_hodl\": 0.04384054291923456, \"sharpe\": -2.077051643618199, \"sterling\": -2.353209263438213, \"ulcer\": -0.16165974990664334}], \"train_objective\": [{\"annualised_returns\": 0.3770269425011723, \"annualised_returns_over_hodl\": 0.13878165602579218, \"annualised_returns_over_uniform_hodl\": 0.13878165602579218, \"calmar\": 0.5890552884415361, \"daily_log_sharpe\": 0.3982342467755406, \"daily_returns\": 0.02048415125346165, \"fee_revenue_over_value\": 0.0613200597043111, \"jax_sharpe\": 0.8034240693101506, \"return\": 0.21438089225235202, \"returns_over_hodl\": 0.08209704710466181, \"returns_over_uniform_hodl\": 0.08209704710466181, \"sharpe\": 0.8228751039489444, \"sterling\": 1.4421355896405057, \"ulcer\": -0.13325154850915966}], \"train_return\": 0.21438089225235202, \"train_returns_over_hodl\": 0.08209704710466181, \"train_sharpe\": 0.8034240693101505, \"validation_return\": -0.004865349072137093, \"validation_returns_over_hodl\": 0.007477905619242886, \"validation_sharpe\": 0.2681166608934342}, {\"centeredness_margin\": 0.16461369222237543, \"continuous_test_metrics\": [{\"annualised_returns\": -0.8620443020355564, \"annualised_returns_over_hodl\": 0.30028539335941185, \"annualised_returns_over_uniform_hodl\": 0.21182334939631398, \"calmar\": -1.3185573459135582, \"daily_log_sharpe\": -2.294830871694102, \"daily_returns\": 0.007729866015392395, \"fee_revenue_over_value\": 0.05972765000260493, \"jax_sharpe\": -0.9594613273869756, \"return\": -0.549659903916244, \"returns_over_hodl\": 0.11154664294129013, \"returns_over_uniform_hodl\": 0.08044874308028205, \"sharpe\": -1.892022321370847, \"sterling\": -2.1814659234203013, \"ulcer\": -0.16450591533602302}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": 0.6997263730442436, \"optuna_trial_number\": 130, \"price_ratio\": 2.959624204101313, \"shift_exponent\": 0.135229847744102, \"step\": 130, \"test_objective\": [{\"annualised_returns\": -0.8620443020355564, \"annualised_returns_over_hodl\": 0.30028539335941185, \"annualised_returns_over_uniform_hodl\": 0.21182334939631398, \"calmar\": -1.3185573459135582, \"daily_log_sharpe\": -2.294830871694102, \"daily_returns\": 0.007729866015392395, \"fee_revenue_over_value\": 0.05972765000260493, \"jax_sharpe\": -0.9594613273869756, \"return\": -0.549659903916244, \"returns_over_hodl\": 0.11154664294129013, \"returns_over_uniform_hodl\": 0.08044874308028205, \"sharpe\": -1.892022321370847, \"sterling\": -2.1814659234203013, \"ulcer\": -0.16450591533602302}], \"train_objective\": [{\"annualised_returns\": 0.5262156035254575, \"annualised_returns_over_hodl\": 0.2621585524523202, \"annualised_returns_over_uniform_hodl\": 0.26215855245232067, \"calmar\": 0.8311538277247694, \"daily_log_sharpe\": 0.5164795355075216, \"daily_returns\": 0.02060585664241667, \"fee_revenue_over_value\": 0.11056550588697613, \"jax_sharpe\": 0.92111336118768, \"return\": 0.2926386375865737, \"returns_over_hodl\": 0.15183009023758398, \"returns_over_uniform_hodl\": 0.1518300902375842, \"sharpe\": 0.9416241676604092, \"sterling\": 2.0257853229260943, \"ulcer\": -0.13121223533037282}], \"train_return\": 0.2926386375865737, \"train_returns_over_hodl\": 0.15183009023758398, \"train_sharpe\": 0.92111336118768, \"validation_return\": -0.013335255777805255, \"validation_returns_over_hodl\": 0.013580811216431732, \"validation_sharpe\": 0.19638143172581757}, {\"centeredness_margin\": 0.047472451699334554, \"continuous_test_metrics\": [{\"annualised_returns\": -0.7223901723232415, \"annualised_returns_over_hodl\": 1.7009877348870752, \"annualised_returns_over_uniform_hodl\": 1.4385659756314637, \"calmar\": -1.0185313462108472, \"daily_log_sharpe\": -1.596655704911407, \"daily_returns\": 0.010324007011938962, \"fee_revenue_over_value\": 0.2814870014949347, \"jax_sharpe\": -0.2292335159371076, \"return\": -0.4031706700994212, \"returns_over_hodl\": 0.49207439466349134, \"returns_over_uniform_hodl\": 0.4319033657722473, \"sharpe\": -1.1961022575844151, \"sterling\": -1.8883858666623603, \"ulcer\": -0.145835309214675}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": 0.9897055518375449, \"optuna_trial_number\": 131, \"price_ratio\": 1.046107746803469, \"shift_exponent\": 0.21746894553477064, \"step\": 131, \"test_objective\": [{\"annualised_returns\": -0.7223901723232415, \"annualised_returns_over_hodl\": 1.7009877348870752, \"annualised_returns_over_uniform_hodl\": 1.4385659756314637, \"calmar\": -1.0185313462108472, \"daily_log_sharpe\": -1.596655704911407, \"daily_returns\": 0.010324007011938962, \"fee_revenue_over_value\": 0.2814870014949347, \"jax_sharpe\": -0.2292335159371076, \"return\": -0.4031706700994212, \"returns_over_hodl\": 0.49207439466349134, \"returns_over_uniform_hodl\": 0.4319033657722473, \"sharpe\": -1.1961022575844151, \"sterling\": -1.8883858666623603, \"ulcer\": -0.145835309214675}], \"train_objective\": [{\"annualised_returns\": 0.782013751821266, \"annualised_returns_over_hodl\": 0.4736999754513007, \"annualised_returns_over_uniform_hodl\": 0.47369997545130027, \"calmar\": 1.1182273430550853, \"daily_log_sharpe\": 0.6611338135564272, \"daily_returns\": 0.022948302896854487, \"fee_revenue_over_value\": 0.6846254724110667, \"jax_sharpe\": 1.0935583256997239, \"return\": 0.4201475369636454, \"returns_over_hodl\": 0.26544930507847697, \"returns_over_uniform_hodl\": 0.26544930507847675, \"sharpe\": 1.0991864001878453, \"sterling\": 2.8599470460827057, \"ulcer\": -0.143308919974642}], \"train_return\": 0.4201475369636454, \"train_returns_over_hodl\": 0.26544930507847697, \"train_sharpe\": 1.093558325699724, \"validation_return\": 0.05488476482031035, \"validation_returns_over_hodl\": 0.0980394776921969, \"validation_sharpe\": 0.8658668441142346}, {\"centeredness_margin\": 0.19241551729238973, \"continuous_test_metrics\": [{\"annualised_returns\": -0.8608132362915433, \"annualised_returns_over_hodl\": 0.3114643752650945, \"annualised_returns_over_uniform_hodl\": 0.22263721381256674, \"calmar\": -1.3203529499546556, \"daily_log_sharpe\": -2.303820019280146, \"daily_returns\": 0.0077414895337955605, \"fee_revenue_over_value\": 0.060977281980868485, \"jax_sharpe\": -0.9932396877033489, \"return\": -0.548045726616528, \"returns_over_hodl\": 0.1153855032891613, \"returns_over_uniform_hodl\": 0.08432145139507208, \"sharpe\": -1.9039867404159205, \"sterling\": -2.1975100552552718, \"ulcer\": -0.16348575954164307}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": 0.7046304144878511, \"optuna_trial_number\": 132, \"price_ratio\": 2.8520850468516215, \"shift_exponent\": 0.17357641306997681, \"step\": 132, \"test_objective\": [{\"annualised_returns\": -0.8608132362915433, \"annualised_returns_over_hodl\": 0.3114643752650945, \"annualised_returns_over_uniform_hodl\": 0.22263721381256674, \"calmar\": -1.3203529499546556, \"daily_log_sharpe\": -2.303820019280146, \"daily_returns\": 0.0077414895337955605, \"fee_revenue_over_value\": 0.060977281980868485, \"jax_sharpe\": -0.9932396877033489, \"return\": -0.548045726616528, \"returns_over_hodl\": 0.1153855032891613, \"returns_over_uniform_hodl\": 0.08432145139507208, \"sharpe\": -1.9039867404159205, \"sterling\": -2.1975100552552718, \"ulcer\": -0.16348575954164307}], \"train_objective\": [{\"annualised_returns\": 0.5351940898579386, \"annualised_returns_over_hodl\": 0.26958363268766905, \"annualised_returns_over_uniform_hodl\": 0.26958363268766905, \"calmar\": 0.8458665631146652, \"daily_log_sharpe\": 0.5231713629414925, \"daily_returns\": 0.02059530750945212, \"fee_revenue_over_value\": 0.11377854902787284, \"jax_sharpe\": 0.9278373531635502, \"return\": 0.2972501045058187, \"returns_over_hodl\": 0.15593922499750335, \"returns_over_uniform_hodl\": 0.15593922499750335, \"sharpe\": 0.9483631832646178, \"sterling\": 2.0610331520547405, \"ulcer\": -0.13110583185536637}], \"train_return\": 0.2972501045058187, \"train_returns_over_hodl\": 0.15593922499750335, \"train_sharpe\": 0.9278373531635502, \"validation_return\": -0.013867389564355403, \"validation_returns_over_hodl\": 0.014123560432712567, \"validation_sharpe\": 0.19191299755468436}, {\"centeredness_margin\": 0.30754353573014626, \"continuous_test_metrics\": [{\"annualised_returns\": -0.8655123702840405, \"annualised_returns_over_hodl\": 0.23995669837938194, \"annualised_returns_over_uniform_hodl\": 0.18135932259043175, \"calmar\": -1.3258116220308995, \"daily_log_sharpe\": -2.395017183228393, \"daily_returns\": 0.007380927029296678, \"fee_revenue_over_value\": 0.04956299594887188, \"jax_sharpe\": -1.385448317620391, \"return\": -0.5542540336435481, \"returns_over_hodl\": 0.09048159038635117, \"returns_over_uniform_hodl\": 0.0694265806466483, \"sharpe\": -2.004759508450609, \"sterling\": -2.3482323259517144, \"ulcer\": -0.1615074025739635}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": 0.6617513422273512, \"optuna_trial_number\": 133, \"price_ratio\": 4.093465799866142, \"shift_exponent\": 1.2848992304757993, \"step\": 133, \"test_objective\": [{\"annualised_returns\": -0.8655123702840405, \"annualised_returns_over_hodl\": 0.23995669837938194, \"annualised_returns_over_uniform_hodl\": 0.18135932259043175, \"calmar\": -1.3258116220308995, \"daily_log_sharpe\": -2.395017183228393, \"daily_returns\": 0.007380927029296678, \"fee_revenue_over_value\": 0.04956299594887188, \"jax_sharpe\": -1.385448317620391, \"return\": -0.5542540336435481, \"returns_over_hodl\": 0.09048159038635117, \"returns_over_uniform_hodl\": 0.0694265806466483, \"sharpe\": -2.004759508450609, \"sterling\": -2.3482323259517144, \"ulcer\": -0.1615074025739635}], \"train_objective\": [{\"annualised_returns\": 0.4580938769856926, \"annualised_returns_over_hodl\": 0.20582285547649803, \"annualised_returns_over_uniform_hodl\": 0.20582285547649803, \"calmar\": 0.7198379092172378, \"daily_log_sharpe\": 0.4639535552276297, \"daily_returns\": 0.02053585061325763, \"fee_revenue_over_value\": 0.08934480726885392, \"jax_sharpe\": 0.8687996215830809, \"return\": 0.2572963976994489, \"returns_over_hodl\": 0.12033771938103155, \"returns_over_uniform_hodl\": 0.12033771938103155, \"sharpe\": 0.8888661269304047, \"sterling\": 1.7582979945999493, \"ulcer\": -0.13214977792275898}], \"train_return\": 0.2572963976994489, \"train_returns_over_hodl\": 0.12033771938103155, \"train_sharpe\": 0.868799621583081, \"validation_return\": -0.009796256279498428, \"validation_returns_over_hodl\": 0.010896685769427261, \"validation_sharpe\": 0.22491802107475736}, {\"centeredness_margin\": 0.08748881292271456, \"continuous_test_metrics\": [{\"annualised_returns\": -0.7451249001858045, \"annualised_returns_over_hodl\": 1.4768547591332144, \"annualised_returns_over_uniform_hodl\": 1.238860747992911, \"calmar\": -1.050852787735736, \"daily_log_sharpe\": -1.6986335897554168, \"daily_returns\": 0.009886721502775826, \"fee_revenue_over_value\": 0.2412972074615109, \"jax_sharpe\": -0.29104280267572985, \"return\": -0.4233589008142309, \"returns_over_hodl\": 0.44091594218916064, \"returns_over_uniform_hodl\": 0.3834680861013602, \"sharpe\": -1.3010913275211602, \"sterling\": -1.943088509764353, \"ulcer\": -0.14723084284665633}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": 0.9905870534640832, \"optuna_trial_number\": 134, \"price_ratio\": 1.0815141809198652, \"shift_exponent\": 0.26072402552222507, \"step\": 134, \"test_objective\": [{\"annualised_returns\": -0.7451249001858045, \"annualised_returns_over_hodl\": 1.4768547591332144, \"annualised_returns_over_uniform_hodl\": 1.238860747992911, \"calmar\": -1.050852787735736, \"daily_log_sharpe\": -1.6986335897554168, \"daily_returns\": 0.009886721502775826, \"fee_revenue_over_value\": 0.2412972074615109, \"jax_sharpe\": -0.29104280267572985, \"return\": -0.4233589008142309, \"returns_over_hodl\": 0.44091594218916064, \"returns_over_uniform_hodl\": 0.3834680861013602, \"sharpe\": -1.3010913275211602, \"sterling\": -1.943088509764353, \"ulcer\": -0.14723084284665633}], \"train_objective\": [{\"annualised_returns\": 0.8614217513636533, \"annualised_returns_over_hodl\": 0.5393692593480446, \"annualised_returns_over_uniform_hodl\": 0.5393692593480446, \"calmar\": 1.2681492320087528, \"daily_log_sharpe\": 0.7190697538753874, \"daily_returns\": 0.02269197577418552, \"fee_revenue_over_value\": 0.5726757674049034, \"jax_sharpe\": 1.1442027010219284, \"return\": 0.4582384299785116, \"returns_over_hodl\": 0.2993909152569092, \"returns_over_uniform_hodl\": 0.2993909152569092, \"sharpe\": 1.1533539396598675, \"sterling\": 3.2205575309175534, \"ulcer\": -0.1388261210026993}], \"train_return\": 0.4582384299785116, \"train_returns_over_hodl\": 0.2993909152569092, \"train_sharpe\": 1.1442027010219282, \"validation_return\": 0.04441898766940744, \"validation_returns_over_hodl\": 0.0856324729254101, \"validation_sharpe\": 0.7652702050204083}, {\"centeredness_margin\": 0.051535551550556245, \"continuous_test_metrics\": [{\"annualised_returns\": -0.7537484750852725, \"annualised_returns_over_hodl\": 1.4112204759232827, \"annualised_returns_over_uniform_hodl\": 1.1631099847215252, \"calmar\": -1.0620033085057834, \"daily_log_sharpe\": -1.710104292770295, \"daily_returns\": 0.009666850226633748, \"fee_revenue_over_value\": 0.2176271438883784, \"jax_sharpe\": -0.3076196700885435, \"return\": -0.43129732062373294, \"returns_over_hodl\": 0.4254148087393985, \"returns_over_uniform_hodl\": 0.36442235648543697, \"sharpe\": -1.3059491862921795, \"sterling\": -1.9376683885254349, \"ulcer\": -0.15080585346572484}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": 0.9511754020757863, \"optuna_trial_number\": 135, \"price_ratio\": 1.1128920862879415, \"shift_exponent\": 0.6319747811679036, \"step\": 135, \"test_objective\": [{\"annualised_returns\": -0.7537484750852725, \"annualised_returns_over_hodl\": 1.4112204759232827, \"annualised_returns_over_uniform_hodl\": 1.1631099847215252, \"calmar\": -1.0620033085057834, \"daily_log_sharpe\": -1.710104292770295, \"daily_returns\": 0.009666850226633748, \"fee_revenue_over_value\": 0.2176271438883784, \"jax_sharpe\": -0.3076196700885435, \"return\": -0.43129732062373294, \"returns_over_hodl\": 0.4254148087393985, \"returns_over_uniform_hodl\": 0.36442235648543697, \"sharpe\": -1.3059491862921795, \"sterling\": -1.9376683885254349, \"ulcer\": -0.15080585346572484}], \"train_objective\": [{\"annualised_returns\": 0.823837954729987, \"annualised_returns_over_hodl\": 0.5082879951772179, \"annualised_returns_over_uniform_hodl\": 0.5082879951772188, \"calmar\": 1.233082554468554, \"daily_log_sharpe\": 0.7020190529099064, \"daily_returns\": 0.022193969429556593, \"fee_revenue_over_value\": 0.5085432724013753, \"jax_sharpe\": 1.1221240735319085, \"return\": 0.440291286048754, \"returns_over_hodl\": 0.2833987734385923, \"returns_over_uniform_hodl\": 0.28339877343859277, \"sharpe\": 1.1333787145832885, \"sterling\": 3.106453563406888, \"ulcer\": -0.1359548191197322}], \"train_return\": 0.440291286048754, \"train_returns_over_hodl\": 0.2833987734385923, \"train_sharpe\": 1.1221240735319085, \"validation_return\": 0.03133486970409405, \"validation_returns_over_hodl\": 0.07917275716199601, \"validation_sharpe\": 0.6384999932845783}, {\"centeredness_margin\": 0.451309762430874, \"continuous_test_metrics\": [{\"annualised_returns\": -0.8704810227498827, \"annualised_returns_over_hodl\": 0.16949421307570645, \"annualised_returns_over_uniform_hodl\": 0.13771394105138812, \"calmar\": -1.3310008257934638, \"daily_log_sharpe\": -2.4839097326070156, \"daily_returns\": 0.006982298557885628, \"fee_revenue_over_value\": 0.0341228717410892, \"jax_sharpe\": -1.4733290122655327, \"return\": -0.5609610125038076, \"returns_over_hodl\": 0.06508776931354565, \"returns_over_uniform_hodl\": 0.053335304425741414, \"sharpe\": -2.0999629620739992, \"sterling\": -2.3836326928877285, \"ulcer\": -0.15941969627201003}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": 0.616680551214757, \"optuna_trial_number\": 136, \"price_ratio\": 9.165528003082436, \"shift_exponent\": 0.012495388905927177, \"step\": 136, \"test_objective\": [{\"annualised_returns\": -0.8704810227498827, \"annualised_returns_over_hodl\": 0.16949421307570645, \"annualised_returns_over_uniform_hodl\": 0.13771394105138812, \"calmar\": -1.3310008257934638, \"daily_log_sharpe\": -2.4839097326070156, \"daily_returns\": 0.006982298557885628, \"fee_revenue_over_value\": 0.0341228717410892, \"jax_sharpe\": -1.4733290122655327, \"return\": -0.5609610125038076, \"returns_over_hodl\": 0.06508776931354565, \"returns_over_uniform_hodl\": 0.053335304425741414, \"sharpe\": -2.0999629620739992, \"sterling\": -2.3836326928877285, \"ulcer\": -0.15941969627201003}], \"train_objective\": [{\"annualised_returns\": 0.38216795906080026, \"annualised_returns_over_hodl\": 0.14303320345070714, \"annualised_returns_over_uniform_hodl\": 0.14303320345070714, \"calmar\": 0.597279258256773, \"daily_log_sharpe\": 0.40250973417798314, \"daily_returns\": 0.02047885263180436, \"fee_revenue_over_value\": 0.06318568575966699, \"jax_sharpe\": 0.8076769258288554, \"return\": 0.2171314384783425, \"returns_over_hodl\": 0.08454797330751807, \"returns_over_uniform_hodl\": 0.08454797330751807, \"sharpe\": 0.8271680846355838, \"sterling\": 1.462163116925685, \"ulcer\": -0.13317724354833704}], \"train_return\": 0.2171314384783425, \"train_returns_over_hodl\": 0.08454797330751807, \"train_sharpe\": 0.8076769258288554, \"validation_return\": -0.005539112811209779, \"validation_returns_over_hodl\": 0.0073672877123578395, \"validation_sharpe\": 0.2614856666542074}, {\"centeredness_margin\": 0.04159032733606942, \"continuous_test_metrics\": [{\"annualised_returns\": -0.8120674423547216, \"annualised_returns_over_hodl\": 0.8488044580903495, \"annualised_returns_over_uniform_hodl\": 0.6508275107637429, \"calmar\": -1.143101065529716, \"daily_log_sharpe\": -1.9418378956713982, \"daily_returns\": 0.008932327608830051, \"fee_revenue_over_value\": 0.1439784033372309, \"jax_sharpe\": -0.4748121134679149, \"return\": -0.48994952607542386, \"returns_over_hodl\": 0.28081702659379637, \"returns_over_uniform_hodl\": 0.22370492490372862, \"sharpe\": -1.530045664761785, \"sterling\": -2.0164186286427035, \"ulcer\": -0.1605152702607286}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": 0.9079246769361369, \"optuna_trial_number\": 137, \"price_ratio\": 1.3032678944535077, \"shift_exponent\": 0.21625058511993153, \"step\": 137, \"test_objective\": [{\"annualised_returns\": -0.8120674423547216, \"annualised_returns_over_hodl\": 0.8488044580903495, \"annualised_returns_over_uniform_hodl\": 0.6508275107637429, \"calmar\": -1.143101065529716, \"daily_log_sharpe\": -1.9418378956713982, \"daily_returns\": 0.008932327608830051, \"fee_revenue_over_value\": 0.1439784033372309, \"jax_sharpe\": -0.4748121134679149, \"return\": -0.48994952607542386, \"returns_over_hodl\": 0.28081702659379637, \"returns_over_uniform_hodl\": 0.22370492490372862, \"sharpe\": -1.530045664761785, \"sterling\": -2.0164186286427035, \"ulcer\": -0.1605152702607286}], \"train_objective\": [{\"annualised_returns\": 1.009460211400477, \"annualised_returns_over_hodl\": 0.6617949559506358, \"annualised_returns_over_uniform_hodl\": 0.6617949559506353, \"calmar\": 1.6283815768170706, \"daily_log_sharpe\": 0.8107484761619268, \"daily_returns\": 0.021153175352587022, \"fee_revenue_over_value\": 0.33189915564666317, \"jax_sharpe\": 1.2309988007648915, \"return\": 0.5275871342841552, \"returns_over_hodl\": 0.36118538899116603, \"returns_over_uniform_hodl\": 0.3611853889911658, \"sharpe\": 1.2493768647354504, \"sterling\": 3.9236055577738527, \"ulcer\": -0.12624623635430987}], \"train_return\": 0.5275871342841552, \"train_returns_over_hodl\": 0.36118538899116603, \"train_sharpe\": 1.2309988007648915, \"validation_return\": -0.0064471963125734, \"validation_returns_over_hodl\": 0.05001862949644553, \"validation_sharpe\": 0.2801480258915863}, {\"centeredness_margin\": 0.022744891846380063, \"continuous_test_metrics\": [{\"annualised_returns\": -0.8580034464144415, \"annualised_returns_over_hodl\": 0.3684791083339902, \"annualised_returns_over_uniform_hodl\": 0.24731882559236373, \"calmar\": -1.2188732935166324, \"daily_log_sharpe\": -2.162602996196137, \"daily_returns\": 0.008094640371636687, \"fee_revenue_over_value\": 0.07312730334649224, \"jax_sharpe\": -0.6690176197218447, \"return\": -0.5443931890741722, \"returns_over_hodl\": 0.13466658319460678, \"returns_over_uniform_hodl\": 0.0930845609449662, \"sharpe\": -1.741007049829753, \"sterling\": -2.07065965214396, \"ulcer\": -0.16910790261656036}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": 0.7426577080138073, \"optuna_trial_number\": 138, \"price_ratio\": 2.3155643306940976, \"shift_exponent\": 0.4129027702275412, \"step\": 138, \"test_objective\": [{\"annualised_returns\": -0.8580034464144415, \"annualised_returns_over_hodl\": 0.3684791083339902, \"annualised_returns_over_uniform_hodl\": 0.24731882559236373, \"calmar\": -1.2188732935166324, \"daily_log_sharpe\": -2.162602996196137, \"daily_returns\": 0.008094640371636687, \"fee_revenue_over_value\": 0.07312730334649224, \"jax_sharpe\": -0.6690176197218447, \"return\": -0.5443931890741722, \"returns_over_hodl\": 0.13466658319460678, \"returns_over_uniform_hodl\": 0.0930845609449662, \"sharpe\": -1.741007049829753, \"sterling\": -2.07065965214396, \"ulcer\": -0.16910790261656036}], \"train_objective\": [{\"annualised_returns\": 0.6078239378837549, \"annualised_returns_over_hodl\": 0.3296474818825956, \"annualised_returns_over_uniform_hodl\": 0.3296474818825956, \"calmar\": 0.965670860121677, \"daily_log_sharpe\": 0.5761681743592234, \"daily_returns\": 0.020642323894605342, \"fee_revenue_over_value\": 0.13694065545190295, \"jax_sharpe\": 0.9808935235368306, \"return\": 0.3341719260722764, \"returns_over_hodl\": 0.188839111965164, \"returns_over_uniform_hodl\": 0.188839111965164, \"sharpe\": 1.0017132579295975, \"sterling\": 2.347621059493321, \"ulcer\": -0.13017847431971163}], \"train_return\": 0.3341719260722764, \"train_returns_over_hodl\": 0.188839111965164, \"train_sharpe\": 0.9808935235368305, \"validation_return\": -0.017911866290736933, \"validation_returns_over_hodl\": 0.016689132474785096, \"validation_sharpe\": 0.16075344125918853}, {\"centeredness_margin\": 0.01115394319697513, \"continuous_test_metrics\": [{\"annualised_returns\": -0.7621099257473444, \"annualised_returns_over_hodl\": 1.3495352370865423, \"annualised_returns_over_uniform_hodl\": 1.0896617596997826, \"calmar\": -1.072698104156987, \"daily_log_sharpe\": -1.7141407170143608, \"daily_returns\": 0.009735055782036338, \"fee_revenue_over_value\": 0.2075492495828933, \"jax_sharpe\": -0.3206583279487673, \"return\": -0.439154606402286, \"returns_over_hodl\": 0.4106149042832219, \"returns_over_uniform_hodl\": 0.34557128233661993, \"sharpe\": -1.3030822585093929, \"sterling\": -1.9344792009219416, \"ulcer\": -0.15394363768906538}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": 0.9492050241662396, \"optuna_trial_number\": 139, \"price_ratio\": 1.1309371031561013, \"shift_exponent\": 0.17904178117402572, \"step\": 139, \"test_objective\": [{\"annualised_returns\": -0.7621099257473444, \"annualised_returns_over_hodl\": 1.3495352370865423, \"annualised_returns_over_uniform_hodl\": 1.0896617596997826, \"calmar\": -1.072698104156987, \"daily_log_sharpe\": -1.7141407170143608, \"daily_returns\": 0.009735055782036338, \"fee_revenue_over_value\": 0.2075492495828933, \"jax_sharpe\": -0.3206583279487673, \"return\": -0.439154606402286, \"returns_over_hodl\": 0.4106149042832219, \"returns_over_uniform_hodl\": 0.34557128233661993, \"sharpe\": -1.3030822585093929, \"sterling\": -1.9344792009219416, \"ulcer\": -0.15394363768906538}], \"train_objective\": [{\"annualised_returns\": 0.9758372429649209, \"annualised_returns_over_hodl\": 0.6339892402498246, \"annualised_returns_over_uniform_hodl\": 0.6339892402498242, \"calmar\": 1.516501150172219, \"daily_log_sharpe\": 0.7875571777433387, \"daily_returns\": 0.022007691638545494, \"fee_revenue_over_value\": 0.49521102478388657, \"jax_sharpe\": 1.2110531510255622, \"return\": 0.5120176414417279, \"returns_over_hodl\": 0.34731189811429664, \"returns_over_uniform_hodl\": 0.3473118981142964, \"sharpe\": 1.2245221156661041, \"sterling\": 3.7508653392047635, \"ulcer\": -0.13075646168474794}], \"train_return\": 0.5120176414417279, \"train_returns_over_hodl\": 0.34731189811429664, \"train_sharpe\": 1.2110531510255622, \"validation_return\": 0.02050357524446067, \"validation_returns_over_hodl\": 0.0775337425389715, \"validation_sharpe\": 0.5361415992440671}, {\"centeredness_margin\": 0.20684011097550783, \"continuous_test_metrics\": [{\"annualised_returns\": -0.7684842495223236, \"annualised_returns_over_hodl\": 1.1931888099169163, \"annualised_returns_over_uniform_hodl\": 1.0336687525162525, \"calmar\": -1.087019132554529, \"daily_log_sharpe\": -1.8295006961791653, \"daily_returns\": 0.009384499745383391, \"fee_revenue_over_value\": 0.22529542998205207, \"jax_sharpe\": -0.2829174128695276, \"return\": -0.4452560805515796, \"returns_over_hodl\": 0.3720319175455893, \"returns_over_uniform_hodl\": 0.3309327233167385, \"sharpe\": -1.438233708617617, \"sterling\": -1.9936864856042402, \"ulcer\": -0.14832425249403036}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": 0.9182097464593135, \"optuna_trial_number\": 140, \"price_ratio\": 1.0986264690056315, \"shift_exponent\": 0.5368042035898348, \"step\": 140, \"test_objective\": [{\"annualised_returns\": -0.7684842495223236, \"annualised_returns_over_hodl\": 1.1931888099169163, \"annualised_returns_over_uniform_hodl\": 1.0336687525162525, \"calmar\": -1.087019132554529, \"daily_log_sharpe\": -1.8295006961791653, \"daily_returns\": 0.009384499745383391, \"fee_revenue_over_value\": 0.22529542998205207, \"jax_sharpe\": -0.2829174128695276, \"return\": -0.4452560805515796, \"returns_over_hodl\": 0.3720319175455893, \"returns_over_uniform_hodl\": 0.3309327233167385, \"sharpe\": -1.438233708617617, \"sterling\": -1.9936864856042402, \"ulcer\": -0.14832425249403036}], \"train_objective\": [{\"annualised_returns\": 0.7021633020105529, \"annualised_returns_over_hodl\": 0.40766479148846435, \"annualised_returns_over_uniform_hodl\": 0.407664791488463, \"calmar\": 1.0236081775642918, \"daily_log_sharpe\": 0.6197248015617494, \"daily_returns\": 0.022145373095288853, \"fee_revenue_over_value\": 0.511250394966647, \"jax_sharpe\": 1.0440779517019374, \"return\": 0.38116569363241615, \"returns_over_hodl\": 0.2307137967807611, \"returns_over_uniform_hodl\": 0.23071379678076043, \"sharpe\": 1.0524381867269401, \"sterling\": 2.628787568793208, \"ulcer\": -0.14054111201565925}], \"train_return\": 0.38116569363241615, \"train_returns_over_hodl\": 0.2307137967807611, \"train_sharpe\": 1.0440779517019374, \"validation_return\": 0.030885770374524002, \"validation_returns_over_hodl\": 0.060740555792916595, \"validation_sharpe\": 0.6335277763797188}, {\"centeredness_margin\": 0.040043052770078004, \"continuous_test_metrics\": [{\"annualised_returns\": -0.8632771999507776, \"annualised_returns_over_hodl\": 0.25822177236010324, \"annualised_returns_over_uniform_hodl\": 0.20099339091593138, \"calmar\": -1.2574658031980201, \"daily_log_sharpe\": -2.306378351284278, \"daily_returns\": 0.007327756772575927, \"fee_revenue_over_value\": 0.04808099168275696, \"jax_sharpe\": -0.7021418754146757, \"return\": -0.5512851291664197, \"returns_over_hodl\": 0.09692262928604167, \"returns_over_uniform_hodl\": 0.07654952869976173, \"sharpe\": -1.9033817957299186, \"sterling\": -2.1329909424055633, \"ulcer\": -0.163530418034163}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": 0.6497942340273655, \"optuna_trial_number\": 141, \"price_ratio\": 4.7959393466389715, \"shift_exponent\": 0.04771508188275398, \"step\": 141, \"test_objective\": [{\"annualised_returns\": -0.8632771999507776, \"annualised_returns_over_hodl\": 0.25822177236010324, \"annualised_returns_over_uniform_hodl\": 0.20099339091593138, \"calmar\": -1.2574658031980201, \"daily_log_sharpe\": -2.306378351284278, \"daily_returns\": 0.007327756772575927, \"fee_revenue_over_value\": 0.04808099168275696, \"jax_sharpe\": -0.7021418754146757, \"return\": -0.5512851291664197, \"returns_over_hodl\": 0.09692262928604167, \"returns_over_uniform_hodl\": 0.07654952869976173, \"sharpe\": -1.9033817957299186, \"sterling\": -2.1329909424055633, \"ulcer\": -0.163530418034163}], \"train_objective\": [{\"annualised_returns\": 0.43712640373350276, \"annualised_returns_over_hodl\": 0.18848305392589437, \"annualised_returns_over_uniform_hodl\": 0.18848305392589393, \"calmar\": 0.6858628430200571, \"daily_log_sharpe\": 0.4473251823967301, \"daily_returns\": 0.02052236841000703, \"fee_revenue_over_value\": 0.08211489097186231, \"jax_sharpe\": 0.8522266953245309, \"return\": 0.24628843064345673, \"returns_over_hodl\": 0.1105288622737517, \"returns_over_uniform_hodl\": 0.11052886227375147, \"sharpe\": 0.8721512239964345, \"sterling\": 1.676467609368557, \"ulcer\": -0.13242377967660554}], \"train_return\": 0.24628843064345673, \"train_returns_over_hodl\": 0.1105288622737517, \"train_sharpe\": 0.8522266953245309, \"validation_return\": -0.008543151120140036, \"validation_returns_over_hodl\": 0.010081039309004725, \"validation_sharpe\": 0.23588655193944405}, {\"centeredness_margin\": 0.04547703618547862, \"continuous_test_metrics\": [{\"annualised_returns\": -0.8655343566202781, \"annualised_returns_over_hodl\": 0.22846077243589225, \"annualised_returns_over_uniform_hodl\": 0.1811661913460283, \"calmar\": -1.323836630630443, \"daily_log_sharpe\": -2.354889783169353, \"daily_returns\": 0.0072042372358677225, \"fee_revenue_over_value\": 0.04328030620930545, \"jax_sharpe\": -1.3926663900021035, \"return\": -0.5542833832018977, \"returns_over_hodl\": 0.08639853839420719, \"returns_over_uniform_hodl\": 0.06935616565650049, \"sharpe\": -1.9572590649910813, \"sterling\": -2.3348255324640355, \"ulcer\": -0.16243051303337452}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": 0.6342797878300077, \"optuna_trial_number\": 142, \"price_ratio\": 6.096814588513121, \"shift_exponent\": 0.04031209428096098, \"step\": 142, \"test_objective\": [{\"annualised_returns\": -0.8655343566202781, \"annualised_returns_over_hodl\": 0.22846077243589225, \"annualised_returns_over_uniform_hodl\": 0.1811661913460283, \"calmar\": -1.323836630630443, \"daily_log_sharpe\": -2.354889783169353, \"daily_returns\": 0.0072042372358677225, \"fee_revenue_over_value\": 0.04328030620930545, \"jax_sharpe\": -1.3926663900021035, \"return\": -0.5542833832018977, \"returns_over_hodl\": 0.08639853839420719, \"returns_over_uniform_hodl\": 0.06935616565650049, \"sharpe\": -1.9572590649910813, \"sterling\": -2.3348255324640355, \"ulcer\": -0.16243051303337452}], \"train_objective\": [{\"annualised_returns\": 0.41128770825969174, \"annualised_returns_over_hodl\": 0.1671148210227913, \"annualised_returns_over_uniform_hodl\": 0.1671148210227913, \"calmar\": 0.6441083957060155, \"daily_log_sharpe\": 0.4264682884946374, \"daily_returns\": 0.020501210902129183, \"fee_revenue_over_value\": 0.07352665035083998, \"jax_sharpe\": 0.8314857768372327, \"return\": 0.23263588070327068, \"returns_over_hodl\": 0.09836349960214075, \"returns_over_uniform_hodl\": 0.09836349960214075, \"sharpe\": 0.8512109473968577, \"sterling\": 1.575547558751943, \"ulcer\": -0.13277717852271603}], \"train_return\": 0.23263588070327068, \"train_returns_over_hodl\": 0.09836349960214075, \"train_sharpe\": 0.8314857768372328, \"validation_return\": -0.007031414913188705, \"validation_returns_over_hodl\": 0.009012600669630277, \"validation_sharpe\": 0.24893434764324648}, {\"centeredness_margin\": 0.10450170794756758, \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -Infinity, \"optuna_trial_number\": 143, \"price_ratio\": 1.0233690763565324, \"shift_exponent\": 8.617127744513406, \"step\": 143, \"test_objective\": -Infinity, \"train_objective\": -Infinity, \"train_return\": -Infinity, \"train_returns_over_hodl\": -Infinity, \"train_sharpe\": -Infinity, \"validation_return\": -Infinity, \"validation_returns_over_hodl\": -Infinity, \"validation_sharpe\": -Infinity}, {\"centeredness_margin\": 0.1570952078459626, \"continuous_test_metrics\": [{\"annualised_returns\": -0.7228873512106919, \"annualised_returns_over_hodl\": 1.6456267338763517, \"annualised_returns_over_uniform_hodl\": 1.434198682409587, \"calmar\": -1.0219653892328433, \"daily_log_sharpe\": -1.6212155517386737, \"daily_returns\": 0.01072322452145127, \"fee_revenue_over_value\": 0.32731548466596233, \"jax_sharpe\": -0.23641606610332827, \"return\": -0.40360137801113627, \"returns_over_hodl\": 0.4796814755063876, \"returns_over_uniform_hodl\": 0.4308700182513532, \"sharpe\": -1.2270289749991787, \"sterling\": -1.8828208590005877, \"ulcer\": -0.14662778061154366}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": 0.9939201049658785, \"optuna_trial_number\": 144, \"price_ratio\": 1.0172458333691397, \"shift_exponent\": 0.355655232414949, \"step\": 144, \"test_objective\": [{\"annualised_returns\": -0.7228873512106919, \"annualised_returns_over_hodl\": 1.6456267338763517, \"annualised_returns_over_uniform_hodl\": 1.434198682409587, \"calmar\": -1.0219653892328433, \"daily_log_sharpe\": -1.6212155517386737, \"daily_returns\": 0.01072322452145127, \"fee_revenue_over_value\": 0.32731548466596233, \"jax_sharpe\": -0.23641606610332827, \"return\": -0.40360137801113627, \"returns_over_hodl\": 0.4796814755063876, \"returns_over_uniform_hodl\": 0.4308700182513532, \"sharpe\": -1.2270289749991787, \"sterling\": -1.8828208590005877, \"ulcer\": -0.14662778061154366}], \"train_objective\": [{\"annualised_returns\": 0.3048720856603142, \"annualised_returns_over_hodl\": 0.07911061777124084, \"annualised_returns_over_uniform_hodl\": 0.07911061777124062, \"calmar\": 0.4053685479057352, \"daily_log_sharpe\": 0.2986006931129192, \"daily_returns\": 0.018052939201384625, \"fee_revenue_over_value\": 0.754271123509739, \"jax_sharpe\": 0.7436755815004885, \"return\": 0.17534068081761012, \"returns_over_hodl\": 0.04730952880509309, \"returns_over_uniform_hodl\": 0.04730952880509287, \"sharpe\": 0.7376698985538068, \"sterling\": 1.054205212190969, \"ulcer\": -0.1569105100268153}], \"train_return\": 0.17534068081761012, \"train_returns_over_hodl\": 0.04730952880509309, \"train_sharpe\": 0.7436755815004885, \"validation_return\": 0.05698425691918496, \"validation_returns_over_hodl\": 0.07647436943482289, \"validation_sharpe\": 0.8896227706909199}, {\"centeredness_margin\": 0.1382041134028562, \"continuous_test_metrics\": [{\"annualised_returns\": -0.7488524627578679, \"annualised_returns_over_hodl\": 1.4083815550877312, \"annualised_returns_over_uniform_hodl\": 1.2061172844911243, \"calmar\": -1.0577276345190645, \"daily_log_sharpe\": -1.7308521774991315, \"daily_returns\": 0.009934933234451737, \"fee_revenue_over_value\": 0.25780081756293954, \"jax_sharpe\": -0.30454972159601207, \"return\": -0.42677030392183146, \"returns_over_hodl\": 0.42473867468235516, \"returns_over_uniform_hodl\": 0.3752835024238239, \"sharpe\": -1.3367705208316465, \"sterling\": -1.948703765063352, \"ulcer\": -0.14725586932361104}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": 0.9204005552868058, \"optuna_trial_number\": 145, \"price_ratio\": 1.0624130129103906, \"shift_exponent\": 0.5232259558124546, \"step\": 145, \"test_objective\": [{\"annualised_returns\": -0.7488524627578679, \"annualised_returns_over_hodl\": 1.4083815550877312, \"annualised_returns_over_uniform_hodl\": 1.2061172844911243, \"calmar\": -1.0577276345190645, \"daily_log_sharpe\": -1.7308521774991315, \"daily_returns\": 0.009934933234451737, \"fee_revenue_over_value\": 0.25780081756293954, \"jax_sharpe\": -0.30454972159601207, \"return\": -0.42677030392183146, \"returns_over_hodl\": 0.42473867468235516, \"returns_over_uniform_hodl\": 0.3752835024238239, \"sharpe\": -1.3367705208316465, \"sterling\": -1.948703765063352, \"ulcer\": -0.14725586932361104}], \"train_objective\": [{\"annualised_returns\": 0.593532767634843, \"annualised_returns_over_hodl\": 0.3178288877648652, \"annualised_returns_over_uniform_hodl\": 0.31782888776486407, \"calmar\": 0.8408819794324426, \"daily_log_sharpe\": 0.5372253753181625, \"daily_returns\": 0.02261854694749277, \"fee_revenue_over_value\": 0.5942189782813253, \"jax_sharpe\": 0.9690302258469847, \"return\": 0.3269595658044939, \"returns_over_hodl\": 0.18241240202743914, \"returns_over_uniform_hodl\": 0.18241240202743847, \"sharpe\": 0.9730851970974355, \"sterling\": 2.1686954784456045, \"ulcer\": -0.14503114426326993}], \"train_return\": 0.3269595658044939, \"train_returns_over_hodl\": 0.18241240202743914, \"train_sharpe\": 0.9690302258469847, \"validation_return\": 0.035855101932864786, \"validation_returns_over_hodl\": 0.06725990328598952, \"validation_sharpe\": 0.6824706055893176}, {\"centeredness_margin\": 0.19433788157197843, \"continuous_test_metrics\": [{\"annualised_returns\": -0.7558569956808184, \"annualised_returns_over_hodl\": 1.3138781399858144, \"annualised_returns_over_uniform_hodl\": 1.1445884265106843, \"calmar\": -1.068494185148402, \"daily_log_sharpe\": -1.7695396483458454, \"daily_returns\": 0.00964152801790775, \"fee_revenue_over_value\": 0.2472619647809681, \"jax_sharpe\": -0.24923880982692453, \"return\": -0.43326348929420155, \"returns_over_hodl\": 0.4019537711449277, \"returns_over_uniform_hodl\": 0.3597051561136171, \"sharpe\": -1.3775882490852525, \"sterling\": -1.9656317569033865, \"ulcer\": -0.1472229163938104}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": 0.9051273202367208, \"optuna_trial_number\": 146, \"price_ratio\": 1.073661604034699, \"shift_exponent\": 0.35694341812283237, \"step\": 146, \"test_objective\": [{\"annualised_returns\": -0.7558569956808184, \"annualised_returns_over_hodl\": 1.3138781399858144, \"annualised_returns_over_uniform_hodl\": 1.1445884265106843, \"calmar\": -1.068494185148402, \"daily_log_sharpe\": -1.7695396483458454, \"daily_returns\": 0.00964152801790775, \"fee_revenue_over_value\": 0.2472619647809681, \"jax_sharpe\": -0.24923880982692453, \"return\": -0.43326348929420155, \"returns_over_hodl\": 0.4019537711449277, \"returns_over_uniform_hodl\": 0.3597051561136171, \"sharpe\": -1.3775882490852525, \"sterling\": -1.9656317569033865, \"ulcer\": -0.1472229163938104}], \"train_objective\": [{\"annualised_returns\": 0.6033628166740648, \"annualised_returns_over_hodl\": 0.3259581982221924, \"annualised_returns_over_uniform_hodl\": 0.32595819822219374, \"calmar\": 0.8632409470506007, \"daily_log_sharpe\": 0.5478216312742573, \"daily_returns\": 0.022360307419459567, \"fee_revenue_over_value\": 0.5566481049890646, \"jax_sharpe\": 0.9762450902286733, \"return\": 0.33192323490937437, \"returns_over_hodl\": 0.18683537320185528, \"returns_over_uniform_hodl\": 0.18683537320185595, \"sharpe\": 0.9814048295300021, \"sterling\": 2.2245656822462325, \"ulcer\": -0.1431589146747199}], \"train_return\": 0.33192323490937437, \"train_returns_over_hodl\": 0.18683537320185528, \"train_sharpe\": 0.9762450902286732, \"validation_return\": 0.042871053112940594, \"validation_returns_over_hodl\": 0.07251565329169773, \"validation_sharpe\": 0.7520106146506085}, {\"centeredness_margin\": 0.15881418660552177, \"continuous_test_metrics\": [{\"annualised_returns\": -0.8055338167212045, \"annualised_returns_over_hodl\": 0.8557197524386186, \"annualised_returns_over_uniform_hodl\": 0.7082198491429146, \"calmar\": -1.3099745380703738, \"daily_log_sharpe\": -1.9763245416590287, \"daily_returns\": 0.00869116036567795, \"fee_revenue_over_value\": 0.1491357366628645, \"jax_sharpe\": -0.8937585683884123, \"return\": -0.4828808547680852, \"returns_over_hodl\": 0.28274430647003856, \"returns_over_uniform_hodl\": 0.24066396784855493, \"sharpe\": -1.5777175037901197, \"sterling\": -2.180911137270482, \"ulcer\": -0.15565614546800027}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": 0.821962213105676, \"optuna_trial_number\": 147, \"price_ratio\": 1.28753087863819, \"shift_exponent\": 0.5593269954619283, \"step\": 147, \"test_objective\": [{\"annualised_returns\": -0.8055338167212045, \"annualised_returns_over_hodl\": 0.8557197524386186, \"annualised_returns_over_uniform_hodl\": 0.7082198491429146, \"calmar\": -1.3099745380703738, \"daily_log_sharpe\": -1.9763245416590287, \"daily_returns\": 0.00869116036567795, \"fee_revenue_over_value\": 0.1491357366628645, \"jax_sharpe\": -0.8937585683884123, \"return\": -0.4828808547680852, \"returns_over_hodl\": 0.28274430647003856, \"returns_over_uniform_hodl\": 0.24066396784855493, \"sharpe\": -1.5777175037901197, \"sterling\": -2.180911137270482, \"ulcer\": -0.15565614546800027}], \"train_objective\": [{\"annualised_returns\": 0.7214358023753844, \"annualised_returns_over_hodl\": 0.42360287461801827, \"annualised_returns_over_uniform_hodl\": 0.4236028746180174, \"calmar\": 1.1191924129995436, \"daily_log_sharpe\": 0.6383676720097964, \"daily_returns\": 0.02121348137823665, \"fee_revenue_over_value\": 0.31912237218314643, \"jax_sharpe\": 1.0571233942632166, \"return\": 0.3906388824376341, \"returns_over_hodl\": 0.23915506071878134, \"returns_over_uniform_hodl\": 0.2391550607187809, \"sharpe\": 1.0720831135460913, \"sterling\": 2.7539975807324706, \"ulcer\": -0.13240307746352753}], \"train_return\": 0.3906388824376341, \"train_returns_over_hodl\": 0.23915506071878134, \"train_sharpe\": 1.0571233942632166, \"validation_return\": 0.009713274882521938, \"validation_returns_over_hodl\": 0.05135919230200048, \"validation_sharpe\": 0.4262282083976523}, {\"centeredness_margin\": 0.18700508834978363, \"continuous_test_metrics\": [{\"annualised_returns\": -0.7673059524648598, \"annualised_returns_over_hodl\": 1.214863851876907, \"annualised_returns_over_uniform_hodl\": 1.0440190889490961, \"calmar\": -1.0845509375534794, \"daily_log_sharpe\": -1.8233637238966638, \"daily_returns\": 0.00946299584215187, \"fee_revenue_over_value\": 0.22176696819119543, \"jax_sharpe\": -0.35915857405305607, \"return\": -0.4441207288165073, \"returns_over_hodl\": 0.3774768642708284, \"returns_over_uniform_hodl\": 0.3336566410086794, \"sharpe\": -1.4317972342450138, \"sterling\": -1.9937032744938807, \"ulcer\": -0.148065093779183}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": 0.9456743241209278, \"optuna_trial_number\": 148, \"price_ratio\": 1.1040437011377768, \"shift_exponent\": 0.23381181905450976, \"step\": 148, \"test_objective\": [{\"annualised_returns\": -0.7673059524648598, \"annualised_returns_over_hodl\": 1.214863851876907, \"annualised_returns_over_uniform_hodl\": 1.0440190889490961, \"calmar\": -1.0845509375534794, \"daily_log_sharpe\": -1.8233637238966638, \"daily_returns\": 0.00946299584215187, \"fee_revenue_over_value\": 0.22176696819119543, \"jax_sharpe\": -0.35915857405305607, \"return\": -0.4441207288165073, \"returns_over_hodl\": 0.3774768642708284, \"returns_over_uniform_hodl\": 0.3336566410086794, \"sharpe\": -1.4317972342450138, \"sterling\": -1.9937032744938807, \"ulcer\": -0.148065093779183}], \"train_objective\": [{\"annualised_returns\": 0.7871426372836126, \"annualised_returns_over_hodl\": 0.4779414906316555, \"annualised_returns_over_uniform_hodl\": 0.47794149063165614, \"calmar\": 1.1656974714841013, \"daily_log_sharpe\": 0.6774652655814501, \"daily_returns\": 0.02224690340968864, \"fee_revenue_over_value\": 0.5051542479398895, \"jax_sharpe\": 1.0996139478877125, \"return\": 0.4226276739090382, \"returns_over_hodl\": 0.2676592779808391, \"returns_over_uniform_hodl\": 0.26765927798083955, \"sharpe\": 1.1091431230189217, \"sterling\": 2.9736386181109205, \"ulcer\": -0.1378625303923469}], \"train_return\": 0.4226276739090382, \"train_returns_over_hodl\": 0.2676592779808391, \"train_sharpe\": 1.0996139478877125, \"validation_return\": 0.041017116492744865, \"validation_returns_over_hodl\": 0.0744034078155209, \"validation_sharpe\": 0.7335695565754191}, {\"centeredness_margin\": 0.9295119368329369, \"continuous_test_metrics\": [{\"annualised_returns\": -0.8697711144328644, \"annualised_returns_over_hodl\": 0.1462480115010527, \"annualised_returns_over_uniform_hodl\": 0.14394988119149899, \"calmar\": -1.3383814407896, \"daily_log_sharpe\": -2.55379204470378, \"daily_returns\": 0.0067397907060591416, \"fee_revenue_over_value\": 0.03587631854980838, \"jax_sharpe\": -1.6802429967455614, \"return\": -0.5599934367298123, \"returns_over_hodl\": 0.05651029283837139, \"returns_over_uniform_hodl\": 0.05565669672000806, \"sharpe\": -2.180094779773843, \"sterling\": -2.4999631280978054, \"ulcer\": -0.15550107775137184}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": 0.5839093536523393, \"optuna_trial_number\": 149, \"price_ratio\": 8.020936487492804, \"shift_exponent\": 0.12832736388247745, \"step\": 149, \"test_objective\": [{\"annualised_returns\": -0.8697711144328644, \"annualised_returns_over_hodl\": 0.1462480115010527, \"annualised_returns_over_uniform_hodl\": 0.14394988119149899, \"calmar\": -1.3383814407896, \"daily_log_sharpe\": -2.55379204470378, \"daily_returns\": 0.0067397907060591416, \"fee_revenue_over_value\": 0.03587631854980838, \"jax_sharpe\": -1.6802429967455614, \"return\": -0.5599934367298123, \"returns_over_hodl\": 0.05651029283837139, \"returns_over_uniform_hodl\": 0.05565669672000806, \"sharpe\": -2.180094779773843, \"sterling\": -2.4999631280978054, \"ulcer\": -0.15550107775137184}], \"train_objective\": [{\"annualised_returns\": 0.3198038263799585, \"annualised_returns_over_hodl\": 0.09145895453884134, \"annualised_returns_over_uniform_hodl\": 0.09145895453884134, \"calmar\": 0.49499410740793226, \"daily_log_sharpe\": 0.3479554803472249, \"daily_returns\": 0.020477046077834935, \"fee_revenue_over_value\": 0.06020930730892178, \"jax_sharpe\": 0.755126150298381, \"return\": 0.183487904227561, \"returns_over_hodl\": 0.05456926621553437, \"returns_over_uniform_hodl\": 0.05456926621553437, \"sharpe\": 0.7725782146682032, \"sterling\": 1.2177236916310947, \"ulcer\": -0.13473386044311855}], \"train_return\": 0.183487904227561, \"train_returns_over_hodl\": 0.05456926621553437, \"train_sharpe\": 0.7551261502983809, \"validation_return\": 0.004977671374626347, \"validation_returns_over_hodl\": 0.007122975067257897, \"validation_sharpe\": 0.364112581195792}, {\"centeredness_margin\": 0.8260489054089272, \"continuous_test_metrics\": [{\"annualised_returns\": -0.8701838170326291, \"annualised_returns_over_hodl\": 0.14616792632852937, \"annualised_returns_over_uniform_hodl\": 0.14032464023276403, \"calmar\": -1.3393106386743796, \"daily_log_sharpe\": -2.554051189240735, \"daily_returns\": 0.0067253671234692655, \"fee_revenue_over_value\": 0.03153507077228162, \"jax_sharpe\": -1.1006171545551666, \"return\": -0.5605555488643467, \"returns_over_hodl\": 0.05648056394571488, \"returns_over_uniform_hodl\": 0.054308086293112234, \"sharpe\": -2.180131508489181, \"sterling\": -2.3646844782259615, \"ulcer\": -0.15556500952857288}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": 0.5805735777989199, \"optuna_trial_number\": 150, \"price_ratio\": 15.64588728948928, \"shift_exponent\": 0.6226372394015174, \"step\": 150, \"test_objective\": [{\"annualised_returns\": -0.8701838170326291, \"annualised_returns_over_hodl\": 0.14616792632852937, \"annualised_returns_over_uniform_hodl\": 0.14032464023276403, \"calmar\": -1.3393106386743796, \"daily_log_sharpe\": -2.554051189240735, \"daily_returns\": 0.0067253671234692655, \"fee_revenue_over_value\": 0.03153507077228162, \"jax_sharpe\": -1.1006171545551666, \"return\": -0.5605555488643467, \"returns_over_hodl\": 0.05648056394571488, \"returns_over_uniform_hodl\": 0.054308086293112234, \"sharpe\": -2.180131508489181, \"sterling\": -2.3646844782259615, \"ulcer\": -0.15556500952857288}], \"train_objective\": [{\"annualised_returns\": 0.31950511390713987, \"annualised_returns_over_hodl\": 0.09121192358107777, \"annualised_returns_over_uniform_hodl\": 0.09121192358107777, \"calmar\": 0.49554619239045866, \"daily_log_sharpe\": 0.3481180015484709, \"daily_returns\": 0.020446763859623882, \"fee_revenue_over_value\": 0.05322951301580553, \"jax_sharpe\": 0.7549097055019939, \"return\": 0.1833252734873787, \"returns_over_hodl\": 0.05442435102060439, \"returns_over_uniform_hodl\": 0.05442435102060439, \"sharpe\": 0.7730275630104699, \"sterling\": 1.2167883955055652, \"ulcer\": -0.13444442371658524}], \"train_return\": 0.1833252734873787, \"train_returns_over_hodl\": 0.05442435102060439, \"train_sharpe\": 0.7549097055019938, \"validation_return\": 0.0034829613249596214, \"validation_returns_over_hodl\": 0.007840941189218187, \"validation_sharpe\": 0.3490201999166576}, {\"centeredness_margin\": 0.10203080346161533, \"continuous_test_metrics\": [{\"annualised_returns\": -0.7035409382015472, \"annualised_returns_over_hodl\": 1.8394637513923606, \"annualised_returns_over_uniform_hodl\": 1.6041404489148667, \"calmar\": -0.9940821465372468, \"daily_log_sharpe\": -1.527992565063786, \"daily_returns\": 0.011606515561996525, \"fee_revenue_over_value\": 0.37238294531918464, \"jax_sharpe\": -0.18355079397857424, \"return\": -0.38716970740946366, \"returns_over_hodl\": 0.5224232669580404, \"returns_over_uniform_hodl\": 0.4702926190871992, \"sharpe\": -1.1292719689387631, \"sterling\": -1.8302758078909112, \"ulcer\": -0.146178729414746}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": 1.0636186314160514, \"optuna_trial_number\": 151, \"price_ratio\": 1.0115630824992143, \"shift_exponent\": 0.0949770888161681, \"step\": 151, \"test_objective\": [{\"annualised_returns\": -0.7035409382015472, \"annualised_returns_over_hodl\": 1.8394637513923606, \"annualised_returns_over_uniform_hodl\": 1.6041404489148667, \"calmar\": -0.9940821465372468, \"daily_log_sharpe\": -1.527992565063786, \"daily_returns\": 0.011606515561996525, \"fee_revenue_over_value\": 0.37238294531918464, \"jax_sharpe\": -0.18355079397857424, \"return\": -0.38716970740946366, \"returns_over_hodl\": 0.5224232669580404, \"returns_over_uniform_hodl\": 0.4702926190871992, \"sharpe\": -1.1292719689387631, \"sterling\": -1.8302758078909112, \"ulcer\": -0.146178729414746}], \"train_objective\": [{\"annualised_returns\": 0.7749465732627834, \"annualised_returns_over_hodl\": 0.4678555195050369, \"annualised_returns_over_uniform_hodl\": 0.467855519505042, \"calmar\": 1.079907204582802, \"daily_log_sharpe\": 0.6440296563630318, \"daily_returns\": 0.023960390145075665, \"fee_revenue_over_value\": 0.8857454323314415, \"jax_sharpe\": 1.0894512928078108, \"return\": 0.4167255146543505, \"returns_over_hodl\": 0.26240004741999234, \"returns_over_uniform_hodl\": 0.262400047419995, \"sharpe\": 1.0840256921002414, \"sterling\": 2.76793540861433, \"ulcer\": -0.14781426691009347}], \"train_return\": 0.4167255146543505, \"train_returns_over_hodl\": 0.26240004741999234, \"train_sharpe\": 1.0894512928078108, \"validation_return\": 0.09589500357295133, \"validation_returns_over_hodl\": 0.11050370678710864, \"validation_sharpe\": 1.2595333234513935}, {\"centeredness_margin\": 0.19002786492437504, \"continuous_test_metrics\": [{\"annualised_returns\": -0.7821402035783285, \"annualised_returns_over_hodl\": 1.0699117423908482, \"annualised_returns_over_uniform_hodl\": 0.91371282298578, \"calmar\": -1.104624250250166, \"daily_log_sharpe\": -1.8348973650901568, \"daily_returns\": 0.009170385676663603, \"fee_revenue_over_value\": 0.19141289951799248, \"jax_sharpe\": -0.3871071010109448, \"return\": -0.4586740046415898, \"returns_over_hodl\": 0.3404349767905772, \"returns_over_uniform_hodl\": 0.29874065482479173, \"sharpe\": -1.429758300727019, \"sterling\": -1.9866248894522536, \"ulcer\": -0.15291222668822696}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": 0.9408627024175304, \"optuna_trial_number\": 152, \"price_ratio\": 1.155715822320357, \"shift_exponent\": 0.036078488472552954, \"step\": 152, \"test_objective\": [{\"annualised_returns\": -0.7821402035783285, \"annualised_returns_over_hodl\": 1.0699117423908482, \"annualised_returns_over_uniform_hodl\": 0.91371282298578, \"calmar\": -1.104624250250166, \"daily_log_sharpe\": -1.8348973650901568, \"daily_returns\": 0.009170385676663603, \"fee_revenue_over_value\": 0.19141289951799248, \"jax_sharpe\": -0.3871071010109448, \"return\": -0.4586740046415898, \"returns_over_hodl\": 0.3404349767905772, \"returns_over_uniform_hodl\": 0.29874065482479173, \"sharpe\": -1.429758300727019, \"sterling\": -1.9866248894522536, \"ulcer\": -0.15291222668822696}], \"train_objective\": [{\"annualised_returns\": 1.0585433144397745, \"annualised_returns_over_hodl\": 0.7023859826305141, \"annualised_returns_over_uniform_hodl\": 0.7023859826305141, \"calmar\": 1.7090917642996313, \"daily_log_sharpe\": 0.8407419806433082, \"daily_returns\": 0.021805366758130506, \"fee_revenue_over_value\": 0.4213971613289294, \"jax_sharpe\": 1.2602518906008677, \"return\": 0.5501330908978641, \"returns_over_hodl\": 0.381275389773865, \"returns_over_uniform_hodl\": 0.381275389773865, \"sharpe\": 1.2745905531525572, \"sterling\": 4.119538487844372, \"ulcer\": -0.12766858258136263}], \"train_return\": 0.5501330908978641, \"train_returns_over_hodl\": 0.381275389773865, \"train_sharpe\": 1.260251890600868, \"validation_return\": 0.03230149664207271, \"validation_returns_over_hodl\": 0.06823960261542417, \"validation_sharpe\": 0.6476007385618137}, {\"centeredness_margin\": 0.07492728892500188, \"continuous_test_metrics\": [{\"annualised_returns\": -0.7447561358444474, \"annualised_returns_over_hodl\": 1.484936928684582, \"annualised_returns_over_uniform_hodl\": 1.2421000287611204, \"calmar\": -1.050077681906612, \"daily_log_sharpe\": -1.6917784152249447, \"daily_returns\": 0.009823462975160643, \"fee_revenue_over_value\": 0.2353614966466177, \"jax_sharpe\": -0.2394443745301724, \"return\": -0.42302303795642937, \"returns_over_hodl\": 0.44280770118755464, \"returns_over_uniform_hodl\": 0.38427388288853215, \"sharpe\": -1.2929496851161275, \"sterling\": -1.9369335051448815, \"ulcer\": -0.1479261433206268}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": 0.9763622956713894, \"optuna_trial_number\": 153, \"price_ratio\": 1.088014797408064, \"shift_exponent\": 0.5502711331491981, \"step\": 153, \"test_objective\": [{\"annualised_returns\": -0.7447561358444474, \"annualised_returns_over_hodl\": 1.484936928684582, \"annualised_returns_over_uniform_hodl\": 1.2421000287611204, \"calmar\": -1.050077681906612, \"daily_log_sharpe\": -1.6917784152249447, \"daily_returns\": 0.009823462975160643, \"fee_revenue_over_value\": 0.2353614966466177, \"jax_sharpe\": -0.2394443745301724, \"return\": -0.42302303795642937, \"returns_over_hodl\": 0.44280770118755464, \"returns_over_uniform_hodl\": 0.38427388288853215, \"sharpe\": -1.2929496851161275, \"sterling\": -1.9369335051448815, \"ulcer\": -0.1479261433206268}], \"train_objective\": [{\"annualised_returns\": 0.822350562776867, \"annualised_returns_over_hodl\": 0.5070579432303375, \"annualised_returns_over_uniform_hodl\": 0.5070579432303364, \"calmar\": 1.2042662577942087, \"daily_log_sharpe\": 0.6940824994120232, \"daily_returns\": 0.022387653758212077, \"fee_revenue_over_value\": 0.555517060202566, \"jax_sharpe\": 1.120134034015411, \"return\": 0.43957804777921683, \"returns_over_hodl\": 0.28276322899757655, \"returns_over_uniform_hodl\": 0.2827632289975759, \"sharpe\": 1.1293444828237473, \"sterling\": 3.071343965853197, \"ulcer\": -0.13936627149664715}], \"train_return\": 0.43957804777921683, \"train_returns_over_hodl\": 0.28276322899757655, \"train_sharpe\": 1.1201340340154107, \"validation_return\": 0.038454943797465724, \"validation_returns_over_hodl\": 0.08192996497373528, \"validation_sharpe\": 0.7073780055762035}, {\"centeredness_margin\": 0.01099383307308184, \"continuous_test_metrics\": [{\"annualised_returns\": -0.7594162203286183, \"annualised_returns_over_hodl\": 1.3766993850919849, \"annualised_returns_over_uniform_hodl\": 1.1133236683485213, \"calmar\": -1.0689855128434858, \"daily_log_sharpe\": -1.705395554274481, \"daily_returns\": 0.009766345974301021, \"fee_revenue_over_value\": 0.21198969297526735, \"jax_sharpe\": -0.31351872539977105, \"return\": -0.4366055636507423, \"returns_over_hodl\": 0.4171605448779967, \"returns_over_uniform_hodl\": 0.3516869048648237, \"sharpe\": -1.2945875085517984, \"sterling\": -1.93081272909504, \"ulcer\": -0.15340569139710716}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": 0.963215828044262, \"optuna_trial_number\": 154, \"price_ratio\": 1.1232615995398454, \"shift_exponent\": 0.1746579389275373, \"step\": 154, \"test_objective\": [{\"annualised_returns\": -0.7594162203286183, \"annualised_returns_over_hodl\": 1.3766993850919849, \"annualised_returns_over_uniform_hodl\": 1.1133236683485213, \"calmar\": -1.0689855128434858, \"daily_log_sharpe\": -1.705395554274481, \"daily_returns\": 0.009766345974301021, \"fee_revenue_over_value\": 0.21198969297526735, \"jax_sharpe\": -0.31351872539977105, \"return\": -0.4366055636507423, \"returns_over_hodl\": 0.4171605448779967, \"returns_over_uniform_hodl\": 0.3516869048648237, \"sharpe\": -1.2945875085517984, \"sterling\": -1.93081272909504, \"ulcer\": -0.15340569139710716}], \"train_objective\": [{\"annualised_returns\": 0.9938916645648828, \"annualised_returns_over_hodl\": 0.6489199896009221, \"annualised_returns_over_uniform_hodl\": 0.6489199896009208, \"calmar\": 1.5433930828342228, \"daily_log_sharpe\": 0.7987383652187241, \"daily_returns\": 0.022066051428900967, \"fee_revenue_over_value\": 0.5097252329061889, \"jax_sharpe\": 1.22161713634265, \"return\": 0.5203907716344751, \"returns_over_hodl\": 0.3547729340334227, \"returns_over_uniform_hodl\": 0.354772934033422, \"sharpe\": 1.234893978242628, \"sterling\": 3.821118428356541, \"ulcer\": -0.13062362203088693}], \"train_return\": 0.5203907716344751, \"train_returns_over_hodl\": 0.3547729340334227, \"train_sharpe\": 1.22161713634265, \"validation_return\": 0.023022614354714754, \"validation_returns_over_hodl\": 0.07866843298458615, \"validation_sharpe\": 0.5601052746742856}, {\"centeredness_margin\": 0.10453465127377035, \"continuous_test_metrics\": [{\"annualised_returns\": -0.7741664934520608, \"annualised_returns_over_hodl\": 1.1838627846190377, \"annualised_returns_over_uniform_hodl\": 0.9837550775276658, \"calmar\": -1.0913632384054577, \"daily_log_sharpe\": -1.7862352090115874, \"daily_returns\": 0.009355793292351994, \"fee_revenue_over_value\": 0.19477005347936993, \"jax_sharpe\": -0.31842011092528527, \"return\": -0.45078026046384445, \"returns_over_hodl\": 0.3696792552393786, \"returns_over_uniform_hodl\": 0.3176791993808068, \"sharpe\": -1.378384786096296, \"sterling\": -1.9646639894090778, \"ulcer\": -0.15405058229750618}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": 0.9179566741427583, \"optuna_trial_number\": 155, \"price_ratio\": 1.153285413537039, \"shift_exponent\": 0.07024487199414756, \"step\": 155, \"test_objective\": [{\"annualised_returns\": -0.7741664934520608, \"annualised_returns_over_hodl\": 1.1838627846190377, \"annualised_returns_over_uniform_hodl\": 0.9837550775276658, \"calmar\": -1.0913632384054577, \"daily_log_sharpe\": -1.7862352090115874, \"daily_returns\": 0.009355793292351994, \"fee_revenue_over_value\": 0.19477005347936993, \"jax_sharpe\": -0.31842011092528527, \"return\": -0.45078026046384445, \"returns_over_hodl\": 0.3696792552393786, \"returns_over_uniform_hodl\": 0.3176791993808068, \"sharpe\": -1.378384786096296, \"sterling\": -1.9646639894090778, \"ulcer\": -0.15405058229750618}], \"train_objective\": [{\"annualised_returns\": 0.9357329349152808, \"annualised_returns_over_hodl\": 0.6008235490603806, \"annualised_returns_over_uniform_hodl\": 0.6008235490603802, \"calmar\": 1.4649145018181884, \"daily_log_sharpe\": 0.7678306764093277, \"daily_returns\": 0.0217975215424577, \"fee_revenue_over_value\": 0.43827616919502665, \"jax_sharpe\": 1.18889366320644, \"return\": 0.4933100929460894, \"returns_over_hodl\": 0.3306421834351141, \"returns_over_uniform_hodl\": 0.33064218343511387, \"sharpe\": 1.202804916987576, \"sterling\": 3.6125272869496876, \"ulcer\": -0.13000925849249909}], \"train_return\": 0.4933100929460894, \"train_returns_over_hodl\": 0.3306421834351141, \"train_sharpe\": 1.1888936632064402, \"validation_return\": 0.02528970293906685, \"validation_returns_over_hodl\": 0.06947705470415455, \"validation_sharpe\": 0.5799242952231604}, {\"centeredness_margin\": 0.21441679519263246, \"continuous_test_metrics\": [{\"annualised_returns\": -0.7465129111452452, \"annualised_returns_over_hodl\": 1.3949373158155538, \"annualised_returns_over_uniform_hodl\": 1.2266682534842617, \"calmar\": -1.054792608035755, \"daily_log_sharpe\": -1.6919606191750212, \"daily_returns\": 0.010259818641423545, \"fee_revenue_over_value\": 0.2810731170162729, \"jax_sharpe\": -0.20794490870584983, \"return\": -0.4246256810204241, \"returns_over_hodl\": 0.4215302332781956, \"returns_over_uniform_hodl\": 0.38042884732728055, \"sharpe\": -1.2917719485099652, \"sterling\": -1.9310663084872721, \"ulcer\": -0.14650917239705952}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": 0.8715114473542576, \"optuna_trial_number\": 156, \"price_ratio\": 1.0547633767271771, \"shift_exponent\": 0.03260219907818412, \"step\": 156, \"test_objective\": [{\"annualised_returns\": -0.7465129111452452, \"annualised_returns_over_hodl\": 1.3949373158155538, \"annualised_returns_over_uniform_hodl\": 1.2266682534842617, \"calmar\": -1.054792608035755, \"daily_log_sharpe\": -1.6919606191750212, \"daily_returns\": 0.010259818641423545, \"fee_revenue_over_value\": 0.2810731170162729, \"jax_sharpe\": -0.20794490870584983, \"return\": -0.4246256810204241, \"returns_over_hodl\": 0.4215302332781956, \"returns_over_uniform_hodl\": 0.38042884732728055, \"sharpe\": -1.2917719485099652, \"sterling\": -1.9310663084872721, \"ulcer\": -0.14650917239705952}], \"train_objective\": [{\"annualised_returns\": 0.836561220076091, \"annualised_returns_over_hodl\": 0.5188099542861506, \"annualised_returns_over_uniform_hodl\": 0.5188099542861484, \"calmar\": 1.3055273072816154, \"daily_log_sharpe\": 0.6963198821345192, \"daily_returns\": 0.022267872801592863, \"fee_revenue_over_value\": 0.5485693120424588, \"jax_sharpe\": 1.1281967319675263, \"return\": 0.44638307101914565, \"returns_over_hodl\": 0.2888269735775397, \"returns_over_uniform_hodl\": 0.2888269735775386, \"sharpe\": 1.1325637547866971, \"sterling\": 3.1599702316515885, \"ulcer\": -0.13409201764515702}], \"train_return\": 0.44638307101914565, \"train_returns_over_hodl\": 0.2888269735775397, \"train_sharpe\": 1.1281967319675263, \"validation_return\": 0.06584639325935071, \"validation_returns_over_hodl\": 0.08873083165514117, \"validation_sharpe\": 0.9767426723333338}, {\"centeredness_margin\": 0.07182086301949546, \"continuous_test_metrics\": [{\"annualised_returns\": -0.7182489934107099, \"annualised_returns_over_hodl\": 1.7343548859819937, \"annualised_returns_over_uniform_hodl\": 1.4749427065260927, \"calmar\": -1.0129872234706576, \"daily_log_sharpe\": -1.5764842522934541, \"daily_returns\": 0.010618247683476764, \"fee_revenue_over_value\": 0.30375531429819413, \"jax_sharpe\": -0.2160585485013735, \"return\": -0.399600918305415, \"returns_over_hodl\": 0.49947069853709514, \"returns_over_uniform_hodl\": 0.4404678570811116, \"sharpe\": -1.1749991180870396, \"sterling\": -1.8723460731076078, \"ulcer\": -0.14545581762824106}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": 1.0236442711686444, \"optuna_trial_number\": 157, \"price_ratio\": 1.0368507692973117, \"shift_exponent\": 0.08562334908474627, \"step\": 157, \"test_objective\": [{\"annualised_returns\": -0.7182489934107099, \"annualised_returns_over_hodl\": 1.7343548859819937, \"annualised_returns_over_uniform_hodl\": 1.4749427065260927, \"calmar\": -1.0129872234706576, \"daily_log_sharpe\": -1.5764842522934541, \"daily_returns\": 0.010618247683476764, \"fee_revenue_over_value\": 0.30375531429819413, \"jax_sharpe\": -0.2160585485013735, \"return\": -0.399600918305415, \"returns_over_hodl\": 0.49947069853709514, \"returns_over_uniform_hodl\": 0.4404678570811116, \"sharpe\": -1.1749991180870396, \"sterling\": -1.8723460731076078, \"ulcer\": -0.14545581762824106}], \"train_objective\": [{\"annualised_returns\": 0.9398824384209636, \"annualised_returns_over_hodl\": 0.604255129320755, \"annualised_returns_over_uniform_hodl\": 0.6042551293207519, \"calmar\": 1.383194034009186, \"daily_log_sharpe\": 0.7582594296535989, \"daily_returns\": 0.02237316707588943, \"fee_revenue_over_value\": 0.7293869791303629, \"jax_sharpe\": 1.1890313142360407, \"return\": 0.49525273815835513, \"returns_over_hodl\": 0.33237321416952215, \"returns_over_uniform_hodl\": 0.3323732141695206, \"sharpe\": 1.19517498914105, \"sterling\": 3.4649205635934717, \"ulcer\": -0.1390057993614688}], \"train_return\": 0.49525273815835513, \"train_returns_over_hodl\": 0.33237321416952215, \"train_sharpe\": 1.1890313142360405, \"validation_return\": 0.07206769178033268, \"validation_returns_over_hodl\": 0.10514689408131361, \"validation_sharpe\": 1.0315616092586322}, {\"centeredness_margin\": 0.18369539493894077, \"continuous_test_metrics\": [{\"annualised_returns\": -0.7196857757266562, \"annualised_returns_over_hodl\": 1.646627348491692, \"annualised_returns_over_uniform_hodl\": 1.4623217971750928, \"calmar\": -1.0182021610695455, \"daily_log_sharpe\": -1.5993473893171273, \"daily_returns\": 0.010968395365109651, \"fee_revenue_over_value\": 0.3535733735664356, \"jax_sharpe\": -0.1159949934099588, \"return\": -0.40083587179601277, \"returns_over_hodl\": 0.47990683708807125, \"returns_over_uniform_hodl\": 0.43750497645315445, \"sharpe\": -1.203245242292565, \"sterling\": -1.8681258429908618, \"ulcer\": -0.14753698703715984}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": 1.0330034236722554, \"optuna_trial_number\": 158, \"price_ratio\": 1.0113731038102967, \"shift_exponent\": 0.17304825693711648, \"step\": 158, \"test_objective\": [{\"annualised_returns\": -0.7196857757266562, \"annualised_returns_over_hodl\": 1.646627348491692, \"annualised_returns_over_uniform_hodl\": 1.4623217971750928, \"calmar\": -1.0182021610695455, \"daily_log_sharpe\": -1.5993473893171273, \"daily_returns\": 0.010968395365109651, \"fee_revenue_over_value\": 0.3535733735664356, \"jax_sharpe\": -0.1159949934099588, \"return\": -0.40083587179601277, \"returns_over_hodl\": 0.47990683708807125, \"returns_over_uniform_hodl\": 0.43750497645315445, \"sharpe\": -1.203245242292565, \"sterling\": -1.8681258429908618, \"ulcer\": -0.14753698703715984}], \"train_objective\": [{\"annualised_returns\": 0.4744031327223781, \"annualised_returns_over_hodl\": 0.21931037753080318, \"annualised_returns_over_uniform_hodl\": 0.2193103775308134, \"calmar\": 0.6339804505480332, \"daily_log_sharpe\": 0.4342155440942197, \"daily_returns\": 0.017974294503534098, \"fee_revenue_over_value\": 0.8289808312197947, \"jax_sharpe\": 0.8813598681071406, \"return\": 0.26581584643206146, \"returns_over_hodl\": 0.12792913520067128, \"returns_over_uniform_hodl\": 0.12792913520067706, \"sharpe\": 0.8742547426067727, \"sterling\": 1.6550535191270628, \"ulcer\": -0.15542119159447218}], \"train_return\": 0.26581584643206146, \"train_returns_over_hodl\": 0.12792913520067128, \"train_sharpe\": 0.8813598681071407, \"validation_return\": 0.08369760033762952, \"validation_returns_over_hodl\": 0.0941234648658531, \"validation_sharpe\": 1.1500299540478518}, {\"centeredness_margin\": 0.14178045508733345, \"continuous_test_metrics\": [{\"annualised_returns\": -0.7256537589514174, \"annualised_returns_over_hodl\": 1.6264466274529852, \"annualised_returns_over_uniform_hodl\": 1.4098981457617592, \"calmar\": -1.0246160150882315, \"daily_log_sharpe\": -1.6033914566768095, \"daily_returns\": 0.01062066886658591, \"fee_revenue_over_value\": 0.30953435614798036, \"jax_sharpe\": -0.23416906089317022, \"return\": -0.40600639750687717, \"returns_over_hodl\": 0.475351793842804, \"returns_over_uniform_hodl\": 0.4250999340109005, \"sharpe\": -1.2025204459194738, \"sterling\": -1.8816329382399726, \"ulcer\": -0.1461561671367053}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": 0.9842275946444308, \"optuna_trial_number\": 159, \"price_ratio\": 1.0358549892833029, \"shift_exponent\": 0.06186012976561825, \"step\": 159, \"test_objective\": [{\"annualised_returns\": -0.7256537589514174, \"annualised_returns_over_hodl\": 1.6264466274529852, \"annualised_returns_over_uniform_hodl\": 1.4098981457617592, \"calmar\": -1.0246160150882315, \"daily_log_sharpe\": -1.6033914566768095, \"daily_returns\": 0.01062066886658591, \"fee_revenue_over_value\": 0.30953435614798036, \"jax_sharpe\": -0.23416906089317022, \"return\": -0.40600639750687717, \"returns_over_hodl\": 0.475351793842804, \"returns_over_uniform_hodl\": 0.4250999340109005, \"sharpe\": -1.2025204459194738, \"sterling\": -1.8816329382399726, \"ulcer\": -0.1461561671367053}], \"train_objective\": [{\"annualised_returns\": 0.9181307939496952, \"annualised_returns_over_hodl\": 0.5862668293479982, \"annualised_returns_over_uniform_hodl\": 0.5862668293479973, \"calmar\": 1.364020993564835, \"daily_log_sharpe\": 0.7465416724306281, \"daily_returns\": 0.02265838300301414, \"fee_revenue_over_value\": 0.7021169951920524, \"jax_sharpe\": 1.177228779985247, \"return\": 0.48505115978755886, \"returns_over_hodl\": 0.32328290494177025, \"returns_over_uniform_hodl\": 0.3232829049417698, \"sharpe\": 1.1821747926148478, \"sterling\": 3.4144555319578465, \"ulcer\": -0.13792044706142348}], \"train_return\": 0.48505115978755886, \"train_returns_over_hodl\": 0.32328290494177025, \"train_sharpe\": 1.1772287799852468, \"validation_return\": 0.0706033367194221, \"validation_returns_over_hodl\": 0.09716433038745564, \"validation_sharpe\": 1.0206864401043483}, {\"centeredness_margin\": 0.016554307464636903, \"continuous_test_metrics\": [{\"annualised_returns\": -0.6818414488101339, \"annualised_returns_over_hodl\": 2.1756596462503754, \"annualised_returns_over_uniform_hodl\": 1.794751987999462, \"calmar\": -0.9602020833708562, \"daily_log_sharpe\": -1.3934027268110587, \"daily_returns\": 0.010815996841220529, \"fee_revenue_over_value\": 0.31281065301360855, \"jax_sharpe\": -0.11443504917124576, \"return\": -0.36948450175443803, \"returns_over_hodl\": 0.592602939271099, \"returns_over_uniform_hodl\": 0.5127226811386469, \"sharpe\": -0.9744697437459149, \"sterling\": -1.7657263377837158, \"ulcer\": -0.1453249208783024}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": 0.9732566153596734, \"optuna_trial_number\": 160, \"price_ratio\": 1.049529208872834, \"shift_exponent\": 0.017050507581166104, \"step\": 160, \"test_objective\": [{\"annualised_returns\": -0.6818414488101339, \"annualised_returns_over_hodl\": 2.1756596462503754, \"annualised_returns_over_uniform_hodl\": 1.794751987999462, \"calmar\": -0.9602020833708562, \"daily_log_sharpe\": -1.3934027268110587, \"daily_returns\": 0.010815996841220529, \"fee_revenue_over_value\": 0.31281065301360855, \"jax_sharpe\": -0.11443504917124576, \"return\": -0.36948450175443803, \"returns_over_hodl\": 0.592602939271099, \"returns_over_uniform_hodl\": 0.5127226811386469, \"sharpe\": -0.9744697437459149, \"sterling\": -1.7657263377837158, \"ulcer\": -0.1453249208783024}], \"train_objective\": [{\"annualised_returns\": 1.1331105336947584, \"annualised_returns_over_hodl\": 0.7640520102204966, \"annualised_returns_over_uniform_hodl\": 0.764052010220498, \"calmar\": 1.8763414670812784, \"daily_log_sharpe\": 0.8861347064372777, \"daily_returns\": 0.021997547342624425, \"fee_revenue_over_value\": 0.511697651378073, \"jax_sharpe\": 1.2931725123788618, \"return\": 0.5839849359662728, \"returns_over_hodl\": 0.4114397161571861, \"returns_over_uniform_hodl\": 0.4114397161571868, \"sharpe\": 1.3287182080140902, \"sterling\": 4.350295428089784, \"ulcer\": -0.1266364734013232}], \"train_return\": 0.5839849359662728, \"train_returns_over_hodl\": 0.4114397161571861, \"train_sharpe\": 1.293172512378862, \"validation_return\": 0.052664577641288624, \"validation_returns_over_hodl\": 0.13177986302930966, \"validation_sharpe\": 0.8413950033875488}, {\"centeredness_margin\": 0.013823869171224044, \"continuous_test_metrics\": [{\"annualised_returns\": -0.8578221185730336, \"annualised_returns_over_hodl\": 0.38086243879638104, \"annualised_returns_over_uniform_hodl\": 0.24891163629432067, \"calmar\": -1.2080102888406303, \"daily_log_sharpe\": -2.130214524236085, \"daily_returns\": 0.008221300741657472, \"fee_revenue_over_value\": 0.07583379452851458, \"jax_sharpe\": -0.6305559664482305, \"return\": -0.5441589637882138, \"returns_over_hodl\": 0.13879059999840138, \"returns_over_uniform_hodl\": 0.09364651049823092, \"sharpe\": -1.701975693922939, \"sterling\": -2.049582167875879, \"ulcer\": -0.17064052523635684}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": 0.7577498920105028, \"optuna_trial_number\": 161, \"price_ratio\": 2.1736066011990167, \"shift_exponent\": 0.043052181625253795, \"step\": 161, \"test_objective\": [{\"annualised_returns\": -0.8578221185730336, \"annualised_returns_over_hodl\": 0.38086243879638104, \"annualised_returns_over_uniform_hodl\": 0.24891163629432067, \"calmar\": -1.2080102888406303, \"daily_log_sharpe\": -2.130214524236085, \"daily_returns\": 0.008221300741657472, \"fee_revenue_over_value\": 0.07583379452851458, \"jax_sharpe\": -0.6305559664482305, \"return\": -0.5441589637882138, \"returns_over_hodl\": 0.13879059999840138, \"returns_over_uniform_hodl\": 0.09364651049823092, \"sharpe\": -1.701975693922939, \"sterling\": -2.049582167875879, \"ulcer\": -0.17064052523635684}], \"train_objective\": [{\"annualised_returns\": 0.6376752003423951, \"annualised_returns_over_hodl\": 0.3543340504949466, \"annualised_returns_over_uniform_hodl\": 0.3543340504949457, \"calmar\": 1.0153222566880888, \"daily_log_sharpe\": 0.5972704048203897, \"daily_returns\": 0.02066905669546104, \"fee_revenue_over_value\": 0.14629411028160888, \"jax_sharpe\": 1.0020165489788153, \"return\": 0.3491562883846364, \"returns_over_hodl\": 0.20219121122364103, \"returns_over_uniform_hodl\": 0.2021912112236406, \"sharpe\": 1.022948233459759, \"sterling\": 2.4660787392187657, \"ulcer\": -0.12979878535902817}], \"train_return\": 0.3491562883846364, \"train_returns_over_hodl\": 0.20219121122364103, \"train_sharpe\": 1.0020165489788155, \"validation_return\": -0.019531761444650186, \"validation_returns_over_hodl\": 0.017990710096794205, \"validation_sharpe\": 0.14864408524693523}, {\"centeredness_margin\": 0.40244974839524483, \"continuous_test_metrics\": [{\"annualised_returns\": -0.8186109588533786, \"annualised_returns_over_hodl\": 0.6531386488768915, \"annualised_returns_over_uniform_hodl\": 0.593348289555522, \"calmar\": -1.166227254198709, \"daily_log_sharpe\": -2.113780712888564, \"daily_returns\": 0.008296306845280233, \"fee_revenue_over_value\": 0.16051891768529716, \"jax_sharpe\": -0.44443057515533024, \"return\": -0.49717758919079025, \"returns_over_hodl\": 0.22439459573103782, \"returns_over_uniform_hodl\": 0.20636347168689095, \"sharpe\": -1.7309878884004295, \"sterling\": -2.105417662236063, \"ulcer\": -0.15485650217127772}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": 0.8156240633820424, \"optuna_trial_number\": 162, \"price_ratio\": 1.2329737746440956, \"shift_exponent\": 2.9945219458915795, \"step\": 162, \"test_objective\": [{\"annualised_returns\": -0.8186109588533786, \"annualised_returns_over_hodl\": 0.6531386488768915, \"annualised_returns_over_uniform_hodl\": 0.593348289555522, \"calmar\": -1.166227254198709, \"daily_log_sharpe\": -2.113780712888564, \"daily_returns\": 0.008296306845280233, \"fee_revenue_over_value\": 0.16051891768529716, \"jax_sharpe\": -0.44443057515533024, \"return\": -0.49717758919079025, \"returns_over_hodl\": 0.22439459573103782, \"returns_over_uniform_hodl\": 0.20636347168689095, \"sharpe\": -1.7309878884004295, \"sterling\": -2.105417662236063, \"ulcer\": -0.15485650217127772}], \"train_objective\": [{\"annualised_returns\": 0.5921149520411138, \"annualised_returns_over_hodl\": 0.31665637447559325, \"annualised_returns_over_uniform_hodl\": 0.31665637447559325, \"calmar\": 0.8900339202702714, \"daily_log_sharpe\": 0.552030116473652, \"daily_returns\": 0.021378286522769952, \"fee_revenue_over_value\": 0.33674158880038474, \"jax_sharpe\": 0.9686420378988606, \"return\": 0.32624265041951883, \"returns_over_hodl\": 0.18177358102320906, \"returns_over_uniform_hodl\": 0.18177358102320906, \"sharpe\": 0.9807546630993129, \"sterling\": 2.242329874118052, \"ulcer\": -0.13656878583476134}], \"train_return\": 0.32624265041951883, \"train_returns_over_hodl\": 0.18177358102320906, \"train_sharpe\": 0.9686420378988606, \"validation_return\": 0.012724122504152557, \"validation_returns_over_hodl\": 0.030567241341807216, \"validation_sharpe\": 0.4493350309905547}, {\"centeredness_margin\": 0.06519313805971573, \"continuous_test_metrics\": [{\"annualised_returns\": -0.6736706113419958, \"annualised_returns_over_hodl\": 2.2572158342130755, \"annualised_returns_over_uniform_hodl\": 1.8665258384029744, \"calmar\": -0.949222117001779, \"daily_log_sharpe\": -1.3827522409200617, \"daily_returns\": 0.009451887164496523, \"fee_revenue_over_value\": 0.33792865018141804, \"jax_sharpe\": -0.05773751339141345, \"return\": -0.3630124391858316, \"returns_over_hodl\": 0.6089505561203561, \"returns_over_uniform_hodl\": 0.5282503499564981, \"sharpe\": -0.9612545690796771, \"sterling\": -1.7375247984532998, \"ulcer\": -0.14731230556285332}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": 0.824228822975179, \"optuna_trial_number\": 163, \"price_ratio\": 1.033849114180978, \"shift_exponent\": 0.009693943775451559, \"step\": 163, \"test_objective\": [{\"annualised_returns\": -0.6736706113419958, \"annualised_returns_over_hodl\": 2.2572158342130755, \"annualised_returns_over_uniform_hodl\": 1.8665258384029744, \"calmar\": -0.949222117001779, \"daily_log_sharpe\": -1.3827522409200617, \"daily_returns\": 0.009451887164496523, \"fee_revenue_over_value\": 0.33792865018141804, \"jax_sharpe\": -0.05773751339141345, \"return\": -0.3630124391858316, \"returns_over_hodl\": 0.6089505561203561, \"returns_over_uniform_hodl\": 0.5282503499564981, \"sharpe\": -0.9612545690796771, \"sterling\": -1.7375247984532998, \"ulcer\": -0.14731230556285332}], \"train_objective\": [{\"annualised_returns\": 0.9270920351193472, \"annualised_returns_over_hodl\": 0.5936776480794683, \"annualised_returns_over_uniform_hodl\": 0.593677648079465, \"calmar\": 1.5593642893243156, \"daily_log_sharpe\": 0.7648858833326349, \"daily_returns\": 0.02131998390820327, \"fee_revenue_over_value\": 0.4202362332428433, \"jax_sharpe\": 1.1776592962501935, \"return\": 0.4892594822038405, \"returns_over_hodl\": 0.32703281017247465, \"returns_over_uniform_hodl\": 0.3270328101724729, \"sharpe\": 1.2111293226283981, \"sterling\": 3.5487880586693055, \"ulcer\": -0.12518289248979997}], \"train_return\": 0.4892594822038405, \"train_returns_over_hodl\": 0.32703281017247465, \"train_sharpe\": 1.1776592962501935, \"validation_return\": -0.01764865440535246, \"validation_returns_over_hodl\": 0.056182087743987585, \"validation_sharpe\": 0.20086407391562996}, {\"centeredness_margin\": 0.26513427506722315, \"continuous_test_metrics\": [{\"annualised_returns\": -0.8546563590504059, \"annualised_returns_over_hodl\": 0.35626537771189115, \"annualised_returns_over_uniform_hodl\": 0.2767201383330167, \"calmar\": -1.2202943201853096, \"daily_log_sharpe\": -2.2811561150317012, \"daily_returns\": 0.0077297108443051, \"fee_revenue_over_value\": 0.07046740383804063, \"jax_sharpe\": -0.6554216519717959, \"return\": -0.5400981014993245, \"returns_over_hodl\": 0.130577157235789, \"returns_over_uniform_hodl\": 0.10338926623774336, \"sharpe\": -1.8838924045682812, \"sterling\": -2.121112243389818, \"ulcer\": -0.16150309583780384}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": 0.7448244203153307, \"optuna_trial_number\": 164, \"price_ratio\": 2.279545433186281, \"shift_exponent\": 0.048778711236589896, \"step\": 164, \"test_objective\": [{\"annualised_returns\": -0.8546563590504059, \"annualised_returns_over_hodl\": 0.35626537771189115, \"annualised_returns_over_uniform_hodl\": 0.2767201383330167, \"calmar\": -1.2202943201853096, \"daily_log_sharpe\": -2.2811561150317012, \"daily_returns\": 0.0077297108443051, \"fee_revenue_over_value\": 0.07046740383804063, \"jax_sharpe\": -0.6554216519717959, \"return\": -0.5400981014993245, \"returns_over_hodl\": 0.130577157235789, \"returns_over_uniform_hodl\": 0.10338926623774336, \"sharpe\": -1.8838924045682812, \"sterling\": -2.121112243389818, \"ulcer\": -0.16150309583780384}], \"train_objective\": [{\"annualised_returns\": 0.6128535937693873, \"annualised_returns_over_hodl\": 0.33380693561723107, \"annualised_returns_over_uniform_hodl\": 0.33380693561723107, \"calmar\": 0.9741673932360019, \"daily_log_sharpe\": 0.57972912235516, \"daily_returns\": 0.020655552990350094, \"fee_revenue_over_value\": 0.13870131133396196, \"jax_sharpe\": 0.9844876966455771, \"return\": 0.3367042574621828, \"returns_over_hodl\": 0.19109559371383988, \"returns_over_uniform_hodl\": 0.19109559371383988, \"sharpe\": 1.005303330626376, \"sterling\": 2.3677682390234422, \"ulcer\": -0.1300866258519488}], \"train_return\": 0.3367042574621828, \"train_returns_over_hodl\": 0.19109559371383988, \"train_sharpe\": 0.984487696645577, \"validation_return\": -0.011234629057232826, \"validation_returns_over_hodl\": 0.021161249302058982, \"validation_sharpe\": 0.21540414429956287}, {\"centeredness_margin\": 0.05930585331135336, \"continuous_test_metrics\": [{\"annualised_returns\": -0.7089104142834889, \"annualised_returns_over_hodl\": 1.8668165462261586, \"annualised_returns_over_uniform_hodl\": 1.556974173174671, \"calmar\": -0.9986947315875241, \"daily_log_sharpe\": -1.4940044866079996, \"daily_returns\": 0.01035016489177473, \"fee_revenue_over_value\": 0.2842100854518507, \"jax_sharpe\": -0.17768155493780777, \"return\": -0.39166435155123003, \"returns_over_hodl\": 0.5283127672062238, \"returns_over_uniform_hodl\": 0.45950914087640826, \"sharpe\": -1.0781757506216747, \"sterling\": -1.827047350417388, \"ulcer\": -0.14689992282476458}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": 0.9493025196484283, \"optuna_trial_number\": 165, \"price_ratio\": 1.0618923151077397, \"shift_exponent\": 0.019686383567790046, \"step\": 165, \"test_objective\": [{\"annualised_returns\": -0.7089104142834889, \"annualised_returns_over_hodl\": 1.8668165462261586, \"annualised_returns_over_uniform_hodl\": 1.556974173174671, \"calmar\": -0.9986947315875241, \"daily_log_sharpe\": -1.4940044866079996, \"daily_returns\": 0.01035016489177473, \"fee_revenue_over_value\": 0.2842100854518507, \"jax_sharpe\": -0.17768155493780777, \"return\": -0.39166435155123003, \"returns_over_hodl\": 0.5283127672062238, \"returns_over_uniform_hodl\": 0.45950914087640826, \"sharpe\": -1.0781757506216747, \"sterling\": -1.827047350417388, \"ulcer\": -0.14689992282476458}], \"train_objective\": [{\"annualised_returns\": 1.092330869880103, \"annualised_returns_over_hodl\": 0.7303278094385763, \"annualised_returns_over_uniform_hodl\": 0.7303278094385741, \"calmar\": 1.793436141415897, \"daily_log_sharpe\": 0.8440469719134499, \"daily_returns\": 0.022690601615778225, \"fee_revenue_over_value\": 0.5038856578265398, \"jax_sharpe\": 1.2725091723512216, \"return\": 0.5655305605285255, \"returns_over_hodl\": 0.3949955961164737, \"returns_over_uniform_hodl\": 0.3949955961164726, \"sharpe\": 1.285622969879287, \"sterling\": 4.194279911449063, \"ulcer\": -0.12772722995958485}], \"train_return\": 0.5655305605285255, \"train_returns_over_hodl\": 0.3949955961164737, \"train_sharpe\": 1.2725091723512214, \"validation_return\": 0.05499834699054085, \"validation_returns_over_hodl\": 0.12112677659348692, \"validation_sharpe\": 0.8655785315272778}, {\"centeredness_margin\": 0.2249776047138776, \"continuous_test_metrics\": [{\"annualised_returns\": -0.8620132802739954, \"annualised_returns_over_hodl\": 0.2967647468830583, \"annualised_returns_over_uniform_hodl\": 0.2120958491593088, \"calmar\": -1.2292803023136871, \"daily_log_sharpe\": -2.2981218660054092, \"daily_returns\": 0.007405385881737006, \"fee_revenue_over_value\": 0.0544582280692105, \"jax_sharpe\": -0.6826642005196228, \"return\": -0.5496191226519086, \"returns_over_hodl\": 0.11033357432503665, \"returns_over_uniform_hodl\": 0.08054658483626964, \"sharpe\": -1.8953503093797983, \"sterling\": -2.116935149285013, \"ulcer\": -0.16419233571965403}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": 0.7052146352572722, \"optuna_trial_number\": 166, \"price_ratio\": 2.8388249991714454, \"shift_exponent\": 0.008063293859821418, \"step\": 166, \"test_objective\": [{\"annualised_returns\": -0.8620132802739954, \"annualised_returns_over_hodl\": 0.2967647468830583, \"annualised_returns_over_uniform_hodl\": 0.2120958491593088, \"calmar\": -1.2292803023136871, \"daily_log_sharpe\": -2.2981218660054092, \"daily_returns\": 0.007405385881737006, \"fee_revenue_over_value\": 0.0544582280692105, \"jax_sharpe\": -0.6826642005196228, \"return\": -0.5496191226519086, \"returns_over_hodl\": 0.11033357432503665, \"returns_over_uniform_hodl\": 0.08054658483626964, \"sharpe\": -1.8953503093797983, \"sterling\": -2.116935149285013, \"ulcer\": -0.16419233571965403}], \"train_objective\": [{\"annualised_returns\": 0.5365315166265987, \"annualised_returns_over_hodl\": 0.27068966556431096, \"annualised_returns_over_uniform_hodl\": 0.27068966556431096, \"calmar\": 0.8480698755942728, \"daily_log_sharpe\": 0.5241775964549876, \"daily_returns\": 0.020607760087201842, \"fee_revenue_over_value\": 0.11419993904873943, \"jax_sharpe\": 0.9288375867666211, \"return\": 0.29793611643515105, \"returns_over_hodl\": 0.15655050889347466, \"returns_over_uniform_hodl\": 0.15655050889347466, \"sharpe\": 0.949377873914274, \"sterling\": 2.0662117451558273, \"ulcer\": -0.13108965087084992}], \"train_return\": 0.29793611643515105, \"train_returns_over_hodl\": 0.15655050889347466, \"train_sharpe\": 0.9288375867666211, \"validation_return\": -0.014901036170904214, \"validation_returns_over_hodl\": 0.013190708712105259, \"validation_sharpe\": 0.18149410242062228}, {\"centeredness_margin\": 0.1534942305261725, \"continuous_test_metrics\": [{\"annualised_returns\": -0.7224906994682196, \"annualised_returns_over_hodl\": 1.6726045408213412, \"annualised_returns_over_uniform_hodl\": 1.4376829302529104, \"calmar\": -1.0195965129937499, \"daily_log_sharpe\": -1.5758966211600538, \"daily_returns\": 0.010338895437327595, \"fee_revenue_over_value\": 0.2976706804302957, \"jax_sharpe\": -0.15585832308527806, \"return\": -0.40325771995580006, \"returns_over_hodl\": 0.4857397836512254, \"returns_over_uniform_hodl\": 0.43169451715155427, \"sharpe\": -1.1695485488949893, \"sterling\": -1.873754371497423, \"ulcer\": -0.14498179574333975}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": 0.8781760143350564, \"optuna_trial_number\": 167, \"price_ratio\": 1.0523137566699112, \"shift_exponent\": 0.022658897832261993, \"step\": 167, \"test_objective\": [{\"annualised_returns\": -0.7224906994682196, \"annualised_returns_over_hodl\": 1.6726045408213412, \"annualised_returns_over_uniform_hodl\": 1.4376829302529104, \"calmar\": -1.0195965129937499, \"daily_log_sharpe\": -1.5758966211600538, \"daily_returns\": 0.010338895437327595, \"fee_revenue_over_value\": 0.2976706804302957, \"jax_sharpe\": -0.15585832308527806, \"return\": -0.40325771995580006, \"returns_over_hodl\": 0.4857397836512254, \"returns_over_uniform_hodl\": 0.43169451715155427, \"sharpe\": -1.1695485488949893, \"sterling\": -1.873754371497423, \"ulcer\": -0.14498179574333975}], \"train_objective\": [{\"annualised_returns\": 0.8729236809235936, \"annualised_returns_over_hodl\": 0.5488811911683242, \"annualised_returns_over_uniform_hodl\": 0.5488811911683258, \"calmar\": 1.3832869602742384, \"daily_log_sharpe\": 0.7118581857015632, \"daily_returns\": 0.022253872755768768, \"fee_revenue_over_value\": 0.5191644506219415, \"jax_sharpe\": 1.1485901777224372, \"return\": 0.46370234607946026, \"returns_over_hodl\": 0.3042596409723619, \"returns_over_uniform_hodl\": 0.3042596409723628, \"sharpe\": 1.152036791751074, \"sterling\": 3.3119454381529265, \"ulcer\": -0.13209286067146292}], \"train_return\": 0.46370234607946026, \"train_returns_over_hodl\": 0.3042596409723619, \"train_sharpe\": 1.1485901777224372, \"validation_return\": 0.061981707482254844, \"validation_returns_over_hodl\": 0.11053914367197759, \"validation_sharpe\": 0.9361772807240352}, {\"centeredness_margin\": 0.4836833195319423, \"continuous_test_metrics\": [{\"annualised_returns\": -0.8772520550741228, \"annualised_returns_over_hodl\": 0.09556901359131165, \"annualised_returns_over_uniform_hodl\": 0.0782361870252648, \"calmar\": -1.3302605900034943, \"daily_log_sharpe\": -2.5654272068479176, \"daily_returns\": 0.006810811463351935, \"fee_revenue_over_value\": 0.016870033163323535, \"jax_sharpe\": -1.1146397111597712, \"return\": -0.5703532077168891, \"returns_over_hodl\": 0.03744342542094059, \"returns_over_uniform_hodl\": 0.030801700154245992, \"sharpe\": -2.184663736866219, \"sterling\": -2.3292579892224556, \"ulcer\": -0.15943639879117705}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": 0.5855235254519496, \"optuna_trial_number\": 168, \"price_ratio\": 34.30713690567158, \"shift_exponent\": 0.001346552430263593, \"step\": 168, \"test_objective\": [{\"annualised_returns\": -0.8772520550741228, \"annualised_returns_over_hodl\": 0.09556901359131165, \"annualised_returns_over_uniform_hodl\": 0.0782361870252648, \"calmar\": -1.3302605900034943, \"daily_log_sharpe\": -2.5654272068479176, \"daily_returns\": 0.006810811463351935, \"fee_revenue_over_value\": 0.016870033163323535, \"jax_sharpe\": -1.1146397111597712, \"return\": -0.5703532077168891, \"returns_over_hodl\": 0.03744342542094059, \"returns_over_uniform_hodl\": 0.030801700154245992, \"sharpe\": -2.184663736866219, \"sterling\": -2.3292579892224556, \"ulcer\": -0.15943639879117705}], \"train_objective\": [{\"annualised_returns\": 0.33214030456528953, \"annualised_returns_over_hodl\": 0.10166104617831206, \"annualised_returns_over_uniform_hodl\": 0.10166104617831206, \"calmar\": 0.5171712342697087, \"daily_log_sharpe\": 0.36009056547892226, \"daily_returns\": 0.02043380154204296, \"fee_revenue_over_value\": 0.0461499450077967, \"jax_sharpe\": 0.765652073790567, \"return\": 0.19019178755647093, \"returns_over_hodl\": 0.06054288816613407, \"returns_over_uniform_hodl\": 0.06054288816613407, \"sharpe\": 0.7846365378837336, \"sterling\": 1.2675913084055224, \"ulcer\": -0.13390898431718629}], \"train_return\": 0.19019178755647093, \"train_returns_over_hodl\": 0.06054288816613407, \"train_sharpe\": 0.7656520737905669, \"validation_return\": -0.0021066566144999355, \"validation_returns_over_hodl\": 0.005611688612623844, \"validation_sharpe\": 0.29328790545172656}, {\"centeredness_margin\": 0.01624430097631191, \"continuous_test_metrics\": [{\"annualised_returns\": -0.7450968440343098, \"annualised_returns_over_hodl\": 1.5190888272737513, \"annualised_returns_over_uniform_hodl\": 1.2391071973964292, \"calmar\": -1.0487869879416163, \"daily_log_sharpe\": -1.6217215171829087, \"daily_returns\": 0.009947908665375812, \"fee_revenue_over_value\": 0.22906601513860908, \"jax_sharpe\": -0.24140243940640269, \"return\": -0.4233333376609183, \"returns_over_hodl\": 0.4507611814796606, \"returns_over_uniform_hodl\": 0.38352941680920916, \"sharpe\": -1.2033249524799041, \"sterling\": -1.881589554980181, \"ulcer\": -0.15369469522296134}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": 1.0058288948497742, \"optuna_trial_number\": 169, \"price_ratio\": 1.103213531249892, \"shift_exponent\": 0.05905603511910827, \"step\": 169, \"test_objective\": [{\"annualised_returns\": -0.7450968440343098, \"annualised_returns_over_hodl\": 1.5190888272737513, \"annualised_returns_over_uniform_hodl\": 1.2391071973964292, \"calmar\": -1.0487869879416163, \"daily_log_sharpe\": -1.6217215171829087, \"daily_returns\": 0.009947908665375812, \"fee_revenue_over_value\": 0.22906601513860908, \"jax_sharpe\": -0.24140243940640269, \"return\": -0.4233333376609183, \"returns_over_hodl\": 0.4507611814796606, \"returns_over_uniform_hodl\": 0.38352941680920916, \"sharpe\": -1.2033249524799041, \"sterling\": -1.881589554980181, \"ulcer\": -0.15369469522296134}], \"train_objective\": [{\"annualised_returns\": 1.1726355097063204, \"annualised_returns_over_hodl\": 0.7967386020711067, \"annualised_returns_over_uniform_hodl\": 0.7967386020711058, \"calmar\": 1.8730247483432978, \"daily_log_sharpe\": 0.894878883286404, \"daily_returns\": 0.02225638288133419, \"fee_revenue_over_value\": 0.5478323696950059, \"jax_sharpe\": 1.3175562172213588, \"return\": 0.6017396903875203, \"returns_over_hodl\": 0.4272604256675834, \"returns_over_uniform_hodl\": 0.42726042566758293, \"sharpe\": 1.331745645614573, \"sterling\": 4.551692076082482, \"ulcer\": -0.12795876979773355}], \"train_return\": 0.6017396903875203, \"train_returns_over_hodl\": 0.4272604256675834, \"train_sharpe\": 1.3175562172213586, \"validation_return\": 0.03186344233451277, \"validation_returns_over_hodl\": 0.08466666777315068, \"validation_sharpe\": 0.6439141954568947}, {\"centeredness_margin\": 0.21237070003163724, \"continuous_test_metrics\": [{\"annualised_returns\": -0.7651777207771785, \"annualised_returns_over_hodl\": 1.2185708811093319, \"annualised_returns_over_uniform_hodl\": 1.0627137923220742, \"calmar\": -1.0812196757358001, \"daily_log_sharpe\": -1.7793440320889677, \"daily_returns\": 0.009416463909480702, \"fee_revenue_over_value\": 0.22277660622982476, \"jax_sharpe\": -0.2723740904393643, \"return\": -0.4420787347403582, \"returns_over_hodl\": 0.37840490871954824, \"returns_over_uniform_hodl\": 0.33855576047891733, \"sharpe\": -1.3801032425821114, \"sterling\": -1.9769703782995922, \"ulcer\": -0.1484545713871388}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": 0.9688190723176997, \"optuna_trial_number\": 170, \"price_ratio\": 1.1078748243028798, \"shift_exponent\": 0.04235593297311354, \"step\": 170, \"test_objective\": [{\"annualised_returns\": -0.7651777207771785, \"annualised_returns_over_hodl\": 1.2185708811093319, \"annualised_returns_over_uniform_hodl\": 1.0627137923220742, \"calmar\": -1.0812196757358001, \"daily_log_sharpe\": -1.7793440320889677, \"daily_returns\": 0.009416463909480702, \"fee_revenue_over_value\": 0.22277660622982476, \"jax_sharpe\": -0.2723740904393643, \"return\": -0.4420787347403582, \"returns_over_hodl\": 0.37840490871954824, \"returns_over_uniform_hodl\": 0.33855576047891733, \"sharpe\": -1.3801032425821114, \"sterling\": -1.9769703782995922, \"ulcer\": -0.1484545713871388}], \"train_objective\": [{\"annualised_returns\": 1.0088447538354743, \"annualised_returns_over_hodl\": 0.6612859813158956, \"annualised_returns_over_uniform_hodl\": 0.6612859813158956, \"calmar\": 1.5892963631164936, \"daily_log_sharpe\": 0.8137267900975598, \"daily_returns\": 0.022201983917818705, \"fee_revenue_over_value\": 0.48450789593263394, \"jax_sharpe\": 1.231781459925028, \"return\": 0.5273030636387377, \"returns_over_hodl\": 0.360932262473336, \"returns_over_uniform_hodl\": 0.360932262473336, \"sharpe\": 1.2448886098606131, \"sterling\": 3.9062135977544017, \"ulcer\": -0.13033239572338465}], \"train_return\": 0.5273030636387377, \"train_returns_over_hodl\": 0.360932262473336, \"train_sharpe\": 1.231781459925028, \"validation_return\": 0.04939964712563638, \"validation_returns_over_hodl\": 0.07910890175911955, \"validation_sharpe\": 0.8161130490298384}, {\"centeredness_margin\": 0.20718635641833943, \"continuous_test_metrics\": [{\"annualised_returns\": -0.7981611443155101, \"annualised_returns_over_hodl\": 0.9087703826255338, \"annualised_returns_over_uniform_hodl\": 0.7729824990406606, \"calmar\": -1.128917826825182, \"daily_log_sharpe\": -1.9487912674762098, \"daily_returns\": 0.008734815688875774, \"fee_revenue_over_value\": 0.1607625383567112, \"jax_sharpe\": -0.4490595539814892, \"return\": -0.4750727288767189, \"returns_over_hodl\": 0.29738875407240806, \"returns_over_uniform_hodl\": 0.2593970983836835, \"sharpe\": -1.5510449806188542, \"sterling\": -2.033057992232245, \"ulcer\": -0.15375783272905472}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": 0.8556411955298009, \"optuna_trial_number\": 171, \"price_ratio\": 1.239096239421489, \"shift_exponent\": 0.08560063184797304, \"step\": 171, \"test_objective\": [{\"annualised_returns\": -0.7981611443155101, \"annualised_returns_over_hodl\": 0.9087703826255338, \"annualised_returns_over_uniform_hodl\": 0.7729824990406606, \"calmar\": -1.128917826825182, \"daily_log_sharpe\": -1.9487912674762098, \"daily_returns\": 0.008734815688875774, \"fee_revenue_over_value\": 0.1607625383567112, \"jax_sharpe\": -0.4490595539814892, \"return\": -0.4750727288767189, \"returns_over_hodl\": 0.29738875407240806, \"returns_over_uniform_hodl\": 0.2593970983836835, \"sharpe\": -1.5510449806188542, \"sterling\": -2.033057992232245, \"ulcer\": -0.15375783272905472}], \"train_objective\": [{\"annualised_returns\": 0.8098557623831655, \"annualised_returns_over_hodl\": 0.49672492138095525, \"annualised_returns_over_uniform_hodl\": 0.49672492138095525, \"calmar\": 1.2750939674099724, \"daily_log_sharpe\": 0.6963256803282154, \"daily_returns\": 0.021377397783234806, \"fee_revenue_over_value\": 0.3442845363229488, \"jax_sharpe\": 1.114376411799674, \"return\": 0.4335774494676392, \"returns_over_hodl\": 0.27741628245448924, \"returns_over_uniform_hodl\": 0.27741628245448924, \"sharpe\": 1.1290981181553756, \"sterling\": 3.1221192738242514, \"ulcer\": -0.13014767709537298}], \"train_return\": 0.4335774494676392, \"train_returns_over_hodl\": 0.27741628245448924, \"train_sharpe\": 1.1143764117996737, \"validation_return\": 0.01908992929913178, \"validation_returns_over_hodl\": 0.05434553641274453, \"validation_sharpe\": 0.5174428222934363}, {\"centeredness_margin\": 0.1544541144436466, \"continuous_test_metrics\": [{\"annualised_returns\": -0.8593136755192479, \"annualised_returns_over_hodl\": 0.33724799080504697, \"annualised_returns_over_uniform_hodl\": 0.23580957845222583, \"calmar\": -1.2195743958767784, \"daily_log_sharpe\": -2.2708837163131976, \"daily_returns\": 0.007879119665219009, \"fee_revenue_over_value\": 0.06533555046566543, \"jax_sharpe\": -0.6702732014879437, \"return\": -0.5460909753837417, \"returns_over_hodl\": 0.12416569350004547, \"returns_over_uniform_hodl\": 0.08901125923333719, \"sharpe\": -1.8674258852373606, \"sterling\": -2.109068924448666, \"ulcer\": -0.16448497068970833}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": 0.7204258282376017, \"optuna_trial_number\": 172, \"price_ratio\": 2.589475476938504, \"shift_exponent\": 0.19091759382352616, \"step\": 172, \"test_objective\": [{\"annualised_returns\": -0.8593136755192479, \"annualised_returns_over_hodl\": 0.33724799080504697, \"annualised_returns_over_uniform_hodl\": 0.23580957845222583, \"calmar\": -1.2195743958767784, \"daily_log_sharpe\": -2.2708837163131976, \"daily_returns\": 0.007879119665219009, \"fee_revenue_over_value\": 0.06533555046566543, \"jax_sharpe\": -0.6702732014879437, \"return\": -0.5460909753837417, \"returns_over_hodl\": 0.12416569350004547, \"returns_over_uniform_hodl\": 0.08901125923333719, \"sharpe\": -1.8674258852373606, \"sterling\": -2.109068924448666, \"ulcer\": -0.16448497068970833}], \"train_objective\": [{\"annualised_returns\": 0.5649034229217238, \"annualised_returns_over_hodl\": 0.29415282771325657, \"annualised_returns_over_uniform_hodl\": 0.29415282771325635, \"calmar\": 0.894735008589641, \"daily_log_sharpe\": 0.5451635022523019, \"daily_returns\": 0.02063739260647353, \"fee_revenue_over_value\": 0.12331088748832551, \"jax_sharpe\": 0.9498320158421799, \"return\": 0.3124342183097837, \"returns_over_hodl\": 0.16946931659808628, \"returns_over_uniform_hodl\": 0.16946931659808606, \"sharpe\": 0.9704862054935015, \"sterling\": 2.1781206077636583, \"ulcer\": -0.13071562429362335}], \"train_return\": 0.3124342183097837, \"train_returns_over_hodl\": 0.16946931659808628, \"train_sharpe\": 0.9498320158421799, \"validation_return\": -0.015566017533781062, \"validation_returns_over_hodl\": 0.01524961172658501, \"validation_sharpe\": 0.17846148205660245}, {\"centeredness_margin\": 0.04779020992439273, \"continuous_test_metrics\": [{\"annualised_returns\": -0.7244499739901662, \"annualised_returns_over_hodl\": 1.6860870736701936, \"annualised_returns_over_uniform_hodl\": 1.420472371728652, \"calmar\": -1.1674824105396377, \"daily_log_sharpe\": -1.5688431553519504, \"daily_returns\": 0.010235401122961476, \"fee_revenue_over_value\": 0.26772585355221423, \"jax_sharpe\": -0.5383675519545784, \"return\": -0.40495809659131954, \"returns_over_hodl\": 0.4887538232137787, \"returns_over_uniform_hodl\": 0.42761500077140857, \"sharpe\": -1.1589365823049345, \"sterling\": -1.981599220526297, \"ulcer\": -0.14786138544839136}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": 1.0884823157039598, \"optuna_trial_number\": 173, \"price_ratio\": 1.0644721432147104, \"shift_exponent\": 0.05377650700146635, \"step\": 173, \"test_objective\": [{\"annualised_returns\": -0.7244499739901662, \"annualised_returns_over_hodl\": 1.6860870736701936, \"annualised_returns_over_uniform_hodl\": 1.420472371728652, \"calmar\": -1.1674824105396377, \"daily_log_sharpe\": -1.5688431553519504, \"daily_returns\": 0.010235401122961476, \"fee_revenue_over_value\": 0.26772585355221423, \"jax_sharpe\": -0.5383675519545784, \"return\": -0.40495809659131954, \"returns_over_hodl\": 0.4887538232137787, \"returns_over_uniform_hodl\": 0.42761500077140857, \"sharpe\": -1.1589365823049345, \"sterling\": -1.981599220526297, \"ulcer\": -0.14786138544839136}], \"train_objective\": [{\"annualised_returns\": 1.210982105851885, \"annualised_returns_over_hodl\": 0.8284506905668356, \"annualised_returns_over_uniform_hodl\": 0.8284506905668334, \"calmar\": 1.8888127785174043, \"daily_log_sharpe\": 0.9157556068676282, \"daily_returns\": 0.022517311209392883, \"fee_revenue_over_value\": 0.6352612436384654, \"jax_sharpe\": 1.3362790667808557, \"return\": 0.6188442265392515, \"returns_over_hodl\": 0.4425017458991256, \"returns_over_uniform_hodl\": 0.4425017458991245, \"sharpe\": 1.349991727121219, \"sterling\": 4.6363668697645695, \"ulcer\": -0.13026798873674333}], \"train_return\": 0.6188442265392515, \"train_returns_over_hodl\": 0.4425017458991256, \"train_sharpe\": 1.3362790667808555, \"validation_return\": 0.05349001755034277, \"validation_returns_over_hodl\": 0.09802286854687448, \"validation_sharpe\": 0.8515122265817112}, {\"centeredness_margin\": 0.03202746372134965, \"continuous_test_metrics\": [{\"annualised_returns\": -0.7424994388421088, \"annualised_returns_over_hodl\": 1.5268209142838693, \"annualised_returns_over_uniform_hodl\": 1.2619231905463728, \"calmar\": -1.0457834096559429, \"daily_log_sharpe\": -1.646585191989469, \"daily_returns\": 0.009883226992992227, \"fee_revenue_over_value\": 0.23221749425177363, \"jax_sharpe\": -0.2351417880168765, \"return\": -0.4209739715642705, \"returns_over_hodl\": 0.4525529173208023, \"returns_over_uniform_hodl\": 0.3891899701460262, \"sharpe\": -1.2380157007778232, \"sterling\": -1.9072899129025085, \"ulcer\": -0.15050685447127518}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": 1.0129350806019712, \"optuna_trial_number\": 174, \"price_ratio\": 1.095497958129864, \"shift_exponent\": 0.11811603543645174, \"step\": 174, \"test_objective\": [{\"annualised_returns\": -0.7424994388421088, \"annualised_returns_over_hodl\": 1.5268209142838693, \"annualised_returns_over_uniform_hodl\": 1.2619231905463728, \"calmar\": -1.0457834096559429, \"daily_log_sharpe\": -1.646585191989469, \"daily_returns\": 0.009883226992992227, \"fee_revenue_over_value\": 0.23221749425177363, \"jax_sharpe\": -0.2351417880168765, \"return\": -0.4209739715642705, \"returns_over_hodl\": 0.4525529173208023, \"returns_over_uniform_hodl\": 0.3891899701460262, \"sharpe\": -1.2380157007778232, \"sterling\": -1.9072899129025085, \"ulcer\": -0.15050685447127518}], \"train_objective\": [{\"annualised_returns\": 1.0409210113618306, \"annualised_returns_over_hodl\": 0.6878125891385614, \"annualised_returns_over_uniform_hodl\": 0.6878125891385618, \"calmar\": 1.603838078661811, \"daily_log_sharpe\": 0.8290374487289235, \"daily_returns\": 0.022272198735437114, \"fee_revenue_over_value\": 0.5608527295420934, \"jax_sharpe\": 1.2483504682008164, \"return\": 0.5420629745204175, \"returns_over_hodl\": 0.3740843600421393, \"returns_over_uniform_hodl\": 0.3740843600421395, \"sharpe\": 1.2618290534283105, \"sterling\": 3.981515618659409, \"ulcer\": -0.13191578769238946}], \"train_return\": 0.5420629745204175, \"train_returns_over_hodl\": 0.3740843600421393, \"train_sharpe\": 1.2483504682008164, \"validation_return\": 0.03614088383451164, \"validation_returns_over_hodl\": 0.0862830132544894, \"validation_sharpe\": 0.6846700164077891}, {\"centeredness_margin\": 0.03152838928801929, \"continuous_test_metrics\": [{\"annualised_returns\": -0.7195791651873764, \"annualised_returns_over_hodl\": 1.7512677568409725, \"annualised_returns_over_uniform_hodl\": 1.4632582799931075, \"calmar\": -1.013698194659194, \"daily_log_sharpe\": -1.5484552119514108, \"daily_returns\": 0.010430878264954998, \"fee_revenue_over_value\": 0.27563062912643255, \"jax_sharpe\": -0.20846126456261355, \"return\": -0.4007441074638243, \"returns_over_hodl\": 0.5031990989793336, \"returns_over_uniform_hodl\": 0.4377251359686738, \"sharpe\": -1.137857921934673, \"sterling\": -1.8568926017122576, \"ulcer\": -0.1474633047561344}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": 1.1053994578801596, \"optuna_trial_number\": 175, \"price_ratio\": 1.0582108421631593, \"shift_exponent\": 0.05355985715203052, \"step\": 175, \"test_objective\": [{\"annualised_returns\": -0.7195791651873764, \"annualised_returns_over_hodl\": 1.7512677568409725, \"annualised_returns_over_uniform_hodl\": 1.4632582799931075, \"calmar\": -1.013698194659194, \"daily_log_sharpe\": -1.5484552119514108, \"daily_returns\": 0.010430878264954998, \"fee_revenue_over_value\": 0.27563062912643255, \"jax_sharpe\": -0.20846126456261355, \"return\": -0.4007441074638243, \"returns_over_hodl\": 0.5031990989793336, \"returns_over_uniform_hodl\": 0.4377251359686738, \"sharpe\": -1.137857921934673, \"sterling\": -1.8568926017122576, \"ulcer\": -0.1474633047561344}], \"train_objective\": [{\"annualised_returns\": 1.2518053082214236, \"annualised_returns_over_hodl\": 0.8622108971131313, \"annualised_returns_over_uniform_hodl\": 0.8622108971131333, \"calmar\": 1.9504855191617205, \"daily_log_sharpe\": 0.9349283733009298, \"daily_returns\": 0.022695201454974962, \"fee_revenue_over_value\": 0.6634905559190317, \"jax_sharpe\": 1.3559519898169126, \"return\": 0.6369258648940137, \"returns_over_hodl\": 0.45861373151692497, \"returns_over_uniform_hodl\": 0.45861373151692586, \"sharpe\": 1.3696711273661324, \"sterling\": 4.774631257945641, \"ulcer\": -0.13036231405997573}], \"train_return\": 0.6369258648940137, \"train_returns_over_hodl\": 0.45861373151692497, \"train_sharpe\": 1.3559519898169126, \"validation_return\": 0.057828597330493725, \"validation_returns_over_hodl\": 0.10284257982916523, \"validation_sharpe\": 0.8922985942924611}, {\"centeredness_margin\": 0.018651446047314772, \"continuous_test_metrics\": [{\"annualised_returns\": -0.8587799876029267, \"annualised_returns_over_hodl\": 0.3422008650330006, \"annualised_returns_over_uniform_hodl\": 0.24049757240848302, \"calmar\": -1.216406338124867, \"daily_log_sharpe\": -2.159453047075721, \"daily_returns\": 0.007869864308014656, \"fee_revenue_over_value\": 0.06687819814863113, \"jax_sharpe\": -0.6107014995500927, \"return\": -0.5453982909193313, \"returns_over_hodl\": 0.1258407073970338, \"returns_over_uniform_hodl\": 0.09067313670201393, \"sharpe\": -1.7353164537377082, \"sterling\": -2.059414649617973, \"ulcer\": -0.16884525865058742}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": 0.7157242208381184, \"optuna_trial_number\": 176, \"price_ratio\": 2.6614782341092953, \"shift_exponent\": 0.06330558818594247, \"step\": 176, \"test_objective\": [{\"annualised_returns\": -0.8587799876029267, \"annualised_returns_over_hodl\": 0.3422008650330006, \"annualised_returns_over_uniform_hodl\": 0.24049757240848302, \"calmar\": -1.216406338124867, \"daily_log_sharpe\": -2.159453047075721, \"daily_returns\": 0.007869864308014656, \"fee_revenue_over_value\": 0.06687819814863113, \"jax_sharpe\": -0.6107014995500927, \"return\": -0.5453982909193313, \"returns_over_hodl\": 0.1258407073970338, \"returns_over_uniform_hodl\": 0.09067313670201393, \"sharpe\": -1.7353164537377082, \"sterling\": -2.059414649617973, \"ulcer\": -0.16884525865058742}], \"train_objective\": [{\"annualised_returns\": 0.5563793507137837, \"annualised_returns_over_hodl\": 0.28710354148258177, \"annualised_returns_over_uniform_hodl\": 0.28710354148258177, \"calmar\": 0.880771706312327, \"daily_log_sharpe\": 0.5389066659434509, \"daily_returns\": 0.020622211774690605, \"fee_revenue_over_value\": 0.12042699286833904, \"jax_sharpe\": 0.9435634248022059, \"return\": 0.30808933333005206, \"returns_over_hodl\": 0.16559772471404655, \"returns_over_uniform_hodl\": 0.16559772471404655, \"sharpe\": 0.9641797322574355, \"sterling\": 2.144553815256718, \"ulcer\": -0.13083110142782173}], \"train_return\": 0.30808933333005206, \"train_returns_over_hodl\": 0.16559772471404655, \"train_sharpe\": 0.9435634248022059, \"validation_return\": -0.015138882015408694, \"validation_returns_over_hodl\": 0.014851310528263939, \"validation_sharpe\": 0.18188317190723796}, {\"centeredness_margin\": 0.013852667840537698, \"continuous_test_metrics\": [{\"annualised_returns\": -0.7925773703352292, \"annualised_returns_over_hodl\": 1.0498135074769945, \"annualised_returns_over_uniform_hodl\": 0.8220311993618308, \"calmar\": -1.1150093543883317, \"daily_log_sharpe\": -1.809748644861773, \"daily_returns\": 0.009382177163587512, \"fee_revenue_over_value\": 0.17327783961159343, \"jax_sharpe\": -0.3966966707351925, \"return\": -0.46927184548387324, \"returns_over_hodl\": 0.33517797337346167, \"returns_over_uniform_hodl\": 0.2733144848768232, \"sharpe\": -1.3887508035076097, \"sterling\": -1.9598049282703245, \"ulcer\": -0.16023875915042646}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": 0.922446341025371, \"optuna_trial_number\": 177, \"price_ratio\": 1.1986811911287314, \"shift_exponent\": 0.05033571449498018, \"step\": 177, \"test_objective\": [{\"annualised_returns\": -0.7925773703352292, \"annualised_returns_over_hodl\": 1.0498135074769945, \"annualised_returns_over_uniform_hodl\": 0.8220311993618308, \"calmar\": -1.1150093543883317, \"daily_log_sharpe\": -1.809748644861773, \"daily_returns\": 0.009382177163587512, \"fee_revenue_over_value\": 0.17327783961159343, \"jax_sharpe\": -0.3966966707351925, \"return\": -0.46927184548387324, \"returns_over_hodl\": 0.33517797337346167, \"returns_over_uniform_hodl\": 0.2733144848768232, \"sharpe\": -1.3887508035076097, \"sterling\": -1.9598049282703245, \"ulcer\": -0.16023875915042646}], \"train_objective\": [{\"annualised_returns\": 1.1316744416353108, \"annualised_returns_over_hodl\": 0.7628643825545549, \"annualised_returns_over_uniform_hodl\": 0.7628643825545549, \"calmar\": 1.857364122948793, \"daily_log_sharpe\": 0.8678635457583664, \"daily_returns\": 0.021574533008274725, \"fee_revenue_over_value\": 0.40453657884280997, \"jax_sharpe\": 1.2955269372090197, \"return\": 0.5833374163226661, \"returns_over_hodl\": 0.41086273153995556, \"returns_over_uniform_hodl\": 0.41086273153995556, \"sharpe\": 1.310855313530614, \"sterling\": 4.401658428675427, \"ulcer\": -0.1251713620562466}], \"train_return\": 0.5833374163226661, \"train_returns_over_hodl\": 0.41086273153995556, \"train_sharpe\": 1.2955269372090197, \"validation_return\": 0.0031358835379737826, \"validation_returns_over_hodl\": 0.06152393382815391, \"validation_sharpe\": 0.37320639973348974}, {\"centeredness_margin\": 0.019430884775145436, \"continuous_test_metrics\": [{\"annualised_returns\": -0.7070673465079518, \"annualised_returns_over_hodl\": 1.8797074552469866, \"annualised_returns_over_uniform_hodl\": 1.5731639543716152, \"calmar\": -0.9962543155014331, \"daily_log_sharpe\": -1.5245248680244239, \"daily_returns\": 0.010584404178255912, \"fee_revenue_over_value\": 0.29575439845600565, \"jax_sharpe\": -0.18748808276041312, \"return\": -0.39011603007952345, \"returns_over_hodl\": 0.5310767550096773, \"returns_over_uniform_hodl\": 0.4632238489437943, \"sharpe\": -1.1202427710134435, \"sterling\": -1.850568054179395, \"ulcer\": -0.1447550858514001}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": 1.0245758511450849, \"optuna_trial_number\": 178, \"price_ratio\": 1.0417044995324451, \"shift_exponent\": 0.1420308951940259, \"step\": 178, \"test_objective\": [{\"annualised_returns\": -0.7070673465079518, \"annualised_returns_over_hodl\": 1.8797074552469866, \"annualised_returns_over_uniform_hodl\": 1.5731639543716152, \"calmar\": -0.9962543155014331, \"daily_log_sharpe\": -1.5245248680244239, \"daily_returns\": 0.010584404178255912, \"fee_revenue_over_value\": 0.29575439845600565, \"jax_sharpe\": -0.18748808276041312, \"return\": -0.39011603007952345, \"returns_over_hodl\": 0.5310767550096773, \"returns_over_uniform_hodl\": 0.4632238489437943, \"sharpe\": -1.1202427710134435, \"sterling\": -1.850568054179395, \"ulcer\": -0.1447550858514001}], \"train_objective\": [{\"annualised_returns\": 0.89558274071601, \"annualised_returns_over_hodl\": 0.5676199106791615, \"annualised_returns_over_uniform_hodl\": 0.5676199106791624, \"calmar\": 1.2890542531905542, \"daily_log_sharpe\": 0.7290669070024883, \"daily_returns\": 0.022337258574664596, \"fee_revenue_over_value\": 0.7209708080512947, \"jax_sharpe\": 1.162301204245147, \"return\": 0.47442796876993376, \"returns_over_hodl\": 0.3138169097961441, \"returns_over_uniform_hodl\": 0.3138169097961445, \"sharpe\": 1.1684791323572021, \"sterling\": 3.266088702504301, \"ulcer\": -0.1427715071984947}], \"train_return\": 0.47442796876993376, \"train_returns_over_hodl\": 0.3138169097961441, \"train_sharpe\": 1.162301204245147, \"validation_return\": 0.0623322471130594, \"validation_returns_over_hodl\": 0.10520126562539756, \"validation_sharpe\": 0.9354591069179528}, {\"centeredness_margin\": 0.020433315916385267, \"continuous_test_metrics\": [{\"annualised_returns\": -0.7402320529517668, \"annualised_returns_over_hodl\": 1.5594431639962094, \"annualised_returns_over_uniform_hodl\": 1.2818402451120825, \"calmar\": -1.0421612928706856, \"daily_log_sharpe\": -1.6089194578281767, \"daily_returns\": 0.010071731258827715, \"fee_revenue_over_value\": 0.23837822433684583, \"jax_sharpe\": -0.2553177594736288, \"return\": -0.4189259732385515, \"returns_over_hodl\": 0.46007655113975643, \"returns_over_uniform_hodl\": 0.3941034949156308, \"sharpe\": -1.192292838837242, \"sterling\": -1.8780834887514835, \"ulcer\": -0.15239013904203358}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": 1.0546867841204324, \"optuna_trial_number\": 179, \"price_ratio\": 1.0905996860243765, \"shift_exponent\": 0.05180389162015129, \"step\": 179, \"test_objective\": [{\"annualised_returns\": -0.7402320529517668, \"annualised_returns_over_hodl\": 1.5594431639962094, \"annualised_returns_over_uniform_hodl\": 1.2818402451120825, \"calmar\": -1.0421612928706856, \"daily_log_sharpe\": -1.6089194578281767, \"daily_returns\": 0.010071731258827715, \"fee_revenue_over_value\": 0.23837822433684583, \"jax_sharpe\": -0.2553177594736288, \"return\": -0.4189259732385515, \"returns_over_hodl\": 0.46007655113975643, \"returns_over_uniform_hodl\": 0.3941034949156308, \"sharpe\": -1.192292838837242, \"sterling\": -1.8780834887514835, \"ulcer\": -0.15239013904203358}], \"train_objective\": [{\"annualised_returns\": 1.2522731515294478, \"annualised_returns_over_hodl\": 0.8625977968611558, \"annualised_returns_over_uniform_hodl\": 0.8625977968611542, \"calmar\": 2.0105949655853803, \"daily_log_sharpe\": 0.9363194589048994, \"daily_returns\": 0.02252530610779916, \"fee_revenue_over_value\": 0.5760951499421596, \"jax_sharpe\": 1.358381304804852, \"return\": 0.637132334629462, \"returns_over_hodl\": 0.4587977102771905, \"returns_over_uniform_hodl\": 0.45879771027718985, \"sharpe\": 1.372617094922867, \"sterling\": 4.8663316230309706, \"ulcer\": -0.12708077993182712}], \"train_return\": 0.637132334629462, \"train_returns_over_hodl\": 0.4587977102771905, \"train_sharpe\": 1.3583813048048519, \"validation_return\": 0.03712939080915745, \"validation_returns_over_hodl\": 0.08829855830411071, \"validation_sharpe\": 0.6940412556329948}, {\"centeredness_margin\": 0.06375959967519026, \"continuous_test_metrics\": [{\"annualised_returns\": -0.6659175723402104, \"annualised_returns_over_hodl\": 2.3346018203311556, \"annualised_returns_over_uniform_hodl\": 1.9346296849984612, \"calmar\": -0.9379538462387026, \"daily_log_sharpe\": -1.3418038056295314, \"daily_returns\": 0.009517381342130817, \"fee_revenue_over_value\": 0.3220118525949847, \"jax_sharpe\": -0.041148716544968364, \"return\": -0.35696020786520877, \"returns_over_hodl\": 0.6242377322004098, \"returns_over_uniform_hodl\": 0.5427707663708115, \"sharpe\": -0.9203170817617874, \"sterling\": -1.7196800337643612, \"ulcer\": -0.14652853001402155}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": 0.9000526206854534, \"optuna_trial_number\": 180, \"price_ratio\": 1.046112255170618, \"shift_exponent\": 0.011159178142000112, \"step\": 180, \"test_objective\": [{\"annualised_returns\": -0.6659175723402104, \"annualised_returns_over_hodl\": 2.3346018203311556, \"annualised_returns_over_uniform_hodl\": 1.9346296849984612, \"calmar\": -0.9379538462387026, \"daily_log_sharpe\": -1.3418038056295314, \"daily_returns\": 0.009517381342130817, \"fee_revenue_over_value\": 0.3220118525949847, \"jax_sharpe\": -0.041148716544968364, \"return\": -0.35696020786520877, \"returns_over_hodl\": 0.6242377322004098, \"returns_over_uniform_hodl\": 0.5427707663708115, \"sharpe\": -0.9203170817617874, \"sterling\": -1.7196800337643612, \"ulcer\": -0.14652853001402155}], \"train_objective\": [{\"annualised_returns\": 1.0887168979892503, \"annualised_returns_over_hodl\": 0.7273391061912555, \"annualised_returns_over_uniform_hodl\": 0.7273391061912569, \"calmar\": 1.8424069021630778, \"daily_log_sharpe\": 0.8595380709448714, \"daily_returns\": 0.021501136648945346, \"fee_revenue_over_value\": 0.46007176025351715, \"jax_sharpe\": 1.2687903228603477, \"return\": 0.5638883117889821, \"returns_over_hodl\": 0.39353223933689074, \"returns_over_uniform_hodl\": 0.3935322393368914, \"sharpe\": 1.304062556450815, \"sterling\": 4.187696469984783, \"ulcer\": -0.12470916447456962}], \"train_return\": 0.5638883117889821, \"train_returns_over_hodl\": 0.39353223933689074, \"train_sharpe\": 1.2687903228603474, \"validation_return\": 0.011533216781747901, \"validation_returns_over_hodl\": 0.08755718568351556, \"validation_sharpe\": 0.4633154915780362}, {\"centeredness_margin\": 0.012122997449159695, \"continuous_test_metrics\": [{\"annualised_returns\": -0.6744523927484549, \"annualised_returns_over_hodl\": 2.192202885762869, \"annualised_returns_over_uniform_hodl\": 1.8596585543657804, \"calmar\": -1.16436461876601, \"daily_log_sharpe\": -1.4111177674694098, \"daily_returns\": 0.011554866520116926, \"fee_revenue_over_value\": 0.37139694843245113, \"jax_sharpe\": -0.5067030086786972, \"return\": -0.36362746523510714, \"returns_over_hodl\": 0.5959390638333795, \"returns_over_uniform_hodl\": 0.5267747893131529, \"sharpe\": -1.0066873345933327, \"sterling\": -1.9267903189961035, \"ulcer\": -0.14353913670182156}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": 1.070766999911217, \"optuna_trial_number\": 181, \"price_ratio\": 1.0119967656853917, \"shift_exponent\": 0.14662423357137624, \"step\": 181, \"test_objective\": [{\"annualised_returns\": -0.6744523927484549, \"annualised_returns_over_hodl\": 2.192202885762869, \"annualised_returns_over_uniform_hodl\": 1.8596585543657804, \"calmar\": -1.16436461876601, \"daily_log_sharpe\": -1.4111177674694098, \"daily_returns\": 0.011554866520116926, \"fee_revenue_over_value\": 0.37139694843245113, \"jax_sharpe\": -0.5067030086786972, \"return\": -0.36362746523510714, \"returns_over_hodl\": 0.5959390638333795, \"returns_over_uniform_hodl\": 0.5267747893131529, \"sharpe\": -1.0066873345933327, \"sterling\": -1.9267903189961035, \"ulcer\": -0.14353913670182156}], \"train_objective\": [{\"annualised_returns\": 0.7669497608458897, \"annualised_returns_over_hodl\": 0.46124226960703885, \"annualised_returns_over_uniform_hodl\": 0.46124226960704306, \"calmar\": 1.068453439397556, \"daily_log_sharpe\": 0.6367842750920609, \"daily_returns\": 0.025319953466622233, \"fee_revenue_over_value\": 0.9324233904717715, \"jax_sharpe\": 1.0833662376202517, \"return\": 0.4128468889554915, \"returns_over_hodl\": 0.2589439246809606, \"returns_over_uniform_hodl\": 0.25894392468096283, \"sharpe\": 1.0793167951834697, \"sterling\": 2.7316404476606833, \"ulcer\": -0.15003938900789285}], \"train_return\": 0.4128468889554915, \"train_returns_over_hodl\": 0.2589439246809606, \"train_sharpe\": 1.0833662376202517, \"validation_return\": 0.08188422266604167, \"validation_returns_over_hodl\": 0.1117861074639781, \"validation_sharpe\": 1.1203368696573779}, {\"centeredness_margin\": 0.020619571873845735, \"continuous_test_metrics\": [{\"annualised_returns\": -0.8474312454826349, \"annualised_returns_over_hodl\": 0.5075200558846427, \"annualised_returns_over_uniform_hodl\": 0.3401866094730608, \"calmar\": -1.1915087283706578, \"daily_log_sharpe\": -2.0747661752635693, \"daily_returns\": 0.00861188248163143, \"fee_revenue_over_value\": 0.09444360903692589, \"jax_sharpe\": -0.5886367282688741, \"return\": -0.5310238971670751, \"returns_over_hodl\": 0.17975898161808512, \"returns_over_uniform_hodl\": 0.12515995188286322, \"sharpe\": -1.6488959039759699, \"sterling\": -2.038947545885573, \"ulcer\": -0.16896830554572217}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": 0.8354414328178837, \"optuna_trial_number\": 182, \"price_ratio\": 1.7166085732886391, \"shift_exponent\": 0.047915114402539434, \"step\": 182, \"test_objective\": [{\"annualised_returns\": -0.8474312454826349, \"annualised_returns_over_hodl\": 0.5075200558846427, \"annualised_returns_over_uniform_hodl\": 0.3401866094730608, \"calmar\": -1.1915087283706578, \"daily_log_sharpe\": -2.0747661752635693, \"daily_returns\": 0.00861188248163143, \"fee_revenue_over_value\": 0.09444360903692589, \"jax_sharpe\": -0.5886367282688741, \"return\": -0.5310238971670751, \"returns_over_hodl\": 0.17975898161808512, \"returns_over_uniform_hodl\": 0.12515995188286322, \"sharpe\": -1.6488959039759699, \"sterling\": -2.038947545885573, \"ulcer\": -0.16896830554572217}], \"train_objective\": [{\"annualised_returns\": 0.8051160932132146, \"annualised_returns_over_hodl\": 0.49280528252729017, \"annualised_returns_over_uniform_hodl\": 0.49280528252729017, \"calmar\": 1.2972766125591562, \"daily_log_sharpe\": 0.708236392268572, \"daily_returns\": 0.02077313533625978, \"fee_revenue_over_value\": 0.19891546987052242, \"jax_sharpe\": 1.113858730118779, \"return\": 0.43129697934259226, \"returns_over_hodl\": 0.27538422644630645, \"returns_over_uniform_hodl\": 0.27538422644630645, \"sharpe\": 1.1349887768332299, \"sterling\": 3.13346966271402, \"ulcer\": -0.12780467461972825}], \"train_return\": 0.43129697934259226, \"train_returns_over_hodl\": 0.27538422644630645, \"train_sharpe\": 1.113858730118779, \"validation_return\": -0.027409745430418853, \"validation_returns_over_hodl\": 0.02522821815232601, \"validation_sharpe\": 0.08987525311726942}, {\"centeredness_margin\": 0.041688260350534814, \"continuous_test_metrics\": [{\"annualised_returns\": -0.8177168947967486, \"annualised_returns_over_hodl\": 0.8078389434887912, \"annualised_returns_over_uniform_hodl\": 0.6012018810756021, \"calmar\": -1.1498604423196677, \"daily_log_sharpe\": -1.9061524579481346, \"daily_returns\": 0.008526649967370762, \"fee_revenue_over_value\": 0.13188240053825115, \"jax_sharpe\": -0.4592647661321758, \"return\": -0.4961809072714978, \"returns_over_hodl\": 0.26931073473633504, \"returns_over_uniform_hodl\": 0.20875469497860988, \"sharpe\": -1.4794857818049598, \"sterling\": -1.9906553686892643, \"ulcer\": -0.16505620674539798}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": 0.925746862535153, \"optuna_trial_number\": 183, \"price_ratio\": 1.3088860847749186, \"shift_exponent\": 0.011260374396117477, \"step\": 183, \"test_objective\": [{\"annualised_returns\": -0.8177168947967486, \"annualised_returns_over_hodl\": 0.8078389434887912, \"annualised_returns_over_uniform_hodl\": 0.6012018810756021, \"calmar\": -1.1498604423196677, \"daily_log_sharpe\": -1.9061524579481346, \"daily_returns\": 0.008526649967370762, \"fee_revenue_over_value\": 0.13188240053825115, \"jax_sharpe\": -0.4592647661321758, \"return\": -0.4961809072714978, \"returns_over_hodl\": 0.26931073473633504, \"returns_over_uniform_hodl\": 0.20875469497860988, \"sharpe\": -1.4794857818049598, \"sterling\": -1.9906553686892643, \"ulcer\": -0.16505620674539798}], \"train_objective\": [{\"annualised_returns\": 1.127316783057429, \"annualised_returns_over_hodl\": 0.7592606610161068, \"annualised_returns_over_uniform_hodl\": 0.7592606610161068, \"calmar\": 1.8693976000222878, \"daily_log_sharpe\": 0.8828554081829157, \"daily_returns\": 0.02117252823814159, \"fee_revenue_over_value\": 0.2995062490610088, \"jax_sharpe\": 1.29752657916412, \"return\": 0.5813715415131604, \"returns_over_hodl\": 0.4091110016338648, \"returns_over_uniform_hodl\": 0.4091110016338648, \"sharpe\": 1.317838312704735, \"sterling\": 4.432686417106546, \"ulcer\": -0.12339099865447713}], \"train_return\": 0.5813715415131604, \"train_returns_over_hodl\": 0.4091110016338648, \"train_sharpe\": 1.2975265791641197, \"validation_return\": -0.020738374584018837, \"validation_returns_over_hodl\": 0.04388098465205559, \"validation_sharpe\": 0.14860522172532248}, {\"centeredness_margin\": 0.013384923889175372, \"continuous_test_metrics\": [{\"annualised_returns\": -0.8578642897835672, \"annualised_returns_over_hodl\": 0.3775807040416097, \"annualised_returns_over_uniform_hodl\": 0.2485411981148844, \"calmar\": -1.2085064090489472, \"daily_log_sharpe\": -2.1383884132859334, \"daily_returns\": 0.008181641332588396, \"fee_revenue_over_value\": 0.07519559936670178, \"jax_sharpe\": -0.6145024516235078, \"return\": -0.544213421368396, \"returns_over_hodl\": 0.13769984336825836, \"returns_over_uniform_hodl\": 0.09351585674438878, \"sharpe\": -1.7118488895441524, \"sterling\": -2.0535075870985144, \"ulcer\": -0.1702292828698529}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": 0.7531952791042359, \"optuna_trial_number\": 184, \"price_ratio\": 2.211607092942986, \"shift_exponent\": 0.057404928250015926, \"step\": 184, \"test_objective\": [{\"annualised_returns\": -0.8578642897835672, \"annualised_returns_over_hodl\": 0.3775807040416097, \"annualised_returns_over_uniform_hodl\": 0.2485411981148844, \"calmar\": -1.2085064090489472, \"daily_log_sharpe\": -2.1383884132859334, \"daily_returns\": 0.008181641332588396, \"fee_revenue_over_value\": 0.07519559936670178, \"jax_sharpe\": -0.6145024516235078, \"return\": -0.544213421368396, \"returns_over_hodl\": 0.13769984336825836, \"returns_over_uniform_hodl\": 0.09351585674438878, \"sharpe\": -1.7118488895441524, \"sterling\": -2.0535075870985144, \"ulcer\": -0.1702292828698529}], \"train_objective\": [{\"annualised_returns\": 0.6289178903133241, \"annualised_returns_over_hodl\": 0.34709187991030777, \"annualised_returns_over_uniform_hodl\": 0.3470918799103073, \"calmar\": 1.0008177401411564, \"daily_log_sharpe\": 0.5910921623665207, \"daily_returns\": 0.02066684644823181, \"fee_revenue_over_value\": 0.14359282716780955, \"jax_sharpe\": 0.9958626524054027, \"return\": 0.34477160901366655, \"returns_over_hodl\": 0.19828415979513303, \"returns_over_uniform_hodl\": 0.1982841597951328, \"sharpe\": 1.016745324097335, \"sterling\": 2.4310552334052944, \"ulcer\": -0.1299114627934095}], \"train_return\": 0.34477160901366655, \"train_returns_over_hodl\": 0.19828415979513303, \"train_sharpe\": 0.9958626524054027, \"validation_return\": -0.019122212538971284, \"validation_returns_over_hodl\": 0.01741157050397435, \"validation_sharpe\": 0.15155009152761637}, {\"centeredness_margin\": 0.018103167854065808, \"continuous_test_metrics\": [{\"annualised_returns\": -0.8492919901267731, \"annualised_returns_over_hodl\": 0.4906826170052554, \"annualised_returns_over_uniform_hodl\": 0.3238415520357669, \"calmar\": -1.1939826685130948, \"daily_log_sharpe\": -2.090169205947062, \"daily_returns\": 0.008582456577517843, \"fee_revenue_over_value\": 0.09141674218132469, \"jax_sharpe\": -0.5983310707004628, \"return\": -0.5333358741723542, \"returns_over_hodl\": 0.17443442526229358, \"returns_over_uniform_hodl\": 0.11961309369478013, \"sharpe\": -1.6653982494962367, \"sterling\": -2.044959454265333, \"ulcer\": -0.16903871621520555}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": 0.8201679680604951, \"optuna_trial_number\": 185, \"price_ratio\": 1.7802850125796787, \"shift_exponent\": 0.07086828499373286, \"step\": 185, \"test_objective\": [{\"annualised_returns\": -0.8492919901267731, \"annualised_returns_over_hodl\": 0.4906826170052554, \"annualised_returns_over_uniform_hodl\": 0.3238415520357669, \"calmar\": -1.1939826685130948, \"daily_log_sharpe\": -2.090169205947062, \"daily_returns\": 0.008582456577517843, \"fee_revenue_over_value\": 0.09141674218132469, \"jax_sharpe\": -0.5983310707004628, \"return\": -0.5333358741723542, \"returns_over_hodl\": 0.17443442526229358, \"returns_over_uniform_hodl\": 0.11961309369478013, \"sharpe\": -1.6653982494962367, \"sterling\": -2.044959454265333, \"ulcer\": -0.16903871621520555}], \"train_objective\": [{\"annualised_returns\": 0.7703002687138594, \"annualised_returns_over_hodl\": 0.4640130918623311, \"annualised_returns_over_uniform_hodl\": 0.4640130918623311, \"calmar\": 1.2378454851140954, \"daily_log_sharpe\": 0.6860734548884189, \"daily_returns\": 0.020748053590305463, \"fee_revenue_over_value\": 0.18819802713716155, \"jax_sharpe\": 1.0914874023034218, \"return\": 0.4144727948765048, \"returns_over_hodl\": 0.26039271888319626, \"returns_over_uniform_hodl\": 0.26039271888319626, \"sharpe\": 1.1125841663982192, \"sterling\": 2.993509392220085, \"ulcer\": -0.12823836179343298}], \"train_return\": 0.4144727948765048, \"train_returns_over_hodl\": 0.26039271888319626, \"train_sharpe\": 1.0914874023034218, \"validation_return\": -0.02662559377636453, \"validation_returns_over_hodl\": 0.022939319984963902, \"validation_sharpe\": 0.09740834199912494}, {\"centeredness_margin\": 0.04165241505459771, \"continuous_test_metrics\": [{\"annualised_returns\": -0.8577705821747527, \"annualised_returns_over_hodl\": 0.3750919313023984, \"annualised_returns_over_uniform_hodl\": 0.24936433967447935, \"calmar\": -1.2089092404125206, \"daily_log_sharpe\": -2.1685931128653135, \"daily_returns\": 0.008152451205722373, \"fee_revenue_over_value\": 0.07398767850061863, \"jax_sharpe\": -0.6434302379615879, \"return\": -0.544092425617295, \"returns_over_hodl\": 0.13687160989692715, \"returns_over_uniform_hodl\": 0.09380614781181063, \"sharpe\": -1.7484731754739093, \"sterling\": -2.06704189589436, \"ulcer\": -0.16876503147897992}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": 0.7489884513159214, \"optuna_trial_number\": 186, \"price_ratio\": 2.251804078359862, \"shift_exponent\": 0.12902991218142387, \"step\": 186, \"test_objective\": [{\"annualised_returns\": -0.8577705821747527, \"annualised_returns_over_hodl\": 0.3750919313023984, \"annualised_returns_over_uniform_hodl\": 0.24936433967447935, \"calmar\": -1.2089092404125206, \"daily_log_sharpe\": -2.1685931128653135, \"daily_returns\": 0.008152451205722373, \"fee_revenue_over_value\": 0.07398767850061863, \"jax_sharpe\": -0.6434302379615879, \"return\": -0.544092425617295, \"returns_over_hodl\": 0.13687160989692715, \"returns_over_uniform_hodl\": 0.09380614781181063, \"sharpe\": -1.7484731754739093, \"sterling\": -2.06704189589436, \"ulcer\": -0.16876503147897992}], \"train_objective\": [{\"annualised_returns\": 0.6200143966428426, \"annualised_returns_over_hodl\": 0.3397288175376354, \"annualised_returns_over_uniform_hodl\": 0.3397288175376354, \"calmar\": 0.9858793364931151, \"daily_log_sharpe\": 0.5848226744451339, \"daily_returns\": 0.020635680976083922, \"fee_revenue_over_value\": 0.14089580189426473, \"jax_sharpe\": 0.98956930739564, \"return\": 0.34030423237907503, \"returns_over_hodl\": 0.1943034194068105, \"returns_over_uniform_hodl\": 0.1943034194068105, \"sharpe\": 1.010426101991298, \"sterling\": 2.3958732951122963, \"ulcer\": -0.1300166822641509}], \"train_return\": 0.34030423237907503, \"train_returns_over_hodl\": 0.1943034194068105, \"train_sharpe\": 0.98956930739564, \"validation_return\": -0.018601519387539955, \"validation_returns_over_hodl\": 0.01714247564878435, \"validation_sharpe\": 0.15554219568604985}, {\"centeredness_margin\": 0.055474474101397844, \"continuous_test_metrics\": [{\"annualised_returns\": -0.7337567947360457, \"annualised_returns_over_hodl\": 1.618641548290773, \"annualised_returns_over_uniform_hodl\": 1.3387198754206726, \"calmar\": -1.033476526960911, \"daily_log_sharpe\": -1.581542132819663, \"daily_returns\": 0.009940817164918425, \"fee_revenue_over_value\": 0.25316806799315966, \"jax_sharpe\": -0.23695972691841766, \"return\": -0.4131353812186386, \"returns_over_hodl\": 0.47358448441553613, \"returns_over_uniform_hodl\": 0.4079961905117211, \"sharpe\": -1.1643944043975916, \"sterling\": -1.869119278941171, \"ulcer\": -0.15059210842529094}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": 1.0112252659240393, \"optuna_trial_number\": 187, \"price_ratio\": 1.0796651898309255, \"shift_exponent\": 0.024314700146045004, \"step\": 187, \"test_objective\": [{\"annualised_returns\": -0.7337567947360457, \"annualised_returns_over_hodl\": 1.618641548290773, \"annualised_returns_over_uniform_hodl\": 1.3387198754206726, \"calmar\": -1.033476526960911, \"daily_log_sharpe\": -1.581542132819663, \"daily_returns\": 0.009940817164918425, \"fee_revenue_over_value\": 0.25316806799315966, \"jax_sharpe\": -0.23695972691841766, \"return\": -0.4131353812186386, \"returns_over_hodl\": 0.47358448441553613, \"returns_over_uniform_hodl\": 0.4079961905117211, \"sharpe\": -1.1643944043975916, \"sterling\": -1.869119278941171, \"ulcer\": -0.15059210842529094}], \"train_objective\": [{\"annualised_returns\": 1.2805120868739603, \"annualised_returns_over_hodl\": 0.8859509939290515, \"annualised_returns_over_uniform_hodl\": 0.8859509939290482, \"calmar\": 2.1097653226656714, \"daily_log_sharpe\": 0.9386345725582665, \"daily_returns\": 0.022494891151066593, \"fee_revenue_over_value\": 0.5505650784231043, \"jax_sharpe\": 1.3703089353063411, \"return\": 0.6495637851741372, \"returns_over_hodl\": 0.46987498925238236, \"returns_over_uniform_hodl\": 0.4698749892523808, \"sharpe\": 1.380731094240648, \"sterling\": 4.929043682696674, \"ulcer\": -0.12746497588064962}], \"train_return\": 0.6495637851741372, \"train_returns_over_hodl\": 0.46987498925238236, \"train_sharpe\": 1.370308935306341, \"validation_return\": 0.044119455812871244, \"validation_returns_over_hodl\": 0.09912781850348251, \"validation_sharpe\": 0.761169189564995}, {\"centeredness_margin\": 0.10888605519892947, \"continuous_test_metrics\": [{\"annualised_returns\": -0.7031005188473434, \"annualised_returns_over_hodl\": 1.8605406650404026, \"annualised_returns_over_uniform_hodl\": 1.608009157962953, \"calmar\": -0.9926300438215343, \"daily_log_sharpe\": -1.5138496586504637, \"daily_returns\": 0.011380870263100765, \"fee_revenue_over_value\": 0.3532970826469275, \"jax_sharpe\": -0.17812266292993106, \"return\": -0.38680320929416345, \"returns_over_hodl\": 0.5269644451851943, \"returns_over_uniform_hodl\": 0.4711719154933174, \"sharpe\": -1.111563677222575, \"sterling\": -1.82597775062019, \"ulcer\": -0.1446309880535263}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": 0.9724728756206584, \"optuna_trial_number\": 188, \"price_ratio\": 1.0228828732152395, \"shift_exponent\": 0.05084159038643142, \"step\": 188, \"test_objective\": [{\"annualised_returns\": -0.7031005188473434, \"annualised_returns_over_hodl\": 1.8605406650404026, \"annualised_returns_over_uniform_hodl\": 1.608009157962953, \"calmar\": -0.9926300438215343, \"daily_log_sharpe\": -1.5138496586504637, \"daily_returns\": 0.011380870263100765, \"fee_revenue_over_value\": 0.3532970826469275, \"jax_sharpe\": -0.17812266292993106, \"return\": -0.38680320929416345, \"returns_over_hodl\": 0.5269644451851943, \"returns_over_uniform_hodl\": 0.4711719154933174, \"sharpe\": -1.111563677222575, \"sterling\": -1.82597775062019, \"ulcer\": -0.1446309880535263}], \"train_objective\": [{\"annualised_returns\": 0.8826884569423421, \"annualised_returns_over_hodl\": 0.5569565217679966, \"annualised_returns_over_uniform_hodl\": 0.5569565217679961, \"calmar\": 1.2993730466564333, \"daily_log_sharpe\": 0.7171403938482543, \"daily_returns\": 0.022725099966775974, \"fee_revenue_over_value\": 0.7535751699051292, \"jax_sharpe\": 1.1547324303293713, \"return\": 0.46833070000252053, \"returns_over_hodl\": 0.30838382321621327, \"returns_over_uniform_hodl\": 0.30838382321621305, \"sharpe\": 1.1551212310525243, \"sterling\": 3.2535839489863645, \"ulcer\": -0.13914283169234565}], \"train_return\": 0.46833070000252053, \"train_returns_over_hodl\": 0.30838382321621327, \"train_sharpe\": 1.1547324303293713, \"validation_return\": 0.08473856614854158, \"validation_returns_over_hodl\": 0.10321099210801377, \"validation_sharpe\": 1.1535541847417898}, {\"centeredness_margin\": 0.10948899736099264, \"continuous_test_metrics\": [{\"annualised_returns\": -0.6966240978223964, \"annualised_returns_over_hodl\": 1.9144625802761692, \"annualised_returns_over_uniform_hodl\": 1.6648990025605626, \"calmar\": -0.9834908152397261, \"daily_log_sharpe\": -1.4832928891338708, \"daily_returns\": 0.011506463410836646, \"fee_revenue_over_value\": 0.3549330407323043, \"jax_sharpe\": -0.1607234991429323, \"return\": -0.3814508876525392, \"returns_over_hodl\": 0.5384921071990345, \"returns_over_uniform_hodl\": 0.4840131198198776, \"sharpe\": -1.0787340205449285, \"sterling\": -1.8124794647655837, \"ulcer\": -0.1436406360771758}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": 0.9408573361036812, \"optuna_trial_number\": 189, \"price_ratio\": 1.0252414600823192, \"shift_exponent\": 0.039467470245758564, \"step\": 189, \"test_objective\": [{\"annualised_returns\": -0.6966240978223964, \"annualised_returns_over_hodl\": 1.9144625802761692, \"annualised_returns_over_uniform_hodl\": 1.6648990025605626, \"calmar\": -0.9834908152397261, \"daily_log_sharpe\": -1.4832928891338708, \"daily_returns\": 0.011506463410836646, \"fee_revenue_over_value\": 0.3549330407323043, \"jax_sharpe\": -0.1607234991429323, \"return\": -0.3814508876525392, \"returns_over_hodl\": 0.5384921071990345, \"returns_over_uniform_hodl\": 0.4840131198198776, \"sharpe\": -1.0787340205449285, \"sterling\": -1.8124794647655837, \"ulcer\": -0.1436406360771758}], \"train_objective\": [{\"annualised_returns\": 0.9253170298063311, \"annualised_returns_over_hodl\": 0.5922097439830214, \"annualised_returns_over_uniform_hodl\": 0.592209743983021, \"calmar\": 1.4021303577307516, \"daily_log_sharpe\": 0.7405715627320223, \"daily_returns\": 0.022178012993506475, \"fee_revenue_over_value\": 0.7167252106202018, \"jax_sharpe\": 1.1795007517546308, \"return\": 0.4884265268737038, \"returns_over_hodl\": 0.32629058958183177, \"returns_over_uniform_hodl\": 0.32629058958183155, \"sharpe\": 1.1796729763236966, \"sterling\": 3.4209613475301612, \"ulcer\": -0.1369836866815336}], \"train_return\": 0.4884265268737038, \"train_returns_over_hodl\": 0.32629058958183177, \"train_sharpe\": 1.179500751754631, \"validation_return\": 0.08614750134929006, \"validation_returns_over_hodl\": 0.10956238393797046, \"validation_sharpe\": 1.165730682361359}, {\"centeredness_margin\": 0.012309000678055529, \"continuous_test_metrics\": [{\"annualised_returns\": -0.7535580565415447, \"annualised_returns_over_hodl\": 1.4386062221541986, \"annualised_returns_over_uniform_hodl\": 1.1647826495034241, \"calmar\": -1.0607156823986132, \"daily_log_sharpe\": -1.6884593087729056, \"daily_returns\": 0.009801725211852751, \"fee_revenue_over_value\": 0.21718798497012914, \"jax_sharpe\": -0.3006804129129349, \"return\": -0.43112025357633377, \"returns_over_hodl\": 0.4319128833808641, \"returns_over_uniform_hodl\": 0.3648471729085516, \"sharpe\": -1.2798213296990073, \"sterling\": -1.9263896782846361, \"ulcer\": -0.15232882891878724}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": 0.9639490089349163, \"optuna_trial_number\": 190, \"price_ratio\": 1.1147965553897918, \"shift_exponent\": 0.25533422402934647, \"step\": 190, \"test_objective\": [{\"annualised_returns\": -0.7535580565415447, \"annualised_returns_over_hodl\": 1.4386062221541986, \"annualised_returns_over_uniform_hodl\": 1.1647826495034241, \"calmar\": -1.0607156823986132, \"daily_log_sharpe\": -1.6884593087729056, \"daily_returns\": 0.009801725211852751, \"fee_revenue_over_value\": 0.21718798497012914, \"jax_sharpe\": -0.3006804129129349, \"return\": -0.43112025357633377, \"returns_over_hodl\": 0.4319128833808641, \"returns_over_uniform_hodl\": 0.3648471729085516, \"sharpe\": -1.2798213296990073, \"sterling\": -1.9263896782846361, \"ulcer\": -0.15232882891878724}], \"train_objective\": [{\"annualised_returns\": 0.938335244271757, \"annualised_returns_over_hodl\": 0.6029756218100066, \"annualised_returns_over_uniform_hodl\": 0.6029756218100075, \"calmar\": 1.434345977436244, \"daily_log_sharpe\": 0.7685487937184844, \"daily_returns\": 0.022199427163196533, \"fee_revenue_over_value\": 0.5196662114364836, \"jax_sharpe\": 1.1902612788680647, \"return\": 0.49452858971296765, \"returns_over_hodl\": 0.33172794801009786, \"returns_over_uniform_hodl\": 0.3317279480100983, \"sharpe\": 1.2026690096719608, \"sterling\": 3.574108021631637, \"ulcer\": -0.13265272270681913}], \"train_return\": 0.49452858971296765, \"train_returns_over_hodl\": 0.33172794801009786, \"train_sharpe\": 1.1902612788680647, \"validation_return\": 0.025639172194804827, \"validation_returns_over_hodl\": 0.08125817900158538, \"validation_sharpe\": 0.5845789263126305}, {\"centeredness_margin\": 0.017843729172311446, \"continuous_test_metrics\": [{\"annualised_returns\": -0.7393584636684996, \"annualised_returns_over_hodl\": 1.5700894078496606, \"annualised_returns_over_uniform_hodl\": 1.2895139831806488, \"calmar\": -1.0411188342994337, \"daily_log_sharpe\": -1.626098423595066, \"daily_returns\": 0.009986219512268258, \"fee_revenue_over_value\": 0.23610366367623478, \"jax_sharpe\": -0.2608412772426442, \"return\": -0.41813976059564717, \"returns_over_hodl\": 0.46251947734671917, \"returns_over_uniform_hodl\": 0.3959897636916223, \"sharpe\": -1.215390995333683, \"sterling\": -1.894805695838022, \"ulcer\": -0.15072892991014283}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": 1.015075730046801, \"optuna_trial_number\": 191, \"price_ratio\": 1.091362838676647, \"shift_exponent\": 0.14231065533901788, \"step\": 191, \"test_objective\": [{\"annualised_returns\": -0.7393584636684996, \"annualised_returns_over_hodl\": 1.5700894078496606, \"annualised_returns_over_uniform_hodl\": 1.2895139831806488, \"calmar\": -1.0411188342994337, \"daily_log_sharpe\": -1.626098423595066, \"daily_returns\": 0.009986219512268258, \"fee_revenue_over_value\": 0.23610366367623478, \"jax_sharpe\": -0.2608412772426442, \"return\": -0.41813976059564717, \"returns_over_hodl\": 0.46251947734671917, \"returns_over_uniform_hodl\": 0.3959897636916223, \"sharpe\": -1.215390995333683, \"sterling\": -1.894805695838022, \"ulcer\": -0.15072892991014283}], \"train_objective\": [{\"annualised_returns\": 1.021369593739637, \"annualised_returns_over_hodl\": 0.6716438454123037, \"annualised_returns_over_uniform_hodl\": 0.6716438454123042, \"calmar\": 1.5626696560849163, \"daily_log_sharpe\": 0.8173085176124902, \"daily_returns\": 0.022450064747935053, \"fee_revenue_over_value\": 0.5727174250593465, \"jax_sharpe\": 1.237462342850152, \"return\": 0.5330773173364651, \"returns_over_hodl\": 0.3660775203701023, \"returns_over_uniform_hodl\": 0.36607752037010255, \"sharpe\": 1.2502962952315821, \"sterling\": 3.8840118307137237, \"ulcer\": -0.132823197141893}], \"train_return\": 0.5330773173364651, \"train_returns_over_hodl\": 0.3660775203701023, \"train_sharpe\": 1.237462342850152, \"validation_return\": 0.036568812912898485, \"validation_returns_over_hodl\": 0.08791722066143182, \"validation_sharpe\": 0.6887205995229255}, {\"centeredness_margin\": 0.03114452231167053, \"continuous_test_metrics\": [{\"annualised_returns\": -0.7971583838434189, \"annualised_returns_over_hodl\": 0.9952304012534465, \"annualised_returns_over_uniform_hodl\": 0.7817908960250677, \"calmar\": -1.1217063618099579, \"daily_log_sharpe\": -1.8386187979841546, \"daily_returns\": 0.009253965018272043, \"fee_revenue_over_value\": 0.16704068924765197, \"jax_sharpe\": -0.4141028488171662, \"return\": -0.4740239815344589, \"returns_over_hodl\": 0.32074370504045047, \"returns_over_uniform_hodl\": 0.2619132362039829, \"sharpe\": -1.4193544480142486, \"sterling\": -1.972126491344151, \"ulcer\": -0.16028459631975184}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": 0.9127235232265805, \"optuna_trial_number\": 192, \"price_ratio\": 1.2144503068279837, \"shift_exponent\": 0.05198466073055208, \"step\": 192, \"test_objective\": [{\"annualised_returns\": -0.7971583838434189, \"annualised_returns_over_hodl\": 0.9952304012534465, \"annualised_returns_over_uniform_hodl\": 0.7817908960250677, \"calmar\": -1.1217063618099579, \"daily_log_sharpe\": -1.8386187979841546, \"daily_returns\": 0.009253965018272043, \"fee_revenue_over_value\": 0.16704068924765197, \"jax_sharpe\": -0.4141028488171662, \"return\": -0.4740239815344589, \"returns_over_hodl\": 0.32074370504045047, \"returns_over_uniform_hodl\": 0.2619132362039829, \"sharpe\": -1.4193544480142486, \"sterling\": -1.972126491344151, \"ulcer\": -0.16028459631975184}], \"train_objective\": [{\"annualised_returns\": 1.0963383620839022, \"annualised_returns_over_hodl\": 0.7336419483762362, \"annualised_returns_over_uniform_hodl\": 0.7336419483762358, \"calmar\": 1.7932481448658875, \"daily_log_sharpe\": 0.8507731160481334, \"daily_returns\": 0.021475340814990966, \"fee_revenue_over_value\": 0.38669919723339136, \"jax_sharpe\": 1.2770670709500034, \"return\": 0.5673503289202739, \"returns_over_hodl\": 0.39661713513745744, \"returns_over_uniform_hodl\": 0.3966171351374572, \"sharpe\": 1.2929286768191026, \"sterling\": 4.260717899526466, \"ulcer\": -0.12543143316432895}], \"train_return\": 0.5673503289202739, \"train_returns_over_hodl\": 0.39661713513745744, \"train_sharpe\": 1.2770670709500034, \"validation_return\": 0.002593168093356635, \"validation_returns_over_hodl\": 0.05852343171082386, \"validation_sharpe\": 0.3665928457614549}, {\"centeredness_margin\": 0.06371727544607575, \"continuous_test_metrics\": [{\"annualised_returns\": -0.701320316864439, \"annualised_returns_over_hodl\": 1.9072637736156155, \"annualised_returns_over_uniform_hodl\": 1.6236467166963466, \"calmar\": -0.9888631286173961, \"daily_log_sharpe\": -1.4916037960941255, \"daily_returns\": 0.010863313197392662, \"fee_revenue_over_value\": 0.31445545096066674, \"jax_sharpe\": -0.16803370186517427, \"return\": -0.38532510093559347, \"returns_over_hodl\": 0.536960524794198, \"returns_over_uniform_hodl\": 0.4747181693846352, \"sharpe\": -1.0837270598982496, \"sterling\": -1.8225295657248293, \"ulcer\": -0.1446039539440811}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": 1.0215797188777878, \"optuna_trial_number\": 193, \"price_ratio\": 1.0398371439826828, \"shift_exponent\": 0.04311254432427306, \"step\": 193, \"test_objective\": [{\"annualised_returns\": -0.701320316864439, \"annualised_returns_over_hodl\": 1.9072637736156155, \"annualised_returns_over_uniform_hodl\": 1.6236467166963466, \"calmar\": -0.9888631286173961, \"daily_log_sharpe\": -1.4916037960941255, \"daily_returns\": 0.010863313197392662, \"fee_revenue_over_value\": 0.31445545096066674, \"jax_sharpe\": -0.16803370186517427, \"return\": -0.38532510093559347, \"returns_over_hodl\": 0.536960524794198, \"returns_over_uniform_hodl\": 0.4747181693846352, \"sharpe\": -1.0837270598982496, \"sterling\": -1.8225295657248293, \"ulcer\": -0.1446039539440811}], \"train_objective\": [{\"annualised_returns\": 1.1101484027173139, \"annualised_returns_over_hodl\": 0.7450626551590189, \"annualised_returns_over_uniform_hodl\": 0.7450626551590198, \"calmar\": 1.7177279899487095, \"daily_log_sharpe\": 0.8529621094836257, \"daily_returns\": 0.021633619019660585, \"fee_revenue_over_value\": 0.6999854082183375, \"jax_sharpe\": 1.2820629958610492, \"return\": 0.5736109082384189, \"returns_over_hodl\": 0.40219574267035685, \"returns_over_uniform_hodl\": 0.4021957426703573, \"sharpe\": 1.2905497814272258, \"sterling\": 4.170056215662663, \"ulcer\": -0.13351503150660848}], \"train_return\": 0.5736109082384189, \"train_returns_over_hodl\": 0.40219574267035685, \"train_sharpe\": 1.282062995861049, \"validation_return\": 0.07379342242448272, \"validation_returns_over_hodl\": 0.11142103609138987, \"validation_sharpe\": 1.0465398016465433}, {\"centeredness_margin\": 0.026274510420893286, \"continuous_test_metrics\": [{\"annualised_returns\": -0.7897870631962854, \"annualised_returns_over_hodl\": 1.0982140444559936, \"annualised_returns_over_uniform_hodl\": 0.8465416718747587, \"calmar\": -1.1106279975360833, \"daily_log_sharpe\": -1.7709747381571819, \"daily_returns\": 0.008934674800187642, \"fee_revenue_over_value\": 0.16876579894903498, \"jax_sharpe\": -0.36749634197816605, \"return\": -0.4664079664760721, \"returns_over_hodl\": 0.3477864434692586, \"returns_over_uniform_hodl\": 0.2801854575066671, \"sharpe\": -1.3439049473918072, \"sterling\": -1.9429856775308585, \"ulcer\": -0.16168285938442897}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": 0.8700473839539642, \"optuna_trial_number\": 194, \"price_ratio\": 1.1934777221710506, \"shift_exponent\": 0.012804795105981146, \"step\": 194, \"test_objective\": [{\"annualised_returns\": -0.7897870631962854, \"annualised_returns_over_hodl\": 1.0982140444559936, \"annualised_returns_over_uniform_hodl\": 0.8465416718747587, \"calmar\": -1.1106279975360833, \"daily_log_sharpe\": -1.7709747381571819, \"daily_returns\": 0.008934674800187642, \"fee_revenue_over_value\": 0.16876579894903498, \"jax_sharpe\": -0.36749634197816605, \"return\": -0.4664079664760721, \"returns_over_hodl\": 0.3477864434692586, \"returns_over_uniform_hodl\": 0.2801854575066671, \"sharpe\": -1.3439049473918072, \"sterling\": -1.9429856775308585, \"ulcer\": -0.16168285938442897}], \"train_objective\": [{\"annualised_returns\": 1.0977003635322427, \"annualised_returns_over_hodl\": 0.7347683041627351, \"annualised_returns_over_uniform_hodl\": 0.7347683041627344, \"calmar\": 1.8519982932741466, \"daily_log_sharpe\": 0.8625240825960092, \"daily_returns\": 0.021549864726919832, \"fee_revenue_over_value\": 0.3221286966244207, \"jax_sharpe\": 1.2784825823096442, \"return\": 0.5679684910319363, \"returns_over_hodl\": 0.3971679601709617, \"returns_over_uniform_hodl\": 0.39716796017096145, \"sharpe\": 1.3038476576445333, \"sterling\": 4.301661159246129, \"ulcer\": -0.12246035397334712}], \"train_return\": 0.5679684910319363, \"train_returns_over_hodl\": 0.3971679601709617, \"train_sharpe\": 1.2784825823096442, \"validation_return\": -0.00840994748765167, \"validation_returns_over_hodl\": 0.06287501878870905, \"validation_sharpe\": 0.264195864418715}, {\"centeredness_margin\": 0.019166458958792373, \"continuous_test_metrics\": [{\"annualised_returns\": -0.7504009049297559, \"annualised_returns_over_hodl\": 1.4813129059268038, \"annualised_returns_over_uniform_hodl\": 1.1925155383742836, \"calmar\": -1.0558604783225303, \"daily_log_sharpe\": -1.6194283241862142, \"daily_returns\": 0.009713000403828217, \"fee_revenue_over_value\": 0.219647983594041, \"jax_sharpe\": -0.27123565276742884, \"return\": -0.42819630116224605, \"returns_over_hodl\": 0.44195989678548964, \"returns_over_uniform_hodl\": 0.37186227269224914, \"sharpe\": -1.1947994274435463, \"sterling\": -1.877431962524672, \"ulcer\": -0.15601480637057297}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": 0.9479838811999225, \"optuna_trial_number\": 195, \"price_ratio\": 1.1166173660623162, \"shift_exponent\": 0.020445863145450025, \"step\": 195, \"test_objective\": [{\"annualised_returns\": -0.7504009049297559, \"annualised_returns_over_hodl\": 1.4813129059268038, \"annualised_returns_over_uniform_hodl\": 1.1925155383742836, \"calmar\": -1.0558604783225303, \"daily_log_sharpe\": -1.6194283241862142, \"daily_returns\": 0.009713000403828217, \"fee_revenue_over_value\": 0.219647983594041, \"jax_sharpe\": -0.27123565276742884, \"return\": -0.42819630116224605, \"returns_over_hodl\": 0.44195989678548964, \"returns_over_uniform_hodl\": 0.37186227269224914, \"sharpe\": -1.1947994274435463, \"sterling\": -1.877431962524672, \"ulcer\": -0.15601480637057297}], \"train_objective\": [{\"annualised_returns\": 1.2891702208751044, \"annualised_returns_over_hodl\": 0.8931111473520503, \"annualised_returns_over_uniform_hodl\": 0.8931111473520503, \"calmar\": 2.1744015955180918, \"daily_log_sharpe\": 0.9419041953258313, \"daily_returns\": 0.022183675604379637, \"fee_revenue_over_value\": 0.48429544768799454, \"jax_sharpe\": 1.3759553039619237, \"return\": 0.6533631685899424, \"returns_over_hodl\": 0.47326050165733635, \"returns_over_uniform_hodl\": 0.47326050165733635, \"sharpe\": 1.386565880575299, \"sterling\": 5.017527987461163, \"ulcer\": -0.12484917894110274}], \"train_return\": 0.6533631685899424, \"train_returns_over_hodl\": 0.47326050165733635, \"train_sharpe\": 1.3759553039619237, \"validation_return\": 0.019450154327344205, \"validation_returns_over_hodl\": 0.0833936980836456, \"validation_sharpe\": 0.5263151975364472}, {\"centeredness_margin\": 0.02370615529362582, \"continuous_test_metrics\": [{\"annualised_returns\": -0.8516704051121655, \"annualised_returns_over_hodl\": 0.46673986781884635, \"annualised_returns_over_uniform_hodl\": 0.3029492014016133, \"calmar\": -1.1973788736481015, \"daily_log_sharpe\": -2.090575941799518, \"daily_returns\": 0.00846736483011683, \"fee_revenue_over_value\": 0.08740663319784892, \"jax_sharpe\": -0.6033128316055333, \"return\": -0.5363160191795576, \"returns_over_hodl\": 0.16680070842279227, \"returns_over_uniform_hodl\": 0.11246317754200863, \"sharpe\": -1.6624364812956207, \"sterling\": -2.0406150990504885, \"ulcer\": -0.17001157025048838}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": 0.812298875459053, \"optuna_trial_number\": 196, \"price_ratio\": 1.8159289165154535, \"shift_exponent\": 0.026963954553327195, \"step\": 196, \"test_objective\": [{\"annualised_returns\": -0.8516704051121655, \"annualised_returns_over_hodl\": 0.46673986781884635, \"annualised_returns_over_uniform_hodl\": 0.3029492014016133, \"calmar\": -1.1973788736481015, \"daily_log_sharpe\": -2.090575941799518, \"daily_returns\": 0.00846736483011683, \"fee_revenue_over_value\": 0.08740663319784892, \"jax_sharpe\": -0.6033128316055333, \"return\": -0.5363160191795576, \"returns_over_hodl\": 0.16680070842279227, \"returns_over_uniform_hodl\": 0.11246317754200863, \"sharpe\": -1.6624364812956207, \"sterling\": -2.0406150990504885, \"ulcer\": -0.17001157025048838}], \"train_objective\": [{\"annualised_returns\": 0.7522863698507893, \"annualised_returns_over_hodl\": 0.449115854237113, \"annualised_returns_over_uniform_hodl\": 0.449115854237113, \"calmar\": 1.2074051891668947, \"daily_log_sharpe\": 0.6744118214967457, \"daily_returns\": 0.020755698083457674, \"fee_revenue_over_value\": 0.18287050554167, \"jax_sharpe\": 1.0797347492468983, \"return\": 0.4057168650239371, \"returns_over_hodl\": 0.2525905820918757, \"returns_over_uniform_hodl\": 0.2525905820918757, \"sharpe\": 1.1008182038324965, \"sterling\": 2.922182755002702, \"ulcer\": -0.1284087603121541}], \"train_return\": 0.4057168650239371, \"train_returns_over_hodl\": 0.2525905820918757, \"train_sharpe\": 1.0797347492468985, \"validation_return\": -0.02634481376820763, \"validation_returns_over_hodl\": 0.02166746290713606, \"validation_sharpe\": 0.09831950683118701}, {\"centeredness_margin\": 0.034631208575486364, \"continuous_test_metrics\": [{\"annualised_returns\": -0.7749575749759066, \"annualised_returns_over_hodl\": 1.2103008626237193, \"annualised_returns_over_uniform_hodl\": 0.9768060998774686, \"calmar\": -1.0911025649523145, \"daily_log_sharpe\": -1.7709104998258696, \"daily_returns\": 0.00951791483097735, \"fee_revenue_over_value\": 0.19272830204506985, \"jax_sharpe\": -0.3571961866314547, \"return\": -0.4515558935755032, \"returns_over_hodl\": 0.37633325795625727, \"returns_over_uniform_hodl\": 0.3158183128466723, \"sharpe\": -1.3599367326816005, \"sterling\": -1.9574090082087707, \"ulcer\": -0.1553335107813227}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": 0.9184369863831467, \"optuna_trial_number\": 197, \"price_ratio\": 1.1575242863069428, \"shift_exponent\": 0.11171471831017142, \"step\": 197, \"test_objective\": [{\"annualised_returns\": -0.7749575749759066, \"annualised_returns_over_hodl\": 1.2103008626237193, \"annualised_returns_over_uniform_hodl\": 0.9768060998774686, \"calmar\": -1.0911025649523145, \"daily_log_sharpe\": -1.7709104998258696, \"daily_returns\": 0.00951791483097735, \"fee_revenue_over_value\": 0.19272830204506985, \"jax_sharpe\": -0.3571961866314547, \"return\": -0.4515558935755032, \"returns_over_hodl\": 0.37633325795625727, \"returns_over_uniform_hodl\": 0.3158183128466723, \"sharpe\": -1.3599367326816005, \"sterling\": -1.9574090082087707, \"ulcer\": -0.1553335107813227}], \"train_objective\": [{\"annualised_returns\": 0.9644064856115242, \"annualised_returns_over_hodl\": 0.6245361668300069, \"annualised_returns_over_uniform_hodl\": 0.6245361668300069, \"calmar\": 1.5166502203421908, \"daily_log_sharpe\": 0.7800727900216049, \"daily_returns\": 0.021728593009756034, \"fee_revenue_over_value\": 0.44870328127313064, \"jax_sharpe\": 1.2046377830772577, \"return\": 0.5067008352192166, \"returns_over_hodl\": 0.3425742574365547, \"returns_over_uniform_hodl\": 0.3425742574365547, \"sharpe\": 1.2184233223059713, \"sterling\": 3.7214454043221687, \"ulcer\": -0.12948446315218565}], \"train_return\": 0.5067008352192166, \"train_returns_over_hodl\": 0.3425742574365547, \"train_sharpe\": 1.2046377830772577, \"validation_return\": 0.01573984773900894, \"validation_returns_over_hodl\": 0.0689736770970446, \"validation_sharpe\": 0.4902335217792174}, {\"centeredness_margin\": 0.05267820078703455, \"continuous_test_metrics\": [{\"annualised_returns\": -0.7369867398315163, \"annualised_returns_over_hodl\": 1.5790786394882406, \"annualised_returns_over_uniform_hodl\": 1.3103475577729582, \"calmar\": -1.0387734267239555, \"daily_log_sharpe\": -1.6488367438578864, \"daily_returns\": 0.009850292510935114, \"fee_revenue_over_value\": 0.24727674209178915, \"jax_sharpe\": -0.2646234021808891, \"return\": -0.4160131619764559, \"returns_over_hodl\": 0.46457747749737655, \"returns_over_uniform_hodl\": 0.4010918650259754, \"sharpe\": -1.2472767629584693, \"sterling\": -1.9191505806576403, \"ulcer\": -0.1473544562510742}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": 1.0152029549478738, \"optuna_trial_number\": 198, \"price_ratio\": 1.0763161140143005, \"shift_exponent\": 0.2159606175143964, \"step\": 198, \"test_objective\": [{\"annualised_returns\": -0.7369867398315163, \"annualised_returns_over_hodl\": 1.5790786394882406, \"annualised_returns_over_uniform_hodl\": 1.3103475577729582, \"calmar\": -1.0387734267239555, \"daily_log_sharpe\": -1.6488367438578864, \"daily_returns\": 0.009850292510935114, \"fee_revenue_over_value\": 0.24727674209178915, \"jax_sharpe\": -0.2646234021808891, \"return\": -0.4160131619764559, \"returns_over_hodl\": 0.46457747749737655, \"returns_over_uniform_hodl\": 0.4010918650259754, \"sharpe\": -1.2472767629584693, \"sterling\": -1.9191505806576403, \"ulcer\": -0.1473544562510742}], \"train_objective\": [{\"annualised_returns\": 0.9190962032232772, \"annualised_returns_over_hodl\": 0.5870652090582134, \"annualised_returns_over_uniform_hodl\": 0.5870652090582129, \"calmar\": 1.3591762953859245, \"daily_log_sharpe\": 0.7531949301756589, \"daily_returns\": 0.022571785025363972, \"fee_revenue_over_value\": 0.5944050959949778, \"jax_sharpe\": 1.1783566727982038, \"return\": 0.48550489999374635, \"returns_over_hodl\": 0.3236872187286559, \"returns_over_uniform_hodl\": 0.3236872187286557, \"sharpe\": 1.1879918573293553, \"sterling\": 3.4347781345893074, \"ulcer\": -0.1381036582625688}], \"train_return\": 0.48550489999374635, \"train_returns_over_hodl\": 0.3236872187286559, \"train_sharpe\": 1.1783566727982038, \"validation_return\": 0.044016723615072584, \"validation_returns_over_hodl\": 0.09179967375002263, \"validation_sharpe\": 0.760737943555765}, {\"centeredness_margin\": 0.20579540909996213, \"continuous_test_metrics\": [{\"annualised_returns\": -0.8995912984406251, \"annualised_returns_over_hodl\": 0.0022168521326701995, \"annualised_returns_over_uniform_hodl\": -0.11799504603588395, \"calmar\": -1.263163416916907, \"daily_log_sharpe\": -2.3735378806457206, \"daily_returns\": 0.00784175201213701, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": -0.8300085065626086, \"return\": -0.6037442597029006, \"returns_over_hodl\": 0.0008922198403509274, \"returns_over_uniform_hodl\": -0.04930957677212533, \"sharpe\": -1.9321437039582179, \"sterling\": -2.083091026762694, \"ulcer\": -0.18108046881813056}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -0.007817142835782609, \"optuna_trial_number\": 199, \"price_ratio\": 1.1839091831499855, \"shift_exponent\": 0.0016128281968191133, \"step\": 199, \"test_objective\": [{\"annualised_returns\": -0.8995912984406251, \"annualised_returns_over_hodl\": 0.0022168521326701995, \"annualised_returns_over_uniform_hodl\": -0.11799504603588395, \"calmar\": -1.263163416916907, \"daily_log_sharpe\": -2.3735378806457206, \"daily_returns\": 0.00784175201213701, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": -0.8300085065626086, \"return\": -0.6037442597029006, \"returns_over_hodl\": 0.0008922198403509274, \"returns_over_uniform_hodl\": -0.04930957677212533, \"sharpe\": -1.9321437039582179, \"sterling\": -2.083091026762694, \"ulcer\": -0.18108046881813056}], \"train_objective\": [{\"annualised_returns\": 0.953010610774597, \"annualised_returns_over_hodl\": 0.6151119407541636, \"annualised_returns_over_uniform_hodl\": 0.6151119407541632, \"calmar\": 1.6178563149217644, \"daily_log_sharpe\": 0.795786958449043, \"daily_returns\": 0.02158195405185268, \"fee_revenue_over_value\": 0.22669327663564762, \"jax_sharpe\": 1.2011531160532234, \"return\": 0.5013881407748031, \"returns_over_hodl\": 0.3378402806364038, \"returns_over_uniform_hodl\": 0.3378402806364036, \"sharpe\": 1.2323949011690996, \"sterling\": 3.7550566044282587, \"ulcer\": -0.12222277444026766}], \"train_return\": 0.5013881407748031, \"train_returns_over_hodl\": 0.3378402806364038, \"train_sharpe\": 1.2011531160532232, \"validation_return\": -0.06990342221136214, \"validation_returns_over_hodl\": -2.2521895459703956e-10, \"validation_sharpe\": -0.28596766116950567}, {\"centeredness_margin\": 0.02906653424301349, \"continuous_test_metrics\": [{\"annualised_returns\": -0.793346519971798, \"annualised_returns_over_hodl\": 1.0626857733860224, \"annualised_returns_over_uniform_hodl\": 0.8152748746682772, \"calmar\": -1.1153836035342768, \"daily_log_sharpe\": -1.781370089939227, \"daily_returns\": 0.008534445568863935, \"fee_revenue_over_value\": 0.16042693088069498, \"jax_sharpe\": -0.3877962671288044, \"return\": -0.47006531611168423, \"returns_over_hodl\": 0.3385484377439616, \"returns_over_uniform_hodl\": 0.2714108028597326, \"sharpe\": -1.3532127351829064, \"sterling\": -1.9476058364775495, \"ulcer\": -0.16261925846688882}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": 0.8782645290819344, \"optuna_trial_number\": 200, \"price_ratio\": 1.2065426692258387, \"shift_exponent\": 0.010162861501139434, \"step\": 200, \"test_objective\": [{\"annualised_returns\": -0.793346519971798, \"annualised_returns_over_hodl\": 1.0626857733860224, \"annualised_returns_over_uniform_hodl\": 0.8152748746682772, \"calmar\": -1.1153836035342768, \"daily_log_sharpe\": -1.781370089939227, \"daily_returns\": 0.008534445568863935, \"fee_revenue_over_value\": 0.16042693088069498, \"jax_sharpe\": -0.3877962671288044, \"return\": -0.47006531611168423, \"returns_over_hodl\": 0.3385484377439616, \"returns_over_uniform_hodl\": 0.2714108028597326, \"sharpe\": -1.3532127351829064, \"sterling\": -1.9476058364775495, \"ulcer\": -0.16261925846688882}], \"train_objective\": [{\"annualised_returns\": 1.0969919978594382, \"annualised_returns_over_hodl\": 0.7341824958469716, \"annualised_returns_over_uniform_hodl\": 0.7341824958469712, \"calmar\": 1.8510419780163179, \"daily_log_sharpe\": 0.8719384921981483, \"daily_returns\": 0.02148013458401726, \"fee_revenue_over_value\": 0.30471417762959263, \"jax_sharpe\": 1.278843666613, \"return\": 0.5676470097229651, \"returns_over_hodl\": 0.39688149817427165, \"returns_over_uniform_hodl\": 0.39688149817427143, \"sharpe\": 1.311917371045747, \"sterling\": 4.303856779556823, \"ulcer\": -0.12232736755868835}], \"train_return\": 0.5676470097229651, \"train_returns_over_hodl\": 0.39688149817427165, \"train_sharpe\": 1.2788436666129999, \"validation_return\": -0.02043413741160116, \"validation_returns_over_hodl\": 0.053187255801584454, \"validation_sharpe\": 0.15840196896757316}, {\"centeredness_margin\": 0.038800289465769064, \"continuous_test_metrics\": [{\"annualised_returns\": -0.6938812177559932, \"annualised_returns_over_hodl\": 2.005576731801247, \"annualised_returns_over_uniform_hodl\": 1.6889928686212299, \"calmar\": -0.9778530453179163, \"daily_log_sharpe\": -1.4482502463533011, \"daily_returns\": 0.010814295125345427, \"fee_revenue_over_value\": 0.31167893757096676, \"jax_sharpe\": -0.14637292870285107, \"return\": -0.3792046598519375, \"returns_over_hodl\": 0.5576849000757051, \"returns_over_uniform_hodl\": 0.4894022335695465, \"sharpe\": -1.0350580218137606, \"sterling\": -1.8002449472532935, \"ulcer\": -0.1447034165480211}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": 1.0378998632283771, \"optuna_trial_number\": 201, \"price_ratio\": 1.0443770142477637, \"shift_exponent\": 0.030314853061986644, \"step\": 201, \"test_objective\": [{\"annualised_returns\": -0.6938812177559932, \"annualised_returns_over_hodl\": 2.005576731801247, \"annualised_returns_over_uniform_hodl\": 1.6889928686212299, \"calmar\": -0.9778530453179163, \"daily_log_sharpe\": -1.4482502463533011, \"daily_returns\": 0.010814295125345427, \"fee_revenue_over_value\": 0.31167893757096676, \"jax_sharpe\": -0.14637292870285107, \"return\": -0.3792046598519375, \"returns_over_hodl\": 0.5576849000757051, \"returns_over_uniform_hodl\": 0.4894022335695465, \"sharpe\": -1.0350580218137606, \"sterling\": -1.8002449472532935, \"ulcer\": -0.1447034165480211}], \"train_objective\": [{\"annualised_returns\": 1.2132083658702841, \"annualised_returns_over_hodl\": 0.8302917758733364, \"annualised_returns_over_uniform_hodl\": 0.8302917758733348, \"calmar\": 1.9412270103463878, \"daily_log_sharpe\": 0.9040530085525605, \"daily_returns\": 0.021796087108747524, \"fee_revenue_over_value\": 0.6585550287822228, \"jax_sharpe\": 1.3350131812975097, \"return\": 0.6198336572360694, \"returns_over_hodl\": 0.44338339682279493, \"returns_over_uniform_hodl\": 0.44338339682279426, \"sharpe\": 1.3432268679242896, \"sterling\": 4.6119834698257485, \"ulcer\": -0.13057672676813303}], \"train_return\": 0.6198336572360694, \"train_returns_over_hodl\": 0.44338339682279493, \"train_sharpe\": 1.3350131812975097, \"validation_return\": 0.0742082032008129, \"validation_returns_over_hodl\": 0.11811923030490701, \"validation_sharpe\": 1.0482838024038865}, {\"centeredness_margin\": 0.06499277271249987, \"continuous_test_metrics\": [{\"annualised_returns\": -0.6867947034632009, \"annualised_returns_over_hodl\": 2.0408396764314904, \"annualised_returns_over_uniform_hodl\": 1.7512418631357578, \"calmar\": -0.9688517177138554, \"daily_log_sharpe\": -1.4386845781064674, \"daily_returns\": 0.01144756717122844, \"fee_revenue_over_value\": 0.3508348205887537, \"jax_sharpe\": -0.1351912854043759, \"return\": -0.3734563855168205, \"returns_over_hodl\": 0.5650195255878749, \"returns_over_uniform_hodl\": 0.5031934012542973, \"sharpe\": -1.0305812077075427, \"sterling\": -1.7883926304324678, \"ulcer\": -0.14311892892359676}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": 0.9808307482003894, \"optuna_trial_number\": 202, \"price_ratio\": 1.0279779206119226, \"shift_exponent\": 0.03836982329541171, \"step\": 202, \"test_objective\": [{\"annualised_returns\": -0.6867947034632009, \"annualised_returns_over_hodl\": 2.0408396764314904, \"annualised_returns_over_uniform_hodl\": 1.7512418631357578, \"calmar\": -0.9688517177138554, \"daily_log_sharpe\": -1.4386845781064674, \"daily_returns\": 0.01144756717122844, \"fee_revenue_over_value\": 0.3508348205887537, \"jax_sharpe\": -0.1351912854043759, \"return\": -0.3734563855168205, \"returns_over_hodl\": 0.5650195255878749, \"returns_over_uniform_hodl\": 0.5031934012542973, \"sharpe\": -1.0305812077075427, \"sterling\": -1.7883926304324678, \"ulcer\": -0.14311892892359676}], \"train_objective\": [{\"annualised_returns\": 1.0451636965734035, \"annualised_returns_over_hodl\": 0.6913212293416762, \"annualised_returns_over_uniform_hodl\": 0.6913212293416757, \"calmar\": 1.6146169798714933, \"daily_log_sharpe\": 0.8095022569778145, \"daily_returns\": 0.02185155225157557, \"fee_revenue_over_value\": 0.7308146443236283, \"jax_sharpe\": 1.2460815114214556, \"return\": 0.544008401973582, \"returns_over_hodl\": 0.3758178699448862, \"returns_over_uniform_hodl\": 0.375817869944886, \"sharpe\": 1.249457892421276, \"sterling\": 3.891536868082345, \"ulcer\": -0.13518787220676118}], \"train_return\": 0.544008401973582, \"train_returns_over_hodl\": 0.3758178699448862, \"train_sharpe\": 1.2460815114214554, \"validation_return\": 0.08143590400270417, \"validation_returns_over_hodl\": 0.11328834407266597, \"validation_sharpe\": 1.1186490675943406}, {\"centeredness_margin\": 0.036257772461781135, \"continuous_test_metrics\": [{\"annualised_returns\": -0.7088663750981693, \"annualised_returns_over_hodl\": 1.8445513270074887, \"annualised_returns_over_uniform_hodl\": 1.5573610199221855, \"calmar\": -1.172753819716194, \"daily_log_sharpe\": -1.5369786090800988, \"daily_returns\": 0.01067656737187577, \"fee_revenue_over_value\": 0.30765280888476426, \"jax_sharpe\": -0.5446948067565249, \"return\": -0.3916272870062003, \"returns_over_hodl\": 0.523521261449726, \"returns_over_uniform_hodl\": 0.4595980655389875, \"sharpe\": -1.1342396586835257, \"sterling\": -1.990911389487387, \"ulcer\": -0.14473563689746075}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": 1.000314745908726, \"optuna_trial_number\": 203, \"price_ratio\": 1.0334229746625603, \"shift_exponent\": 0.16040043831984377, \"step\": 203, \"test_objective\": [{\"annualised_returns\": -0.7088663750981693, \"annualised_returns_over_hodl\": 1.8445513270074887, \"annualised_returns_over_uniform_hodl\": 1.5573610199221855, \"calmar\": -1.172753819716194, \"daily_log_sharpe\": -1.5369786090800988, \"daily_returns\": 0.01067656737187577, \"fee_revenue_over_value\": 0.30765280888476426, \"jax_sharpe\": -0.5446948067565249, \"return\": -0.3916272870062003, \"returns_over_hodl\": 0.523521261449726, \"returns_over_uniform_hodl\": 0.4595980655389875, \"sharpe\": -1.1342396586835257, \"sterling\": -1.990911389487387, \"ulcer\": -0.14473563689746075}], \"train_objective\": [{\"annualised_returns\": 0.790693576581279, \"annualised_returns_over_hodl\": 0.4808780668227506, \"annualised_returns_over_uniform_hodl\": 0.48087806682275014, \"calmar\": 1.1272560021022198, \"daily_log_sharpe\": 0.6632031442762537, \"daily_returns\": 0.023404129943859663, \"fee_revenue_over_value\": 0.7521397196886475, \"jax_sharpe\": 1.0990799565242642, \"return\": 0.4243431371951172, \"returns_over_hodl\": 0.26918787396594257, \"returns_over_uniform_hodl\": 0.26918787396594235, \"sharpe\": 1.1024484469669875, \"sterling\": 2.8680469767782215, \"ulcer\": -0.1448085183939196}], \"train_return\": 0.4243431371951172, \"train_returns_over_hodl\": 0.26918787396594257, \"train_sharpe\": 1.099079956524264, \"validation_return\": 0.06820559979823404, \"validation_returns_over_hodl\": 0.10583583824795229, \"validation_sharpe\": 0.9929469939999849}, {\"centeredness_margin\": 0.0398675891538966, \"continuous_test_metrics\": [{\"annualised_returns\": -0.7307648670477795, \"annualised_returns_over_hodl\": 1.6400921479100643, \"annualised_returns_over_uniform_hodl\": 1.3650014127971173, \"calmar\": -1.0298121114310468, \"daily_log_sharpe\": -1.6285797468219239, \"daily_returns\": 0.010177589751073463, \"fee_revenue_over_value\": 0.25887858892490184, \"jax_sharpe\": -0.20586576603685997, \"return\": -0.41048821204538277, \"returns_over_hodl\": 0.4784340381183292, \"returns_over_uniform_hodl\": 0.4143472363786942, \"sharpe\": -1.2282004449792516, \"sterling\": -1.9105614644004036, \"ulcer\": -0.14645130830993897}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": 1.0124871389355634, \"optuna_trial_number\": 204, \"price_ratio\": 1.0640967755927373, \"shift_exponent\": 0.30884221451946303, \"step\": 204, \"test_objective\": [{\"annualised_returns\": -0.7307648670477795, \"annualised_returns_over_hodl\": 1.6400921479100643, \"annualised_returns_over_uniform_hodl\": 1.3650014127971173, \"calmar\": -1.0298121114310468, \"daily_log_sharpe\": -1.6285797468219239, \"daily_returns\": 0.010177589751073463, \"fee_revenue_over_value\": 0.25887858892490184, \"jax_sharpe\": -0.20586576603685997, \"return\": -0.41048821204538277, \"returns_over_hodl\": 0.4784340381183292, \"returns_over_uniform_hodl\": 0.4143472363786942, \"sharpe\": -1.2282004449792516, \"sterling\": -1.9105614644004036, \"ulcer\": -0.14645130830993897}], \"train_objective\": [{\"annualised_returns\": 0.8579075373917016, \"annualised_returns_over_hodl\": 0.5364630544779085, \"annualised_returns_over_uniform_hodl\": 0.5364630544779063, \"calmar\": 1.24524071934038, \"daily_log_sharpe\": 0.7115092325928428, \"daily_returns\": 0.022879710707129953, \"fee_revenue_over_value\": 0.6284539940849407, \"jax_sharpe\": 1.140835693839621, \"return\": 0.45656638228501345, \"returns_over_hodl\": 0.2979010055561764, \"returns_over_uniform_hodl\": 0.29790100555617527, \"sharpe\": 1.1486107235593468, \"sterling\": 3.168938834341571, \"ulcer\": -0.14043348490564614}], \"train_return\": 0.45656638228501345, \"train_returns_over_hodl\": 0.2979010055561764, \"train_sharpe\": 1.1408356938396207, \"validation_return\": 0.04613878478728206, \"validation_returns_over_hodl\": 0.0937250731917516, \"validation_sharpe\": 0.7809870000271073}, {\"centeredness_margin\": 0.017425104102391353, \"continuous_test_metrics\": [{\"annualised_returns\": -0.8340499028880701, \"annualised_returns_over_hodl\": 0.6507836363725803, \"annualised_returns_over_uniform_hodl\": 0.45773031112244533, \"calmar\": -1.215714631019774, \"daily_log_sharpe\": -1.9846056238958412, \"daily_returns\": 0.008692004166749968, \"fee_revenue_over_value\": 0.11251246913063451, \"jax_sharpe\": -0.6445530167697084, \"return\": -0.5148730063525976, \"returns_over_hodl\": 0.22369182669283694, \"returns_over_uniform_hodl\": 0.16390891035198352, \"sharpe\": -1.5558304039240574, \"sterling\": -2.043495961050582, \"ulcer\": -0.1677236488020776}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": 0.9254570939781179, \"optuna_trial_number\": 205, \"price_ratio\": 1.454974922646267, \"shift_exponent\": 0.01627503510033518, \"step\": 205, \"test_objective\": [{\"annualised_returns\": -0.8340499028880701, \"annualised_returns_over_hodl\": 0.6507836363725803, \"annualised_returns_over_uniform_hodl\": 0.45773031112244533, \"calmar\": -1.215714631019774, \"daily_log_sharpe\": -1.9846056238958412, \"daily_returns\": 0.008692004166749968, \"fee_revenue_over_value\": 0.11251246913063451, \"jax_sharpe\": -0.6445530167697084, \"return\": -0.5148730063525976, \"returns_over_hodl\": 0.22369182669283694, \"returns_over_uniform_hodl\": 0.16390891035198352, \"sharpe\": -1.5558304039240574, \"sterling\": -2.043495961050582, \"ulcer\": -0.1677236488020776}], \"train_objective\": [{\"annualised_returns\": 1.0395428340802244, \"annualised_returns_over_hodl\": 0.6866728561684905, \"annualised_returns_over_uniform_hodl\": 0.6866728561684905, \"calmar\": 1.7048472071859482, \"daily_log_sharpe\": 0.8455093295674168, \"daily_returns\": 0.020980283016371556, \"fee_revenue_over_value\": 0.2656525557874875, \"jax_sharpe\": 1.253643298547072, \"return\": 0.5414306876647696, \"returns_over_hodl\": 0.3735209488885345, \"returns_over_uniform_hodl\": 0.3735209488885345, \"sharpe\": 1.27458813303192, \"sterling\": 4.082188677425276, \"ulcer\": -0.12501055409168738}], \"train_return\": 0.5414306876647696, \"train_returns_over_hodl\": 0.3735209488885345, \"train_sharpe\": 1.2536432985470722, \"validation_return\": -0.026983780304137284, \"validation_returns_over_hodl\": 0.03660933741930816, \"validation_sharpe\": 0.09413272833347625}, {\"centeredness_margin\": 0.013251920304100825, \"continuous_test_metrics\": [{\"annualised_returns\": -0.8400404141834976, \"annualised_returns_over_hodl\": 0.5847059273669153, \"annualised_returns_over_uniform_hodl\": 0.4051087697889919, \"calmar\": -1.1809329192806228, \"daily_log_sharpe\": -2.0286464326774616, \"daily_returns\": 0.0087415556013841, \"fee_revenue_over_value\": 0.10565700536356216, \"jax_sharpe\": -0.5476786643881126, \"return\": -0.522003376268013, \"returns_over_hodl\": 0.20372393762621543, \"returns_over_uniform_hodl\": 0.14680184109520744, \"sharpe\": -1.602307210945276, \"sterling\": -2.0264183964156426, \"ulcer\": -0.16805913745666648}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": 0.8885545130274672, \"optuna_trial_number\": 206, \"price_ratio\": 1.5627237467632873, \"shift_exponent\": 0.04506199992834186, \"step\": 206, \"test_objective\": [{\"annualised_returns\": -0.8400404141834976, \"annualised_returns_over_hodl\": 0.5847059273669153, \"annualised_returns_over_uniform_hodl\": 0.4051087697889919, \"calmar\": -1.1809329192806228, \"daily_log_sharpe\": -2.0286464326774616, \"daily_returns\": 0.0087415556013841, \"fee_revenue_over_value\": 0.10565700536356216, \"jax_sharpe\": -0.5476786643881126, \"return\": -0.522003376268013, \"returns_over_hodl\": 0.20372393762621543, \"returns_over_uniform_hodl\": 0.14680184109520744, \"sharpe\": -1.602307210945276, \"sterling\": -2.0264183964156426, \"ulcer\": -0.16805913745666648}], \"train_objective\": [{\"annualised_returns\": 0.934904056487905, \"annualised_returns_over_hodl\": 0.6001380784141368, \"annualised_returns_over_uniform_hodl\": 0.6001380784141368, \"calmar\": 1.521599651713762, \"daily_log_sharpe\": 0.7872864250859329, \"daily_returns\": 0.02089188453548232, \"fee_revenue_over_value\": 0.23392084277243821, \"jax_sharpe\": 1.1935750763843296, \"return\": 0.4929218465215559, \"returns_over_hodl\": 0.3302962291202718, \"returns_over_uniform_hodl\": 0.3302962291202718, \"sharpe\": 1.2148746324167388, \"sterling\": 3.6568692353445407, \"ulcer\": -0.12628716686687963}], \"train_return\": 0.4929218465215559, \"train_returns_over_hodl\": 0.3302962291202718, \"train_sharpe\": 1.1935750763843294, \"validation_return\": -0.02768674157760609, \"validation_returns_over_hodl\": 0.034779676974660445, \"validation_sharpe\": 0.0883166910340477}, {\"centeredness_margin\": 0.04131548127786643, \"continuous_test_metrics\": [{\"annualised_returns\": -0.8593373135298423, \"annualised_returns_over_hodl\": 0.31349180397304965, \"annualised_returns_over_uniform_hodl\": 0.2356019386548538, \"calmar\": -1.3151026480032917, \"daily_log_sharpe\": -2.2132692511145975, \"daily_returns\": 0.007583172488879433, \"fee_revenue_over_value\": 0.05751297434374757, \"jax_sharpe\": -1.0019110785643925, \"return\": -0.5461216919077594, \"returns_over_hodl\": 0.11607962580069886, \"returns_over_uniform_hodl\": 0.08893756464106017, \"sharpe\": -1.7988866094560625, \"sterling\": -2.1714455389782112, \"ulcer\": -0.1660249270514763}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": 0.6785293813333427, \"optuna_trial_number\": 207, \"price_ratio\": 3.4436283401325647, \"shift_exponent\": 0.00868412923069457, \"step\": 207, \"test_objective\": [{\"annualised_returns\": -0.8593373135298423, \"annualised_returns_over_hodl\": 0.31349180397304965, \"annualised_returns_over_uniform_hodl\": 0.2356019386548538, \"calmar\": -1.3151026480032917, \"daily_log_sharpe\": -2.2132692511145975, \"daily_returns\": 0.007583172488879433, \"fee_revenue_over_value\": 0.05751297434374757, \"jax_sharpe\": -1.0019110785643925, \"return\": -0.5461216919077594, \"returns_over_hodl\": 0.11607962580069886, \"returns_over_uniform_hodl\": 0.08893756464106017, \"sharpe\": -1.7988866094560625, \"sterling\": -2.1714455389782112, \"ulcer\": -0.1660249270514763}], \"train_objective\": [{\"annualised_returns\": 0.4871195973502327, \"annualised_returns_over_hodl\": 0.22982671254267495, \"annualised_returns_over_uniform_hodl\": 0.22982671254267517, \"calmar\": 0.766967446112982, \"daily_log_sharpe\": 0.48656842760985747, \"daily_returns\": 0.020555329490716957, \"fee_revenue_over_value\": 0.09928837678789416, \"jax_sharpe\": 0.8913705381332325, \"return\": 0.2724328704121657, \"returns_over_hodl\": 0.1338253594867913, \"returns_over_uniform_hodl\": 0.13382535948679153, \"sharpe\": 0.9115992150769316, \"sterling\": 1.8719221493962943, \"ulcer\": -0.13176426778069297}], \"train_return\": 0.2724328704121657, \"train_returns_over_hodl\": 0.1338253594867913, \"train_sharpe\": 0.8913705381332324, \"validation_return\": -0.011581923243226822, \"validation_returns_over_hodl\": 0.012170303736960042, \"validation_sharpe\": 0.210266853583544}, {\"centeredness_margin\": 0.125261137506127, \"continuous_test_metrics\": [{\"annualised_returns\": -0.8633702935653955, \"annualised_returns_over_hodl\": 0.28243219451518065, \"annualised_returns_over_uniform_hodl\": 0.2001756427725907, \"calmar\": -1.3153365458496244, \"daily_log_sharpe\": -2.2558508424634263, \"daily_returns\": 0.00765957280196009, \"fee_revenue_over_value\": 0.05526111315371111, \"jax_sharpe\": -1.1487253602262886, \"return\": -0.5514082014223338, \"returns_over_hodl\": 0.10537476068059681, \"returns_over_uniform_hodl\": 0.07625425571526212, \"sharpe\": -1.8437545257932997, \"sterling\": -2.2243987350580747, \"ulcer\": -0.16665351637062725}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": 0.6889201544827929, \"optuna_trial_number\": 208, \"price_ratio\": 3.156009058154633, \"shift_exponent\": 0.007918808183048152, \"step\": 208, \"test_objective\": [{\"annualised_returns\": -0.8633702935653955, \"annualised_returns_over_hodl\": 0.28243219451518065, \"annualised_returns_over_uniform_hodl\": 0.2001756427725907, \"calmar\": -1.3153365458496244, \"daily_log_sharpe\": -2.2558508424634263, \"daily_returns\": 0.00765957280196009, \"fee_revenue_over_value\": 0.05526111315371111, \"jax_sharpe\": -1.1487253602262886, \"return\": -0.5514082014223338, \"returns_over_hodl\": 0.10537476068059681, \"returns_over_uniform_hodl\": 0.07625425571526212, \"sharpe\": -1.8437545257932997, \"sterling\": -2.2243987350580747, \"ulcer\": -0.16665351637062725}], \"train_objective\": [{\"annualised_returns\": 0.5063049971056968, \"annualised_returns_over_hodl\": 0.24569276470964274, \"annualised_returns_over_uniform_hodl\": 0.24569276470964274, \"calmar\": 0.7983663189893351, \"daily_log_sharpe\": 0.501289114551085, \"daily_returns\": 0.020584426359685246, \"fee_revenue_over_value\": 0.1054161485215748, \"jax_sharpe\": 0.9060597168666851, \"return\": 0.2823740955015255, \"returns_over_hodl\": 0.1426836760021697, \"returns_over_uniform_hodl\": 0.1426836760021697, \"sharpe\": 0.9263893973595058, \"sterling\": 1.9472160553418634, \"ulcer\": -0.13150921390607614}], \"train_return\": 0.2823740955015255, \"train_returns_over_hodl\": 0.1426836760021697, \"train_sharpe\": 0.9060597168666852, \"validation_return\": -0.012717861743287595, \"validation_returns_over_hodl\": 0.012888779749933743, \"validation_sharpe\": 0.2008911430024484}, {\"centeredness_margin\": 0.19057484435570388, \"continuous_test_metrics\": [{\"annualised_returns\": -0.8571655553102155, \"annualised_returns_over_hodl\": 0.35226244162743137, \"annualised_returns_over_uniform_hodl\": 0.2546789855519338, \"calmar\": -1.2164368732727502, \"daily_log_sharpe\": -2.2462042873946295, \"daily_returns\": 0.00779594848089405, \"fee_revenue_over_value\": 0.06701469822580436, \"jax_sharpe\": -0.6250814161981678, \"return\": -0.5433123554890522, \"returns_over_hodl\": 0.12923210291909948, \"returns_over_uniform_hodl\": 0.0956776795650427, \"sharpe\": -1.8401940954491653, \"sterling\": -2.0994414358637044, \"ulcer\": -0.1643839748399295}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": 0.7438438910993246, \"optuna_trial_number\": 209, \"price_ratio\": 2.301680726775711, \"shift_exponent\": 0.014128583963592027, \"step\": 209, \"test_objective\": [{\"annualised_returns\": -0.8571655553102155, \"annualised_returns_over_hodl\": 0.35226244162743137, \"annualised_returns_over_uniform_hodl\": 0.2546789855519338, \"calmar\": -1.2164368732727502, \"daily_log_sharpe\": -2.2462042873946295, \"daily_returns\": 0.00779594848089405, \"fee_revenue_over_value\": 0.06701469822580436, \"jax_sharpe\": -0.6250814161981678, \"return\": -0.5433123554890522, \"returns_over_hodl\": 0.12923210291909948, \"returns_over_uniform_hodl\": 0.0956776795650427, \"sharpe\": -1.8401940954491653, \"sterling\": -2.0994414358637044, \"ulcer\": -0.1643839748399295}], \"train_objective\": [{\"annualised_returns\": 0.6101695428875327, \"annualised_returns_over_hodl\": 0.33158726379109793, \"annualised_returns_over_uniform_hodl\": 0.33158726379109793, \"calmar\": 0.9696120400699191, \"daily_log_sharpe\": 0.5778668367441477, \"daily_returns\": 0.02065506896150507, \"fee_revenue_over_value\": 0.13777147182160399, \"jax_sharpe\": 0.9825666051835847, \"return\": 0.33535327808141124, \"returns_over_hodl\": 0.18989177800168644, \"returns_over_uniform_hodl\": 0.18989177800168644, \"sharpe\": 1.0034080260293101, \"sterling\": 2.3570475528200237, \"ulcer\": -0.13012611346370284}], \"train_return\": 0.33535327808141124, \"train_returns_over_hodl\": 0.18989177800168644, \"train_sharpe\": 0.9825666051835846, \"validation_return\": -0.01764015909919381, \"validation_returns_over_hodl\": 0.017223125070342737, \"validation_sharpe\": 0.15873390421838898}, {\"centeredness_margin\": 0.07333263720030588, \"continuous_test_metrics\": [{\"annualised_returns\": -0.8606761493720152, \"annualised_returns_over_hodl\": 0.32340725471117837, \"annualised_returns_over_uniform_hodl\": 0.2238414056830893, \"calmar\": -1.2195193844904697, \"daily_log_sharpe\": -2.223625924471406, \"daily_returns\": 0.007855369605548298, \"fee_revenue_over_value\": 0.0652422634862401, \"jax_sharpe\": -0.6632859915698124, \"return\": -0.5478665062722337, \"returns_over_hodl\": 0.11946515421555737, \"returns_over_uniform_hodl\": 0.0847514339735973, \"sharpe\": -1.8104648720719358, \"sterling\": -2.0873828089517237, \"ulcer\": -0.16742388196108005}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": 0.7145974997018646, \"optuna_trial_number\": 210, \"price_ratio\": 2.6792569279583316, \"shift_exponent\": 1.320191030682913, \"step\": 210, \"test_objective\": [{\"annualised_returns\": -0.8606761493720152, \"annualised_returns_over_hodl\": 0.32340725471117837, \"annualised_returns_over_uniform_hodl\": 0.2238414056830893, \"calmar\": -1.2195193844904697, \"daily_log_sharpe\": -2.223625924471406, \"daily_returns\": 0.007855369605548298, \"fee_revenue_over_value\": 0.0652422634862401, \"jax_sharpe\": -0.6632859915698124, \"return\": -0.5478665062722337, \"returns_over_hodl\": 0.11946515421555737, \"returns_over_uniform_hodl\": 0.0847514339735973, \"sharpe\": -1.8104648720719358, \"sterling\": -2.0873828089517237, \"ulcer\": -0.16742388196108005}], \"train_objective\": [{\"annualised_returns\": 0.5542590343018718, \"annualised_returns_over_hodl\": 0.28535007002873347, \"annualised_returns_over_uniform_hodl\": 0.28535007002873347, \"calmar\": 0.8773097120566637, \"daily_log_sharpe\": 0.5373317195020625, \"daily_returns\": 0.02061675295707, \"fee_revenue_over_value\": 0.11974886146016596, \"jax_sharpe\": 0.9419996753818184, \"return\": 0.3070071161295398, \"returns_over_hodl\": 0.16463339462249826, \"returns_over_uniform_hodl\": 0.16463339462249826, \"sharpe\": 0.962605498563136, \"sterling\": 2.1362106086985886, \"ulcer\": -0.13084291824499106}], \"train_return\": 0.3070071161295398, \"train_returns_over_hodl\": 0.16463339462249826, \"train_sharpe\": 0.9419996753818184, \"validation_return\": -0.014942437576274559, \"validation_returns_over_hodl\": 0.014833157231920469, \"validation_sharpe\": 0.1835908978687097}, {\"centeredness_margin\": 0.036655306504632, \"continuous_test_metrics\": [{\"annualised_returns\": -0.857761068361985, \"annualised_returns_over_hodl\": 0.3712862572977469, \"annualised_returns_over_uniform_hodl\": 0.24944791041946313, \"calmar\": -1.2096092492815442, \"daily_log_sharpe\": -2.172039496760378, \"daily_returns\": 0.008102022738990347, \"fee_revenue_over_value\": 0.07308008351324789, \"jax_sharpe\": -0.6225551067064563, \"return\": -0.5440801439955127, \"returns_over_hodl\": 0.13560339453667924, \"returns_over_uniform_hodl\": 0.09383561368201176, \"sharpe\": -1.7529714780675096, \"sterling\": -2.069485641992302, \"ulcer\": -0.1686537819421969}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": 0.7433028593805348, \"optuna_trial_number\": 211, \"price_ratio\": 2.3058337271036495, \"shift_exponent\": 0.7777596007752394, \"step\": 211, \"test_objective\": [{\"annualised_returns\": -0.857761068361985, \"annualised_returns_over_hodl\": 0.3712862572977469, \"annualised_returns_over_uniform_hodl\": 0.24944791041946313, \"calmar\": -1.2096092492815442, \"daily_log_sharpe\": -2.172039496760378, \"daily_returns\": 0.008102022738990347, \"fee_revenue_over_value\": 0.07308008351324789, \"jax_sharpe\": -0.6225551067064563, \"return\": -0.5440801439955127, \"returns_over_hodl\": 0.13560339453667924, \"returns_over_uniform_hodl\": 0.09383561368201176, \"sharpe\": -1.7529714780675096, \"sterling\": -2.069485641992302, \"ulcer\": -0.1686537819421969}], \"train_objective\": [{\"annualised_returns\": 0.6093662694446318, \"annualised_returns_over_hodl\": 0.33092296810209243, \"annualised_returns_over_uniform_hodl\": 0.33092296810209243, \"calmar\": 0.9683143727326498, \"daily_log_sharpe\": 0.5772609024635738, \"daily_returns\": 0.02066383236223536, \"fee_revenue_over_value\": 0.13752141172961074, \"jax_sharpe\": 0.9819961357239828, \"return\": 0.3349487896761376, \"returns_over_hodl\": 0.1895313509629155, \"returns_over_uniform_hodl\": 0.1895313509629155, \"sharpe\": 1.0028098237068674, \"sterling\": 2.353675998582774, \"ulcer\": -0.1301657909991791}], \"train_return\": 0.3349487896761376, \"train_returns_over_hodl\": 0.1895313509629155, \"train_sharpe\": 0.9819961357239828, \"validation_return\": -0.017977470945020113, \"validation_returns_over_hodl\": 0.01700279866963128, \"validation_sharpe\": 0.16032125508242726}, {\"centeredness_margin\": 0.013034246541382216, \"continuous_test_metrics\": [{\"annualised_returns\": -0.7849942444217071, \"annualised_returns_over_hodl\": 1.1257146140205032, \"annualised_returns_over_uniform_hodl\": 0.888642504143073, \"calmar\": -1.1046207681962354, \"daily_log_sharpe\": -1.8008886518104499, \"daily_returns\": 0.00945726560412926, \"fee_revenue_over_value\": 0.18165041786215524, \"jax_sharpe\": -0.38285162963457, \"return\": -0.46154129727114834, \"returns_over_hodl\": 0.35487314303396444, \"returns_over_uniform_hodl\": 0.29186149228832137, \"sharpe\": -1.3866573985227464, \"sterling\": -1.9655412830420127, \"ulcer\": -0.1575612047079747}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": 0.9178007638836926, \"optuna_trial_number\": 212, \"price_ratio\": 1.181372774231635, \"shift_exponent\": 0.15215474429503986, \"step\": 212, \"test_objective\": [{\"annualised_returns\": -0.7849942444217071, \"annualised_returns_over_hodl\": 1.1257146140205032, \"annualised_returns_over_uniform_hodl\": 0.888642504143073, \"calmar\": -1.1046207681962354, \"daily_log_sharpe\": -1.8008886518104499, \"daily_returns\": 0.00945726560412926, \"fee_revenue_over_value\": 0.18165041786215524, \"jax_sharpe\": -0.38285162963457, \"return\": -0.46154129727114834, \"returns_over_hodl\": 0.35487314303396444, \"returns_over_uniform_hodl\": 0.29186149228832137, \"sharpe\": -1.3866573985227464, \"sterling\": -1.9655412830420127, \"ulcer\": -0.1575612047079747}], \"train_objective\": [{\"annualised_returns\": 0.9911641595647618, \"annualised_returns_over_hodl\": 0.6466643818382902, \"annualised_returns_over_uniform_hodl\": 0.6466643818382911, \"calmar\": 1.572321556673215, \"daily_log_sharpe\": 0.793155824350251, \"daily_returns\": 0.021650927977891796, \"fee_revenue_over_value\": 0.4266119639659499, \"jax_sharpe\": 1.2198234052221397, \"return\": 0.5191277478960192, \"returns_over_hodl\": 0.3536474928588069, \"returns_over_uniform_hodl\": 0.35364749285880737, \"sharpe\": 1.2338413370794794, \"sterling\": 3.8336271317751445, \"ulcer\": -0.12862034109882592}], \"train_return\": 0.5191277478960192, \"train_returns_over_hodl\": 0.3536474928588069, \"train_sharpe\": 1.2198234052221397, \"validation_return\": 0.007987250599088558, \"validation_returns_over_hodl\": 0.06496893250160407, \"validation_sharpe\": 0.41812598941083073}, {\"centeredness_margin\": 0.024040657513980972, \"continuous_test_metrics\": [{\"annualised_returns\": -0.7182297258990715, \"annualised_returns_over_hodl\": 1.7964808861774944, \"annualised_returns_over_uniform_hodl\": 1.4751119552112346, \"calmar\": -1.011293685237703, \"daily_log_sharpe\": -1.5154675761686272, \"daily_returns\": 0.010277715042773193, \"fee_revenue_over_value\": 0.2696723332177225, \"jax_sharpe\": -0.16874578575987717, \"return\": -0.39958438294281906, \"returns_over_hodl\": 0.5130994956917383, \"returns_over_uniform_hodl\": 0.440507528458119, \"sharpe\": -1.0959608155510887, \"sterling\": -1.833242502626886, \"ulcer\": -0.14969143817395017}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": 1.046648147187616, \"optuna_trial_number\": 213, \"price_ratio\": 1.0683147249802263, \"shift_exponent\": 0.022548630141321257, \"step\": 213, \"test_objective\": [{\"annualised_returns\": -0.7182297258990715, \"annualised_returns_over_hodl\": 1.7964808861774944, \"annualised_returns_over_uniform_hodl\": 1.4751119552112346, \"calmar\": -1.011293685237703, \"daily_log_sharpe\": -1.5154675761686272, \"daily_returns\": 0.010277715042773193, \"fee_revenue_over_value\": 0.2696723332177225, \"jax_sharpe\": -0.16874578575987717, \"return\": -0.39958438294281906, \"returns_over_hodl\": 0.5130994956917383, \"returns_over_uniform_hodl\": 0.440507528458119, \"sharpe\": -1.0959608155510887, \"sterling\": -1.833242502626886, \"ulcer\": -0.14969143817395017}], \"train_objective\": [{\"annualised_returns\": 1.3611013921862027, \"annualised_returns_over_hodl\": 0.9525972008614612, \"annualised_returns_over_uniform_hodl\": 0.9525972008614629, \"calmar\": 2.25825372369303, \"daily_log_sharpe\": 0.9740508739716619, \"daily_returns\": 0.022768756389143163, \"fee_revenue_over_value\": 0.5829158556433541, \"jax_sharpe\": 1.4077754880747355, \"return\": 0.6847127994157094, \"returns_over_hodl\": 0.5011951827452972, \"returns_over_uniform_hodl\": 0.5011951827452981, \"sharpe\": 1.4176548544803296, \"sterling\": 5.228991964836559, \"ulcer\": -0.12686461567894816}], \"train_return\": 0.6847127994157094, \"train_returns_over_hodl\": 0.5011951827452972, \"train_sharpe\": 1.4077754880747355, \"validation_return\": 0.047956254336784854, \"validation_returns_over_hodl\": 0.10824510872816684, \"validation_sharpe\": 0.7972093592798515}, {\"centeredness_margin\": 0.024036202366971376, \"continuous_test_metrics\": [{\"annualised_returns\": -0.7827247949958867, \"annualised_returns_over_hodl\": 1.142197105524061, \"annualised_returns_over_uniform_hodl\": 0.908577685111039, \"calmar\": -1.101385942559839, \"daily_log_sharpe\": -1.7709903351045209, \"daily_returns\": 0.009496250880293674, \"fee_revenue_over_value\": 0.1841340978582359, \"jax_sharpe\": -0.3686679668168106, \"return\": -0.4592594751373479, \"returns_over_hodl\": 0.35909434664999806, \"returns_over_uniform_hodl\": 0.2973360033919008, \"sharpe\": -1.3512916460720599, \"sterling\": -1.9455940084472463, \"ulcer\": -0.15870370172287127}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": 0.9253739480051234, \"optuna_trial_number\": 214, \"price_ratio\": 1.1740831228115993, \"shift_exponent\": 0.04562224618402448, \"step\": 214, \"test_objective\": [{\"annualised_returns\": -0.7827247949958867, \"annualised_returns_over_hodl\": 1.142197105524061, \"annualised_returns_over_uniform_hodl\": 0.908577685111039, \"calmar\": -1.101385942559839, \"daily_log_sharpe\": -1.7709903351045209, \"daily_returns\": 0.009496250880293674, \"fee_revenue_over_value\": 0.1841340978582359, \"jax_sharpe\": -0.3686679668168106, \"return\": -0.4592594751373479, \"returns_over_hodl\": 0.35909434664999806, \"returns_over_uniform_hodl\": 0.2973360033919008, \"sharpe\": -1.3512916460720599, \"sterling\": -1.9455940084472463, \"ulcer\": -0.15870370172287127}], \"train_objective\": [{\"annualised_returns\": 1.128075200126195, \"annualised_returns_over_hodl\": 0.759887861122996, \"annualised_returns_over_uniform_hodl\": 0.7598878611229964, \"calmar\": 1.8473542447857685, \"daily_log_sharpe\": 0.864440847473556, \"daily_returns\": 0.02169483408145054, \"fee_revenue_over_value\": 0.4277464308263434, \"jax_sharpe\": 1.294171879671297, \"return\": 0.5817138005868749, \"returns_over_hodl\": 0.40941597805055063, \"returns_over_uniform_hodl\": 0.40941597805055086, \"sharpe\": 1.307548397417369, \"sterling\": 4.383682579595426, \"ulcer\": -0.12605285388313606}], \"train_return\": 0.5817138005868749, \"train_returns_over_hodl\": 0.40941597805055063, \"train_sharpe\": 1.2941718796712973, \"validation_return\": 0.00869883637142177, \"validation_returns_over_hodl\": 0.0654512435703436, \"validation_sharpe\": 0.42468521086296984}, {\"centeredness_margin\": 0.01574015428090659, \"continuous_test_metrics\": [{\"annualised_returns\": -0.7767695672278234, \"annualised_returns_over_hodl\": 1.2020742595700642, \"annualised_returns_over_uniform_hodl\": 0.9608892906974367, \"calmar\": -1.0932634097624263, \"daily_log_sharpe\": -1.7756052854851692, \"daily_returns\": 0.009560561694643872, \"fee_revenue_over_value\": 0.19179217403084414, \"jax_sharpe\": -0.36230141219349427, \"return\": -0.45333865912062465, \"returns_over_hodl\": 0.3742678845800689, \"returns_over_uniform_hodl\": 0.3115411303146618, \"sharpe\": -1.3641678576048597, \"sterling\": -1.958961223668421, \"ulcer\": -0.15584203551916798}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": 0.9212617194993027, \"optuna_trial_number\": 215, \"price_ratio\": 1.1591339075915021, \"shift_exponent\": 0.18210847378454179, \"step\": 215, \"test_objective\": [{\"annualised_returns\": -0.7767695672278234, \"annualised_returns_over_hodl\": 1.2020742595700642, \"annualised_returns_over_uniform_hodl\": 0.9608892906974367, \"calmar\": -1.0932634097624263, \"daily_log_sharpe\": -1.7756052854851692, \"daily_returns\": 0.009560561694643872, \"fee_revenue_over_value\": 0.19179217403084414, \"jax_sharpe\": -0.36230141219349427, \"return\": -0.45333865912062465, \"returns_over_hodl\": 0.3742678845800689, \"returns_over_uniform_hodl\": 0.3115411303146618, \"sharpe\": -1.3641678576048597, \"sterling\": -1.958961223668421, \"ulcer\": -0.15584203551916798}], \"train_objective\": [{\"annualised_returns\": 0.9505681170544056, \"annualised_returns_over_hodl\": 0.6130920332580472, \"annualised_returns_over_uniform_hodl\": 0.6130920332580467, \"calmar\": 1.4873016225764055, \"daily_log_sharpe\": 0.7711794379867153, \"daily_returns\": 0.021787956216314797, \"fee_revenue_over_value\": 0.4508189862794787, \"jax_sharpe\": 1.1963510326245848, \"return\": 0.500247880201641, \"returns_over_hodl\": 0.3368242299005757, \"returns_over_uniform_hodl\": 0.3368242299005755, \"sharpe\": 1.2101102203753686, \"sterling\": 3.6608905181796825, \"ulcer\": -0.13011756776612624}], \"train_return\": 0.500247880201641, \"train_returns_over_hodl\": 0.3368242299005757, \"train_sharpe\": 1.196351032624585, \"validation_return\": 0.01302975277888363, \"validation_returns_over_hodl\": 0.06930988199658494, \"validation_sharpe\": 0.46534246559237524}, {\"centeredness_margin\": 0.09201109880276218, \"continuous_test_metrics\": [{\"annualised_returns\": -0.7202388541667866, \"annualised_returns_over_hodl\": 1.6948063771440358, \"annualised_returns_over_uniform_hodl\": 1.457463473976508, \"calmar\": -1.2384398005869746, \"daily_log_sharpe\": -1.5977398928678597, \"daily_returns\": 0.010497056454122095, \"fee_revenue_over_value\": 0.3038242191935796, \"jax_sharpe\": -0.6474918821949518, \"return\": -0.4013122653804979, \"returns_over_hodl\": 0.4906982273606144, \"returns_over_uniform_hodl\": 0.4363620205981362, \"sharpe\": -1.199604908937381, \"sterling\": -2.0517841301536666, \"ulcer\": -0.14554007198464622}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": 0.9574943711720029, \"optuna_trial_number\": 216, \"price_ratio\": 1.0327180049435516, \"shift_exponent\": 0.19441188182276983, \"step\": 216, \"test_objective\": [{\"annualised_returns\": -0.7202388541667866, \"annualised_returns_over_hodl\": 1.6948063771440358, \"annualised_returns_over_uniform_hodl\": 1.457463473976508, \"calmar\": -1.2384398005869746, \"daily_log_sharpe\": -1.5977398928678597, \"daily_returns\": 0.010497056454122095, \"fee_revenue_over_value\": 0.3038242191935796, \"jax_sharpe\": -0.6474918821949518, \"return\": -0.4013122653804979, \"returns_over_hodl\": 0.4906982273606144, \"returns_over_uniform_hodl\": 0.4363620205981362, \"sharpe\": -1.199604908937381, \"sterling\": -2.0517841301536666, \"ulcer\": -0.14554007198464622}], \"train_objective\": [{\"annualised_returns\": 0.6223306886600648, \"annualised_returns_over_hodl\": 0.3416443580240267, \"annualised_returns_over_uniform_hodl\": 0.3416443580240258, \"calmar\": 0.8755402049304415, \"daily_log_sharpe\": 0.5509096640337122, \"daily_returns\": 0.024265977803021376, \"fee_revenue_over_value\": 0.7215830625338874, \"jax_sharpe\": 0.9888510070712168, \"return\": 0.34146737090342527, \"returns_over_hodl\": 0.19533985597346226, \"returns_over_uniform_hodl\": 0.19533985597346182, \"sharpe\": 0.9893935112507434, \"sterling\": 2.24431841268243, \"ulcer\": -0.14728444261111204}], \"train_return\": 0.34146737090342527, \"train_returns_over_hodl\": 0.19533985597346226, \"train_sharpe\": 0.9888510070712169, \"validation_return\": 0.060611070887369056, \"validation_returns_over_hodl\": 0.09238497429624326, \"validation_sharpe\": 0.9224071674250454}, {\"centeredness_margin\": 0.17130859082584732, \"continuous_test_metrics\": [{\"annualised_returns\": -0.7461781025736192, \"annualised_returns_over_hodl\": 1.4109029931968693, \"annualised_returns_over_uniform_hodl\": 1.2296092617257566, \"calmar\": -1.054617820138141, \"daily_log_sharpe\": -1.7149289340404956, \"daily_returns\": 0.009988148258299606, \"fee_revenue_over_value\": 0.2707999068617095, \"jax_sharpe\": -0.2940870309840139, \"return\": -0.4243197366950766, \"returns_over_hodl\": 0.42533921880981906, \"returns_over_uniform_hodl\": 0.38116286405075006, \"sharpe\": -1.3198751571406167, \"sterling\": -1.9414862749944581, \"ulcer\": -0.14696474847737942}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": 0.9363598354923663, \"optuna_trial_number\": 217, \"price_ratio\": 1.053574735318702, \"shift_exponent\": 0.126320894304629, \"step\": 217, \"test_objective\": [{\"annualised_returns\": -0.7461781025736192, \"annualised_returns_over_hodl\": 1.4109029931968693, \"annualised_returns_over_uniform_hodl\": 1.2296092617257566, \"calmar\": -1.054617820138141, \"daily_log_sharpe\": -1.7149289340404956, \"daily_returns\": 0.009988148258299606, \"fee_revenue_over_value\": 0.2707999068617095, \"jax_sharpe\": -0.2940870309840139, \"return\": -0.4243197366950766, \"returns_over_hodl\": 0.42533921880981906, \"returns_over_uniform_hodl\": 0.38116286405075006, \"sharpe\": -1.3198751571406167, \"sterling\": -1.9414862749944581, \"ulcer\": -0.14696474847737942}], \"train_objective\": [{\"annualised_returns\": 0.7254897985392794, \"annualised_returns_over_hodl\": 0.42695547166790226, \"annualised_returns_over_uniform_hodl\": 0.42695547166790404, \"calmar\": 1.0449568611570093, \"daily_log_sharpe\": 0.63010813367936, \"daily_returns\": 0.022593992473936828, \"fee_revenue_over_value\": 0.6217753984084632, \"jax_sharpe\": 1.059480754149906, \"return\": 0.3926262658574209, \"returns_over_hodl\": 0.24092595627859725, \"returns_over_uniform_hodl\": 0.24092595627859814, \"sharpe\": 1.0640005312162506, \"sterling\": 2.6752890375053857, \"ulcer\": -0.14278542847699097}], \"train_return\": 0.3926262658574209, \"train_returns_over_hodl\": 0.24092595627859725, \"train_sharpe\": 1.059480754149906, \"validation_return\": 0.05924563991760268, \"validation_returns_over_hodl\": 0.08591231358015983, \"validation_sharpe\": 0.9121107640701323}, {\"centeredness_margin\": 0.010395273984546139, \"continuous_test_metrics\": [{\"annualised_returns\": -0.8563064757648339, \"annualised_returns_over_hodl\": 0.3951877349325823, \"annualised_returns_over_uniform_hodl\": 0.2622252679269521, \"calmar\": -1.2893022820524107, \"daily_log_sharpe\": -2.138613553069141, \"daily_returns\": 0.008222739905237036, \"fee_revenue_over_value\": 0.07690655993267699, \"jax_sharpe\": -0.8450279373842843, \"return\": -0.5422081145232672, \"returns_over_hodl\": 0.14353388625867813, \"returns_over_uniform_hodl\": 0.09832695679777315, \"sharpe\": -1.7147174930038163, \"sterling\": -2.1156695819634663, \"ulcer\": -0.1695630082558638}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": 0.756639886011293, \"optuna_trial_number\": 218, \"price_ratio\": 2.1782182763627587, \"shift_exponent\": 0.38595005363531204, \"step\": 218, \"test_objective\": [{\"annualised_returns\": -0.8563064757648339, \"annualised_returns_over_hodl\": 0.3951877349325823, \"annualised_returns_over_uniform_hodl\": 0.2622252679269521, \"calmar\": -1.2893022820524107, \"daily_log_sharpe\": -2.138613553069141, \"daily_returns\": 0.008222739905237036, \"fee_revenue_over_value\": 0.07690655993267699, \"jax_sharpe\": -0.8450279373842843, \"return\": -0.5422081145232672, \"returns_over_hodl\": 0.14353388625867813, \"returns_over_uniform_hodl\": 0.09832695679777315, \"sharpe\": -1.7147174930038163, \"sterling\": -2.1156695819634663, \"ulcer\": -0.1695630082558638}], \"train_objective\": [{\"annualised_returns\": 0.6356582159357984, \"annualised_returns_over_hodl\": 0.35266603313680345, \"annualised_returns_over_uniform_hodl\": 0.35266603313680345, \"calmar\": 1.011870203067781, \"daily_log_sharpe\": 0.5958031945968914, \"daily_returns\": 0.020680124621854673, \"fee_revenue_over_value\": 0.14595179281490264, \"jax_sharpe\": 1.0005973505527805, \"return\": 0.3481472267824379, \"returns_over_hodl\": 0.2012920678106871, \"returns_over_uniform_hodl\": 0.2012920678106871, \"sharpe\": 1.021489946261264, \"sterling\": 2.457586282278535, \"ulcer\": -0.12984757728074756}], \"train_return\": 0.3481472267824379, \"train_returns_over_hodl\": 0.2012920678106871, \"train_sharpe\": 1.0005973505527805, \"validation_return\": -0.019314129663514645, \"validation_returns_over_hodl\": 0.01791393026708965, \"validation_sharpe\": 0.1506479630343646}, {\"centeredness_margin\": 0.15749473553114013, \"continuous_test_metrics\": [{\"annualised_returns\": -0.8608204942406377, \"annualised_returns_over_hodl\": 0.3213141523701748, \"annualised_returns_over_uniform_hodl\": 0.2225734589092816, \"calmar\": -1.2221631297920168, \"daily_log_sharpe\": -2.2763811988488283, \"daily_returns\": 0.007853902862907361, \"fee_revenue_over_value\": 0.06315939250920023, \"jax_sharpe\": -0.6753941458427698, \"return\": -0.5480552182215992, \"returns_over_hodl\": 0.11875175077567213, \"returns_over_uniform_hodl\": 0.08429867928826162, \"sharpe\": -1.8718197711452569, \"sterling\": -2.1089929284820124, \"ulcer\": -0.16495198822981597}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": 0.7136227639115673, \"optuna_trial_number\": 219, \"price_ratio\": 2.6966656039067876, \"shift_exponent\": 0.08326409001825649, \"step\": 219, \"test_objective\": [{\"annualised_returns\": -0.8608204942406377, \"annualised_returns_over_hodl\": 0.3213141523701748, \"annualised_returns_over_uniform_hodl\": 0.2225734589092816, \"calmar\": -1.2221631297920168, \"daily_log_sharpe\": -2.2763811988488283, \"daily_returns\": 0.007853902862907361, \"fee_revenue_over_value\": 0.06315939250920023, \"jax_sharpe\": -0.6753941458427698, \"return\": -0.5480552182215992, \"returns_over_hodl\": 0.11875175077567213, \"returns_over_uniform_hodl\": 0.08429867928826162, \"sharpe\": -1.8718197711452569, \"sterling\": -2.1089929284820124, \"ulcer\": -0.16495198822981597}], \"train_objective\": [{\"annualised_returns\": 0.55207246479354, \"annualised_returns_over_hodl\": 0.28354180820838715, \"annualised_returns_over_uniform_hodl\": 0.28354180820838715, \"calmar\": 0.8736478265148349, \"daily_log_sharpe\": 0.5357305761636386, \"daily_returns\": 0.020618775752055764, \"fee_revenue_over_value\": 0.11909541040851697, \"jax_sharpe\": 0.9403820177912187, \"return\": 0.3058904754340275, \"returns_over_hodl\": 0.16363839082509046, \"returns_over_uniform_hodl\": 0.16363839082509046, \"sharpe\": 0.9609908564986387, \"sterling\": 2.127559560930312, \"ulcer\": -0.13088093980557594}], \"train_return\": 0.3058904754340275, \"train_returns_over_hodl\": 0.16363839082509046, \"train_sharpe\": 0.9403820177912187, \"validation_return\": -0.014872606980708358, \"validation_returns_over_hodl\": 0.01466979835644544, \"validation_sharpe\": 0.18405140963306976}, {\"centeredness_margin\": 0.023628258299866155, \"continuous_test_metrics\": [{\"annualised_returns\": -0.6501410122407744, \"annualised_returns_over_hodl\": 2.4210330497873827, \"annualised_returns_over_uniform_hodl\": 2.073213333109743, \"calmar\": -0.9174177133178102, \"daily_log_sharpe\": -1.314602455131073, \"daily_returns\": 0.012399027621476842, \"fee_revenue_over_value\": 0.409477678986345, \"jax_sharpe\": -0.05805469362965808, \"return\": -0.34489868112829136, \"returns_over_hodl\": 0.6410633113343187, \"returns_over_uniform_hodl\": 0.5717085880657018, \"sharpe\": -0.905517002710307, \"sterling\": -1.7020382569961898, \"ulcer\": -0.14085209083710476}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": 0.9933993931944404, \"optuna_trial_number\": 220, \"price_ratio\": 1.016427205411514, \"shift_exponent\": 0.039063582766726807, \"step\": 220, \"test_objective\": [{\"annualised_returns\": -0.6501410122407744, \"annualised_returns_over_hodl\": 2.4210330497873827, \"annualised_returns_over_uniform_hodl\": 2.073213333109743, \"calmar\": -0.9174177133178102, \"daily_log_sharpe\": -1.314602455131073, \"daily_returns\": 0.012399027621476842, \"fee_revenue_over_value\": 0.409477678986345, \"jax_sharpe\": -0.05805469362965808, \"return\": -0.34489868112829136, \"returns_over_hodl\": 0.6410633113343187, \"returns_over_uniform_hodl\": 0.5717085880657018, \"sharpe\": -0.905517002710307, \"sterling\": -1.7020382569961898, \"ulcer\": -0.14085209083710476}], \"train_objective\": [{\"annualised_returns\": 0.9903966262413331, \"annualised_returns_over_hodl\": 0.6460296427187202, \"annualised_returns_over_uniform_hodl\": 0.6460296427187198, \"calmar\": 1.474627614369618, \"daily_log_sharpe\": 0.7711384768963268, \"daily_returns\": 0.021771212444345246, \"fee_revenue_over_value\": 0.7999008286479476, \"jax_sharpe\": 1.2144409151254485, \"return\": 0.5187722042754161, \"returns_over_hodl\": 0.3533306789955246, \"returns_over_uniform_hodl\": 0.3533306789955244, \"sharpe\": 1.2123246130810783, \"sterling\": 3.6295326094100204, \"ulcer\": -0.13838208020572013}], \"train_return\": 0.5187722042754161, \"train_returns_over_hodl\": 0.3533306789955246, \"train_sharpe\": 1.2144409151254485, \"validation_return\": 0.10497058855587493, \"validation_returns_over_hodl\": 0.12930166268639165, \"validation_sharpe\": 1.333965163066435}, {\"centeredness_margin\": 0.23943873011560043, \"continuous_test_metrics\": [{\"annualised_returns\": -0.8995912984352719, \"annualised_returns_over_hodl\": 0.002216852132469249, \"annualised_returns_over_uniform_hodl\": -0.11799504598886001, \"calmar\": -1.2631634170704122, \"daily_log_sharpe\": -2.3735378808110887, \"daily_returns\": 0.007841752011552574, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": -0.8300085070829046, \"return\": -0.6037442596943923, \"returns_over_hodl\": 0.0008922198402701031, \"returns_over_uniform_hodl\": -0.049309576751712214, \"sharpe\": -1.9321437041642624, \"sterling\": -2.0830910269272493, \"ulcer\": -0.18108046880778372}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -0.010057307236034818, \"optuna_trial_number\": 221, \"price_ratio\": 1.074391680317538, \"shift_exponent\": 0.001973515698842204, \"step\": 221, \"test_objective\": [{\"annualised_returns\": -0.8995912984352719, \"annualised_returns_over_hodl\": 0.002216852132469249, \"annualised_returns_over_uniform_hodl\": -0.11799504598886001, \"calmar\": -1.2631634170704122, \"daily_log_sharpe\": -2.3735378808110887, \"daily_returns\": 0.007841752011552574, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": -0.8300085070829046, \"return\": -0.6037442596943923, \"returns_over_hodl\": 0.0008922198402701031, \"returns_over_uniform_hodl\": -0.049309576751712214, \"sharpe\": -1.9321437041642624, \"sterling\": -2.0830910269272493, \"ulcer\": -0.18108046880778372}], \"train_objective\": [{\"annualised_returns\": 0.652934705691407, \"annualised_returns_over_hodl\": 0.3669534439397235, \"annualised_returns_over_uniform_hodl\": 0.36695344393972484, \"calmar\": 1.1220892740485005, \"daily_log_sharpe\": 0.5934378212621647, \"daily_returns\": 0.02197883957075941, \"fee_revenue_over_value\": 0.1583695307849278, \"jax_sharpe\": 1.0092561441154522, \"return\": 0.35677459153163205, \"returns_over_hodl\": 0.20897964423663207, \"returns_over_uniform_hodl\": 0.20897964423663273, \"sharpe\": 1.0380580669567026, \"sterling\": 2.561131798427837, \"ulcer\": -0.12357672049712677}], \"train_return\": 0.35677459153163205, \"train_returns_over_hodl\": 0.20897964423663207, \"train_sharpe\": 1.0092561441154522, \"validation_return\": -0.06990342224384904, \"validation_returns_over_hodl\": -2.899194218031198e-10, \"validation_sharpe\": -0.28596766155792197}, {\"centeredness_margin\": 0.04408657933001378, \"continuous_test_metrics\": [{\"annualised_returns\": -0.7203842262033158, \"annualised_returns_over_hodl\": 1.7394072877422642, \"annualised_returns_over_uniform_hodl\": 1.4561865044071833, \"calmar\": -1.0149061809334687, \"daily_log_sharpe\": -1.542081283928664, \"daily_returns\": 0.01048792722812917, \"fee_revenue_over_value\": 0.27814600889446545, \"jax_sharpe\": -0.2078796502466599, \"return\": -0.4014375747838158, \"returns_over_hodl\": 0.5005859272124751, \"returns_over_uniform_hodl\": 0.4360613802854296, \"sharpe\": -1.1283899954362164, \"sterling\": -1.8508036335748652, \"ulcer\": -0.14791776396352982}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": 1.073469128932291, \"optuna_trial_number\": 222, \"price_ratio\": 1.0592750452871305, \"shift_exponent\": 0.03632590132148495, \"step\": 222, \"test_objective\": [{\"annualised_returns\": -0.7203842262033158, \"annualised_returns_over_hodl\": 1.7394072877422642, \"annualised_returns_over_uniform_hodl\": 1.4561865044071833, \"calmar\": -1.0149061809334687, \"daily_log_sharpe\": -1.542081283928664, \"daily_returns\": 0.01048792722812917, \"fee_revenue_over_value\": 0.27814600889446545, \"jax_sharpe\": -0.2078796502466599, \"return\": -0.4014375747838158, \"returns_over_hodl\": 0.5005859272124751, \"returns_over_uniform_hodl\": 0.4360613802854296, \"sharpe\": -1.1283899954362164, \"sterling\": -1.8508036335748652, \"ulcer\": -0.14791776396352982}], \"train_objective\": [{\"annualised_returns\": 1.306159410350447, \"annualised_returns_over_hodl\": 0.9071609649178014, \"annualised_returns_over_uniform_hodl\": 0.9071609649178027, \"calmar\": 2.0973496606652717, \"daily_log_sharpe\": 0.9562645378540656, \"daily_returns\": 0.022596822650439347, \"fee_revenue_over_value\": 0.6428134464369497, \"jax_sharpe\": 1.3820285506755197, \"return\": 0.6608020378153119, \"returns_over_hodl\": 0.4798890466829746, \"returns_over_uniform_hodl\": 0.4798890466829753, \"sharpe\": 1.3940454779348357, \"sterling\": 5.005048459666625, \"ulcer\": -0.12923874533166801}], \"train_return\": 0.6608020378153119, \"train_returns_over_hodl\": 0.4798890466829746, \"train_sharpe\": 1.3820285506755197, \"validation_return\": 0.060931381332379075, \"validation_returns_over_hodl\": 0.10506406604920038, \"validation_sharpe\": 0.921788198652513}, {\"centeredness_margin\": 0.033317617959703685, \"continuous_test_metrics\": [{\"annualised_returns\": -0.7207580074601586, \"annualised_returns_over_hodl\": 1.7872188999952034, \"annualised_returns_over_uniform_hodl\": 1.4529031543078967, \"calmar\": -1.0120530590461543, \"daily_log_sharpe\": -1.5588809504722918, \"daily_returns\": 0.007841752010721943, \"fee_revenue_over_value\": 0.2837337753299989, \"jax_sharpe\": -0.17879577252241458, \"return\": -0.4017599496546481, \"returns_over_hodl\": 0.5110792123979622, \"returns_over_uniform_hodl\": 0.43528794366048684, \"sharpe\": -1.1191899869706372, \"sterling\": -1.8207463005435247, \"ulcer\": -0.15721712394342868}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -0.01086804466193736, \"optuna_trial_number\": 223, \"price_ratio\": 1.0168327440824674, \"shift_exponent\": 0.006862544515227025, \"step\": 223, \"test_objective\": [{\"annualised_returns\": -0.7207580074601586, \"annualised_returns_over_hodl\": 1.7872188999952034, \"annualised_returns_over_uniform_hodl\": 1.4529031543078967, \"calmar\": -1.0120530590461543, \"daily_log_sharpe\": -1.5588809504722918, \"daily_returns\": 0.007841752010721943, \"fee_revenue_over_value\": 0.2837337753299989, \"jax_sharpe\": -0.17879577252241458, \"return\": -0.4017599496546481, \"returns_over_hodl\": 0.5110792123979622, \"returns_over_uniform_hodl\": 0.43528794366048684, \"sharpe\": -1.1191899869706372, \"sterling\": -1.8207463005435247, \"ulcer\": -0.15721712394342868}], \"train_objective\": [{\"annualised_returns\": 0.7601795735236823, \"annualised_returns_over_hodl\": 0.4556434211804583, \"annualised_returns_over_uniform_hodl\": 0.45564342118045786, \"calmar\": 1.269579396305899, \"daily_log_sharpe\": 0.6578142198034216, \"daily_returns\": 0.019844711241069363, \"fee_revenue_over_value\": 0.3394386588122717, \"jax_sharpe\": 1.0762035574691478, \"return\": 0.40955780882576165, \"returns_over_hodl\": 0.2560131276643285, \"returns_over_uniform_hodl\": 0.25601312766432827, \"sharpe\": 1.1071531045308067, \"sterling\": 2.8986099881120864, \"ulcer\": -0.12412955115768845}], \"train_return\": 0.40955780882576165, \"train_returns_over_hodl\": 0.2560131276643285, \"train_sharpe\": 1.0762035574691475, \"validation_return\": -0.06990342222608503, \"validation_returns_over_hodl\": -3.1336333528031446e-10, \"validation_sharpe\": -0.2859676614902784}, {\"centeredness_margin\": 0.011783116281334077, \"continuous_test_metrics\": [{\"annualised_returns\": -0.8001759369669248, \"annualised_returns_over_hodl\": 0.9769488794110825, \"annualised_returns_over_uniform_hodl\": 0.755284261017859, \"calmar\": -1.2089524822401678, \"daily_log_sharpe\": -1.8608819326134252, \"daily_returns\": 0.009279041290921896, \"fee_revenue_over_value\": 0.16180980274618628, \"jax_sharpe\": -0.6312608235212529, \"return\": -0.47718936763442577, \"returns_over_hodl\": 0.3158565874646204, \"returns_over_uniform_hodl\": 0.2543188925894251, \"sharpe\": -1.4442694269606446, \"sterling\": -2.047618085351599, \"ulcer\": -0.16013024264044198}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": 0.9265714043063499, \"optuna_trial_number\": 224, \"price_ratio\": 1.2337156390969488, \"shift_exponent\": 0.10955966642768342, \"step\": 224, \"test_objective\": [{\"annualised_returns\": -0.8001759369669248, \"annualised_returns_over_hodl\": 0.9769488794110825, \"annualised_returns_over_uniform_hodl\": 0.755284261017859, \"calmar\": -1.2089524822401678, \"daily_log_sharpe\": -1.8608819326134252, \"daily_returns\": 0.009279041290921896, \"fee_revenue_over_value\": 0.16180980274618628, \"jax_sharpe\": -0.6312608235212529, \"return\": -0.47718936763442577, \"returns_over_hodl\": 0.3158565874646204, \"returns_over_uniform_hodl\": 0.2543188925894251, \"sharpe\": -1.4442694269606446, \"sterling\": -2.047618085351599, \"ulcer\": -0.16013024264044198}], \"train_objective\": [{\"annualised_returns\": 1.0978938614814124, \"annualised_returns_over_hodl\": 0.734928324208961, \"annualised_returns_over_uniform_hodl\": 0.7349283242089599, \"calmar\": 1.7854856756773003, \"daily_log_sharpe\": 0.8542836407561158, \"daily_returns\": 0.021379881820684742, \"fee_revenue_over_value\": 0.38264856455752155, \"jax_sharpe\": 1.2782691826530808, \"return\": 0.5680562998043108, \"returns_over_hodl\": 0.3972462038372633, \"returns_over_uniform_hodl\": 0.39724620383726283, \"sharpe\": 1.2954184630006043, \"sterling\": 4.270269496214562, \"ulcer\": -0.12559130206742475}], \"train_return\": 0.5680562998043108, \"train_returns_over_hodl\": 0.3972462038372633, \"train_sharpe\": 1.2782691826530808, \"validation_return\": -0.002878326490519778, \"validation_returns_over_hodl\": 0.05620947967051326, \"validation_sharpe\": 0.3164535984425929}, {\"centeredness_margin\": 0.1402234483212457, \"continuous_test_metrics\": [{\"annualised_returns\": -0.7999471072795231, \"annualised_returns_over_hodl\": 0.9968028396561606, \"annualised_returns_over_uniform_hodl\": 0.7572943349932006, \"calmar\": -1.1232519693181733, \"daily_log_sharpe\": -1.8076517281931803, \"daily_returns\": 0.00784175201200595, \"fee_revenue_over_value\": 0.14798894918745387, \"jax_sharpe\": -0.4059792381365124, \"return\": -0.4769483310540108, \"returns_over_hodl\": 0.32116280669526076, \"returns_over_uniform_hodl\": 0.25489718369121794, \"sharpe\": -1.372884256192213, \"sterling\": -1.9451636009735722, \"ulcer\": -0.16663659614192292}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": 0.8164027705481156, \"optuna_trial_number\": 225, \"price_ratio\": 1.160745018841232, \"shift_exponent\": 0.005392401272878346, \"step\": 225, \"test_objective\": [{\"annualised_returns\": -0.7999471072795231, \"annualised_returns_over_hodl\": 0.9968028396561606, \"annualised_returns_over_uniform_hodl\": 0.7572943349932006, \"calmar\": -1.1232519693181733, \"daily_log_sharpe\": -1.8076517281931803, \"daily_returns\": 0.00784175201200595, \"fee_revenue_over_value\": 0.14798894918745387, \"jax_sharpe\": -0.4059792381365124, \"return\": -0.4769483310540108, \"returns_over_hodl\": 0.32116280669526076, \"returns_over_uniform_hodl\": 0.25489718369121794, \"sharpe\": -1.372884256192213, \"sterling\": -1.9451636009735722, \"ulcer\": -0.16663659614192292}], \"train_objective\": [{\"annualised_returns\": 0.9840516825285988, \"annualised_returns_over_hodl\": 0.6407824646965863, \"annualised_returns_over_uniform_hodl\": 0.6407824646965863, \"calmar\": 1.6727179163076717, \"daily_log_sharpe\": 0.8108619339042902, \"daily_returns\": 0.02168547619446183, \"fee_revenue_over_value\": 0.29562065906400686, \"jax_sharpe\": 1.2177804116483208, \"return\": 0.5158309769433866, \"returns_over_hodl\": 0.3507098427890607, \"returns_over_uniform_hodl\": 0.3507098427890607, \"sharpe\": 1.2493051474333343, \"sterling\": 3.869196306702728, \"ulcer\": -0.1222050913884953}], \"train_return\": 0.5158309769433866, \"train_returns_over_hodl\": 0.3507098427890607, \"train_sharpe\": 1.2177804116483208, \"validation_return\": -0.0692894983165997, \"validation_returns_over_hodl\": 0.0006600644411098866, \"validation_sharpe\": -0.28070162323044595}, {\"centeredness_margin\": 0.09920102705711362, \"continuous_test_metrics\": [{\"annualised_returns\": -0.8995912984343396, \"annualised_returns_over_hodl\": 0.0022168521324350543, \"annualised_returns_over_uniform_hodl\": -0.11799504598067156, \"calmar\": -1.26316341709714, \"daily_log_sharpe\": -2.3735378808398706, \"daily_returns\": 0.007841752011450778, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": -0.830008507173497, \"return\": -0.6037442596929107, \"returns_over_hodl\": 0.0008922198402563364, \"returns_over_uniform_hodl\": -0.04930957674815761, \"sharpe\": -1.9321437042001381, \"sterling\": -2.0830910269559015, \"ulcer\": -0.1810804688059821}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -0.010797286214722313, \"optuna_trial_number\": 226, \"price_ratio\": 1.023684776420302, \"shift_exponent\": 0.001283049357195803, \"step\": 226, \"test_objective\": [{\"annualised_returns\": -0.8995912984343396, \"annualised_returns_over_hodl\": 0.0022168521324350543, \"annualised_returns_over_uniform_hodl\": -0.11799504598067156, \"calmar\": -1.26316341709714, \"daily_log_sharpe\": -2.3735378808398706, \"daily_returns\": 0.007841752011450778, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": -0.830008507173497, \"return\": -0.6037442596929107, \"returns_over_hodl\": 0.0008922198402563364, \"returns_over_uniform_hodl\": -0.04930957674815761, \"sharpe\": -1.9321437042001381, \"sterling\": -2.0830910269559015, \"ulcer\": -0.1810804688059821}], \"train_objective\": [{\"annualised_returns\": 0.6390228679106216, \"annualised_returns_over_hodl\": 0.35544855236687756, \"annualised_returns_over_uniform_hodl\": 0.35544855236687734, \"calmar\": 1.1083906691975804, \"daily_log_sharpe\": 0.5766531778046539, \"daily_returns\": 0.01971991961248997, \"fee_revenue_over_value\": 0.13933720956767334, \"jax_sharpe\": 0.9985532186408644, \"return\": 0.3498302305258987, \"returns_over_hodl\": 0.20279174010682088, \"returns_over_uniform_hodl\": 0.20279174010682066, \"sharpe\": 1.0264282377857274, \"sterling\": 2.5083994579958295, \"ulcer\": -0.12331412149650856}], \"train_return\": 0.3498302305258987, \"train_returns_over_hodl\": 0.20279174010682088, \"train_sharpe\": 0.9985532186408643, \"validation_return\": -0.06990342225881574, \"validation_returns_over_hodl\": -3.113029833912151e-10, \"validation_sharpe\": -0.28596766171586135}, {\"centeredness_margin\": 0.2086992174015292, \"continuous_test_metrics\": [{\"annualised_returns\": -0.7338615634167613, \"annualised_returns_over_hodl\": 1.5100239975645398, \"annualised_returns_over_uniform_hodl\": 1.3377995717619608, \"calmar\": -1.0377514599223456, \"daily_log_sharpe\": -1.6522827514611522, \"daily_returns\": 0.010897808743171698, \"fee_revenue_over_value\": 0.3290198174426053, \"jax_sharpe\": -0.15139380482499779, \"return\": -0.41322839864759264, \"returns_over_hodl\": 0.4486564249984011, \"returns_over_uniform_hodl\": 0.4077730245865192, \"sharpe\": -1.2549384070328224, \"sterling\": -1.89523122913334, \"ulcer\": -0.1474002526490162}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": 0.9609433386128896, \"optuna_trial_number\": 227, \"price_ratio\": 1.0253024296749087, \"shift_exponent\": 0.05839645344218889, \"step\": 227, \"test_objective\": [{\"annualised_returns\": -0.7338615634167613, \"annualised_returns_over_hodl\": 1.5100239975645398, \"annualised_returns_over_uniform_hodl\": 1.3377995717619608, \"calmar\": -1.0377514599223456, \"daily_log_sharpe\": -1.6522827514611522, \"daily_returns\": 0.010897808743171698, \"fee_revenue_over_value\": 0.3290198174426053, \"jax_sharpe\": -0.15139380482499779, \"return\": -0.41322839864759264, \"returns_over_hodl\": 0.4486564249984011, \"returns_over_uniform_hodl\": 0.4077730245865192, \"sharpe\": -1.2549384070328224, \"sterling\": -1.89523122913334, \"ulcer\": -0.1474002526490162}], \"train_objective\": [{\"annualised_returns\": 0.8146422343201858, \"annualised_returns_over_hodl\": 0.500683265455953, \"annualised_returns_over_uniform_hodl\": 0.5006832654559525, \"calmar\": 1.1836776023264624, \"daily_log_sharpe\": 0.6798503474460228, \"daily_returns\": 0.023255038941881426, \"fee_revenue_over_value\": 0.7182742324595413, \"jax_sharpe\": 1.1152629273308, \"return\": 0.43587805859367723, \"returns_over_hodl\": 0.27946628370015336, \"returns_over_uniform_hodl\": 0.27946628370015314, \"sharpe\": 1.1152939074206425, \"sterling\": 3.0170918478542754, \"ulcer\": -0.14005460066016273}], \"train_return\": 0.43587805859367723, \"train_returns_over_hodl\": 0.27946628370015336, \"train_sharpe\": 1.1152629273307997, \"validation_return\": 0.08077679558807249, \"validation_returns_over_hodl\": 0.09447582669438326, \"validation_sharpe\": 1.121821288566343}, {\"centeredness_margin\": 0.19933634044752396, \"continuous_test_metrics\": [{\"annualised_returns\": -0.8523602733926506, \"annualised_returns_over_hodl\": 0.3944494976905222, \"annualised_returns_over_uniform_hodl\": 0.29688929592010727, \"calmar\": -1.2965457018508206, \"daily_log_sharpe\": -2.243661713158132, \"daily_returns\": 0.007840341475812715, \"fee_revenue_over_value\": 0.07598315911819341, \"jax_sharpe\": -0.8957831610701518, \"return\": -0.537185754585024, \"returns_over_hodl\": 0.14329015939676681, \"returns_over_uniform_hodl\": 0.11037652229229722, \"sharpe\": -1.8432703699755322, \"sterling\": -2.169589176269575, \"ulcer\": -0.16231753401753268}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": 0.7643342771913968, \"optuna_trial_number\": 228, \"price_ratio\": 2.121292875820443, \"shift_exponent\": 0.1139275912586939, \"step\": 228, \"test_objective\": [{\"annualised_returns\": -0.8523602733926506, \"annualised_returns_over_hodl\": 0.3944494976905222, \"annualised_returns_over_uniform_hodl\": 0.29688929592010727, \"calmar\": -1.2965457018508206, \"daily_log_sharpe\": -2.243661713158132, \"daily_returns\": 0.007840341475812715, \"fee_revenue_over_value\": 0.07598315911819341, \"jax_sharpe\": -0.8957831610701518, \"return\": -0.537185754585024, \"returns_over_hodl\": 0.14329015939676681, \"returns_over_uniform_hodl\": 0.11037652229229722, \"sharpe\": -1.8432703699755322, \"sterling\": -2.169589176269575, \"ulcer\": -0.16231753401753268}], \"train_objective\": [{\"annualised_returns\": 0.6510399438476429, \"annualised_returns_over_hodl\": 0.3653865028991248, \"annualised_returns_over_uniform_hodl\": 0.3653865028991248, \"calmar\": 1.0376147807243592, \"daily_log_sharpe\": 0.606534179287132, \"daily_returns\": 0.020680308836724882, \"fee_revenue_over_value\": 0.15029569054885555, \"jax_sharpe\": 1.0113611249495682, \"return\": 0.3558301392948422, \"returns_over_hodl\": 0.2081380722199102, \"returns_over_uniform_hodl\": 0.2081380722199102, \"sharpe\": 1.032298740889533, \"sterling\": 2.519074957934942, \"ulcer\": -0.12962619776890838}], \"train_return\": 0.3558301392948422, \"train_returns_over_hodl\": 0.2081380722199102, \"train_sharpe\": 1.0113611249495682, \"validation_return\": -0.014956311581154402, \"validation_returns_over_hodl\": 0.023376621502761985, \"validation_sharpe\": 0.18355179149480286}, {\"centeredness_margin\": 0.19135876684705277, \"continuous_test_metrics\": [{\"annualised_returns\": -0.7671456819405985, \"annualised_returns_over_hodl\": 1.2085863427092107, \"annualised_returns_over_uniform_hodl\": 1.0454269290483849, \"calmar\": -1.3131397707044918, \"daily_log_sharpe\": -1.8190521340051415, \"daily_returns\": 0.009399157523532679, \"fee_revenue_over_value\": 0.22055849822139464, \"jax_sharpe\": -0.8372593151123509, \"return\": -0.4439665651689628, \"returns_over_hodl\": 0.37590318929550204, \"returns_over_uniform_hodl\": 0.3340265079618254, \"sharpe\": -1.4268744026963902, \"sterling\": -2.178477649875201, \"ulcer\": -0.14785417155493957}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": 0.9428758262288973, \"optuna_trial_number\": 229, \"price_ratio\": 1.1061866989684195, \"shift_exponent\": 0.16883630383753584, \"step\": 229, \"test_objective\": [{\"annualised_returns\": -0.7671456819405985, \"annualised_returns_over_hodl\": 1.2085863427092107, \"annualised_returns_over_uniform_hodl\": 1.0454269290483849, \"calmar\": -1.3131397707044918, \"daily_log_sharpe\": -1.8190521340051415, \"daily_returns\": 0.009399157523532679, \"fee_revenue_over_value\": 0.22055849822139464, \"jax_sharpe\": -0.8372593151123509, \"return\": -0.4439665651689628, \"returns_over_hodl\": 0.37590318929550204, \"returns_over_uniform_hodl\": 0.3340265079618254, \"sharpe\": -1.4268744026963902, \"sterling\": -2.178477649875201, \"ulcer\": -0.14785417155493957}], \"train_objective\": [{\"annualised_returns\": 0.813340289897676, \"annualised_returns_over_hodl\": 0.49960657597388214, \"annualised_returns_over_uniform_hodl\": 0.4996065759738817, \"calmar\": 1.2135943992093936, \"daily_log_sharpe\": 0.6957900475747044, \"daily_returns\": 0.022254077372322784, \"fee_revenue_over_value\": 0.5004920840584599, \"jax_sharpe\": 1.1162953090062362, \"return\": 0.4352525176432265, \"returns_over_hodl\": 0.278908883612879, \"returns_over_uniform_hodl\": 0.2789088836128788, \"sharpe\": 1.1266238388852292, \"sterling\": 3.0884378032332616, \"ulcer\": -0.1367686573954318}], \"train_return\": 0.4352525176432265, \"train_returns_over_hodl\": 0.278908883612879, \"train_sharpe\": 1.1162953090062362, \"validation_return\": 0.04337922908412062, \"validation_returns_over_hodl\": 0.07352838250186955, \"validation_sharpe\": 0.7567570308250786}, {\"centeredness_margin\": 0.18949637752124635, \"continuous_test_metrics\": [{\"annualised_returns\": -0.855163888146927, \"annualised_returns_over_hodl\": 0.36809069641775927, \"annualised_returns_over_uniform_hodl\": 0.2722619273367537, \"calmar\": -1.3049358661452044, \"daily_log_sharpe\": -2.2625306516685018, \"daily_returns\": 0.007856165202389825, \"fee_revenue_over_value\": 0.07091552897493572, \"jax_sharpe\": -0.9212202584084941, \"return\": -0.5407455511772259, \"returns_over_hodl\": 0.1345368706820882, \"returns_over_uniform_hodl\": 0.1018359153440973, \"sharpe\": -1.8624721714623196, \"sterling\": -2.1771738904056197, \"ulcer\": -0.16280236320523045}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": 0.7426297976427982, \"optuna_trial_number\": 230, \"price_ratio\": 2.319128096352635, \"shift_exponent\": 0.27020131131972774, \"step\": 230, \"test_objective\": [{\"annualised_returns\": -0.855163888146927, \"annualised_returns_over_hodl\": 0.36809069641775927, \"annualised_returns_over_uniform_hodl\": 0.2722619273367537, \"calmar\": -1.3049358661452044, \"daily_log_sharpe\": -2.2625306516685018, \"daily_returns\": 0.007856165202389825, \"fee_revenue_over_value\": 0.07091552897493572, \"jax_sharpe\": -0.9212202584084941, \"return\": -0.5407455511772259, \"returns_over_hodl\": 0.1345368706820882, \"returns_over_uniform_hodl\": 0.1018359153440973, \"sharpe\": -1.8624721714623196, \"sterling\": -2.1771738904056197, \"ulcer\": -0.16280236320523045}], \"train_objective\": [{\"annualised_returns\": 0.6074047797287039, \"annualised_returns_over_hodl\": 0.32930084406222093, \"annualised_returns_over_uniform_hodl\": 0.32930084406222093, \"calmar\": 0.9648770129213425, \"daily_log_sharpe\": 0.575849791486167, \"daily_returns\": 0.020659138926673776, \"fee_revenue_over_value\": 0.1367307438674522, \"jax_sharpe\": 0.9805935366907879, \"return\": 0.33396074791238006, \"returns_over_hodl\": 0.1886509376743013, \"returns_over_uniform_hodl\": 0.1886509376743013, \"sharpe\": 1.0013989182314724, \"sterling\": 2.346003318666266, \"ulcer\": -0.1301867107191452}], \"train_return\": 0.33396074791238006, \"train_returns_over_hodl\": 0.1886509376743013, \"train_sharpe\": 0.980593536690788, \"validation_return\": -0.015616065686820502, \"validation_returns_over_hodl\": 0.019009096686690485, \"validation_sharpe\": 0.17764806669361363}, {\"centeredness_margin\": 0.22677712430746008, \"continuous_test_metrics\": [{\"annualised_returns\": -0.6825673731359148, \"annualised_returns_over_hodl\": 1.9535219116485085, \"annualised_returns_over_uniform_hodl\": 1.7883753608586046, \"calmar\": -0.9665490517438758, \"daily_log_sharpe\": -1.4486548198030316, \"daily_returns\": 0.012525462598720805, \"fee_revenue_over_value\": 0.41238110766396, \"jax_sharpe\": 0.009608283994664487, \"return\": -0.3700642802571906, \"returns_over_hodl\": 0.5467630444477054, \"returns_over_uniform_hodl\": 0.5113316858441761, \"sharpe\": -1.0464208443363407, \"sterling\": -1.7760354094331232, \"ulcer\": -0.14330331133764318}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": 0.8180596969220795, \"optuna_trial_number\": 231, \"price_ratio\": 1.0127223636312679, \"shift_exponent\": 0.03892240311096177, \"step\": 231, \"test_objective\": [{\"annualised_returns\": -0.6825673731359148, \"annualised_returns_over_hodl\": 1.9535219116485085, \"annualised_returns_over_uniform_hodl\": 1.7883753608586046, \"calmar\": -0.9665490517438758, \"daily_log_sharpe\": -1.4486548198030316, \"daily_returns\": 0.012525462598720805, \"fee_revenue_over_value\": 0.41238110766396, \"jax_sharpe\": 0.009608283994664487, \"return\": -0.3700642802571906, \"returns_over_hodl\": 0.5467630444477054, \"returns_over_uniform_hodl\": 0.5113316858441761, \"sharpe\": -1.0464208443363407, \"sterling\": -1.7760354094331232, \"ulcer\": -0.14330331133764318}], \"train_objective\": [{\"annualised_returns\": 0.5600021443272429, \"annualised_returns_over_hodl\": 0.29009954016876693, \"annualised_returns_over_uniform_hodl\": 0.2900995401687667, \"calmar\": 0.7938410037652832, \"daily_log_sharpe\": 0.4883040026591887, \"daily_returns\": 0.02222794365872947, \"fee_revenue_over_value\": 0.6838404362777668, \"jax_sharpe\": 0.9443229717709712, \"return\": 0.3099370809636406, \"returns_over_hodl\": 0.16724419516730982, \"returns_over_uniform_hodl\": 0.1672441951673096, \"sharpe\": 0.9303418961276017, \"sterling\": 2.0139939495000987, \"ulcer\": -0.1437530905879513}], \"train_return\": 0.3099370809636406, \"train_returns_over_hodl\": 0.16724419516730982, \"train_sharpe\": 0.9443229717709714, \"validation_return\": 0.1227179605495663, \"validation_returns_over_hodl\": 0.11746412944970408, \"validation_sharpe\": 1.5183546857835328}, {\"centeredness_margin\": 0.061869374407890754, \"continuous_test_metrics\": [{\"annualised_returns\": -0.7971232992069877, \"annualised_returns_over_hodl\": 0.9769314192756142, \"annualised_returns_over_uniform_hodl\": 0.7820990846846134, \"calmar\": -1.122919093271192, \"daily_log_sharpe\": -1.8960419314825372, \"daily_returns\": 0.009078431418757197, \"fee_revenue_over_value\": 0.16335975292083477, \"jax_sharpe\": -0.43733050651072397, \"return\": -0.47398734403297293, \"returns_over_hodl\": 0.31585190705037314, \"returns_over_uniform_hodl\": 0.2620011363105341, \"sharpe\": -1.491901620048801, \"sterling\": -2.011221832454876, \"ulcer\": -0.15734645178415502}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": 0.8519033420626777, \"optuna_trial_number\": 232, \"price_ratio\": 1.231371150783046, \"shift_exponent\": 2.3166052762109905, \"step\": 232, \"test_objective\": [{\"annualised_returns\": -0.7971232992069877, \"annualised_returns_over_hodl\": 0.9769314192756142, \"annualised_returns_over_uniform_hodl\": 0.7820990846846134, \"calmar\": -1.122919093271192, \"daily_log_sharpe\": -1.8960419314825372, \"daily_returns\": 0.009078431418757197, \"fee_revenue_over_value\": 0.16335975292083477, \"jax_sharpe\": -0.43733050651072397, \"return\": -0.47398734403297293, \"returns_over_hodl\": 0.31585190705037314, \"returns_over_uniform_hodl\": 0.2620011363105341, \"sharpe\": -1.491901620048801, \"sterling\": -2.011221832454876, \"ulcer\": -0.15734645178415502}], \"train_objective\": [{\"annualised_returns\": 0.7867752377651835, \"annualised_returns_over_hodl\": 0.4776376564661029, \"annualised_returns_over_uniform_hodl\": 0.47763765646610246, \"calmar\": 1.2182834906966253, \"daily_log_sharpe\": 0.6767685551482827, \"daily_returns\": 0.021417080006879464, \"fee_revenue_over_value\": 0.3656659606891972, \"jax_sharpe\": 1.0983539253176107, \"return\": 0.42245010632347, \"returns_over_hodl\": 0.2675010530275068, \"returns_over_uniform_hodl\": 0.2675010530275066, \"sharpe\": 1.113160510807963, \"sterling\": 2.9926503396964823, \"ulcer\": -0.13256560229033057}], \"train_return\": 0.42245010632347, \"train_returns_over_hodl\": 0.2675010530275068, \"train_sharpe\": 1.098353925317611, \"validation_return\": 0.005600367070161871, \"validation_returns_over_hodl\": 0.056806129935595084, \"validation_sharpe\": 0.39086189307055536}, {\"centeredness_margin\": 0.22763315561148884, \"continuous_test_metrics\": [{\"annualised_returns\": -0.8730288619963498, \"annualised_returns_over_hodl\": 0.1301631697914234, \"annualised_returns_over_uniform_hodl\": 0.11533334253364647, \"calmar\": -1.3339158620942333, \"daily_log_sharpe\": -2.5295105486910514, \"daily_returns\": 0.00676350347463027, \"fee_revenue_over_value\": 0.025744226792177225, \"jax_sharpe\": -1.6229821808325033, \"return\": -0.5644599392176886, \"returns_over_hodl\": 0.05051425794779196, \"returns_over_uniform_hodl\": 0.04494073551433875, \"sharpe\": -2.149243101344984, \"sterling\": -2.4492951911122636, \"ulcer\": -0.1582717354535485}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": 0.5798661635280478, \"optuna_trial_number\": 233, \"price_ratio\": 54.51592435526114, \"shift_exponent\": 0.005534906107729557, \"step\": 233, \"test_objective\": [{\"annualised_returns\": -0.8730288619963498, \"annualised_returns_over_hodl\": 0.1301631697914234, \"annualised_returns_over_uniform_hodl\": 0.11533334253364647, \"calmar\": -1.3339158620942333, \"daily_log_sharpe\": -2.5295105486910514, \"daily_returns\": 0.00676350347463027, \"fee_revenue_over_value\": 0.025744226792177225, \"jax_sharpe\": -1.6229821808325033, \"return\": -0.5644599392176886, \"returns_over_hodl\": 0.05051425794779196, \"returns_over_uniform_hodl\": 0.04494073551433875, \"sharpe\": -2.149243101344984, \"sterling\": -2.4492951911122636, \"ulcer\": -0.1582717354535485}], \"train_objective\": [{\"annualised_returns\": 0.3234705245814893, \"annualised_returns_over_hodl\": 0.09449126169363176, \"annualised_returns_over_uniform_hodl\": 0.09449126169363176, \"calmar\": 0.5033809553318151, \"daily_log_sharpe\": 0.3525927688855786, \"daily_returns\": 0.020428256020900978, \"fee_revenue_over_value\": 0.042919743261203284, \"jax_sharpe\": 0.7582200463255827, \"return\": 0.1854830211893559, \"returns_over_hodl\": 0.056347052894129224, \"returns_over_uniform_hodl\": 0.056347052894129224, \"sharpe\": 0.7771151611528282, \"sterling\": 1.2339672328623843, \"ulcer\": -0.13403761407275516}], \"train_return\": 0.1854830211893559, \"train_returns_over_hodl\": 0.056347052894129224, \"train_sharpe\": 0.7582200463255826, \"validation_return\": -0.0015118415121168072, \"validation_returns_over_hodl\": 0.0052402402185574015, \"validation_sharpe\": 0.2988167107294462}, {\"centeredness_margin\": 0.011944155476121147, \"continuous_test_metrics\": [{\"annualised_returns\": -0.719862300116876, \"annualised_returns_over_hodl\": 1.7631944694330652, \"annualised_returns_over_uniform_hodl\": 1.4607711807021042, \"calmar\": -1.0137077652986546, \"daily_log_sharpe\": -1.5560310184019206, \"daily_returns\": 0.010297260205783155, \"fee_revenue_over_value\": 0.2648593203030769, \"jax_sharpe\": -0.21341810176169693, \"return\": -0.40098785986579366, \"returns_over_hodl\": 0.5058200899869509, \"returns_over_uniform_hodl\": 0.43714032911132117, \"sharpe\": -1.1472202698067349, \"sterling\": -1.8652775081580801, \"ulcer\": -0.14744314071761053}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": 1.0744519918588877, \"optuna_trial_number\": 234, \"price_ratio\": 1.0650105028527714, \"shift_exponent\": 0.09445393172417607, \"step\": 234, \"test_objective\": [{\"annualised_returns\": -0.719862300116876, \"annualised_returns_over_hodl\": 1.7631944694330652, \"annualised_returns_over_uniform_hodl\": 1.4607711807021042, \"calmar\": -1.0137077652986546, \"daily_log_sharpe\": -1.5560310184019206, \"daily_returns\": 0.010297260205783155, \"fee_revenue_over_value\": 0.2648593203030769, \"jax_sharpe\": -0.21341810176169693, \"return\": -0.40098785986579366, \"returns_over_hodl\": 0.5058200899869509, \"returns_over_uniform_hodl\": 0.43714032911132117, \"sharpe\": -1.1472202698067349, \"sterling\": -1.8652775081580801, \"ulcer\": -0.14744314071761053}], \"train_objective\": [{\"annualised_returns\": 1.1166163531990305, \"annualised_returns_over_hodl\": 0.7504115580260051, \"annualised_returns_over_uniform_hodl\": 0.7504115580260069, \"calmar\": 1.689885674961102, \"daily_log_sharpe\": 0.8645122509998346, \"daily_returns\": 0.022824467528229038, \"fee_revenue_over_value\": 0.6481834903186184, \"jax_sharpe\": 1.2872944498520376, \"return\": 0.5765375217345126, \"returns_over_hodl\": 0.40480355694209313, \"returns_over_uniform_hodl\": 0.404803556942094, \"sharpe\": 1.2994931057853356, \"sterling\": 4.206482843476784, \"ulcer\": -0.13428440463946917}], \"train_return\": 0.5765375217345126, \"train_returns_over_hodl\": 0.40480355694209313, \"train_sharpe\": 1.2872944498520373, \"validation_return\": 0.049682785434062016, \"validation_returns_over_hodl\": 0.09923321082753378, \"validation_sharpe\": 0.8137933224985586}, {\"centeredness_margin\": 0.7011239284413853, \"continuous_test_metrics\": [{\"annualised_returns\": -0.8849599129486416, \"annualised_returns_over_hodl\": 0.011083698924235774, \"annualised_returns_over_uniform_hodl\": 0.010529218165032272, \"calmar\": -1.329127527062925, \"daily_log_sharpe\": -2.6286262634027597, \"daily_returns\": 0.006600619852721574, \"fee_revenue_over_value\": 0.02244570282229105, \"jax_sharpe\": -1.4108805280054664, \"return\": -0.5814296593030901, \"returns_over_hodl\": 0.0044491363687317875, \"returns_over_uniform_hodl\": 0.004227254977872574, \"sharpe\": -2.25807929894759, \"sterling\": -2.4515482650816667, \"ulcer\": -0.16275121368402798}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": 0.5698118530288782, \"optuna_trial_number\": 235, \"price_ratio\": 114.86020526159682, \"shift_exponent\": 122.2160731123206, \"step\": 235, \"test_objective\": [{\"annualised_returns\": -0.8849599129486416, \"annualised_returns_over_hodl\": 0.011083698924235774, \"annualised_returns_over_uniform_hodl\": 0.010529218165032272, \"calmar\": -1.329127527062925, \"daily_log_sharpe\": -2.6286262634027597, \"daily_returns\": 0.006600619852721574, \"fee_revenue_over_value\": 0.02244570282229105, \"jax_sharpe\": -1.4108805280054664, \"return\": -0.5814296593030901, \"returns_over_hodl\": 0.0044491363687317875, \"returns_over_uniform_hodl\": 0.004227254977872574, \"sharpe\": -2.25807929894759, \"sterling\": -2.4515482650816667, \"ulcer\": -0.16275121368402798}], \"train_objective\": [{\"annualised_returns\": 0.3086861754260253, \"annualised_returns_over_hodl\": 0.08226481564898003, \"annualised_returns_over_uniform_hodl\": 0.08226481564897958, \"calmar\": 0.47998853076775994, \"daily_log_sharpe\": 0.3395336303111552, \"daily_returns\": 0.020423627625672337, \"fee_revenue_over_value\": 0.039083361765294665, \"jax_sharpe\": 0.7454042105838774, \"return\": 0.17742523491178708, \"returns_over_hodl\": 0.049167010130951816, \"returns_over_uniform_hodl\": 0.049167010130951594, \"sharpe\": 0.7640579881188977, \"sterling\": 1.1769364531809257, \"ulcer\": -0.13419638649438143}], \"train_return\": 0.17742523491178708, \"train_returns_over_hodl\": 0.049167010130951816, \"train_sharpe\": 0.7454042105838774, \"validation_return\": 0.004289216420949371, \"validation_returns_over_hodl\": 0.004696212454304849, \"validation_sharpe\": 0.3564303463508494}, {\"centeredness_margin\": 0.0825006900405266, \"continuous_test_metrics\": [{\"annualised_returns\": -0.7028959687147951, \"annualised_returns_over_hodl\": 1.8651600173069447, \"annualised_returns_over_uniform_hodl\": 1.6098059567208276, \"calmar\": -0.9928174850493543, \"daily_log_sharpe\": -1.5248164497238936, \"daily_returns\": 0.011361571177868282, \"fee_revenue_over_value\": 0.3613471783745413, \"jax_sharpe\": -0.18260737943846223, \"return\": -0.386633101861944, \"returns_over_hodl\": 0.5279570468350059, \"returns_over_uniform_hodl\": 0.47158003451920116, \"sharpe\": -1.1251960149111941, \"sterling\": -1.8286729422191854, \"ulcer\": -0.14603426721650617}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": 1.0291259511529678, \"optuna_trial_number\": 236, \"price_ratio\": 1.0115574122291004, \"shift_exponent\": 0.15575900281805957, \"step\": 236, \"test_objective\": [{\"annualised_returns\": -0.7028959687147951, \"annualised_returns_over_hodl\": 1.8651600173069447, \"annualised_returns_over_uniform_hodl\": 1.6098059567208276, \"calmar\": -0.9928174850493543, \"daily_log_sharpe\": -1.5248164497238936, \"daily_returns\": 0.011361571177868282, \"fee_revenue_over_value\": 0.3613471783745413, \"jax_sharpe\": -0.18260737943846223, \"return\": -0.386633101861944, \"returns_over_hodl\": 0.5279570468350059, \"returns_over_uniform_hodl\": 0.47158003451920116, \"sharpe\": -1.1251960149111941, \"sterling\": -1.8286729422191854, \"ulcer\": -0.14603426721650617}], \"train_objective\": [{\"annualised_returns\": 0.582114524984092, \"annualised_returns_over_hodl\": 0.30838616382577966, \"annualised_returns_over_uniform_hodl\": 0.3083861638257748, \"calmar\": 0.7933355623534881, \"daily_log_sharpe\": 0.5119594887030338, \"daily_returns\": 0.021722662845662846, \"fee_revenue_over_value\": 0.8789819226539444, \"jax_sharpe\": 0.9604503059115352, \"return\": 0.32117881304904405, \"returns_over_hodl\": 0.17726135302245183, \"returns_over_uniform_hodl\": 0.17726135302244916, \"sharpe\": 0.9541260448737852, \"sterling\": 2.0491506268616737, \"ulcer\": -0.15300209725293132}], \"train_return\": 0.32117881304904405, \"train_returns_over_hodl\": 0.17726135302245183, \"train_sharpe\": 0.9604503059115351, \"validation_return\": 0.08036562869406105, \"validation_returns_over_hodl\": 0.10313431133102235, \"validation_sharpe\": 1.1105281364024884}, {\"centeredness_margin\": 0.013050380514909948, \"continuous_test_metrics\": [{\"annualised_returns\": -0.7520972017075311, \"annualised_returns_over_hodl\": 1.4552148328539012, \"annualised_returns_over_uniform_hodl\": 1.1776150154300007, \"calmar\": -1.0584045672878644, \"daily_log_sharpe\": -1.6330785131808323, \"daily_returns\": 0.009936074517132834, \"fee_revenue_over_value\": 0.2210723177879583, \"jax_sharpe\": -0.2784069047211561, \"return\": -0.42976453898415035, \"returns_over_hodl\": 0.43583255454086545, \"returns_over_uniform_hodl\": 0.3680997816365732, \"sharpe\": -1.210051683572383, \"sterling\": -1.8832386279852016, \"ulcer\": -0.15575995777922677}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": 0.9739317949639142, \"optuna_trial_number\": 237, \"price_ratio\": 1.1134106813230318, \"shift_exponent\": 0.03148707976551704, \"step\": 237, \"test_objective\": [{\"annualised_returns\": -0.7520972017075311, \"annualised_returns_over_hodl\": 1.4552148328539012, \"annualised_returns_over_uniform_hodl\": 1.1776150154300007, \"calmar\": -1.0584045672878644, \"daily_log_sharpe\": -1.6330785131808323, \"daily_returns\": 0.009936074517132834, \"fee_revenue_over_value\": 0.2210723177879583, \"jax_sharpe\": -0.2784069047211561, \"return\": -0.42976453898415035, \"returns_over_hodl\": 0.43583255454086545, \"returns_over_uniform_hodl\": 0.3680997816365732, \"sharpe\": -1.210051683572383, \"sterling\": -1.8832386279852016, \"ulcer\": -0.15575995777922677}], \"train_objective\": [{\"annualised_returns\": 1.2908937484303644, \"annualised_returns_over_hodl\": 0.8945364800764928, \"annualised_returns_over_uniform_hodl\": 0.8945364800764921, \"calmar\": 2.1484219717326574, \"daily_log_sharpe\": 0.9456827175877971, \"daily_returns\": 0.02214737894354364, \"fee_revenue_over_value\": 0.5180692253341338, \"jax_sharpe\": 1.3764403746956684, \"return\": 0.6541188169051462, \"returns_over_hodl\": 0.47393383637113873, \"returns_over_uniform_hodl\": 0.4739338363711385, \"sharpe\": 1.3886041908171163, \"sterling\": 5.0115198977491735, \"ulcer\": -0.1260551035812755}], \"train_return\": 0.6541188169051462, \"train_returns_over_hodl\": 0.47393383637113873, \"train_sharpe\": 1.3764403746956686, \"validation_return\": 0.024678118685791173, \"validation_returns_over_hodl\": 0.08126286584280606, \"validation_sharpe\": 0.5760225079386785}, {\"centeredness_margin\": 0.011334365744530756, \"continuous_test_metrics\": [{\"annualised_returns\": -0.8208509203978189, \"annualised_returns_over_hodl\": 0.7733906511503645, \"annualised_returns_over_uniform_hodl\": 0.5736721345191274, \"calmar\": -1.1544940219883482, \"daily_log_sharpe\": -1.9595288573003786, \"daily_returns\": 0.008996689650473033, \"fee_revenue_over_value\": 0.13372072972910265, \"jax_sharpe\": -0.49684489203029064, \"return\": -0.49968760104780263, \"returns_over_hodl\": 0.25951385267449645, \"returns_over_uniform_hodl\": 0.20034149145548552, \"sharpe\": -1.541672177401944, \"sterling\": -2.0161890863415377, \"ulcer\": -0.1633655662206919}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": 0.9334808525831759, \"optuna_trial_number\": 238, \"price_ratio\": 1.3535159128221983, \"shift_exponent\": 0.16444296788807844, \"step\": 238, \"test_objective\": [{\"annualised_returns\": -0.8208509203978189, \"annualised_returns_over_hodl\": 0.7733906511503645, \"annualised_returns_over_uniform_hodl\": 0.5736721345191274, \"calmar\": -1.1544940219883482, \"daily_log_sharpe\": -1.9595288573003786, \"daily_returns\": 0.008996689650473033, \"fee_revenue_over_value\": 0.13372072972910265, \"jax_sharpe\": -0.49684489203029064, \"return\": -0.49968760104780263, \"returns_over_hodl\": 0.25951385267449645, \"returns_over_uniform_hodl\": 0.20034149145548552, \"sharpe\": -1.541672177401944, \"sterling\": -2.0161890863415377, \"ulcer\": -0.1633655662206919}], \"train_objective\": [{\"annualised_returns\": 1.1077529301877935, \"annualised_returns_over_hodl\": 0.7430816335174422, \"annualised_returns_over_uniform_hodl\": 0.7430816335174413, \"calmar\": 1.7999549405194946, \"daily_log_sharpe\": 0.8650881964979481, \"daily_returns\": 0.021099867586196523, \"fee_revenue_over_value\": 0.31233152558216354, \"jax_sharpe\": 1.283590749919507, \"return\": 0.572526112496571, \"returns_over_hodl\": 0.40122911492081514, \"returns_over_uniform_hodl\": 0.4012291149208147, \"sharpe\": 1.304226002902009, \"sterling\": 4.315406846908189, \"ulcer\": -0.12542159772653888}], \"train_return\": 0.572526112496571, \"train_returns_over_hodl\": 0.40122911492081514, \"train_sharpe\": 1.283590749919507, \"validation_return\": -0.014942651351693037, \"validation_returns_over_hodl\": 0.046099604752607215, \"validation_sharpe\": 0.20463949595451295}, {\"centeredness_margin\": 0.6701624037753054, \"continuous_test_metrics\": [{\"annualised_returns\": -0.8644561404390071, \"annualised_returns_over_hodl\": 0.20869896176477742, \"annualised_returns_over_uniform_hodl\": 0.19063740249163708, \"calmar\": -1.3336112751833225, \"daily_log_sharpe\": -2.479127753115146, \"daily_returns\": 0.006950159187629165, \"fee_revenue_over_value\": 0.04257861757215758, \"jax_sharpe\": -0.7727579199928095, \"return\": -0.552847434312826, \"returns_over_hodl\": 0.07932598317490847, \"returns_over_uniform_hodl\": 0.07280127122408597, \"sharpe\": -2.100392513887438, \"sterling\": -2.2564897228660397, \"ulcer\": -0.15624180225383003}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": 0.6161253229455028, \"optuna_trial_number\": 239, \"price_ratio\": 5.320928489925149, \"shift_exponent\": 0.025955580080237368, \"step\": 239, \"test_objective\": [{\"annualised_returns\": -0.8644561404390071, \"annualised_returns_over_hodl\": 0.20869896176477742, \"annualised_returns_over_uniform_hodl\": 0.19063740249163708, \"calmar\": -1.3336112751833225, \"daily_log_sharpe\": -2.479127753115146, \"daily_returns\": 0.006950159187629165, \"fee_revenue_over_value\": 0.04257861757215758, \"jax_sharpe\": -0.7727579199928095, \"return\": -0.552847434312826, \"returns_over_hodl\": 0.07932598317490847, \"returns_over_uniform_hodl\": 0.07280127122408597, \"sharpe\": -2.100392513887438, \"sterling\": -2.2564897228660397, \"ulcer\": -0.15624180225383003}], \"train_objective\": [{\"annualised_returns\": 0.38516095878822454, \"annualised_returns_over_hodl\": 0.14550837156897956, \"annualised_returns_over_uniform_hodl\": 0.14550837156897956, \"calmar\": 0.6021962037901335, \"daily_log_sharpe\": 0.4034333767348754, \"daily_returns\": 0.020515908357280416, \"fee_revenue_over_value\": 0.07099573958054753, \"jax_sharpe\": 0.8102188383114547, \"return\": 0.21873090175965815, \"returns_over_hodl\": 0.08597320529585573, \"returns_over_uniform_hodl\": 0.08597320529585573, \"sharpe\": 0.8290851723046786, \"sterling\": 1.4708576760609517, \"ulcer\": -0.13320455063524128}], \"train_return\": 0.21873090175965815, \"train_returns_over_hodl\": 0.08597320529585573, \"train_sharpe\": 0.8102188383114547, \"validation_return\": 0.0018050326935081795, \"validation_returns_over_hodl\": 0.010953308508495851, \"validation_sharpe\": 0.3336532310582259}, {\"centeredness_margin\": 0.09286238151919037, \"continuous_test_metrics\": [{\"annualised_returns\": -0.8617439933423083, \"annualised_returns_over_hodl\": 0.30335596187946945, \"annualised_returns_over_uniform_hodl\": 0.21446130557988874, \"calmar\": -1.2254160655724118, \"daily_log_sharpe\": -2.2450923767867454, \"daily_returns\": 0.007731154501764425, \"fee_revenue_over_value\": 0.06100682183751513, \"jax_sharpe\": -0.6681402852963212, \"return\": -0.5492653474116436, \"returns_over_hodl\": 0.112603032814798, \"returns_over_uniform_hodl\": 0.08139535672445208, \"sharpe\": -1.8334500267018732, \"sterling\": -2.0939372920933113, \"ulcer\": -0.16649570042861528}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": 0.6999237226695562, \"optuna_trial_number\": 240, \"price_ratio\": 2.9513789146203844, \"shift_exponent\": 0.11781473283380263, \"step\": 240, \"test_objective\": [{\"annualised_returns\": -0.8617439933423083, \"annualised_returns_over_hodl\": 0.30335596187946945, \"annualised_returns_over_uniform_hodl\": 0.21446130557988874, \"calmar\": -1.2254160655724118, \"daily_log_sharpe\": -2.2450923767867454, \"daily_returns\": 0.007731154501764425, \"fee_revenue_over_value\": 0.06100682183751513, \"jax_sharpe\": -0.6681402852963212, \"return\": -0.5492653474116436, \"returns_over_hodl\": 0.112603032814798, \"returns_over_uniform_hodl\": 0.08139535672445208, \"sharpe\": -1.8334500267018732, \"sterling\": -2.0939372920933113, \"ulcer\": -0.16649570042861528}], \"train_objective\": [{\"annualised_returns\": 0.5268013018058921, \"annualised_returns_over_hodl\": 0.2626429165828532, \"annualised_returns_over_uniform_hodl\": 0.2626429165828532, \"calmar\": 0.8320893129235801, \"daily_log_sharpe\": 0.5168746706243661, \"daily_returns\": 0.020600031908428282, \"fee_revenue_over_value\": 0.1107994964859219, \"jax_sharpe\": 0.9215537806197206, \"return\": 0.29293978420338274, \"returns_over_hodl\": 0.15209843262247635, \"returns_over_uniform_hodl\": 0.15209843262247635, \"sharpe\": 0.9420372776071871, \"sterling\": 2.0280237862526156, \"ulcer\": -0.13121182928468036}], \"train_return\": 0.29293978420338274, \"train_returns_over_hodl\": 0.15209843262247635, \"train_sharpe\": 0.9215537806197205, \"validation_return\": -0.01334802486979747, \"validation_returns_over_hodl\": 0.013754275051179432, \"validation_sharpe\": 0.19633991749718288}, {\"centeredness_margin\": 0.04658149375372059, \"continuous_test_metrics\": [{\"annualised_returns\": -0.7104768306170863, \"annualised_returns_over_hodl\": 1.823192968023681, \"annualised_returns_over_uniform_hodl\": 1.5432145393506422, \"calmar\": -1.00201675911941, \"daily_log_sharpe\": -1.5581255089978845, \"daily_returns\": 0.010648174696305699, \"fee_revenue_over_value\": 0.31278182340499205, \"jax_sharpe\": -0.20300548335993876, \"return\": -0.39298487334877696, \"returns_over_hodl\": 0.5189038171364726, \"returns_over_uniform_hodl\": 0.45634096613741204, \"sharpe\": -1.1591210537957548, \"sterling\": -1.8571830724941054, \"ulcer\": -0.14538650337069672}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": 0.9381366330017663, \"optuna_trial_number\": 241, \"price_ratio\": 1.0243177920761113, \"shift_exponent\": 0.9052213339133472, \"step\": 241, \"test_objective\": [{\"annualised_returns\": -0.7104768306170863, \"annualised_returns_over_hodl\": 1.823192968023681, \"annualised_returns_over_uniform_hodl\": 1.5432145393506422, \"calmar\": -1.00201675911941, \"daily_log_sharpe\": -1.5581255089978845, \"daily_returns\": 0.010648174696305699, \"fee_revenue_over_value\": 0.31278182340499205, \"jax_sharpe\": -0.20300548335993876, \"return\": -0.39298487334877696, \"returns_over_hodl\": 0.5189038171364726, \"returns_over_uniform_hodl\": 0.45634096613741204, \"sharpe\": -1.1591210537957548, \"sterling\": -1.8571830724941054, \"ulcer\": -0.14538650337069672}], \"train_objective\": [{\"annualised_returns\": 0.27898981432359715, \"annualised_returns_over_hodl\": 0.057706348250559314, \"annualised_returns_over_uniform_hodl\": 0.057706348250558426, \"calmar\": 0.3700228464046159, \"daily_log_sharpe\": 0.2755127833325034, \"daily_returns\": 0.02731205611077131, \"fee_revenue_over_value\": 0.7187112553606554, \"jax_sharpe\": 0.7224698882423175, \"return\": 0.16113119549096044, \"returns_over_hodl\": 0.03464789833071524, \"returns_over_uniform_hodl\": 0.0346478983307148, \"sharpe\": 0.7183912173277529, \"sterling\": 0.9549013895412375, \"ulcer\": -0.15823110284272215}], \"train_return\": 0.16113119549096044, \"train_returns_over_hodl\": 0.03464789833071524, \"train_sharpe\": 0.7224698882423176, \"validation_return\": 0.038079800290517385, \"validation_returns_over_hodl\": 0.07793841301286997, \"validation_sharpe\": 0.7034770036739474}, {\"centeredness_margin\": 0.07211743120200492, \"continuous_test_metrics\": [{\"annualised_returns\": -0.7641537216682642, \"annualised_returns_over_hodl\": 1.2918775714675639, \"annualised_returns_over_uniform_hodl\": 1.071708752648127, \"calmar\": -1.07715906057655, \"daily_log_sharpe\": -1.7577765368714284, \"daily_returns\": 0.009472401972998175, \"fee_revenue_over_value\": 0.20387747443898124, \"jax_sharpe\": -0.29833859804693325, \"return\": -0.4411001667749068, \"returns_over_hodl\": 0.3965699925754327, \"returns_over_uniform_hodl\": 0.34090352506280586, \"sharpe\": -1.3556856438032243, \"sterling\": -1.961370432163969, \"ulcer\": -0.15223583340878996}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": 0.8974580190709212, \"optuna_trial_number\": 242, \"price_ratio\": 1.1343300686665236, \"shift_exponent\": 2.1632479788286405, \"step\": 242, \"test_objective\": [{\"annualised_returns\": -0.7641537216682642, \"annualised_returns_over_hodl\": 1.2918775714675639, \"annualised_returns_over_uniform_hodl\": 1.071708752648127, \"calmar\": -1.07715906057655, \"daily_log_sharpe\": -1.7577765368714284, \"daily_returns\": 0.009472401972998175, \"fee_revenue_over_value\": 0.20387747443898124, \"jax_sharpe\": -0.29833859804693325, \"return\": -0.4411001667749068, \"returns_over_hodl\": 0.3965699925754327, \"returns_over_uniform_hodl\": 0.34090352506280586, \"sharpe\": -1.3556856438032243, \"sterling\": -1.961370432163969, \"ulcer\": -0.15223583340878996}], \"train_objective\": [{\"annualised_returns\": 0.7051043922302596, \"annualised_returns_over_hodl\": 0.4100970311836696, \"annualised_returns_over_uniform_hodl\": 0.4100970311836687, \"calmar\": 1.0441952513369537, \"daily_log_sharpe\": 0.6243583598396997, \"daily_returns\": 0.021888941448883887, \"fee_revenue_over_value\": 0.4590639928293126, \"jax_sharpe\": 1.0457840903023379, \"return\": 0.3826140689746098, \"returns_over_hodl\": 0.23200439900522452, \"returns_over_uniform_hodl\": 0.23200439900522407, \"sharpe\": 1.0562524473969612, \"sterling\": 2.6397977361576546, \"ulcer\": -0.1371218707479438}], \"train_return\": 0.3826140689746098, \"train_returns_over_hodl\": 0.23200439900522452, \"train_sharpe\": 1.0457840903023379, \"validation_return\": 0.026823280019533602, \"validation_returns_over_hodl\": 0.07593068562379357, \"validation_sharpe\": 0.5948289418920247}, {\"centeredness_margin\": 0.016597627529185324, \"continuous_test_metrics\": [{\"annualised_returns\": -0.6996612288097781, \"annualised_returns_over_hodl\": 1.9560009214584388, \"annualised_returns_over_uniform_hodl\": 1.6382203926880452, \"calmar\": -1.1377724048539857, \"daily_log_sharpe\": -1.5048536249145288, \"daily_returns\": 0.010794602403463369, \"fee_revenue_over_value\": 0.31893176980516863, \"jax_sharpe\": -0.4940127061920141, \"return\": -0.383952283569144, \"returns_over_hodl\": 0.5472857720481963, \"returns_over_uniform_hodl\": 0.4780118108146516, \"sharpe\": -1.1021043096176872, \"sterling\": -1.9484119763962058, \"ulcer\": -0.1450487628073462}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": 0.9754062634154448, \"optuna_trial_number\": 243, \"price_ratio\": 1.022728278253363, \"shift_exponent\": 1.2729915688918374, \"step\": 243, \"test_objective\": [{\"annualised_returns\": -0.6996612288097781, \"annualised_returns_over_hodl\": 1.9560009214584388, \"annualised_returns_over_uniform_hodl\": 1.6382203926880452, \"calmar\": -1.1377724048539857, \"daily_log_sharpe\": -1.5048536249145288, \"daily_returns\": 0.010794602403463369, \"fee_revenue_over_value\": 0.31893176980516863, \"jax_sharpe\": -0.4940127061920141, \"return\": -0.383952283569144, \"returns_over_hodl\": 0.5472857720481963, \"returns_over_uniform_hodl\": 0.4780118108146516, \"sharpe\": -1.1021043096176872, \"sterling\": -1.9484119763962058, \"ulcer\": -0.1450487628073462}], \"train_objective\": [{\"annualised_returns\": 0.25012745925482505, \"annualised_returns_over_hodl\": 0.03383759195569591, \"annualised_returns_over_uniform_hodl\": 0.03383759195569547, \"calmar\": 0.32994936892516263, \"daily_log_sharpe\": 0.2489368254565468, \"daily_returns\": 0.02247740127951135, \"fee_revenue_over_value\": 0.7184716999092005, \"jax_sharpe\": 0.6975199832517519, \"return\": 0.14515170923973097, \"returns_over_hodl\": 0.020409075077630012, \"returns_over_uniform_hodl\": 0.02040907507762979, \"sharpe\": 0.6925711696044832, \"sterling\": 0.8485806835805234, \"ulcer\": -0.1599568036670237}], \"train_return\": 0.14515170923973097, \"train_returns_over_hodl\": 0.020409075077630012, \"train_sharpe\": 0.6975199832517519, \"validation_return\": 0.03372953946351909, \"validation_returns_over_hodl\": 0.07843054075049705, \"validation_sharpe\": 0.6615925645465047}, {\"centeredness_margin\": 0.049566879137214685, \"continuous_test_metrics\": [{\"annualised_returns\": -0.6980504370362395, \"annualised_returns_over_hodl\": 1.955227171764971, \"annualised_returns_over_uniform_hodl\": 1.652369826970149, \"calmar\": -0.9840790784813923, \"daily_log_sharpe\": -1.4797204277293108, \"daily_returns\": 0.010937317077459844, \"fee_revenue_over_value\": 0.3159137530669063, \"jax_sharpe\": -0.1622034555891624, \"return\": -0.38262375585491926, \"returns_over_hodl\": 0.5471226461834131, \"returns_over_uniform_hodl\": 0.48119919322066895, \"sharpe\": -1.0721173634756023, \"sterling\": -1.817474257615396, \"ulcer\": -0.14433084559342443}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": 1.0414155950504103, \"optuna_trial_number\": 244, \"price_ratio\": 1.0367230383606576, \"shift_exponent\": 0.058413160119439714, \"step\": 244, \"test_objective\": [{\"annualised_returns\": -0.6980504370362395, \"annualised_returns_over_hodl\": 1.955227171764971, \"annualised_returns_over_uniform_hodl\": 1.652369826970149, \"calmar\": -0.9840790784813923, \"daily_log_sharpe\": -1.4797204277293108, \"daily_returns\": 0.010937317077459844, \"fee_revenue_over_value\": 0.3159137530669063, \"jax_sharpe\": -0.1622034555891624, \"return\": -0.38262375585491926, \"returns_over_hodl\": 0.5471226461834131, \"returns_over_uniform_hodl\": 0.48119919322066895, \"sharpe\": -1.0721173634756023, \"sterling\": -1.817474257615396, \"ulcer\": -0.14433084559342443}], \"train_objective\": [{\"annualised_returns\": 1.0755410785517534, \"annualised_returns_over_hodl\": 0.7164428912985581, \"annualised_returns_over_uniform_hodl\": 0.7164428912985572, \"calmar\": 1.626780863019224, \"daily_log_sharpe\": 0.8344630669122973, \"daily_returns\": 0.021659756979473935, \"fee_revenue_over_value\": 0.7357283210064182, \"jax_sharpe\": 1.263641778952778, \"return\": 0.5578915233861852, \"returns_over_hodl\": 0.3881886876850351, \"returns_over_uniform_hodl\": 0.3881886876850347, \"sharpe\": 1.2718000488187535, \"sterling\": 4.003150741438936, \"ulcer\": -0.13530612009797163}], \"train_return\": 0.5578915233861852, \"train_returns_over_hodl\": 0.3881886876850351, \"train_sharpe\": 1.263641778952778, \"validation_return\": 0.073911949509327, \"validation_returns_over_hodl\": 0.1114356194952364, \"validation_sharpe\": 1.0477458985525967}, {\"centeredness_margin\": 0.06617148470620213, \"continuous_test_metrics\": [{\"annualised_returns\": -0.7489505562058489, \"annualised_returns_over_hodl\": 1.4480231092148133, \"annualised_returns_over_uniform_hodl\": 1.2052556170686088, \"calmar\": -1.0555653357888448, \"daily_log_sharpe\": -1.6964237636539623, \"daily_returns\": 0.009738317305429988, \"fee_revenue_over_value\": 0.22551796544930783, \"jax_sharpe\": -0.2966674931324271, \"return\": -0.42686048446347546, \"returns_over_hodl\": 0.4341372410157953, \"returns_over_uniform_hodl\": 0.3750671427132033, \"sharpe\": -1.2943080540210417, \"sterling\": -1.9357422291441206, \"ulcer\": -0.14936467770312423}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": 0.975287550013809, \"optuna_trial_number\": 245, \"price_ratio\": 1.1013196452990903, \"shift_exponent\": 0.512596584653818, \"step\": 245, \"test_objective\": [{\"annualised_returns\": -0.7489505562058489, \"annualised_returns_over_hodl\": 1.4480231092148133, \"annualised_returns_over_uniform_hodl\": 1.2052556170686088, \"calmar\": -1.0555653357888448, \"daily_log_sharpe\": -1.6964237636539623, \"daily_returns\": 0.009738317305429988, \"fee_revenue_over_value\": 0.22551796544930783, \"jax_sharpe\": -0.2966674931324271, \"return\": -0.42686048446347546, \"returns_over_hodl\": 0.4341372410157953, \"returns_over_uniform_hodl\": 0.3750671427132033, \"sharpe\": -1.2943080540210417, \"sterling\": -1.9357422291441206, \"ulcer\": -0.14936467770312423}], \"train_objective\": [{\"annualised_returns\": 0.8342451756784157, \"annualised_returns_over_hodl\": 0.5168946185775933, \"annualised_returns_over_uniform_hodl\": 0.5168946185775947, \"calmar\": 1.2378909589407465, \"daily_log_sharpe\": 0.7064692717175187, \"daily_returns\": 0.0223360832905855, \"fee_revenue_over_value\": 0.5293772682620101, \"jax_sharpe\": 1.1283726158388927, \"return\": 0.44527540720889514, \"returns_over_hodl\": 0.2878399688034152, \"returns_over_uniform_hodl\": 0.2878399688034159, \"sharpe\": 1.1388587291470604, \"sterling\": 3.1369295418650642, \"ulcer\": -0.13711709297110283}], \"train_return\": 0.44527540720889514, \"train_returns_over_hodl\": 0.2878399688034152, \"train_sharpe\": 1.1283726158388927, \"validation_return\": 0.035762478955040145, \"validation_returns_over_hodl\": 0.08322565033309548, \"validation_sharpe\": 0.681210370433069}, {\"centeredness_margin\": 0.03818391386437367, \"continuous_test_metrics\": [{\"annualised_returns\": -0.7361949317855226, \"annualised_returns_over_hodl\": 1.5872336806451508, \"annualised_returns_over_uniform_hodl\": 1.3173029173016544, \"calmar\": -1.0368332084883274, \"daily_log_sharpe\": -1.6039225729335327, \"daily_returns\": 0.010122132524132603, \"fee_revenue_over_value\": 0.25050277259873205, \"jax_sharpe\": -0.2478824551772892, \"return\": -0.41530574130419773, \"returns_over_hodl\": 0.4664407944993403, \"returns_over_uniform_hodl\": 0.40278909736841406, \"sharpe\": -1.190622991540922, \"sterling\": -1.8807352867924196, \"ulcer\": -0.1504069139869102}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": 1.0857642563519951, \"optuna_trial_number\": 246, \"price_ratio\": 1.0778363904006114, \"shift_exponent\": 0.04792397120050043, \"step\": 246, \"test_objective\": [{\"annualised_returns\": -0.7361949317855226, \"annualised_returns_over_hodl\": 1.5872336806451508, \"annualised_returns_over_uniform_hodl\": 1.3173029173016544, \"calmar\": -1.0368332084883274, \"daily_log_sharpe\": -1.6039225729335327, \"daily_returns\": 0.010122132524132603, \"fee_revenue_over_value\": 0.25050277259873205, \"jax_sharpe\": -0.2478824551772892, \"return\": -0.41530574130419773, \"returns_over_hodl\": 0.4664407944993403, \"returns_over_uniform_hodl\": 0.40278909736841406, \"sharpe\": -1.190622991540922, \"sterling\": -1.8807352867924196, \"ulcer\": -0.1504069139869102}], \"train_objective\": [{\"annualised_returns\": 1.2791710795081968, \"annualised_returns_over_hodl\": 0.8848419999496318, \"annualised_returns_over_uniform_hodl\": 0.8848419999496326, \"calmar\": 2.040787685972635, \"daily_log_sharpe\": 0.9498257593985487, \"daily_returns\": 0.02250975909879508, \"fee_revenue_over_value\": 0.6019995937840332, \"jax_sharpe\": 1.3712017404669476, \"return\": 0.6489748145881549, \"returns_over_hodl\": 0.4693501758795837, \"returns_over_uniform_hodl\": 0.46935017587958416, \"sharpe\": 1.385454397593963, \"sterling\": 4.9557915664592445, \"ulcer\": -0.12811420361269957}], \"train_return\": 0.6489748145881549, \"train_returns_over_hodl\": 0.4693501758795837, \"train_sharpe\": 1.3712017404669474, \"validation_return\": 0.04523464228394669, \"validation_returns_over_hodl\": 0.09289572895929554, \"validation_sharpe\": 0.7717032343468773}, {\"centeredness_margin\": 0.1546589919194417, \"continuous_test_metrics\": [{\"annualised_returns\": -0.7288333774422853, \"annualised_returns_over_hodl\": 1.6587033323400204, \"annualised_returns_over_uniform_hodl\": 1.3819679044867725, \"calmar\": -1.1767988325439873, \"daily_log_sharpe\": -1.5724749189679723, \"daily_returns\": 0.008886986274402346, \"fee_revenue_over_value\": 0.24738392932405864, \"jax_sharpe\": -0.5384420911480367, \"return\": -0.4087886082923736, \"returns_over_hodl\": 0.48262262461824834, \"returns_over_uniform_hodl\": 0.41842489847150466, \"sharpe\": -1.1576352551340165, \"sterling\": -1.98498554334118, \"ulcer\": -0.14889621145658855}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": 0.8338905485942778, \"optuna_trial_number\": 247, \"price_ratio\": 1.0880723935870606, \"shift_exponent\": 0.010868427940910762, \"step\": 247, \"test_objective\": [{\"annualised_returns\": -0.7288333774422853, \"annualised_returns_over_hodl\": 1.6587033323400204, \"annualised_returns_over_uniform_hodl\": 1.3819679044867725, \"calmar\": -1.1767988325439873, \"daily_log_sharpe\": -1.5724749189679723, \"daily_returns\": 0.008886986274402346, \"fee_revenue_over_value\": 0.24738392932405864, \"jax_sharpe\": -0.5384420911480367, \"return\": -0.4087886082923736, \"returns_over_hodl\": 0.48262262461824834, \"returns_over_uniform_hodl\": 0.41842489847150466, \"sharpe\": -1.1576352551340165, \"sterling\": -1.98498554334118, \"ulcer\": -0.14889621145658855}], \"train_objective\": [{\"annualised_returns\": 0.9569181070789867, \"annualised_returns_over_hodl\": 0.6183433845081574, \"annualised_returns_over_uniform_hodl\": 0.6183433845081574, \"calmar\": 1.6066870529530617, \"daily_log_sharpe\": 0.7905481402082838, \"daily_returns\": 0.022476574750493025, \"fee_revenue_over_value\": 0.38073509102442504, \"jax_sharpe\": 1.1989513900776116, \"return\": 0.5032111628192419, \"returns_over_hodl\": 0.3394647188861153, \"returns_over_uniform_hodl\": 0.3394647188861153, \"sharpe\": 1.2320395473597077, \"sterling\": 3.6949492960820343, \"ulcer\": -0.1253582271849649}], \"train_return\": 0.5032111628192419, \"train_returns_over_hodl\": 0.3394647188861153, \"train_sharpe\": 1.1989513900776114, \"validation_return\": 0.006050067509834056, \"validation_returns_over_hodl\": 0.08166193842362812, \"validation_sharpe\": 0.4085181515006797}, {\"centeredness_margin\": 0.05582015294661584, \"continuous_test_metrics\": [{\"annualised_returns\": -0.6598506689593024, \"annualised_returns_over_hodl\": 2.3951578545896077, \"annualised_returns_over_uniform_hodl\": 1.987922265761675, \"calmar\": -0.9306294711162524, \"daily_log_sharpe\": -1.3421528430735403, \"daily_returns\": 0.010058907645391258, \"fee_revenue_over_value\": 0.3685109633249273, \"jax_sharpe\": -0.06246048552166079, \"return\": -0.35228248266049533, \"returns_over_hodl\": 0.6360530783504557, \"returns_over_uniform_hodl\": 0.5539934897344614, \"sharpe\": -0.9202790610823398, \"sterling\": -1.7014640240637338, \"ulcer\": -0.14702031567768375}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": 0.8190133267683113, \"optuna_trial_number\": 248, \"price_ratio\": 1.0238211902568566, \"shift_exponent\": 0.010634107860828522, \"step\": 248, \"test_objective\": [{\"annualised_returns\": -0.6598506689593024, \"annualised_returns_over_hodl\": 2.3951578545896077, \"annualised_returns_over_uniform_hodl\": 1.987922265761675, \"calmar\": -0.9306294711162524, \"daily_log_sharpe\": -1.3421528430735403, \"daily_returns\": 0.010058907645391258, \"fee_revenue_over_value\": 0.3685109633249273, \"jax_sharpe\": -0.06246048552166079, \"return\": -0.35228248266049533, \"returns_over_hodl\": 0.6360530783504557, \"returns_over_uniform_hodl\": 0.5539934897344614, \"sharpe\": -0.9202790610823398, \"sterling\": -1.7014640240637338, \"ulcer\": -0.14702031567768375}], \"train_objective\": [{\"annualised_returns\": 0.856345964811507, \"annualised_returns_over_hodl\": 0.5351716562096545, \"annualised_returns_over_uniform_hodl\": 0.5351716562096576, \"calmar\": 1.4180660619887722, \"daily_log_sharpe\": 0.7248960291974623, \"daily_returns\": 0.020314557033669453, \"fee_revenue_over_value\": 0.4285424780073365, \"jax_sharpe\": 1.1352960526198959, \"return\": 0.4558229941622536, \"returns_over_hodl\": 0.29723859551857923, \"returns_over_uniform_hodl\": 0.2972385955185808, \"sharpe\": 1.1692720891601371, \"sterling\": 3.247795114547518, \"ulcer\": -0.12628301170095602}], \"train_return\": 0.4558229941622536, \"train_returns_over_hodl\": 0.29723859551857923, \"train_sharpe\": 1.1352960526198959, \"validation_return\": 0.02199048724990882, \"validation_returns_over_hodl\": 0.09880039487582315, \"validation_sharpe\": 0.5593410984530266}, {\"centeredness_margin\": 0.18552611809567013, \"continuous_test_metrics\": [{\"annualised_returns\": -0.7545381501368464, \"annualised_returns_over_hodl\": 1.3722386685776957, \"annualised_returns_over_uniform_hodl\": 1.1561733617327472, \"calmar\": -1.0641140722453981, \"daily_log_sharpe\": -1.687124174584629, \"daily_returns\": 0.00895672499914258, \"fee_revenue_over_value\": 0.21925769189854566, \"jax_sharpe\": -0.2490732052159247, \"return\": -0.43203250047102193, \"returns_over_hodl\": 0.41608874073116087, \"returns_over_uniform_hodl\": 0.36265852477502736, \"sharpe\": -1.2759805554456023, \"sterling\": -1.926890409404529, \"ulcer\": -0.15035993864314515}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": 0.8127873050491055, \"optuna_trial_number\": 249, \"price_ratio\": 1.113338850120314, \"shift_exponent\": 0.011816132340123403, \"step\": 249, \"test_objective\": [{\"annualised_returns\": -0.7545381501368464, \"annualised_returns_over_hodl\": 1.3722386685776957, \"annualised_returns_over_uniform_hodl\": 1.1561733617327472, \"calmar\": -1.0641140722453981, \"daily_log_sharpe\": -1.687124174584629, \"daily_returns\": 0.00895672499914258, \"fee_revenue_over_value\": 0.21925769189854566, \"jax_sharpe\": -0.2490732052159247, \"return\": -0.43203250047102193, \"returns_over_hodl\": 0.41608874073116087, \"returns_over_uniform_hodl\": 0.36265852477502736, \"sharpe\": -1.2759805554456023, \"sterling\": -1.926890409404529, \"ulcer\": -0.15035993864314515}], \"train_objective\": [{\"annualised_returns\": 0.902894875631818, \"annualised_returns_over_hodl\": 0.5736669420418048, \"annualised_returns_over_uniform_hodl\": 0.5736669420418052, \"calmar\": 1.5080224587948248, \"daily_log_sharpe\": 0.7629089954810635, \"daily_returns\": 0.022190929733428937, \"fee_revenue_over_value\": 0.349744695746662, \"jax_sharpe\": 1.168510064501249, \"return\": 0.4778783883960591, \"returns_over_hodl\": 0.31689147142052687, \"returns_over_uniform_hodl\": 0.3168914714205271, \"sharpe\": 1.2013620197845716, \"sterling\": 3.493009637386767, \"ulcer\": -0.12599266481958282}], \"train_return\": 0.4778783883960591, \"train_returns_over_hodl\": 0.31689147142052687, \"train_sharpe\": 1.1685100645012487, \"validation_return\": 0.011314634190942474, \"validation_returns_over_hodl\": 0.08732217499302619, \"validation_sharpe\": 0.45225506240607755}, {\"centeredness_margin\": 0.1613288080543931, \"continuous_test_metrics\": [{\"annualised_returns\": -0.7740082608144443, \"annualised_returns_over_hodl\": 1.1773706760139158, \"annualised_returns_over_uniform_hodl\": 0.9851450165278635, \"calmar\": -1.0914660779320284, \"daily_log_sharpe\": -1.768872756998343, \"daily_returns\": 0.009119269183294886, \"fee_revenue_over_value\": 0.19258640884708778, \"jax_sharpe\": -0.35360358089582805, \"return\": -0.45062531266930084, \"returns_over_hodl\": 0.3680379562024816, \"returns_over_uniform_hodl\": 0.3180509476468696, \"sharpe\": -1.356840992507998, \"sterling\": -1.9539252794016069, \"ulcer\": -0.1539886671132324}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": 0.7947489097637803, \"optuna_trial_number\": 250, \"price_ratio\": 1.1546700472191138, \"shift_exponent\": 0.014872744877937317, \"step\": 250, \"test_objective\": [{\"annualised_returns\": -0.7740082608144443, \"annualised_returns_over_hodl\": 1.1773706760139158, \"annualised_returns_over_uniform_hodl\": 0.9851450165278635, \"calmar\": -1.0914660779320284, \"daily_log_sharpe\": -1.768872756998343, \"daily_returns\": 0.009119269183294886, \"fee_revenue_over_value\": 0.19258640884708778, \"jax_sharpe\": -0.35360358089582805, \"return\": -0.45062531266930084, \"returns_over_hodl\": 0.3680379562024816, \"returns_over_uniform_hodl\": 0.3180509476468696, \"sharpe\": -1.356840992507998, \"sterling\": -1.9539252794016069, \"ulcer\": -0.1539886671132324}], \"train_objective\": [{\"annualised_returns\": 0.8714644078314406, \"annualised_returns_over_hodl\": 0.5476743931187131, \"annualised_returns_over_uniform_hodl\": 0.5476743931187114, \"calmar\": 1.4509434340830283, \"daily_log_sharpe\": 0.7452498126376549, \"daily_returns\": 0.021761479718364538, \"fee_revenue_over_value\": 0.3221430564660725, \"jax_sharpe\": 1.1511919955372587, \"return\": 0.4630098596249652, \"returns_over_hodl\": 0.3036425878283704, \"returns_over_uniform_hodl\": 0.3036425878283695, \"sharpe\": 1.182086770836843, \"sterling\": 3.390596018900718, \"ulcer\": -0.12564574436668902}], \"train_return\": 0.4630098596249652, \"train_returns_over_hodl\": 0.3036425878283704, \"train_sharpe\": 1.1511919955372587, \"validation_return\": 0.01227633323220001, \"validation_returns_over_hodl\": 0.08189175582757136, \"validation_sharpe\": 0.4529594204254679}, {\"centeredness_margin\": 0.07782397624625624, \"continuous_test_metrics\": [{\"annualised_returns\": -0.8610922829831228, \"annualised_returns_over_hodl\": 0.3278963780388022, \"annualised_returns_over_uniform_hodl\": 0.220186026210913, \"calmar\": -1.2170601711538644, \"daily_log_sharpe\": -2.2001916608364858, \"daily_returns\": 0.007966567619453618, \"fee_revenue_over_value\": 0.06578752197501893, \"jax_sharpe\": -0.6325255353869039, \"return\": -0.54841086380859, \"returns_over_hodl\": 0.12099293717212989, \"returns_over_uniform_hodl\": 0.08344542009418143, \"sharpe\": -1.7809947969710047, \"sterling\": -2.0739426833083168, \"ulcer\": -0.1685772097709019}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": 0.7277985626226453, \"optuna_trial_number\": 251, \"price_ratio\": 2.494947494157341, \"shift_exponent\": 0.01879114767813578, \"step\": 251, \"test_objective\": [{\"annualised_returns\": -0.8610922829831228, \"annualised_returns_over_hodl\": 0.3278963780388022, \"annualised_returns_over_uniform_hodl\": 0.220186026210913, \"calmar\": -1.2170601711538644, \"daily_log_sharpe\": -2.2001916608364858, \"daily_returns\": 0.007966567619453618, \"fee_revenue_over_value\": 0.06578752197501893, \"jax_sharpe\": -0.6325255353869039, \"return\": -0.54841086380859, \"returns_over_hodl\": 0.12099293717212989, \"returns_over_uniform_hodl\": 0.08344542009418143, \"sharpe\": -1.7809947969710047, \"sterling\": -2.0739426833083168, \"ulcer\": -0.1685772097709019}], \"train_objective\": [{\"annualised_returns\": 0.579131762248861, \"annualised_returns_over_hodl\": 0.3059194615541483, \"annualised_returns_over_uniform_hodl\": 0.3059194615541483, \"calmar\": 0.9182931046421158, \"daily_log_sharpe\": 0.5555620948340412, \"daily_returns\": 0.02063490605039226, \"fee_revenue_over_value\": 0.12750151419658814, \"jax_sharpe\": 0.9602190997868384, \"return\": 0.31966602225484, \"returns_over_hodl\": 0.17591335219195248, \"returns_over_uniform_hodl\": 0.17591335219195248, \"sharpe\": 0.9809451500734996, \"sterling\": 2.2342930325178942, \"ulcer\": -0.13052814948243108}], \"train_return\": 0.31966602225484, \"train_returns_over_hodl\": 0.17591335219195248, \"train_sharpe\": 0.9602190997868385, \"validation_return\": -0.016226473516805062, \"validation_returns_over_hodl\": 0.015767810417861794, \"validation_sharpe\": 0.1737008504657076}, {\"centeredness_margin\": 0.07024913897084319, \"continuous_test_metrics\": [{\"annualised_returns\": -0.7409065593004871, \"annualised_returns_over_hodl\": 1.5191390213927387, \"annualised_returns_over_uniform_hodl\": 1.2759152811217866, \"calmar\": -1.1919733653508886, \"daily_log_sharpe\": -1.646473495190184, \"daily_returns\": 0.009937291366034304, \"fee_revenue_over_value\": 0.2446930830532532, \"jax_sharpe\": -0.5869162670521879, \"return\": -0.4195340962769233, \"returns_over_hodl\": 0.45077282341318337, \"returns_over_uniform_hodl\": 0.3926444958654447, \"sharpe\": -1.2401115847023005, \"sterling\": -2.026534453815711, \"ulcer\": -0.14895498793933082}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": 1.0448153355556096, \"optuna_trial_number\": 252, \"price_ratio\": 1.0823203079093413, \"shift_exponent\": 0.0730527588766174, \"step\": 252, \"test_objective\": [{\"annualised_returns\": -0.7409065593004871, \"annualised_returns_over_hodl\": 1.5191390213927387, \"annualised_returns_over_uniform_hodl\": 1.2759152811217866, \"calmar\": -1.1919733653508886, \"daily_log_sharpe\": -1.646473495190184, \"daily_returns\": 0.009937291366034304, \"fee_revenue_over_value\": 0.2446930830532532, \"jax_sharpe\": -0.5869162670521879, \"return\": -0.4195340962769233, \"returns_over_hodl\": 0.45077282341318337, \"returns_over_uniform_hodl\": 0.3926444958654447, \"sharpe\": -1.2401115847023005, \"sterling\": -2.026534453815711, \"ulcer\": -0.14895498793933082}], \"train_objective\": [{\"annualised_returns\": 1.0631444329154975, \"annualised_returns_over_hodl\": 0.7061910420346795, \"annualised_returns_over_uniform_hodl\": 0.7061910420346795, \"calmar\": 1.630029765609427, \"daily_log_sharpe\": 0.840553002673445, \"daily_returns\": 0.02235746034066085, \"fee_revenue_over_value\": 0.5746896386193606, \"jax_sharpe\": 1.2601947066002503, \"return\": 0.5522356944587803, \"returns_over_hodl\": 0.3831489544182154, \"returns_over_uniform_hodl\": 0.3831489544182154, \"sharpe\": 1.2732120958651145, \"sterling\": 4.054008002321931, \"ulcer\": -0.13233050182192915}], \"train_return\": 0.5522356944587803, \"train_returns_over_hodl\": 0.3831489544182154, \"train_sharpe\": 1.2601947066002503, \"validation_return\": 0.045392136991021514, \"validation_returns_over_hodl\": 0.0892989480942159, \"validation_sharpe\": 0.7740576748703873}, {\"centeredness_margin\": 0.06802477992040518, \"continuous_test_metrics\": [{\"annualised_returns\": -0.6936663229081602, \"annualised_returns_over_hodl\": 1.9642179177176686, \"annualised_returns_over_uniform_hodl\": 1.6908805368952589, \"calmar\": -0.9786974320391861, \"daily_log_sharpe\": -1.472716316806571, \"daily_returns\": 0.011320178477936836, \"fee_revenue_over_value\": 0.34704404284464946, \"jax_sharpe\": -0.08963903558429885, \"return\": -0.3790291846007108, \"returns_over_hodl\": 0.5490165499413207, \"returns_over_uniform_hodl\": 0.48982323098078817, \"sharpe\": -1.0675903918732557, \"sterling\": -1.8051878796555239, \"ulcer\": -0.1438205631063203}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": 1.0096593564753238, \"optuna_trial_number\": 253, \"price_ratio\": 1.026551560937308, \"shift_exponent\": 0.0481695782659607, \"step\": 253, \"test_objective\": [{\"annualised_returns\": -0.6936663229081602, \"annualised_returns_over_hodl\": 1.9642179177176686, \"annualised_returns_over_uniform_hodl\": 1.6908805368952589, \"calmar\": -0.9786974320391861, \"daily_log_sharpe\": -1.472716316806571, \"daily_returns\": 0.011320178477936836, \"fee_revenue_over_value\": 0.34704404284464946, \"jax_sharpe\": -0.08963903558429885, \"return\": -0.3790291846007108, \"returns_over_hodl\": 0.5490165499413207, \"returns_over_uniform_hodl\": 0.48982323098078817, \"sharpe\": -1.0675903918732557, \"sterling\": -1.8051878796555239, \"ulcer\": -0.1438205631063203}], \"train_objective\": [{\"annualised_returns\": 1.0122387344814077, \"annualised_returns_over_hodl\": 0.664092754938985, \"annualised_returns_over_uniform_hodl\": 0.6640927549389846, \"calmar\": 1.5253820307702233, \"daily_log_sharpe\": 0.794585041193675, \"daily_returns\": 0.022031533896329277, \"fee_revenue_over_value\": 0.7588199396468273, \"jax_sharpe\": 1.2285953363981386, \"return\": 0.5288691643679093, \"returns_over_hodl\": 0.36232776612899875, \"returns_over_uniform_hodl\": 0.36232776612899853, \"sharpe\": 1.2327768407134352, \"sterling\": 3.7489607455092715, \"ulcer\": -0.1366326849025804}], \"train_return\": 0.5288691643679093, \"train_returns_over_hodl\": 0.36232776612899875, \"train_sharpe\": 1.2285953363981386, \"validation_return\": 0.0806414233380186, \"validation_returns_over_hodl\": 0.10804703877950184, \"validation_sharpe\": 1.1120215503325965}, {\"centeredness_margin\": 0.127108822392128, \"continuous_test_metrics\": [{\"annualised_returns\": -0.7329421030762441, \"annualised_returns_over_hodl\": 1.564899741217464, \"annualised_returns_over_uniform_hodl\": 1.345876248013273, \"calmar\": -1.034227443388703, \"daily_log_sharpe\": -1.6273793841030353, \"daily_returns\": 0.010210409440318858, \"fee_revenue_over_value\": 0.27429241891771533, \"jax_sharpe\": -0.18641666232161944, \"return\": -0.4124128132745769, \"returns_over_hodl\": 0.46132939395538286, \"returns_over_uniform_hodl\": 0.4097297639459778, \"sharpe\": -1.2245944669570241, \"sterling\": -1.9028709897190053, \"ulcer\": -0.14630192279064375}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": 1.008700230348327, \"optuna_trial_number\": 254, \"price_ratio\": 1.0577322159183433, \"shift_exponent\": 0.057322994878438165, \"step\": 254, \"test_objective\": [{\"annualised_returns\": -0.7329421030762441, \"annualised_returns_over_hodl\": 1.564899741217464, \"annualised_returns_over_uniform_hodl\": 1.345876248013273, \"calmar\": -1.034227443388703, \"daily_log_sharpe\": -1.6273793841030353, \"daily_returns\": 0.010210409440318858, \"fee_revenue_over_value\": 0.27429241891771533, \"jax_sharpe\": -0.18641666232161944, \"return\": -0.4124128132745769, \"returns_over_hodl\": 0.46132939395538286, \"returns_over_uniform_hodl\": 0.4097297639459778, \"sharpe\": -1.2245944669570241, \"sterling\": -1.9028709897190053, \"ulcer\": -0.14630192279064375}], \"train_objective\": [{\"annualised_returns\": 0.9927065665328949, \"annualised_returns_over_hodl\": 0.6479399304185129, \"annualised_returns_over_uniform_hodl\": 0.6479399304185138, \"calmar\": 1.5062989982992867, \"daily_log_sharpe\": 0.7958695159376095, \"daily_returns\": 0.022344756093260466, \"fee_revenue_over_value\": 0.6219020447068309, \"jax_sharpe\": 1.2204277209702186, \"return\": 0.5198420726045765, \"returns_over_hodl\": 0.35428400539184746, \"returns_over_uniform_hodl\": 0.3542840053918479, \"sharpe\": 1.2299459443497338, \"sterling\": 3.7500148897283165, \"ulcer\": -0.13409121397483226}], \"train_return\": 0.5198420726045765, \"train_returns_over_hodl\": 0.35428400539184746, \"train_sharpe\": 1.2204277209702186, \"validation_return\": 0.06268141956494633, \"validation_returns_over_hodl\": 0.0968347588199181, \"validation_sharpe\": 0.9437231787047871}, {\"centeredness_margin\": 0.022746725179469175, \"continuous_test_metrics\": [{\"annualised_returns\": -0.7108270490824626, \"annualised_returns_over_hodl\": 1.8431605699496427, \"annualised_returns_over_uniform_hodl\": 1.5401381683127306, \"calmar\": -1.0017980834966258, \"daily_log_sharpe\": -1.54806276945384, \"daily_returns\": 0.01056023264659625, \"fee_revenue_over_value\": 0.29511332339152097, \"jax_sharpe\": -0.20074278886622332, \"return\": -0.39328069816551436, \"returns_over_hodl\": 0.5232212264923386, \"returns_over_uniform_hodl\": 0.4556312279768633, \"sharpe\": -1.1463267071079364, \"sterling\": -1.8606583776874137, \"ulcer\": -0.14516254062166584}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": 0.9853684547756694, \"optuna_trial_number\": 255, \"price_ratio\": 1.0370232707593554, \"shift_exponent\": 0.41766989691787965, \"step\": 255, \"test_objective\": [{\"annualised_returns\": -0.7108270490824626, \"annualised_returns_over_hodl\": 1.8431605699496427, \"annualised_returns_over_uniform_hodl\": 1.5401381683127306, \"calmar\": -1.0017980834966258, \"daily_log_sharpe\": -1.54806276945384, \"daily_returns\": 0.01056023264659625, \"fee_revenue_over_value\": 0.29511332339152097, \"jax_sharpe\": -0.20074278886622332, \"return\": -0.39328069816551436, \"returns_over_hodl\": 0.5232212264923386, \"returns_over_uniform_hodl\": 0.4556312279768633, \"sharpe\": -1.1463267071079364, \"sterling\": -1.8606583776874137, \"ulcer\": -0.14516254062166584}], \"train_objective\": [{\"annualised_returns\": 0.6231818156542142, \"annualised_returns_over_hodl\": 0.3423482279179051, \"annualised_returns_over_uniform_hodl\": 0.3423482279179042, \"calmar\": 0.8690515335922532, \"daily_log_sharpe\": 0.5494001816635904, \"daily_returns\": 0.023609357883220366, \"fee_revenue_over_value\": 0.7123599053099045, \"jax_sharpe\": 0.9888223658995481, \"return\": 0.3418946049713101, \"returns_over_hodl\": 0.19572055096481988, \"returns_over_uniform_hodl\": 0.19572055096481944, \"sharpe\": 0.9905850313860984, \"sterling\": 2.232514055580623, \"ulcer\": -0.14796052597264234}], \"train_return\": 0.3418946049713101, \"train_returns_over_hodl\": 0.19572055096481988, \"train_sharpe\": 0.9888223658995482, \"validation_return\": 0.04937899715358385, \"validation_returns_over_hodl\": 0.09486144047165102, \"validation_sharpe\": 0.8119506679596528}, {\"centeredness_margin\": 0.13797824558731572, \"continuous_test_metrics\": [{\"annualised_returns\": -0.7382995906483166, \"annualised_returns_over_hodl\": 1.5041848386742385, \"annualised_returns_over_uniform_hodl\": 1.2988152811251106, \"calmar\": -1.0426781987389258, \"daily_log_sharpe\": -1.6726855776245544, \"daily_returns\": 0.010151526694749873, \"fee_revenue_over_value\": 0.27918907738844434, \"jax_sharpe\": -0.1939847355889724, \"return\": -0.41718890342840864, \"returns_over_hodl\": 0.44729822972230227, \"returns_over_uniform_hodl\": 0.39827104497242716, \"sharpe\": -1.2752971256427055, \"sterling\": -1.9224851323222034, \"ulcer\": -0.1466029170090796}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": 0.952329754637288, \"optuna_trial_number\": 256, \"price_ratio\": 1.0482157545284267, \"shift_exponent\": 0.13059705042666545, \"step\": 256, \"test_objective\": [{\"annualised_returns\": -0.7382995906483166, \"annualised_returns_over_hodl\": 1.5041848386742385, \"annualised_returns_over_uniform_hodl\": 1.2988152811251106, \"calmar\": -1.0426781987389258, \"daily_log_sharpe\": -1.6726855776245544, \"daily_returns\": 0.010151526694749873, \"fee_revenue_over_value\": 0.27918907738844434, \"jax_sharpe\": -0.1939847355889724, \"return\": -0.41718890342840864, \"returns_over_hodl\": 0.44729822972230227, \"returns_over_uniform_hodl\": 0.39827104497242716, \"sharpe\": -1.2752971256427055, \"sterling\": -1.9224851323222034, \"ulcer\": -0.1466029170090796}], \"train_objective\": [{\"annualised_returns\": 0.7337831220051181, \"annualised_returns_over_hodl\": 0.43381393197750073, \"annualised_returns_over_uniform_hodl\": 0.43381393197749785, \"calmar\": 1.051964889755782, \"daily_log_sharpe\": 0.633151420857766, \"daily_returns\": 0.022613525918680655, \"fee_revenue_over_value\": 0.6480686587060581, \"jax_sharpe\": 1.0643575560369338, \"return\": 0.39668618139141487, \"returns_over_hodl\": 0.2445436207517957, \"returns_over_uniform_hodl\": 0.24454362075179414, \"sharpe\": 1.0684776278246513, \"sterling\": 2.695127274044047, \"ulcer\": -0.1433902388638475}], \"train_return\": 0.39668618139141487, \"train_returns_over_hodl\": 0.2445436207517957, \"train_sharpe\": 1.0643575560369338, \"validation_return\": 0.060715545705090745, \"validation_returns_over_hodl\": 0.09016864108836864, \"validation_sharpe\": 0.925261686179497}, {\"centeredness_margin\": 0.047310295754161284, \"continuous_test_metrics\": [{\"annualised_returns\": -0.8602859717609805, \"annualised_returns_over_hodl\": 0.32855743890937883, \"annualised_returns_over_uniform_hodl\": 0.22726878379388848, \"calmar\": -1.3113813873341225, \"daily_log_sharpe\": -2.186471976086842, \"daily_returns\": 0.00787312042863485, \"fee_revenue_over_value\": 0.06525871229849724, \"jax_sharpe\": -1.0257103691155083, \"return\": -0.5473569840197137, \"returns_over_hodl\": 0.1212176552478188, \"returns_over_uniform_hodl\": 0.08597387159816994, \"sharpe\": -1.7655618323078264, \"sterling\": -2.170207931743479, \"ulcer\": -0.1685811144286148}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": 0.7165670470507629, \"optuna_trial_number\": 257, \"price_ratio\": 2.645818563659898, \"shift_exponent\": 0.026693311688164075, \"step\": 257, \"test_objective\": [{\"annualised_returns\": -0.8602859717609805, \"annualised_returns_over_hodl\": 0.32855743890937883, \"annualised_returns_over_uniform_hodl\": 0.22726878379388848, \"calmar\": -1.3113813873341225, \"daily_log_sharpe\": -2.186471976086842, \"daily_returns\": 0.00787312042863485, \"fee_revenue_over_value\": 0.06525871229849724, \"jax_sharpe\": -1.0257103691155083, \"return\": -0.5473569840197137, \"returns_over_hodl\": 0.1212176552478188, \"returns_over_uniform_hodl\": 0.08597387159816994, \"sharpe\": -1.7655618323078264, \"sterling\": -2.170207931743479, \"ulcer\": -0.1685811144286148}], \"train_objective\": [{\"annualised_returns\": 0.5578911306002314, \"annualised_returns_over_hodl\": 0.28835376190274875, \"annualised_returns_over_uniform_hodl\": 0.28835376190274875, \"calmar\": 0.8832174493896706, \"daily_log_sharpe\": 0.540033541042291, \"daily_returns\": 0.02062030792437987, \"fee_revenue_over_value\": 0.1210321386422455, \"jax_sharpe\": 0.944678921525844, \"return\": 0.30886059757670914, \"returns_over_hodl\": 0.16628497429872735, \"returns_over_uniform_hodl\": 0.16628497429872735, \"sharpe\": 0.9653222873073709, \"sterling\": 2.150450950502559, \"ulcer\": -0.1308119782400967}], \"train_return\": 0.30886059757670914, \"train_returns_over_hodl\": 0.16628497429872735, \"train_sharpe\": 0.944678921525844, \"validation_return\": -0.015148836799252163, \"validation_returns_over_hodl\": 0.014964072165673636, \"validation_sharpe\": 0.18198535179756467}, {\"centeredness_margin\": 0.019042742597642624, \"continuous_test_metrics\": [{\"annualised_returns\": -0.8547368092838903, \"annualised_returns_over_hodl\": 0.43815559348829414, \"annualised_returns_over_uniform_hodl\": 0.2760134515282, \"calmar\": -1.2404173004742312, \"daily_log_sharpe\": -2.0968611974317533, \"daily_returns\": 0.008512628149455113, \"fee_revenue_over_value\": 0.08041774760994207, \"jax_sharpe\": -0.717110029562838, \"return\": -0.5402006408817714, \"returns_over_hodl\": 0.15758900872851855, \"returns_over_uniform_hodl\": 0.10314325539428393, \"sharpe\": -1.6661062831709623, \"sterling\": -2.0678069453919585, \"ulcer\": -0.17117990932659155}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": 0.7945001634318166, \"optuna_trial_number\": 258, \"price_ratio\": 1.9100182744525798, \"shift_exponent\": 0.014088843187387382, \"step\": 258, \"test_objective\": [{\"annualised_returns\": -0.8547368092838903, \"annualised_returns_over_hodl\": 0.43815559348829414, \"annualised_returns_over_uniform_hodl\": 0.2760134515282, \"calmar\": -1.2404173004742312, \"daily_log_sharpe\": -2.0968611974317533, \"daily_returns\": 0.008512628149455113, \"fee_revenue_over_value\": 0.08041774760994207, \"jax_sharpe\": -0.717110029562838, \"return\": -0.5402006408817714, \"returns_over_hodl\": 0.15758900872851855, \"returns_over_uniform_hodl\": 0.10314325539428393, \"sharpe\": -1.6661062831709623, \"sterling\": -2.0678069453919585, \"ulcer\": -0.17117990932659155}], \"train_objective\": [{\"annualised_returns\": 0.7136585136458651, \"annualised_returns_over_hodl\": 0.4171711676808205, \"annualised_returns_over_uniform_hodl\": 0.4171711676808205, \"calmar\": 1.1423191281451617, \"daily_log_sharpe\": 0.6490236135863877, \"daily_returns\": 0.020722675524917883, \"fee_revenue_over_value\": 0.17068236151838342, \"jax_sharpe\": 1.0541351877669713, \"return\": 0.38682108077277233, \"returns_over_hodl\": 0.23575313638487994, \"returns_over_uniform_hodl\": 0.23575313638487994, \"sharpe\": 1.0751812692166183, \"sterling\": 2.767912816902738, \"ulcer\": -0.12886771133676}], \"train_return\": 0.38682108077277233, \"train_returns_over_hodl\": 0.23575313638487994, \"train_sharpe\": 1.0541351877669711, \"validation_return\": -0.023842237839932334, \"validation_returns_over_hodl\": 0.0206816166708379, \"validation_sharpe\": 0.11756580354522267}, {\"centeredness_margin\": 0.03298880306584571, \"continuous_test_metrics\": [{\"annualised_returns\": -0.7274538379960159, \"annualised_returns_over_hodl\": 1.6927755892721086, \"annualised_returns_over_uniform_hodl\": 1.3940859839649637, \"calmar\": -1.0244219848779388, \"daily_log_sharpe\": -1.5520764308355939, \"daily_returns\": 0.010132468165648471, \"fee_revenue_over_value\": 0.261001189771036, \"jax_sharpe\": -0.1915450795127484, \"return\": -0.4075791118971994, \"returns_over_hodl\": 0.4902456978426746, \"returns_over_uniform_hodl\": 0.42132670284400064, \"sharpe\": -1.1332576672494465, \"sterling\": -1.8530482164040223, \"ulcer\": -0.1503873946315688}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": 1.0406142098518916, \"optuna_trial_number\": 259, \"price_ratio\": 1.0731586921164187, \"shift_exponent\": 0.026130606324000515, \"step\": 259, \"test_objective\": [{\"annualised_returns\": -0.7274538379960159, \"annualised_returns_over_hodl\": 1.6927755892721086, \"annualised_returns_over_uniform_hodl\": 1.3940859839649637, \"calmar\": -1.0244219848779388, \"daily_log_sharpe\": -1.5520764308355939, \"daily_returns\": 0.010132468165648471, \"fee_revenue_over_value\": 0.261001189771036, \"jax_sharpe\": -0.1915450795127484, \"return\": -0.4075791118971994, \"returns_over_hodl\": 0.4902456978426746, \"returns_over_uniform_hodl\": 0.42132670284400064, \"sharpe\": -1.1332576672494465, \"sterling\": -1.8530482164040223, \"ulcer\": -0.1503873946315688}], \"train_objective\": [{\"annualised_returns\": 1.3308596351355662, \"annualised_returns_over_hodl\": 0.9275876987868683, \"annualised_returns_over_uniform_hodl\": 0.9275876987868701, \"calmar\": 2.189169590494006, \"daily_log_sharpe\": 0.9627876749330236, \"daily_returns\": 0.022762545046558447, \"fee_revenue_over_value\": 0.5811855383873664, \"jax_sharpe\": 1.394243176122378, \"return\": 0.6715789613051155, \"returns_over_hodl\": 0.4894920280536381, \"returns_over_uniform_hodl\": 0.489492028053639, \"sharpe\": 1.404828891794167, \"sterling\": 5.117057692403427, \"ulcer\": -0.1277064434061315}], \"train_return\": 0.6715789613051155, \"train_returns_over_hodl\": 0.4894920280536381, \"train_sharpe\": 1.3942431761223781, \"validation_return\": 0.04849010669508136, \"validation_returns_over_hodl\": 0.10105160299158067, \"validation_sharpe\": 0.802353738105723}, {\"centeredness_margin\": 0.016193006963364037, \"continuous_test_metrics\": [{\"annualised_returns\": -0.857673549739207, \"annualised_returns_over_hodl\": 0.3364440567863429, \"annualised_returns_over_uniform_hodl\": 0.25021668700610955, \"calmar\": -1.3128893750019106, \"daily_log_sharpe\": -2.1777364551751623, \"daily_returns\": 0.007669828903806407, \"fee_revenue_over_value\": 0.06105711743488738, \"jax_sharpe\": -1.0933003036413818, \"return\": -0.5439671869985819, \"returns_over_hodl\": 0.12389346149201352, \"returns_over_uniform_hodl\": 0.09410661829922762, \"sharpe\": -1.7588530402904903, \"sterling\": -2.199755027178951, \"ulcer\": -0.16691708888364798}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": 0.6898619992892272, \"optuna_trial_number\": 260, \"price_ratio\": 3.1375264307667514, \"shift_exponent\": 0.05861596644566536, \"step\": 260, \"test_objective\": [{\"annualised_returns\": -0.857673549739207, \"annualised_returns_over_hodl\": 0.3364440567863429, \"annualised_returns_over_uniform_hodl\": 0.25021668700610955, \"calmar\": -1.3128893750019106, \"daily_log_sharpe\": -2.1777364551751623, \"daily_returns\": 0.007669828903806407, \"fee_revenue_over_value\": 0.06105711743488738, \"jax_sharpe\": -1.0933003036413818, \"return\": -0.5439671869985819, \"returns_over_hodl\": 0.12389346149201352, \"returns_over_uniform_hodl\": 0.09410661829922762, \"sharpe\": -1.7588530402904903, \"sterling\": -2.199755027178951, \"ulcer\": -0.16691708888364798}], \"train_objective\": [{\"annualised_returns\": 0.5074579493873961, \"annualised_returns_over_hodl\": 0.24664623981470313, \"annualised_returns_over_uniform_hodl\": 0.24664623981470313, \"calmar\": 0.8001817046130385, \"daily_log_sharpe\": 0.5021839065745347, \"daily_returns\": 0.020563972434000648, \"fee_revenue_over_value\": 0.10585626262712679, \"jax_sharpe\": 0.9069356424869537, \"return\": 0.2829699269226751, \"returns_over_hodl\": 0.1432146028518193, \"returns_over_uniform_hodl\": 0.1432146028518193, \"sharpe\": 0.9272747843772641, \"sterling\": 1.9518323141008944, \"ulcer\": -0.13148270624334482}], \"train_return\": 0.2829699269226751, \"train_returns_over_hodl\": 0.1432146028518193, \"train_sharpe\": 0.9069356424869538, \"validation_return\": -0.012777959910270531, \"validation_returns_over_hodl\": 0.012890433850095251, \"validation_sharpe\": 0.2004305152065717}, {\"centeredness_margin\": 0.08344887342709656, \"continuous_test_metrics\": [{\"annualised_returns\": -0.7184429166752735, \"annualised_returns_over_hodl\": 1.7215354276075434, \"annualised_returns_over_uniform_hodl\": 1.4732392557556149, \"calmar\": -1.0132314178957424, \"daily_log_sharpe\": -1.5610438582929438, \"daily_returns\": 0.010497428657753672, \"fee_revenue_over_value\": 0.2916960506777788, \"jax_sharpe\": -0.21174590745141575, \"return\": -0.3997673807101957, \"returns_over_hodl\": 0.49663549438361376, \"returns_over_uniform_hodl\": 0.4400684831466575, \"sharpe\": -1.1554769248092114, \"sterling\": -1.8672135161194554, \"ulcer\": -0.14533440151825103}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": 1.031440100338066, \"optuna_trial_number\": 261, \"price_ratio\": 1.048576396892968, \"shift_exponent\": 0.0507080479272693, \"step\": 261, \"test_objective\": [{\"annualised_returns\": -0.7184429166752735, \"annualised_returns_over_hodl\": 1.7215354276075434, \"annualised_returns_over_uniform_hodl\": 1.4732392557556149, \"calmar\": -1.0132314178957424, \"daily_log_sharpe\": -1.5610438582929438, \"daily_returns\": 0.010497428657753672, \"fee_revenue_over_value\": 0.2916960506777788, \"jax_sharpe\": -0.21174590745141575, \"return\": -0.3997673807101957, \"returns_over_hodl\": 0.49663549438361376, \"returns_over_uniform_hodl\": 0.4400684831466575, \"sharpe\": -1.1554769248092114, \"sterling\": -1.8672135161194554, \"ulcer\": -0.14533440151825103}], \"train_objective\": [{\"annualised_returns\": 1.0697236847330873, \"annualised_returns_over_hodl\": 0.7116319895201675, \"annualised_returns_over_uniform_hodl\": 0.7116319895201644, \"calmar\": 1.6363474247218877, \"daily_log_sharpe\": 0.8351148924150414, \"daily_returns\": 0.02201898459917528, \"fee_revenue_over_value\": 0.6631984518901607, \"jax_sharpe\": 1.2613894461075073, \"return\": 0.5552390609685101, \"returns_over_hodl\": 0.38582516091346597, \"returns_over_uniform_hodl\": 0.3858251609134644, \"sharpe\": 1.2709562852241443, \"sterling\": 4.025346733268921, \"ulcer\": -0.13353872978655593}], \"train_return\": 0.5552390609685101, \"train_returns_over_hodl\": 0.38582516091346597, \"train_sharpe\": 1.2613894461075073, \"validation_return\": 0.06905005047806956, \"validation_returns_over_hodl\": 0.10309071042538753, \"validation_sharpe\": 1.0027947249746307}, {\"centeredness_margin\": 0.09506656215718376, \"continuous_test_metrics\": [{\"annualised_returns\": -0.8624301359258862, \"annualised_returns_over_hodl\": 0.29186285742333085, \"annualised_returns_over_uniform_hodl\": 0.2084341271736072, \"calmar\": -1.2293502837612742, \"daily_log_sharpe\": -2.2527614556176334, \"daily_returns\": 0.007666475459220788, \"fee_revenue_over_value\": 0.05869973472342173, \"jax_sharpe\": -0.6715002979811138, \"return\": -0.5501675802054208, \"returns_over_hodl\": 0.1086413061978515, \"returns_over_uniform_hodl\": 0.0792307342613856, \"sharpe\": -1.8414195411800331, \"sterling\": -2.096417088789088, \"ulcer\": -0.1663329692554051}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": 0.6898179965514732, \"optuna_trial_number\": 262, \"price_ratio\": 3.1340451520118453, \"shift_exponent\": 0.05218823905234285, \"step\": 262, \"test_objective\": [{\"annualised_returns\": -0.8624301359258862, \"annualised_returns_over_hodl\": 0.29186285742333085, \"annualised_returns_over_uniform_hodl\": 0.2084341271736072, \"calmar\": -1.2293502837612742, \"daily_log_sharpe\": -2.2527614556176334, \"daily_returns\": 0.007666475459220788, \"fee_revenue_over_value\": 0.05869973472342173, \"jax_sharpe\": -0.6715002979811138, \"return\": -0.5501675802054208, \"returns_over_hodl\": 0.1086413061978515, \"returns_over_uniform_hodl\": 0.0792307342613856, \"sharpe\": -1.8414195411800331, \"sterling\": -2.096417088789088, \"ulcer\": -0.1663329692554051}], \"train_objective\": [{\"annualised_returns\": 0.5073215755459777, \"annualised_returns_over_hodl\": 0.2465334605914533, \"annualised_returns_over_uniform_hodl\": 0.24653346059145398, \"calmar\": 0.7998717341041304, \"daily_log_sharpe\": 0.5020893509851104, \"daily_returns\": 0.020584749389259676, \"fee_revenue_over_value\": 0.10593624242922615, \"jax_sharpe\": 0.9068327092024444, \"return\": 0.2828994599534427, \"returns_over_hodl\": 0.14315181192698545, \"returns_over_uniform_hodl\": 0.1431518119269859, \"sharpe\": 0.9271773302100664, \"sterling\": 1.9512429265767555, \"ulcer\": -0.1314935948822613}], \"train_return\": 0.2828994599534427, \"train_returns_over_hodl\": 0.14315181192698545, \"train_sharpe\": 0.9068327092024443, \"validation_return\": -0.012768398284931815, \"validation_returns_over_hodl\": 0.01283426715966507, \"validation_sharpe\": 0.20054274921232582}, {\"centeredness_margin\": 0.14027651188240675, \"continuous_test_metrics\": [{\"annualised_returns\": -0.8610696245668353, \"annualised_returns_over_hodl\": 0.31565564705583715, \"annualised_returns_over_uniform_hodl\": 0.22038506110633826, \"calmar\": -1.3174903383568446, \"daily_log_sharpe\": -2.273348902599771, \"daily_returns\": 0.007804745429205223, \"fee_revenue_over_value\": 0.06248954513808816, \"jax_sharpe\": -1.093971935860861, \"return\": -0.5483811985643715, \"returns_over_hodl\": 0.11681974636699088, \"returns_over_uniform_hodl\": 0.0835165924728083, \"sharpe\": -1.8682970525339906, \"sterling\": -2.2220446321047613, \"ulcer\": -0.16513245429270085}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": 0.7090306826228767, \"optuna_trial_number\": 263, \"price_ratio\": 2.7799694514552944, \"shift_exponent\": 0.20234376433925844, \"step\": 263, \"test_objective\": [{\"annualised_returns\": -0.8610696245668353, \"annualised_returns_over_hodl\": 0.31565564705583715, \"annualised_returns_over_uniform_hodl\": 0.22038506110633826, \"calmar\": -1.3174903383568446, \"daily_log_sharpe\": -2.273348902599771, \"daily_returns\": 0.007804745429205223, \"fee_revenue_over_value\": 0.06248954513808816, \"jax_sharpe\": -1.093971935860861, \"return\": -0.5483811985643715, \"returns_over_hodl\": 0.11681974636699088, \"returns_over_uniform_hodl\": 0.0835165924728083, \"sharpe\": -1.8682970525339906, \"sterling\": -2.2220446321047613, \"ulcer\": -0.16513245429270085}], \"train_objective\": [{\"annualised_returns\": 0.5433601433905908, \"annualised_returns_over_hodl\": 0.2763368425763719, \"annualised_returns_over_uniform_hodl\": 0.2763368425763715, \"calmar\": 0.8593200614180396, \"daily_log_sharpe\": 0.5292719882944756, \"daily_returns\": 0.020616927237088575, \"fee_revenue_over_value\": 0.11614477038551915, \"jax_sharpe\": 0.9339242389991024, \"return\": 0.3014351025741051, \"returns_over_hodl\": 0.15966834662707075, \"returns_over_uniform_hodl\": 0.15966834662707052, \"sharpe\": 0.95449557763702, \"sterling\": 2.093308364285561, \"ulcer\": -0.13099241525456692}], \"train_return\": 0.3014351025741051, \"train_returns_over_hodl\": 0.15966834662707075, \"train_sharpe\": 0.9339242389991024, \"validation_return\": -0.014304509010438893, \"validation_returns_over_hodl\": 0.014412226895199609, \"validation_sharpe\": 0.18863830446568902}, {\"centeredness_margin\": 0.04554121768579425, \"continuous_test_metrics\": [{\"annualised_returns\": -0.7772689783187604, \"annualised_returns_over_hodl\": 1.190672124414414, \"annualised_returns_over_uniform_hodl\": 0.9565023894684579, \"calmar\": -1.0940107527373395, \"daily_log_sharpe\": -1.7418731625756416, \"daily_returns\": 0.009407871739391187, \"fee_revenue_over_value\": 0.1900675582536841, \"jax_sharpe\": -0.3286343361718868, \"return\": -0.4538315331816075, \"returns_over_hodl\": 0.37139762669294973, \"returns_over_uniform_hodl\": 0.3103586347644838, \"sharpe\": -1.3203704933599039, \"sterling\": -1.9327410868230532, \"ulcer\": -0.1580812291923324}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": 0.9155470951095002, \"optuna_trial_number\": 264, \"price_ratio\": 1.1600409924955628, \"shift_exponent\": 0.026982047881470433, \"step\": 264, \"test_objective\": [{\"annualised_returns\": -0.7772689783187604, \"annualised_returns_over_hodl\": 1.190672124414414, \"annualised_returns_over_uniform_hodl\": 0.9565023894684579, \"calmar\": -1.0940107527373395, \"daily_log_sharpe\": -1.7418731625756416, \"daily_returns\": 0.009407871739391187, \"fee_revenue_over_value\": 0.1900675582536841, \"jax_sharpe\": -0.3286343361718868, \"return\": -0.4538315331816075, \"returns_over_hodl\": 0.37139762669294973, \"returns_over_uniform_hodl\": 0.3103586347644838, \"sharpe\": -1.3203704933599039, \"sterling\": -1.9327410868230532, \"ulcer\": -0.1580812291923324}], \"train_objective\": [{\"annualised_returns\": 1.1712077578519904, \"annualised_returns_over_hodl\": 0.7955578716359308, \"annualised_returns_over_uniform_hodl\": 0.7955578716359308, \"calmar\": 1.9496650275210112, \"daily_log_sharpe\": 0.888745723176066, \"daily_returns\": 0.02162650821258736, \"fee_revenue_over_value\": 0.42850778421007063, \"jax_sharpe\": 1.317926664209361, \"return\": 0.6011005600918251, \"returns_over_hodl\": 0.426690916537376, \"returns_over_uniform_hodl\": 0.426690916537376, \"sharpe\": 1.3307414856192938, \"sterling\": 4.5648125603579945, \"ulcer\": -0.12495673335226455}], \"train_return\": 0.6011005600918251, \"train_returns_over_hodl\": 0.426690916537376, \"train_sharpe\": 1.317926664209361, \"validation_return\": 0.013142151063110674, \"validation_returns_over_hodl\": 0.06853724293921704, \"validation_sharpe\": 0.4658449660945171}, {\"centeredness_margin\": 0.019289085143868352, \"continuous_test_metrics\": [{\"annualised_returns\": -0.676405525728135, \"annualised_returns_over_hodl\": 2.2153004786229085, \"annualised_returns_over_uniform_hodl\": 1.8425019440613455, \"calmar\": -0.9530885436867144, \"daily_log_sharpe\": -1.3815963017151232, \"daily_returns\": 0.011011044314341814, \"fee_revenue_over_value\": 0.32916953539979776, \"jax_sharpe\": -0.07143773540495853, \"return\": -0.3651678565052411, \"returns_over_hodl\": 0.6005797172427063, \"returns_over_uniform_hodl\": 0.523079107258321, \"sharpe\": -0.9649521795034103, \"sterling\": -1.7574906852725938, \"ulcer\": -0.1440360987309839}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": 1.025743479720082, \"optuna_trial_number\": 265, \"price_ratio\": 1.0409460988969446, \"shift_exponent\": 0.021487582316831323, \"step\": 265, \"test_objective\": [{\"annualised_returns\": -0.676405525728135, \"annualised_returns_over_hodl\": 2.2153004786229085, \"annualised_returns_over_uniform_hodl\": 1.8425019440613455, \"calmar\": -0.9530885436867144, \"daily_log_sharpe\": -1.3815963017151232, \"daily_returns\": 0.011011044314341814, \"fee_revenue_over_value\": 0.32916953539979776, \"jax_sharpe\": -0.07143773540495853, \"return\": -0.3651678565052411, \"returns_over_hodl\": 0.6005797172427063, \"returns_over_uniform_hodl\": 0.523079107258321, \"sharpe\": -0.9649521795034103, \"sterling\": -1.7574906852725938, \"ulcer\": -0.1440360987309839}], \"train_objective\": [{\"annualised_returns\": 1.2145121350456067, \"annualised_returns_over_hodl\": 0.831369974399975, \"annualised_returns_over_uniform_hodl\": 0.8313699743999758, \"calmar\": 1.9733614569448892, \"daily_log_sharpe\": 0.9010698715898324, \"daily_returns\": 0.021758402193799268, \"fee_revenue_over_value\": 0.6032171797938708, \"jax_sharpe\": 1.3332595651004027, \"return\": 0.6204129179956441, \"returns_over_hodl\": 0.44389955807124437, \"returns_over_uniform_hodl\": 0.4438995580712448, \"sharpe\": 1.3441266013938524, \"sterling\": 4.631729222567116, \"ulcer\": -0.12886097742088484}], \"train_return\": 0.6204129179956441, \"train_returns_over_hodl\": 0.44389955807124437, \"train_sharpe\": 1.3332595651004024, \"validation_return\": 0.06359169774817452, \"validation_returns_over_hodl\": 0.13267402580527166, \"validation_sharpe\": 0.9462200673034994}, {\"centeredness_margin\": 0.04169666464042215, \"continuous_test_metrics\": [{\"annualised_returns\": -0.7834247852444458, \"annualised_returns_over_hodl\": 1.1617183231631492, \"annualised_returns_over_uniform_hodl\": 0.9024288667579725, \"calmar\": -1.1005847832807318, \"daily_log_sharpe\": -1.7261443547767539, \"daily_returns\": 0.007861937475455511, \"fee_revenue_over_value\": 0.16811487174121267, \"jax_sharpe\": -0.3499169474249086, \"return\": -0.45996175708775633, \"returns_over_hodl\": 0.36406875853825893, \"returns_over_uniform_hodl\": 0.29565109978858994, \"sharpe\": -1.2932751027449498, \"sterling\": -1.9185303417743547, \"ulcer\": -0.16387897138343518}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": 0.8733029743538087, \"optuna_trial_number\": 266, \"price_ratio\": 1.1617216264721733, \"shift_exponent\": 0.006027773472557013, \"step\": 266, \"test_objective\": [{\"annualised_returns\": -0.7834247852444458, \"annualised_returns_over_hodl\": 1.1617183231631492, \"annualised_returns_over_uniform_hodl\": 0.9024288667579725, \"calmar\": -1.1005847832807318, \"daily_log_sharpe\": -1.7261443547767539, \"daily_returns\": 0.007861937475455511, \"fee_revenue_over_value\": 0.16811487174121267, \"jax_sharpe\": -0.3499169474249086, \"return\": -0.45996175708775633, \"returns_over_hodl\": 0.36406875853825893, \"returns_over_uniform_hodl\": 0.29565109978858994, \"sharpe\": -1.2932751027449498, \"sterling\": -1.9185303417743547, \"ulcer\": -0.16387897138343518}], \"train_objective\": [{\"annualised_returns\": 1.1252741752217155, \"annualised_returns_over_hodl\": 0.7575714534472706, \"annualised_returns_over_uniform_hodl\": 0.7575714534472697, \"calmar\": 1.9244028558587836, \"daily_log_sharpe\": 0.8859048827156464, \"daily_returns\": 0.021721450909260722, \"fee_revenue_over_value\": 0.32496542934049893, \"jax_sharpe\": 1.294137160041292, \"return\": 0.5804495132772685, \"returns_over_hodl\": 0.4082894109469801, \"returns_over_uniform_hodl\": 0.4082894109469797, \"sharpe\": 1.326620271004535, \"sterling\": 4.434986967348662, \"ulcer\": -0.12119558921787782}], \"train_return\": 0.5804495132772685, \"train_returns_over_hodl\": 0.4082894109469801, \"train_sharpe\": 1.294137160041292, \"validation_return\": -0.060331116860169676, \"validation_returns_over_hodl\": 0.010291732492619055, \"validation_sharpe\": -0.19737113096267261}, {\"centeredness_margin\": 0.09889618841236308, \"continuous_test_metrics\": [{\"annualised_returns\": -0.623524372148424, \"annualised_returns_over_hodl\": 2.7376219192194227, \"annualised_returns_over_uniform_hodl\": 2.307017854577944, \"calmar\": -0.880550432673255, \"daily_log_sharpe\": -1.2313896974582532, \"daily_returns\": 0.012676546647728182, \"fee_revenue_over_value\": 0.4168051634830321, \"jax_sharpe\": 0.0007823413201043005, \"return\": -0.32526507708001073, \"returns_over_hodl\": 0.7006142436411713, \"returns_over_uniform_hodl\": 0.6188132163245981, \"sharpe\": -0.8147570473971726, \"sterling\": -1.6198784715481227, \"ulcer\": -0.1430020000191546}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": 0.7171804295867555, \"optuna_trial_number\": 267, \"price_ratio\": 1.019764395391542, \"shift_exponent\": 0.01675269675100449, \"step\": 267, \"test_objective\": [{\"annualised_returns\": -0.623524372148424, \"annualised_returns_over_hodl\": 2.7376219192194227, \"annualised_returns_over_uniform_hodl\": 2.307017854577944, \"calmar\": -0.880550432673255, \"daily_log_sharpe\": -1.2313896974582532, \"daily_returns\": 0.012676546647728182, \"fee_revenue_over_value\": 0.4168051634830321, \"jax_sharpe\": 0.0007823413201043005, \"return\": -0.32526507708001073, \"returns_over_hodl\": 0.7006142436411713, \"returns_over_uniform_hodl\": 0.6188132163245981, \"sharpe\": -0.8147570473971726, \"sterling\": -1.6198784715481227, \"ulcer\": -0.1430020000191546}], \"train_objective\": [{\"annualised_returns\": 0.5386055470574203, \"annualised_returns_over_hodl\": 0.2724048591714672, \"annualised_returns_over_uniform_hodl\": 0.27240485917146673, \"calmar\": 0.844314953943063, \"daily_log_sharpe\": 0.5169355498432937, \"daily_returns\": 0.01959484015287156, \"fee_revenue_over_value\": 0.43739485876785716, \"jax_sharpe\": 0.9286298703964335, \"return\": 0.29899949377224133, \"returns_over_hodl\": 0.15749805136862816, \"returns_over_uniform_hodl\": 0.15749805136862793, \"sharpe\": 0.9579471245158354, \"sterling\": 2.0054088098547016, \"ulcer\": -0.1330426976884671}], \"train_return\": 0.29899949377224133, \"train_returns_over_hodl\": 0.15749805136862816, \"train_sharpe\": 0.9286298703964334, \"validation_return\": 0.10216332166288278, \"validation_returns_over_hodl\": 0.18499879210488013, \"validation_sharpe\": 1.301314734793766}, {\"centeredness_margin\": 0.13579788767364848, \"continuous_test_metrics\": [{\"annualised_returns\": -0.7322511870886929, \"annualised_returns_over_hodl\": 1.6303947868685231, \"annualised_returns_over_uniform_hodl\": 1.3519453567093258, \"calmar\": -1.0316891787867348, \"daily_log_sharpe\": -1.5825079154716066, \"daily_returns\": 0.008913651809139942, \"fee_revenue_over_value\": 0.24431744477627415, \"jax_sharpe\": -0.234045062742196, \"return\": -0.41180105570889003, \"returns_over_hodl\": 0.4762445837253282, \"returns_over_uniform_hodl\": 0.41119748289586555, \"sharpe\": -1.1670154615664052, \"sterling\": -1.875491228107125, \"ulcer\": -0.14945359430927904}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": 0.8564567991568472, \"optuna_trial_number\": 268, \"price_ratio\": 1.0890018701181232, \"shift_exponent\": 0.011246319542529566, \"step\": 268, \"test_objective\": [{\"annualised_returns\": -0.7322511870886929, \"annualised_returns_over_hodl\": 1.6303947868685231, \"annualised_returns_over_uniform_hodl\": 1.3519453567093258, \"calmar\": -1.0316891787867348, \"daily_log_sharpe\": -1.5825079154716066, \"daily_returns\": 0.008913651809139942, \"fee_revenue_over_value\": 0.24431744477627415, \"jax_sharpe\": -0.234045062742196, \"return\": -0.41180105570889003, \"returns_over_hodl\": 0.4762445837253282, \"returns_over_uniform_hodl\": 0.41119748289586555, \"sharpe\": -1.1670154615664052, \"sterling\": -1.875491228107125, \"ulcer\": -0.14945359430927904}], \"train_objective\": [{\"annualised_returns\": 1.009102446145802, \"annualised_returns_over_hodl\": 0.6614990891839003, \"annualised_returns_over_uniform_hodl\": 0.661499089183899, \"calmar\": 1.6986135672850686, \"daily_log_sharpe\": 0.8204202683829203, \"daily_returns\": 0.022487313257387263, \"fee_revenue_over_value\": 0.39156962752901087, \"jax_sharpe\": 1.2283658511868194, \"return\": 0.5274220082836729, \"returns_over_hodl\": 0.3610382503473837, \"returns_over_uniform_hodl\": 0.361038250347383, \"sharpe\": 1.2620708697040195, \"sterling\": 3.8997968357869404, \"ulcer\": -0.12505027405928285}], \"train_return\": 0.5274220082836729, \"train_returns_over_hodl\": 0.3610382503473837, \"train_sharpe\": 1.2283658511868192, \"validation_return\": 0.00879899505877657, \"validation_returns_over_hodl\": 0.08461746758954769, \"validation_sharpe\": 0.4330626352203391}, {\"centeredness_margin\": 0.012523774907271768, \"continuous_test_metrics\": [{\"annualised_returns\": -0.6875884864371191, \"annualised_returns_over_hodl\": 2.0692797820443345, \"annualised_returns_over_uniform_hodl\": 1.7442691555467214, \"calmar\": -0.9694880521332027, \"daily_log_sharpe\": -1.4569784503775207, \"daily_returns\": 0.011220829618065874, \"fee_revenue_over_value\": 0.3428043952955455, \"jax_sharpe\": -0.14329797118814208, \"return\": -0.374096378809156, \"returns_over_hodl\": 0.5708980888266466, \"returns_over_uniform_hodl\": 0.5016579396014331, \"sharpe\": -1.0527250857567383, \"sterling\": -1.800383100879887, \"ulcer\": -0.14362638364978506}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": 1.0270988751640897, \"optuna_trial_number\": 269, \"price_ratio\": 1.0196038346812177, \"shift_exponent\": 0.1659137755818413, \"step\": 269, \"test_objective\": [{\"annualised_returns\": -0.6875884864371191, \"annualised_returns_over_hodl\": 2.0692797820443345, \"annualised_returns_over_uniform_hodl\": 1.7442691555467214, \"calmar\": -0.9694880521332027, \"daily_log_sharpe\": -1.4569784503775207, \"daily_returns\": 0.011220829618065874, \"fee_revenue_over_value\": 0.3428043952955455, \"jax_sharpe\": -0.14329797118814208, \"return\": -0.374096378809156, \"returns_over_hodl\": 0.5708980888266466, \"returns_over_uniform_hodl\": 0.5016579396014331, \"sharpe\": -1.0527250857567383, \"sterling\": -1.800383100879887, \"ulcer\": -0.14362638364978506}], \"train_objective\": [{\"annualised_returns\": 0.7121925134025324, \"annualised_returns_over_hodl\": 0.41595880637305704, \"annualised_returns_over_uniform_hodl\": 0.41595880637305704, \"calmar\": 0.9985870846811647, \"daily_log_sharpe\": 0.6049294260422357, \"daily_returns\": 0.026810891607266883, \"fee_revenue_over_value\": 0.8471680571790203, \"jax_sharpe\": 1.048279279583343, \"return\": 0.386100672434716, \"returns_over_hodl\": 0.23511120291878718, \"returns_over_uniform_hodl\": 0.23511120291878718, \"sharpe\": 1.0470412515649008, \"sterling\": 2.536039880251693, \"ulcer\": -0.1491424561658015}], \"train_return\": 0.386100672434716, \"train_returns_over_hodl\": 0.23511120291878718, \"train_sharpe\": 1.048279279583343, \"validation_return\": 0.0732048659751039, \"validation_returns_over_hodl\": 0.10945652015551377, \"validation_sharpe\": 1.0387568863214434}, {\"centeredness_margin\": 0.062213267964604246, \"continuous_test_metrics\": [{\"annualised_returns\": -0.8325590738322012, \"annualised_returns_over_hodl\": 0.6712905888955567, \"annualised_returns_over_uniform_hodl\": 0.4708259750675916, \"calmar\": -1.1690393793038918, \"daily_log_sharpe\": -1.955538404892099, \"daily_returns\": 0.007841752013264846, \"fee_revenue_over_value\": 0.08907442344515394, \"jax_sharpe\": -0.505386571414576, \"return\": -0.5131224863846668, \"returns_over_hodl\": 0.22979143481355147, \"returns_over_uniform_hodl\": 0.16810872981184333, \"sharpe\": -1.5106473503641586, \"sterling\": -1.953020066880353, \"ulcer\": -0.1768598023499769}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -0.007918578184049951, \"optuna_trial_number\": 270, \"price_ratio\": 1.11021854129465, \"shift_exponent\": 0.004077382143725431, \"step\": 270, \"test_objective\": [{\"annualised_returns\": -0.8325590738322012, \"annualised_returns_over_hodl\": 0.6712905888955567, \"annualised_returns_over_uniform_hodl\": 0.4708259750675916, \"calmar\": -1.1690393793038918, \"daily_log_sharpe\": -1.955538404892099, \"daily_returns\": 0.007841752013264846, \"fee_revenue_over_value\": 0.08907442344515394, \"jax_sharpe\": -0.505386571414576, \"return\": -0.5131224863846668, \"returns_over_hodl\": 0.22979143481355147, \"returns_over_uniform_hodl\": 0.16810872981184333, \"sharpe\": -1.5106473503641586, \"sterling\": -1.953020066880353, \"ulcer\": -0.1768598023499769}], \"train_objective\": [{\"annualised_returns\": 1.1984030407673298, \"annualised_returns_over_hodl\": 0.818047983019059, \"annualised_returns_over_uniform_hodl\": 0.8180479830190586, \"calmar\": 2.094728304970346, \"daily_log_sharpe\": 0.922634433858685, \"daily_returns\": 0.022239604545264854, \"fee_revenue_over_value\": 0.3448901596074719, \"jax_sharpe\": 1.3316744043819342, \"return\": 0.6132462623603168, \"returns_over_hodl\": 0.4375135741101337, \"returns_over_uniform_hodl\": 0.4375135741101335, \"sharpe\": 1.3642554067199697, \"sterling\": 4.753876465295289, \"ulcer\": -0.11975960600975712}], \"train_return\": 0.6132462623603168, \"train_returns_over_hodl\": 0.4375135741101337, \"train_sharpe\": 1.3316744043819344, \"validation_return\": -0.06990342226799351, \"validation_returns_over_hodl\": -2.2814639066837117e-10, \"validation_sharpe\": -0.28596766157554504}, {\"centeredness_margin\": 0.017518725110174466, \"continuous_test_metrics\": [{\"annualised_returns\": -0.8503721993869444, \"annualised_returns_over_hodl\": 0.49349111142760504, \"annualised_returns_over_uniform_hodl\": 0.3143528333889518, \"calmar\": -1.1943026498111258, \"daily_log_sharpe\": -2.048210490615482, \"daily_returns\": 0.00786802592377975, \"fee_revenue_over_value\": 0.08219804020540125, \"jax_sharpe\": -0.584859386060772, \"return\": -0.5346858641609882, \"returns_over_hodl\": 0.17532505132289455, \"returns_over_uniform_hodl\": 0.11637421934387326, \"sharpe\": -1.6117371920176513, \"sterling\": -2.020367409956261, \"ulcer\": -0.17304697005303024}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": 0.8908591424416928, \"optuna_trial_number\": 271, \"price_ratio\": 1.5554727406127027, \"shift_exponent\": 0.005263997775477394, \"step\": 271, \"test_objective\": [{\"annualised_returns\": -0.8503721993869444, \"annualised_returns_over_hodl\": 0.49349111142760504, \"annualised_returns_over_uniform_hodl\": 0.3143528333889518, \"calmar\": -1.1943026498111258, \"daily_log_sharpe\": -2.048210490615482, \"daily_returns\": 0.00786802592377975, \"fee_revenue_over_value\": 0.08219804020540125, \"jax_sharpe\": -0.584859386060772, \"return\": -0.5346858641609882, \"returns_over_hodl\": 0.17532505132289455, \"returns_over_uniform_hodl\": 0.11637421934387326, \"sharpe\": -1.6117371920176513, \"sterling\": -2.020367409956261, \"ulcer\": -0.17304697005303024}], \"train_objective\": [{\"annualised_returns\": 0.9406897855338183, \"annualised_returns_over_hodl\": 0.6049227938767507, \"annualised_returns_over_uniform_hodl\": 0.6049227938767507, \"calmar\": 1.5315307088455083, \"daily_log_sharpe\": 0.7906429176206738, \"daily_returns\": 0.020888586886107785, \"fee_revenue_over_value\": 0.23598848676304168, \"jax_sharpe\": 1.1969828826239999, \"return\": 0.4956305186451224, \"returns_over_hodl\": 0.33270984261269887, \"returns_over_uniform_hodl\": 0.33270984261269887, \"sharpe\": 1.2182890238287092, \"sterling\": 3.680092197790633, \"ulcer\": -0.12624589333859584}], \"train_return\": 0.4956305186451224, \"train_returns_over_hodl\": 0.33270984261269887, \"train_sharpe\": 1.1969828826239999, \"validation_return\": -0.039923777227537594, \"validation_returns_over_hodl\": 0.02189735567337059, \"validation_sharpe\": -0.020605935459633597}, {\"centeredness_margin\": 0.042465352941625484, \"continuous_test_metrics\": [{\"annualised_returns\": -0.7566197627430674, \"annualised_returns_over_hodl\": 1.3884969960145734, \"annualised_returns_over_uniform_hodl\": 1.1378881672983252, \"calmar\": -1.0651879141505658, \"daily_log_sharpe\": -1.6687099449332576, \"daily_returns\": 0.009828042245597263, \"fee_revenue_over_value\": 0.2205300106918577, \"jax_sharpe\": -0.29612931149415767, \"return\": -0.4339772570759417, \"returns_over_hodl\": 0.4199894522353729, \"returns_over_uniform_hodl\": 0.3579926958878039, \"sharpe\": -1.2499936117839143, \"sterling\": -1.9031920734684398, \"ulcer\": -0.15465968506188388}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": 0.980597242053963, \"optuna_trial_number\": 272, \"price_ratio\": 1.112055696808252, \"shift_exponent\": 0.03742676247014048, \"step\": 272, \"test_objective\": [{\"annualised_returns\": -0.7566197627430674, \"annualised_returns_over_hodl\": 1.3884969960145734, \"annualised_returns_over_uniform_hodl\": 1.1378881672983252, \"calmar\": -1.0651879141505658, \"daily_log_sharpe\": -1.6687099449332576, \"daily_returns\": 0.009828042245597263, \"fee_revenue_over_value\": 0.2205300106918577, \"jax_sharpe\": -0.29612931149415767, \"return\": -0.4339772570759417, \"returns_over_hodl\": 0.4199894522353729, \"returns_over_uniform_hodl\": 0.3579926958878039, \"sharpe\": -1.2499936117839143, \"sterling\": -1.9031920734684398, \"ulcer\": -0.15465968506188388}], \"train_objective\": [{\"annualised_returns\": 1.245517662501919, \"annualised_returns_over_hodl\": 0.8570111037147885, \"annualised_returns_over_uniform_hodl\": 0.8570111037147876, \"calmar\": 2.0492510223745715, \"daily_log_sharpe\": 0.9273796879351481, \"daily_returns\": 0.022195461975538892, \"fee_revenue_over_value\": 0.5215474614917488, \"jax_sharpe\": 1.3547570523249235, \"return\": 0.6341493486059258, \"returns_over_hodl\": 0.4561396641993767, \"returns_over_uniform_hodl\": 0.45613966419937624, \"sharpe\": 1.3676102781921524, \"sterling\": 4.838967205877994, \"ulcer\": -0.12649877198385043}], \"train_return\": 0.6341493486059258, \"train_returns_over_hodl\": 0.4561396641993767, \"train_sharpe\": 1.3547570523249233, \"validation_return\": 0.029545029644538534, \"validation_returns_over_hodl\": 0.08120741173869828, \"validation_sharpe\": 0.6217281423349854}, {\"centeredness_margin\": 0.014196133871047505, \"continuous_test_metrics\": [{\"annualised_returns\": -0.8581018037378716, \"annualised_returns_over_hodl\": 0.34178247429246844, \"annualised_returns_over_uniform_hodl\": 0.24645484024869546, \"calmar\": -1.218260938759103, \"daily_log_sharpe\": -2.1615442722415894, \"daily_returns\": 0.007785559867327079, \"fee_revenue_over_value\": 0.06469041182255034, \"jax_sharpe\": -0.6330569820119951, \"return\": -0.5445203141965491, \"returns_over_hodl\": 0.12569935461863646, \"returns_over_uniform_hodl\": 0.0927795643881848, \"sharpe\": -1.7387852075237777, \"sterling\": -2.0623383921702207, \"ulcer\": -0.16811694389011866}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": 0.7062146057800105, \"optuna_trial_number\": 273, \"price_ratio\": 2.83267151633267, \"shift_exponent\": 0.063463014649346, \"step\": 273, \"test_objective\": [{\"annualised_returns\": -0.8581018037378716, \"annualised_returns_over_hodl\": 0.34178247429246844, \"annualised_returns_over_uniform_hodl\": 0.24645484024869546, \"calmar\": -1.218260938759103, \"daily_log_sharpe\": -2.1615442722415894, \"daily_returns\": 0.007785559867327079, \"fee_revenue_over_value\": 0.06469041182255034, \"jax_sharpe\": -0.6330569820119951, \"return\": -0.5445203141965491, \"returns_over_hodl\": 0.12569935461863646, \"returns_over_uniform_hodl\": 0.0927795643881848, \"sharpe\": -1.7387852075237777, \"sterling\": -2.0623383921702207, \"ulcer\": -0.16811694389011866}], \"train_objective\": [{\"annualised_returns\": 0.5379883430824772, \"annualised_returns_over_hodl\": 0.27189444028059495, \"annualised_returns_over_uniform_hodl\": 0.27189444028059495, \"calmar\": 0.8504828052178449, \"daily_log_sharpe\": 0.5252900595696782, \"daily_returns\": 0.020595648233364513, \"fee_revenue_over_value\": 0.11440341241008678, \"jax_sharpe\": 0.9299255015082591, \"return\": 0.29868310565749545, \"returns_over_hodl\": 0.15721612775891858, \"returns_over_uniform_hodl\": 0.15721612775891858, \"sharpe\": 0.950486036616965, \"sterling\": 2.0721783134570155, \"ulcer\": -0.1310604496381215}], \"train_return\": 0.29868310565749545, \"train_returns_over_hodl\": 0.15721612775891858, \"train_sharpe\": 0.9299255015082589, \"validation_return\": -0.013970933976889643, \"validation_returns_over_hodl\": 0.014195272214715837, \"validation_sharpe\": 0.19136444808028025}, {\"centeredness_margin\": 0.09685841489995516, \"continuous_test_metrics\": [{\"annualised_returns\": -0.7151309129380183, \"annualised_returns_over_hodl\": 1.7435733184473046, \"annualised_returns_over_uniform_hodl\": 1.5023323887056463, \"calmar\": -1.0096405455453794, \"daily_log_sharpe\": -1.5726872217370351, \"daily_returns\": 0.011117975816947278, \"fee_revenue_over_value\": 0.33719995284830423, \"jax_sharpe\": -0.13197525962123832, \"return\": -0.3969337216761816, \"returns_over_hodl\": 0.5015045813319419, \"returns_over_uniform_hodl\": 0.4468669524995825, \"sharpe\": -1.1737603423563494, \"sterling\": -1.8593586311850816, \"ulcer\": -0.14614241988961962}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": 1.0216610713172232, \"optuna_trial_number\": 274, \"price_ratio\": 1.0217173690576027, \"shift_exponent\": 0.09017171888904547, \"step\": 274, \"test_objective\": [{\"annualised_returns\": -0.7151309129380183, \"annualised_returns_over_hodl\": 1.7435733184473046, \"annualised_returns_over_uniform_hodl\": 1.5023323887056463, \"calmar\": -1.0096405455453794, \"daily_log_sharpe\": -1.5726872217370351, \"daily_returns\": 0.011117975816947278, \"fee_revenue_over_value\": 0.33719995284830423, \"jax_sharpe\": -0.13197525962123832, \"return\": -0.3969337216761816, \"returns_over_hodl\": 0.5015045813319419, \"returns_over_uniform_hodl\": 0.4468669524995825, \"sharpe\": -1.1737603423563494, \"sterling\": -1.8593586311850816, \"ulcer\": -0.14614241988961962}], \"train_objective\": [{\"annualised_returns\": 0.8382912030579386, \"annualised_returns_over_hodl\": 0.5202406255563765, \"annualised_returns_over_uniform_hodl\": 0.5202406255563761, \"calmar\": 1.2034746179268438, \"daily_log_sharpe\": 0.6918838782229134, \"daily_returns\": 0.02489669299543658, \"fee_revenue_over_value\": 0.8057666124390293, \"jax_sharpe\": 1.12896399441147, \"return\": 0.44721008899205494, \"returns_over_hodl\": 0.2895639035738007, \"returns_over_uniform_hodl\": 0.28956390357380046, \"sharpe\": 1.1293290970916832, \"sterling\": 3.0581976679554916, \"ulcer\": -0.14253531903663522}], \"train_return\": 0.44721008899205494, \"train_returns_over_hodl\": 0.2895639035738007, \"train_sharpe\": 1.12896399441147, \"validation_return\": 0.07953744605350876, \"validation_returns_over_hodl\": 0.10415149662236467, \"validation_sharpe\": 1.1043841179146576}, {\"centeredness_margin\": 0.07617018209243236, \"continuous_test_metrics\": [{\"annualised_returns\": -0.7103701435531619, \"annualised_returns_over_hodl\": 1.8132529119971217, \"annualised_returns_over_uniform_hodl\": 1.544151694372506, \"calmar\": -1.0018503621820196, \"daily_log_sharpe\": -1.530342687763797, \"daily_returns\": 0.010623992926989127, \"fee_revenue_over_value\": 0.30319189231590815, \"jax_sharpe\": -0.1924085601966678, \"return\": -0.39289479869802224, \"returns_over_hodl\": 0.5167477701988692, \"returns_over_uniform_hodl\": 0.45655707179630767, \"sharpe\": -1.1245766463609204, \"sterling\": -1.8477988810297101, \"ulcer\": -0.14488656199041341}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": 1.0230330474945868, \"optuna_trial_number\": 275, \"price_ratio\": 1.041687246611848, \"shift_exponent\": 0.05818091741632876, \"step\": 275, \"test_objective\": [{\"annualised_returns\": -0.7103701435531619, \"annualised_returns_over_hodl\": 1.8132529119971217, \"annualised_returns_over_uniform_hodl\": 1.544151694372506, \"calmar\": -1.0018503621820196, \"daily_log_sharpe\": -1.530342687763797, \"daily_returns\": 0.010623992926989127, \"fee_revenue_over_value\": 0.30319189231590815, \"jax_sharpe\": -0.1924085601966678, \"return\": -0.39289479869802224, \"returns_over_hodl\": 0.5167477701988692, \"returns_over_uniform_hodl\": 0.45655707179630767, \"sharpe\": -1.1245766463609204, \"sterling\": -1.8477988810297101, \"ulcer\": -0.14488656199041341}], \"train_objective\": [{\"annualised_returns\": 1.0316260556479557, \"annualised_returns_over_hodl\": 0.6801257932351286, \"annualised_returns_over_uniform_hodl\": 0.6801257932351299, \"calmar\": 1.558425306709509, \"daily_log_sharpe\": 0.8122429086458667, \"daily_returns\": 0.02190111109949396, \"fee_revenue_over_value\": 0.6991905476273449, \"jax_sharpe\": 1.240446753348696, \"return\": 0.537795332760592, \"returns_over_hodl\": 0.37028159718917375, \"returns_over_uniform_hodl\": 0.3702815971891744, \"sharpe\": 1.2488349077511127, \"sterling\": 3.8530935773572477, \"ulcer\": -0.13518939233568536}], \"train_return\": 0.537795332760592, \"train_returns_over_hodl\": 0.37028159718917375, \"train_sharpe\": 1.240446753348696, \"validation_return\": 0.07102173722292893, \"validation_returns_over_hodl\": 0.10752140318794168, \"validation_sharpe\": 1.021442885742926}, {\"centeredness_margin\": 0.35452702244846745, \"continuous_test_metrics\": [{\"annualised_returns\": -0.874171844672811, \"annualised_returns_over_hodl\": 0.11457806695115536, \"annualised_returns_over_uniform_hodl\": 0.1052932128708024, \"calmar\": -1.3359000059526045, \"daily_log_sharpe\": -2.560868227546625, \"daily_returns\": 0.0066839721761230535, \"fee_revenue_over_value\": 0.02243669591563118, \"jax_sharpe\": -1.2832073454756519, \"return\": -0.566043215482047, \"returns_over_hodl\": 0.0446556975644814, \"returns_over_uniform_hodl\": 0.04114216446847618, \"sharpe\": -2.183428300960605, \"sterling\": -2.3881188839314755, \"ulcer\": -0.1573896237741079}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": 0.5698480676725654, \"optuna_trial_number\": 276, \"price_ratio\": 187.7314124667509, \"shift_exponent\": 0.003614090213555413, \"step\": 276, \"test_objective\": [{\"annualised_returns\": -0.874171844672811, \"annualised_returns_over_hodl\": 0.11457806695115536, \"annualised_returns_over_uniform_hodl\": 0.1052932128708024, \"calmar\": -1.3359000059526045, \"daily_log_sharpe\": -2.560868227546625, \"daily_returns\": 0.0066839721761230535, \"fee_revenue_over_value\": 0.02243669591563118, \"jax_sharpe\": -1.2832073454756519, \"return\": -0.566043215482047, \"returns_over_hodl\": 0.0446556975644814, \"returns_over_uniform_hodl\": 0.04114216446847618, \"sharpe\": -2.183428300960605, \"sterling\": -2.3881188839314755, \"ulcer\": -0.1573896237741079}], \"train_objective\": [{\"annualised_returns\": 0.30794422969806834, \"annualised_returns_over_hodl\": 0.08165123710618905, \"annualised_returns_over_uniform_hodl\": 0.0816512371061886, \"calmar\": 0.4786915519572555, \"daily_log_sharpe\": 0.33902284174516784, \"daily_returns\": 0.02041801946312825, \"fee_revenue_over_value\": 0.03726451668173888, \"jax_sharpe\": 0.7447979798921464, \"return\": 0.1770199187969621, \"returns_over_hodl\": 0.048805845545940585, \"returns_over_uniform_hodl\": 0.04880584554594036, \"sharpe\": 0.7635220020849997, \"sterling\": 1.173796986777431, \"ulcer\": -0.13426909079792615}], \"train_return\": 0.1770199187969621, \"train_returns_over_hodl\": 0.048805845545940585, \"train_sharpe\": 0.7447979798921464, \"validation_return\": -0.000450322580136886, \"validation_returns_over_hodl\": 0.004560131620803487, \"validation_sharpe\": 0.3087889850964625}, {\"centeredness_margin\": 0.04194339265387449, \"continuous_test_metrics\": [{\"annualised_returns\": -0.8590512041103792, \"annualised_returns_over_hodl\": 0.36005288201559194, \"annualised_returns_over_uniform_hodl\": 0.2381151663076695, \"calmar\": -1.2111958831668708, \"daily_log_sharpe\": -2.173053911611274, \"daily_returns\": 0.008116113280038932, \"fee_revenue_over_value\": 0.07253565537470458, \"jax_sharpe\": -0.6473769963418959, \"return\": -0.5457501126172741, \"returns_over_hodl\": 0.1318476414955101, \"returns_over_uniform_hodl\": 0.08982905172127098, \"sharpe\": -1.7518385993510146, \"sterling\": -2.0668141313360437, \"ulcer\": -0.16923017842405388}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": 0.7453042877591218, \"optuna_trial_number\": 277, \"price_ratio\": 2.2889272872658273, \"shift_exponent\": 0.06642657746991074, \"step\": 277, \"test_objective\": [{\"annualised_returns\": -0.8590512041103792, \"annualised_returns_over_hodl\": 0.36005288201559194, \"annualised_returns_over_uniform_hodl\": 0.2381151663076695, \"calmar\": -1.2111958831668708, \"daily_log_sharpe\": -2.173053911611274, \"daily_returns\": 0.008116113280038932, \"fee_revenue_over_value\": 0.07253565537470458, \"jax_sharpe\": -0.6473769963418959, \"return\": -0.5457501126172741, \"returns_over_hodl\": 0.1318476414955101, \"returns_over_uniform_hodl\": 0.08982905172127098, \"sharpe\": -1.7518385993510146, \"sterling\": -2.0668141313360437, \"ulcer\": -0.16923017842405388}], \"train_objective\": [{\"annualised_returns\": 0.6131138409000214, \"annualised_returns_over_hodl\": 0.33402215628521725, \"annualised_returns_over_uniform_hodl\": 0.33402215628521637, \"calmar\": 0.9744558042346821, \"daily_log_sharpe\": 0.5799249491284016, \"daily_returns\": 0.02065756514866123, \"fee_revenue_over_value\": 0.13855149689503093, \"jax_sharpe\": 0.984663602724923, \"return\": 0.336835202215094, \"returns_over_hodl\": 0.19121227451091372, \"returns_over_uniform_hodl\": 0.19121227451091327, \"sharpe\": 1.0054838929611707, \"sterling\": 2.368582529416205, \"ulcer\": -0.13011227579234916}], \"train_return\": 0.336835202215094, \"train_returns_over_hodl\": 0.19121227451091372, \"train_sharpe\": 0.9846636027249229, \"validation_return\": -0.01815109490614808, \"validation_returns_over_hodl\": 0.016926745637179774, \"validation_sharpe\": 0.15905381398375668}, {\"centeredness_margin\": 0.014184365267540147, \"continuous_test_metrics\": [{\"annualised_returns\": -0.6758235149340812, \"annualised_returns_over_hodl\": 2.1925022250036443, \"annualised_returns_over_uniform_hodl\": 1.8476144133557768, \"calmar\": -0.9526159678402146, \"daily_log_sharpe\": -1.3969489830639292, \"daily_returns\": 0.01127155899170449, \"fee_revenue_over_value\": 0.3417368759209175, \"jax_sharpe\": -0.11107555158392246, \"return\": -0.36470825846842403, \"returns_over_hodl\": 0.5959993336304865, \"returns_over_uniform_hodl\": 0.5241817675044758, \"sharpe\": -0.9873361763181581, \"sterling\": -1.7645281230561323, \"ulcer\": -0.14290797375257744}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": 1.070530743730845, \"optuna_trial_number\": 278, \"price_ratio\": 1.028090197581073, \"shift_exponent\": 0.0603931622106948, \"step\": 278, \"test_objective\": [{\"annualised_returns\": -0.6758235149340812, \"annualised_returns_over_hodl\": 2.1925022250036443, \"annualised_returns_over_uniform_hodl\": 1.8476144133557768, \"calmar\": -0.9526159678402146, \"daily_log_sharpe\": -1.3969489830639292, \"daily_returns\": 0.01127155899170449, \"fee_revenue_over_value\": 0.3417368759209175, \"jax_sharpe\": -0.11107555158392246, \"return\": -0.36470825846842403, \"returns_over_hodl\": 0.5959993336304865, \"returns_over_uniform_hodl\": 0.5241817675044758, \"sharpe\": -0.9873361763181581, \"sterling\": -1.7645281230561323, \"ulcer\": -0.14290797375257744}], \"train_objective\": [{\"annualised_returns\": 1.143042134592656, \"annualised_returns_over_hodl\": 0.7722653026176367, \"annualised_returns_over_uniform_hodl\": 0.7722653026176327, \"calmar\": 1.7289086741623847, \"daily_log_sharpe\": 0.8670550287738628, \"daily_returns\": 0.02214911715132222, \"fee_revenue_over_value\": 0.8066662933690727, \"jax_sharpe\": 1.2980007468628754, \"return\": 0.5884583175256655, \"returns_over_hodl\": 0.4154258073472543, \"returns_over_uniform_hodl\": 0.4154258073472523, \"sharpe\": 1.3058080888207426, \"sterling\": 4.23389179300466, \"ulcer\": -0.1358371845566481}], \"train_return\": 0.5884583175256655, \"train_returns_over_hodl\": 0.4154258073472543, \"train_sharpe\": 1.2980007468628754, \"validation_return\": 0.07982216461445657, \"validation_returns_over_hodl\": 0.11755713702448678, \"validation_sharpe\": 1.1016074988561513}, {\"centeredness_margin\": 0.8608755331986979, \"continuous_test_metrics\": [{\"annualised_returns\": -0.8878140152538871, \"annualised_returns_over_hodl\": 0.03391567434576159, \"annualised_returns_over_uniform_hodl\": -0.014541640567846348, \"calmar\": -1.320072051335004, \"daily_log_sharpe\": -2.513754345379172, \"daily_returns\": 0.007039446394465328, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": -1.3411874566808037, \"return\": -0.5856433230919177, \"returns_over_hodl\": 0.013523225914627401, \"returns_over_uniform_hodl\": -0.0058820996720736485, \"sharpe\": -2.11305609209761, \"sterling\": -2.2777913971557484, \"ulcer\": -0.16753049797364442}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -0.4594534912197378, \"optuna_trial_number\": 279, \"price_ratio\": 4.70602591998065, \"shift_exponent\": 0.00019947251250422023, \"step\": 279, \"test_objective\": [{\"annualised_returns\": -0.8878140152538871, \"annualised_returns_over_hodl\": 0.03391567434576159, \"annualised_returns_over_uniform_hodl\": -0.014541640567846348, \"calmar\": -1.320072051335004, \"daily_log_sharpe\": -2.513754345379172, \"daily_returns\": 0.007039446394465328, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": -1.3411874566808037, \"return\": -0.5856433230919177, \"returns_over_hodl\": 0.013523225914627401, \"returns_over_uniform_hodl\": -0.0058820996720736485, \"sharpe\": -2.11305609209761, \"sterling\": -2.2777913971557484, \"ulcer\": -0.16753049797364442}], \"train_objective\": [{\"annualised_returns\": 0.2904617046611473, \"annualised_returns_over_hodl\": 0.06719343806204225, \"annualised_returns_over_uniform_hodl\": 0.06719343806204225, \"calmar\": 0.45391497432786315, \"daily_log_sharpe\": 0.3255604306799091, \"daily_returns\": 0.02052146486464206, \"fee_revenue_over_value\": 0.022585432243349253, \"jax_sharpe\": 0.729020471958493, \"return\": 0.16744312318486654, \"returns_over_hodl\": 0.04027226080437485, \"returns_over_uniform_hodl\": 0.04027226080437485, \"sharpe\": 0.750136391666205, \"sterling\": 1.109184575007424, \"ulcer\": -0.13359426046046816}], \"train_return\": 0.16744312318486654, \"train_returns_over_hodl\": 0.04027226080437485, \"train_sharpe\": 0.7290204719584928, \"validation_return\": -0.020649065346716178, \"validation_returns_over_hodl\": -0.0013903983360772365, \"validation_sharpe\": 0.11148016536716197}, {\"centeredness_margin\": 0.03447759467135073, \"continuous_test_metrics\": [{\"annualised_returns\": -0.7245472721246919, \"annualised_returns_over_hodl\": 1.7399488829544572, \"annualised_returns_over_uniform_hodl\": 1.4196176904577005, \"calmar\": -1.0199408314927696, \"daily_log_sharpe\": -1.5356097225512197, \"daily_returns\": 0.010177997611478565, \"fee_revenue_over_value\": 0.258223167787694, \"jax_sharpe\": -0.18603606749010862, \"return\": -0.40504272571190736, \"returns_over_hodl\": 0.500705401887612, \"returns_over_uniform_hodl\": 0.4274119599412396, \"sharpe\": -1.1148873363627976, \"sterling\": -1.8437599366538084, \"ulcer\": -0.15087083614120733}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": 0.9444303551677647, \"optuna_trial_number\": 280, \"price_ratio\": 1.0780977894478352, \"shift_exponent\": 0.016367180287627157, \"step\": 280, \"test_objective\": [{\"annualised_returns\": -0.7245472721246919, \"annualised_returns_over_hodl\": 1.7399488829544572, \"annualised_returns_over_uniform_hodl\": 1.4196176904577005, \"calmar\": -1.0199408314927696, \"daily_log_sharpe\": -1.5356097225512197, \"daily_returns\": 0.010177997611478565, \"fee_revenue_over_value\": 0.258223167787694, \"jax_sharpe\": -0.18603606749010862, \"return\": -0.40504272571190736, \"returns_over_hodl\": 0.500705401887612, \"returns_over_uniform_hodl\": 0.4274119599412396, \"sharpe\": -1.1148873363627976, \"sterling\": -1.8437599366538084, \"ulcer\": -0.15087083614120733}], \"train_objective\": [{\"annualised_returns\": 1.1704118482553851, \"annualised_returns_over_hodl\": 0.7948996657428613, \"annualised_returns_over_uniform_hodl\": 0.7948996657428613, \"calmar\": 1.972951539144609, \"daily_log_sharpe\": 0.9055073827368747, \"daily_returns\": 0.022555904756630784, \"fee_revenue_over_value\": 0.4600269059589606, \"jax_sharpe\": 1.3141180158141799, \"return\": 0.6007442010547253, \"returns_over_hodl\": 0.42637337608181114, \"returns_over_uniform_hodl\": 0.42637337608181114, \"sharpe\": 1.3485502881540534, \"sterling\": 4.520556316917173, \"ulcer\": -0.12491321058048219}], \"train_return\": 0.6007442010547253, \"train_returns_over_hodl\": 0.42637337608181114, \"train_sharpe\": 1.3141180158141799, \"validation_return\": 0.030398337612522086, \"validation_returns_over_hodl\": 0.10784015554229098, \"validation_sharpe\": 0.6298259901850043}, {\"centeredness_margin\": 0.01363002756802852, \"continuous_test_metrics\": [{\"annualised_returns\": -0.8492699351011715, \"annualised_returns_over_hodl\": 0.4937324844399167, \"annualised_returns_over_uniform_hodl\": 0.3240352866577503, \"calmar\": -1.1937656442759141, \"daily_log_sharpe\": -2.0746864582224727, \"daily_returns\": 0.008630919920285283, \"fee_revenue_over_value\": 0.0924541547010114, \"jax_sharpe\": -0.5925263686479079, \"return\": -0.5333083712344919, \"returns_over_hodl\": 0.17540154851959922, \"returns_over_uniform_hodl\": 0.11967907830264135, \"sharpe\": -1.646297758275867, \"sterling\": -2.0359797829369706, \"ulcer\": -0.16986571181870858}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": 0.8283753740999131, \"optuna_trial_number\": 281, \"price_ratio\": 1.7448768289018821, \"shift_exponent\": 0.03979795895014019, \"step\": 281, \"test_objective\": [{\"annualised_returns\": -0.8492699351011715, \"annualised_returns_over_hodl\": 0.4937324844399167, \"annualised_returns_over_uniform_hodl\": 0.3240352866577503, \"calmar\": -1.1937656442759141, \"daily_log_sharpe\": -2.0746864582224727, \"daily_returns\": 0.008630919920285283, \"fee_revenue_over_value\": 0.0924541547010114, \"jax_sharpe\": -0.5925263686479079, \"return\": -0.5333083712344919, \"returns_over_hodl\": 0.17540154851959922, \"returns_over_uniform_hodl\": 0.11967907830264135, \"sharpe\": -1.646297758275867, \"sterling\": -2.0359797829369706, \"ulcer\": -0.16986571181870858}], \"train_objective\": [{\"annualised_returns\": 0.7882561063203295, \"annualised_returns_over_hodl\": 0.4788623136558321, \"annualised_returns_over_uniform_hodl\": 0.4788623136558321, \"calmar\": 1.2683831374518513, \"daily_log_sharpe\": 0.6975794245391657, \"daily_returns\": 0.020802121598713086, \"fee_revenue_over_value\": 0.19394550721238898, \"jax_sharpe\": 1.1030898406103198, \"return\": 0.42316573621723297, \"returns_over_hodl\": 0.26813872857049414, \"returns_over_uniform_hodl\": 0.26813872857049414, \"sharpe\": 1.1242143489723035, \"sterling\": 3.065873471613021, \"ulcer\": -0.12798570712832755}], \"train_return\": 0.42316573621723297, \"train_returns_over_hodl\": 0.26813872857049414, \"train_sharpe\": 1.1030898406103198, \"validation_return\": -0.02763004957334103, \"validation_returns_over_hodl\": 0.023554362502263393, \"validation_sharpe\": 0.08963790275815718}, {\"centeredness_margin\": 0.02439889970993435, \"continuous_test_metrics\": [{\"annualised_returns\": -0.7773015016301439, \"annualised_returns_over_hodl\": 1.1971385502039653, \"annualised_returns_over_uniform_hodl\": 0.9562166998687114, \"calmar\": -1.0938420025412134, \"daily_log_sharpe\": -1.7504332898589157, \"daily_returns\": 0.009530338367927542, \"fee_revenue_over_value\": 0.19091521658028973, \"jax_sharpe\": -0.35392172185687654, \"return\": -0.4538636536617112, \"returns_over_hodl\": 0.37302651134023335, \"returns_over_uniform_hodl\": 0.3102815718232601, \"sharpe\": -1.331666660573762, \"sterling\": -1.9387718355001864, \"ulcer\": -0.15770686658741023}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": 0.9243798072555864, \"optuna_trial_number\": 282, \"price_ratio\": 1.1618429324615593, \"shift_exponent\": 0.06106837065919944, \"step\": 282, \"test_objective\": [{\"annualised_returns\": -0.7773015016301439, \"annualised_returns_over_hodl\": 1.1971385502039653, \"annualised_returns_over_uniform_hodl\": 0.9562166998687114, \"calmar\": -1.0938420025412134, \"daily_log_sharpe\": -1.7504332898589157, \"daily_returns\": 0.009530338367927542, \"fee_revenue_over_value\": 0.19091521658028973, \"jax_sharpe\": -0.35392172185687654, \"return\": -0.4538636536617112, \"returns_over_hodl\": 0.37302651134023335, \"returns_over_uniform_hodl\": 0.3102815718232601, \"sharpe\": -1.331666660573762, \"sterling\": -1.9387718355001864, \"ulcer\": -0.15770686658741023}], \"train_objective\": [{\"annualised_returns\": 1.0609975493754114, \"annualised_returns_over_hodl\": 0.7044155999444679, \"annualised_returns_over_uniform_hodl\": 0.704415599944467, \"calmar\": 1.7063185141588977, \"daily_log_sharpe\": 0.830512723864005, \"daily_returns\": 0.02164018508100305, \"fee_revenue_over_value\": 0.4432678211150707, \"jax_sharpe\": 1.2582221690881634, \"return\": 0.5512548483812498, \"returns_over_hodl\": 0.38227495298182035, \"returns_over_uniform_hodl\": 0.3822749529818199, \"sharpe\": 1.2717125900138864, \"sterling\": 4.104512980919972, \"ulcer\": -0.12775140030433504}], \"train_return\": 0.5512548483812498, \"train_returns_over_hodl\": 0.38227495298182035, \"train_sharpe\": 1.2582221690881632, \"validation_return\": 0.012862772354835794, \"validation_returns_over_hodl\": 0.06904145972851028, \"validation_sharpe\": 0.46345743785839966}, {\"centeredness_margin\": 0.012821468523778155, \"continuous_test_metrics\": [{\"annualised_returns\": -0.7909995949210821, \"annualised_returns_over_hodl\": 1.0861113112729495, \"annualised_returns_over_uniform_hodl\": 0.8358906130372254, \"calmar\": -1.1122211502741755, \"daily_log_sharpe\": -1.7747059490865795, \"daily_returns\": 0.009203636050552133, \"fee_revenue_over_value\": 0.1692778951976745, \"jax_sharpe\": -0.3820052815552861, \"return\": -0.46764966216534953, \"returns_over_hodl\": 0.34465007615839127, \"returns_over_uniform_hodl\": 0.27720640110366257, \"sharpe\": -1.3470917271933336, \"sterling\": -1.9439415417795731, \"ulcer\": -0.16190966131102102}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": 0.9237157384196437, \"optuna_trial_number\": 283, \"price_ratio\": 1.1944753095941558, \"shift_exponent\": 0.014670116974812885, \"step\": 283, \"test_objective\": [{\"annualised_returns\": -0.7909995949210821, \"annualised_returns_over_hodl\": 1.0861113112729495, \"annualised_returns_over_uniform_hodl\": 0.8358906130372254, \"calmar\": -1.1122211502741755, \"daily_log_sharpe\": -1.7747059490865795, \"daily_returns\": 0.009203636050552133, \"fee_revenue_over_value\": 0.1692778951976745, \"jax_sharpe\": -0.3820052815552861, \"return\": -0.46764966216534953, \"returns_over_hodl\": 0.34465007615839127, \"returns_over_uniform_hodl\": 0.27720640110366257, \"sharpe\": -1.3470917271933336, \"sterling\": -1.9439415417795731, \"ulcer\": -0.16190966131102102}], \"train_objective\": [{\"annualised_returns\": 1.2309853471237835, \"annualised_returns_over_hodl\": 0.8449930860119952, \"annualised_returns_over_uniform_hodl\": 0.8449930860119947, \"calmar\": 2.0783033512902933, \"daily_log_sharpe\": 0.921482932255384, \"daily_returns\": 0.02157591272930579, \"fee_revenue_over_value\": 0.3708897710943594, \"jax_sharpe\": 1.3481271100597718, \"return\": 0.6277204134671988, \"returns_over_hodl\": 0.4504110400304464, \"returns_over_uniform_hodl\": 0.4504110400304462, \"sharpe\": 1.363790846129853, \"sterling\": 4.828408820181986, \"ulcer\": -0.12229066193581106}], \"train_return\": 0.6277204134671988, \"train_returns_over_hodl\": 0.4504110400304464, \"train_sharpe\": 1.3481271100597718, \"validation_return\": -0.006948317916219859, \"validation_returns_over_hodl\": 0.06292753644481941, \"validation_sharpe\": 0.27895839237290315}, {\"centeredness_margin\": 0.017877496753863728, \"continuous_test_metrics\": [{\"annualised_returns\": -0.8183355583439965, \"annualised_returns_over_hodl\": 0.8132608229974403, \"annualised_returns_over_uniform_hodl\": 0.5957674485509772, \"calmar\": -1.1490672999868496, \"daily_log_sharpe\": -2.018414451265899, \"daily_returns\": 0.007841752010022152, \"fee_revenue_over_value\": 0.12464127257830462, \"jax_sharpe\": -0.45555432213319325, \"return\": -0.4968702669856101, \"returns_over_hodl\": 0.2708424991587741, \"returns_over_uniform_hodl\": 0.2071007941975782, \"sharpe\": -1.571102353489132, \"sterling\": -1.9539007270333302, \"ulcer\": -0.17174468774030374}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -0.01210078238901613, \"optuna_trial_number\": 284, \"price_ratio\": 1.0148305725523716, \"shift_exponent\": 0.0052377475479891975, \"step\": 284, \"test_objective\": [{\"annualised_returns\": -0.8183355583439965, \"annualised_returns_over_hodl\": 0.8132608229974403, \"annualised_returns_over_uniform_hodl\": 0.5957674485509772, \"calmar\": -1.1490672999868496, \"daily_log_sharpe\": -2.018414451265899, \"daily_returns\": 0.007841752010022152, \"fee_revenue_over_value\": 0.12464127257830462, \"jax_sharpe\": -0.45555432213319325, \"return\": -0.4968702669856101, \"returns_over_hodl\": 0.2708424991587741, \"returns_over_uniform_hodl\": 0.2071007941975782, \"sharpe\": -1.571102353489132, \"sterling\": -1.9539007270333302, \"ulcer\": -0.17174468774030374}], \"train_objective\": [{\"annualised_returns\": 0.5361043660069937, \"annualised_returns_over_hodl\": 0.27033641808965947, \"annualised_returns_over_uniform_hodl\": 0.270336418089659, \"calmar\": 0.8960339249256031, \"daily_log_sharpe\": 0.507597355535146, \"daily_returns\": 0.019898166312071558, \"fee_revenue_over_value\": 0.24289819429971926, \"jax_sharpe\": 0.9268549618859157, \"return\": 0.2977170417809474, \"returns_over_hodl\": 0.1563552982821077, \"returns_over_uniform_hodl\": 0.15635529828210748, \"sharpe\": 0.9548796136931309, \"sterling\": 2.081828130678962, \"ulcer\": -0.12392487828463876}], \"train_return\": 0.2977170417809474, \"train_returns_over_hodl\": 0.1563552982821077, \"train_sharpe\": 0.9268549618859155, \"validation_return\": -0.06990342222596657, \"validation_returns_over_hodl\": -3.489996069916401e-10, \"validation_sharpe\": -0.28596766157801523}, {\"centeredness_margin\": 0.19661834497870978, \"continuous_test_metrics\": [{\"annualised_returns\": -0.8505911606878948, \"annualised_returns_over_hodl\": 0.41298205178494496, \"annualised_returns_over_uniform_hodl\": 0.3124294447865159, \"calmar\": -1.2701853914943013, \"daily_log_sharpe\": -2.210477019507626, \"daily_returns\": 0.00798895326119585, \"fee_revenue_over_value\": 0.08078778246509545, \"jax_sharpe\": -0.8228673512586956, \"return\": -0.5349602196378591, \"returns_over_hodl\": 0.14938547812752367, \"returns_over_uniform_hodl\": 0.11571599007954547, \"sharpe\": -1.8056497187665603, \"sterling\": -2.138846573001962, \"ulcer\": -0.16312863204530575}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": 0.7881059195627858, \"optuna_trial_number\": 285, \"price_ratio\": 1.9344298689963986, \"shift_exponent\": 0.02883732266751891, \"step\": 285, \"test_objective\": [{\"annualised_returns\": -0.8505911606878948, \"annualised_returns_over_hodl\": 0.41298205178494496, \"annualised_returns_over_uniform_hodl\": 0.3124294447865159, \"calmar\": -1.2701853914943013, \"daily_log_sharpe\": -2.210477019507626, \"daily_returns\": 0.00798895326119585, \"fee_revenue_over_value\": 0.08078778246509545, \"jax_sharpe\": -0.8228673512586956, \"return\": -0.5349602196378591, \"returns_over_hodl\": 0.14938547812752367, \"returns_over_uniform_hodl\": 0.11571599007954547, \"sharpe\": -1.8056497187665603, \"sterling\": -2.138846573001962, \"ulcer\": -0.16312863204530575}], \"train_objective\": [{\"annualised_returns\": 0.7017885045265626, \"annualised_returns_over_hodl\": 0.4073548393108264, \"annualised_returns_over_uniform_hodl\": 0.4073548393108264, \"calmar\": 1.122717859451611, \"daily_log_sharpe\": 0.6410377655830035, \"daily_returns\": 0.020727151354781544, \"fee_revenue_over_value\": 0.16699708554934942, \"jax_sharpe\": 1.0461690855410346, \"return\": 0.3809810494864079, \"returns_over_hodl\": 0.23054926612449322, \"returns_over_uniform_hodl\": 0.23054926612449322, \"sharpe\": 1.0671421777775572, \"sterling\": 2.720847660106081, \"ulcer\": -0.1289814578087824}], \"train_return\": 0.3809810494864079, \"train_returns_over_hodl\": 0.23054926612449322, \"train_sharpe\": 1.0461690855410344, \"validation_return\": -0.01460524889175463, \"validation_returns_over_hodl\": 0.024997322857133186, \"validation_sharpe\": 0.1880330914062758}, {\"centeredness_margin\": 0.07456158229640071, \"continuous_test_metrics\": [{\"annualised_returns\": -0.8682840649724768, \"annualised_returns_over_hodl\": 0.25070238528928157, \"annualised_returns_over_uniform_hodl\": 0.15701234460833668, \"calmar\": -1.3046143873657894, \"daily_log_sharpe\": -2.220973408244719, \"daily_returns\": 0.007857452176922639, \"fee_revenue_over_value\": 0.04340566277875953, \"jax_sharpe\": -1.0627958858639681, \"return\": -0.5579768116684964, \"returns_over_hodl\": 0.09427778251058161, \"returns_over_uniform_hodl\": 0.06049495125632576, \"sharpe\": -1.7952527421324689, \"sterling\": -2.1664522735975567, \"ulcer\": -0.1709962983657785}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": 0.714460094227354, \"optuna_trial_number\": 286, \"price_ratio\": 2.690880361659019, \"shift_exponent\": 0.0007501785633613936, \"step\": 286, \"test_objective\": [{\"annualised_returns\": -0.8682840649724768, \"annualised_returns_over_hodl\": 0.25070238528928157, \"annualised_returns_over_uniform_hodl\": 0.15701234460833668, \"calmar\": -1.3046143873657894, \"daily_log_sharpe\": -2.220973408244719, \"daily_returns\": 0.007857452176922639, \"fee_revenue_over_value\": 0.04340566277875953, \"jax_sharpe\": -1.0627958858639681, \"return\": -0.5579768116684964, \"returns_over_hodl\": 0.09427778251058161, \"returns_over_uniform_hodl\": 0.06049495125632576, \"sharpe\": -1.7952527421324689, \"sterling\": -2.1664522735975567, \"ulcer\": -0.1709962983657785}], \"train_objective\": [{\"annualised_returns\": 0.5535412094735674, \"annualised_returns_over_hodl\": 0.2847564391261832, \"annualised_returns_over_uniform_hodl\": 0.2847564391261832, \"calmar\": 0.8760324785649977, \"daily_log_sharpe\": 0.536818764739082, \"daily_returns\": 0.020617340396905895, \"fee_revenue_over_value\": 0.11931529652333157, \"jax_sharpe\": 0.9414671338151407, \"return\": 0.3066406043259178, \"returns_over_hodl\": 0.1643068073524261, \"returns_over_uniform_hodl\": 0.1643068073524261, \"sharpe\": 0.9620877107889796, \"sterling\": 2.1333334783158002, \"ulcer\": -0.13086716297972198}], \"train_return\": 0.3066406043259178, \"train_returns_over_hodl\": 0.1643068073524261, \"train_sharpe\": 0.9414671338151407, \"validation_return\": -0.014871599564373339, \"validation_returns_over_hodl\": 0.014610817020670241, \"validation_sharpe\": 0.18415297957551172}, {\"centeredness_margin\": 0.2555667782006595, \"continuous_test_metrics\": [{\"annualised_returns\": -0.855644235538531, \"annualised_returns_over_hodl\": 0.3480246403132863, \"annualised_returns_over_uniform_hodl\": 0.2680424844753406, \"calmar\": -1.321993442482748, \"daily_log_sharpe\": -2.2827220962244064, \"daily_returns\": 0.0077164402720138235, \"fee_revenue_over_value\": 0.06909024248804324, \"jax_sharpe\": -1.2077127420287332, \"return\": -0.541359574751689, \"returns_over_hodl\": 0.12780553592597443, \"returns_over_uniform_hodl\": 0.1003627598222605, \"sharpe\": -1.8845543938413483, \"sterling\": -2.277940332391175, \"ulcer\": -0.16195338071416693}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": 0.741802046145594, \"optuna_trial_number\": 287, \"price_ratio\": 2.320599154405687, \"shift_exponent\": 0.0383433701629016, \"step\": 287, \"test_objective\": [{\"annualised_returns\": -0.855644235538531, \"annualised_returns_over_hodl\": 0.3480246403132863, \"annualised_returns_over_uniform_hodl\": 0.2680424844753406, \"calmar\": -1.321993442482748, \"daily_log_sharpe\": -2.2827220962244064, \"daily_returns\": 0.0077164402720138235, \"fee_revenue_over_value\": 0.06909024248804324, \"jax_sharpe\": -1.2077127420287332, \"return\": -0.541359574751689, \"returns_over_hodl\": 0.12780553592597443, \"returns_over_uniform_hodl\": 0.1003627598222605, \"sharpe\": -1.8845543938413483, \"sterling\": -2.277940332391175, \"ulcer\": -0.16195338071416693}], \"train_objective\": [{\"annualised_returns\": 0.6062611974113945, \"annualised_returns_over_hodl\": 0.3283551177841699, \"annualised_returns_over_uniform_hodl\": 0.3283551177841699, \"calmar\": 0.9631123006705962, \"daily_log_sharpe\": 0.5750352210729105, \"daily_returns\": 0.020658834056695156, \"fee_revenue_over_value\": 0.13646205646868126, \"jax_sharpe\": 0.9797816099641957, \"return\": 0.3333844839404476, \"returns_over_hodl\": 0.18813744677012179, \"returns_over_uniform_hodl\": 0.18813744677012179, \"sharpe\": 1.000578980221675, \"sterling\": 2.341428869646065, \"ulcer\": -0.13019504743372318}], \"train_return\": 0.3333844839404476, \"train_returns_over_hodl\": 0.18813744677012179, \"train_sharpe\": 0.9797816099641957, \"validation_return\": -0.012578927439001575, \"validation_returns_over_hodl\": 0.020462710718238908, \"validation_sharpe\": 0.20275398119808963}, {\"centeredness_margin\": 0.9017054959542584, \"continuous_test_metrics\": [{\"annualised_returns\": -0.8611988154466259, \"annualised_returns_over_hodl\": 0.2233102952353372, \"annualised_returns_over_uniform_hodl\": 0.21925022922212167, \"calmar\": -1.336027784112821, \"daily_log_sharpe\": -2.473870745765522, \"daily_returns\": 0.007217948802038497, \"fee_revenue_over_value\": 0.06929464889962614, \"jax_sharpe\": -1.656987592298813, \"return\": -0.5485503788382873, \"returns_over_hodl\": 0.08456182073918028, \"returns_over_uniform_hodl\": 0.08311069787027159, \"sharpe\": -2.0984208490865557, \"sterling\": -2.521677303334609, \"ulcer\": -0.15493390951588457}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": 0.6214444530514736, \"optuna_trial_number\": 288, \"price_ratio\": 1.8680876157451163, \"shift_exponent\": 0.03974653355954037, \"step\": 288, \"test_objective\": [{\"annualised_returns\": -0.8611988154466259, \"annualised_returns_over_hodl\": 0.2233102952353372, \"annualised_returns_over_uniform_hodl\": 0.21925022922212167, \"calmar\": -1.336027784112821, \"daily_log_sharpe\": -2.473870745765522, \"daily_returns\": 0.007217948802038497, \"fee_revenue_over_value\": 0.06929464889962614, \"jax_sharpe\": -1.656987592298813, \"return\": -0.5485503788382873, \"returns_over_hodl\": 0.08456182073918028, \"returns_over_uniform_hodl\": 0.08311069787027159, \"sharpe\": -2.0984208490865557, \"sterling\": -2.521677303334609, \"ulcer\": -0.15493390951588457}], \"train_objective\": [{\"annualised_returns\": 0.3672929733670658, \"annualised_returns_over_hodl\": 0.13073180228065984, \"annualised_returns_over_uniform_hodl\": 0.1307318022806594, \"calmar\": 0.565131074338215, \"daily_log_sharpe\": 0.3849261860501088, \"daily_returns\": 0.02060272799749918, \"fee_revenue_over_value\": 0.11832698209754494, \"jax_sharpe\": 0.795132823421475, \"return\": 0.2091619485852978, \"returns_over_hodl\": 0.07744660870666431, \"returns_over_uniform_hodl\": 0.07744660870666409, \"sharpe\": 0.8091627049208868, \"sterling\": 1.3967931376053313, \"ulcer\": -0.13527485103133993}], \"train_return\": 0.2091619485852978, \"train_returns_over_hodl\": 0.07744660870666431, \"train_sharpe\": 0.7951328234214748, \"validation_return\": 0.013121154531326118, \"validation_returns_over_hodl\": 0.01489243448880706, \"validation_sharpe\": 0.4492753362221879}, {\"centeredness_margin\": 0.16793948651184343, \"continuous_test_metrics\": [{\"annualised_returns\": -0.8594310299421257, \"annualised_returns_over_hodl\": 0.3353653975078148, \"annualised_returns_over_uniform_hodl\": 0.23477871976427966, \"calmar\": -1.2185599502532065, \"daily_log_sharpe\": -2.250992312163474, \"daily_returns\": 0.007823529308866487, \"fee_revenue_over_value\": 0.06442297319513589, \"jax_sharpe\": -0.6358820620342783, \"return\": -0.5462435024911234, \"returns_over_hodl\": 0.12352804699538522, \"returns_over_uniform_hodl\": 0.08864531864112823, \"sharpe\": -1.843197031453002, \"sterling\": -2.0980290646649733, \"ulcer\": -0.16532454803107674}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": 0.7313801424847156, \"optuna_trial_number\": 289, \"price_ratio\": 2.446448666823727, \"shift_exponent\": 0.015584434707112148, \"step\": 289, \"test_objective\": [{\"annualised_returns\": -0.8594310299421257, \"annualised_returns_over_hodl\": 0.3353653975078148, \"annualised_returns_over_uniform_hodl\": 0.23477871976427966, \"calmar\": -1.2185599502532065, \"daily_log_sharpe\": -2.250992312163474, \"daily_returns\": 0.007823529308866487, \"fee_revenue_over_value\": 0.06442297319513589, \"jax_sharpe\": -0.6358820620342783, \"return\": -0.5462435024911234, \"returns_over_hodl\": 0.12352804699538522, \"returns_over_uniform_hodl\": 0.08864531864112823, \"sharpe\": -1.843197031453002, \"sterling\": -2.0980290646649733, \"ulcer\": -0.16532454803107674}], \"train_objective\": [{\"annualised_returns\": 0.5860111110995601, \"annualised_returns_over_hodl\": 0.3116085850090231, \"annualised_returns_over_uniform_hodl\": 0.3116085850090231, \"calmar\": 0.9296586631839732, \"daily_log_sharpe\": 0.5605370372588324, \"daily_returns\": 0.02064330754813741, \"fee_revenue_over_value\": 0.1298387080044648, \"jax_sharpe\": 0.9652081320343421, \"return\": 0.32315338773644986, \"returns_over_hodl\": 0.17902083511917932, \"returns_over_uniform_hodl\": 0.17902083511917932, \"sharpe\": 0.9859579681456743, \"sterling\": 2.261400831759799, \"ulcer\": -0.13044967103520996}], \"train_return\": 0.32315338773644986, \"train_returns_over_hodl\": 0.17902083511917932, \"train_sharpe\": 0.9652081320343421, \"validation_return\": -0.017189925521835292, \"validation_returns_over_hodl\": 0.015470734723119772, \"validation_sharpe\": 0.1644370223656416}, {\"centeredness_margin\": 0.20231914231723347, \"continuous_test_metrics\": [{\"annualised_returns\": -0.7423583505276209, \"annualised_returns_over_hodl\": 1.437965088597934, \"annualised_returns_over_uniform_hodl\": 1.2631625312647792, \"calmar\": -1.049335602987473, \"daily_log_sharpe\": -1.6831682732977564, \"daily_returns\": 0.010463113489728062, \"fee_revenue_over_value\": 0.30189572597715547, \"jax_sharpe\": -0.186711643715026, \"return\": -0.42084622114786685, \"returns_over_hodl\": 0.43176125519223496, \"returns_over_uniform_hodl\": 0.38949646689821793, \"sharpe\": -1.2855935438821147, \"sterling\": -1.9161365685298235, \"ulcer\": -0.14785468352934913}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": 0.9579609618669033, \"optuna_trial_number\": 290, \"price_ratio\": 1.0373144391574805, \"shift_exponent\": 0.05821669833424981, \"step\": 290, \"test_objective\": [{\"annualised_returns\": -0.7423583505276209, \"annualised_returns_over_hodl\": 1.437965088597934, \"annualised_returns_over_uniform_hodl\": 1.2631625312647792, \"calmar\": -1.049335602987473, \"daily_log_sharpe\": -1.6831682732977564, \"daily_returns\": 0.010463113489728062, \"fee_revenue_over_value\": 0.30189572597715547, \"jax_sharpe\": -0.186711643715026, \"return\": -0.42084622114786685, \"returns_over_hodl\": 0.43176125519223496, \"returns_over_uniform_hodl\": 0.38949646689821793, \"sharpe\": -1.2855935438821147, \"sterling\": -1.9161365685298235, \"ulcer\": -0.14785468352934913}], \"train_objective\": [{\"annualised_returns\": 0.8533475737331719, \"annualised_returns_over_hodl\": 0.5326920295210233, \"annualised_returns_over_uniform_hodl\": 0.5326920295210194, \"calmar\": 1.2634963841534177, \"daily_log_sharpe\": 0.7081851980390241, \"daily_returns\": 0.022847580617580382, \"fee_revenue_over_value\": 0.6678434819705683, \"jax_sharpe\": 1.1394189309356375, \"return\": 0.45439491808789145, \"returns_over_hodl\": 0.2959660813403977, \"returns_over_uniform_hodl\": 0.2959660813403957, \"sharpe\": 1.142770509412096, \"sterling\": 3.18309830765313, \"ulcer\": -0.138412978731293}], \"train_return\": 0.45439491808789145, \"train_returns_over_hodl\": 0.2959660813403977, \"train_sharpe\": 1.1394189309356375, \"validation_return\": 0.06964237598295475, \"validation_returns_over_hodl\": 0.08970989126040263, \"validation_sharpe\": 1.013830892459909}, {\"centeredness_margin\": 0.23945556253052253, \"continuous_test_metrics\": [{\"annualised_returns\": -0.8014910514800404, \"annualised_returns_over_hodl\": 0.8701721062563506, \"annualised_returns_over_uniform_hodl\": 0.7437320997252228, \"calmar\": -1.1351738206768185, \"daily_log_sharpe\": -1.986504415101937, \"daily_returns\": 0.008614657075340504, \"fee_revenue_over_value\": 0.15636122658894777, \"jax_sharpe\": -0.46810963712551895, \"return\": -0.4785778435066552, \"returns_over_hodl\": 0.2867583460773182, \"returns_over_uniform_hodl\": 0.250987683523225, \"sharpe\": -1.5944632989022256, \"sterling\": -2.0550442538819813, \"ulcer\": -0.15299098451779394}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": 0.8141850270661689, \"optuna_trial_number\": 291, \"price_ratio\": 1.2600199063719033, \"shift_exponent\": 0.9407319563773042, \"step\": 291, \"test_objective\": [{\"annualised_returns\": -0.8014910514800404, \"annualised_returns_over_hodl\": 0.8701721062563506, \"annualised_returns_over_uniform_hodl\": 0.7437320997252228, \"calmar\": -1.1351738206768185, \"daily_log_sharpe\": -1.986504415101937, \"daily_returns\": 0.008614657075340504, \"fee_revenue_over_value\": 0.15636122658894777, \"jax_sharpe\": -0.46810963712551895, \"return\": -0.4785778435066552, \"returns_over_hodl\": 0.2867583460773182, \"returns_over_uniform_hodl\": 0.250987683523225, \"sharpe\": -1.5944632989022256, \"sterling\": -2.0550442538819813, \"ulcer\": -0.15299098451779394}], \"train_objective\": [{\"annualised_returns\": 0.6439544074207, \"annualised_returns_over_hodl\": 0.3595268652572874, \"annualised_returns_over_uniform_hodl\": 0.35952686525728694, \"calmar\": 0.9797176757034926, \"daily_log_sharpe\": 0.5897541821912314, \"daily_returns\": 0.02131250104644148, \"fee_revenue_over_value\": 0.32685852521075887, \"jax_sharpe\": 1.0054940860760304, \"return\": 0.3522945430056057, \"returns_over_hodl\": 0.20498761231993545, \"returns_over_uniform_hodl\": 0.20498761231993523, \"sharpe\": 1.0189280804378307, \"sterling\": 2.450692220040525, \"ulcer\": -0.1345840683240593}], \"train_return\": 0.3522945430056057, \"train_returns_over_hodl\": 0.20498761231993545, \"train_sharpe\": 1.0054940860760304, \"validation_return\": 0.02054858729278153, \"validation_returns_over_hodl\": 0.0536773186102355, \"validation_sharpe\": 0.5307954383023036}, {\"centeredness_margin\": 0.2281733781000424, \"continuous_test_metrics\": [{\"annualised_returns\": -0.817828538459235, \"annualised_returns_over_hodl\": 0.7163257037422301, \"annualised_returns_over_uniform_hodl\": 0.6002211865554827, \"calmar\": -1.1588585644698108, \"daily_log_sharpe\": -2.063788979374658, \"daily_returns\": 0.008353310861383214, \"fee_revenue_over_value\": 0.1286770489441087, \"jax_sharpe\": -0.5165540405753966, \"return\": -0.49630520527409394, \"returns_over_hodl\": 0.24303161687971953, \"returns_over_uniform_hodl\": 0.20845648120230997, \"sharpe\": -1.6684438023630452, \"sterling\": -2.0685366008077035, \"ulcer\": -0.15575146977697718}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": 0.8167372165656467, \"optuna_trial_number\": 292, \"price_ratio\": 1.3893006804407173, \"shift_exponent\": 0.16860154308441994, \"step\": 292, \"test_objective\": [{\"annualised_returns\": -0.817828538459235, \"annualised_returns_over_hodl\": 0.7163257037422301, \"annualised_returns_over_uniform_hodl\": 0.6002211865554827, \"calmar\": -1.1588585644698108, \"daily_log_sharpe\": -2.063788979374658, \"daily_returns\": 0.008353310861383214, \"fee_revenue_over_value\": 0.1286770489441087, \"jax_sharpe\": -0.5165540405753966, \"return\": -0.49630520527409394, \"returns_over_hodl\": 0.24303161687971953, \"returns_over_uniform_hodl\": 0.20845648120230997, \"sharpe\": -1.6684438023630452, \"sterling\": -2.0685366008077035, \"ulcer\": -0.15575146977697718}], \"train_objective\": [{\"annualised_returns\": 0.749834960241655, \"annualised_returns_over_hodl\": 0.44708857342791086, \"annualised_returns_over_uniform_hodl\": 0.44708857342791086, \"calmar\": 1.1884706084001009, \"daily_log_sharpe\": 0.6600524814176998, \"daily_returns\": 0.0210403141902413, \"fee_revenue_over_value\": 0.26971410978661464, \"jax_sharpe\": 1.0763102854954025, \"return\": 0.40452259206584396, \"returns_over_hodl\": 0.2515264025995647, \"returns_over_uniform_hodl\": 0.2515264025995647, \"sharpe\": 1.0931615876206178, \"sterling\": 2.887356702479408, \"ulcer\": -0.1299887007696101}], \"train_return\": 0.40452259206584396, \"train_returns_over_hodl\": 0.2515264025995647, \"train_sharpe\": 1.0763102854954025, \"validation_return\": 0.0072594270146950635, \"validation_returns_over_hodl\": 0.04182226056071592, \"validation_sharpe\": 0.4001936939236158}, {\"centeredness_margin\": 0.2829931451026997, \"continuous_test_metrics\": [{\"annualised_returns\": -0.8039549333166793, \"annualised_returns_over_hodl\": 0.8342109122851828, \"annualised_returns_over_uniform_hodl\": 0.7220889955704224, \"calmar\": -1.3237301586945887, \"daily_log_sharpe\": -2.021713264536905, \"daily_returns\": 0.008535349813580683, \"fee_revenue_over_value\": 0.1564240371921225, \"jax_sharpe\": -1.0276585301846974, \"return\": -0.48119403270532957, \"returns_over_hodl\": 0.2767356543625994, \"returns_over_uniform_hodl\": 0.24471096431489991, \"sharpe\": -1.6340033811200327, \"sterling\": -2.2708230096109676, \"ulcer\": -0.15149618572016513}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": 0.8264748253927188, \"optuna_trial_number\": 293, \"price_ratio\": 1.2597232480474538, \"shift_exponent\": 0.6272483612600919, \"step\": 293, \"test_objective\": [{\"annualised_returns\": -0.8039549333166793, \"annualised_returns_over_hodl\": 0.8342109122851828, \"annualised_returns_over_uniform_hodl\": 0.7220889955704224, \"calmar\": -1.3237301586945887, \"daily_log_sharpe\": -2.021713264536905, \"daily_returns\": 0.008535349813580683, \"fee_revenue_over_value\": 0.1564240371921225, \"jax_sharpe\": -1.0276585301846974, \"return\": -0.48119403270532957, \"returns_over_hodl\": 0.2767356543625994, \"returns_over_uniform_hodl\": 0.24471096431489991, \"sharpe\": -1.6340033811200327, \"sterling\": -2.2708230096109676, \"ulcer\": -0.15149618572016513}], \"train_objective\": [{\"annualised_returns\": 0.6689882941613596, \"annualised_returns_over_hodl\": 0.38022953280822747, \"annualised_returns_over_uniform_hodl\": 0.3802295328082279, \"calmar\": 1.021358254359211, \"daily_log_sharpe\": 0.6087128521825337, \"daily_returns\": 0.021339607060423614, \"fee_revenue_over_value\": 0.3273014190688652, \"jax_sharpe\": 1.02276410584776, \"return\": 0.36475957498274236, \"returns_over_hodl\": 0.21609481466524483, \"returns_over_uniform_hodl\": 0.21609481466524505, \"sharpe\": 1.0367499307768435, \"sterling\": 2.5509232245806244, \"ulcer\": -0.13413332292144253}], \"train_return\": 0.36475957498274236, \"train_returns_over_hodl\": 0.21609481466524483, \"train_sharpe\": 1.0227641058477597, \"validation_return\": 0.022314837847246638, \"validation_returns_over_hodl\": 0.05027450037221448, \"validation_sharpe\": 0.5478823527441571}, {\"centeredness_margin\": 0.1608019758512444, \"continuous_test_metrics\": [{\"annualised_returns\": -0.8585442362574058, \"annualised_returns_over_hodl\": 0.3432012049202935, \"annualised_returns_over_uniform_hodl\": 0.2425684472572165, \"calmar\": -1.218606243491663, \"daily_log_sharpe\": -2.2638652737378036, \"daily_returns\": 0.007882157738581358, \"fee_revenue_over_value\": 0.06665439496897908, \"jax_sharpe\": -0.6656288116391902, \"return\": -0.5450928022925585, \"returns_over_hodl\": 0.12617856430637864, \"returns_over_uniform_hodl\": 0.09140606012076424, \"sharpe\": -1.8599028829045456, \"sterling\": -2.1070808464280146, \"ulcer\": -0.16441669454933894}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": 0.7267808040767759, \"optuna_trial_number\": 294, \"price_ratio\": 2.5027143707672033, \"shift_exponent\": 0.09650463456321479, \"step\": 294, \"test_objective\": [{\"annualised_returns\": -0.8585442362574058, \"annualised_returns_over_hodl\": 0.3432012049202935, \"annualised_returns_over_uniform_hodl\": 0.2425684472572165, \"calmar\": -1.218606243491663, \"daily_log_sharpe\": -2.2638652737378036, \"daily_returns\": 0.007882157738581358, \"fee_revenue_over_value\": 0.06665439496897908, \"jax_sharpe\": -0.6656288116391902, \"return\": -0.5450928022925585, \"returns_over_hodl\": 0.12617856430637864, \"returns_over_uniform_hodl\": 0.09140606012076424, \"sharpe\": -1.8599028829045456, \"sterling\": -2.1070808464280146, \"ulcer\": -0.16441669454933894}], \"train_objective\": [{\"annualised_returns\": 0.5771519110049816, \"annualised_returns_over_hodl\": 0.30428215279235005, \"annualised_returns_over_uniform_hodl\": 0.30428215279235005, \"calmar\": 0.9149715000444586, \"daily_log_sharpe\": 0.5541201413158364, \"daily_returns\": 0.020638758010201038, \"fee_revenue_over_value\": 0.12713323618471375, \"jax_sharpe\": 0.9587812961689826, \"return\": 0.3186612658855701, \"returns_over_hodl\": 0.1750180450381731, \"returns_over_uniform_hodl\": 0.1750180450381731, \"sharpe\": 0.9794918274191918, \"sterling\": 2.2264643111995506, \"ulcer\": -0.13055781554631654}], \"train_return\": 0.3186612658855701, \"train_returns_over_hodl\": 0.1750180450381731, \"train_sharpe\": 0.9587812961689827, \"validation_return\": -0.016256314697904783, \"validation_returns_over_hodl\": 0.015505729859011108, \"validation_sharpe\": 0.172777914156475}, {\"centeredness_margin\": 0.14982922857445044, \"continuous_test_metrics\": [{\"annualised_returns\": -0.8559937337788366, \"annualised_returns_over_hodl\": 0.3716414204177081, \"annualised_returns_over_uniform_hodl\": 0.2649724400014659, \"calmar\": -1.2761828960570587, \"daily_log_sharpe\": -2.238820927346524, \"daily_returns\": 0.007964846676209697, \"fee_revenue_over_value\": 0.07186823259722003, \"jax_sharpe\": -0.8448748790264741, \"return\": -0.5418071024343799, \"returns_over_hodl\": 0.13572183913299307, \"returns_over_uniform_hodl\": 0.09928905857633108, \"sharpe\": -1.8335308249645492, \"sterling\": -2.145169501749999, \"ulcer\": -0.16442720697557053}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": 0.7461156211946369, \"optuna_trial_number\": 295, \"price_ratio\": 2.275295359219172, \"shift_exponent\": 0.10798165687700263, \"step\": 295, \"test_objective\": [{\"annualised_returns\": -0.8559937337788366, \"annualised_returns_over_hodl\": 0.3716414204177081, \"annualised_returns_over_uniform_hodl\": 0.2649724400014659, \"calmar\": -1.2761828960570587, \"daily_log_sharpe\": -2.238820927346524, \"daily_returns\": 0.007964846676209697, \"fee_revenue_over_value\": 0.07186823259722003, \"jax_sharpe\": -0.8448748790264741, \"return\": -0.5418071024343799, \"returns_over_hodl\": 0.13572183913299307, \"returns_over_uniform_hodl\": 0.09928905857633108, \"sharpe\": -1.8335308249645492, \"sterling\": -2.145169501749999, \"ulcer\": -0.16442720697557053}], \"train_objective\": [{\"annualised_returns\": 0.6148142631997968, \"annualised_returns_over_hodl\": 0.3354283812926695, \"annualised_returns_over_uniform_hodl\": 0.3354283812926697, \"calmar\": 0.9771706479624267, \"daily_log_sharpe\": 0.5811118032462325, \"daily_returns\": 0.02065749207441374, \"fee_revenue_over_value\": 0.13939097704646286, \"jax_sharpe\": 0.9858780854951765, \"return\": 0.33769057471301855, \"returns_over_hodl\": 0.19197447034261983, \"returns_over_uniform_hodl\": 0.19197447034262005, \"sharpe\": 1.0066946747655765, \"sterling\": 2.375127562096745, \"ulcer\": -0.13008595316007737}], \"train_return\": 0.33769057471301855, \"train_returns_over_hodl\": 0.19197447034261983, \"train_sharpe\": 0.9858780854951765, \"validation_return\": -0.017787041388371394, \"validation_returns_over_hodl\": 0.017757395019639466, \"validation_sharpe\": 0.16071250331183617}, {\"centeredness_margin\": 0.02333889013831889, \"continuous_test_metrics\": [{\"annualised_returns\": -0.7795896912084926, \"annualised_returns_over_hodl\": 1.174820295905016, \"annualised_returns_over_uniform_hodl\": 0.9361169026163862, \"calmar\": -1.0970362007366798, \"daily_log_sharpe\": -1.7640322822901673, \"daily_returns\": 0.009497356620776711, \"fee_revenue_over_value\": 0.1876905409492775, \"jax_sharpe\": -0.36196182057544324, \"return\": -0.45613057302594995, \"returns_over_hodl\": 0.36739238360354354, \"returns_over_uniform_hodl\": 0.30484281520563794, \"sharpe\": -1.34633248723996, \"sterling\": -1.945682925665819, \"ulcer\": -0.15774550150132194}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": 0.922130877964759, \"optuna_trial_number\": 296, \"price_ratio\": 1.1682753925418745, \"shift_exponent\": 0.06522459882148714, \"step\": 296, \"test_objective\": [{\"annualised_returns\": -0.7795896912084926, \"annualised_returns_over_hodl\": 1.174820295905016, \"annualised_returns_over_uniform_hodl\": 0.9361169026163862, \"calmar\": -1.0970362007366798, \"daily_log_sharpe\": -1.7640322822901673, \"daily_returns\": 0.009497356620776711, \"fee_revenue_over_value\": 0.1876905409492775, \"jax_sharpe\": -0.36196182057544324, \"return\": -0.45613057302594995, \"returns_over_hodl\": 0.36739238360354354, \"returns_over_uniform_hodl\": 0.30484281520563794, \"sharpe\": -1.34633248723996, \"sterling\": -1.945682925665819, \"ulcer\": -0.15774550150132194}], \"train_objective\": [{\"annualised_returns\": 1.054117785946159, \"annualised_returns_over_hodl\": 0.6987261336390376, \"annualised_returns_over_uniform_hodl\": 0.6987261336390371, \"calmar\": 1.6935593194949599, \"daily_log_sharpe\": 0.8270248587021839, \"daily_returns\": 0.021715907051974803, \"fee_revenue_over_value\": 0.43632417312156463, \"jax_sharpe\": 1.2546366884886182, \"return\": 0.5481089851321261, \"returns_over_hodl\": 0.37947177207359784, \"returns_over_uniform_hodl\": 0.3794717720735976, \"sharpe\": 1.268243275849208, \"sterling\": 4.079269694127624, \"ulcer\": -0.12766600104931278}], \"train_return\": 0.5481089851321261, \"train_returns_over_hodl\": 0.37947177207359784, \"train_sharpe\": 1.254636688488618, \"validation_return\": 0.01124341682219221, \"validation_returns_over_hodl\": 0.06757212719566286, \"validation_sharpe\": 0.44846432056368396}, {\"centeredness_margin\": 0.030790352270422955, \"continuous_test_metrics\": [{\"annualised_returns\": -0.6932972618427706, \"annualised_returns_over_hodl\": 2.061314886060683, \"annualised_returns_over_uniform_hodl\": 1.6941224241314594, \"calmar\": -0.9740749481684644, \"daily_log_sharpe\": -1.4062884134727447, \"daily_returns\": 0.007841752010875318, \"fee_revenue_over_value\": 0.2743109841513228, \"jax_sharpe\": -0.12849390349661233, \"return\": -0.37872799434560656, \"returns_over_hodl\": 0.569255038162205, \"returns_over_uniform_hodl\": 0.4905458418151003, \"sharpe\": -0.9767341198101959, \"sterling\": -1.7695465293748773, \"ulcer\": -0.1522096433704436}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": 0.9232145955469117, \"optuna_trial_number\": 297, \"price_ratio\": 1.0646796391841196, \"shift_exponent\": 0.007315520847849351, \"step\": 297, \"test_objective\": [{\"annualised_returns\": -0.6932972618427706, \"annualised_returns_over_hodl\": 2.061314886060683, \"annualised_returns_over_uniform_hodl\": 1.6941224241314594, \"calmar\": -0.9740749481684644, \"daily_log_sharpe\": -1.4062884134727447, \"daily_returns\": 0.007841752010875318, \"fee_revenue_over_value\": 0.2743109841513228, \"jax_sharpe\": -0.12849390349661233, \"return\": -0.37872799434560656, \"returns_over_hodl\": 0.569255038162205, \"returns_over_uniform_hodl\": 0.4905458418151003, \"sharpe\": -0.9767341198101959, \"sterling\": -1.7695465293748773, \"ulcer\": -0.1522096433704436}], \"train_objective\": [{\"annualised_returns\": 1.2494392485514796, \"annualised_returns_over_hodl\": 0.8602541994872299, \"annualised_returns_over_uniform_hodl\": 0.8602541994872313, \"calmar\": 2.170738057239855, \"daily_log_sharpe\": 0.9422059733004714, \"daily_returns\": 0.02284353694060608, \"fee_revenue_over_value\": 0.4404310279608045, \"jax_sharpe\": 1.3530378794215137, \"return\": 0.635881411485139, \"returns_over_hodl\": 0.45768305156569045, \"returns_over_uniform_hodl\": 0.4576830515656911, \"sharpe\": 1.3875962588582003, \"sterling\": 4.866930746796453, \"ulcer\": -0.12122087725008523}], \"train_return\": 0.635881411485139, \"train_returns_over_hodl\": 0.45768305156569045, \"train_sharpe\": 1.3530378794215137, \"validation_return\": -0.06988761331649829, \"validation_returns_over_hodl\": 1.6996889214748734e-05, \"validation_sharpe\": -0.28581518262928185}, {\"centeredness_margin\": 0.11486061528991016, \"continuous_test_metrics\": [{\"annualised_returns\": -0.704628556600628, \"annualised_returns_over_hodl\": 1.8184146434138095, \"annualised_returns_over_uniform_hodl\": 1.5945866472909667, \"calmar\": -0.9950952868842406, \"daily_log_sharpe\": -1.5200189475947017, \"daily_returns\": 0.011343597223028063, \"fee_revenue_over_value\": 0.3517592681661606, \"jax_sharpe\": -0.18182019709793576, \"return\": -0.3880761721939515, \"returns_over_hodl\": 0.5178679419482486, \"returns_over_uniform_hodl\": 0.46811784329982586, \"sharpe\": -1.1178773599921674, \"sterling\": -1.8286957538585777, \"ulcer\": -0.14480018722592758}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": 0.9763471075751662, \"optuna_trial_number\": 298, \"price_ratio\": 1.0231097312300161, \"shift_exponent\": 0.050349034180317234, \"step\": 298, \"test_objective\": [{\"annualised_returns\": -0.704628556600628, \"annualised_returns_over_hodl\": 1.8184146434138095, \"annualised_returns_over_uniform_hodl\": 1.5945866472909667, \"calmar\": -0.9950952868842406, \"daily_log_sharpe\": -1.5200189475947017, \"daily_returns\": 0.011343597223028063, \"fee_revenue_over_value\": 0.3517592681661606, \"jax_sharpe\": -0.18182019709793576, \"return\": -0.3880761721939515, \"returns_over_hodl\": 0.5178679419482486, \"returns_over_uniform_hodl\": 0.46811784329982586, \"sharpe\": -1.1178773599921674, \"sterling\": -1.8286957538585777, \"ulcer\": -0.14480018722592758}], \"train_objective\": [{\"annualised_returns\": 0.9026449622662931, \"annualised_returns_over_hodl\": 0.5734602672502882, \"annualised_returns_over_uniform_hodl\": 0.5734602672502878, \"calmar\": 1.333030685548742, \"daily_log_sharpe\": 0.7292095854162914, \"daily_returns\": 0.02261819127337642, \"fee_revenue_over_value\": 0.7519273810483019, \"jax_sharpe\": 1.166657528123762, \"return\": 0.47776054639181176, \"returns_over_hodl\": 0.31678646607530614, \"returns_over_uniform_hodl\": 0.3167864660753059, \"sharpe\": 1.1673118236798443, \"sterling\": 3.3334211357632184, \"ulcer\": -0.13881122466747428}], \"train_return\": 0.47776054639181176, \"train_returns_over_hodl\": 0.31678646607530614, \"train_sharpe\": 1.166657528123762, \"validation_return\": 0.08362064104060396, \"validation_returns_over_hodl\": 0.10338444125028623, \"validation_sharpe\": 1.1429490705549774}, {\"centeredness_margin\": 0.7515195559378628, \"continuous_test_metrics\": [{\"annualised_returns\": -0.8726179783122208, \"annualised_returns_over_hodl\": 0.12641345166586282, \"annualised_returns_over_uniform_hodl\": 0.11894260586716765, \"calmar\": -1.3373872136396947, \"daily_log_sharpe\": -2.5660174212326363, \"daily_returns\": 0.006687929373500356, \"fee_revenue_over_value\": 0.025507790414283454, \"jax_sharpe\": -1.195836540086056, \"return\": -0.563892858036873, \"returns_over_hodl\": 0.049109140278449814, \"returns_over_uniform_hodl\": 0.04630126759745745, \"sharpe\": -2.1906476782692477, \"sterling\": -2.379447207230918, \"ulcer\": -0.15641213506924756}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": 0.5760256799726328, \"optuna_trial_number\": 299, \"price_ratio\": 38.681800675923384, \"shift_exponent\": 0.01693754800270805, \"step\": 299, \"test_objective\": [{\"annualised_returns\": -0.8726179783122208, \"annualised_returns_over_hodl\": 0.12641345166586282, \"annualised_returns_over_uniform_hodl\": 0.11894260586716765, \"calmar\": -1.3373872136396947, \"daily_log_sharpe\": -2.5660174212326363, \"daily_returns\": 0.006687929373500356, \"fee_revenue_over_value\": 0.025507790414283454, \"jax_sharpe\": -1.195836540086056, \"return\": -0.563892858036873, \"returns_over_hodl\": 0.049109140278449814, \"returns_over_uniform_hodl\": 0.04630126759745745, \"sharpe\": -2.1906476782692477, \"sterling\": -2.379447207230918, \"ulcer\": -0.15641213506924756}], \"train_objective\": [{\"annualised_returns\": 0.3193677888116153, \"annualised_returns_over_hodl\": 0.0910983576842399, \"annualised_returns_over_uniform_hodl\": 0.0910983576842399, \"calmar\": 0.4970992043309838, \"daily_log_sharpe\": 0.34866258756379376, \"daily_returns\": 0.020431583966598905, \"fee_revenue_over_value\": 0.04180833533162796, \"jax_sharpe\": 0.7547009041494349, \"return\": 0.18325050348713612, \"returns_over_hodl\": 0.05435772579865761, \"returns_over_uniform_hodl\": 0.05435772579865761, \"sharpe\": 0.773369917083843, \"sterling\": 1.217964864322742, \"ulcer\": -0.1340542357485978}], \"train_return\": 0.18325050348713612, \"train_returns_over_hodl\": 0.05435772579865761, \"train_sharpe\": 0.7547009041494348, \"validation_return\": 0.0005159197418096451, \"validation_returns_over_hodl\": 0.0057596262702217516, \"validation_sharpe\": 0.3185109114562278}]" \ No newline at end of file diff --git a/results/run_44f5e094f9ee304409c9656580e4fb0c8f0eb6d75a9a37c3261d5f96a09db477.json b/results/run_44f5e094f9ee304409c9656580e4fb0c8f0eb6d75a9a37c3261d5f96a09db477.json new file mode 100644 index 0000000..42c426c --- /dev/null +++ b/results/run_44f5e094f9ee304409c9656580e4fb0c8f0eb6d75a9a37c3261d5f96a09db477.json @@ -0,0 +1 @@ +"[{\"alphabetic\": true, \"arb_fees\": 0.0, \"arb_frequency\": 5, \"arb_quality\": 1.0, \"bout_offset\": 10080, \"checkpoint_fused\": \"scan\", \"chunk_period\": 1440, \"do_arb\": true, \"do_trades\": false, \"endDateString\": \"2025-10-05 00:00:00\", \"endTestDateString\": \"2026-05-01 00:00:00\", \"ensemble_init_method\": \"gaussian\", \"ensemble_init_scale\": 0.5, \"ensemble_init_seed\": 42, \"evaluation_starts\": [7200, 8973, 9071, 9363, 9932, 10746, 12272, 12520, 14121, 14293, 15077, 16067, 17840, 19614, 21387, 21850, 22135, 22630, 23161, 24121, 24289, 24934, 25630, 26183, 26708, 27957, 28443, 28481, 30255, 31352, 32028, 33802, 34669, 35575, 37349, 37603, 39122, 39303, 39430, 40896], \"fees\": 0.0005, \"freq\": \"minute\", \"gas_cost\": 1.0, \"initial_arc_length_speed\": 0.0001, \"initial_centeredness_margin\": 0.2, \"initial_daily_price_shift_base\": 0.999991935483871, \"initial_k_per_day\": 20, \"initial_log_amplitude\": 0.0, \"initial_memory_length\": 10.0, \"initial_memory_length_delta\": 0.0, \"initial_pool_value\": 1000000.0, \"initial_pre_exp_scaling\": 0.5, \"initial_price_ratio\": 4.0, \"initial_raw_exponents\": 0.0, \"initial_raw_width\": 0.0, \"initial_shift_exponent\": 1.0, \"initial_weights_logits\": 1.0, \"learnable_bounds_settings\": {\"freeze_bounds\": false, \"max_weights_per_asset\": null, \"min_weights_per_asset\": null}, \"max_memory_days\": 365, \"maximum_change\": 0.0003, \"minimum_weight\": null, \"n_ensemble_members\": 1, \"noise_arrays_path\": \"results/mm_noise/_sim_arrays/boldusdc_2025-07-15_2026-05-01_mm.npz\", \"noise_model\": \"mm_observed\", \"noise_trader_ratio\": 0.0, \"numeraire\": null, \"optimisation_settings\": {\"base_lr\": 0.1, \"batch_size\": 8, \"bfgs_settings\": {\"compute_dtype\": \"float32\", \"maxiter\": 100, \"n_evaluation_points\": 20, \"tol\": 1e-06}, \"checkpoint_interval\": 10, \"clip_norm\": 10.0, \"cma_es_settings\": {\"compute_dtype\": \"float32\", \"memory_budget\": null, \"n_evaluation_points\": 20, \"n_generations\": 300, \"population_size\": null, \"sigma0\": 0.5, \"tol\": 1e-08}, \"decay_lr_plateau\": 100, \"decay_lr_ratio\": 0.8, \"early_stopping\": true, \"early_stopping_metric\": \"daily_log_sharpe\", \"early_stopping_patience\": 200, \"force_scalar\": false, \"include_flipped_training_data\": false, \"initial_random_key\": 0, \"lr_decay_ratio\": 1000, \"lr_schedule_type\": \"constant\", \"max_mc_version\": 9, \"method\": \"optuna\", \"min_lr\": 1e-06, \"n_cycles\": 5, \"n_iterations\": 1000, \"n_parameter_sets\": 1, \"noise_scale\": 0.1, \"optimiser\": \"adamw\", \"optuna_settings\": {\"early_stopping\": {\"enabled\": false, \"min_improvement\": 0.001, \"patience\": 100}, \"expand_around\": false, \"make_scalar\": true, \"min_train_returns_over_hodl\": -0.5, \"multi_objective\": false, \"n_jobs\": 4, \"n_startup_trials\": 10, \"n_trials\": 50, \"overfitting_penalty\": 0.2, \"parameter_config\": {\"centeredness_margin\": {\"high\": 0.99, \"low\": 0.01, \"scalar\": true}, \"k_per_day\": {\"high\": 1000, \"log_scale\": true, \"low\": 0.1, \"scalar\": true}, \"log_amplitude\": {\"high\": 10, \"log_scale\": false, \"low\": -10, \"scalar\": true}, \"log_k\": {\"high\": 10.0, \"log_scale\": false, \"low\": -10.0, \"scalar\": true}, \"logit_lamb\": {\"high\": 4.602848117654388, \"log_scale\": false, \"low\": -5.955817303419269, \"scalar\": true}, \"memory_days_1\": {\"high\": 200, \"log_scale\": true, \"low\": 0.5, \"scalar\": true}, \"memory_days_2\": {\"high\": 200, \"log_scale\": true, \"low\": 0.5, \"scalar\": true}, \"memory_length\": {\"high\": 200, \"log_scale\": true, \"low\": 1, \"scalar\": true}, \"memory_length_delta\": {\"high\": 100, \"log_scale\": true, \"low\": 0.1, \"scalar\": true}, \"price_ratio\": {\"high\": 1.05, \"log_scale\": true, \"low\": 1.01, \"scalar\": true}, \"raw_exponents\": {\"high\": 10, \"log_scale\": false, \"low\": 0, \"scalar\": true}, \"raw_pre_exp_scaling\": {\"high\": 10, \"log_scale\": false, \"low\": -10, \"scalar\": true}, \"raw_width\": {\"high\": 10, \"log_scale\": false, \"low\": -10, \"scalar\": true}, \"shift_exponent\": {\"high\": 125.0, \"log_scale\": true, \"low\": 1e-05, \"scalar\": true}, \"weights_logits\": {\"high\": 10, \"log_scale\": false, \"low\": -10, \"scalar\": true}}, \"storage\": {\"type\": \"sqlite\", \"url\": null}, \"study_name\": null, \"timeout\": 7200}, \"parameter_init_method\": \"gaussian\", \"sample_method\": \"uniform\", \"swa_freq\": 10, \"swa_start_frac\": 0.75, \"track_checkpoints\": false, \"train_on_hessian_trace\": false, \"training_data_kind\": \"historic\", \"use_gradient_clipping\": true, \"use_plateau_decay\": false, \"use_swa\": false, \"val_fraction\": 0.2, \"warmup_steps\": 100, \"weight_decay\": 0.01}, \"price_noise_sigma\": 0.0, \"protocol_fee_split\": 0.25, \"reclamm_arc_length_speed\": null, \"reclamm_centeredness_scaling\": false, \"reclamm_interpolation_method\": \"geometric\", \"reclamm_learn_arc_length_speed\": false, \"reclamm_learn_fees\": false, \"reclamm_use_shift_exponent\": true, \"return_val\": \"returns_over_hodl\", \"rule\": \"reclamm\", \"startDateString\": \"2025-07-15 00:00:00\", \"ste_max_change\": false, \"ste_min_max_weight\": false, \"ste_temperature\": 10.0, \"subsidary_pools\": [], \"tokens\": [\"BOLD\", \"USDC\"], \"training_method\": \"optuna\", \"turnover_penalty\": 0.0, \"use_alt_lamb\": false, \"use_fused_reserves\": true, \"use_pre_exp_scaling\": true, \"weight_calculation_method\": \"auto\", \"weight_interpolation_method\": \"linear\", \"weight_interpolation_period\": 1440}, {\"centeredness_margin\": 0.9705315135199268, \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -Infinity, \"optuna_trial_number\": 0, \"price_ratio\": 1.0269216889261827, \"shift_exponent\": 3.828046963309817, \"step\": 0, \"test_objective\": -Infinity, \"train_objective\": -Infinity, \"train_return\": -Infinity, \"train_returns_over_hodl\": -Infinity, \"train_sharpe\": -Infinity, \"validation_return\": -Infinity, \"validation_returns_over_hodl\": -Infinity, \"validation_sharpe\": -Infinity}, {\"centeredness_margin\": 0.8483785433995226, \"continuous_test_metrics\": [{\"annualised_returns\": -0.0070318614084021736, \"annualised_returns_over_hodl\": -0.008709152139531495, \"annualised_returns_over_uniform_hodl\": -0.008655929042722388, \"calmar\": -1.0791695185673866, \"daily_log_sharpe\": -0.5960824551368603, \"daily_returns\": -4.623807273241882e-05, \"fee_revenue_over_value\": 6.715119112712034e-08, \"jax_sharpe\": -0.6148668676552378, \"return\": -0.004013264976805986, \"returns_over_hodl\": -0.004972341987827367, \"returns_over_uniform_hodl\": -0.004941898233937314, \"sharpe\": -0.5905177992314403, \"sterling\": -2.884276277807359, \"ulcer\": -0.0012501156699691526}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -0.000753162225985557, \"optuna_trial_number\": 1, \"price_ratio\": 1.0186778147600801, \"shift_exponent\": 0.017753754612275772, \"step\": 1, \"test_objective\": [{\"annualised_returns\": -0.0070318614084021736, \"annualised_returns_over_hodl\": -0.008709152139531495, \"annualised_returns_over_uniform_hodl\": -0.008655929042722388, \"calmar\": -1.0791695185673866, \"daily_log_sharpe\": -0.5960824551368603, \"daily_returns\": -4.623807273241882e-05, \"fee_revenue_over_value\": 6.715119112712034e-08, \"jax_sharpe\": -0.6148668676552378, \"return\": -0.004013264976805986, \"returns_over_hodl\": -0.004972341987827367, \"returns_over_uniform_hodl\": -0.004941898233937314, \"sharpe\": -0.5905177992314403, \"sterling\": -2.884276277807359, \"ulcer\": -0.0012501156699691526}], \"train_objective\": [{\"annualised_returns\": -0.005288023588112978, \"annualised_returns_over_hodl\": -0.004160891421715829, \"annualised_returns_over_uniform_hodl\": -0.004160891421716384, \"calmar\": -2.682904787719356, \"daily_log_sharpe\": -0.6213801860346686, \"daily_returns\": -0.000418286869733153, \"fee_revenue_over_value\": 2.1322309985610545e-08, \"jax_sharpe\": -0.6133529860890407, \"return\": -0.000952453263642683, \"returns_over_hodl\": -0.000749091972206628, \"returns_over_uniform_hodl\": -0.0007490919722067391, \"sharpe\": -0.6170857988632001, \"sterling\": -3.1639515729484975, \"ulcer\": -0.000793052049761743}], \"train_return\": -0.000952453263642683, \"train_returns_over_hodl\": -0.000749091972206628, \"train_sharpe\": -0.6133529860890405, \"validation_return\": -0.00030256492034297366, \"validation_returns_over_hodl\": -0.0001461082643764433, \"validation_sharpe\": -0.8653224960839583}, {\"centeredness_margin\": 0.1435054076939128, \"continuous_test_metrics\": [{\"annualised_returns\": -0.999999999999258, \"annualised_returns_over_hodl\": -0.9999999999992593, \"annualised_returns_over_uniform_hodl\": -0.9999999999992593, \"calmar\": -1.0000001218291144, \"daily_log_sharpe\": -1.3991374576685274, \"daily_returns\": -4.669304346907343e-05, \"fee_revenue_over_value\": 5.922504137937057e-08, \"jax_sharpe\": -10.620242459966331, \"return\": -0.9999998775947065, \"returns_over_hodl\": -0.9999998777137343, \"returns_over_uniform_hodl\": -0.9999998777088343, \"sharpe\": -1.9896363763128277, \"sterling\": -9.982804138233442, \"ulcer\": -0.06221563445122143}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -0.00014473387453358688, \"optuna_trial_number\": 2, \"price_ratio\": 1.0219520108601237, \"shift_exponent\": 47.71175613598654, \"step\": 2, \"test_objective\": [{\"annualised_returns\": -0.999999999999258, \"annualised_returns_over_hodl\": -0.9999999999992593, \"annualised_returns_over_uniform_hodl\": -0.9999999999992593, \"calmar\": -1.0000001218291144, \"daily_log_sharpe\": -1.3991374576685274, \"daily_returns\": -4.669304346907343e-05, \"fee_revenue_over_value\": 5.922504137937057e-08, \"jax_sharpe\": -10.620242459966331, \"return\": -0.9999998775947065, \"returns_over_hodl\": -0.9999998777137343, \"returns_over_uniform_hodl\": -0.9999998777088343, \"sharpe\": -1.9896363763128277, \"sterling\": -9.982804138233442, \"ulcer\": -0.06221563445122143}], \"train_objective\": [{\"annualised_returns\": 0.0006440676706649384, \"annualised_returns_over_hodl\": 0.0017779216329476544, \"annualised_returns_over_uniform_hodl\": 0.0017779216329489866, \"calmar\": 0.33955936933294467, \"daily_log_sharpe\": 0.07215639722852649, \"daily_returns\": -0.0004228490253137523, \"fee_revenue_over_value\": 2.1448033367835388e-08, \"jax_sharpe\": 0.0763556891089936, \"return\": 0.00011572393305936401, \"returns_over_hodl\": 0.000319302657484144, \"returns_over_uniform_hodl\": 0.00031930265748436604, \"sharpe\": 0.0766488170731237, \"sterling\": 0.3776382675021919, \"ulcer\": -0.0007618429251113228}], \"train_return\": 0.00011572393305936401, \"train_returns_over_hodl\": 0.000319302657484144, \"train_sharpe\": 0.0763556891089936, \"validation_return\": -8.727929765606213e-05, \"validation_returns_over_hodl\": 6.730638289131896e-05, \"validation_sharpe\": -0.2714382894508191}, {\"centeredness_margin\": 0.31170056013703984, \"continuous_test_metrics\": [{\"annualised_returns\": 0.002830117296601875, \"annualised_returns_over_hodl\": 0.0011324956510567752, \"annualised_returns_over_uniform_hodl\": 0.001189919718326049, \"calmar\": 0.6735477633193868, \"daily_log_sharpe\": 0.3221162688379301, \"daily_returns\": -4.63384482852676e-05, \"fee_revenue_over_value\": 6.734964862492636e-08, \"jax_sharpe\": 0.29373733784833866, \"return\": 0.0016117934594890304, \"returns_over_hodl\": 0.0006452081263255138, \"returns_over_uniform_hodl\": 0.00067791553779184, \"sharpe\": 0.3270288360203714, \"sterling\": 1.5738818488926838, \"ulcer\": -0.0007359265434141715}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -0.0001793904798752355, \"optuna_trial_number\": 3, \"price_ratio\": 1.0269459875320377, \"shift_exponent\": 0.07697056285540975, \"step\": 3, \"test_objective\": [{\"annualised_returns\": 0.002830117296601875, \"annualised_returns_over_hodl\": 0.0011324956510567752, \"annualised_returns_over_uniform_hodl\": 0.001189919718326049, \"calmar\": 0.6735477633193868, \"daily_log_sharpe\": 0.3221162688379301, \"daily_returns\": -4.63384482852676e-05, \"fee_revenue_over_value\": 6.734964862492636e-08, \"jax_sharpe\": 0.29373733784833866, \"return\": 0.0016117934594890304, \"returns_over_hodl\": 0.0006452081263255138, \"returns_over_uniform_hodl\": 0.00067791553779184, \"sharpe\": 0.3270288360203714, \"sterling\": 1.5738818488926838, \"ulcer\": -0.0007359265434141715}], \"train_objective\": [{\"annualised_returns\": 0.0003191878620236732, \"annualised_returns_over_hodl\": 0.0014526736951496755, \"annualised_returns_over_uniform_hodl\": 0.0014526736951496755, \"calmar\": 0.16717580233768653, \"daily_log_sharpe\": 0.03621157872891251, \"daily_returns\": -0.00041828687828537365, \"fee_revenue_over_value\": 2.1204372415326673e-08, \"jax_sharpe\": 0.04052638427086134, \"return\": 5.7358250794337096e-05, \"returns_over_hodl\": 0.00026092509458308655, \"returns_over_uniform_hodl\": 0.00026092509458308655, \"sharpe\": 0.040655193886243654, \"sterling\": 0.18875666484065598, \"ulcer\": -0.0007650362747108154}], \"train_return\": 5.7358250794337096e-05, \"train_returns_over_hodl\": 0.00026092509458308655, \"train_sharpe\": 0.04052638427086134, \"validation_return\": -9.908251163703863e-05, \"validation_returns_over_hodl\": 5.500418138204566e-05, \"validation_sharpe\": -0.302056642897357}, {\"centeredness_margin\": 0.19757177606387216, \"continuous_test_metrics\": [{\"annualised_returns\": 0.003865701438038549, \"annualised_returns_over_hodl\": 0.002142360654313835, \"annualised_returns_over_uniform_hodl\": 0.0022238100907268077, \"calmar\": 0.9536129196775749, \"daily_log_sharpe\": 0.44954967268727886, \"daily_returns\": -4.6992831588591404e-05, \"fee_revenue_over_value\": 6.666161766360154e-08, \"jax_sharpe\": 0.420784483850652, \"return\": 0.002201084794766217, \"returns_over_hodl\": 0.0012202860831742601, \"returns_over_uniform_hodl\": 0.0012666574324862179, \"sharpe\": 0.4541851842347319, \"sterling\": 2.371793011448105, \"ulcer\": -0.0006118864857495677}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -0.00011543041626387307, \"optuna_trial_number\": 4, \"price_ratio\": 1.0189779037348938, \"shift_exponent\": 0.00024176679153565904, \"step\": 4, \"test_objective\": [{\"annualised_returns\": 0.003865701438038549, \"annualised_returns_over_hodl\": 0.002142360654313835, \"annualised_returns_over_uniform_hodl\": 0.0022238100907268077, \"calmar\": 0.9536129196775749, \"daily_log_sharpe\": 0.44954967268727886, \"daily_returns\": -4.6992831588591404e-05, \"fee_revenue_over_value\": 6.666161766360154e-08, \"jax_sharpe\": 0.420784483850652, \"return\": 0.002201084794766217, \"returns_over_hodl\": 0.0012202860831742601, \"returns_over_uniform_hodl\": 0.0012666574324862179, \"sharpe\": 0.4541851842347319, \"sterling\": 2.371793011448105, \"ulcer\": -0.0006118864857495677}], \"train_objective\": [{\"annualised_returns\": 0.0009188433926954342, \"annualised_returns_over_hodl\": 0.0020530087099863703, \"annualised_returns_over_uniform_hodl\": 0.0020530087099863703, \"calmar\": 0.4876643723482636, \"daily_log_sharpe\": 0.10300122664048747, \"daily_returns\": -0.0004182868782575554, \"fee_revenue_over_value\": 2.159654808318211e-08, \"jax_sharpe\": 0.10724093194713924, \"return\": 0.0001650761264666567, \"returns_over_hodl\": 0.00036866489678555325, \"returns_over_uniform_hodl\": 0.00036866489678555325, \"sharpe\": 0.10748441790553563, \"sterling\": 0.5345848958384085, \"ulcer\": -0.0007571702044217817}], \"train_return\": 0.0001650761264666567, \"train_returns_over_hodl\": 0.00036866489678555325, \"train_sharpe\": 0.10724093194713921, \"validation_return\": -7.729995583372062e-05, \"validation_returns_over_hodl\": 7.770761833181261e-05, \"validation_sharpe\": -0.24405315973159467}, {\"centeredness_margin\": 0.06801178262232609, \"continuous_test_metrics\": [{\"annualised_returns\": 0.00481054963208849, \"annualised_returns_over_hodl\": 0.003077037527198012, \"annualised_returns_over_uniform_hodl\": 0.0031671129206185533, \"calmar\": 1.1942402650225643, \"daily_log_sharpe\": 0.5626879601356506, \"daily_returns\": -4.7226064538751335e-05, \"fee_revenue_over_value\": 6.855408719099562e-08, \"jax_sharpe\": 0.5368489055656892, \"return\": 0.00273851544174164, \"returns_over_hodl\": 0.0017523253082134538, \"returns_over_uniform_hodl\": 0.0018035869924946102, \"sharpe\": 0.5671760105596919, \"sterling\": 3.124398064621498, \"ulcer\": -0.0005589561953890088}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -9.263216184786927e-05, \"optuna_trial_number\": 5, \"price_ratio\": 1.0171682170362146, \"shift_exponent\": 1.67421801873129e-05, \"step\": 5, \"test_objective\": [{\"annualised_returns\": 0.00481054963208849, \"annualised_returns_over_hodl\": 0.003077037527198012, \"annualised_returns_over_uniform_hodl\": 0.0031671129206185533, \"calmar\": 1.1942402650225643, \"daily_log_sharpe\": 0.5626879601356506, \"daily_returns\": -4.7226064538751335e-05, \"fee_revenue_over_value\": 6.855408719099562e-08, \"jax_sharpe\": 0.5368489055656892, \"return\": 0.00273851544174164, \"returns_over_hodl\": 0.0017523253082134538, \"returns_over_uniform_hodl\": 0.0018035869924946102, \"sharpe\": 0.5671760105596919, \"sterling\": 3.124398064621498, \"ulcer\": -0.0005589561953890088}], \"train_objective\": [{\"annualised_returns\": 0.0011326690015966978, \"annualised_returns_over_hodl\": 0.0022670766098495942, \"annualised_returns_over_uniform_hodl\": 0.0022670766098495942, \"calmar\": 0.605496792288272, \"daily_log_sharpe\": 0.1258990644488, \"daily_returns\": -0.0004241031213781333, \"fee_revenue_over_value\": 2.1688196698789577e-08, \"jax_sharpe\": 0.129709397898856, \"return\": 0.00020347344235505105, \"returns_over_hodl\": 0.00040707002864603936, \"returns_over_uniform_hodl\": 0.00040707002864603936, \"sharpe\": 0.13041663440512247, \"sterling\": 0.6550504890496187, \"ulcer\": -0.0007548145538433133}], \"train_return\": 0.00020347344235505105, \"train_returns_over_hodl\": 0.00040707002864603936, \"train_sharpe\": 0.129709397898856, \"validation_return\": -6.95364798308784e-05, \"validation_returns_over_hodl\": 8.57993148781766e-05, \"validation_sharpe\": -0.21994038890444897}, {\"centeredness_margin\": 0.8812651525819237, \"continuous_test_metrics\": [{\"annualised_returns\": -0.000429606968937013, \"annualised_returns_over_hodl\": -0.0021402725316576054, \"annualised_returns_over_uniform_hodl\": -0.002064473044145698, \"calmar\": -0.08846378116808873, \"daily_log_sharpe\": -0.009759723843075774, \"daily_returns\": -4.6847473055543624e-05, \"fee_revenue_over_value\": 6.094335546949726e-08, \"jax_sharpe\": -0.032462254888176785, \"return\": -0.0002448389291770381, \"returns_over_hodl\": -0.0012202200739555025, \"returns_over_uniform_hodl\": -0.0011769857730177247, \"sharpe\": -0.004115272870416158, \"sterling\": -0.19934918943491012, \"ulcer\": -0.001011987238040047}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -0.0004127901621728353, \"optuna_trial_number\": 6, \"price_ratio\": 1.0460008670372156, \"shift_exponent\": 0.0029750485212631174, \"step\": 6, \"test_objective\": [{\"annualised_returns\": -0.000429606968937013, \"annualised_returns_over_hodl\": -0.0021402725316576054, \"annualised_returns_over_uniform_hodl\": -0.002064473044145698, \"calmar\": -0.08846378116808873, \"daily_log_sharpe\": -0.009759723843075774, \"daily_returns\": -4.6847473055543624e-05, \"fee_revenue_over_value\": 6.094335546949726e-08, \"jax_sharpe\": -0.032462254888176785, \"return\": -0.0002448389291770381, \"returns_over_hodl\": -0.0012202200739555025, \"returns_over_uniform_hodl\": -0.0011769857730177247, \"sharpe\": -0.004115272870416158, \"sterling\": -0.19934918943491012, \"ulcer\": -0.001011987238040047}], \"train_objective\": [{\"annualised_returns\": -0.002276692544529335, \"annualised_returns_over_hodl\": -0.001146148166193517, \"annualised_returns_over_uniform_hodl\": -0.0011461481661929618, \"calmar\": -1.1262244546847484, \"daily_log_sharpe\": -0.26327236878176946, \"daily_returns\": -0.0004204960053340095, \"fee_revenue_over_value\": 1.96117727753207e-08, \"jax_sharpe\": -0.2577564808232864, \"return\": -0.00040955917556440014, \"returns_over_hodl\": -0.0002060873752312009, \"returns_over_uniform_hodl\": -0.0002060873752310899, \"sharpe\": -0.2588990851454688, \"sterling\": -1.3430634060714324, \"ulcer\": -0.0008036865888934341}], \"train_return\": -0.00040955917556440014, \"train_returns_over_hodl\": -0.0002060873752312009, \"train_sharpe\": -0.2577564808232864, \"validation_return\": -0.0001232785946706505, \"validation_returns_over_hodl\": 3.306510600564749e-05, \"validation_sharpe\": -0.36223388360320563}, {\"centeredness_margin\": 0.5209450519058768, \"continuous_test_metrics\": [{\"annualised_returns\": -0.9999999999999113, \"annualised_returns_over_hodl\": -0.9999999999999115, \"annualised_returns_over_uniform_hodl\": -0.9999999999999115, \"calmar\": -1.0000000362726984, \"daily_log_sharpe\": -2.6770243876566022, \"daily_returns\": -4.728268897071485e-05, \"fee_revenue_over_value\": 9.573876148025981e-09, \"jax_sharpe\": -13.918601370968403, \"return\": -0.9999999635235529, \"returns_over_hodl\": -0.9999999635594704, \"returns_over_uniform_hodl\": -0.9999999635575627, \"sharpe\": -2.7213052132537277, \"sterling\": -3.003539250738222, \"ulcer\": -0.1929264346185054}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -8.709599430423432e-05, \"optuna_trial_number\": 7, \"price_ratio\": 1.0167796553153785, \"shift_exponent\": 12.96009542042159, \"step\": 7, \"test_objective\": [{\"annualised_returns\": -0.9999999999999113, \"annualised_returns_over_hodl\": -0.9999999999999115, \"annualised_returns_over_uniform_hodl\": -0.9999999999999115, \"calmar\": -1.0000000362726984, \"daily_log_sharpe\": -2.6770243876566022, \"daily_returns\": -4.728268897071485e-05, \"fee_revenue_over_value\": 9.573876148025981e-09, \"jax_sharpe\": -13.918601370968403, \"return\": -0.9999999635235529, \"returns_over_hodl\": -0.9999999635594704, \"returns_over_uniform_hodl\": -0.9999999635575627, \"sharpe\": -2.7213052132537277, \"sterling\": -3.003539250738222, \"ulcer\": -0.1929264346185054}], \"train_objective\": [{\"annualised_returns\": 0.001184599707077183, \"annualised_returns_over_hodl\": 0.0023190661592666917, \"annualised_returns_over_uniform_hodl\": 0.0023190661592666917, \"calmar\": 0.6340936925702964, \"daily_log_sharpe\": 0.1299089493195694, \"daily_returns\": -0.00042423637398822617, \"fee_revenue_over_value\": 2.170800304254294e-08, \"jax_sharpe\": 0.13438967376621594, \"return\": 0.0002127977820214344, \"returns_over_hodl\": 0.00041639626632994364, \"returns_over_uniform_hodl\": 0.00041639626632994364, \"sharpe\": 0.13448897077532876, \"sterling\": 0.6840904120897559, \"ulcer\": -0.0007543952108578901}], \"train_return\": 0.0002127977820214344, \"train_returns_over_hodl\": 0.00041639626632994364, \"train_sharpe\": 0.1343896737662159, \"validation_return\": -6.765129859032104e-05, \"validation_returns_over_hodl\": 8.776419825395898e-05, \"validation_sharpe\": -0.21409600002417173}, {\"centeredness_margin\": 0.988031719881667, \"continuous_test_metrics\": [{\"annualised_returns\": -0.006294838604110953, \"annualised_returns_over_hodl\": -0.007934588116751073, \"annualised_returns_over_uniform_hodl\": -0.00792011168988116, \"calmar\": -1.0607676773184975, \"daily_log_sharpe\": -0.531520522328535, \"daily_returns\": -5.108568823890045e-05, \"fee_revenue_over_value\": 2.791160595633111e-08, \"jax_sharpe\": -0.5507456651995571, \"return\": -0.0035920547782578582, \"returns_over_hodl\": -0.00452935860432202, \"returns_over_uniform_hodl\": -0.004521080761301088, \"sharpe\": -0.5259142133319055, \"sterling\": -2.602928055857092, \"ulcer\": -0.0012034107269803108}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -0.0009698913735971898, \"optuna_trial_number\": 8, \"price_ratio\": 1.0253141250717166, \"shift_exponent\": 0.0022437951549727107, \"step\": 8, \"test_objective\": [{\"annualised_returns\": -0.006294838604110953, \"annualised_returns_over_hodl\": -0.007934588116751073, \"annualised_returns_over_uniform_hodl\": -0.00792011168988116, \"calmar\": -1.0607676773184975, \"daily_log_sharpe\": -0.531520522328535, \"daily_returns\": -5.108568823890045e-05, \"fee_revenue_over_value\": 2.791160595633111e-08, \"jax_sharpe\": -0.5507456651995571, \"return\": -0.0035920547782578582, \"returns_over_hodl\": -0.00452935860432202, \"returns_over_uniform_hodl\": -0.004521080761301088, \"sharpe\": -0.5259142133319055, \"sterling\": -2.602928055857092, \"ulcer\": -0.0012034107269803108}], \"train_objective\": [{\"annualised_returns\": -0.005921079438325094, \"annualised_returns_over_hodl\": -0.004794664602802867, \"annualised_returns_over_uniform_hodl\": -0.004794664602802867, \"calmar\": -2.6458973753486816, \"daily_log_sharpe\": -0.6810606219585663, \"daily_returns\": -0.0004222512624125535, \"fee_revenue_over_value\": 1.3801822985193785e-08, \"jax_sharpe\": -0.6704708792988321, \"return\": -0.0010667544145916974, \"returns_over_hodl\": -0.0008634163897457414, \"returns_over_uniform_hodl\": -0.0008634163897457414, \"sharpe\": -0.6766706384707122, \"sterling\": -3.3998616153343226, \"ulcer\": -0.0008456789882654904}], \"train_return\": -0.0010667544145916974, \"train_returns_over_hodl\": -0.0008634163897457414, \"train_sharpe\": -0.6704708792988321, \"validation_return\": -0.0003787145716238616, \"validation_returns_over_hodl\": -0.00022544350989706086, \"validation_sharpe\": -1.1075541558843474}, {\"centeredness_margin\": 0.08206634412143726, \"continuous_test_metrics\": [{\"annualised_returns\": 0.0029880374681980904, \"annualised_returns_over_hodl\": 0.0013084038725106328, \"annualised_returns_over_uniform_hodl\": 0.0013475816006278674, \"calmar\": 0.6952538195894145, \"daily_log_sharpe\": 0.32525683181282616, \"daily_returns\": -4.5839579527839575e-05, \"fee_revenue_over_value\": 6.549944912055727e-08, \"jax_sharpe\": 0.30197685741426056, \"return\": 0.001701673649341151, \"returns_over_hodl\": 0.0007453988028258696, \"returns_over_uniform_hodl\": 0.0007677119255904419, \"sharpe\": 0.330301654756731, \"sterling\": 1.620534201407616, \"ulcer\": -0.000778700962511635}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -0.0002415284538617252, \"optuna_trial_number\": 9, \"price_ratio\": 1.0396269633686013, \"shift_exponent\": 2.375032934430731e-05, \"step\": 9, \"test_objective\": [{\"annualised_returns\": 0.0029880374681980904, \"annualised_returns_over_hodl\": 0.0013084038725106328, \"annualised_returns_over_uniform_hodl\": 0.0013475816006278674, \"calmar\": 0.6952538195894145, \"daily_log_sharpe\": 0.32525683181282616, \"daily_returns\": -4.5839579527839575e-05, \"fee_revenue_over_value\": 6.549944912055727e-08, \"jax_sharpe\": 0.30197685741426056, \"return\": 0.001701673649341151, \"returns_over_hodl\": 0.0007453988028258696, \"returns_over_uniform_hodl\": 0.0007677119255904419, \"sharpe\": 0.330301654756731, \"sterling\": 1.620534201407616, \"ulcer\": -0.000778700962511635}], \"train_objective\": [{\"annualised_returns\": -0.00021980321425263405, \"annualised_returns_over_hodl\": 0.0009130718750649525, \"annualised_returns_over_uniform_hodl\": 0.0009130718750649525, \"calmar\": -0.11287095697324459, \"daily_log_sharpe\": -0.0247565322089569, \"daily_returns\": -0.0004208415028742001, \"fee_revenue_over_value\": 2.0615999641970387e-08, \"jax_sharpe\": -0.02020473147248405, \"return\": -3.950750205283793e-05, \"returns_over_hodl\": 0.00016403962421107643, \"returns_over_uniform_hodl\": 0.00016403962421107643, \"sharpe\": -0.020273660316453068, \"sterling\": -0.13134687685092708, \"ulcer\": -0.0007734945922629643}], \"train_return\": -3.950750205283793e-05, \"train_returns_over_hodl\": 0.00016403962421107643, \"train_sharpe\": -0.02020473147248405, \"validation_return\": -0.00011568802416239699, \"validation_returns_over_hodl\": 3.769668164022022e-05, \"validation_sharpe\": -0.3503802510554694}, {\"centeredness_margin\": 0.40568382859472657, \"continuous_test_metrics\": [{\"annualised_returns\": 0.002029640340732941, \"annualised_returns_over_hodl\": 0.00034428911411543694, \"annualised_returns_over_uniform_hodl\": 0.00039075199753257905, \"calmar\": 0.4756624358551505, \"daily_log_sharpe\": 0.2299694720035385, \"daily_returns\": -4.6039871825105047e-05, \"fee_revenue_over_value\": 6.62789010719208e-08, \"jax_sharpe\": 0.2022991237858996, \"return\": 0.001156108710249848, \"returns_over_hodl\": 0.00019618245188235406, \"returns_over_uniform_hodl\": 0.00022265565767787265, \"sharpe\": 0.23514231296087845, \"sterling\": 1.0812682924056973, \"ulcer\": -0.0007954916049290155}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -0.00021853728120247164, \"optuna_trial_number\": 10, \"price_ratio\": 1.033329966564406, \"shift_exponent\": 0.008034540706451312, \"step\": 10, \"test_objective\": [{\"annualised_returns\": 0.002029640340732941, \"annualised_returns_over_hodl\": 0.00034428911411543694, \"annualised_returns_over_uniform_hodl\": 0.00039075199753257905, \"calmar\": 0.4756624358551505, \"daily_log_sharpe\": 0.2299694720035385, \"daily_returns\": -4.6039871825105047e-05, \"fee_revenue_over_value\": 6.62789010719208e-08, \"jax_sharpe\": 0.2022991237858996, \"return\": 0.001156108710249848, \"returns_over_hodl\": 0.00019618245188235406, \"returns_over_uniform_hodl\": 0.00022265565767787265, \"sharpe\": 0.23514231296087845, \"sterling\": 1.0812682924056973, \"ulcer\": -0.0007954916049290155}], \"train_objective\": [{\"annualised_returns\": -1.881226433431138e-05, \"annualised_returns_over_hodl\": 0.0011142905726839736, \"annualised_returns_over_uniform_hodl\": 0.0011142905726839736, \"calmar\": -0.009718040957239523, \"daily_log_sharpe\": -0.002122773859529543, \"daily_returns\": -0.00042131257717104985, \"fee_revenue_over_value\": 2.0902902109334014e-08, \"jax_sharpe\": 0.0023238900183436347, \"return\": -3.3810438301307144e-06, \"returns_over_hodl\": 0.00020017343616118843, \"returns_over_uniform_hodl\": 0.00020017343616118843, \"sharpe\": 0.002349369553174303, \"sterling\": -0.011194789012938255, \"ulcer\": -0.0007695073013547083}], \"train_return\": -3.3810438301307144e-06, \"train_returns_over_hodl\": 0.00020017343616118843, \"train_sharpe\": 0.0023238900183436347, \"validation_return\": -0.00010902095929965494, \"validation_returns_over_hodl\": 4.464558852346201e-05, \"validation_sharpe\": -0.331758933961017}, {\"centeredness_margin\": 0.926013817890981, \"continuous_test_metrics\": [{\"annualised_returns\": -0.0021749770095473853, \"annualised_returns_over_hodl\": -0.0038542432856114583, \"annualised_returns_over_uniform_hodl\": -0.003806988412100698, \"calmar\": -0.43317324635706567, \"daily_log_sharpe\": -0.16517557024543592, \"daily_returns\": -4.60669845974308e-05, \"fee_revenue_over_value\": 5.2087581719107485e-08, \"jax_sharpe\": -0.18547701439100156, \"return\": -0.0012400151890110678, \"returns_over_hodl\": -0.002198207362261395, \"returns_over_uniform_hodl\": -0.0021712341552615477, \"sharpe\": -0.15948554695625605, \"sterling\": -0.9794037108072988, \"ulcer\": -0.001053210190594978}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -0.0005688438266606505, \"optuna_trial_number\": 11, \"price_ratio\": 1.0318670667859244, \"shift_exponent\": 0.001725866789080287, \"step\": 11, \"test_objective\": [{\"annualised_returns\": -0.0021749770095473853, \"annualised_returns_over_hodl\": -0.0038542432856114583, \"annualised_returns_over_uniform_hodl\": -0.003806988412100698, \"calmar\": -0.43317324635706567, \"daily_log_sharpe\": -0.16517557024543592, \"daily_returns\": -4.60669845974308e-05, \"fee_revenue_over_value\": 5.2087581719107485e-08, \"jax_sharpe\": -0.18547701439100156, \"return\": -0.0012400151890110678, \"returns_over_hodl\": -0.002198207362261395, \"returns_over_uniform_hodl\": -0.0021712341552615477, \"sharpe\": -0.15948554695625605, \"sterling\": -0.9794037108072988, \"ulcer\": -0.001053210190594978}], \"train_objective\": [{\"annualised_returns\": -0.003210735127432751, \"annualised_returns_over_hodl\": -0.0020812491353078277, \"annualised_returns_over_uniform_hodl\": -0.0020812491353071616, \"calmar\": -1.5514498568404178, \"daily_log_sharpe\": -0.37418467853756565, \"daily_returns\": -0.0004214486635937713, \"fee_revenue_over_value\": 1.745243988709705e-08, \"jax_sharpe\": -0.36772798843328464, \"return\": -0.0005778079249524337, \"returns_over_hodl\": -0.0003743703725216374, \"returns_over_uniform_hodl\": -0.0003743703725215264, \"sharpe\": -0.3698397801668877, \"sterling\": -1.8967239145105879, \"ulcer\": -0.0008039499426406654}], \"train_return\": -0.0005778079249524337, \"train_returns_over_hodl\": -0.0003743703725216374, \"train_sharpe\": -0.36772798843328464, \"validation_return\": -0.0002789823917117573, \"validation_returns_over_hodl\": -0.0001238997416656007, \"validation_sharpe\": -0.805481688664809}, {\"centeredness_margin\": 0.6025676727517822, \"continuous_test_metrics\": [{\"annualised_returns\": 0.0026312691238983277, \"annualised_returns_over_hodl\": 0.0009527070320891617, \"annualised_returns_over_uniform_hodl\": 0.0009913967754751063, \"calmar\": 0.605538134246308, \"daily_log_sharpe\": 0.28488365483738204, \"daily_returns\": -4.5826624608761324e-05, \"fee_revenue_over_value\": 5.8239553453583705e-08, \"jax_sharpe\": 0.26124489236597775, \"return\": 0.001498610457635996, \"returns_over_hodl\": 0.0005427994969551264, \"returns_over_uniform_hodl\": 0.0005648380649543316, \"sharpe\": 0.290037532931639, \"sterling\": 1.3915258803016708, \"ulcer\": -0.000815778880112569}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -0.00026014075897213277, \"optuna_trial_number\": 12, \"price_ratio\": 1.0498800109374409, \"shift_exponent\": 1.879273073793736e-05, \"step\": 12, \"test_objective\": [{\"annualised_returns\": 0.0026312691238983277, \"annualised_returns_over_hodl\": 0.0009527070320891617, \"annualised_returns_over_uniform_hodl\": 0.0009913967754751063, \"calmar\": 0.605538134246308, \"daily_log_sharpe\": 0.28488365483738204, \"daily_returns\": -4.5826624608761324e-05, \"fee_revenue_over_value\": 5.8239553453583705e-08, \"jax_sharpe\": 0.26124489236597775, \"return\": 0.001498610457635996, \"returns_over_hodl\": 0.0005427994969551264, \"returns_over_uniform_hodl\": 0.0005648380649543316, \"sharpe\": 0.290037532931639, \"sterling\": 1.3915258803016708, \"ulcer\": -0.000815778880112569}], \"train_objective\": [{\"annualised_returns\": -0.00040282794222457063, \"annualised_returns_over_hodl\": 0.0007298397573538562, \"annualised_returns_over_uniform_hodl\": 0.0007298397573538562, \"calmar\": -0.20619905738623137, \"daily_log_sharpe\": -0.04583811980605102, \"daily_returns\": -0.0004203289501581347, \"fee_revenue_over_value\": 2.0169693814951087e-08, \"jax_sharpe\": -0.041298759207707036, \"return\": -7.240986351009226e-05, \"returns_over_hodl\": 0.00013113056530822398, \"returns_over_uniform_hodl\": 0.00013113056530822398, \"sharpe\": -0.041399430061006486, \"sterling\": -0.24228063136173536, \"ulcer\": -0.0007744077994164249}], \"train_return\": -7.240986351009226e-05, \"train_returns_over_hodl\": 0.00013113056530822398, \"train_sharpe\": -0.041298759207707036, \"validation_return\": -0.0001225104313521408, \"validation_returns_over_hodl\": 3.056765605524703e-05, \"validation_sharpe\": -0.36668421150467173}, {\"centeredness_margin\": 0.44409206762800946, \"continuous_test_metrics\": [{\"annualised_returns\": -0.012421048342217023, \"annualised_returns_over_hodl\": -0.014473354468504951, \"annualised_returns_over_uniform_hodl\": -0.014036301590924838, \"calmar\": -1.1978104135540362, \"daily_log_sharpe\": -0.944164425622122, \"daily_returns\": -5.690236401136156e-05, \"fee_revenue_over_value\": 7.038586586680694e-08, \"jax_sharpe\": -0.9605788092152842, \"return\": -0.0070972940376407, \"returns_over_hodl\": -0.008273654132566977, \"returns_over_uniform_hodl\": -0.008023051822789284, \"sharpe\": -0.9380216428703629, \"sterling\": -4.685585217420836, \"ulcer\": -0.0014031108667419572}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -0.0009982817353654306, \"optuna_trial_number\": 13, \"price_ratio\": 1.010274847611044, \"shift_exponent\": 0.27895610784089786, \"step\": 13, \"test_objective\": [{\"annualised_returns\": -0.012421048342217023, \"annualised_returns_over_hodl\": -0.014473354468504951, \"annualised_returns_over_uniform_hodl\": -0.014036301590924838, \"calmar\": -1.1978104135540362, \"daily_log_sharpe\": -0.944164425622122, \"daily_returns\": -5.690236401136156e-05, \"fee_revenue_over_value\": 7.038586586680694e-08, \"jax_sharpe\": -0.9605788092152842, \"return\": -0.0070972940376407, \"returns_over_hodl\": -0.008273654132566977, \"returns_over_uniform_hodl\": -0.008023051822789284, \"sharpe\": -0.9380216428703629, \"sterling\": -4.685585217420836, \"ulcer\": -0.0014031108667419572}], \"train_objective\": [{\"annualised_returns\": -0.007414654557348799, \"annualised_returns_over_hodl\": -0.006289932127869546, \"annualised_returns_over_uniform_hodl\": -0.0062899321278702125, \"calmar\": -3.4447263547077265, \"daily_log_sharpe\": -0.8301100520821049, \"daily_returns\": -0.0004279639297458482, \"fee_revenue_over_value\": 2.2034788587600973e-08, \"jax_sharpe\": -0.82662277761531, \"return\": -0.0013366630832210014, \"returns_over_hodl\": -0.0011333799996795513, \"returns_over_uniform_hodl\": -0.0011333799996796623, \"sharpe\": -0.8255789037755825, \"sterling\": -3.769768431248404, \"ulcer\": -0.0008596241042636524}], \"train_return\": -0.0013366630832210014, \"train_returns_over_hodl\": -0.0011333799996795513, \"train_sharpe\": -0.8266227776153099, \"validation_return\": -4.230402514937559e-05, \"validation_returns_over_hodl\": 0.00014261619528976865, \"validation_sharpe\": -0.11602809447596746}, {\"centeredness_margin\": 0.5946097415736847, \"continuous_test_metrics\": [{\"annualised_returns\": -0.9999999999998803, \"annualised_returns_over_hodl\": -0.9999999999998805, \"annualised_returns_over_uniform_hodl\": -0.9999999999998805, \"calmar\": -1.0000000430168712, \"daily_log_sharpe\": -1.6116984329198079, \"daily_returns\": -4.6779937337003405e-05, \"fee_revenue_over_value\": 2.6157192192010916e-09, \"jax_sharpe\": -11.772774536397659, \"return\": -0.9999999567305403, \"returns_over_hodl\": -0.999999956772694, \"returns_over_uniform_hodl\": -0.9999999567708837, \"sharpe\": -1.884975213816705, \"sterling\": -5.898848180685208, \"ulcer\": -0.14369952100917222}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -0.00013624071440709873, \"optuna_trial_number\": 14, \"price_ratio\": 1.0209982614877327, \"shift_exponent\": 39.759793332730524, \"step\": 14, \"test_objective\": [{\"annualised_returns\": -0.9999999999998803, \"annualised_returns_over_hodl\": -0.9999999999998805, \"annualised_returns_over_uniform_hodl\": -0.9999999999998805, \"calmar\": -1.0000000430168712, \"daily_log_sharpe\": -1.6116984329198079, \"daily_returns\": -4.6779937337003405e-05, \"fee_revenue_over_value\": 2.6157192192010916e-09, \"jax_sharpe\": -11.772774536397659, \"return\": -0.9999999567305403, \"returns_over_hodl\": -0.999999956772694, \"returns_over_uniform_hodl\": -0.9999999567708837, \"sharpe\": -1.884975213816705, \"sterling\": -5.898848180685208, \"ulcer\": -0.14369952100917222}], \"train_objective\": [{\"annualised_returns\": 0.0007236998854187604, \"annualised_returns_over_hodl\": 0.0018576440808879546, \"annualised_returns_over_uniform_hodl\": 0.001857644080889287, \"calmar\": 0.38289543979853696, \"daily_log_sharpe\": 0.08128124443243961, \"daily_returns\": -0.00041828686973826993, \"fee_revenue_over_value\": 2.1495377959234005e-08, \"jax_sharpe\": 0.08566609630028973, \"return\": 0.00013002773960679725, \"returns_over_hodl\": 0.00033360937564541615, \"returns_over_uniform_hodl\": 0.0003336093756456382, \"sharpe\": 0.08576064277857685, \"sterling\": 0.4229952467186126, \"ulcer\": -0.0007596662594185313}], \"train_return\": 0.00013002773960679725, \"train_returns_over_hodl\": 0.00033360937564541615, \"train_sharpe\": 0.08566609630028973, \"validation_return\": -8.438687291956182e-05, \"validation_returns_over_hodl\": 7.032108863258557e-05, \"validation_sharpe\": -0.26251928331900426}, {\"centeredness_margin\": 0.534816964042462, \"continuous_test_metrics\": [{\"annualised_returns\": 0.0053140887878768694, \"annualised_returns_over_hodl\": 0.0035330550041474673, \"annualised_returns_over_uniform_hodl\": 0.0036698285035110523, \"calmar\": 1.28569135124944, \"daily_log_sharpe\": 0.6285295796667285, \"daily_returns\": -4.849816404114671e-05, \"fee_revenue_over_value\": 2.317922780966333e-08, \"jax_sharpe\": 0.5870917563343523, \"return\": 0.0030248402509460703, \"returns_over_hodl\": 0.0020118233725410217, \"returns_over_uniform_hodl\": 0.0020896448395690825, \"sharpe\": 0.6329654627898057, \"sterling\": 3.6209885979905083, \"ulcer\": -0.0004755698948777609}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": 6.147640318139026e-05, \"optuna_trial_number\": 15, \"price_ratio\": 1.0103016632854462, \"shift_exponent\": 1.6001296106285563e-05, \"step\": 15, \"test_objective\": [{\"annualised_returns\": 0.0053140887878768694, \"annualised_returns_over_hodl\": 0.0035330550041474673, \"annualised_returns_over_uniform_hodl\": 0.0036698285035110523, \"calmar\": 1.28569135124944, \"daily_log_sharpe\": 0.6285295796667285, \"daily_returns\": -4.849816404114671e-05, \"fee_revenue_over_value\": 2.317922780966333e-08, \"jax_sharpe\": 0.5870917563343523, \"return\": 0.0030248402509460703, \"returns_over_hodl\": 0.0020118233725410217, \"returns_over_uniform_hodl\": 0.0020896448395690825, \"sharpe\": 0.6329654627898057, \"sterling\": 3.6209885979905083, \"ulcer\": -0.0004755698948777609}], \"train_objective\": [{\"annualised_returns\": 0.002525217288763537, \"annualised_returns_over_hodl\": 0.0036612028271154617, \"annualised_returns_over_uniform_hodl\": 0.003661202827116572, \"calmar\": 1.2681141156769806, \"daily_log_sharpe\": 0.27040598068503113, \"daily_returns\": -0.00041828685263915744, \"fee_revenue_over_value\": 1.9624346080748328e-08, \"jax_sharpe\": 0.27222979488977916, \"return\": 0.0004533731471603186, \"returns_over_hodl\": 0.0006570206018277069, \"returns_over_uniform_hodl\": 0.000657020601827929, \"sharpe\": 0.27504760569589587, \"sterling\": 1.408853440521711, \"ulcer\": -0.0007384166559925903}], \"train_return\": 0.0004533731471603186, \"train_returns_over_hodl\": 0.0006570206018277069, \"train_sharpe\": 0.27222979488977916, \"validation_return\": -1.4074940065889052e-05, \"validation_returns_over_hodl\": 0.0001423413506898008, \"validation_sharpe\": -0.04511386984529566}, {\"centeredness_margin\": 0.6271372983636601, \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -Infinity, \"optuna_trial_number\": 16, \"price_ratio\": 1.017340534351543, \"shift_exponent\": 59.1318877787967, \"step\": 16, \"test_objective\": -Infinity, \"train_objective\": -Infinity, \"train_return\": -Infinity, \"train_returns_over_hodl\": -Infinity, \"train_sharpe\": -Infinity, \"validation_return\": -Infinity, \"validation_returns_over_hodl\": -Infinity, \"validation_sharpe\": -Infinity}, {\"centeredness_margin\": 0.05624495749185493, \"continuous_test_metrics\": [{\"annualised_returns\": 0.004340377455966937, \"annualised_returns_over_hodl\": 0.0026218921280118934, \"annualised_returns_over_uniform_hodl\": 0.002697709743404886, \"calmar\": 1.058187849940496, \"daily_log_sharpe\": 0.5337379843464682, \"daily_returns\": -4.683808303846063e-05, \"fee_revenue_over_value\": 6.836923196238105e-08, \"jax_sharpe\": 0.48919646351279544, \"return\": 0.0024711081972059734, \"returns_over_hodl\": 0.0014932728981795762, \"returns_over_uniform_hodl\": 0.0015364290718222762, \"sharpe\": 0.5380641070929417, \"sterling\": 2.638217887135953, \"ulcer\": -0.0006299640173647679}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -0.0001305573672417737, \"optuna_trial_number\": 17, \"price_ratio\": 1.0204050102203064, \"shift_exponent\": 3.4678646479472374e-05, \"step\": 17, \"test_objective\": [{\"annualised_returns\": 0.004340377455966937, \"annualised_returns_over_hodl\": 0.0026218921280118934, \"annualised_returns_over_uniform_hodl\": 0.002697709743404886, \"calmar\": 1.058187849940496, \"daily_log_sharpe\": 0.5337379843464682, \"daily_returns\": -4.683808303846063e-05, \"fee_revenue_over_value\": 6.836923196238105e-08, \"jax_sharpe\": 0.48919646351279544, \"return\": 0.0024711081972059734, \"returns_over_hodl\": 0.0014932728981795762, \"returns_over_uniform_hodl\": 0.0015364290718222762, \"sharpe\": 0.5380641070929417, \"sterling\": 2.638217887135953, \"ulcer\": -0.0006299640173647679}], \"train_objective\": [{\"annualised_returns\": 0.0007769905332926097, \"annualised_returns_over_hodl\": 0.0019109951136822012, \"annualised_returns_over_uniform_hodl\": 0.0019109951136822012, \"calmar\": 0.4109587512353234, \"daily_log_sharpe\": 0.08656140122088254, \"daily_returns\": -0.00042319025128732503, \"fee_revenue_over_value\": 2.1524960868392252e-08, \"jax_sharpe\": 0.0907220346205863, \"return\": 0.00013959946375408094, \"returns_over_hodl\": 0.0003431830481666065, \"returns_over_uniform_hodl\": 0.0003431830481666065, \"sharpe\": 0.09107776151288284, \"sterling\": 0.45386528116858443, \"ulcer\": -0.0007584719418487777}], \"train_return\": 0.00013959946375408094, \"train_returns_over_hodl\": 0.0003431830481666065, \"train_sharpe\": 0.0907220346205863, \"validation_return\": -8.245138650397887e-05, \"validation_returns_over_hodl\": 7.233840110743017e-05, \"validation_sharpe\": -0.2576288429466494}, {\"centeredness_margin\": 0.02618915085725712, \"continuous_test_metrics\": [{\"annualised_returns\": 0.005292692734730098, \"annualised_returns_over_hodl\": 0.003508722954396193, \"annualised_returns_over_uniform_hodl\": 0.0036484674450798504, \"calmar\": 1.3642817873350332, \"daily_log_sharpe\": 0.661567006454807, \"daily_returns\": -4.8579259647227754e-05, \"fee_revenue_over_value\": 5.876939201489565e-08, \"jax_sharpe\": 0.6230169375053684, \"return\": 0.003012675181399027, \"returns_over_hodl\": 0.0019979784304047232, \"returns_over_uniform_hodl\": 0.002077491112430385, \"sharpe\": 0.665739949829398, \"sterling\": 3.860355481417872, \"ulcer\": -0.00044212692594195466}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": 3.937557194134134e-05, \"optuna_trial_number\": 18, \"price_ratio\": 1.01105160769199, \"shift_exponent\": 3.441813935232187e-05, \"step\": 18, \"test_objective\": [{\"annualised_returns\": 0.005292692734730098, \"annualised_returns_over_hodl\": 0.003508722954396193, \"annualised_returns_over_uniform_hodl\": 0.0036484674450798504, \"calmar\": 1.3642817873350332, \"daily_log_sharpe\": 0.661567006454807, \"daily_returns\": -4.8579259647227754e-05, \"fee_revenue_over_value\": 5.876939201489565e-08, \"jax_sharpe\": 0.6230169375053684, \"return\": 0.003012675181399027, \"returns_over_hodl\": 0.0019979784304047232, \"returns_over_uniform_hodl\": 0.002077491112430385, \"sharpe\": 0.665739949829398, \"sterling\": 3.860355481417872, \"ulcer\": -0.00044212692594195466}], \"train_objective\": [{\"annualised_returns\": 0.0023744460883270424, \"annualised_returns_over_hodl\": 0.0035102607841892564, \"annualised_returns_over_uniform_hodl\": 0.0035102607841905886, \"calmar\": 1.2083105155945268, \"daily_log_sharpe\": 0.257524491322361, \"daily_returns\": -0.0004272881078812173, \"fee_revenue_over_value\": 2.2005370044795188e-08, \"jax_sharpe\": 0.2603363815412292, \"return\": 0.0004263302568363603, \"returns_over_hodl\": 0.0006299722067835134, \"returns_over_uniform_hodl\": 0.0006299722067837354, \"sharpe\": 0.2621144011599262, \"sterling\": 1.3250245735561978, \"ulcer\": -0.0007490185918167388}], \"train_return\": 0.0004263302568363603, \"train_returns_over_hodl\": 0.0006299722067835134, \"train_sharpe\": 0.2603363815412292, \"validation_return\": -2.4489305784802795e-05, \"validation_returns_over_hodl\": 0.00013275108007859693, \"validation_sharpe\": -0.07881706779011492}, {\"centeredness_margin\": 0.38734475472873575, \"continuous_test_metrics\": [{\"annualised_returns\": 0.0036597031457017426, \"annualised_returns_over_hodl\": 0.0019570368493122547, \"annualised_returns_over_uniform_hodl\": 0.0020181487227548534, \"calmar\": 0.8751314675056431, \"daily_log_sharpe\": 0.4164883637586103, \"daily_returns\": -4.6437865305643344e-05, \"fee_revenue_over_value\": 5.686097393809706e-08, \"jax_sharpe\": 0.3897927398075544, \"return\": 0.0020838838638146395, \"returns_over_hodl\": 0.0011147702279721283, \"returns_over_uniform_hodl\": 0.0011495657767675027, \"sharpe\": 0.4212319456644117, \"sterling\": 2.1260112498506376, \"ulcer\": -0.0006830336064526534}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -0.0001696741961512066, \"optuna_trial_number\": 19, \"price_ratio\": 1.0253304462525643, \"shift_exponent\": 1.208845726379127e-05, \"step\": 19, \"test_objective\": [{\"annualised_returns\": 0.0036597031457017426, \"annualised_returns_over_hodl\": 0.0019570368493122547, \"annualised_returns_over_uniform_hodl\": 0.0020181487227548534, \"calmar\": 0.8751314675056431, \"daily_log_sharpe\": 0.4164883637586103, \"daily_returns\": -4.6437865305643344e-05, \"fee_revenue_over_value\": 5.686097393809706e-08, \"jax_sharpe\": 0.3897927398075544, \"return\": 0.0020838838638146395, \"returns_over_hodl\": 0.0011147702279721283, \"returns_over_uniform_hodl\": 0.0011495657767675027, \"sharpe\": 0.4212319456644117, \"sterling\": 2.1260112498506376, \"ulcer\": -0.0006830336064526534}], \"train_objective\": [{\"annualised_returns\": 0.0004102606181257684, \"annualised_returns_over_hodl\": 0.0015438496479902586, \"annualised_returns_over_uniform_hodl\": 0.0015438496479902586, \"calmar\": 0.21507750077032106, \"daily_log_sharpe\": 0.04633078153769644, \"daily_returns\": -0.00042224874648489816, \"fee_revenue_over_value\": 2.128242650618217e-08, \"jax_sharpe\": 0.05061396930735433, \"return\": 7.372132693261868e-05, \"returns_over_hodl\": 0.000277291501509902, \"returns_over_uniform_hodl\": 0.000277291501509902, \"sharpe\": 0.05079224207273589, \"sterling\": 0.2421545282332039, \"ulcer\": -0.0007618383820661472}], \"train_return\": 7.372132693261868e-05, \"train_returns_over_hodl\": 0.000277291501509902, \"train_sharpe\": 0.05061396930735434, \"validation_return\": -9.577327957388526e-05, \"validation_returns_over_hodl\": 5.84533109946328e-05, \"validation_sharpe\": -0.29542921300769825}, {\"centeredness_margin\": 0.7373481422994814, \"continuous_test_metrics\": [{\"annualised_returns\": 0.004782093169693136, \"annualised_returns_over_hodl\": 0.003026698124901639, \"annualised_returns_over_uniform_hodl\": 0.0031387030007232752, \"calmar\": 1.1100643994894577, \"daily_log_sharpe\": 0.6013137820363557, \"daily_returns\": -4.7824391934017327e-05, \"fee_revenue_over_value\": 1.3958815046599188e-08, \"jax_sharpe\": 0.5247348398830253, \"return\": 0.002722332551617246, \"returns_over_hodl\": 0.0017236764160108997, \"returns_over_uniform_hodl\": 0.0017874191908944237, \"sharpe\": 0.6055379446010655, \"sterling\": 3.0077104151859615, \"ulcer\": -0.0005582786389718081}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -5.580956007588401e-05, \"optuna_trial_number\": 20, \"price_ratio\": 1.0140283399050525, \"shift_exponent\": 1.3260684493289513e-05, \"step\": 20, \"test_objective\": [{\"annualised_returns\": 0.004782093169693136, \"annualised_returns_over_hodl\": 0.003026698124901639, \"annualised_returns_over_uniform_hodl\": 0.0031387030007232752, \"calmar\": 1.1100643994894577, \"daily_log_sharpe\": 0.6013137820363557, \"daily_returns\": -4.7824391934017327e-05, \"fee_revenue_over_value\": 1.3958815046599188e-08, \"jax_sharpe\": 0.5247348398830253, \"return\": 0.002722332551617246, \"returns_over_hodl\": 0.0017236764160108997, \"returns_over_uniform_hodl\": 0.0017874191908944237, \"sharpe\": 0.6055379446010655, \"sterling\": 3.0077104151859615, \"ulcer\": -0.0005582786389718081}], \"train_objective\": [{\"annualised_returns\": 0.0014706620635589474, \"annualised_returns_over_hodl\": 0.002605452659913521, \"annualised_returns_over_uniform_hodl\": 0.002605452659913521, \"calmar\": 0.7794720378050325, \"daily_log_sharpe\": 0.15899699107659154, \"daily_returns\": -0.00042539115931780424, \"fee_revenue_over_value\": 1.4870108493759951e-08, \"jax_sharpe\": 0.16390578864204156, \"return\": 0.00026415416729630437, \"returns_over_hodl\": 0.000467763105462371, \"returns_over_uniform_hodl\": 0.000467763105462371, \"sharpe\": 0.1636353176151234, \"sterling\": 0.8361008211352461, \"ulcer\": -0.0007568930890868834}], \"train_return\": 0.00026415416729630437, \"train_returns_over_hodl\": 0.000467763105462371, \"train_sharpe\": 0.16390578864204156, \"validation_return\": -5.283757565455183e-05, \"validation_returns_over_hodl\": 0.00010310259159451718, \"validation_sharpe\": -0.16975069228808268}, {\"centeredness_margin\": 0.7538875716302246, \"continuous_test_metrics\": [{\"annualised_returns\": 0.005197126651665718, \"annualised_returns_over_hodl\": 0.003392477313631126, \"annualised_returns_over_uniform_hodl\": 0.0035530576669122738, \"calmar\": 1.185623301453069, \"daily_log_sharpe\": 0.5415542220766973, \"daily_returns\": -4.9147861767092814e-05, \"fee_revenue_over_value\": 1.239812894067734e-08, \"jax_sharpe\": 0.5193061199201056, \"return\": 0.00295833819081448, \"returns_over_hodl\": 0.0019318326400725727, \"returns_over_uniform_hodl\": 0.002023204784304289, \"sharpe\": 0.5465301734284792, \"sterling\": 3.273956908615545, \"ulcer\": -0.0005473226807016279}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -1.788220353519665e-05, \"optuna_trial_number\": 21, \"price_ratio\": 1.0121109456606732, \"shift_exponent\": 1.3400509094066617e-05, \"step\": 21, \"test_objective\": [{\"annualised_returns\": 0.005197126651665718, \"annualised_returns_over_hodl\": 0.003392477313631126, \"annualised_returns_over_uniform_hodl\": 0.0035530576669122738, \"calmar\": 1.185623301453069, \"daily_log_sharpe\": 0.5415542220766973, \"daily_returns\": -4.9147861767092814e-05, \"fee_revenue_over_value\": 1.239812894067734e-08, \"jax_sharpe\": 0.5193061199201056, \"return\": 0.00295833819081448, \"returns_over_hodl\": 0.0019318326400725727, \"returns_over_uniform_hodl\": 0.002023204784304289, \"sharpe\": 0.5465301734284792, \"sterling\": 3.273956908615545, \"ulcer\": -0.0005473226807016279}], \"train_objective\": [{\"annualised_returns\": 0.0018454959920195524, \"annualised_returns_over_hodl\": 0.002980711321753482, \"annualised_returns_over_uniform_hodl\": 0.002980711321753482, \"calmar\": 0.9555370822886182, \"daily_log_sharpe\": 0.20244257285434616, \"daily_returns\": -0.0004265061429431668, \"fee_revenue_over_value\": 1.3917455223831695e-08, \"jax_sharpe\": 0.20579984862091202, \"return\": 0.00033142938142627365, \"returns_over_hodl\": 0.0005350520138101, \"returns_over_uniform_hodl\": 0.0005350520138101, \"sharpe\": 0.20699753495730236, \"sterling\": 1.0334889780710468, \"ulcer\": -0.0007543554274812417}], \"train_return\": 0.00033142938142627365, \"train_returns_over_hodl\": 0.0005350520138101, \"train_sharpe\": 0.20579984862091202, \"validation_return\": -4.597954498597456e-05, \"validation_returns_over_hodl\": 0.00011184782116058223, \"validation_sharpe\": -0.14856773787181476}, {\"centeredness_margin\": 0.7799016066430687, \"continuous_test_metrics\": [{\"annualised_returns\": 0.003925107046516718, \"annualised_returns_over_hodl\": 0.0020591653590120718, \"annualised_returns_over_uniform_hodl\": 0.0022831185372500507, \"calmar\": 0.7731006064259414, \"daily_log_sharpe\": 0.4252770611423177, \"daily_returns\": -5.0884357319968274e-05, \"fee_revenue_over_value\": 1.5853270623910297e-08, \"jax_sharpe\": 0.3664790613725467, \"return\": 0.002234881175519954, \"returns_over_hodl\": 0.0011729190959048896, \"returns_over_uniform_hodl\": 0.0013004223023354022, \"sharpe\": 0.4303442461978311, \"sterling\": 2.100490720181369, \"ulcer\": -0.0006559718181975635}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -8.052585025898063e-05, \"optuna_trial_number\": 22, \"price_ratio\": 1.012580616461133, \"shift_exponent\": 3.6058655726715554e-05, \"step\": 22, \"test_objective\": [{\"annualised_returns\": 0.003925107046516718, \"annualised_returns_over_hodl\": 0.0020591653590120718, \"annualised_returns_over_uniform_hodl\": 0.0022831185372500507, \"calmar\": 0.7731006064259414, \"daily_log_sharpe\": 0.4252770611423177, \"daily_returns\": -5.0884357319968274e-05, \"fee_revenue_over_value\": 1.5853270623910297e-08, \"jax_sharpe\": 0.3664790613725467, \"return\": 0.002234881175519954, \"returns_over_hodl\": 0.0011729190959048896, \"returns_over_uniform_hodl\": 0.0013004223023354022, \"sharpe\": 0.4303442461978311, \"sterling\": 2.100490720181369, \"ulcer\": -0.0006559718181975635}], \"train_objective\": [{\"annualised_returns\": 0.0012684766867847586, \"annualised_returns_over_hodl\": 0.0024030381820063784, \"annualised_returns_over_uniform_hodl\": 0.0024030381820063784, \"calmar\": 0.6621768820311253, \"daily_log_sharpe\": 0.13668661093081927, \"daily_returns\": -0.0004262016082916001, \"fee_revenue_over_value\": 1.2184330301164086e-08, \"jax_sharpe\": 0.14104596748887735, \"return\": 0.0002278573490410718, \"returns_over_hodl\": 0.00043145889880236155, \"returns_over_uniform_hodl\": 0.00043145889880236155, \"sharpe\": 0.14134368931719468, \"sterling\": 0.7022914109257686, \"ulcer\": -0.000771026240043555}], \"train_return\": 0.0002278573490410718, \"train_returns_over_hodl\": 0.00043145889880236155, \"train_sharpe\": 0.14104596748887735, \"validation_return\": -6.975919262730557e-05, \"validation_returns_over_hodl\": 9.08768992251563e-05, \"validation_sharpe\": -0.22056278078680802}, {\"centeredness_margin\": 0.5917285669570868, \"continuous_test_metrics\": [{\"annualised_returns\": 0.004244596569223713, \"annualised_returns_over_hodl\": 0.0025189582687681344, \"annualised_returns_over_uniform_hodl\": 0.0026020855128845444, \"calmar\": 0.9737097663128215, \"daily_log_sharpe\": 0.45520422117382675, \"daily_returns\": -4.703778857915465e-05, \"fee_revenue_over_value\": 3.0738572170540334e-08, \"jax_sharpe\": 0.4279323949028855, \"return\": 0.0024166268643539546, \"returns_over_hodl\": 0.0014346796304323117, \"returns_over_uniform_hodl\": 0.001481998536009721, \"sharpe\": 0.46016378782042083, \"sterling\": 2.592253491759179, \"ulcer\": -0.0005813242790474491}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -4.92481134893169e-05, \"optuna_trial_number\": 23, \"price_ratio\": 1.0143081880034814, \"shift_exponent\": 2.595676316182535e-05, \"step\": 23, \"test_objective\": [{\"annualised_returns\": 0.004244596569223713, \"annualised_returns_over_hodl\": 0.0025189582687681344, \"annualised_returns_over_uniform_hodl\": 0.0026020855128845444, \"calmar\": 0.9737097663128215, \"daily_log_sharpe\": 0.45520422117382675, \"daily_returns\": -4.703778857915465e-05, \"fee_revenue_over_value\": 3.0738572170540334e-08, \"jax_sharpe\": 0.4279323949028855, \"return\": 0.0024166268643539546, \"returns_over_hodl\": 0.0014346796304323117, \"returns_over_uniform_hodl\": 0.001481998536009721, \"sharpe\": 0.46016378782042083, \"sterling\": 2.592253491759179, \"ulcer\": -0.0005813242790474491}], \"train_objective\": [{\"annualised_returns\": 0.001467992634155646, \"annualised_returns_over_hodl\": 0.0026027802057158045, \"annualised_returns_over_uniform_hodl\": 0.0026027802057158045, \"calmar\": 0.7795539064928655, \"daily_log_sharpe\": 0.16000491131020933, \"daily_returns\": -0.00042525341283996453, \"fee_revenue_over_value\": 1.9822780163937054e-08, \"jax_sharpe\": 0.16303159095552494, \"return\": 0.0002636749838944574, \"returns_over_hodl\": 0.0004672838245203259, \"returns_over_uniform_hodl\": 0.0004672838245203259, \"sharpe\": 0.16459569597398324, \"sterling\": 0.8459313995310164, \"ulcer\": -0.0007453486046716789}], \"train_return\": 0.0002636749838944574, \"train_returns_over_hodl\": 0.0004672838245203259, \"train_sharpe\": 0.16303159095552497, \"validation_return\": -5.0968287999331174e-05, \"validation_returns_over_hodl\": 0.00010275795415592981, \"validation_sharpe\": -0.16625019117402895}, {\"centeredness_margin\": 0.7162404842489385, \"continuous_test_metrics\": [{\"annualised_returns\": 0.00153424384996792, \"annualised_returns_over_hodl\": -0.0002669075566078538, \"annualised_returns_over_uniform_hodl\": -0.00010383423822457605, \"calmar\": 0.26335448582254956, \"daily_log_sharpe\": 0.1576348274256455, \"daily_returns\": -4.923211709403038e-05, \"fee_revenue_over_value\": 3.288830519231097e-08, \"jax_sharpe\": 0.1310814615132343, \"return\": 0.0008740176220431994, \"returns_over_hodl\": -0.00015210896899386928, \"returns_over_uniform_hodl\": -5.9172415814989776e-05, \"sharpe\": 0.16372722808455661, \"sterling\": 0.6969510405496425, \"ulcer\": -0.0008137795385833267}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -0.00024298164924708987, \"optuna_trial_number\": 24, \"price_ratio\": 1.0139447466678104, \"shift_exponent\": 0.00013324512500718927, \"step\": 24, \"test_objective\": [{\"annualised_returns\": 0.00153424384996792, \"annualised_returns_over_hodl\": -0.0002669075566078538, \"annualised_returns_over_uniform_hodl\": -0.00010383423822457605, \"calmar\": 0.26335448582254956, \"daily_log_sharpe\": 0.1576348274256455, \"daily_returns\": -4.923211709403038e-05, \"fee_revenue_over_value\": 3.288830519231097e-08, \"jax_sharpe\": 0.1310814615132343, \"return\": 0.0008740176220431994, \"returns_over_hodl\": -0.00015210896899386928, \"returns_over_uniform_hodl\": -5.9172415814989776e-05, \"sharpe\": 0.16372722808455661, \"sterling\": 0.6969510405496425, \"ulcer\": -0.0008137795385833267}], \"train_objective\": [{\"annualised_returns\": -0.00026150120856704984, \"annualised_returns_over_hodl\": 0.0008713266317457169, \"annualised_returns_over_uniform_hodl\": 0.0008713266317470492, \"calmar\": -0.1343311459936119, \"daily_log_sharpe\": -0.02899715208795857, \"daily_returns\": -0.00041828686971696335, \"fee_revenue_over_value\": 1.4784614126879526e-08, \"jax_sharpe\": -0.024033254443118303, \"return\": -4.700311726357764e-05, \"returns_over_hodl\": 0.00015654248322904962, \"returns_over_uniform_hodl\": 0.00015654248322927167, \"sharpe\": -0.02444183573880608, \"sterling\": -0.14524777126627783, \"ulcer\": -0.0007825017723650301}], \"train_return\": -4.700311726357764e-05, \"train_returns_over_hodl\": 0.00015654248322904962, \"train_sharpe\": -0.024033254443118303, \"validation_return\": -5.561012968779977e-05, \"validation_returns_over_hodl\": 0.00010541038246958401, \"validation_sharpe\": -0.17290456888609157}, {\"centeredness_margin\": 0.04079298187588158, \"continuous_test_metrics\": [{\"annualised_returns\": 0.0014319915938789674, \"annualised_returns_over_hodl\": -0.0003445642504747992, \"annualised_returns_over_uniform_hodl\": -0.00020591925372126507, \"calmar\": 0.3689592777014508, \"daily_log_sharpe\": 0.19317868733721943, \"daily_returns\": -4.856385278199563e-05, \"fee_revenue_over_value\": 6.966838753928606e-08, \"jax_sharpe\": 0.14708915213685245, \"return\": 0.0008157851712671249, \"returns_over_hodl\": -0.0001963683198586974, \"returns_over_uniform_hodl\": -0.00011735057210249256, \"sharpe\": 0.19819042322149732, \"sterling\": 0.8907234181056677, \"ulcer\": -0.0005643576907442256}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": 3.787024112540949e-05, \"optuna_trial_number\": 25, \"price_ratio\": 1.0110966317923087, \"shift_exponent\": 0.012268486459997585, \"step\": 25, \"test_objective\": [{\"annualised_returns\": 0.0014319915938789674, \"annualised_returns_over_hodl\": -0.0003445642504747992, \"annualised_returns_over_uniform_hodl\": -0.00020591925372126507, \"calmar\": 0.3689592777014508, \"daily_log_sharpe\": 0.19317868733721943, \"daily_returns\": -4.856385278199563e-05, \"fee_revenue_over_value\": 6.966838753928606e-08, \"jax_sharpe\": 0.14708915213685245, \"return\": 0.0008157851712671249, \"returns_over_hodl\": -0.0001963683198586974, \"returns_over_uniform_hodl\": -0.00011735057210249256, \"sharpe\": 0.19819042322149732, \"sterling\": 0.8907234181056677, \"ulcer\": -0.0005643576907442256}], \"train_objective\": [{\"annualised_returns\": 0.0023602979247219213, \"annualised_returns_over_hodl\": 0.003496096588959219, \"annualised_returns_over_uniform_hodl\": 0.003496096588959219, \"calmar\": 1.202004695022952, \"daily_log_sharpe\": 0.257143437676534, \"daily_returns\": -0.000427251835689078, \"fee_revenue_over_value\": 2.200299257498476e-08, \"jax_sharpe\": 0.2573521927741892, \"return\": 0.0004237924176924146, \"returns_over_hodl\": 0.0006274338510494637, \"returns_over_uniform_hodl\": 0.0006274338510494637, \"sharpe\": 0.261705445306399, \"sterling\": 1.3197588687789321, \"ulcer\": -0.000745039813924573}], \"train_return\": 0.0004237924176924146, \"train_returns_over_hodl\": 0.0006274338510494637, \"train_sharpe\": 0.2573521927741892, \"validation_return\": -2.500216041989578e-05, \"validation_returns_over_hodl\": 0.00013221653881045903, \"validation_sharpe\": -0.07997015278183044}, {\"centeredness_margin\": 0.06566039870454846, \"continuous_test_metrics\": [{\"annualised_returns\": 0.003975732548218147, \"annualised_returns_over_hodl\": 0.002183539292742065, \"annualised_returns_over_uniform_hodl\": 0.0023336612374638133, \"calmar\": 0.9738436823587432, \"daily_log_sharpe\": 0.5523495835948069, \"daily_returns\": -4.886761643057823e-05, \"fee_revenue_over_value\": 6.623548374621087e-08, \"jax_sharpe\": 0.4486266401713105, \"return\": 0.0022636817962884415, \"returns_over_hodl\": 0.0012437303893979568, \"returns_over_uniform_hodl\": 0.0013291960701213856, \"sharpe\": 0.5563109263262898, \"sterling\": 2.582982781401223, \"ulcer\": -0.00048682058156571665}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": 6.755312619437515e-05, \"optuna_trial_number\": 26, \"price_ratio\": 1.0102715408891025, \"shift_exponent\": 0.000390822635876792, \"step\": 26, \"test_objective\": [{\"annualised_returns\": 0.003975732548218147, \"annualised_returns_over_hodl\": 0.002183539292742065, \"annualised_returns_over_uniform_hodl\": 0.0023336612374638133, \"calmar\": 0.9738436823587432, \"daily_log_sharpe\": 0.5523495835948069, \"daily_returns\": -4.886761643057823e-05, \"fee_revenue_over_value\": 6.623548374621087e-08, \"jax_sharpe\": 0.4486266401713105, \"return\": 0.0022636817962884415, \"returns_over_hodl\": 0.0012437303893979568, \"returns_over_uniform_hodl\": 0.0013291960701213856, \"sharpe\": 0.5563109263262898, \"sterling\": 2.582982781401223, \"ulcer\": -0.00048682058156571665}], \"train_objective\": [{\"annualised_returns\": 0.0026393063509124737, \"annualised_returns_over_hodl\": 0.003775421166337223, \"annualised_returns_over_uniform_hodl\": 0.003775421166337223, \"calmar\": 1.3246540581694939, \"daily_log_sharpe\": 0.2744903859854288, \"daily_returns\": -0.0004182868696941536, \"fee_revenue_over_value\": 2.20466638062847e-08, \"jax_sharpe\": 0.2764078998497612, \"return\": 0.0004738343737722417, \"returns_over_hodl\": 0.0006774859934282063, \"returns_over_uniform_hodl\": 0.0006774859934282063, \"sharpe\": 0.27925577149274183, \"sterling\": 1.4647349508895144, \"ulcer\": -0.0007450453040258635}], \"train_return\": 0.0004738343737722417, \"train_returns_over_hodl\": 0.0006774859934282063, \"train_sharpe\": 0.2764078998497612, \"validation_return\": -1.4889615703173043e-05, \"validation_returns_over_hodl\": 0.0001427566683709358, \"validation_sharpe\": -0.04840182128056577}, {\"centeredness_margin\": 0.3397766488086633, \"continuous_test_metrics\": [{\"annualised_returns\": 0.0009744417279229367, \"annualised_returns_over_hodl\": -0.0006617701861841851, \"annualised_returns_over_uniform_hodl\": -0.0006627207654253953, \"calmar\": 0.18957347981897052, \"daily_log_sharpe\": 0.15353718298347274, \"daily_returns\": -4.474293306456692e-05, \"fee_revenue_over_value\": 5.4010352285153336e-08, \"jax_sharpe\": 0.10157867859092669, \"return\": 0.0005551801248719901, \"returns_over_hodl\": -0.0003771707843120975, \"returns_over_uniform_hodl\": -0.0003777126368346151, \"sharpe\": 0.15819158743554515, \"sterling\": 0.542293299858641, \"ulcer\": -0.0006057439504529188}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": 3.8043081162841833e-06, \"optuna_trial_number\": 27, \"price_ratio\": 1.010527397112252, \"shift_exponent\": 0.0002502943906104179, \"step\": 27, \"test_objective\": [{\"annualised_returns\": 0.0009744417279229367, \"annualised_returns_over_hodl\": -0.0006617701861841851, \"annualised_returns_over_uniform_hodl\": -0.0006627207654253953, \"calmar\": 0.18957347981897052, \"daily_log_sharpe\": 0.15353718298347274, \"daily_returns\": -4.474293306456692e-05, \"fee_revenue_over_value\": 5.4010352285153336e-08, \"jax_sharpe\": 0.10157867859092669, \"return\": 0.0005551801248719901, \"returns_over_hodl\": -0.0003771707843120975, \"returns_over_uniform_hodl\": -0.0003777126368346151, \"sharpe\": 0.15819158743554515, \"sterling\": 0.542293299858641, \"ulcer\": -0.0006057439504529188}], \"train_objective\": [{\"annualised_returns\": 0.00147521181771193, \"annualised_returns_over_hodl\": 0.002610007569504136, \"annualised_returns_over_uniform_hodl\": 0.002610007569504136, \"calmar\": 0.7439174148272298, \"daily_log_sharpe\": 0.17557848891176794, \"daily_returns\": -0.0004277332400712069, \"fee_revenue_over_value\": 2.0630878263721186e-08, \"jax_sharpe\": 0.17986839438856753, \"return\": 0.00026497088124433077, \"returns_over_hodl\": 0.00046857998565696946, \"returns_over_uniform_hodl\": 0.00046857998565696946, \"sharpe\": 0.17978200839402353, \"sterling\": 0.8735601237399009, \"ulcer\": -0.000680586846793901}], \"train_return\": 0.00026497088124433077, \"train_returns_over_hodl\": 0.00046857998565696946, \"train_sharpe\": 0.1798683943885675, \"validation_return\": -4.542159874554308e-06, \"validation_returns_over_hodl\": 0.0001393041549961893, \"validation_sharpe\": -0.0139899694319445}, {\"centeredness_margin\": 0.39637713313093076, \"continuous_test_metrics\": [{\"annualised_returns\": 0.0016311388299181662, \"annualised_returns_over_hodl\": -5.911620740606249e-05, \"annualised_returns_over_uniform_hodl\": -7.0977366731783675e-06, \"calmar\": 0.3046783234524405, \"daily_log_sharpe\": 0.17962254365405236, \"daily_returns\": -4.619239327115717e-05, \"fee_revenue_over_value\": 4.754278972408896e-08, \"jax_sharpe\": 0.1492555877603308, \"return\": 0.0009291967552280678, \"returns_over_hodl\": -3.3688455961744523e-05, \"returns_over_uniform_hodl\": -4.044730281260733e-06, \"sharpe\": 0.18532124881643003, \"sterling\": 0.8646606260342938, \"ulcer\": -0.0006639807167283642}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -1.1320370173364958e-05, \"optuna_trial_number\": 28, \"price_ratio\": 1.0117615377376021, \"shift_exponent\": 0.00014637161538734857, \"step\": 28, \"test_objective\": [{\"annualised_returns\": 0.0016311388299181662, \"annualised_returns_over_hodl\": -5.911620740606249e-05, \"annualised_returns_over_uniform_hodl\": -7.0977366731783675e-06, \"calmar\": 0.3046783234524405, \"daily_log_sharpe\": 0.17962254365405236, \"daily_returns\": -4.619239327115717e-05, \"fee_revenue_over_value\": 4.754278972408896e-08, \"jax_sharpe\": 0.1492555877603308, \"return\": 0.0009291967552280678, \"returns_over_hodl\": -3.3688455961744523e-05, \"returns_over_uniform_hodl\": -4.044730281260733e-06, \"sharpe\": 0.18532124881643003, \"sterling\": 0.8646606260342938, \"ulcer\": -0.0006639807167283642}], \"train_objective\": [{\"annualised_returns\": 0.00160830553473712, \"annualised_returns_over_hodl\": 0.002743252098234672, \"annualised_returns_over_uniform_hodl\": 0.002743252098234672, \"calmar\": 0.8275917637126533, \"daily_log_sharpe\": 0.18248471786448806, \"daily_returns\": -0.00042674850702263103, \"fee_revenue_over_value\": 2.0572327696605314e-08, \"jax_sharpe\": 0.18732558034969485, \"return\": 0.00028886082443047023, \"returns_over_hodl\": 0.0004924747917645078, \"returns_over_uniform_hodl\": 0.0004924747917645078, \"sharpe\": 0.18689465010752546, \"sterling\": 0.9350442431182815, \"ulcer\": -0.0007167075169005768}], \"train_return\": 0.00028886082443047023, \"train_returns_over_hodl\": 0.0004924747917645078, \"train_sharpe\": 0.18732558034969485, \"validation_return\": -2.4802009538138492e-05, \"validation_returns_over_hodl\": 0.00012479396871523107, \"validation_sharpe\": -0.08362609613215669}, {\"centeredness_margin\": 0.05923328143566958, \"continuous_test_metrics\": [{\"annualised_returns\": 0.003453705988319733, \"annualised_returns_over_hodl\": 0.0017170190898099236, \"annualised_returns_over_uniform_hodl\": 0.001812488487881403, \"calmar\": 0.8609350999606568, \"daily_log_sharpe\": 0.43435552870562766, \"daily_returns\": -4.737673597317784e-05, \"fee_revenue_over_value\": 6.903587328110494e-08, \"jax_sharpe\": 0.3779542984414848, \"return\": 0.0019666732311560686, \"returns_over_hodl\": 0.0009781013767886648, \"returns_over_uniform_hodl\": 0.0010324644283872253, \"sharpe\": 0.4386956978651518, \"sterling\": 2.140723853931127, \"ulcer\": -0.0005945912247394703}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -7.790305874251502e-05, \"optuna_trial_number\": 29, \"price_ratio\": 1.0161718778721502, \"shift_exponent\": 0.2117870592058916, \"step\": 29, \"test_objective\": [{\"annualised_returns\": 0.003453705988319733, \"annualised_returns_over_hodl\": 0.0017170190898099236, \"annualised_returns_over_uniform_hodl\": 0.001812488487881403, \"calmar\": 0.8609350999606568, \"daily_log_sharpe\": 0.43435552870562766, \"daily_returns\": -4.737673597317784e-05, \"fee_revenue_over_value\": 6.903587328110494e-08, \"jax_sharpe\": 0.3779542984414848, \"return\": 0.0019666732311560686, \"returns_over_hodl\": 0.0009781013767886648, \"returns_over_uniform_hodl\": 0.0010324644283872253, \"sharpe\": 0.4386956978651518, \"sterling\": 2.140723853931127, \"ulcer\": -0.0005945912247394703}], \"train_objective\": [{\"annualised_returns\": 0.001270837315665574, \"annualised_returns_over_hodl\": 0.002405401485772396, \"annualised_returns_over_uniform_hodl\": 0.002405401485772396, \"calmar\": 0.6817496172851633, \"daily_log_sharpe\": 0.13741577125751217, \"daily_returns\": -0.0004182868782966659, \"fee_revenue_over_value\": 2.1739075278199572e-08, \"jax_sharpe\": 0.1411313094218755, \"return\": 0.0002282811696550091, \"returns_over_hodl\": 0.00043188280568706716, \"returns_over_uniform_hodl\": 0.00043188280568706716, \"sharpe\": 0.14205380379308527, \"sterling\": 0.731295592794189, \"ulcer\": -0.0007555632081561704}], \"train_return\": 0.0002282811696550091, \"train_returns_over_hodl\": 0.00043188280568706716, \"train_sharpe\": 0.1411313094218755, \"validation_return\": -6.452097496012499e-05, \"validation_returns_over_hodl\": 9.102686769324464e-05, \"validation_sharpe\": -0.20455153325072056}, {\"centeredness_margin\": 0.060439804670370745, \"continuous_test_metrics\": [{\"annualised_returns\": 0.002801552121672879, \"annualised_returns_over_hodl\": 0.0011194031944683491, \"annualised_returns_over_uniform_hodl\": 0.0011614012637035653, \"calmar\": 0.6469033678721214, \"daily_log_sharpe\": 0.303500038326635, \"daily_returns\": -4.591686607268633e-05, \"fee_revenue_over_value\": 6.462497504999784e-08, \"jax_sharpe\": 0.280258353208181, \"return\": 0.0015955349536906915, \"returns_over_hodl\": 0.0006377508549744171, \"returns_over_uniform_hodl\": 0.0006616721910197576, \"sharpe\": 0.3086055846828765, \"sterling\": 1.4971933375788202, \"ulcer\": -0.0008012003479584448}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -0.00025390615978918566, \"optuna_trial_number\": 30, \"price_ratio\": 1.0459018055123313, \"shift_exponent\": 2.411910430988103, \"step\": 30, \"test_objective\": [{\"annualised_returns\": 0.002801552121672879, \"annualised_returns_over_hodl\": 0.0011194031944683491, \"annualised_returns_over_uniform_hodl\": 0.0011614012637035653, \"calmar\": 0.6469033678721214, \"daily_log_sharpe\": 0.303500038326635, \"daily_returns\": -4.591686607268633e-05, \"fee_revenue_over_value\": 6.462497504999784e-08, \"jax_sharpe\": 0.280258353208181, \"return\": 0.0015955349536906915, \"returns_over_hodl\": 0.0006377508549744171, \"returns_over_uniform_hodl\": 0.0006616721910197576, \"sharpe\": 0.3086055846828765, \"sterling\": 1.4971933375788202, \"ulcer\": -0.0008012003479584448}], \"train_objective\": [{\"annualised_returns\": -0.00034152333147152714, \"annualised_returns_over_hodl\": 0.0007912138338408425, \"annualised_returns_over_uniform_hodl\": 0.0007912138338408425, \"calmar\": -0.17500465352084887, \"daily_log_sharpe\": -0.0388980084280256, \"daily_returns\": -0.0004205006410805963, \"fee_revenue_over_value\": 2.0339901538702867e-08, \"jax_sharpe\": -0.03439850173956965, \"return\": -6.138858122972657e-05, \"returns_over_hodl\": 0.00014215409102735777, \"returns_over_uniform_hodl\": 0.00014215409102735777, \"sharpe\": -0.034464594324305924, \"sterling\": -0.20496280309634524, \"ulcer\": -0.0007727658041891594}], \"train_return\": -6.138858122972657e-05, \"train_returns_over_hodl\": 0.00014215409102735777, \"train_sharpe\": -0.03439850173956965, \"validation_return\": -0.0001200438756556732, \"validation_returns_over_hodl\": 3.313694906892373e-05, \"validation_sharpe\": -0.3616501260938264}, {\"centeredness_margin\": 0.03713493968302037, \"continuous_test_metrics\": [{\"annualised_returns\": 0.005549580158815237, \"annualised_returns_over_hodl\": 0.0037541077319436233, \"annualised_returns_over_uniform_hodl\": 0.0039049347121276057, \"calmar\": 1.442804594841702, \"daily_log_sharpe\": 0.7032212890779918, \"daily_returns\": -4.888042175697694e-05, \"fee_revenue_over_value\": 5.623706226762951e-08, \"jax_sharpe\": 0.6619125397975072, \"return\": 0.0031587252934024423, \"returns_over_hodl\": 0.002137595813663795, \"returns_over_uniform_hodl\": 0.0022234050509422065, \"sharpe\": 0.7072989940818653, \"sterling\": 4.187262235716605, \"ulcer\": -0.0004178507530500047}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": 6.880483519998426e-05, \"optuna_trial_number\": 31, \"price_ratio\": 1.0102394435218087, \"shift_exponent\": 2.2665545348679854e-05, \"step\": 31, \"test_objective\": [{\"annualised_returns\": 0.005549580158815237, \"annualised_returns_over_hodl\": 0.0037541077319436233, \"annualised_returns_over_uniform_hodl\": 0.0039049347121276057, \"calmar\": 1.442804594841702, \"daily_log_sharpe\": 0.7032212890779918, \"daily_returns\": -4.888042175697694e-05, \"fee_revenue_over_value\": 5.623706226762951e-08, \"jax_sharpe\": 0.6619125397975072, \"return\": 0.0031587252934024423, \"returns_over_hodl\": 0.002137595813663795, \"returns_over_uniform_hodl\": 0.0022234050509422065, \"sharpe\": 0.7072989940818653, \"sterling\": 4.187262235716605, \"ulcer\": -0.0004178507530500047}], \"train_objective\": [{\"annualised_returns\": 0.0026510725367892007, \"annualised_returns_over_hodl\": 0.0037872006847634587, \"annualised_returns_over_uniform_hodl\": 0.0037872006847634587, \"calmar\": 1.3297487089491116, \"daily_log_sharpe\": 0.2783135636778685, \"daily_returns\": -0.00042799717640505474, \"fee_revenue_over_value\": 2.2048367093862865e-08, \"jax_sharpe\": 0.28445382649925594, \"return\": 0.0004759444638764432, \"returns_over_hodl\": 0.0006795965130521608, \"returns_over_uniform_hodl\": 0.0006795965130521608, \"sharpe\": 0.2830543866773521, \"sterling\": 1.4682642788410833, \"ulcer\": -0.0007544569784971218}], \"train_return\": 0.0004759444638764432, \"train_returns_over_hodl\": 0.0006795965130521608, \"train_sharpe\": 0.28445382649925594, \"validation_return\": -1.4463214276116965e-05, \"validation_returns_over_hodl\": 0.00014320109528020986, \"validation_sharpe\": -0.04634676013012011}, {\"centeredness_margin\": 0.12764043674062714, \"continuous_test_metrics\": [{\"annualised_returns\": 0.003712286907352702, \"annualised_returns_over_hodl\": 0.0019267647721501469, \"annualised_returns_over_uniform_hodl\": 0.0020706464800495095, \"calmar\": 0.8738981726210676, \"daily_log_sharpe\": 0.39349875858477007, \"daily_returns\": -4.869823927946053e-05, \"fee_revenue_over_value\": 6.582495294841682e-08, \"jax_sharpe\": 0.3601093380239897, \"return\": 0.0021138019196078606, \"returns_over_hodl\": 0.0010975337379033334, \"returns_over_uniform_hodl\": 0.001179455937709406, \"sharpe\": 0.39866766599678544, \"sterling\": 2.251227932570609, \"ulcer\": -0.0005538215095634818}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": 5.1003826411924474e-05, \"optuna_trial_number\": 32, \"price_ratio\": 1.0107157848439716, \"shift_exponent\": 0.00038617580664964475, \"step\": 32, \"test_objective\": [{\"annualised_returns\": 0.003712286907352702, \"annualised_returns_over_hodl\": 0.0019267647721501469, \"annualised_returns_over_uniform_hodl\": 0.0020706464800495095, \"calmar\": 0.8738981726210676, \"daily_log_sharpe\": 0.39349875858477007, \"daily_returns\": -4.869823927946053e-05, \"fee_revenue_over_value\": 6.582495294841682e-08, \"jax_sharpe\": 0.3601093380239897, \"return\": 0.0021138019196078606, \"returns_over_hodl\": 0.0010975337379033334, \"returns_over_uniform_hodl\": 0.001179455937709406, \"sharpe\": 0.39866766599678544, \"sterling\": 2.251227932570609, \"ulcer\": -0.0005538215095634818}], \"train_objective\": [{\"annualised_returns\": 0.0024837388946323813, \"annualised_returns_over_hodl\": 0.0036196774328138837, \"annualised_returns_over_uniform_hodl\": 0.003619677432814994, \"calmar\": 1.2567089342579687, \"daily_log_sharpe\": 0.2664576899182112, \"daily_returns\": -0.00042756826848956784, \"fee_revenue_over_value\": 2.2023123365450877e-08, \"jax_sharpe\": 0.26838888993890536, \"return\": 0.0004459337588433865, \"returns_over_hodl\": 0.0006495796991847769, \"returns_over_uniform_hodl\": 0.0006495796991849989, \"sharpe\": 0.27109066415886507, \"sterling\": 1.3818759254872135, \"ulcer\": -0.000746687877925331}], \"train_return\": 0.0004459337588433865, \"train_returns_over_hodl\": 0.0006495796991847769, \"train_sharpe\": 0.2683888899389054, \"validation_return\": -2.0527690301030965e-05, \"validation_returns_over_hodl\": 0.00013688019639435112, \"validation_sharpe\": -0.06622453146245177}, {\"centeredness_margin\": 0.01755190133917555, \"continuous_test_metrics\": [{\"annualised_returns\": 0.0050538322498927535, \"annualised_returns_over_hodl\": 0.0032866322919455904, \"annualised_returns_over_uniform_hodl\": 0.003409997632981776, \"calmar\": 1.3145695853158386, \"daily_log_sharpe\": 0.6158218609193377, \"daily_returns\": -4.813341911575088e-05, \"fee_revenue_over_value\": 6.426366708649339e-08, \"jax_sharpe\": 0.5830468750753665, \"return\": 0.0028768596552486425, \"returns_over_hodl\": 0.0018716021156941487, \"returns_over_uniform_hodl\": 0.0019418022172983385, \"sharpe\": 0.6201135489304256, \"sterling\": 3.594222358391935, \"ulcer\": -0.00046898935357678325}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -4.186639123987535e-06, \"optuna_trial_number\": 33, \"price_ratio\": 1.012521701210514, \"shift_exponent\": 7.529807211612141e-05, \"step\": 33, \"test_objective\": [{\"annualised_returns\": 0.0050538322498927535, \"annualised_returns_over_hodl\": 0.0032866322919455904, \"annualised_returns_over_uniform_hodl\": 0.003409997632981776, \"calmar\": 1.3145695853158386, \"daily_log_sharpe\": 0.6158218609193377, \"daily_returns\": -4.813341911575088e-05, \"fee_revenue_over_value\": 6.426366708649339e-08, \"jax_sharpe\": 0.5830468750753665, \"return\": 0.0028768596552486425, \"returns_over_hodl\": 0.0018716021156941487, \"returns_over_uniform_hodl\": 0.0019418022172983385, \"sharpe\": 0.6201135489304256, \"sterling\": 3.594222358391935, \"ulcer\": -0.00046898935357678325}], \"train_objective\": [{\"annualised_returns\": 0.0019651051467659553, \"annualised_returns_over_hodl\": 0.0031004560085221566, \"annualised_returns_over_uniform_hodl\": 0.0031004560085221566, \"calmar\": 1.021996750686056, \"daily_log_sharpe\": 0.21712753745963503, \"daily_returns\": -0.0004262385578838912, \"fee_revenue_over_value\": 2.192807505783539e-08, \"jax_sharpe\": 0.2219020516821627, \"return\": 0.0003528924937410416, \"returns_over_hodl\": 0.00055651949505231, \"returns_over_uniform_hodl\": 0.00055651949505231, \"sharpe\": 0.22165775010476566, \"sterling\": 1.1106377494800925, \"ulcer\": -0.0007473346754069304}], \"train_return\": 0.0003528924937410416, \"train_returns_over_hodl\": 0.00055651949505231, \"train_sharpe\": 0.22190205168216268, \"validation_return\": -3.9331453638546954e-05, \"validation_returns_over_hodl\": 0.00011728138721722736, \"validation_sharpe\": -0.1259202114912143}, {\"centeredness_margin\": 0.20793366613398886, \"continuous_test_metrics\": [{\"annualised_returns\": 0.005547262519520979, \"annualised_returns_over_hodl\": 0.003760123746447741, \"annualised_returns_over_uniform_hodl\": 0.0039026208634918014, \"calmar\": 1.4442539869606827, \"daily_log_sharpe\": 0.6220247243767859, \"daily_returns\": -4.865333788281044e-05, \"fee_revenue_over_value\": 4.8061490875708824e-08, \"jax_sharpe\": 0.6039671069286064, \"return\": 0.003157407700498549, \"returns_over_hodl\": 0.0021410185808126148, \"returns_over_uniform_hodl\": 0.002222088686529178, \"sharpe\": 0.6265012825914998, \"sterling\": 4.132300912410128, \"ulcer\": -0.0004405675946661247}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": 4.6614269793840424e-05, \"optuna_trial_number\": 34, \"price_ratio\": 1.010840126199725, \"shift_exponent\": 1.0136043927253024e-05, \"step\": 34, \"test_objective\": [{\"annualised_returns\": 0.005547262519520979, \"annualised_returns_over_hodl\": 0.003760123746447741, \"annualised_returns_over_uniform_hodl\": 0.0039026208634918014, \"calmar\": 1.4442539869606827, \"daily_log_sharpe\": 0.6220247243767859, \"daily_returns\": -4.865333788281044e-05, \"fee_revenue_over_value\": 4.8061490875708824e-08, \"jax_sharpe\": 0.6039671069286064, \"return\": 0.003157407700498549, \"returns_over_hodl\": 0.0021410185808126148, \"returns_over_uniform_hodl\": 0.002222088686529178, \"sharpe\": 0.6265012825914998, \"sterling\": 4.132300912410128, \"ulcer\": -0.0004405675946661247}], \"train_objective\": [{\"annualised_returns\": 0.002442480880674891, \"annualised_returns_over_hodl\": 0.0035783726684055495, \"annualised_returns_over_uniform_hodl\": 0.0035783726684055495, \"calmar\": 1.2385035086359657, \"daily_log_sharpe\": 0.26825490381378325, \"daily_returns\": -0.0004182868696987984, \"fee_revenue_over_value\": 2.201654584429279e-08, \"jax_sharpe\": 0.27152824830562566, \"return\": 0.0004385336464927114, \"returns_over_hodl\": 0.000642178080503264, \"returns_over_uniform_hodl\": 0.000642178080503264, \"sharpe\": 0.27279964582280136, \"sterling\": 1.3626882779382392, \"ulcer\": -0.0007439342779081001}], \"train_return\": 0.0004385336464927114, \"train_returns_over_hodl\": 0.000642178080503264, \"train_sharpe\": 0.2715282483056256, \"validation_return\": -2.2023145073046813e-05, \"validation_returns_over_hodl\": 0.00013532151365525102, \"validation_sharpe\": -0.07043907165185614}, {\"centeredness_margin\": 0.18253885019969876, \"continuous_test_metrics\": [{\"annualised_returns\": 0.0030690541989675246, \"annualised_returns_over_hodl\": 0.0012853953183897815, \"annualised_returns_over_uniform_hodl\": 0.0014284658229666292, \"calmar\": 0.7172291900491324, \"daily_log_sharpe\": 0.3779003404813584, \"daily_returns\": -4.8678597815575645e-05, \"fee_revenue_over_value\": 6.395395399395585e-08, \"jax_sharpe\": 0.3111119048560068, \"return\": 0.0017477819202837974, \"returns_over_hodl\": 0.0007322944301442202, \"returns_over_uniform_hodl\": 0.0008137772063283588, \"sharpe\": 0.3825080407325892, \"sterling\": 1.8756648931084423, \"ulcer\": -0.0005469221712075237}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": 4.9083635108712614e-05, \"optuna_trial_number\": 35, \"price_ratio\": 1.010769826672939, \"shift_exponent\": 0.0003170014875022116, \"step\": 35, \"test_objective\": [{\"annualised_returns\": 0.0030690541989675246, \"annualised_returns_over_hodl\": 0.0012853953183897815, \"annualised_returns_over_uniform_hodl\": 0.0014284658229666292, \"calmar\": 0.7172291900491324, \"daily_log_sharpe\": 0.3779003404813584, \"daily_returns\": -4.8678597815575645e-05, \"fee_revenue_over_value\": 6.395395399395585e-08, \"jax_sharpe\": 0.3111119048560068, \"return\": 0.0017477819202837974, \"returns_over_hodl\": 0.0007322944301442202, \"returns_over_uniform_hodl\": 0.0008137772063283588, \"sharpe\": 0.3825080407325892, \"sterling\": 1.8756648931084423, \"ulcer\": -0.0005469221712075237}], \"train_objective\": [{\"annualised_returns\": 0.002465690332946302, \"annualised_returns_over_hodl\": 0.0036016084198666753, \"annualised_returns_over_uniform_hodl\": 0.0036016084198666753, \"calmar\": 1.2487545461691338, \"daily_log_sharpe\": 0.2652142063873801, \"daily_returns\": -0.0004275220044953868, \"fee_revenue_over_value\": 2.202026400502721e-08, \"jax_sharpe\": 0.2694946710403502, \"return\": 0.0004426965667641003, \"returns_over_hodl\": 0.0006463418481583716, \"returns_over_uniform_hodl\": 0.0006463418481583716, \"sharpe\": 0.2698396306180884, \"sterling\": 1.3725109117933663, \"ulcer\": -0.0007477358194414582}], \"train_return\": 0.0004426965667641003, \"train_returns_over_hodl\": 0.0006463418481583716, \"train_sharpe\": 0.26949467104035013, \"validation_return\": -2.118187081456835e-05, \"validation_returns_over_hodl\": 0.00013619835616118792, \"validation_sharpe\": -0.06800897002618747}, {\"centeredness_margin\": 0.7167545801273484, \"continuous_test_metrics\": [{\"annualised_returns\": -0.7655144360507082, \"annualised_returns_over_hodl\": -0.7659074083669326, \"annualised_returns_over_uniform_hodl\": -0.7658979533060208, \"calmar\": -1.3580434029202966, \"daily_log_sharpe\": -2.3433536628328935, \"daily_returns\": -4.587415786029793e-05, \"fee_revenue_over_value\": 6.065562800019587e-08, \"jax_sharpe\": -6.308333415184516, \"return\": -0.5624238257209404, \"returns_over_hodl\": -0.5628418727722524, \"returns_over_uniform_hodl\": -0.5628318108613795, \"sharpe\": -2.414547275359577, \"sterling\": -6.258215242736426, \"ulcer\": -0.06958038711341538}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -0.0002568566961449692, \"optuna_trial_number\": 36, \"price_ratio\": 1.0477022961511124, \"shift_exponent\": 7.963829054799163, \"step\": 36, \"test_objective\": [{\"annualised_returns\": -0.7655144360507082, \"annualised_returns_over_hodl\": -0.7659074083669326, \"annualised_returns_over_uniform_hodl\": -0.7658979533060208, \"calmar\": -1.3580434029202966, \"daily_log_sharpe\": -2.3433536628328935, \"daily_returns\": -4.587415786029793e-05, \"fee_revenue_over_value\": 6.065562800019587e-08, \"jax_sharpe\": -6.308333415184516, \"return\": -0.5624238257209404, \"returns_over_hodl\": -0.5628418727722524, \"returns_over_uniform_hodl\": -0.5628318108613795, \"sharpe\": -2.414547275359577, \"sterling\": -6.258215242736426, \"ulcer\": -0.06958038711341538}], \"train_objective\": [{\"annualised_returns\": -0.00037053615667781425, \"annualised_returns_over_hodl\": 0.0007621681335034936, \"annualised_returns_over_uniform_hodl\": 0.0007621681335021613, \"calmar\": -0.18977589216208365, \"daily_log_sharpe\": -0.04209883387684427, \"daily_returns\": -0.00042041938888845166, \"fee_revenue_over_value\": 2.026241280189369e-08, \"jax_sharpe\": -0.037541668593456444, \"return\": -6.660440914119103e-05, \"returns_over_hodl\": 0.00013693720140750543, \"returns_over_uniform_hodl\": 0.00013693720140728338, \"sharpe\": -0.03765378656613765, \"sterling\": -0.22260348483058348, \"ulcer\": -0.0007730494278323287}], \"train_return\": -6.660440914119103e-05, \"train_returns_over_hodl\": 0.00013693720140750543, \"train_sharpe\": -0.03754166859345644, \"validation_return\": -0.0001212111725367171, \"validation_returns_over_hodl\": 3.19210322878849e-05, \"validation_sharpe\": -0.3632205534117394}, {\"centeredness_margin\": 0.10026923619101107, \"continuous_test_metrics\": [{\"annualised_returns\": 0.0018131914131431781, \"annualised_returns_over_hodl\": 6.333202469432919e-05, \"annualised_returns_over_uniform_hodl\": 0.00017465708704000882, \"calmar\": 0.45747819768107223, \"daily_log_sharpe\": 0.23839998602899917, \"daily_returns\": -4.781485465954429e-05, \"fee_revenue_over_value\": 6.902802048432257e-08, \"jax_sharpe\": 0.1932571007092214, \"return\": 0.0010328646862087787, \"returns_over_hodl\": 3.608996639581363e-05, \"returns_over_uniform_hodl\": 9.952654329925537e-05, \"sharpe\": 0.24315841177354103, \"sterling\": 1.0923705436665925, \"ulcer\": -0.0006036450630362139}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -3.531003624021034e-05, \"optuna_trial_number\": 37, \"price_ratio\": 1.0138366641191046, \"shift_exponent\": 0.005044486667651876, \"step\": 37, \"test_objective\": [{\"annualised_returns\": 0.0018131914131431781, \"annualised_returns_over_hodl\": 6.333202469432919e-05, \"annualised_returns_over_uniform_hodl\": 0.00017465708704000882, \"calmar\": 0.45747819768107223, \"daily_log_sharpe\": 0.23839998602899917, \"daily_returns\": -4.781485465954429e-05, \"fee_revenue_over_value\": 6.902802048432257e-08, \"jax_sharpe\": 0.1932571007092214, \"return\": 0.0010328646862087787, \"returns_over_hodl\": 3.608996639581363e-05, \"returns_over_uniform_hodl\": 9.952654329925537e-05, \"sharpe\": 0.24315841177354103, \"sterling\": 1.0923705436665925, \"ulcer\": -0.0006036450630362139}], \"train_objective\": [{\"annualised_returns\": 0.0016727432087593197, \"annualised_returns_over_hodl\": 0.002807762788140211, \"annualised_returns_over_uniform_hodl\": 0.002807762788140211, \"calmar\": 0.8838340876085745, \"daily_log_sharpe\": 0.18249562880929757, \"daily_returns\": -0.0004182868697812086, \"fee_revenue_over_value\": 2.185951281637059e-08, \"jax_sharpe\": 0.18636306133989408, \"return\": 0.0003004262678791836, \"returns_over_hodl\": 0.0005040425894187184, \"returns_over_uniform_hodl\": 0.0005040425894187184, \"sharpe\": 0.1870811625516593, \"sterling\": 0.9530068109112524, \"ulcer\": -0.0007536239651735832}], \"train_return\": 0.0003004262678791836, \"train_returns_over_hodl\": 0.0005040425894187184, \"train_sharpe\": 0.18636306133989408, \"validation_return\": -4.993649661222399e-05, \"validation_returns_over_hodl\": 0.00010622795847736732, \"validation_sharpe\": -0.1618573335146774}, {\"centeredness_margin\": 0.10154444412604416, \"continuous_test_metrics\": [{\"annualised_returns\": -0.0007337648574956557, \"annualised_returns_over_hodl\": -0.0025075579915646573, \"annualised_returns_over_uniform_hodl\": -0.0023681334615739402, \"calmar\": -0.15615285310518576, \"daily_log_sharpe\": -0.020363722098825934, \"daily_returns\": -4.8593463951476684e-05, \"fee_revenue_over_value\": 6.805925893037306e-08, \"jax_sharpe\": -0.06812463423896471, \"return\": -0.0004182100675601541, \"returns_over_hodl\": -0.0014297313425932767, \"returns_over_uniform_hodl\": -0.0013501952644640047, \"sharpe\": -0.015358649440981783, \"sterling\": -0.4138211245268561, \"ulcer\": -0.0006538222601764989}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": 4.076328634789739e-05, \"optuna_trial_number\": 38, \"price_ratio\": 1.0110104201389132, \"shift_exponent\": 0.0020031941669168716, \"step\": 38, \"test_objective\": [{\"annualised_returns\": -0.0007337648574956557, \"annualised_returns_over_hodl\": -0.0025075579915646573, \"annualised_returns_over_uniform_hodl\": -0.0023681334615739402, \"calmar\": -0.15615285310518576, \"daily_log_sharpe\": -0.020363722098825934, \"daily_returns\": -4.8593463951476684e-05, \"fee_revenue_over_value\": 6.805925893037306e-08, \"jax_sharpe\": -0.06812463423896471, \"return\": -0.0004182100675601541, \"returns_over_hodl\": -0.0014297313425932767, \"returns_over_uniform_hodl\": -0.0013501952644640047, \"sharpe\": -0.015358649440981783, \"sterling\": -0.4138211245268561, \"ulcer\": -0.0006538222601764989}], \"train_objective\": [{\"annualised_returns\": 0.002387489330532011, \"annualised_returns_over_hodl\": 0.00352331880600798, \"annualised_returns_over_uniform_hodl\": 0.0035233188060093124, \"calmar\": 1.214115516501658, \"daily_log_sharpe\": 0.25162525286710985, \"daily_returns\": -0.000427321549218856, \"fee_revenue_over_value\": 2.2007545493836164e-08, \"jax_sharpe\": 0.25348335560121427, \"return\": 0.0004286698737134831, \"returns_over_hodl\": 0.0006323122999019049, \"returns_over_uniform_hodl\": 0.0006323122999021269, \"sharpe\": 0.2563370368433179, \"sterling\": 1.3339835586799673, \"ulcer\": -0.0007463977135836316}], \"train_return\": 0.0004286698737134831, \"train_returns_over_hodl\": 0.0006323122999019049, \"train_sharpe\": 0.2534833556012142, \"validation_return\": -2.401650208305739e-05, \"validation_returns_over_hodl\": 0.00013324387773083757, \"validation_sharpe\": -0.07847385925291371}, {\"centeredness_margin\": 0.05017472797500756, \"continuous_test_metrics\": [{\"annualised_returns\": 0.0033375458069369035, \"annualised_returns_over_hodl\": 0.0016141491350913917, \"annualised_returns_over_uniform_hodl\": 0.0016965182944581603, \"calmar\": 0.8209576554494095, \"daily_log_sharpe\": 0.3943935289479762, \"daily_returns\": -4.7019137576601266e-05, \"fee_revenue_over_value\": 6.834879561398725e-08, \"jax_sharpe\": 0.3627373325037736, \"return\": 0.0019005745110411976, \"returns_over_hodl\": 0.0009195217385338239, \"returns_over_uniform_hodl\": 0.0009664273370746379, \"sharpe\": 0.3990316980465303, \"sterling\": 1.9987418977928635, \"ulcer\": -0.000636752212281181}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -0.00011285979398463764, \"optuna_trial_number\": 39, \"price_ratio\": 1.0187550009948039, \"shift_exponent\": 0.0026263795026912227, \"step\": 39, \"test_objective\": [{\"annualised_returns\": 0.0033375458069369035, \"annualised_returns_over_hodl\": 0.0016141491350913917, \"annualised_returns_over_uniform_hodl\": 0.0016965182944581603, \"calmar\": 0.8209576554494095, \"daily_log_sharpe\": 0.3943935289479762, \"daily_returns\": -4.7019137576601266e-05, \"fee_revenue_over_value\": 6.834879561398725e-08, \"jax_sharpe\": 0.3627373325037736, \"return\": 0.0019005745110411976, \"returns_over_hodl\": 0.0009195217385338239, \"returns_over_uniform_hodl\": 0.0009664273370746379, \"sharpe\": 0.3990316980465303, \"sterling\": 1.9987418977928635, \"ulcer\": -0.000636752212281181}], \"train_objective\": [{\"annualised_returns\": 0.000942950900067574, \"annualised_returns_over_hodl\": 0.002077143534156445, \"annualised_returns_over_uniform_hodl\": 0.002077143534156445, \"calmar\": 0.500752983081683, \"daily_log_sharpe\": 0.10418562994474882, \"daily_returns\": -0.00042361623391010524, \"fee_revenue_over_value\": 2.1607783822496637e-08, \"jax_sharpe\": 0.10780356131698463, \"return\": 0.00016940552151356592, \"returns_over_hodl\": 0.0003729951731030745, \"returns_over_uniform_hodl\": 0.0003729951731030745, \"sharpe\": 0.1087311067695089, \"sterling\": 0.54823904324049, \"ulcer\": -0.000757228458884499}], \"train_return\": 0.00016940552151356592, \"train_returns_over_hodl\": 0.0003729951731030745, \"train_sharpe\": 0.10780356131698461, \"validation_return\": -7.642461809842516e-05, \"validation_returns_over_hodl\": 7.861996431257623e-05, \"validation_sharpe\": -0.2399164335610029}, {\"centeredness_margin\": 0.029897104197839197, \"continuous_test_metrics\": [{\"annualised_returns\": 0.0034372793712811323, \"annualised_returns_over_hodl\": 0.001745044106981286, \"annualised_returns_over_uniform_hodl\": 0.0017960887377037604, \"calmar\": 0.8122604894881919, \"daily_log_sharpe\": 0.4028203190354952, \"daily_returns\": -4.6163246864228886e-05, \"fee_revenue_over_value\": 6.684561658872628e-08, \"jax_sharpe\": 0.36633399076751716, \"return\": 0.0019573261800507336, \"returns_over_hodl\": 0.0009940598676314583, \"returns_over_uniform_hodl\": 0.0010231260922397567, \"sharpe\": 0.4074470102405224, \"sterling\": 1.9458125933710533, \"ulcer\": -0.0007194149747759313}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -0.00019651341379152144, \"optuna_trial_number\": 40, \"price_ratio\": 1.0303580697675268, \"shift_exponent\": 0.016157101088207254, \"step\": 40, \"test_objective\": [{\"annualised_returns\": 0.0034372793712811323, \"annualised_returns_over_hodl\": 0.001745044106981286, \"annualised_returns_over_uniform_hodl\": 0.0017960887377037604, \"calmar\": 0.8122604894881919, \"daily_log_sharpe\": 0.4028203190354952, \"daily_returns\": -4.6163246864228886e-05, \"fee_revenue_over_value\": 6.684561658872628e-08, \"jax_sharpe\": 0.36633399076751716, \"return\": 0.0019573261800507336, \"returns_over_hodl\": 0.0009940598676314583, \"returns_over_uniform_hodl\": 0.0010231260922397567, \"sharpe\": 0.4074470102405224, \"sterling\": 1.9458125933710533, \"ulcer\": -0.0007194149747759313}], \"train_objective\": [{\"annualised_returns\": 0.00015870963605779664, \"annualised_returns_over_hodl\": 0.0012920136274279237, \"annualised_returns_over_uniform_hodl\": 0.0012920136274279237, \"calmar\": 0.08279397286608325, \"daily_log_sharpe\": 0.01791682026735541, \"daily_returns\": -0.00042160277482474146, \"fee_revenue_over_value\": 2.104188016676114e-08, \"jax_sharpe\": 0.02234081940083584, \"return\": 2.852209391313032e-05, \"returns_over_hodl\": 0.00023208306795274858, \"returns_over_uniform_hodl\": 0.00023208306795274858, \"sharpe\": 0.022384123301706218, \"sterling\": 0.09435517653620895, \"ulcer\": -0.0007657137620339987}], \"train_return\": 2.852209391313032e-05, \"train_returns_over_hodl\": 0.00023208306795274858, \"train_sharpe\": 0.02234081940083584, \"validation_return\": -0.00010491450625127463, \"validation_returns_over_hodl\": 4.892563953884377e-05, \"validation_sharpe\": -0.3192004196086638}, {\"centeredness_margin\": 0.01784824749364847, \"continuous_test_metrics\": [{\"annualised_returns\": 0.0036575034090213787, \"annualised_returns_over_hodl\": 0.001959243760971674, \"annualised_returns_over_uniform_hodl\": 0.0020159525838954195, \"calmar\": 0.8695962718176155, \"daily_log_sharpe\": 0.43744830603271456, \"daily_returns\": -4.6317621028669154e-05, \"fee_revenue_over_value\": 6.730351565608381e-08, \"jax_sharpe\": 0.39750684984600954, \"return\": 0.0020826322869924585, \"returns_over_hodl\": 0.0011160268033136855, \"returns_over_uniform_hodl\": 0.0011483153668845336, \"sharpe\": 0.4419604899357063, \"sterling\": 2.1062789932691617, \"ulcer\": -0.0006968276551681361}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -0.00018142563983193362, \"optuna_trial_number\": 41, \"price_ratio\": 1.0273108845310133, \"shift_exponent\": 0.001945215955222029, \"step\": 41, \"test_objective\": [{\"annualised_returns\": 0.0036575034090213787, \"annualised_returns_over_hodl\": 0.001959243760971674, \"annualised_returns_over_uniform_hodl\": 0.0020159525838954195, \"calmar\": 0.8695962718176155, \"daily_log_sharpe\": 0.43744830603271456, \"daily_returns\": -4.6317621028669154e-05, \"fee_revenue_over_value\": 6.730351565608381e-08, \"jax_sharpe\": 0.39750684984600954, \"return\": 0.0020826322869924585, \"returns_over_hodl\": 0.0011160268033136855, \"returns_over_uniform_hodl\": 0.0011483153668845336, \"sharpe\": 0.4419604899357063, \"sterling\": 2.1062789932691617, \"ulcer\": -0.0006968276551681361}], \"train_objective\": [{\"annualised_returns\": 0.0003001120260703871, \"annualised_returns_over_hodl\": 0.0014335762439050548, \"annualised_returns_over_uniform_hodl\": 0.0014335762439050548, \"calmar\": 0.1571101662383602, \"daily_log_sharpe\": 0.03384215805906122, \"daily_returns\": -0.000418286869748287, \"fee_revenue_over_value\": 2.11868429257616e-08, \"jax_sharpe\": 0.0381270898407905, \"return\": 5.3930733432183686e-05, \"returns_over_hodl\": 0.00025749687953191547, \"returns_over_uniform_hodl\": 0.00025749687953191547, \"sharpe\": 0.038312343990436565, \"sterling\": 0.17757441604020915, \"ulcer\": -0.0007634660789973377}], \"train_return\": 5.3930733432183686e-05, \"train_returns_over_hodl\": 0.00025749687953191547, \"train_sharpe\": 0.0381270898407905, \"validation_return\": -9.97756653795534e-05, \"validation_returns_over_hodl\": 5.428172021137989e-05, \"validation_sharpe\": -0.3052853610875176}, {\"centeredness_margin\": 0.1978833125752275, \"continuous_test_metrics\": [{\"annualised_returns\": 0.0038689900261628107, \"annualised_returns_over_hodl\": 0.002153346331731365, \"annualised_returns_over_uniform_hodl\": 0.002227093300138705, \"calmar\": 0.944356006883743, \"daily_log_sharpe\": 0.45741237702712434, \"daily_returns\": -4.678251125472e-05, \"fee_revenue_over_value\": 6.765043759992352e-08, \"jax_sharpe\": 0.42411074338101956, \"return\": 0.0022029557243876674, \"returns_over_hodl\": 0.0012265406186351413, \"returns_over_uniform_hodl\": 0.0012685266176992727, \"sharpe\": 0.46196043314328417, \"sterling\": 2.337774014123153, \"ulcer\": -0.0006333973690166398}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -0.0001359886853739195, \"optuna_trial_number\": 42, \"price_ratio\": 1.0209712270928826, \"shift_exponent\": 0.00044904777560741987, \"step\": 42, \"test_objective\": [{\"annualised_returns\": 0.0038689900261628107, \"annualised_returns_over_hodl\": 0.002153346331731365, \"annualised_returns_over_uniform_hodl\": 0.002227093300138705, \"calmar\": 0.944356006883743, \"daily_log_sharpe\": 0.45741237702712434, \"daily_returns\": -4.678251125472e-05, \"fee_revenue_over_value\": 6.765043759992352e-08, \"jax_sharpe\": 0.42411074338101956, \"return\": 0.0022029557243876674, \"returns_over_hodl\": 0.0012265406186351413, \"returns_over_uniform_hodl\": 0.0012685266176992727, \"sharpe\": 0.46196043314328417, \"sterling\": 2.337774014123153, \"ulcer\": -0.0006333973690166398}], \"train_objective\": [{\"annualised_returns\": 0.0007260629335521518, \"annualised_returns_over_hodl\": 0.001860009806647911, \"annualised_returns_over_uniform_hodl\": 0.0018600098066492432, \"calmar\": 0.3841685037027495, \"daily_log_sharpe\": 0.08025922774995593, \"daily_returns\": -0.0004230595159673963, \"fee_revenue_over_value\": 2.1496723813767543e-08, \"jax_sharpe\": 0.0843807378575879, \"return\": 0.00013045218400509206, \"returns_over_hodl\": 0.0003340339064414888, \"returns_over_uniform_hodl\": 0.0003340339064417108, \"sharpe\": 0.08480914924898583, \"sterling\": 0.4247276425303744, \"ulcer\": -0.0007585591494525196}], \"train_return\": 0.00013045218400509206, \"train_returns_over_hodl\": 0.0003340339064414888, \"train_sharpe\": 0.0843807378575879, \"validation_return\": -8.430104765755342e-05, \"validation_returns_over_hodl\": 7.041054205325636e-05, \"validation_sharpe\": -0.2637815389980875}, {\"centeredness_margin\": 0.18207656872046918, \"continuous_test_metrics\": [{\"annualised_returns\": 0.0030019674628973814, \"annualised_returns_over_hodl\": 0.0012869996812108209, \"annualised_returns_over_uniform_hodl\": 0.0013614888118635982, \"calmar\": 0.7335511808164116, \"daily_log_sharpe\": 0.3465015737003752, \"daily_returns\": -4.680452991950479e-05, \"fee_revenue_over_value\": 6.768193313072317e-08, \"jax_sharpe\": 0.3158903363471671, \"return\": 0.0017096016069739761, \"returns_over_hodl\": 0.0007332081887780895, \"returns_over_uniform_hodl\": 0.0007756324913927859, \"sharpe\": 0.3513003538466716, \"sterling\": 1.7425143493036, \"ulcer\": -0.0006736855057536668}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -0.00013383613599512456, \"optuna_trial_number\": 43, \"price_ratio\": 1.020743102926963, \"shift_exponent\": 0.0013379065021733422, \"step\": 43, \"test_objective\": [{\"annualised_returns\": 0.0030019674628973814, \"annualised_returns_over_hodl\": 0.0012869996812108209, \"annualised_returns_over_uniform_hodl\": 0.0013614888118635982, \"calmar\": 0.7335511808164116, \"daily_log_sharpe\": 0.3465015737003752, \"daily_returns\": -4.680452991950479e-05, \"fee_revenue_over_value\": 6.768193313072317e-08, \"jax_sharpe\": 0.3158903363471671, \"return\": 0.0017096016069739761, \"returns_over_hodl\": 0.0007332081887780895, \"returns_over_uniform_hodl\": 0.0007756324913927859, \"sharpe\": 0.3513003538466716, \"sterling\": 1.7425143493036, \"ulcer\": -0.0006736855057536668}], \"train_objective\": [{\"annualised_returns\": 0.0007462463847038858, \"annualised_returns_over_hodl\": 0.0018802161281570307, \"annualised_returns_over_uniform_hodl\": 0.0018802161281556984, \"calmar\": 0.395048248271198, \"daily_log_sharpe\": 0.0841103194099251, \"daily_returns\": -0.000423111336489351, \"fee_revenue_over_value\": 2.150808902258349e-08, \"jax_sharpe\": 0.08842082725747732, \"return\": 0.00013407744812843347, \"returns_over_hodl\": 0.0003376599085063159, \"returns_over_uniform_hodl\": 0.00033765990850609384, \"sharpe\": 0.08857667771447617, \"sterling\": 0.4358910160942372, \"ulcer\": -0.0007590476648240692}], \"train_return\": 0.00013407744812843347, \"train_returns_over_hodl\": 0.0003376599085063159, \"train_sharpe\": 0.08842082725747732, \"validation_return\": -8.356798516251374e-05, \"validation_returns_over_hodl\": 7.11745964894206e-05, \"validation_sharpe\": -0.26110148371879016}, {\"centeredness_margin\": 0.22379375720670616, \"continuous_test_metrics\": [{\"annualised_returns\": 0.0034894038827557594, \"annualised_returns_over_hodl\": 0.001718985728732969, \"annualised_returns_over_uniform_hodl\": 0.0018481279959574604, \"calmar\": 0.8256114605841, \"daily_log_sharpe\": 0.3905458328681284, \"daily_returns\": -4.8296476542139516e-05, \"fee_revenue_over_value\": 6.195881326762525e-08, \"jax_sharpe\": 0.35444753229487835, \"return\": 0.001986985771176286, \"returns_over_hodl\": 0.000979221260663854, \"returns_over_uniform_hodl\": 0.0010527580295003336, \"sharpe\": 0.395451955698583, \"sterling\": 2.145952049915885, \"ulcer\": -0.0005523573502262288}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": 1.174480851457993e-05, \"optuna_trial_number\": 44, \"price_ratio\": 1.0119408165818633, \"shift_exponent\": 0.00019424637369423584, \"step\": 44, \"test_objective\": [{\"annualised_returns\": 0.0034894038827557594, \"annualised_returns_over_hodl\": 0.001718985728732969, \"annualised_returns_over_uniform_hodl\": 0.0018481279959574604, \"calmar\": 0.8256114605841, \"daily_log_sharpe\": 0.3905458328681284, \"daily_returns\": -4.8296476542139516e-05, \"fee_revenue_over_value\": 6.195881326762525e-08, \"jax_sharpe\": 0.35444753229487835, \"return\": 0.001986985771176286, \"returns_over_hodl\": 0.000979221260663854, \"returns_over_uniform_hodl\": 0.0010527580295003336, \"sharpe\": 0.395451955698583, \"sterling\": 2.145952049915885, \"ulcer\": -0.0005523573502262288}], \"train_objective\": [{\"annualised_returns\": 0.0021147896400453003, \"annualised_returns_over_hodl\": 0.0032503101129151, \"annualised_returns_over_uniform_hodl\": 0.0032503101129151, \"calmar\": 1.0910683871748286, \"daily_log_sharpe\": 0.23702637638688673, \"daily_returns\": -0.0004266223887720975, \"fee_revenue_over_value\": 2.1958535229796643e-08, \"jax_sharpe\": 0.23980273979316957, \"return\": 0.0003797494762460829, \"returns_over_hodl\": 0.0005833819444347466, \"returns_over_uniform_hodl\": 0.0005833819444347466, \"sharpe\": 0.24148184220860666, \"sterling\": 1.1903731927435766, \"ulcer\": -0.0007462041001008655}], \"train_return\": 0.0003797494762460829, \"train_returns_over_hodl\": 0.0005833819444347466, \"train_sharpe\": 0.23980273979316957, \"validation_return\": -3.39032632327152e-05, \"validation_returns_over_hodl\": 0.00012293908549487753, \"validation_sharpe\": -0.11166993296707672}, {\"centeredness_margin\": 0.21414675741214684, \"continuous_test_metrics\": [{\"annualised_returns\": 0.0034643109343532874, \"annualised_returns_over_hodl\": 0.0016744908964296812, \"annualised_returns_over_uniform_hodl\": 0.0018230760887967268, \"calmar\": 0.7767702838031456, \"daily_log_sharpe\": 0.3692270473809525, \"daily_returns\": -4.882772264938876e-05, \"fee_revenue_over_value\": 5.3759872653025926e-08, \"jax_sharpe\": 0.3384539435596129, \"return\": 0.0019727076080466865, \"returns_over_hodl\": 0.0009538838692231266, \"returns_over_uniform_hodl\": 0.0010384931789748642, \"sharpe\": 0.3744075836644456, \"sterling\": 2.1289775358701952, \"ulcer\": -0.0005349634056046745}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": 6.365502815966206e-05, \"optuna_trial_number\": 45, \"price_ratio\": 1.0103728388855158, \"shift_exponent\": 0.00010711543433090381, \"step\": 45, \"test_objective\": [{\"annualised_returns\": 0.0034643109343532874, \"annualised_returns_over_hodl\": 0.0016744908964296812, \"annualised_returns_over_uniform_hodl\": 0.0018230760887967268, \"calmar\": 0.7767702838031456, \"daily_log_sharpe\": 0.3692270473809525, \"daily_returns\": -4.882772264938876e-05, \"fee_revenue_over_value\": 5.3759872653025926e-08, \"jax_sharpe\": 0.3384539435596129, \"return\": 0.0019727076080466865, \"returns_over_hodl\": 0.0009538838692231266, \"returns_over_uniform_hodl\": 0.0010384931789748642, \"sharpe\": 0.3744075836644456, \"sterling\": 2.1289775358701952, \"ulcer\": -0.0005349634056046745}], \"train_objective\": [{\"annualised_returns\": 0.002602660550143021, \"annualised_returns_over_hodl\": 0.0037387338413255033, \"annualised_returns_over_uniform_hodl\": 0.0037387338413255033, \"calmar\": 1.3087476082263887, \"daily_log_sharpe\": 0.27088480646661833, \"daily_returns\": -0.000427873076246114, \"fee_revenue_over_value\": 2.2041290468517963e-08, \"jax_sharpe\": 0.2730292691570092, \"return\": 0.00046726236533078946, \"returns_over_hodl\": 0.0006709126472204119, \"returns_over_uniform_hodl\": 0.0006709126472204119, \"sharpe\": 0.27564838090526267, \"sterling\": 1.4458171480047046, \"ulcer\": -0.0007452143558767711}], \"train_return\": 0.00046726236533078946, \"train_returns_over_hodl\": 0.0006709126472204119, \"train_sharpe\": 0.2730292691570092, \"validation_return\": -1.621761442505143e-05, \"validation_returns_over_hodl\": 0.00014137251320089916, \"validation_sharpe\": -0.05185131100111336}, {\"centeredness_margin\": 0.17889727751843418, \"continuous_test_metrics\": [{\"annualised_returns\": 0.0026902553204901647, \"annualised_returns_over_hodl\": 0.0009024802009374167, \"annualised_returns_over_uniform_hodl\": 0.001050286496088626, \"calmar\": 0.5841471978146826, \"daily_log_sharpe\": 0.3291015743421234, \"daily_returns\": -4.8809560994011376e-05, \"fee_revenue_over_value\": 6.642589550874385e-08, \"jax_sharpe\": 0.26645054396430784, \"return\": 0.0015321859991446196, \"returns_over_hodl\": 0.0005141885917119282, \"returns_over_uniform_hodl\": 0.0005983823014632517, \"sharpe\": 0.3338663121633946, \"sterling\": 1.5711840820642817, \"ulcer\": -0.0005805916220782738}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": 6.188025767016603e-05, \"optuna_trial_number\": 46, \"price_ratio\": 1.010419619078799, \"shift_exponent\": 0.0006195004630886502, \"step\": 46, \"test_objective\": [{\"annualised_returns\": 0.0026902553204901647, \"annualised_returns_over_hodl\": 0.0009024802009374167, \"annualised_returns_over_uniform_hodl\": 0.001050286496088626, \"calmar\": 0.5841471978146826, \"daily_log_sharpe\": 0.3291015743421234, \"daily_returns\": -4.8809560994011376e-05, \"fee_revenue_over_value\": 6.642589550874385e-08, \"jax_sharpe\": 0.26645054396430784, \"return\": 0.0015321859991446196, \"returns_over_hodl\": 0.0005141885917119282, \"returns_over_uniform_hodl\": 0.0005983823014632517, \"sharpe\": 0.3338663121633946, \"sterling\": 1.5711840820642817, \"ulcer\": -0.0005805916220782738}], \"train_objective\": [{\"annualised_returns\": 0.002585976909580845, \"annualised_returns_over_hodl\": 0.0037220312961281365, \"annualised_returns_over_uniform_hodl\": 0.0037220312961268043, \"calmar\": 1.3014857850399315, \"daily_log_sharpe\": 0.2752959139701365, \"daily_returns\": -0.00042783031410257883, \"fee_revenue_over_value\": 2.203881013088893e-08, \"jax_sharpe\": 0.27982078044550845, \"return\": 0.00046427027848072733, \"returns_over_hodl\": 0.0006679199513157652, \"returns_over_uniform_hodl\": 0.0006679199513155432, \"sharpe\": 0.2799697061436621, \"sterling\": 1.4371920957183675, \"ulcer\": -0.0007455556902863912}], \"train_return\": 0.00046427027848072733, \"train_returns_over_hodl\": 0.0006679199513157652, \"train_sharpe\": 0.2798207804455084, \"validation_return\": -1.6822239322977772e-05, \"validation_returns_over_hodl\": 0.00014074232317340396, \"validation_sharpe\": -0.05265691074375665}, {\"centeredness_margin\": 0.3464214401208171, \"continuous_test_metrics\": [{\"annualised_returns\": 0.002784032057486119, \"annualised_returns_over_hodl\": 0.0011025533388790976, \"annualised_returns_over_uniform_hodl\": 0.0011439098547860738, \"calmar\": 0.6424366504558281, \"daily_log_sharpe\": 0.3105868353018095, \"daily_returns\": -4.589935471822786e-05, \"fee_revenue_over_value\": 6.452628002678005e-08, \"jax_sharpe\": 0.2824468438964424, \"return\": 0.001585562920216832, \"returns_over_hodl\": 0.000628153362496553, \"returns_over_uniform_hodl\": 0.0006517094552218605, \"sharpe\": 0.315558070313521, \"sterling\": 1.483370351279434, \"ulcer\": -0.0008060150400301569}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -0.0002551162913233346, \"optuna_trial_number\": 47, \"price_ratio\": 1.0466235354996332, \"shift_exponent\": 0.0022650409013889993, \"step\": 47, \"test_objective\": [{\"annualised_returns\": 0.002784032057486119, \"annualised_returns_over_hodl\": 0.0011025533388790976, \"annualised_returns_over_uniform_hodl\": 0.0011439098547860738, \"calmar\": 0.6424366504558281, \"daily_log_sharpe\": 0.3105868353018095, \"daily_returns\": -4.589935471822786e-05, \"fee_revenue_over_value\": 6.452628002678005e-08, \"jax_sharpe\": 0.2824468438964424, \"return\": 0.001585562920216832, \"returns_over_hodl\": 0.000628153362496553, \"returns_over_uniform_hodl\": 0.0006517094552218605, \"sharpe\": 0.315558070313521, \"sterling\": 1.483370351279434, \"ulcer\": -0.0008060150400301569}], \"train_objective\": [{\"annualised_returns\": -0.0003534226016318476, \"annualised_returns_over_hodl\": 0.0007793010803311962, \"annualised_returns_over_uniform_hodl\": 0.0007793010803311962, \"calmar\": -0.18106470296128932, \"daily_log_sharpe\": -0.04036852203070881, \"daily_returns\": -0.00041828689116965893, \"fee_revenue_over_value\": 2.03087489500672e-08, \"jax_sharpe\": -0.035920117128405725, \"return\": -6.352777688567457e-05, \"returns_over_hodl\": 0.00014001445992728456, \"returns_over_uniform_hodl\": 0.00014001445992728456, \"sharpe\": -0.035947321268062646, \"sterling\": -0.21219352922590118, \"ulcer\": -0.0007740477824145461}], \"train_return\": -6.352777688567457e-05, \"train_returns_over_hodl\": 0.00014001445992728456, \"train_sharpe\": -0.035920117128405725, \"validation_return\": -0.00012052262817152659, \"validation_returns_over_hodl\": 3.263825635424489e-05, \"validation_sharpe\": -0.36187586151526707}, {\"centeredness_margin\": 0.15658225193937902, \"continuous_test_metrics\": [{\"annualised_returns\": 0.004304359873642083, \"annualised_returns_over_hodl\": 0.0025341744817795053, \"annualised_returns_over_uniform_hodl\": 0.0026617510703115244, \"calmar\": 1.0831966678622105, \"daily_log_sharpe\": 0.5136699828614266, \"daily_returns\": -4.825087780376818e-05, \"fee_revenue_over_value\": 6.099330818912243e-08, \"jax_sharpe\": 0.47778518847928153, \"return\": 0.0024506212206905076, \"returns_over_hodl\": 0.0014433413519159277, \"returns_over_uniform_hodl\": 0.0015159611968542652, \"sharpe\": 0.5181690284688911, \"sterling\": 2.8574053661297243, \"ulcer\": -0.0005094393100740077}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": 7.289951932948347e-06, \"optuna_trial_number\": 48, \"price_ratio\": 1.0120977640380395, \"shift_exponent\": 0.00010952351431486546, \"step\": 48, \"test_objective\": [{\"annualised_returns\": 0.004304359873642083, \"annualised_returns_over_hodl\": 0.0025341744817795053, \"annualised_returns_over_uniform_hodl\": 0.0026617510703115244, \"calmar\": 1.0831966678622105, \"daily_log_sharpe\": 0.5136699828614266, \"daily_returns\": -4.825087780376818e-05, \"fee_revenue_over_value\": 6.099330818912243e-08, \"jax_sharpe\": 0.47778518847928153, \"return\": 0.0024506212206905076, \"returns_over_hodl\": 0.0014433413519159277, \"returns_over_uniform_hodl\": 0.0015159611968542652, \"sharpe\": 0.5181690284688911, \"sterling\": 2.8574053661297243, \"ulcer\": -0.0005094393100740077}], \"train_objective\": [{\"annualised_returns\": 0.002072930620242719, \"annualised_returns_over_hodl\": 0.0032084036616470968, \"annualised_returns_over_uniform_hodl\": 0.0032084036616470968, \"calmar\": 1.0718639444958282, \"daily_log_sharpe\": 0.22221277223132552, \"daily_returns\": -0.0004182868526391776, \"fee_revenue_over_value\": 2.1950294792357377e-08, \"jax_sharpe\": 0.22610420268302178, \"return\": 0.00037223929717589144, \"returns_over_hodl\": 0.0005758702366289725, \"returns_over_uniform_hodl\": 0.0005758702366289725, \"sharpe\": 0.22686916250852318, \"sterling\": 1.1681408257045796, \"ulcer\": -0.0007518247513979362}], \"train_return\": 0.00037223929717589144, \"train_returns_over_hodl\": 0.0005758702366289725, \"train_sharpe\": 0.22610420268302175, \"validation_return\": -3.5421122636436486e-05, \"validation_returns_over_hodl\": 0.00012135704408566816, \"validation_sharpe\": -0.11707461807361987}, {\"centeredness_margin\": 0.4137649298733668, \"continuous_test_metrics\": [{\"annualised_returns\": 0.0028184401346791343, \"annualised_returns_over_hodl\": 0.00114382039844374, \"annualised_returns_over_uniform_hodl\": 0.0011782616552042935, \"calmar\": 0.6512752546917528, \"daily_log_sharpe\": 0.3125959610128578, \"daily_returns\": -4.571030313124183e-05, \"fee_revenue_over_value\": 6.473109105506831e-08, \"jax_sharpe\": 0.2849972498280691, \"return\": 0.0016051471666631567, \"returns_over_hodl\": 0.0006516585025928556, \"returns_over_uniform_hodl\": 0.00067127544180412, \"sharpe\": 0.3175830126919182, \"sterling\": 1.5092785472188925, \"ulcer\": -0.000798339058678693}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -0.0002525677773292157, \"optuna_trial_number\": 49, \"price_ratio\": 1.0451291387715875, \"shift_exponent\": 0.0288908954949865, \"step\": 49, \"test_objective\": [{\"annualised_returns\": 0.0028184401346791343, \"annualised_returns_over_hodl\": 0.00114382039844374, \"annualised_returns_over_uniform_hodl\": 0.0011782616552042935, \"calmar\": 0.6512752546917528, \"daily_log_sharpe\": 0.3125959610128578, \"daily_returns\": -4.571030313124183e-05, \"fee_revenue_over_value\": 6.473109105506831e-08, \"jax_sharpe\": 0.2849972498280691, \"return\": 0.0016051471666631567, \"returns_over_hodl\": 0.0006516585025928556, \"returns_over_uniform_hodl\": 0.00067127544180412, \"sharpe\": 0.3175830126919182, \"sterling\": 1.5092785472188925, \"ulcer\": -0.000798339058678693}], \"train_objective\": [{\"annualised_returns\": -0.0003283626058199207, \"annualised_returns_over_hodl\": 0.000804389472228495, \"annualised_returns_over_uniform_hodl\": 0.0008043894722296052, \"calmar\": -0.1682992510849246, \"daily_log_sharpe\": -0.03748345297605414, \"daily_returns\": -0.00042053749337691784, \"fee_revenue_over_value\": 2.0373388793740123e-08, \"jax_sharpe\": -0.03296299386299222, \"return\": -5.902263127610663e-05, \"returns_over_hodl\": 0.0001445205225822921, \"returns_over_uniform_hodl\": 0.00014452052258251413, \"sharpe\": -0.033059985196192174, \"sterling\": -0.19697263618377084, \"ulcer\": -0.000772670900495662}], \"train_return\": -5.902263127610663e-05, \"train_returns_over_hodl\": 0.0001445205225822921, \"train_sharpe\": -0.03296299386299222, \"validation_return\": -0.00011999097976722606, \"validation_returns_over_hodl\": 3.321182613635898e-05, \"validation_sharpe\": -0.3618455017180548}]" \ No newline at end of file diff --git a/results/run_88bd93d37f514232b86396678742bed8255ea3f608707054ffc306e77e008d76.json b/results/run_88bd93d37f514232b86396678742bed8255ea3f608707054ffc306e77e008d76.json new file mode 100644 index 0000000..383d661 --- /dev/null +++ b/results/run_88bd93d37f514232b86396678742bed8255ea3f608707054ffc306e77e008d76.json @@ -0,0 +1 @@ +"[{\"alphabetic\": true, \"arb_fees\": 0.0, \"arb_frequency\": 4, \"arb_quality\": 1.0, \"bout_offset\": 10080, \"checkpoint_fused\": \"scan\", \"chunk_period\": 1440, \"do_arb\": true, \"do_trades\": false, \"endDateString\": \"2025-10-05 00:00:00\", \"endTestDateString\": \"2026-03-01 00:00:00\", \"ensemble_init_method\": \"gaussian\", \"ensemble_init_scale\": 0.5, \"ensemble_init_seed\": 42, \"fees\": 0.0025, \"freq\": \"minute\", \"gas_cost\": 1.0, \"initial_arc_length_speed\": 0.0001, \"initial_centeredness_margin\": 0.2, \"initial_daily_price_shift_base\": 0.999991935483871, \"initial_k_per_day\": 20, \"initial_log_amplitude\": 0.0, \"initial_memory_length\": 10.0, \"initial_memory_length_delta\": 0.0, \"initial_pool_value\": 20000000.0, \"initial_pre_exp_scaling\": 0.5, \"initial_price_ratio\": 4.0, \"initial_raw_exponents\": 0.0, \"initial_raw_width\": 0.0, \"initial_shift_exponent\": 1.0, \"initial_weights_logits\": 1.0, \"learnable_bounds_settings\": {\"freeze_bounds\": false, \"max_weights_per_asset\": null, \"min_weights_per_asset\": null}, \"max_memory_days\": 365, \"maximum_change\": 0.0003, \"minimum_weight\": null, \"n_ensemble_members\": 1, \"noise_arrays_path\": \"results/mm_noise/_sim_arrays/0x9d1fcf346ea1b0_2025-01-01_2026-03-01_mm.npz\", \"noise_model\": \"mm_observed\", \"noise_trader_ratio\": 0.0, \"numeraire\": null, \"optimisation_settings\": {\"base_lr\": 0.1, \"batch_size\": 8, \"bfgs_settings\": {\"compute_dtype\": \"float32\", \"maxiter\": 100, \"n_evaluation_points\": 20, \"tol\": 1e-06}, \"checkpoint_interval\": 10, \"clip_norm\": 10.0, \"cma_es_settings\": {\"compute_dtype\": \"float32\", \"memory_budget\": null, \"n_evaluation_points\": 20, \"n_generations\": 500, \"overfitting_penalty\": 1.0, \"population_size\": null, \"sigma0\": 0.5, \"tol\": 1e-08}, \"decay_lr_plateau\": 100, \"decay_lr_ratio\": 0.8, \"early_stopping\": true, \"early_stopping_metric\": \"daily_log_sharpe\", \"early_stopping_patience\": 200, \"force_scalar\": false, \"include_flipped_training_data\": false, \"initial_random_key\": 0, \"lr_decay_ratio\": 1000, \"lr_schedule_type\": \"constant\", \"max_mc_version\": 9, \"method\": \"cma_es\", \"min_lr\": 1e-06, \"n_cycles\": 5, \"n_iterations\": 1000, \"n_parameter_sets\": 1, \"noise_scale\": 0.1, \"optimiser\": \"adamw\", \"optuna_settings\": {\"early_stopping\": {\"enabled\": false, \"min_improvement\": 0.001, \"patience\": 100}, \"expand_around\": true, \"make_scalar\": false, \"multi_objective\": false, \"n_jobs\": 4, \"n_startup_trials\": 10, \"n_trials\": 20, \"overfitting_penalty\": 0.2, \"parameter_config\": {\"centeredness_margin\": {\"high\": 0.99, \"low\": 0.01, \"scalar\": true}, \"k_per_day\": {\"high\": 1000, \"log_scale\": true, \"low\": 0.1, \"scalar\": false}, \"log_amplitude\": {\"high\": 10, \"log_scale\": false, \"low\": -10, \"scalar\": false}, \"log_k\": {\"high\": 10.0, \"log_scale\": false, \"low\": -10.0, \"scalar\": false}, \"logit_lamb\": {\"high\": 10, \"log_scale\": false, \"low\": -10, \"scalar\": false}, \"memory_days_1\": {\"high\": 200, \"log_scale\": true, \"low\": 0.5, \"scalar\": false}, \"memory_days_2\": {\"high\": 200, \"log_scale\": true, \"low\": 0.5, \"scalar\": false}, \"memory_length\": {\"high\": 200, \"log_scale\": true, \"low\": 1, \"scalar\": false}, \"memory_length_delta\": {\"high\": 100, \"log_scale\": true, \"low\": 0.1, \"scalar\": false}, \"price_ratio\": {\"high\": 200.0, \"log_scale\": true, \"low\": 1.01, \"scalar\": true}, \"raw_exponents\": {\"high\": 10, \"log_scale\": false, \"low\": 0, \"scalar\": false}, \"raw_pre_exp_scaling\": {\"high\": 10, \"log_scale\": false, \"low\": -10, \"scalar\": false}, \"raw_width\": {\"high\": 10, \"log_scale\": false, \"low\": -10, \"scalar\": false}, \"shift_exponent\": {\"high\": 125.0, \"log_scale\": true, \"low\": 1e-05, \"scalar\": true}, \"weights_logits\": {\"high\": 10, \"log_scale\": false, \"low\": -10, \"scalar\": false}}, \"storage\": {\"type\": \"sqlite\", \"url\": null}, \"study_name\": null, \"timeout\": 7200}, \"parameter_init_method\": \"gaussian\", \"sample_method\": \"uniform\", \"swa_freq\": 10, \"swa_start_frac\": 0.75, \"track_checkpoints\": false, \"train_on_hessian_trace\": false, \"training_data_kind\": \"historic\", \"use_gradient_clipping\": true, \"use_plateau_decay\": false, \"use_swa\": false, \"val_fraction\": 0.2, \"warmup_steps\": 100, \"weight_decay\": 0.01}, \"price_noise_sigma\": 0.0, \"protocol_fee_split\": 0.25, \"reclamm_arc_length_speed\": null, \"reclamm_centeredness_scaling\": false, \"reclamm_interpolation_method\": \"geometric\", \"reclamm_learn_arc_length_speed\": false, \"reclamm_learn_fees\": false, \"reclamm_use_shift_exponent\": true, \"return_val\": \"returns_over_hodl\", \"rule\": \"reclamm\", \"startDateString\": \"2025-01-01 00:00:00\", \"ste_max_change\": false, \"ste_min_max_weight\": false, \"ste_temperature\": 10.0, \"subsidary_pools\": [], \"tokens\": [\"AAVE\", \"ETH\"], \"turnover_penalty\": 0.0, \"use_alt_lamb\": false, \"use_fused_reserves\": true, \"use_pre_exp_scaling\": true, \"weight_calculation_method\": \"auto\", \"weight_interpolation_method\": \"linear\", \"weight_interpolation_period\": 1440}, {\"centeredness_margin\": [[0.20000000298023224]], \"continuous_test_metrics\": [{\"annualised_returns\": -0.8749856352806091, \"annualised_returns_over_hodl\": 0.15981769561767578, \"annualised_returns_over_uniform_hodl\": 0.09814417362213135, \"calmar\": -1.2606592178344727, \"daily_log_sharpe\": -2.4292516708374023, \"daily_returns\": [0.007395481690764427, 0.04138880595564842, -0.0643656775355339, 0.02905883640050888, -0.03996468707919121, -0.16705070436000824, 0.025481918826699257, 0.07762559503316879, 0.0404769591987133, -0.025428952649235725, -0.04622294753789902, -0.06284606456756592, -0.05970508232712746, 0.03372563421726227, 0.03009232133626938, 0.022055307403206825, -0.03921392560005188, -0.012507733888924122, 0.0326247476041317, 0.018043726682662964, -0.006314495578408241, 0.06044723838567734, -0.01910444162786007, -0.029341785237193108, -0.00799991562962532, -0.046739596873521805, 0.04996486008167267, -0.016879232600331306, 0.031632497906684875, -0.1223856657743454, -0.06198793649673462, 0.05945185571908951, -0.021812409162521362, 0.05042020231485367, -0.023988937959074974, 0.047734133899211884, 0.05179382860660553, -0.07156267017126083, -0.011674185283482075, -0.039514198899269104, -0.09161195158958435, 0.01893317699432373, -0.029271136969327927, -0.021623577922582626, 0.04652479663491249, -0.024931205436587334, -0.05221652612090111, -0.03922441229224205, 0.017439700663089752, 0.02693965844810009, 0.060772933065891266, 0.007883995771408081, 0.03231872618198395, -0.00040947628440335393, 0.0002306899259565398, -0.015123134478926659, -0.015850774943828583, -0.04510578140616417, 0.09834769368171692, 0.048358429223299026, -0.029262742027640343, -0.0389692597091198, 0.018099866807460785, 0.0005333832232281566, 0.025042330846190453, 0.06116332858800888, -0.0184574406594038, 0.011677342467010021, -0.053423717617988586, 0.024051308631896973, -0.029430145397782326, -0.0035604899749159813, -0.022956160828471184, -0.03749796375632286, -0.021138057112693787, 0.04957909137010574, -0.004954544827342033, -0.05901942774653435, -0.06399186700582504, 0.0017092535272240639, -0.014409245923161507, 0.00062482466455549, 0.027466926723718643, 0.01571214571595192, -0.018087195232510567, -0.019214965403079987, 0.002777530811727047, -0.015712905675172806, 0.01854977011680603, 0.09042556583881378, -0.012282171286642551, 0.013466810807585716, 0.03520561009645462, 0.023522179573774338, -0.04363088309764862, -0.019186152145266533, 0.0036796866916120052, -0.0030091346707195044, 0.015858119353652, -0.012483881786465645, 0.07874932885169983, 0.0008827544515952468, -0.027374424040317535, 0.013456109911203384, -0.004495915025472641, -0.058242399245500565, 0.008097086101770401, -0.06522932648658752, 0.03273447975516319, -0.014963018707931042, 0.0007899831398390234, -0.004587898496538401, -0.05151725932955742, 0.04334479197859764, 0.0310710109770298, 0.00518100056797266, -0.07337626069784164, -0.05838388949632645, -0.0721394419670105, -0.05117528513073921, 0.03766462951898575, -0.033332690596580505, -0.017764054238796234, -0.17139974236488342, 0.12476160377264023, 0.007065403740853071, -0.0072183432057499886, 0.005198994651436806, -0.034477245062589645, -0.019348205998539925, 0.043972354382276535, 0.03956896439194679, 0.056198522448539734, -0.03343544155359268, 0.013345025479793549, -0.0005451681790873408, -0.026555223390460014, 0.009537952020764351, -0.042523667216300964, 0.03261345624923706, -0.018221942707896233, -0.03663158416748047, -0.00379693484865129, 0.06662347167730331, -0.02628016099333763, -0.023793771862983704], \"jax_sharpe\": -0.7656055092811584, \"return\": -0.5671757459640503, \"returns_over_hodl\": 0.06152975559234619, \"returns_over_uniform_hodl\": 0.03842484951019287, \"sharpe\": -2.0327301025390625, \"sterling\": -2.156355619430542, \"ulcer\": -0.16505998373031616}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0.0, \"objective\": [0.06936347484588623], \"price_ratio\": [[4.0]], \"shift_exponent\": [[1.0]], \"step\": 0, \"subsidary_params\": [], \"test_objective\": [{\"annualised_returns\": -0.8749856352806091, \"annualised_returns_over_hodl\": 0.15981769561767578, \"annualised_returns_over_uniform_hodl\": 0.09814417362213135, \"calmar\": -1.2606592178344727, \"daily_log_sharpe\": -2.4292516708374023, \"daily_returns\": [0.007395481690764427, 0.04138880595564842, -0.0643656775355339, 0.02905883640050888, -0.03996468707919121, -0.16705070436000824, 0.025481918826699257, 0.07762559503316879, 0.0404769591987133, -0.025428952649235725, -0.04622294753789902, -0.06284606456756592, -0.05970508232712746, 0.03372563421726227, 0.03009232133626938, 0.022055307403206825, -0.03921392560005188, -0.012507733888924122, 0.0326247476041317, 0.018043726682662964, -0.006314495578408241, 0.06044723838567734, -0.01910444162786007, -0.029341785237193108, -0.00799991562962532, -0.046739596873521805, 0.04996486008167267, -0.016879232600331306, 0.031632497906684875, -0.1223856657743454, -0.06198793649673462, 0.05945185571908951, -0.021812409162521362, 0.05042020231485367, -0.023988937959074974, 0.047734133899211884, 0.05179382860660553, -0.07156267017126083, -0.011674185283482075, -0.039514198899269104, -0.09161195158958435, 0.01893317699432373, -0.029271136969327927, -0.021623577922582626, 0.04652479663491249, -0.024931205436587334, -0.05221652612090111, -0.03922441229224205, 0.017439700663089752, 0.02693965844810009, 0.060772933065891266, 0.007883995771408081, 0.03231872618198395, -0.00040947628440335393, 0.0002306899259565398, -0.015123134478926659, -0.015850774943828583, -0.04510578140616417, 0.09834769368171692, 0.048358429223299026, -0.029262742027640343, -0.0389692597091198, 0.018099866807460785, 0.0005333832232281566, 0.025042330846190453, 0.06116332858800888, -0.0184574406594038, 0.011677342467010021, -0.053423717617988586, 0.024051308631896973, -0.029430145397782326, -0.0035604899749159813, -0.022956160828471184, -0.03749796375632286, -0.021138057112693787, 0.04957909137010574, -0.004954544827342033, -0.05901942774653435, -0.06399186700582504, 0.0017092535272240639, -0.014409245923161507, 0.00062482466455549, 0.027466926723718643, 0.01571214571595192, -0.018087195232510567, -0.019214965403079987, 0.002777530811727047, -0.015712905675172806, 0.01854977011680603, 0.09042556583881378, -0.012282171286642551, 0.013466810807585716, 0.03520561009645462, 0.023522179573774338, -0.04363088309764862, -0.019186152145266533, 0.0036796866916120052, -0.0030091346707195044, 0.015858119353652, -0.012483881786465645, 0.07874932885169983, 0.0008827544515952468, -0.027374424040317535, 0.013456109911203384, -0.004495915025472641, -0.058242399245500565, 0.008097086101770401, -0.06522932648658752, 0.03273447975516319, -0.014963018707931042, 0.0007899831398390234, -0.004587898496538401, -0.05151725932955742, 0.04334479197859764, 0.0310710109770298, 0.00518100056797266, -0.07337626069784164, -0.05838388949632645, -0.0721394419670105, -0.05117528513073921, 0.03766462951898575, -0.033332690596580505, -0.017764054238796234, -0.17139974236488342, 0.12476160377264023, 0.007065403740853071, -0.0072183432057499886, 0.005198994651436806, -0.034477245062589645, -0.019348205998539925, 0.043972354382276535, 0.03956896439194679, 0.056198522448539734, -0.03343544155359268, 0.013345025479793549, -0.0005451681790873408, -0.026555223390460014, 0.009537952020764351, -0.042523667216300964, 0.03261345624923706, -0.018221942707896233, -0.03663158416748047, -0.00379693484865129, 0.06662347167730331, -0.02628016099333763, -0.023793771862983704], \"jax_sharpe\": -0.7656055092811584, \"return\": -0.5671757459640503, \"returns_over_hodl\": 0.06152975559234619, \"returns_over_uniform_hodl\": 0.03842484951019287, \"sharpe\": -2.0327301025390625, \"sterling\": -2.156355619430542, \"ulcer\": -0.16505998373031616}], \"train_objective\": [{\"annualised_returns\": 0.3492856025695801, \"annualised_returns_over_hodl\": 0.11679303646087646, \"annualised_returns_over_uniform_hodl\": 0.11679303646087646, \"calmar\": 0.5493883490562439, \"daily_log_sharpe\": 0.37867751717567444, \"daily_returns\": [0.020415889099240303, 0.03596492484211922, 0.04824770241975784, 0.008958694525063038, -0.016145573928952217, 0.004197292495518923, -0.08849851042032242, -0.03093652054667473, -0.03549734875559807, 0.014429399743676186, 0.003884561825543642, -0.0008101346320472658, -0.01684877835214138, 0.027142424136400223, 0.0770559012889862, -0.034901365637779236, 0.06417534500360489, -0.05520357936620712, -0.026499919593334198, 0.059588927775621414, 0.047181565314531326, -0.04012763500213623, 0.007279120851308107, -0.011302602477371693, -0.007751564960926771, -0.02991197071969509, -0.02462601475417614, -0.04865331947803497, 0.01993781141936779, 0.06229039654135704, 0.03250924497842789, -0.08062947541475296, -0.10520278662443161, 0.03860139846801758, -0.0295181255787611, -0.012976881116628647, -0.05391858145594597, -0.02399902045726776, 0.007604281418025494, 0.006098057143390179, 0.031180955469608307, -0.03251098841428757, 0.046464283019304276, -0.004199132788926363, 0.021720338612794876, -0.023710692301392555, -0.0034579953644424677, 0.03683219850063324, -0.042513515800237656, 0.015635928139090538, 0.0217764750123024, -0.051523465663194656, 0.02981726825237274, 0.012240820564329624, -0.12874576449394226, -0.021614130586385727, -0.03255365416407585, -0.00398601358756423, -0.045538559556007385, 0.0034883099142462015, 0.14420710504055023, -0.17332753539085388, 0.0770927146077156, 0.05237164348363876, -0.03593933954834938, -0.04158855602145195, 0.011154665611684322, -0.08236951380968094, -0.03124191425740719, 0.020596081390976906, -0.023336879909038544, -0.048107732087373734, 0.04880039393901825, 0.005706795025616884, -0.03714345023036003, 0.043161969631910324, -0.01332008931785822, 0.06955765187740326, -0.026908056810498238, -0.005282192025333643, 0.008567293174564838, 0.01967741549015045, 0.03581725060939789, -0.014345340430736542, -0.037306882441043854, 0.019274620339274406, -0.05045443773269653, -0.03967735171318054, -0.017598453909158707, -0.011299185454845428, 0.044141896069049835, -0.0785531997680664, 0.010934482328593731, 0.001724998583085835, -0.0010466792155057192, -0.14135314524173737, 0.005734850652515888, -0.04789971932768822, 0.1355869621038437, -0.08270669728517532, 0.03285232186317444, 0.06915177404880524, -0.0470091812312603, 0.0027143913321197033, -0.024328893050551414, -0.0024887206964194775, 0.021895788609981537, -0.0007460369379259646, 0.02081572264432907, -0.010350026190280914, 0.004431343171745539, 0.11378980427980423, 0.0411975271999836, -0.008996804244816303, 0.0036052686627954245, 0.02905869111418724, -0.026258409023284912, 0.0015433389926329255, -0.0013886909000575542, -0.005371665116399527, 0.03923066332936287, 0.006412927061319351, 0.005981160793453455, -0.025690341368317604, 0.021404307335615158, 0.0024449352640658617, -0.016698945313692093, 0.20605094730854034, 0.045025430619716644, 0.08774780482053757, -0.0266643725335598, -0.0019217637600377202, 0.07045455276966095, -0.02721179649233818, -0.025342881679534912, 0.015179584734141827, -0.031385716050863266, 0.028984280303120613, 0.0410931222140789, 0.018394997343420982, -0.017118871212005615, 0.04336373135447502, -0.02757369354367256, 0.012276713736355305, 0.017548564821481705, 0.0006251453887671232, 0.02892453223466873, -0.006578550208359957, -0.03574375808238983, -0.016558414325118065, -0.00922321155667305, 0.0004940996877849102, 0.0362078920006752, 0.013426081277430058, -0.0003427849733270705, -0.07924545556306839, 0.032490599900484085, 0.022438568994402885, -0.010070061311125755, 0.0900937020778656, 0.06490658968687057, -0.013542494736611843, -0.04085002839565277, -0.019699107855558395, -0.029921531677246094, 0.0019279629923403263, -0.0019656396470963955, -0.01719198189675808, -0.009091557003557682, -0.005706867203116417, -0.041546937078237534, -0.04288022592663765, -0.03420107439160347, 0.10377651453018188, 0.021466072648763657, -0.020144222304224968, -0.010293480940163136, 0.018419796600937843, 0.004190821200609207, 0.04544266313314438, -0.009820332750678062, -0.04091241955757141, 0.06833206862211227, 0.008671260438859463, -0.03884423151612282, 0.014644828625023365, 0.03331887722015381, -0.012418348342180252, 0.03151120990514755, 0.04778021574020386, 0.04696120321750641, -0.014180652797222137, 0.004728454630821943, 0.011743959039449692, 0.020635180175304413, 0.04049888625741005, 0.035748910158872604, 0.015801692381501198, 0.011910609900951385, -0.0005854020128026605, 0.028671642765402794, -0.0009471695520915091, -0.023583529517054558, -0.05002077296376228, 0.0003490984090603888, 0.01937728188931942, 0.0044428459368646145, 0.030890772119164467, -0.03757179528474808, -0.013367991894483566, -0.016048192977905273, -0.04140554741024971, -0.03245825320482254, -0.025736128911376, 0.038584113121032715, 0.042205847799777985, -0.041016094386577606, 0.028267107903957367, 0.07334277033805847, 0.024109426885843277, 0.06122147664427757], \"jax_sharpe\": 0.7800065279006958, \"return\": 0.1994692087173462, \"returns_over_hodl\": 0.06936347484588623, \"returns_over_uniform_hodl\": 0.06936347484588623, \"sharpe\": 0.803220808506012, \"sterling\": 1.3393594026565552, \"ulcer\": -0.1325758844614029}]}, {\"centeredness_margin\": [[0.010268386453390121]], \"continuous_test_metrics\": [{\"annualised_returns\": -0.8630076050758362, \"annualised_returns_over_hodl\": 0.3591388463973999, \"annualised_returns_over_uniform_hodl\": 0.2033613920211792, \"calmar\": -1.3085371255874634, \"daily_log_sharpe\": -2.2675621509552, \"daily_returns\": [0.008585542440414429, 0.04261653870344162, -0.06997442245483398, 0.03306141868233681, -0.04135715961456299, -0.17818330228328705, 0.03922330215573311, 0.07230721414089203, 0.044261571019887924, -0.02487972192466259, -0.048779647797346115, -0.07433853298425674, -0.07287906110286713, 0.03868204727768898, 0.032191429287195206, 0.025670139119029045, -0.040173690766096115, -0.011306791566312313, 0.034750185906887054, 0.018470579758286476, -0.006921668071299791, 0.06180201843380928, -0.019504297524690628, -0.028434813022613525, -0.0067208874970674515, -0.04840058833360672, 0.053566064685583115, -0.01771549880504608, 0.033755067735910416, -0.12599262595176697, -0.05802395939826965, 0.06130744516849518, -0.021565526723861694, 0.05062846094369888, -0.023358790203928947, 0.04823335260152817, 0.047760993242263794, -0.06709597259759903, -0.01095342356711626, -0.039832230657339096, -0.09331168234348297, 0.0202250387519598, -0.02984289452433586, -0.020979758352041245, 0.049513865262269974, -0.02382812276482582, -0.05134640261530876, -0.039839696139097214, 0.01943589560687542, 0.027858516201376915, 0.06110985949635506, 0.008397199213504791, 0.03221678361296654, -0.00015635084128007293, 0.001258622738532722, -0.014543554745614529, -0.014941728673875332, -0.045027993619441986, 0.0958796963095665, 0.0510072335600853, -0.02774336375296116, -0.038763243705034256, 0.017746182158589363, 0.0017019262304529548, 0.02572152018547058, 0.06191496178507805, -0.017538029700517654, 0.010065466165542603, -0.05201415717601776, 0.022484727203845978, -0.026472032070159912, -0.008483395911753178, -0.01845918782055378, -0.037840984761714935, -0.01820782572031021, 0.050278741866350174, -0.003915147855877876, -0.06366908550262451, -0.0807652696967125, 0.00557617237791419, -0.015606379136443138, 0.0038517233915627003, 0.03129277005791664, 0.01730072684586048, -0.020053043961524963, -0.02242833562195301, 0.0011567749315872788, -0.020427273586392403, 0.020820077508687973, 0.10032523423433304, -0.013317568227648735, 0.014644970186054707, 0.03721313178539276, 0.02452213317155838, -0.04373513534665108, -0.01860278658568859, 0.0050628650933504105, -0.0028989920392632484, 0.016591964289546013, -0.012427357956767082, 0.07951493561267853, 0.0008099570404738188, -0.029391467571258545, 0.016768109053373337, -0.0049926117062568665, -0.06441343575716019, 0.01310056634247303, -0.06314410269260406, 0.03511801362037659, -0.015014683827757835, 0.0010101428488269448, -0.004432170186191797, -0.05181584134697914, 0.044219162315130234, 0.03147955238819122, 0.0067254165187478065, -0.07406572997570038, -0.06013741344213486, -0.06812719255685806, -0.04877306893467903, 0.038317278027534485, -0.0326557494699955, -0.018537312746047974, -0.16963398456573486, 0.12532150745391846, 0.007016967516392469, -0.0076987771317362785, 0.005066836252808571, -0.03401993215084076, -0.01763434149324894, 0.041194647550582886, 0.0421237051486969, 0.04963216558098793, -0.04042372852563858, 0.014781469479203224, 0.00021412783826235682, -0.023439155891537666, 0.00572347454726696, -0.027981940656900406, 0.026916923001408577, -0.015200265683233738, -0.039365846663713455, -0.002386226551607251, 0.07645765691995621, -0.02316306345164776, -0.024348242208361626], \"jax_sharpe\": -0.9400712251663208, \"return\": -0.550929069519043, \"returns_over_hodl\": 0.13154125213623047, \"returns_over_uniform_hodl\": 0.07740390300750732, \"sharpe\": -1.8588453531265259, \"sterling\": -2.1638269424438477, \"ulcer\": -0.16750863194465637}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0.0, \"objective\": [0.1233450174331665], \"price_ratio\": [[1.347874641418457]], \"shift_exponent\": [[0.5410670638084412]], \"step\": 1, \"subsidary_params\": [], \"test_objective\": [{\"annualised_returns\": -0.8630076050758362, \"annualised_returns_over_hodl\": 0.3591388463973999, \"annualised_returns_over_uniform_hodl\": 0.2033613920211792, \"calmar\": -1.3085371255874634, \"daily_log_sharpe\": -2.2675621509552, \"daily_returns\": [0.008585542440414429, 0.04261653870344162, -0.06997442245483398, 0.03306141868233681, -0.04135715961456299, -0.17818330228328705, 0.03922330215573311, 0.07230721414089203, 0.044261571019887924, -0.02487972192466259, -0.048779647797346115, -0.07433853298425674, -0.07287906110286713, 0.03868204727768898, 0.032191429287195206, 0.025670139119029045, -0.040173690766096115, -0.011306791566312313, 0.034750185906887054, 0.018470579758286476, -0.006921668071299791, 0.06180201843380928, -0.019504297524690628, -0.028434813022613525, -0.0067208874970674515, -0.04840058833360672, 0.053566064685583115, -0.01771549880504608, 0.033755067735910416, -0.12599262595176697, -0.05802395939826965, 0.06130744516849518, -0.021565526723861694, 0.05062846094369888, -0.023358790203928947, 0.04823335260152817, 0.047760993242263794, -0.06709597259759903, -0.01095342356711626, -0.039832230657339096, -0.09331168234348297, 0.0202250387519598, -0.02984289452433586, -0.020979758352041245, 0.049513865262269974, -0.02382812276482582, -0.05134640261530876, -0.039839696139097214, 0.01943589560687542, 0.027858516201376915, 0.06110985949635506, 0.008397199213504791, 0.03221678361296654, -0.00015635084128007293, 0.001258622738532722, -0.014543554745614529, -0.014941728673875332, -0.045027993619441986, 0.0958796963095665, 0.0510072335600853, -0.02774336375296116, -0.038763243705034256, 0.017746182158589363, 0.0017019262304529548, 0.02572152018547058, 0.06191496178507805, -0.017538029700517654, 0.010065466165542603, -0.05201415717601776, 0.022484727203845978, -0.026472032070159912, -0.008483395911753178, -0.01845918782055378, -0.037840984761714935, -0.01820782572031021, 0.050278741866350174, -0.003915147855877876, -0.06366908550262451, -0.0807652696967125, 0.00557617237791419, -0.015606379136443138, 0.0038517233915627003, 0.03129277005791664, 0.01730072684586048, -0.020053043961524963, -0.02242833562195301, 0.0011567749315872788, -0.020427273586392403, 0.020820077508687973, 0.10032523423433304, -0.013317568227648735, 0.014644970186054707, 0.03721313178539276, 0.02452213317155838, -0.04373513534665108, -0.01860278658568859, 0.0050628650933504105, -0.0028989920392632484, 0.016591964289546013, -0.012427357956767082, 0.07951493561267853, 0.0008099570404738188, -0.029391467571258545, 0.016768109053373337, -0.0049926117062568665, -0.06441343575716019, 0.01310056634247303, -0.06314410269260406, 0.03511801362037659, -0.015014683827757835, 0.0010101428488269448, -0.004432170186191797, -0.05181584134697914, 0.044219162315130234, 0.03147955238819122, 0.0067254165187478065, -0.07406572997570038, -0.06013741344213486, -0.06812719255685806, -0.04877306893467903, 0.038317278027534485, -0.0326557494699955, -0.018537312746047974, -0.16963398456573486, 0.12532150745391846, 0.007016967516392469, -0.0076987771317362785, 0.005066836252808571, -0.03401993215084076, -0.01763434149324894, 0.041194647550582886, 0.0421237051486969, 0.04963216558098793, -0.04042372852563858, 0.014781469479203224, 0.00021412783826235682, -0.023439155891537666, 0.00572347454726696, -0.027981940656900406, 0.026916923001408577, -0.015200265683233738, -0.039365846663713455, -0.002386226551607251, 0.07645765691995621, -0.02316306345164776, -0.024348242208361626], \"jax_sharpe\": -0.9400712251663208, \"return\": -0.550929069519043, \"returns_over_hodl\": 0.13154125213623047, \"returns_over_uniform_hodl\": 0.07740390300750732, \"sharpe\": -1.8588453531265259, \"sterling\": -2.1638269424438477, \"ulcer\": -0.16750863194465637}], \"train_objective\": [{\"annualised_returns\": 0.44030678272247314, \"annualised_returns_over_hodl\": 0.19213056564331055, \"annualised_returns_over_uniform_hodl\": 0.19213056564331055, \"calmar\": 0.6993628740310669, \"daily_log_sharpe\": 0.4447139501571655, \"daily_returns\": [0.020055098459124565, 0.03514653071761131, 0.048323214054107666, 0.010798320174217224, -0.014364039525389671, 0.00498189777135849, -0.08863953500986099, -0.032053615897893906, -0.03584456816315651, 0.015803497284650803, 0.004182371310889721, 0.0008495678193867207, -0.011532635428011417, 0.028112536296248436, 0.07852109521627426, -0.03420042246580124, 0.06267334520816803, -0.05309377238154411, -0.0250893272459507, 0.05023703724145889, 0.02044619619846344, -0.029984109103679657, 0.016658490523695946, -0.009981758892536163, -0.006015242077410221, -0.029155183583498, -0.024355676025152206, -0.05170873925089836, 0.022562969475984573, 0.06666146963834763, 0.03271292522549629, -0.08089888095855713, -0.11254396289587021, 0.051900751888751984, -0.028080180287361145, -0.01539081335067749, -0.05952144041657448, -0.021728327497839928, 0.008440541103482246, 0.009846189990639687, 0.03628034517168999, -0.03395633026957512, 0.04550890251994133, 0.0008382577216252685, 0.02299124374985695, -0.025096014142036438, -0.0012483532773330808, 0.03887372463941574, -0.04495599493384361, 0.01567540131509304, 0.02519780583679676, -0.05662991479039192, 0.025940142571926117, 0.007669599261134863, -0.14223231375217438, -0.030348418280482292, -0.01793116331100464, -0.0008618537103757262, -0.04960254207253456, 0.008061081171035767, 0.14700458943843842, -0.18177272379398346, 0.09374145418405533, 0.05062860995531082, -0.031808704137802124, -0.04111239314079285, 0.008839606307446957, -0.08119184523820877, -0.02629164233803749, 0.025255735963582993, -0.021395215764641762, -0.051828037947416306, 0.05475844070315361, 0.0050224740989506245, -0.03927994146943092, 0.048521582037210464, -0.015789948403835297, 0.07112308591604233, -0.024405593052506447, -0.004261373076587915, 0.009306598454713821, 0.02125312015414238, 0.036074601113796234, -0.014840826392173767, -0.03882316127419472, 0.02360922284424305, -0.0497434064745903, -0.039166394621133804, -0.017885850742459297, -0.014215962961316109, 0.04413451626896858, -0.08617813140153885, 0.011203925125300884, 0.0039143930189311504, 0.0011670319363474846, -0.14933106303215027, 0.018559813499450684, -0.04712912440299988, 0.138003870844841, -0.08062718063592911, 0.03400316834449768, 0.0739855170249939, -0.050868190824985504, -0.0020802821964025497, -0.024593522772192955, -0.000573704659473151, 0.028568189591169357, -0.0018260368378832936, 0.022443052381277084, -0.008640650659799576, 0.006839176174253225, 0.11486631631851196, 0.04399172216653824, -0.008376382291316986, 0.005113689694553614, 0.03023427352309227, -0.02508075162768364, 0.0024083408061414957, -0.0007320835720747709, -0.005104623269289732, 0.04040757939219475, 0.006784679833799601, 0.005359369795769453, -0.023759258911013603, 0.020690279081463814, 0.0026543193962424994, -0.014317640103399754, 0.20732314884662628, 0.04394727945327759, 0.08607878535985947, -0.027234544977545738, 0.00010520805517444387, 0.0701090395450592, -0.027027232572436333, -0.024738024920225143, 0.019877126440405846, -0.031918372958898544, 0.03164298087358475, 0.03989775851368904, 0.012868507765233517, -0.008464931510388851, 0.0446610301733017, -0.03200038522481918, 0.010598408989608288, 0.014516763389110565, 0.004483209457248449, 0.034826114773750305, -0.0011619313154369593, -0.032374102622270584, -0.0192050002515316, -0.006658552680164576, 0.0018736479105427861, 0.03511152043938637, 0.007702055852860212, 0.0032440510112792253, -0.0765831246972084, 0.030274828895926476, 0.021920939907431602, -0.008125433698296547, 0.0806141197681427, 0.053237538784742355, -0.013520350679755211, -0.044200874865055084, -0.022290784865617752, -0.019176822155714035, 0.004859556909650564, -0.0019921937491744757, -0.012149009853601456, -0.002703191712498665, -0.0038261152803897858, -0.042190324515104294, -0.04168768599629402, -0.03340880572795868, 0.09723160415887833, 0.018498331308364868, -0.015088735148310661, -0.006373140960931778, 0.012926792725920677, 0.005340190138667822, 0.03486523777246475, -0.007156188599765301, -0.034807149320840836, 0.07010101526975632, 0.008770357817411423, -0.03483957797288895, 0.009358221665024757, 0.025801390409469604, -0.010602684691548347, 0.03029482252895832, 0.056138839572668076, 0.05625162646174431, -0.008709368295967579, 0.003033014014363289, 0.012410015799105167, 0.019391397014260292, 0.042156022042036057, 0.04243025183677673, 0.014959079213440418, 0.011573569849133492, -0.003941850736737251, 0.023218385875225067, -0.002031943527981639, -0.03244272992014885, -0.06059941649436951, -0.013743225485086441, 0.02777940221130848, 0.00394704844802618, 0.02952495403587818, -0.04879122972488403, -0.021280711516737938, -0.02717558667063713, -0.047278206795454025, -0.022614866495132446, -0.02460579201579094, 0.041543539613485336, 0.03507265821099281, -0.045791249722242355, 0.03261123597621918, 0.07977385073900223, 0.02306322380900383, 0.060875918716192245], \"jax_sharpe\": 0.8549821972846985, \"return\": 0.2479630708694458, \"returns_over_hodl\": 0.11259722709655762, \"returns_over_uniform_hodl\": 0.11259722709655762, \"sharpe\": 0.8812012076377869, \"sterling\": 1.686360239982605, \"ulcer\": -0.12968792021274567}]}]" \ No newline at end of file diff --git a/results/run_cc1d5e6467381c91004a58f57a1110a1c66faca6e4cf16da5854516f8de280cd.json b/results/run_cc1d5e6467381c91004a58f57a1110a1c66faca6e4cf16da5854516f8de280cd.json new file mode 100644 index 0000000..f153bb6 --- /dev/null +++ b/results/run_cc1d5e6467381c91004a58f57a1110a1c66faca6e4cf16da5854516f8de280cd.json @@ -0,0 +1 @@ +"[{\"alphabetic\": true, \"arb_fees\": 0.0, \"arb_frequency\": 1, \"arb_quality\": 1.0, \"bout_offset\": 10080, \"checkpoint_fused\": \"scan\", \"chunk_period\": 1440, \"do_arb\": true, \"do_trades\": false, \"endDateString\": \"2025-10-05 00:00:00\", \"endTestDateString\": \"2026-03-01 00:00:00\", \"ensemble_init_method\": \"gaussian\", \"ensemble_init_scale\": 0.5, \"ensemble_init_seed\": 42, \"evaluation_starts\": [86400, 91129, 93321, 95858, 100587, 103489, 105317, 105383, 107643, 110046, 114775, 118630, 119504, 124234, 125912, 128393, 128963, 129013, 129967, 132291, 133692, 135000, 138421, 139020, 141426, 143151, 147880, 152609, 154668, 157338, 162068, 166586, 166797, 166871, 167366, 168857, 171526, 172693, 176088, 176256], \"fees\": 0.0025, \"freq\": \"minute\", \"gas_cost\": 1.0, \"initial_arc_length_speed\": 0.0001, \"initial_centeredness_margin\": 0.2, \"initial_daily_price_shift_base\": 0.999991935483871, \"initial_k_per_day\": 20, \"initial_log_amplitude\": 0.0, \"initial_memory_length\": 10.0, \"initial_memory_length_delta\": 0.0, \"initial_pool_value\": 5000000.0, \"initial_pre_exp_scaling\": 0.5, \"initial_price_ratio\": 4.0, \"initial_raw_exponents\": 0.0, \"initial_raw_width\": 0.0, \"initial_shift_exponent\": 1.0, \"initial_weights_logits\": 1.0, \"learnable_bounds_settings\": {\"freeze_bounds\": false, \"max_weights_per_asset\": null, \"min_weights_per_asset\": null}, \"max_memory_days\": 365, \"maximum_change\": 0.0003, \"minimum_weight\": null, \"n_ensemble_members\": 1, \"noise_arrays_path\": \"results/mm_noise/_sim_arrays/0xa6f548df93de92_2025-01-01_2026-03-01_mm.npz\", \"noise_model\": \"mm_observed\", \"noise_trader_ratio\": 0.0, \"numeraire\": null, \"optimisation_settings\": {\"base_lr\": 0.1, \"batch_size\": 8, \"bfgs_settings\": {\"compute_dtype\": \"float32\", \"maxiter\": 100, \"n_evaluation_points\": 20, \"tol\": 1e-06}, \"checkpoint_interval\": 10, \"clip_norm\": 10.0, \"cma_es_settings\": {\"compute_dtype\": \"float32\", \"memory_budget\": null, \"n_evaluation_points\": 20, \"n_generations\": 300, \"population_size\": null, \"sigma0\": 0.5, \"tol\": 1e-08}, \"decay_lr_plateau\": 100, \"decay_lr_ratio\": 0.8, \"early_stopping\": true, \"early_stopping_metric\": \"daily_log_sharpe\", \"early_stopping_patience\": 200, \"force_scalar\": false, \"include_flipped_training_data\": false, \"initial_random_key\": 0, \"lr_decay_ratio\": 1000, \"lr_schedule_type\": \"constant\", \"max_mc_version\": 9, \"method\": \"optuna\", \"min_lr\": 1e-06, \"n_cycles\": 5, \"n_iterations\": 1000, \"n_parameter_sets\": 1, \"noise_scale\": 0.1, \"optimiser\": \"adamw\", \"optuna_settings\": {\"early_stopping\": {\"enabled\": false, \"min_improvement\": 0.001, \"patience\": 100}, \"expand_around\": false, \"make_scalar\": true, \"min_train_returns_over_hodl\": -0.5, \"multi_objective\": false, \"n_jobs\": 4, \"n_startup_trials\": 10, \"n_trials\": 50, \"overfitting_penalty\": 0.2, \"parameter_config\": {\"centeredness_margin\": {\"high\": 0.99, \"low\": 0.01, \"scalar\": true}, \"k_per_day\": {\"high\": 1000, \"log_scale\": true, \"low\": 0.1, \"scalar\": true}, \"log_amplitude\": {\"high\": 10, \"log_scale\": false, \"low\": -10, \"scalar\": true}, \"log_k\": {\"high\": 10.0, \"log_scale\": false, \"low\": -10.0, \"scalar\": true}, \"logit_lamb\": {\"high\": 4.602848117654388, \"log_scale\": false, \"low\": -5.955817303419269, \"scalar\": true}, \"memory_days_1\": {\"high\": 200, \"log_scale\": true, \"low\": 0.5, \"scalar\": true}, \"memory_days_2\": {\"high\": 200, \"log_scale\": true, \"low\": 0.5, \"scalar\": true}, \"memory_length\": {\"high\": 200, \"log_scale\": true, \"low\": 1, \"scalar\": true}, \"memory_length_delta\": {\"high\": 100, \"log_scale\": true, \"low\": 0.1, \"scalar\": true}, \"price_ratio\": {\"high\": 5.0, \"log_scale\": true, \"low\": 1.01, \"scalar\": true}, \"raw_exponents\": {\"high\": 10, \"log_scale\": false, \"low\": 0, \"scalar\": true}, \"raw_pre_exp_scaling\": {\"high\": 10, \"log_scale\": false, \"low\": -10, \"scalar\": true}, \"raw_width\": {\"high\": 10, \"log_scale\": false, \"low\": -10, \"scalar\": true}, \"shift_exponent\": {\"high\": 125.0, \"log_scale\": true, \"low\": 1e-05, \"scalar\": true}, \"weights_logits\": {\"high\": 10, \"log_scale\": false, \"low\": -10, \"scalar\": true}}, \"storage\": {\"type\": \"sqlite\", \"url\": null}, \"study_name\": null, \"timeout\": 7200}, \"parameter_init_method\": \"gaussian\", \"sample_method\": \"uniform\", \"swa_freq\": 10, \"swa_start_frac\": 0.75, \"track_checkpoints\": false, \"train_on_hessian_trace\": false, \"training_data_kind\": \"historic\", \"use_gradient_clipping\": true, \"use_plateau_decay\": false, \"use_swa\": false, \"val_fraction\": 0.2, \"warmup_steps\": 100, \"weight_decay\": 0.01}, \"price_noise_sigma\": 0.0, \"protocol_fee_split\": 0.25, \"reclamm_arc_length_speed\": null, \"reclamm_centeredness_scaling\": false, \"reclamm_interpolation_method\": \"geometric\", \"reclamm_learn_arc_length_speed\": false, \"reclamm_learn_fees\": false, \"reclamm_use_shift_exponent\": true, \"return_val\": \"returns_over_hodl\", \"rule\": \"reclamm\", \"startDateString\": \"2025-01-01 00:00:00\", \"ste_max_change\": false, \"ste_min_max_weight\": false, \"ste_temperature\": 10.0, \"subsidary_pools\": [], \"tokens\": [\"BTC\", \"ETH\"], \"training_method\": \"optuna\", \"turnover_penalty\": 0.0, \"use_alt_lamb\": false, \"use_fused_reserves\": true, \"use_pre_exp_scaling\": true, \"weight_calculation_method\": \"auto\", \"weight_interpolation_method\": \"linear\", \"weight_interpolation_period\": 1440}, {\"centeredness_margin\": 0.3147276387836944, \"continuous_test_metrics\": [{\"annualised_returns\": -0.8312010829673651, \"annualised_returns_over_hodl\": -0.050291612805094976, \"annualised_returns_over_uniform_hodl\": -0.019829514774061363, \"calmar\": -1.4270626658998706, \"daily_log_sharpe\": -2.8170377408060348, \"daily_returns\": 0.007170686746859631, \"fee_revenue_over_value\": 8.142442901228541e-05, \"jax_sharpe\": -2.38056974836613, \"return\": -0.5115360276937015, \"returns_over_hodl\": -0.02056694836138162, \"returns_over_uniform_hodl\": -0.008033892765772155, \"sharpe\": -2.511741106407347, \"sterling\": -3.5271395653440982, \"ulcer\": -0.11506745878585406}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": 0.19857763893855068, \"optuna_trial_number\": 0, \"price_ratio\": 1.5973063737576239, \"shift_exponent\": 1.7924533302109447e-05, \"step\": 0, \"test_objective\": [{\"annualised_returns\": -0.8312010829673651, \"annualised_returns_over_hodl\": -0.050291612805094976, \"annualised_returns_over_uniform_hodl\": -0.019829514774061363, \"calmar\": -1.4270626658998706, \"daily_log_sharpe\": -2.8170377408060348, \"daily_returns\": 0.007170686746859631, \"fee_revenue_over_value\": 8.142442901228541e-05, \"jax_sharpe\": -2.38056974836613, \"return\": -0.5115360276937015, \"returns_over_hodl\": -0.02056694836138162, \"returns_over_uniform_hodl\": -0.008033892765772155, \"sharpe\": -2.511741106407347, \"sterling\": -3.5271395653440982, \"ulcer\": -0.11506745878585406}], \"train_objective\": [{\"annualised_returns\": 0.5272210556783168, \"annualised_returns_over_hodl\": 0.03704959988797518, \"annualised_returns_over_uniform_hodl\": 0.03704959988797518, \"calmar\": 0.8861208828137276, \"daily_log_sharpe\": 0.5550835136937159, \"daily_returns\": 0.008891717964706726, \"fee_revenue_over_value\": 3.378234615161209e-05, \"jax_sharpe\": 0.9378368424237751, \"return\": 0.2931555797944905, \"returns_over_hodl\": 0.022332651201108167, \"returns_over_uniform_hodl\": 0.022332651201108167, \"sharpe\": 0.9241895950308691, \"sterling\": 2.26539077335143, \"ulcer\": -0.12013865522083667}], \"train_return\": 0.2931555797944905, \"train_returns_over_hodl\": 0.022332651201108167, \"train_sharpe\": 0.9378368424237751, \"validation_return\": 0.062224629817726695, \"validation_returns_over_hodl\": 0.012812595298003604, \"validation_sharpe\": 1.2384715601320442}, {\"centeredness_margin\": 0.9652144107952862, \"continuous_test_metrics\": [{\"annualised_returns\": -0.8320821203191187, \"annualised_returns_over_hodl\": -0.02216070527745706, \"annualised_returns_over_uniform_hodl\": -0.0249454647087608, \"calmar\": -1.4323673458067954, \"daily_log_sharpe\": -2.9927164031276545, \"daily_returns\": 0.006880705763984296, \"fee_revenue_over_value\": 4.271804038365441e-05, \"jax_sharpe\": -2.5994622279287585, \"return\": -0.5125644178438137, \"returns_over_hodl\": -0.00898472552402696, \"returns_over_uniform_hodl\": -0.010122333739439493, \"sharpe\": -2.710784701218345, \"sterling\": -3.6171965824460166, \"ulcer\": -0.11621888572376676}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": 0.11389781063292784, \"optuna_trial_number\": 1, \"price_ratio\": 2.5010347770244694, \"shift_exponent\": 0.03727729494907189, \"step\": 1, \"test_objective\": [{\"annualised_returns\": -0.8320821203191187, \"annualised_returns_over_hodl\": -0.02216070527745706, \"annualised_returns_over_uniform_hodl\": -0.0249454647087608, \"calmar\": -1.4323673458067954, \"daily_log_sharpe\": -2.9927164031276545, \"daily_returns\": 0.006880705763984296, \"fee_revenue_over_value\": 4.271804038365441e-05, \"jax_sharpe\": -2.5994622279287585, \"return\": -0.5125644178438137, \"returns_over_hodl\": -0.00898472552402696, \"returns_over_uniform_hodl\": -0.010122333739439493, \"sharpe\": -2.710784701218345, \"sterling\": -3.6171965824460166, \"ulcer\": -0.11621888572376676}], \"train_objective\": [{\"annualised_returns\": 0.37461722607337045, \"annualised_returns_over_hodl\": -0.06657504557167804, \"annualised_returns_over_uniform_hodl\": -0.0665750455716777, \"calmar\": 0.7702994666555929, \"daily_log_sharpe\": 0.5447249952492602, \"daily_returns\": 0.008885152955737372, \"fee_revenue_over_value\": 6.540766484472939e-05, \"jax_sharpe\": 0.832573234044386, \"return\": 0.21309025753211586, \"returns_over_hodl\": -0.040964754352191934, \"returns_over_uniform_hodl\": -0.04096475435219171, \"sharpe\": 0.8304399373634881, \"sterling\": 1.995810141468259, \"ulcer\": -0.09068721991285439}], \"train_return\": 0.21309025753211586, \"train_returns_over_hodl\": -0.040964754352191934, \"train_sharpe\": 0.832573234044386, \"validation_return\": 0.042722666182133384, \"validation_returns_over_hodl\": -0.005663016323258052, \"validation_sharpe\": 0.8640206087892123}, {\"centeredness_margin\": 0.3192511805019692, \"continuous_test_metrics\": [{\"annualised_returns\": -0.8232590984070586, \"annualised_returns_over_hodl\": -0.022813229731224016, \"annualised_returns_over_uniform_hodl\": 0.026287480506349326, \"calmar\": -1.4377078235276923, \"daily_log_sharpe\": -2.923628045329693, \"daily_returns\": 0.007217742692687175, \"fee_revenue_over_value\": 4.925402218633479e-05, \"jax_sharpe\": -2.52883588938604, \"return\": -0.5024071076750863, \"returns_over_hodl\": -0.009251115967999213, \"returns_over_uniform_hodl\": 0.010504996011146295, \"sharpe\": -2.6416875036014646, \"sterling\": -3.638912287077602, \"ulcer\": -0.11275966196417472}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": 0.05185558916873091, \"optuna_trial_number\": 2, \"price_ratio\": 3.1008699292083852, \"shift_exponent\": 0.09196989731553974, \"step\": 2, \"test_objective\": [{\"annualised_returns\": -0.8232590984070586, \"annualised_returns_over_hodl\": -0.022813229731224016, \"annualised_returns_over_uniform_hodl\": 0.026287480506349326, \"calmar\": -1.4377078235276923, \"daily_log_sharpe\": -2.923628045329693, \"daily_returns\": 0.007217742692687175, \"fee_revenue_over_value\": 4.925402218633479e-05, \"jax_sharpe\": -2.52883588938604, \"return\": -0.5024071076750863, \"returns_over_hodl\": -0.009251115967999213, \"returns_over_uniform_hodl\": 0.010504996011146295, \"sharpe\": -2.6416875036014646, \"sterling\": -3.638912287077602, \"ulcer\": -0.11275966196417472}], \"train_objective\": [{\"annualised_returns\": 0.21596099152163895, \"annualised_returns_over_hodl\": -0.17430953754312817, \"annualised_returns_over_uniform_hodl\": -0.17430953754312817, \"calmar\": 0.4105513242031547, \"daily_log_sharpe\": 0.2964677152594782, \"daily_returns\": 0.008886135358035285, \"fee_revenue_over_value\": 8.312177476178387e-05, \"jax_sharpe\": 0.6257881339255866, \"return\": 0.12604703447287013, \"returns_over_hodl\": -0.10977869320817224, \"returns_over_uniform_hodl\": -0.10977869320817224, \"sharpe\": 0.5993503147931558, \"sterling\": 1.1163989220355095, \"ulcer\": -0.09648867809062449}], \"train_return\": 0.12604703447287013, \"train_returns_over_hodl\": -0.10977869320817224, \"train_sharpe\": 0.6257881339255865, \"validation_return\": 0.038056353982299784, \"validation_returns_over_hodl\": -0.004077361497044518, \"validation_sharpe\": 0.8401553603729407}, {\"centeredness_margin\": 0.43904752389527396, \"continuous_test_metrics\": [{\"annualised_returns\": -0.8302554459604553, \"annualised_returns_over_hodl\": -0.032502794304975824, \"annualised_returns_over_uniform_hodl\": -0.014338451796855667, \"calmar\": -1.4297706981119163, \"daily_log_sharpe\": -2.8769956070017852, \"daily_returns\": 0.007046338047447419, \"fee_revenue_over_value\": 5.49679630866417e-05, \"jax_sharpe\": -2.4650732822376678, \"return\": -0.5104357944849082, \"returns_over_hodl\": -0.013219407963077634, \"returns_over_uniform_hodl\": -0.005799553868629959, \"sharpe\": -2.5812596183232963, \"sterling\": -3.5637359367353283, \"ulcer\": -0.11523391588983409}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": 0.1891746288681631, \"optuna_trial_number\": 3, \"price_ratio\": 2.1459015884497172, \"shift_exponent\": 1.7586329132467026e-05, \"step\": 3, \"test_objective\": [{\"annualised_returns\": -0.8302554459604553, \"annualised_returns_over_hodl\": -0.032502794304975824, \"annualised_returns_over_uniform_hodl\": -0.014338451796855667, \"calmar\": -1.4297706981119163, \"daily_log_sharpe\": -2.8769956070017852, \"daily_returns\": 0.007046338047447419, \"fee_revenue_over_value\": 5.49679630866417e-05, \"jax_sharpe\": -2.4650732822376678, \"return\": -0.5104357944849082, \"returns_over_hodl\": -0.013219407963077634, \"returns_over_uniform_hodl\": -0.005799553868629959, \"sharpe\": -2.5812596183232963, \"sterling\": -3.5637359367353283, \"ulcer\": -0.11523391588983409}], \"train_objective\": [{\"annualised_returns\": 0.5458365547452653, \"annualised_returns_over_hodl\": 0.04969033436928405, \"annualised_returns_over_uniform_hodl\": 0.04969033436928405, \"calmar\": 0.9468949471181128, \"daily_log_sharpe\": 0.5889449400502048, \"daily_returns\": 0.008887869680937063, \"fee_revenue_over_value\": 2.923775177214017e-05, \"jax_sharpe\": 0.9606766258895345, \"return\": 0.30270251145088856, \"returns_over_hodl\": 0.029880188484032955, \"returns_over_uniform_hodl\": 0.029880188484032955, \"sharpe\": 0.9461201410552752, \"sterling\": 2.4179436540857426, \"ulcer\": -0.11497577257842463}], \"train_return\": 0.30270251145088856, \"train_returns_over_hodl\": 0.029880188484032955, \"train_sharpe\": 0.9606766258895345, \"validation_return\": 0.05712998897596511, \"validation_returns_over_hodl\": 0.008106234025711867, \"validation_sharpe\": 1.1223431898795002}, {\"centeredness_margin\": 0.7267397929465383, \"continuous_test_metrics\": [{\"annualised_returns\": -0.8300509273017959, \"annualised_returns_over_hodl\": -0.0007192203736909875, \"annualised_returns_over_uniform_hodl\": -0.013150866257682248, \"calmar\": -1.4312325382740037, \"daily_log_sharpe\": -2.954739156786953, \"daily_returns\": 0.00680163530443928, \"fee_revenue_over_value\": 4.1915702943146726e-05, \"jax_sharpe\": -2.5726897913478473, \"return\": -0.51019832217831, \"returns_over_hodl\": -0.0002897194847445439, \"returns_over_uniform_hodl\": -0.005317298281918181, \"sharpe\": -2.6708523037388474, \"sterling\": -3.606549912723399, \"ulcer\": -0.11546241042602062}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": 0.10555596391480204, \"optuna_trial_number\": 4, \"price_ratio\": 3.924720766515448, \"shift_exponent\": 68.14716110641697, \"step\": 4, \"test_objective\": [{\"annualised_returns\": -0.8300509273017959, \"annualised_returns_over_hodl\": -0.0007192203736909875, \"annualised_returns_over_uniform_hodl\": -0.013150866257682248, \"calmar\": -1.4312325382740037, \"daily_log_sharpe\": -2.954739156786953, \"daily_returns\": 0.00680163530443928, \"fee_revenue_over_value\": 4.1915702943146726e-05, \"jax_sharpe\": -2.5726897913478473, \"return\": -0.51019832217831, \"returns_over_hodl\": -0.0002897194847445439, \"returns_over_uniform_hodl\": -0.005317298281918181, \"sharpe\": -2.6708523037388474, \"sterling\": -3.606549912723399, \"ulcer\": -0.11546241042602062}], \"train_objective\": [{\"annualised_returns\": 0.33476607090112154, \"annualised_returns_over_hodl\": -0.09363571525848924, \"annualised_returns_over_uniform_hodl\": -0.09363571525848902, \"calmar\": 0.6724433193541911, \"daily_log_sharpe\": 0.4889383386791241, \"daily_returns\": 0.008885605720337782, \"fee_revenue_over_value\": 7.433492378876747e-05, \"jax_sharpe\": 0.7840365651051812, \"return\": 0.1916155271094535, \"returns_over_hodl\": -0.05794207589792455, \"returns_over_uniform_hodl\": -0.05794207589792444, \"sharpe\": 0.7754574964769436, \"sterling\": 1.7661806122697914, \"ulcer\": -0.09275358137776338}], \"train_return\": 0.1916155271094535, \"train_returns_over_hodl\": -0.05794207589792455, \"train_sharpe\": 0.784036565105181, \"validation_return\": 0.041158583364731216, \"validation_returns_over_hodl\": -0.00600624373543579, \"validation_sharpe\": 0.8419043474148417}, {\"centeredness_margin\": 0.8072246801368033, \"continuous_test_metrics\": [{\"annualised_returns\": -0.8395473352006171, \"annualised_returns_over_hodl\": -0.04834785359183391, \"annualised_returns_over_uniform_hodl\": -0.06829398507456108, \"calmar\": -1.4268436568296685, \"daily_log_sharpe\": -3.0604488026966497, \"daily_returns\": 0.006860060528582505, \"fee_revenue_over_value\": 0.00013053192193210805, \"jax_sharpe\": -2.67130644932066, \"return\": -0.5214105135300485, \"returns_over_hodl\": -0.019760114474260848, \"returns_over_uniform_hodl\": -0.02808686664176241, \"sharpe\": -2.7795832775533937, \"sterling\": -3.61848222782676, \"ulcer\": -0.11815069910943962}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": 0.049670643846860374, \"optuna_trial_number\": 5, \"price_ratio\": 1.344691468277918, \"shift_exponent\": 0.8210237531445104, \"step\": 5, \"test_objective\": [{\"annualised_returns\": -0.8395473352006171, \"annualised_returns_over_hodl\": -0.04834785359183391, \"annualised_returns_over_uniform_hodl\": -0.06829398507456108, \"calmar\": -1.4268436568296685, \"daily_log_sharpe\": -3.0604488026966497, \"daily_returns\": 0.006860060528582505, \"fee_revenue_over_value\": 0.00013053192193210805, \"jax_sharpe\": -2.67130644932066, \"return\": -0.5214105135300485, \"returns_over_hodl\": -0.019760114474260848, \"returns_over_uniform_hodl\": -0.02808686664176241, \"sharpe\": -2.7795832775533937, \"sterling\": -3.61848222782676, \"ulcer\": -0.11815069910943962}], \"train_objective\": [{\"annualised_returns\": 0.1861018112330297, \"annualised_returns_over_hodl\": -0.19458522118182198, \"annualised_returns_over_uniform_hodl\": -0.19458522118182175, \"calmar\": 0.3640734320676346, \"daily_log_sharpe\": 0.2877503809960303, \"daily_returns\": 0.008897474612630109, \"fee_revenue_over_value\": 0.00023765181164023852, \"jax_sharpe\": 0.5853368568307571, \"return\": 0.10917745234615506, \"returns_over_hodl\": -0.12311531324812164, \"returns_over_uniform_hodl\": -0.12311531324812153, \"sharpe\": 0.5757657097668546, \"sterling\": 0.9693219607576165, \"ulcer\": -0.0948926367451816}], \"train_return\": 0.10917745234615506, \"train_returns_over_hodl\": -0.12311531324812164, \"train_sharpe\": 0.5853368568307572, \"validation_return\": 0.028668282357580566, \"validation_returns_over_hodl\": -0.019987181550927713, \"validation_sharpe\": 0.6514927813354684}, {\"centeredness_margin\": 0.9217468093793482, \"continuous_test_metrics\": [{\"annualised_returns\": -0.8341536983640978, \"annualised_returns_over_hodl\": -0.2586272566646154, \"annualised_returns_over_uniform_hodl\": -0.03697456829085399, \"calmar\": -1.4320358651525755, \"daily_log_sharpe\": -2.851774580486881, \"daily_returns\": 0.00869052495023827, \"fee_revenue_over_value\": 2.4558903531054318e-05, \"jax_sharpe\": -2.3741025222145917, \"return\": -0.514995231314694, \"returns_over_hodl\": -0.11354064162063693, \"returns_over_uniform_hodl\": -0.01505879725124526, \"sharpe\": -2.5523705468775657, \"sterling\": -3.7316887871060036, \"ulcer\": -0.10386452366738227}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -0.06967069839345606, \"optuna_trial_number\": 6, \"price_ratio\": 1.034144221115526, \"shift_exponent\": 0.004792596495273959, \"step\": 6, \"test_objective\": [{\"annualised_returns\": -0.8341536983640978, \"annualised_returns_over_hodl\": -0.2586272566646154, \"annualised_returns_over_uniform_hodl\": -0.03697456829085399, \"calmar\": -1.4320358651525755, \"daily_log_sharpe\": -2.851774580486881, \"daily_returns\": 0.00869052495023827, \"fee_revenue_over_value\": 2.4558903531054318e-05, \"jax_sharpe\": -2.3741025222145917, \"return\": -0.514995231314694, \"returns_over_hodl\": -0.11354064162063693, \"returns_over_uniform_hodl\": -0.01505879725124526, \"sharpe\": -2.5523705468775657, \"sterling\": -3.7316887871060036, \"ulcer\": -0.10386452366738227}], \"train_objective\": [{\"annualised_returns\": -0.19549879576637252, \"annualised_returns_over_hodl\": -0.4537086501931974, \"annualised_returns_over_uniform_hodl\": -0.4537086501931974, \"calmar\": -0.31384982089182767, \"daily_log_sharpe\": -0.3542281975164275, \"daily_returns\": 0.008839590140184822, \"fee_revenue_over_value\": 1.3974557576498594e-05, \"jax_sharpe\": 0.05281052889202954, \"return\": -0.12371931644978074, \"returns_over_hodl\": -0.3072369880253817, \"returns_over_uniform_hodl\": -0.3072369880253817, \"sharpe\": 0.00019063716483089637, \"sterling\": -0.8913172187805238, \"ulcer\": -0.11562654867221975}], \"train_return\": -0.12371931644978074, \"train_returns_over_hodl\": -0.3072369880253817, \"train_sharpe\": 0.052810528892029536, \"validation_return\": 0.03048627045142238, \"validation_returns_over_hodl\": -7.37649941129348e-11, \"validation_sharpe\": 0.80172657449514}, {\"centeredness_margin\": 0.9236160379223946, \"continuous_test_metrics\": [{\"annualised_returns\": -0.8249718761415619, \"annualised_returns_over_hodl\": -0.05171974449509065, \"annualised_returns_over_uniform_hodl\": 0.01634183504472775, \"calmar\": -1.4378006442812294, \"daily_log_sharpe\": -2.9469976970766574, \"daily_returns\": 0.0073428575715866725, \"fee_revenue_over_value\": 1.3570641025066518e-05, \"jax_sharpe\": -2.5520224414974653, \"return\": -0.5043548077045156, \"returns_over_hodl\": -0.021160379454276068, \"returns_over_uniform_hodl\": 0.006549632820017637, \"sharpe\": -2.6663307259572075, \"sterling\": -3.6388650310490256, \"ulcer\": -0.11331995215535867}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": 0.10904287626162008, \"optuna_trial_number\": 7, \"price_ratio\": 4.575957116754659, \"shift_exponent\": 0.0020693780440319354, \"step\": 7, \"test_objective\": [{\"annualised_returns\": -0.8249718761415619, \"annualised_returns_over_hodl\": -0.05171974449509065, \"annualised_returns_over_uniform_hodl\": 0.01634183504472775, \"calmar\": -1.4378006442812294, \"daily_log_sharpe\": -2.9469976970766574, \"daily_returns\": 0.0073428575715866725, \"fee_revenue_over_value\": 1.3570641025066518e-05, \"jax_sharpe\": -2.5520224414974653, \"return\": -0.5043548077045156, \"returns_over_hodl\": -0.021160379454276068, \"returns_over_uniform_hodl\": 0.006549632820017637, \"sharpe\": -2.6663307259572075, \"sterling\": -3.6388650310490256, \"ulcer\": -0.11331995215535867}], \"train_objective\": [{\"annualised_returns\": 0.3607713620517323, \"annualised_returns_over_hodl\": -0.07597699016270854, \"annualised_returns_over_uniform_hodl\": -0.07597699016270831, \"calmar\": 0.7058206789555646, \"daily_log_sharpe\": 0.4810265457872633, \"daily_returns\": 0.008884062769254119, \"fee_revenue_over_value\": 4.936396397743963e-06, \"jax_sharpe\": 0.8080512977926353, \"return\": 0.2056571669739602, \"returns_over_hodl\": -0.04684114795530703, \"returns_over_uniform_hodl\": -0.04684114795530692, \"sharpe\": 0.7822611933489498, \"sterling\": 1.871380431271233, \"ulcer\": -0.09471819034152562}], \"train_return\": 0.2056571669739602, \"train_returns_over_hodl\": -0.04684114795530703, \"train_sharpe\": 0.8080512977926353, \"validation_return\": 0.04356371175316398, \"validation_returns_over_hodl\": 0.0013166816662713021, \"validation_sharpe\": 0.949929565920463}, {\"centeredness_margin\": 0.3953683725711484, \"continuous_test_metrics\": [{\"annualised_returns\": -0.8161142628554787, \"annualised_returns_over_hodl\": -0.14689661798470455, \"annualised_returns_over_uniform_hodl\": 0.0677756431827583, \"calmar\": -1.4446277391253322, \"daily_log_sharpe\": -2.847682605463989, \"daily_returns\": 0.008532967236451807, \"fee_revenue_over_value\": 0.00010412289508181116, \"jax_sharpe\": -2.402415845784038, \"return\": -0.49440161222175616, \"returns_over_hodl\": -0.061980730226495084, \"returns_over_uniform_hodl\": 0.02676244919408388, \"sharpe\": -2.563643500709178, \"sterling\": -3.660092349923256, \"ulcer\": -0.10776864273884076}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -0.0513275499510886, \"optuna_trial_number\": 8, \"price_ratio\": 1.3934693702999432, \"shift_exponent\": 0.004726758995977553, \"step\": 8, \"test_objective\": [{\"annualised_returns\": -0.8161142628554787, \"annualised_returns_over_hodl\": -0.14689661798470455, \"annualised_returns_over_uniform_hodl\": 0.0677756431827583, \"calmar\": -1.4446277391253322, \"daily_log_sharpe\": -2.847682605463989, \"daily_returns\": 0.008532967236451807, \"fee_revenue_over_value\": 0.00010412289508181116, \"jax_sharpe\": -2.402415845784038, \"return\": -0.49440161222175616, \"returns_over_hodl\": -0.061980730226495084, \"returns_over_uniform_hodl\": 0.02676244919408388, \"sharpe\": -2.563643500709178, \"sterling\": -3.660092349923256, \"ulcer\": -0.10776864273884076}], \"train_objective\": [{\"annualised_returns\": -0.03216682805387361, \"annualised_returns_over_hodl\": -0.34279913180004584, \"annualised_returns_over_uniform_hodl\": -0.34279913180004573, \"calmar\": -0.054883861578959865, \"daily_log_sharpe\": -0.09482569417275354, \"daily_returns\": 0.008901066315454974, \"fee_revenue_over_value\": 8.451801994683857e-05, \"jax_sharpe\": 0.29822131286662, \"return\": -0.019654449927912432, \"returns_over_hodl\": -0.2249662136881605, \"returns_over_uniform_hodl\": -0.2249662136881604, \"sharpe\": 0.2452731718659548, \"sterling\": -0.154616010268441, \"ulcer\": -0.10847806923527921}], \"train_return\": -0.019654449927912432, \"train_returns_over_hodl\": -0.2249662136881605, \"train_sharpe\": 0.29822131286661996, \"validation_return\": 0.033117291145951855, \"validation_returns_over_hodl\": 0.0025531835815058024, \"validation_sharpe\": 0.8555351812359692}, {\"centeredness_margin\": 0.7631307873790555, \"continuous_test_metrics\": [{\"annualised_returns\": -0.8313675633384072, \"annualised_returns_over_hodl\": -0.06130615871652745, \"annualised_returns_over_uniform_hodl\": -0.020796222078430016, \"calmar\": -1.4263315894310478, \"daily_log_sharpe\": -2.7843546937168826, \"daily_returns\": 0.007267905027056986, \"fee_revenue_over_value\": 2.6793880138222024e-05, \"jax_sharpe\": -2.336123076444815, \"return\": -0.5117301056880721, \"returns_over_hodl\": -0.02515770627185976, \"returns_over_uniform_hodl\": -0.008428023763124348, \"sharpe\": -2.474224365710967, \"sterling\": -3.5123499689900815, \"ulcer\": -0.11466630103778763}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": 0.21062453340428577, \"optuna_trial_number\": 9, \"price_ratio\": 1.4561284677861681, \"shift_exponent\": 4.120732923893584e-05, \"step\": 9, \"test_objective\": [{\"annualised_returns\": -0.8313675633384072, \"annualised_returns_over_hodl\": -0.06130615871652745, \"annualised_returns_over_uniform_hodl\": -0.020796222078430016, \"calmar\": -1.4263315894310478, \"daily_log_sharpe\": -2.7843546937168826, \"daily_returns\": 0.007267905027056986, \"fee_revenue_over_value\": 2.6793880138222024e-05, \"jax_sharpe\": -2.336123076444815, \"return\": -0.5117301056880721, \"returns_over_hodl\": -0.02515770627185976, \"returns_over_uniform_hodl\": -0.008428023763124348, \"sharpe\": -2.474224365710967, \"sterling\": -3.5123499689900815, \"ulcer\": -0.11466630103778763}], \"train_objective\": [{\"annualised_returns\": 0.5233352528426132, \"annualised_returns_over_hodl\": 0.03441097055463338, \"annualised_returns_over_uniform_hodl\": 0.03441097055463338, \"calmar\": 0.8717387606857198, \"daily_log_sharpe\": 0.5470416706728221, \"daily_returns\": 0.008896608412888241, \"fee_revenue_over_value\": 7.840623754027143e-06, \"jax_sharpe\": 0.9334678617406748, \"return\": 0.29115699566277753, \"returns_over_hodl\": 0.02075262645702658, \"returns_over_uniform_hodl\": 0.02075262645702658, \"sharpe\": 0.918656529354088, \"sterling\": 2.2301241990097074, \"ulcer\": -0.1216028446691469}], \"train_return\": 0.29115699566277753, \"train_returns_over_hodl\": 0.02075262645702658, \"train_sharpe\": 0.9334678617406748, \"validation_return\": 0.06459789759978452, \"validation_returns_over_hodl\": 0.015310709909341247, \"validation_sharpe\": 1.3055843904287245}, {\"centeredness_margin\": 0.7229025600980851, \"continuous_test_metrics\": [{\"annualised_returns\": -0.8305726682491644, \"annualised_returns_over_hodl\": -0.00833144363377658, \"annualised_returns_over_uniform_hodl\": -0.01618047738632289, \"calmar\": -1.4310374333948934, \"daily_log_sharpe\": -2.93342186153626, \"daily_returns\": 0.006825928174585989, \"fee_revenue_over_value\": 3.860423296892394e-05, \"jax_sharpe\": -2.5388268368621496, \"return\": -0.5108044684488182, \"returns_over_hodl\": -0.003363773006633486, \"returns_over_uniform_hodl\": -0.006548252027660517, \"sharpe\": -2.645669231716398, \"sterling\": -3.587478153291932, \"ulcer\": -0.11576585418044211}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": 0.109363146691791, \"optuna_trial_number\": 10, \"price_ratio\": 4.150720577555207, \"shift_exponent\": 0.018197431627551088, \"step\": 10, \"test_objective\": [{\"annualised_returns\": -0.8305726682491644, \"annualised_returns_over_hodl\": -0.00833144363377658, \"annualised_returns_over_uniform_hodl\": -0.01618047738632289, \"calmar\": -1.4310374333948934, \"daily_log_sharpe\": -2.93342186153626, \"daily_returns\": 0.006825928174585989, \"fee_revenue_over_value\": 3.860423296892394e-05, \"jax_sharpe\": -2.5388268368621496, \"return\": -0.5108044684488182, \"returns_over_hodl\": -0.003363773006633486, \"returns_over_uniform_hodl\": -0.006548252027660517, \"sharpe\": -2.645669231716398, \"sterling\": -3.587478153291932, \"ulcer\": -0.11576585418044211}], \"train_objective\": [{\"annualised_returns\": 0.35622993106156775, \"annualised_returns_over_hodl\": -0.07906081956236077, \"annualised_returns_over_uniform_hodl\": -0.07906081956236088, \"calmar\": 0.7203778707493282, \"daily_log_sharpe\": 0.506843808922815, \"daily_returns\": 0.008885038907995583, \"fee_revenue_over_value\": 5.76480075207445e-05, \"jax_sharpe\": 0.8085345671916063, \"return\": 0.20321265600066285, \"returns_over_hodl\": -0.04877370999445341, \"returns_over_uniform_hodl\": -0.04877370999445352, \"sharpe\": 0.7964091268035137, \"sterling\": 1.9023322963516551, \"ulcer\": -0.0911858686724364}], \"train_return\": 0.20321265600066285, \"train_returns_over_hodl\": -0.04877370999445341, \"train_sharpe\": 0.8085345671916063, \"validation_return\": 0.04405967395432664, \"validation_returns_over_hodl\": -0.0019720410781097764, \"validation_sharpe\": 0.8953664533184503}, {\"centeredness_margin\": 0.9114420352685174, \"continuous_test_metrics\": [{\"annualised_returns\": -0.8324789238155123, \"annualised_returns_over_hodl\": -0.017957795166049895, \"annualised_returns_over_uniform_hodl\": -0.02724959723778808, \"calmar\": -1.4314934026989827, \"daily_log_sharpe\": -2.9927582884990453, \"daily_returns\": 0.0068467334942168175, \"fee_revenue_over_value\": 6.447886302139568e-05, \"jax_sharpe\": -2.608169256037157, \"return\": -0.513028639023525, \"returns_over_hodl\": -0.007271443516858045, \"returns_over_uniform_hodl\": -0.011065067907447212, \"sharpe\": -2.710603559079583, \"sterling\": -3.617254339084041, \"ulcer\": -0.11633660778511494}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": 0.11720113603959184, \"optuna_trial_number\": 11, \"price_ratio\": 2.154842091081405, \"shift_exponent\": 0.6420231506041836, \"step\": 11, \"test_objective\": [{\"annualised_returns\": -0.8324789238155123, \"annualised_returns_over_hodl\": -0.017957795166049895, \"annualised_returns_over_uniform_hodl\": -0.02724959723778808, \"calmar\": -1.4314934026989827, \"daily_log_sharpe\": -2.9927582884990453, \"daily_returns\": 0.0068467334942168175, \"fee_revenue_over_value\": 6.447886302139568e-05, \"jax_sharpe\": -2.608169256037157, \"return\": -0.513028639023525, \"returns_over_hodl\": -0.007271443516858045, \"returns_over_uniform_hodl\": -0.011065067907447212, \"sharpe\": -2.710603559079583, \"sterling\": -3.617254339084041, \"ulcer\": -0.11633660778511494}], \"train_objective\": [{\"annualised_returns\": 0.3743402028842202, \"annualised_returns_over_hodl\": -0.0667631563801292, \"annualised_returns_over_uniform_hodl\": -0.0667631563801292, \"calmar\": 0.7641891702213396, \"daily_log_sharpe\": 0.5425108168744578, \"daily_returns\": 0.008886323919519866, \"fee_revenue_over_value\": 0.00011654369347895392, \"jax_sharpe\": 0.831976766898351, \"return\": 0.2129418280465536, \"returns_over_hodl\": -0.04108209855412859, \"returns_over_uniform_hodl\": -0.04108209855412859, \"sharpe\": 0.8294999676552521, \"sterling\": 1.989409224443268, \"ulcer\": -0.09135470256454852}], \"train_return\": 0.2129418280465536, \"train_returns_over_hodl\": -0.04108209855412859, \"train_sharpe\": 0.8319767668983509, \"validation_return\": 0.041488854114796636, \"validation_returns_over_hodl\": -0.007055358052271976, \"validation_sharpe\": 0.8449491216657721}, {\"centeredness_margin\": 0.47563356501562465, \"continuous_test_metrics\": [{\"annualised_returns\": -0.8295566650303866, \"annualised_returns_over_hodl\": -0.05235765407348891, \"annualised_returns_over_uniform_hodl\": -0.010280816503143275, \"calmar\": -1.4282236673345743, \"daily_log_sharpe\": -2.8093273616259338, \"daily_returns\": 0.007271046195127116, \"fee_revenue_over_value\": 8.149692577839692e-05, \"jax_sharpe\": -2.373295944381749, \"return\": -0.5096251242923733, \"returns_over_hodl\": -0.021425622277323808, \"returns_over_uniform_hodl\": -0.004153255675248713, \"sharpe\": -2.5047500718244535, \"sterling\": -3.53380948633333, \"ulcer\": -0.1143083062121382}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": 0.19736351006304206, \"optuna_trial_number\": 12, \"price_ratio\": 1.5454116532695201, \"shift_exponent\": 8.993755337959179e-05, \"step\": 12, \"test_objective\": [{\"annualised_returns\": -0.8295566650303866, \"annualised_returns_over_hodl\": -0.05235765407348891, \"annualised_returns_over_uniform_hodl\": -0.010280816503143275, \"calmar\": -1.4282236673345743, \"daily_log_sharpe\": -2.8093273616259338, \"daily_returns\": 0.007271046195127116, \"fee_revenue_over_value\": 8.149692577839692e-05, \"jax_sharpe\": -2.373295944381749, \"return\": -0.5096251242923733, \"returns_over_hodl\": -0.021425622277323808, \"returns_over_uniform_hodl\": -0.004153255675248713, \"sharpe\": -2.5047500718244535, \"sterling\": -3.53380948633333, \"ulcer\": -0.1143083062121382}], \"train_objective\": [{\"annualised_returns\": 0.513220188233853, \"annualised_returns_over_hodl\": 0.027542401223197066, \"annualised_returns_over_uniform_hodl\": 0.027542401223197288, \"calmar\": 0.8594057294451084, \"daily_log_sharpe\": 0.540247867802316, \"daily_returns\": 0.008895566520347753, \"fee_revenue_over_value\": 1.886959426813164e-05, \"jax_sharpe\": 0.9254775560568712, \"return\": 0.2859451011701135, \"returns_over_hodl\": 0.016632248369718106, \"returns_over_uniform_hodl\": 0.016632248369718328, \"sharpe\": 0.9100386485863743, \"sterling\": 2.200642890282185, \"ulcer\": -0.12065291576672835}], \"train_return\": 0.2859451011701135, \"train_returns_over_hodl\": 0.016632248369718106, \"train_sharpe\": 0.9254775560568713, \"validation_return\": 0.06168325164783228, \"validation_returns_over_hodl\": 0.013092387303135444, \"validation_sharpe\": 1.2496497023257285}, {\"centeredness_margin\": 0.7944991216991398, \"continuous_test_metrics\": [{\"annualised_returns\": -0.830379942839416, \"annualised_returns_over_hodl\": -0.037185652384764456, \"annualised_returns_over_uniform_hodl\": -0.015061372111778937, \"calmar\": -1.428955435365574, \"daily_log_sharpe\": -2.855951347168461, \"daily_returns\": 0.007080654204007596, \"fee_revenue_over_value\": 2.4195548061486552e-05, \"jax_sharpe\": -2.435679552302757, \"return\": -0.510580434959788, \"returns_over_hodl\": -0.015145749117225105, \"returns_over_uniform_hodl\": -0.006093287812072301, \"sharpe\": -2.5570773373925206, \"sterling\": -3.55167878394649, \"ulcer\": -0.11510940243879925}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": 0.18835137403600594, \"optuna_trial_number\": 13, \"price_ratio\": 1.8752691251044875, \"shift_exponent\": 1.6337270838164026e-05, \"step\": 13, \"test_objective\": [{\"annualised_returns\": -0.830379942839416, \"annualised_returns_over_hodl\": -0.037185652384764456, \"annualised_returns_over_uniform_hodl\": -0.015061372111778937, \"calmar\": -1.428955435365574, \"daily_log_sharpe\": -2.855951347168461, \"daily_returns\": 0.007080654204007596, \"fee_revenue_over_value\": 2.4195548061486552e-05, \"jax_sharpe\": -2.435679552302757, \"return\": -0.510580434959788, \"returns_over_hodl\": -0.015145749117225105, \"returns_over_uniform_hodl\": -0.006093287812072301, \"sharpe\": -2.5570773373925206, \"sterling\": -3.55167878394649, \"ulcer\": -0.11510940243879925}], \"train_objective\": [{\"annualised_returns\": 0.5339209879792657, \"annualised_returns_over_hodl\": 0.04159914566993117, \"annualised_returns_over_uniform_hodl\": 0.04159914566993117, \"calmar\": 0.9138946827347675, \"daily_log_sharpe\": 0.5693669913702121, \"daily_returns\": 0.008887533155324047, \"fee_revenue_over_value\": 8.080391229929259e-06, \"jax_sharpe\": 0.9465165666524029, \"return\": 0.29659686884783776, \"returns_over_hodl\": 0.025053238125395394, \"returns_over_uniform_hodl\": 0.025053238125395394, \"sharpe\": 0.9327078526139801, \"sterling\": 2.32768317096358, \"ulcer\": -0.11771272914665726}], \"train_return\": 0.29659686884783776, \"train_returns_over_hodl\": 0.025053238125395394, \"train_sharpe\": 0.946516566652403, \"validation_return\": 0.058730518359496386, \"validation_returns_over_hodl\": 0.009551668152397275, \"validation_sharpe\": 1.1590768230765836}, {\"centeredness_margin\": 0.30899321083127573, \"continuous_test_metrics\": [{\"annualised_returns\": -0.8408276362356205, \"annualised_returns_over_hodl\": -0.24256277861733788, \"annualised_returns_over_uniform_hodl\": -0.07572835319003912, \"calmar\": -1.4297141807861329, \"daily_log_sharpe\": -2.6602481800602953, \"daily_returns\": 0.008967689676384007, \"fee_revenue_over_value\": 5.983282888168915e-05, \"jax_sharpe\": -2.1827855711584343, \"return\": -0.522952174152203, \"returns_over_hodl\": -0.10585422321399374, \"returns_over_uniform_hodl\": -0.031217650430819144, \"sharpe\": -2.3231069409951344, \"sterling\": -3.339834071909653, \"ulcer\": -0.11709584143105936}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": 0.2675741016367101, \"optuna_trial_number\": 14, \"price_ratio\": 1.0784919293760131, \"shift_exponent\": 1.5639783899759538e-05, \"step\": 14, \"test_objective\": [{\"annualised_returns\": -0.8408276362356205, \"annualised_returns_over_hodl\": -0.24256277861733788, \"annualised_returns_over_uniform_hodl\": -0.07572835319003912, \"calmar\": -1.4297141807861329, \"daily_log_sharpe\": -2.6602481800602953, \"daily_returns\": 0.008967689676384007, \"fee_revenue_over_value\": 5.983282888168915e-05, \"jax_sharpe\": -2.1827855711584343, \"return\": -0.522952174152203, \"returns_over_hodl\": -0.10585422321399374, \"returns_over_uniform_hodl\": -0.031217650430819144, \"sharpe\": -2.3231069409951344, \"sterling\": -3.339834071909653, \"ulcer\": -0.11709584143105936}], \"train_objective\": [{\"annualised_returns\": 0.5068015756517763, \"annualised_returns_over_hodl\": 0.02318388378046743, \"annualised_returns_over_uniform_hodl\": 0.02318388378046743, \"calmar\": 0.8217641846806102, \"daily_log_sharpe\": 0.5102613464905734, \"daily_returns\": 0.008964724855355757, \"fee_revenue_over_value\": 1.905222349313506e-05, \"jax_sharpe\": 0.9168076824427702, \"return\": 0.28263074308627023, \"returns_over_hodl\": 0.014012009521563895, \"returns_over_uniform_hodl\": 0.014012009521563895, \"sharpe\": 0.8898544662170093, \"sterling\": 2.11383171580962, \"ulcer\": -0.1256045229553684}], \"train_return\": 0.28263074308627023, \"train_returns_over_hodl\": 0.014012009521563895, \"train_sharpe\": 0.9168076824427701, \"validation_return\": 0.06264546569370699, \"validation_returns_over_hodl\": 0.010742059099857482, \"validation_sharpe\": 1.4175765115053374}, {\"centeredness_margin\": 0.031175537700796896, \"continuous_test_metrics\": [{\"annualised_returns\": -0.8303859637642476, \"annualised_returns_over_hodl\": -0.04040450018609354, \"annualised_returns_over_uniform_hodl\": -0.015096334023368185, \"calmar\": -1.4285912880687461, \"daily_log_sharpe\": -2.8461736855035196, \"daily_returns\": 0.007117763299864432, \"fee_revenue_over_value\": 8.355183772048136e-05, \"jax_sharpe\": -2.4225629293079325, \"return\": -0.510587431677449, \"returns_over_hodl\": -0.01647310205549335, \"returns_over_uniform_hodl\": -0.00610749665279553, \"sharpe\": -2.5459097953535488, \"sterling\": -3.546052398243033, \"ulcer\": -0.11504128141588546}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": 0.19056544524686986, \"optuna_trial_number\": 15, \"price_ratio\": 1.7861766594548845, \"shift_exponent\": 3.505996170227486e-05, \"step\": 15, \"test_objective\": [{\"annualised_returns\": -0.8303859637642476, \"annualised_returns_over_hodl\": -0.04040450018609354, \"annualised_returns_over_uniform_hodl\": -0.015096334023368185, \"calmar\": -1.4285912880687461, \"daily_log_sharpe\": -2.8461736855035196, \"daily_returns\": 0.007117763299864432, \"fee_revenue_over_value\": 8.355183772048136e-05, \"jax_sharpe\": -2.4225629293079325, \"return\": -0.510587431677449, \"returns_over_hodl\": -0.01647310205549335, \"returns_over_uniform_hodl\": -0.00610749665279553, \"sharpe\": -2.5459097953535488, \"sterling\": -3.546052398243033, \"ulcer\": -0.11504128141588546}], \"train_objective\": [{\"annualised_returns\": 0.5334724941426519, \"annualised_returns_over_hodl\": 0.0412945988251352, \"annualised_returns_over_uniform_hodl\": 0.0412945988251352, \"calmar\": 0.9089427943621707, \"daily_log_sharpe\": 0.566366312001129, \"daily_returns\": 0.008889753273739162, \"fee_revenue_over_value\": 6.486395866143006e-05, \"jax_sharpe\": 0.9453184362772594, \"return\": 0.29636669343264965, \"returns_over_hodl\": 0.024871267876704906, \"returns_over_uniform_hodl\": 0.024871267876704906, \"sharpe\": 0.9314073834915214, \"sterling\": 2.31684795965029, \"ulcer\": -0.11835220709545792}], \"train_return\": 0.29636669343264965, \"train_returns_over_hodl\": 0.024871267876704906, \"train_sharpe\": 0.9453184362772595, \"validation_return\": 0.05948825266196245, \"validation_returns_over_hodl\": 0.01035205327694988, \"validation_sharpe\": 1.1777550780186088}, {\"centeredness_margin\": 0.890696589057117, \"continuous_test_metrics\": [{\"annualised_returns\": -0.8281961951378582, \"annualised_returns_over_hodl\": -0.038494364722348906, \"annualised_returns_over_uniform_hodl\": -0.002380929121537423, \"calmar\": -1.4307992942873047, \"daily_log_sharpe\": -2.8517315615792853, \"daily_returns\": 0.007186847518235992, \"fee_revenue_over_value\": 1.603157002112571e-05, \"jax_sharpe\": -2.4295455411715023, \"return\": -0.508052490933995, \"returns_over_hodl\": -0.015685101255447198, \"returns_over_uniform_hodl\": -0.0009595728673814641, \"sharpe\": -2.554789780051911, \"sterling\": -3.5655692515307047, \"ulcer\": -0.1142528145290365}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": 0.18331815309755178, \"optuna_trial_number\": 16, \"price_ratio\": 1.7645234228930982, \"shift_exponent\": 0.0001484542426769082, \"step\": 16, \"test_objective\": [{\"annualised_returns\": -0.8281961951378582, \"annualised_returns_over_hodl\": -0.038494364722348906, \"annualised_returns_over_uniform_hodl\": -0.002380929121537423, \"calmar\": -1.4307992942873047, \"daily_log_sharpe\": -2.8517315615792853, \"daily_returns\": 0.007186847518235992, \"fee_revenue_over_value\": 1.603157002112571e-05, \"jax_sharpe\": -2.4295455411715023, \"return\": -0.508052490933995, \"returns_over_hodl\": -0.015685101255447198, \"returns_over_uniform_hodl\": -0.0009595728673814641, \"sharpe\": -2.554789780051911, \"sterling\": -3.5655692515307047, \"ulcer\": -0.1142528145290365}], \"train_objective\": [{\"annualised_returns\": 0.5147266487111959, \"annualised_returns_over_hodl\": 0.02856535348637257, \"annualised_returns_over_uniform_hodl\": 0.02856535348637257, \"calmar\": 0.8770663334839929, \"daily_log_sharpe\": 0.5482196532804594, \"daily_returns\": 0.008890390843547435, \"fee_revenue_over_value\": 3.57804504468688e-06, \"jax_sharpe\": 0.9288465105367153, \"return\": 0.2867221864351952, \"returns_over_hodl\": 0.01724659025686104, \"returns_over_uniform_hodl\": 0.01724659025686104, \"sharpe\": 0.9128754326458223, \"sterling\": 2.2379019707596086, \"ulcer\": -0.11832993421867889}], \"train_return\": 0.2867221864351952, \"train_returns_over_hodl\": 0.01724659025686104, \"train_sharpe\": 0.9288465105367154, \"validation_return\": 0.05784097426711576, \"validation_returns_over_hodl\": 0.009900191147423243, \"validation_sharpe\": 1.1680028800254914}, {\"centeredness_margin\": 0.2656799080981239, \"continuous_test_metrics\": [{\"annualised_returns\": -0.8367163192308819, \"annualised_returns_over_hodl\": -0.27008278673686836, \"annualised_returns_over_uniform_hodl\": -0.051855027138585585, \"calmar\": -1.434864307160716, \"daily_log_sharpe\": -2.590924862607728, \"daily_returns\": 0.008690524952244583, \"fee_revenue_over_value\": 3.4479039071954005e-05, \"jax_sharpe\": -2.129862816789058, \"return\": -0.5180274670576843, \"returns_over_hodl\": -0.11908275991004891, \"returns_over_uniform_hodl\": -0.02121662107597655, \"sharpe\": -2.248640676411852, \"sterling\": -3.3052734891835978, \"ulcer\": -0.11535250418560554}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": 0.2737221518025831, \"optuna_trial_number\": 17, \"price_ratio\": 1.0259768121803423, \"shift_exponent\": 1.2137654048546867e-05, \"step\": 17, \"test_objective\": [{\"annualised_returns\": -0.8367163192308819, \"annualised_returns_over_hodl\": -0.27008278673686836, \"annualised_returns_over_uniform_hodl\": -0.051855027138585585, \"calmar\": -1.434864307160716, \"daily_log_sharpe\": -2.590924862607728, \"daily_returns\": 0.008690524952244583, \"fee_revenue_over_value\": 3.4479039071954005e-05, \"jax_sharpe\": -2.129862816789058, \"return\": -0.5180274670576843, \"returns_over_hodl\": -0.11908275991004891, \"returns_over_uniform_hodl\": -0.02121662107597655, \"sharpe\": -2.248640676411852, \"sterling\": -3.3052734891835978, \"ulcer\": -0.11535250418560554}], \"train_objective\": [{\"annualised_returns\": 0.5071254690794667, \"annualised_returns_over_hodl\": 0.02340382152185949, \"annualised_returns_over_uniform_hodl\": 0.02340382152185949, \"calmar\": 0.8195659130211637, \"daily_log_sharpe\": 0.49556785678511706, \"daily_returns\": 0.009116787861735958, \"fee_revenue_over_value\": 1.6538638924599666e-05, \"jax_sharpe\": 0.9169477686721593, \"return\": 0.2827981236047856, \"returns_over_hodl\": 0.014144335880377001, \"returns_over_uniform_hodl\": 0.014144335880377001, \"sharpe\": 0.8755625253474213, \"sterling\": 2.1099625633322128, \"ulcer\": -0.12610065819758498}], \"train_return\": 0.2827981236047856, \"train_returns_over_hodl\": 0.014144335880377001, \"train_sharpe\": 0.9169477686721592, \"validation_return\": 0.057912090811726546, \"validation_returns_over_hodl\": 0.0010309555075063148, \"validation_sharpe\": 1.3400769090219544}, {\"centeredness_margin\": 0.04138523717244236, \"continuous_test_metrics\": [{\"annualised_returns\": -0.8443216671210937, \"annualised_returns_over_hodl\": -0.022979868832079253, \"annualised_returns_over_uniform_hodl\": -0.09601726267247679, \"calmar\": -1.411658792890672, \"daily_log_sharpe\": -2.8105941694881467, \"daily_returns\": 0.006522547972040203, \"fee_revenue_over_value\": 0.00016410692208874614, \"jax_sharpe\": -2.371119474378295, \"return\": -0.5271975348692828, \"returns_over_hodl\": -0.009319162729896302, \"returns_over_uniform_hodl\": -0.039839072240995366, \"sharpe\": -2.492843678815763, \"sterling\": -3.426890430803211, \"ulcer\": -0.11916946912692794}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -0.13579331132261385, \"optuna_trial_number\": 18, \"price_ratio\": 1.2615918090830154, \"shift_exponent\": 1.6692119333326882, \"step\": 18, \"test_objective\": [{\"annualised_returns\": -0.8443216671210937, \"annualised_returns_over_hodl\": -0.022979868832079253, \"annualised_returns_over_uniform_hodl\": -0.09601726267247679, \"calmar\": -1.411658792890672, \"daily_log_sharpe\": -2.8105941694881467, \"daily_returns\": 0.006522547972040203, \"fee_revenue_over_value\": 0.00016410692208874614, \"jax_sharpe\": -2.371119474378295, \"return\": -0.5271975348692828, \"returns_over_hodl\": -0.009319162729896302, \"returns_over_uniform_hodl\": -0.039839072240995366, \"sharpe\": -2.492843678815763, \"sterling\": -3.426890430803211, \"ulcer\": -0.11916946912692794}], \"train_objective\": [{\"annualised_returns\": -0.20013697595074642, \"annualised_returns_over_hodl\": -0.456858176508677, \"annualised_returns_over_uniform_hodl\": -0.456858176508677, \"calmar\": -0.34615394214651635, \"daily_log_sharpe\": -0.4008832897824629, \"daily_returns\": 0.008903748995746539, \"fee_revenue_over_value\": 0.000270564118000289, \"jax_sharpe\": -0.017613284125693636, \"return\": -0.12678998244063078, \"returns_over_hodl\": -0.3096645707172321, \"returns_over_uniform_hodl\": -0.3096645707172321, \"sharpe\": -0.09067095860957429, \"sterling\": -0.9891009071533153, \"ulcer\": -0.10418020313166501}], \"train_return\": -0.12678998244063078, \"train_returns_over_hodl\": -0.3096645707172321, \"train_sharpe\": -0.017613284125693636, \"validation_return\": 0.009927500739681072, \"validation_returns_over_hodl\": -0.02770686568836289, \"validation_sharpe\": 0.36131362505245407}, {\"centeredness_margin\": 0.04982162126121692, \"continuous_test_metrics\": [{\"annualised_returns\": -0.8349377480760989, \"annualised_returns_over_hodl\": -0.26213214711353006, \"annualised_returns_over_uniform_hodl\": -0.041527336757399946, \"calmar\": -1.43630401660645, \"daily_log_sharpe\": -2.6204315009690378, \"daily_returns\": 0.008690524952162796, \"fee_revenue_over_value\": 9.220923580248768e-05, \"jax_sharpe\": -2.1456276594345716, \"return\": -0.5159199731076636, \"returns_over_hodl\": -0.11523082306948496, \"returns_over_uniform_hodl\": -0.016936750526361344, \"sharpe\": -2.284876375017495, \"sterling\": -3.359366874381369, \"ulcer\": -0.1137727080723779}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": 0.2661031207517777, \"optuna_trial_number\": 19, \"price_ratio\": 1.0445074986230658, \"shift_exponent\": 8.686066175453515e-05, \"step\": 19, \"test_objective\": [{\"annualised_returns\": -0.8349377480760989, \"annualised_returns_over_hodl\": -0.26213214711353006, \"annualised_returns_over_uniform_hodl\": -0.041527336757399946, \"calmar\": -1.43630401660645, \"daily_log_sharpe\": -2.6204315009690378, \"daily_returns\": 0.008690524952162796, \"fee_revenue_over_value\": 9.220923580248768e-05, \"jax_sharpe\": -2.1456276594345716, \"return\": -0.5159199731076636, \"returns_over_hodl\": -0.11523082306948496, \"returns_over_uniform_hodl\": -0.016936750526361344, \"sharpe\": -2.284876375017495, \"sterling\": -3.359366874381369, \"ulcer\": -0.1137727080723779}], \"train_objective\": [{\"annualised_returns\": 0.4935157280617548, \"annualised_returns_over_hodl\": 0.014162214732508671, \"annualised_returns_over_uniform_hodl\": 0.014162214732508227, \"calmar\": 0.7992113551505089, \"daily_log_sharpe\": 0.4816918922239037, \"daily_returns\": 0.009009579338884598, \"fee_revenue_over_value\": 2.4466804928058268e-05, \"jax_sharpe\": 0.9053457709739269, \"return\": 0.2757526976927125, \"returns_over_hodl\": 0.008574419109283582, \"returns_over_uniform_hodl\": 0.00857441910928336, \"sharpe\": 0.8612970165606822, \"sterling\": 2.056610577679534, \"ulcer\": -0.12575149728211638}], \"train_return\": 0.2757526976927125, \"train_returns_over_hodl\": 0.008574419109283582, \"train_sharpe\": 0.9053457709739269, \"validation_return\": 0.047506325392280946, \"validation_returns_over_hodl\": 0.002909526801248452, \"validation_sharpe\": 1.143781917666264}, {\"centeredness_margin\": 0.25302723256534865, \"continuous_test_metrics\": [{\"annualised_returns\": -0.8258814087500562, \"annualised_returns_over_hodl\": -0.15923509177241246, \"annualised_returns_over_uniform_hodl\": 0.011060420721291209, \"calmar\": -1.440809625730514, \"daily_log_sharpe\": -2.64492019701093, \"daily_returns\": 0.008573992086204987, \"fee_revenue_over_value\": 0.00012796292804829733, \"jax_sharpe\": -2.167553787328244, \"return\": -0.5053937203963805, \"returns_over_hodl\": -0.06746830189241348, \"returns_over_uniform_hodl\": 0.004439822809179539, \"sharpe\": -2.323477453391594, \"sterling\": -3.4578508689896483, \"ulcer\": -0.10956511184455138}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": 0.24587007952664924, \"optuna_trial_number\": 20, \"price_ratio\": 1.1342391766718742, \"shift_exponent\": 0.0001650739827753108, \"step\": 20, \"test_objective\": [{\"annualised_returns\": -0.8258814087500562, \"annualised_returns_over_hodl\": -0.15923509177241246, \"annualised_returns_over_uniform_hodl\": 0.011060420721291209, \"calmar\": -1.440809625730514, \"daily_log_sharpe\": -2.64492019701093, \"daily_returns\": 0.008573992086204987, \"fee_revenue_over_value\": 0.00012796292804829733, \"jax_sharpe\": -2.167553787328244, \"return\": -0.5053937203963805, \"returns_over_hodl\": -0.06746830189241348, \"returns_over_uniform_hodl\": 0.004439822809179539, \"sharpe\": -2.323477453391594, \"sterling\": -3.4578508689896483, \"ulcer\": -0.10956511184455138}], \"train_objective\": [{\"annualised_returns\": 0.47260937588760976, \"annualised_returns_over_hodl\": -3.410876414777775e-05, \"annualised_returns_over_uniform_hodl\": -3.410876414799979e-05, \"calmar\": 0.7682424998667975, \"daily_log_sharpe\": 0.47689089618966357, \"daily_returns\": 0.008930978716784548, \"fee_revenue_over_value\": 2.9999757302441055e-05, \"jax_sharpe\": 0.8874382568571829, \"return\": 0.2648806621044326, \"returns_over_hodl\": -2.0708298886007448e-05, \"returns_over_uniform_hodl\": -2.070829888611847e-05, \"sharpe\": 0.8555801946416176, \"sterling\": 1.973820996287053, \"ulcer\": -0.12533358037953504}], \"train_return\": 0.2648806621044326, \"train_returns_over_hodl\": -2.0708298886007448e-05, \"train_sharpe\": 0.8874382568571829, \"validation_return\": 0.05345050839514376, \"validation_returns_over_hodl\": 0.009044744835050578, \"validation_sharpe\": 1.2480158126680745}, {\"centeredness_margin\": 0.26645620813345267, \"continuous_test_metrics\": [{\"annualised_returns\": -0.8362615665603798, \"annualised_returns_over_hodl\": -0.26804993324089965, \"annualised_returns_over_uniform_hodl\": -0.04921439914440395, \"calmar\": -1.435234329440402, \"daily_log_sharpe\": -2.5814788491358023, \"daily_returns\": 0.008690524952298781, \"fee_revenue_over_value\": 1.7750312087977807e-05, \"jax_sharpe\": -2.1175053120038037, \"return\": -0.5174873145889012, \"returns_over_hodl\": -0.11809550526685553, \"returns_over_uniform_hodl\": -0.02011968666085051, \"sharpe\": -2.238892651864599, \"sterling\": -3.303181195386332, \"ulcer\": -0.11523901061613925}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": 0.27671681674209847, \"optuna_trial_number\": 21, \"price_ratio\": 1.0135446536102861, \"shift_exponent\": 1.0439828724530492e-05, \"step\": 21, \"test_objective\": [{\"annualised_returns\": -0.8362615665603798, \"annualised_returns_over_hodl\": -0.26804993324089965, \"annualised_returns_over_uniform_hodl\": -0.04921439914440395, \"calmar\": -1.435234329440402, \"daily_log_sharpe\": -2.5814788491358023, \"daily_returns\": 0.008690524952298781, \"fee_revenue_over_value\": 1.7750312087977807e-05, \"jax_sharpe\": -2.1175053120038037, \"return\": -0.5174873145889012, \"returns_over_hodl\": -0.11809550526685553, \"returns_over_uniform_hodl\": -0.02011968666085051, \"sharpe\": -2.238892651864599, \"sterling\": -3.303181195386332, \"ulcer\": -0.11523901061613925}], \"train_objective\": [{\"annualised_returns\": 0.5157354159454561, \"annualised_returns_over_hodl\": 0.029250350365364053, \"annualised_returns_over_uniform_hodl\": 0.029250350365364053, \"calmar\": 0.83285098947154, \"daily_log_sharpe\": 0.49676897742369075, \"daily_returns\": 0.009334658504192888, \"fee_revenue_over_value\": 1.3030734387329742e-05, \"jax_sharpe\": 0.9242459607196962, \"return\": 0.28724237430346933, \"returns_over_hodl\": 0.017657836243658576, \"returns_over_uniform_hodl\": 0.017657836243658576, \"sharpe\": 0.8768599921001071, \"sterling\": 2.1445284105786593, \"ulcer\": -0.12622726497005154}], \"train_return\": 0.28724237430346933, \"train_returns_over_hodl\": 0.017657836243658576, \"train_sharpe\": 0.9242459607196962, \"validation_return\": 0.06290366197822661, \"validation_returns_over_hodl\": -0.0020408622660387232, \"validation_sharpe\": 1.4365391815603652}, {\"centeredness_margin\": 0.26394281025053246, \"continuous_test_metrics\": [{\"annualised_returns\": -0.8367693596755121, \"annualised_returns_over_hodl\": -0.27031989023344816, \"annualised_returns_over_uniform_hodl\": -0.05216301891521402, \"calmar\": -1.434821063473087, \"daily_log_sharpe\": -2.620518637002718, \"daily_returns\": 0.008690524952084498, \"fee_revenue_over_value\": 3.436900870613593e-05, \"jax_sharpe\": -2.144307202148997, \"return\": -0.518090526658382, \"returns_over_hodl\": -0.11919801603544744, \"returns_over_uniform_hodl\": -0.02134468167035819, \"sharpe\": -2.2837281909880445, \"sterling\": -3.348440959717062, \"ulcer\": -0.11526058024442395}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": 0.26439624794570793, \"optuna_trial_number\": 22, \"price_ratio\": 1.0216868747837775, \"shift_exponent\": 8.219846550233926e-05, \"step\": 22, \"test_objective\": [{\"annualised_returns\": -0.8367693596755121, \"annualised_returns_over_hodl\": -0.27031989023344816, \"annualised_returns_over_uniform_hodl\": -0.05216301891521402, \"calmar\": -1.434821063473087, \"daily_log_sharpe\": -2.620518637002718, \"daily_returns\": 0.008690524952084498, \"fee_revenue_over_value\": 3.436900870613593e-05, \"jax_sharpe\": -2.144307202148997, \"return\": -0.518090526658382, \"returns_over_hodl\": -0.11919801603544744, \"returns_over_uniform_hodl\": -0.02134468167035819, \"sharpe\": -2.2837281909880445, \"sterling\": -3.348440959717062, \"ulcer\": -0.11526058024442395}], \"train_objective\": [{\"annualised_returns\": 0.48533309450509443, \"annualised_returns_over_hodl\": 0.008605850233463386, \"annualised_returns_over_uniform_hodl\": 0.008605850233463386, \"calmar\": 0.7840975971261747, \"daily_log_sharpe\": 0.4698898558181, \"daily_returns\": 0.009150407875525625, \"fee_revenue_over_value\": 1.3017964430462804e-05, \"jax_sharpe\": 0.8982298566930247, \"return\": 0.27150460801491394, \"returns_over_hodl\": 0.005215998165428948, \"returns_over_uniform_hodl\": 0.005215998165428948, \"sharpe\": 0.8497129325011753, \"sterling\": 2.018794065807877, \"ulcer\": -0.12615354325985875}], \"train_return\": 0.27150460801491394, \"train_returns_over_hodl\": 0.005215998165428948, \"train_sharpe\": 0.8982298566930247, \"validation_return\": 0.04335162795090497, \"validation_returns_over_hodl\": 0.0013850731310534048, \"validation_sharpe\": 1.0635537536970692}, {\"centeredness_margin\": 0.33756654538808095, \"continuous_test_metrics\": [{\"annualised_returns\": -0.8393183852303532, \"annualised_returns_over_hodl\": -0.2817146458052202, \"annualised_returns_over_uniform_hodl\": -0.06696453339684283, \"calmar\": -1.4325858017612998, \"daily_log_sharpe\": -2.638021378920803, \"daily_returns\": 0.008746719131845663, \"fee_revenue_over_value\": 5.266623520448375e-05, \"jax_sharpe\": -2.1663932575488483, \"return\": -0.5211356011671522, \"returns_over_hodl\": -0.12476360007027087, \"returns_over_uniform_hodl\": -0.027528578289060857, \"sharpe\": -2.2995519515343608, \"sterling\": -3.3347852880334203, \"ulcer\": -0.11610009494845351}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": 0.26997417282750924, \"optuna_trial_number\": 23, \"price_ratio\": 1.0570312512335722, \"shift_exponent\": 3.009051056689297e-05, \"step\": 23, \"test_objective\": [{\"annualised_returns\": -0.8393183852303532, \"annualised_returns_over_hodl\": -0.2817146458052202, \"annualised_returns_over_uniform_hodl\": -0.06696453339684283, \"calmar\": -1.4325858017612998, \"daily_log_sharpe\": -2.638021378920803, \"daily_returns\": 0.008746719131845663, \"fee_revenue_over_value\": 5.266623520448375e-05, \"jax_sharpe\": -2.1663932575488483, \"return\": -0.5211356011671522, \"returns_over_hodl\": -0.12476360007027087, \"returns_over_uniform_hodl\": -0.027528578289060857, \"sharpe\": -2.2995519515343608, \"sterling\": -3.3347852880334203, \"ulcer\": -0.11610009494845351}], \"train_objective\": [{\"annualised_returns\": 0.5067358357121368, \"annualised_returns_over_hodl\": 0.023139243498796036, \"annualised_returns_over_uniform_hodl\": 0.023139243498795592, \"calmar\": 0.8215820825501414, \"daily_log_sharpe\": 0.5035618145791341, \"daily_returns\": 0.008971980121942676, \"fee_revenue_over_value\": 1.7715326330700508e-05, \"jax_sharpe\": 0.9166846505828016, \"return\": 0.28259676850340076, \"returns_over_hodl\": 0.01398515016610724, \"returns_over_uniform_hodl\": 0.013985150166107019, \"sharpe\": 0.8831668441209247, \"sterling\": 2.1136201828938135, \"ulcer\": -0.12557262001274}], \"train_return\": 0.28259676850340076, \"train_returns_over_hodl\": 0.01398515016610724, \"train_sharpe\": 0.9166846505828014, \"validation_return\": 0.05554659496591241, \"validation_returns_over_hodl\": 0.00468000998825735, \"validation_sharpe\": 1.2897098171621402}, {\"centeredness_margin\": 0.041099021184069495, \"continuous_test_metrics\": [{\"annualised_returns\": -0.8348590893940556, \"annualised_returns_over_hodl\": -0.261780523939275, \"annualised_returns_over_uniform_hodl\": -0.041070586679253895, \"calmar\": -1.436367226456557, \"daily_log_sharpe\": -2.601074845420591, \"daily_returns\": 0.008690524952105549, \"fee_revenue_over_value\": 5.306512462841181e-05, \"jax_sharpe\": -2.133550838912773, \"return\": -0.5158270814550774, \"returns_over_hodl\": -0.1150610419039001, \"returns_over_uniform_hodl\": -0.016748107399672696, \"sharpe\": -2.263057556863442, \"sterling\": -3.332379297694621, \"ulcer\": -0.11446641275011771}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": 0.26718816296236353, \"optuna_trial_number\": 24, \"price_ratio\": 1.0218340687138447, \"shift_exponent\": 6.695955834663245e-05, \"step\": 24, \"test_objective\": [{\"annualised_returns\": -0.8348590893940556, \"annualised_returns_over_hodl\": -0.261780523939275, \"annualised_returns_over_uniform_hodl\": -0.041070586679253895, \"calmar\": -1.436367226456557, \"daily_log_sharpe\": -2.601074845420591, \"daily_returns\": 0.008690524952105549, \"fee_revenue_over_value\": 5.306512462841181e-05, \"jax_sharpe\": -2.133550838912773, \"return\": -0.5158270814550774, \"returns_over_hodl\": -0.1150610419039001, \"returns_over_uniform_hodl\": -0.016748107399672696, \"sharpe\": -2.263057556863442, \"sterling\": -3.332379297694621, \"ulcer\": -0.11446641275011771}], \"train_objective\": [{\"annualised_returns\": 0.4915692210098297, \"annualised_returns_over_hodl\": 0.012840451683293619, \"annualised_returns_over_uniform_hodl\": 0.012840451683293397, \"calmar\": 0.7942196407244396, \"daily_log_sharpe\": 0.47560150967863557, \"daily_returns\": 0.009190325394765192, \"fee_revenue_over_value\": 1.5406109809694515e-05, \"jax_sharpe\": 0.9036181226407076, \"return\": 0.2747429812041944, \"returns_over_hodl\": 0.007776165480101183, \"returns_over_uniform_hodl\": 0.0077761654801009605, \"sharpe\": 0.8554405403877194, \"sterling\": 2.044827819837126, \"ulcer\": -0.1261429069928364}], \"train_return\": 0.2747429812041944, \"train_returns_over_hodl\": 0.007776165480101183, \"train_sharpe\": 0.9036181226407076, \"validation_return\": 0.04780932594607279, \"validation_returns_over_hodl\": 0.0025765036003297936, \"validation_sharpe\": 1.1523624884437755}, {\"centeredness_margin\": 0.27674216922827, \"continuous_test_metrics\": [{\"annualised_returns\": -0.8350006258933965, \"annualised_returns_over_hodl\": -0.12045924971793887, \"annualised_returns_over_uniform_hodl\": -0.041892451544709775, \"calmar\": -1.4299787958238046, \"daily_log_sharpe\": -2.699816135460676, \"daily_returns\": 0.007752934896805338, \"fee_revenue_over_value\": 0.00010053430025066562, \"jax_sharpe\": -2.2287100227836056, \"return\": -0.5159942474279848, \"returns_over_hodl\": -0.05038018321117055, \"returns_over_uniform_hodl\": -0.01708758582348391, \"sharpe\": -2.3755870242809594, \"sterling\": -3.4309673273908037, \"ulcer\": -0.11431348993935464}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": 0.2464128424783917, \"optuna_trial_number\": 25, \"price_ratio\": 1.1951309882002557, \"shift_exponent\": 1.656932351464279e-05, \"step\": 25, \"test_objective\": [{\"annualised_returns\": -0.8350006258933965, \"annualised_returns_over_hodl\": -0.12045924971793887, \"annualised_returns_over_uniform_hodl\": -0.041892451544709775, \"calmar\": -1.4299787958238046, \"daily_log_sharpe\": -2.699816135460676, \"daily_returns\": 0.007752934896805338, \"fee_revenue_over_value\": 0.00010053430025066562, \"jax_sharpe\": -2.2287100227836056, \"return\": -0.5159942474279848, \"returns_over_hodl\": -0.05038018321117055, \"returns_over_uniform_hodl\": -0.01708758582348391, \"sharpe\": -2.3755870242809594, \"sterling\": -3.4309673273908037, \"ulcer\": -0.11431348993935464}], \"train_objective\": [{\"annualised_returns\": 0.5010971137688645, \"annualised_returns_over_hodl\": 0.01931030576027526, \"annualised_returns_over_uniform_hodl\": 0.01931030576027526, \"calmar\": 0.8183875226088874, \"daily_log_sharpe\": 0.5164636662273501, \"daily_returns\": 0.008907751709353658, \"fee_revenue_over_value\": 2.8049545334460462e-05, \"jax_sharpe\": 0.9121857663529829, \"return\": 0.2796804903758925, \"returns_over_hodl\": 0.011679622203099704, \"returns_over_uniform_hodl\": 0.011679622203099704, \"sharpe\": 0.894358495892406, \"sterling\": 2.0980907811333025, \"ulcer\": -0.12479985866670507}], \"train_return\": 0.2796804903758925, \"train_returns_over_hodl\": 0.011679622203099704, \"train_sharpe\": 0.9121857663529829, \"validation_return\": 0.06943133757473197, \"validation_returns_over_hodl\": 0.019072876174397013, \"validation_sharpe\": 1.4860864751821796}, {\"centeredness_margin\": 0.5766589290569646, \"continuous_test_metrics\": [{\"annualised_returns\": -0.8351752984866373, \"annualised_returns_over_hodl\": -0.14394483902323485, \"annualised_returns_over_uniform_hodl\": -0.042906728907872616, \"calmar\": -1.4308786542438452, \"daily_log_sharpe\": -2.67757493196577, \"daily_returns\": 0.008010582865414817, \"fee_revenue_over_value\": 4.1638472781808464e-05, \"jax_sharpe\": -2.2030468379647234, \"return\": -0.5162006678996902, \"returns_over_hodl\": -0.060674955691102817, \"returns_over_uniform_hodl\": -0.017506781758865442, \"sharpe\": -2.35007317318637, \"sterling\": -3.4124820163064955, \"ulcer\": -0.11431607748042086}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": 0.2525279206784929, \"optuna_trial_number\": 26, \"price_ratio\": 1.1565539070217534, \"shift_exponent\": 3.532791434709187e-05, \"step\": 26, \"test_objective\": [{\"annualised_returns\": -0.8351752984866373, \"annualised_returns_over_hodl\": -0.14394483902323485, \"annualised_returns_over_uniform_hodl\": -0.042906728907872616, \"calmar\": -1.4308786542438452, \"daily_log_sharpe\": -2.67757493196577, \"daily_returns\": 0.008010582865414817, \"fee_revenue_over_value\": 4.1638472781808464e-05, \"jax_sharpe\": -2.2030468379647234, \"return\": -0.5162006678996902, \"returns_over_hodl\": -0.060674955691102817, \"returns_over_uniform_hodl\": -0.017506781758865442, \"sharpe\": -2.35007317318637, \"sterling\": -3.4124820163064955, \"ulcer\": -0.11431607748042086}], \"train_objective\": [{\"annualised_returns\": 0.4981972320805068, \"annualised_returns_over_hodl\": 0.017341159818073493, \"annualised_returns_over_uniform_hodl\": 0.017341159818073493, \"calmar\": 0.8115640547791028, \"daily_log_sharpe\": 0.5103515854192056, \"daily_returns\": 0.008923437904402077, \"fee_revenue_over_value\": 1.2665268155448411e-05, \"jax_sharpe\": 0.909603771440941, \"return\": 0.27817903131176025, \"returns_over_hodl\": 0.010492610640308264, \"returns_over_uniform_hodl\": 0.010492610640308264, \"sharpe\": 0.888879557799301, \"sterling\": 2.0825400888039995, \"ulcer\": -0.12509295255164846}], \"train_return\": 0.27817903131176025, \"train_returns_over_hodl\": 0.010492610640308264, \"train_sharpe\": 0.909603771440941, \"validation_return\": 0.06412743329555948, \"validation_returns_over_hodl\": 0.014365214930549985, \"validation_sharpe\": 1.4132401062725959}, {\"centeredness_margin\": 0.5264588216508843, \"continuous_test_metrics\": [{\"annualised_returns\": -0.8366834534574517, \"annualised_returns_over_hodl\": -0.15966807307200837, \"annualised_returns_over_uniform_hodl\": -0.0516641843200607, \"calmar\": -1.4301284691533327, \"daily_log_sharpe\": -2.667389736830322, \"daily_returns\": 0.008113818075082272, \"fee_revenue_over_value\": 4.39721763454675e-05, \"jax_sharpe\": -2.1834680226768057, \"return\": -0.5179883991925279, \"returns_over_hodl\": -0.06766174238436684, \"returns_over_uniform_hodl\": -0.02113728257751102, \"sharpe\": -2.336249871769828, \"sterling\": -3.380308827510557, \"ulcer\": -0.11516151723994186}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": 0.2589282033340461, \"optuna_trial_number\": 27, \"price_ratio\": 1.13426578679329, \"shift_exponent\": 1.6946863122309983e-05, \"step\": 27, \"test_objective\": [{\"annualised_returns\": -0.8366834534574517, \"annualised_returns_over_hodl\": -0.15966807307200837, \"annualised_returns_over_uniform_hodl\": -0.0516641843200607, \"calmar\": -1.4301284691533327, \"daily_log_sharpe\": -2.667389736830322, \"daily_returns\": 0.008113818075082272, \"fee_revenue_over_value\": 4.39721763454675e-05, \"jax_sharpe\": -2.1834680226768057, \"return\": -0.5179883991925279, \"returns_over_hodl\": -0.06766174238436684, \"returns_over_uniform_hodl\": -0.02113728257751102, \"sharpe\": -2.336249871769828, \"sterling\": -3.380308827510557, \"ulcer\": -0.11516151723994186}], \"train_objective\": [{\"annualised_returns\": 0.5010690622569229, \"annualised_returns_over_hodl\": 0.019291257562159103, \"annualised_returns_over_uniform_hodl\": 0.019291257562159103, \"calmar\": 0.8147305824902266, \"daily_log_sharpe\": 0.5122729860432876, \"daily_returns\": 0.008923674377136525, \"fee_revenue_over_value\": 1.4039637887266487e-05, \"jax_sharpe\": 0.9120090562738159, \"return\": 0.2796659717284955, \"returns_over_hodl\": 0.011668144166336436, \"returns_over_uniform_hodl\": 0.011668144166336436, \"sharpe\": 0.8911261047767407, \"sterling\": 2.09307275963574, \"ulcer\": -0.12529255139122086}], \"train_return\": 0.2796659717284955, \"train_returns_over_hodl\": 0.011668144166336436, \"train_sharpe\": 0.9120090562738161, \"validation_return\": 0.06584793553635704, \"validation_returns_over_hodl\": 0.015260133793472352, \"validation_sharpe\": 1.4507001735317573}, {\"centeredness_margin\": 0.5783715474177578, \"continuous_test_metrics\": [{\"annualised_returns\": -0.8352357998349492, \"annualised_returns_over_hodl\": -0.14783798379922197, \"annualised_returns_over_uniform_hodl\": -0.043258044170873866, \"calmar\": -1.4310179704588453, \"daily_log_sharpe\": -2.6706866976458876, \"daily_returns\": 0.00804662412865197, \"fee_revenue_over_value\": 4.0873440922118606e-05, \"jax_sharpe\": -2.193812694252443, \"return\": -0.5162721962240374, \"returns_over_hodl\": -0.06239772835962465, \"returns_over_uniform_hodl\": -0.017652040524060664, \"sharpe\": -2.341846515768623, \"sterling\": -3.3996412905528066, \"ulcer\": -0.11439317456361317}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": 0.2552016851132779, \"optuna_trial_number\": 28, \"price_ratio\": 1.1476713750825196, \"shift_exponent\": 2.9908358166649875e-05, \"step\": 28, \"test_objective\": [{\"annualised_returns\": -0.8352357998349492, \"annualised_returns_over_hodl\": -0.14783798379922197, \"annualised_returns_over_uniform_hodl\": -0.043258044170873866, \"calmar\": -1.4310179704588453, \"daily_log_sharpe\": -2.6706866976458876, \"daily_returns\": 0.00804662412865197, \"fee_revenue_over_value\": 4.0873440922118606e-05, \"jax_sharpe\": -2.193812694252443, \"return\": -0.5162721962240374, \"returns_over_hodl\": -0.06239772835962465, \"returns_over_uniform_hodl\": -0.017652040524060664, \"sharpe\": -2.341846515768623, \"sterling\": -3.3996412905528066, \"ulcer\": -0.11439317456361317}], \"train_objective\": [{\"annualised_returns\": 0.49936760813158165, \"annualised_returns_over_hodl\": 0.018135896120963668, \"annualised_returns_over_uniform_hodl\": 0.018135896120963224, \"calmar\": 0.812920292575683, \"daily_log_sharpe\": 0.5111380300900709, \"daily_returns\": 0.008931383912843862, \"fee_revenue_over_value\": 1.2962697531382203e-05, \"jax_sharpe\": 0.9105821133537252, \"return\": 0.2787851490990052, \"returns_over_hodl\": 0.010971790418871974, \"returns_over_uniform_hodl\": 0.010971790418871752, \"sharpe\": 0.889791333487875, \"sterling\": 2.086923801933755, \"ulcer\": -0.12516146219890004}], \"train_return\": 0.2787851490990052, \"train_returns_over_hodl\": 0.010971790418871974, \"train_sharpe\": 0.9105821133537251, \"validation_return\": 0.06483211025336799, \"validation_returns_over_hodl\": 0.014620033747324568, \"validation_sharpe\": 1.4289774882403106}, {\"centeredness_margin\": 0.36574733028065565, \"continuous_test_metrics\": [{\"annualised_returns\": -0.8355589475187567, \"annualised_returns_over_hodl\": -0.014830237235032406, \"annualised_returns_over_uniform_hodl\": -0.04513447695613848, \"calmar\": -1.4237954856732375, \"daily_log_sharpe\": -2.9134522106330825, \"daily_returns\": 0.006731888243908098, \"fee_revenue_over_value\": 8.587458044916075e-05, \"jax_sharpe\": -2.5178951397165443, \"return\": -0.5166545067175949, \"returns_over_hodl\": -0.005999360061407999, \"returns_over_uniform_hodl\": -0.018428431565263748, \"sharpe\": -2.618765071304071, \"sterling\": -3.55038169671286, \"ulcer\": -0.11671153948139021}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": 0.017813645279616763, \"optuna_trial_number\": 29, \"price_ratio\": 1.7227595513532332, \"shift_exponent\": 27.497006657394774, \"step\": 29, \"test_objective\": [{\"annualised_returns\": -0.8355589475187567, \"annualised_returns_over_hodl\": -0.014830237235032406, \"annualised_returns_over_uniform_hodl\": -0.04513447695613848, \"calmar\": -1.4237954856732375, \"daily_log_sharpe\": -2.9134522106330825, \"daily_returns\": 0.006731888243908098, \"fee_revenue_over_value\": 8.587458044916075e-05, \"jax_sharpe\": -2.5178951397165443, \"return\": -0.5166545067175949, \"returns_over_hodl\": -0.005999360061407999, \"returns_over_uniform_hodl\": -0.018428431565263748, \"sharpe\": -2.618765071304071, \"sterling\": -3.55038169671286, \"ulcer\": -0.11671153948139021}], \"train_objective\": [{\"annualised_returns\": 0.09825861633562916, \"annualised_returns_over_hodl\": -0.2542345756629709, \"annualised_returns_over_uniform_hodl\": -0.2542345756629709, \"calmar\": 0.18556672006114275, \"daily_log_sharpe\": 0.13617596248998173, \"daily_returns\": 0.008890769852780995, \"fee_revenue_over_value\": 0.00014943808440530311, \"jax_sharpe\": 0.45714692564945697, \"return\": 0.05855309133769926, \"returns_over_hodl\": -0.16313751785660457, \"returns_over_uniform_hodl\": -0.16313751785660457, \"sharpe\": 0.42871162284596265, \"sterling\": 0.5091671571429425, \"ulcer\": -0.09747715036734546}], \"train_return\": 0.05855309133769926, \"train_returns_over_hodl\": -0.16313751785660457, \"train_sharpe\": 0.457146925649457, \"validation_return\": 0.03244130606319118, \"validation_returns_over_hodl\": -0.010955883499512709, \"validation_sharpe\": 0.7250152730039906}, {\"centeredness_margin\": 0.3546898076134619, \"continuous_test_metrics\": [{\"annualised_returns\": -0.8077105574656179, \"annualised_returns_over_hodl\": -0.14042007509467247, \"annualised_returns_over_uniform_hodl\": 0.11657372870651339, \"calmar\": -1.4559723785061727, \"daily_log_sharpe\": -2.7735563220001627, \"daily_returns\": 0.008690524951294248, \"fee_revenue_over_value\": 0.00013124243626909884, \"jax_sharpe\": -2.3057886566287285, \"return\": -0.4852198731422589, \"returns_over_hodl\": -0.05911922851443796, \"returns_over_uniform_hodl\": 0.045408602213979465, \"sharpe\": -2.489374974932905, \"sterling\": -3.767307794161374, \"ulcer\": -0.09907246854663462}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": 0.0498210856295795, \"optuna_trial_number\": 30, \"price_ratio\": 1.0989523650436248, \"shift_exponent\": 0.0018664512347820682, \"step\": 30, \"test_objective\": [{\"annualised_returns\": -0.8077105574656179, \"annualised_returns_over_hodl\": -0.14042007509467247, \"annualised_returns_over_uniform_hodl\": 0.11657372870651339, \"calmar\": -1.4559723785061727, \"daily_log_sharpe\": -2.7735563220001627, \"daily_returns\": 0.008690524951294248, \"fee_revenue_over_value\": 0.00013124243626909884, \"jax_sharpe\": -2.3057886566287285, \"return\": -0.4852198731422589, \"returns_over_hodl\": -0.05911922851443796, \"returns_over_uniform_hodl\": 0.045408602213979465, \"sharpe\": -2.489374974932905, \"sterling\": -3.767307794161374, \"ulcer\": -0.09907246854663462}], \"train_objective\": [{\"annualised_returns\": 0.038045037108545454, \"annualised_returns_over_hodl\": -0.29512221796799065, \"annualised_returns_over_uniform_hodl\": -0.29512221796799076, \"calmar\": 0.06168095193185533, \"daily_log_sharpe\": 0.007502303808807395, \"daily_returns\": 0.008948423336017211, \"fee_revenue_over_value\": 1.519631337547053e-05, \"jax_sharpe\": 0.42680241255461393, \"return\": 0.022928313685890878, \"returns_over_hodl\": -0.19130147117690888, \"returns_over_uniform_hodl\": -0.191301471176909, \"sharpe\": 0.37933198845525207, \"sterling\": 0.1614901207366151, \"ulcer\": -0.12495324149207149}], \"train_return\": 0.022928313685890878, \"train_returns_over_hodl\": -0.19130147117690888, \"train_sharpe\": 0.4268024125546139, \"validation_return\": 0.030486270473979227, \"validation_returns_over_hodl\": -4.0983438864827804e-11, \"validation_sharpe\": 0.8017265750341069}, {\"centeredness_margin\": 0.2424189025288776, \"continuous_test_metrics\": [{\"annualised_returns\": -0.8229176798681792, \"annualised_returns_over_hodl\": -0.025185474465655444, \"annualised_returns_over_uniform_hodl\": 0.028270007295036814, \"calmar\": -1.4378898457671208, \"daily_log_sharpe\": -2.9186479179399267, \"daily_returns\": 0.0072593456733821005, \"fee_revenue_over_value\": 5.373434952411137e-05, \"jax_sharpe\": -2.522522969659071, \"return\": -0.5020202098665205, \"returns_over_hodl\": -0.010220471436380718, \"returns_over_uniform_hodl\": 0.011290702910364603, \"sharpe\": -2.6363767328031047, \"sterling\": -3.639202592863523, \"ulcer\": -0.11253873534245971}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": 0.04642914973219929, \"optuna_trial_number\": 31, \"price_ratio\": 2.7595208347475806, \"shift_exponent\": 49.236204519494265, \"step\": 31, \"test_objective\": [{\"annualised_returns\": -0.8229176798681792, \"annualised_returns_over_hodl\": -0.025185474465655444, \"annualised_returns_over_uniform_hodl\": 0.028270007295036814, \"calmar\": -1.4378898457671208, \"daily_log_sharpe\": -2.9186479179399267, \"daily_returns\": 0.0072593456733821005, \"fee_revenue_over_value\": 5.373434952411137e-05, \"jax_sharpe\": -2.522522969659071, \"return\": -0.5020202098665205, \"returns_over_hodl\": -0.010220471436380718, \"returns_over_uniform_hodl\": 0.011290702910364603, \"sharpe\": -2.6363767328031047, \"sterling\": -3.639202592863523, \"ulcer\": -0.11253873534245971}], \"train_objective\": [{\"annualised_returns\": 0.18588399971970682, \"annualised_returns_over_hodl\": -0.19473312468400483, \"annualised_returns_over_uniform_hodl\": -0.19473312468400483, \"calmar\": 0.3473047003232584, \"daily_log_sharpe\": 0.2515339697993838, \"daily_returns\": 0.00888660232400238, \"fee_revenue_over_value\": 9.250240754939277e-05, \"jax_sharpe\": 0.5853825127216776, \"return\": 0.10905378612394334, \"returns_over_hodl\": -0.12321308030631095, \"returns_over_uniform_hodl\": -0.12321308030631095, \"sharpe\": 0.5547856797162176, \"sterling\": 0.9495264328989064, \"ulcer\": -0.09879932388809971}], \"train_return\": 0.10905378612394334, \"train_returns_over_hodl\": -0.12321308030631095, \"train_sharpe\": 0.5853825127216776, \"validation_return\": 0.033583603278537044, \"validation_returns_over_hodl\": -0.007183000691897701, \"validation_sharpe\": 0.7702219236281578}, {\"centeredness_margin\": 0.14419405874049318, \"continuous_test_metrics\": [{\"annualised_returns\": -0.8218626967777318, \"annualised_returns_over_hodl\": -0.041216444755332815, \"annualised_returns_over_uniform_hodl\": 0.03439601394156644, \"calmar\": -1.4371348215477078, \"daily_log_sharpe\": -2.866300909229338, \"daily_returns\": 0.007459450831085553, \"fee_revenue_over_value\": 8.814638707957089e-05, \"jax_sharpe\": -2.454153571531999, \"return\": -0.5008275035282774, \"returns_over_hodl\": -0.01680834265820419, \"returns_over_uniform_hodl\": 0.013712835003002688, \"sharpe\": -2.57790393980204, \"sterling\": -3.619147479999706, \"ulcer\": -0.11162832483044058}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -0.0524043468371231, \"optuna_trial_number\": 32, \"price_ratio\": 1.7320909332763945, \"shift_exponent\": 0.027671836158132887, \"step\": 32, \"test_objective\": [{\"annualised_returns\": -0.8218626967777318, \"annualised_returns_over_hodl\": -0.041216444755332815, \"annualised_returns_over_uniform_hodl\": 0.03439601394156644, \"calmar\": -1.4371348215477078, \"daily_log_sharpe\": -2.866300909229338, \"daily_returns\": 0.007459450831085553, \"fee_revenue_over_value\": 8.814638707957089e-05, \"jax_sharpe\": -2.454153571531999, \"return\": -0.5008275035282774, \"returns_over_hodl\": -0.01680834265820419, \"returns_over_uniform_hodl\": 0.013712835003002688, \"sharpe\": -2.57790393980204, \"sterling\": -3.619147479999706, \"ulcer\": -0.11162832483044058}], \"train_objective\": [{\"annualised_returns\": -0.025050276677797467, \"annualised_returns_over_hodl\": -0.3379666835243351, \"annualised_returns_over_uniform_hodl\": -0.337966683524335, \"calmar\": -0.044187600077762644, \"daily_log_sharpe\": -0.08482508620103081, \"daily_returns\": 0.00889352342180917, \"fee_revenue_over_value\": 0.00012648358402192345, \"jax_sharpe\": 0.28978520013231335, \"return\": -0.015284281545304812, \"returns_over_hodl\": -0.2215112807329963, \"returns_over_uniform_hodl\": -0.2215112807329962, \"sharpe\": 0.23206651360065217, \"sterling\": -0.12512359678345883, \"ulcer\": -0.10239800709048946}], \"train_return\": -0.015284281545304812, \"train_returns_over_hodl\": -0.2215112807329963, \"train_sharpe\": 0.28978520013231335, \"validation_return\": 0.025240008018478255, \"validation_returns_over_hodl\": -0.009332515763111893, \"validation_sharpe\": 0.646864832284423}, {\"centeredness_margin\": 0.2334887956992607, \"continuous_test_metrics\": [{\"annualised_returns\": -0.8147764284709256, \"annualised_returns_over_hodl\": -0.045539209045002416, \"annualised_returns_over_uniform_hodl\": 0.07554409218060298, \"calmar\": -1.4434996962912425, \"daily_log_sharpe\": -2.866645570847533, \"daily_returns\": 0.007779029907874285, \"fee_revenue_over_value\": 9.493500468523606e-05, \"jax_sharpe\": -2.4567230703018987, \"return\": -0.49292338410661607, \"returns_over_hodl\": -0.018596012572387965, \"returns_over_uniform_hodl\": 0.02976441509559402, \"sharpe\": -2.5855320180841552, \"sterling\": -3.6712632210462486, \"ulcer\": -0.10878917528277374}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": 0.12629552323766205, \"optuna_trial_number\": 33, \"price_ratio\": 1.6599637743830482, \"shift_exponent\": 0.001126540626839893, \"step\": 33, \"test_objective\": [{\"annualised_returns\": -0.8147764284709256, \"annualised_returns_over_hodl\": -0.045539209045002416, \"annualised_returns_over_uniform_hodl\": 0.07554409218060298, \"calmar\": -1.4434996962912425, \"daily_log_sharpe\": -2.866645570847533, \"daily_returns\": 0.007779029907874285, \"fee_revenue_over_value\": 9.493500468523606e-05, \"jax_sharpe\": -2.4567230703018987, \"return\": -0.49292338410661607, \"returns_over_hodl\": -0.018596012572387965, \"returns_over_uniform_hodl\": 0.02976441509559402, \"sharpe\": -2.5855320180841552, \"sterling\": -3.6712632210462486, \"ulcer\": -0.10878917528277374}], \"train_objective\": [{\"annualised_returns\": 0.36108031269531016, \"annualised_returns_over_hodl\": -0.07576719922241437, \"annualised_returns_over_uniform_hodl\": -0.07576719922241437, \"calmar\": 0.6111672676735843, \"daily_log_sharpe\": 0.39206859214101264, \"daily_returns\": 0.00889449115044861, \"fee_revenue_over_value\": 4.7702107926604345e-05, \"jax_sharpe\": 0.7868460494958401, \"return\": 0.20582334895749366, \"returns_over_hodl\": -0.04670976912474534, \"returns_over_uniform_hodl\": -0.04670976912474534, \"sharpe\": 0.7557080866112884, \"sterling\": 1.5736663553199273, \"ulcer\": -0.11861053398721383}], \"train_return\": 0.20582334895749366, \"train_returns_over_hodl\": -0.04670976912474534, \"train_sharpe\": 0.7868460494958401, \"validation_return\": 0.04691820605849606, \"validation_returns_over_hodl\": 0.0062444118698492534, \"validation_sharpe\": 1.076222548343492}, {\"centeredness_margin\": 0.2801610104496445, \"continuous_test_metrics\": [{\"annualised_returns\": -0.8331205390652725, \"annualised_returns_over_hodl\": -0.0693202706045507, \"annualised_returns_over_uniform_hodl\": -0.030975286606780505, \"calmar\": -1.4258124588230068, \"daily_log_sharpe\": -2.786379394691744, \"daily_returns\": 0.007267504995874629, \"fee_revenue_over_value\": 9.772175017283864e-05, \"jax_sharpe\": -2.3348864572008337, \"return\": -0.5137806575294952, \"returns_over_hodl\": -0.02851816926090467, \"returns_over_uniform_hodl\": -0.012592257039561239, \"sharpe\": -2.4747109180264335, \"sterling\": -3.506753830835507, \"ulcer\": -0.11501821830654217}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": 0.21387140180994596, \"optuna_trial_number\": 34, \"price_ratio\": 1.4469589444743778, \"shift_exponent\": 1.518276193497397e-05, \"step\": 34, \"test_objective\": [{\"annualised_returns\": -0.8331205390652725, \"annualised_returns_over_hodl\": -0.0693202706045507, \"annualised_returns_over_uniform_hodl\": -0.030975286606780505, \"calmar\": -1.4258124588230068, \"daily_log_sharpe\": -2.786379394691744, \"daily_returns\": 0.007267504995874629, \"fee_revenue_over_value\": 9.772175017283864e-05, \"jax_sharpe\": -2.3348864572008337, \"return\": -0.5137806575294952, \"returns_over_hodl\": -0.02851816926090467, \"returns_over_uniform_hodl\": -0.012592257039561239, \"sharpe\": -2.4747109180264335, \"sterling\": -3.506753830835507, \"ulcer\": -0.11501821830654217}], \"train_objective\": [{\"annualised_returns\": 0.5277319562020988, \"annualised_returns_over_hodl\": 0.03739652359086709, \"annualised_returns_over_uniform_hodl\": 0.037396523590867536, \"calmar\": 0.8785114537647406, \"daily_log_sharpe\": 0.5510349613048624, \"daily_returns\": 0.008894998307345652, \"fee_revenue_over_value\": 2.7526801243209327e-05, \"jax_sharpe\": 0.9370815784634957, \"return\": 0.2934182024676346, \"returns_over_hodl\": 0.02254027334487496, \"returns_over_uniform_hodl\": 0.022540273344875184, \"sharpe\": 0.9229760725924991, \"sterling\": 2.2462788891197607, \"ulcer\": -0.1217193636942749}], \"train_return\": 0.2934182024676346, \"train_returns_over_hodl\": 0.02254027334487496, \"train_sharpe\": 0.9370815784634958, \"validation_return\": 0.06569250662968762, \"validation_returns_over_hodl\": 0.015991431295024228, \"validation_sharpe\": 1.3203732475572376}, {\"centeredness_margin\": 0.36424502137433135, \"continuous_test_metrics\": [{\"annualised_returns\": -0.837846399419047, \"annualised_returns_over_hodl\": -0.2751345162043617, \"annualised_returns_over_uniform_hodl\": -0.058417102688891664, \"calmar\": -1.4339390643416807, \"daily_log_sharpe\": -2.6030260416255215, \"daily_returns\": 0.008690524952252464, \"fee_revenue_over_value\": 3.358622969255911e-05, \"jax_sharpe\": -2.1414083472479137, \"return\": -0.5193736720058442, \"returns_over_hodl\": -0.12154326349990774, \"returns_over_uniform_hodl\": -0.023950476094311668, \"sharpe\": -2.2611391415056867, \"sterling\": -3.309910434268811, \"ulcer\": -0.11584146441965554}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": 0.27326218850888606, \"optuna_trial_number\": 35, \"price_ratio\": 1.0337817169078793, \"shift_exponent\": 1.1654255830763121e-05, \"step\": 35, \"test_objective\": [{\"annualised_returns\": -0.837846399419047, \"annualised_returns_over_hodl\": -0.2751345162043617, \"annualised_returns_over_uniform_hodl\": -0.058417102688891664, \"calmar\": -1.4339390643416807, \"daily_log_sharpe\": -2.6030260416255215, \"daily_returns\": 0.008690524952252464, \"fee_revenue_over_value\": 3.358622969255911e-05, \"jax_sharpe\": -2.1414083472479137, \"return\": -0.5193736720058442, \"returns_over_hodl\": -0.12154326349990774, \"returns_over_uniform_hodl\": -0.023950476094311668, \"sharpe\": -2.2611391415056867, \"sterling\": -3.309910434268811, \"ulcer\": -0.11584146441965554}], \"train_objective\": [{\"annualised_returns\": 0.5089286418686425, \"annualised_returns_over_hodl\": 0.024628254365153923, \"annualised_returns_over_uniform_hodl\": 0.024628254365153923, \"calmar\": 0.82354620790388, \"daily_log_sharpe\": 0.5004906116625754, \"daily_returns\": 0.00905248694613863, \"fee_revenue_over_value\": 1.5328531305747444e-05, \"jax_sharpe\": 0.9184757798600741, \"return\": 0.2837297027478205, \"returns_over_hodl\": 0.01488081630855076, \"returns_over_uniform_hodl\": 0.01488081630855076, \"sharpe\": 0.8803272539064485, \"sterling\": 2.1195929688117783, \"ulcer\": -0.12587831648673486}], \"train_return\": 0.2837297027478205, \"train_returns_over_hodl\": 0.01488081630855076, \"train_sharpe\": 0.918475779860074, \"validation_return\": 0.05748391468604841, \"validation_returns_over_hodl\": 0.0016326388526684, \"validation_sharpe\": 1.3278335139345334}, {\"centeredness_margin\": 0.3508386103838136, \"continuous_test_metrics\": [{\"annualised_returns\": -0.8409515442358118, \"annualised_returns_over_hodl\": -0.23707571380067116, \"annualised_returns_over_uniform_hodl\": -0.07644785404233068, \"calmar\": -1.4294699211967248, \"daily_log_sharpe\": -2.661022116559123, \"daily_returns\": 0.008879568731008399, \"fee_revenue_over_value\": 5.5651070689894164e-05, \"jax_sharpe\": -2.18385389827413, \"return\": -0.5231017691294477, \"returns_over_hodl\": -0.10325114475761599, \"returns_over_uniform_hodl\": -0.03152144591145334, \"sharpe\": -2.3237334799801075, \"sterling\": -3.3387170855853117, \"ulcer\": -0.11711234324062875}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": 0.2678373522429586, \"optuna_trial_number\": 36, \"price_ratio\": 1.080568941655359, \"shift_exponent\": 1.1388162428130138e-05, \"step\": 36, \"test_objective\": [{\"annualised_returns\": -0.8409515442358118, \"annualised_returns_over_hodl\": -0.23707571380067116, \"annualised_returns_over_uniform_hodl\": -0.07644785404233068, \"calmar\": -1.4294699211967248, \"daily_log_sharpe\": -2.661022116559123, \"daily_returns\": 0.008879568731008399, \"fee_revenue_over_value\": 5.5651070689894164e-05, \"jax_sharpe\": -2.18385389827413, \"return\": -0.5231017691294477, \"returns_over_hodl\": -0.10325114475761599, \"returns_over_uniform_hodl\": -0.03152144591145334, \"sharpe\": -2.3237334799801075, \"sterling\": -3.3387170855853117, \"ulcer\": -0.11711234324062875}], \"train_objective\": [{\"annualised_returns\": 0.5085093732020689, \"annualised_returns_over_hodl\": 0.02434355268343036, \"annualised_returns_over_uniform_hodl\": 0.02434355268343036, \"calmar\": 0.8247049409526682, \"daily_log_sharpe\": 0.5124027980118229, \"daily_returns\": 0.008960779035999903, \"fee_revenue_over_value\": 1.8400730884437214e-05, \"jax_sharpe\": 0.9182570640386873, \"return\": 0.2835131336273229, \"returns_over_hodl\": 0.014709602816078693, \"returns_over_uniform_hodl\": 0.014709602816078693, \"sharpe\": 0.8919528378061428, \"sterling\": 2.1212528703113542, \"ulcer\": -0.12557152396326576}], \"train_return\": 0.2835131336273229, \"train_returns_over_hodl\": 0.014709602816078693, \"train_sharpe\": 0.9182570640386871, \"validation_return\": 0.06393170085493716, \"validation_returns_over_hodl\": 0.011770160801770846, \"validation_sharpe\": 1.4400088119228285}, {\"centeredness_margin\": 0.32718550439888994, \"continuous_test_metrics\": [{\"annualised_returns\": -0.8336799986151874, \"annualised_returns_over_hodl\": -0.07193047821087539, \"annualised_returns_over_uniform_hodl\": -0.034223919643912804, \"calmar\": -1.4259757480973994, \"daily_log_sharpe\": -2.7869917211505197, \"daily_returns\": 0.00725381026311497, \"fee_revenue_over_value\": 9.679725395392679e-05, \"jax_sharpe\": -2.336676458122122, \"return\": -0.5144377943600846, \"returns_over_hodl\": -0.029616406159897135, \"returns_over_uniform_hodl\": -0.013926761733289661, \"sharpe\": -2.474844043163772, \"sterling\": -3.50443027620235, \"ulcer\": -0.11499382633960124}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": 0.21574798549352767, \"optuna_trial_number\": 37, \"price_ratio\": 1.4373516492194178, \"shift_exponent\": 1.0089672192731767e-05, \"step\": 37, \"test_objective\": [{\"annualised_returns\": -0.8336799986151874, \"annualised_returns_over_hodl\": -0.07193047821087539, \"annualised_returns_over_uniform_hodl\": -0.034223919643912804, \"calmar\": -1.4259757480973994, \"daily_log_sharpe\": -2.7869917211505197, \"daily_returns\": 0.00725381026311497, \"fee_revenue_over_value\": 9.679725395392679e-05, \"jax_sharpe\": -2.336676458122122, \"return\": -0.5144377943600846, \"returns_over_hodl\": -0.029616406159897135, \"returns_over_uniform_hodl\": -0.013926761733289661, \"sharpe\": -2.474844043163772, \"sterling\": -3.50443027620235, \"ulcer\": -0.11499382633960124}], \"train_objective\": [{\"annualised_returns\": 0.5293394727333098, \"annualised_returns_over_hodl\": 0.03848809731512115, \"annualised_returns_over_uniform_hodl\": 0.03848809731512115, \"calmar\": 0.8807761110294758, \"daily_log_sharpe\": 0.5522095318105846, \"daily_returns\": 0.008894602737109928, \"fee_revenue_over_value\": 2.339954118602609e-05, \"jax_sharpe\": 0.938358522447105, \"return\": 0.2942443032825328, \"returns_over_hodl\": 0.023193365555472356, \"returns_over_uniform_hodl\": 0.023193365555472356, \"sharpe\": 0.9243729463915784, \"sterling\": 2.2514775093059476, \"ulcer\": -0.12182450034553229}], \"train_return\": 0.2942443032825328, \"train_returns_over_hodl\": 0.023193365555472356, \"train_sharpe\": 0.9383585224471049, \"validation_return\": 0.06626621977470304, \"validation_returns_over_hodl\": 0.016492791826545394, \"validation_sharpe\": 1.331201281763685}, {\"centeredness_margin\": 0.3075523740347971, \"continuous_test_metrics\": [{\"annualised_returns\": -0.8353890945632636, \"annualised_returns_over_hodl\": -0.13300489540046967, \"annualised_returns_over_uniform_hodl\": -0.044148185949482, \"calmar\": -1.4301819220472456, \"daily_log_sharpe\": -2.6809401051876, \"daily_returns\": 0.007891888145408612, \"fee_revenue_over_value\": 8.492416820679642e-05, \"jax_sharpe\": -2.2067970510581394, \"return\": -0.5164535007073439, \"returns_over_hodl\": -0.055858780342318726, \"returns_over_uniform_hodl\": -0.018020231245846174, \"sharpe\": -2.353281639387032, \"sterling\": -3.4096806859446547, \"ulcer\": -0.11450101363032586}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": 0.25158891386231624, \"optuna_trial_number\": 38, \"price_ratio\": 1.1668255010026958, \"shift_exponent\": 1.2619562767880982e-05, \"step\": 38, \"test_objective\": [{\"annualised_returns\": -0.8353890945632636, \"annualised_returns_over_hodl\": -0.13300489540046967, \"annualised_returns_over_uniform_hodl\": -0.044148185949482, \"calmar\": -1.4301819220472456, \"daily_log_sharpe\": -2.6809401051876, \"daily_returns\": 0.007891888145408612, \"fee_revenue_over_value\": 8.492416820679642e-05, \"jax_sharpe\": -2.2067970510581394, \"return\": -0.5164535007073439, \"returns_over_hodl\": -0.055858780342318726, \"returns_over_uniform_hodl\": -0.018020231245846174, \"sharpe\": -2.353281639387032, \"sterling\": -3.4096806859446547, \"ulcer\": -0.11450101363032586}], \"train_objective\": [{\"annualised_returns\": 0.5016991959075687, \"annualised_returns_over_hodl\": 0.019719145750217715, \"annualised_returns_over_uniform_hodl\": 0.019719145750217715, \"calmar\": 0.8179085540289237, \"daily_log_sharpe\": 0.5155565569353623, \"daily_returns\": 0.008916170694524704, \"fee_revenue_over_value\": 2.9344293401981403e-05, \"jax_sharpe\": 0.9126346638537802, \"return\": 0.27999208490215133, \"returns_over_hodl\": 0.01192596012493019, \"returns_over_uniform_hodl\": 0.01192596012493019, \"sharpe\": 0.8939172808798403, \"sterling\": 2.099840058179112, \"ulcer\": -0.12499648262657598}], \"train_return\": 0.27999208490215133, \"train_returns_over_hodl\": 0.01192596012493019, \"train_sharpe\": 0.9126346638537802, \"validation_return\": 0.06647896155578703, \"validation_returns_over_hodl\": 0.01597877030006245, \"validation_sharpe\": 1.4446243890908872}, {\"centeredness_margin\": 0.34825637738524456, \"continuous_test_metrics\": [{\"annualised_returns\": -0.8404229905240047, \"annualised_returns_over_hodl\": -0.24903972265181507, \"annualised_returns_over_uniform_hodl\": -0.0733786829997829, \"calmar\": -1.430273972949315, \"daily_log_sharpe\": -2.6570660267760213, \"daily_returns\": 0.009057000809718337, \"fee_revenue_over_value\": 5.6527352381074186e-05, \"jax_sharpe\": -2.1799475050877066, \"return\": -0.522464126496788, \"returns_over_hodl\": -0.10894143859487182, \"returns_over_uniform_hodl\": -0.03022652977436424, \"sharpe\": -2.3199268167356943, \"sterling\": -3.3397756890451116, \"ulcer\": -0.11683558267525805}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": 0.2679214206351387, \"optuna_trial_number\": 39, \"price_ratio\": 1.0754497926740432, \"shift_exponent\": 2.0432739938245116e-05, \"step\": 39, \"test_objective\": [{\"annualised_returns\": -0.8404229905240047, \"annualised_returns_over_hodl\": -0.24903972265181507, \"annualised_returns_over_uniform_hodl\": -0.0733786829997829, \"calmar\": -1.430273972949315, \"daily_log_sharpe\": -2.6570660267760213, \"daily_returns\": 0.009057000809718337, \"fee_revenue_over_value\": 5.6527352381074186e-05, \"jax_sharpe\": -2.1799475050877066, \"return\": -0.522464126496788, \"returns_over_hodl\": -0.10894143859487182, \"returns_over_uniform_hodl\": -0.03022652977436424, \"sharpe\": -2.3199268167356943, \"sterling\": -3.3397756890451116, \"ulcer\": -0.11683558267525805}], \"train_objective\": [{\"annualised_returns\": 0.5070037312430065, \"annualised_returns_over_hodl\": 0.02332115622980968, \"annualised_returns_over_uniform_hodl\": 0.02332115622980968, \"calmar\": 0.822191455545669, \"daily_log_sharpe\": 0.5093651328076374, \"daily_returns\": 0.008967896735494462, \"fee_revenue_over_value\": 1.8561219268102855e-05, \"jax_sharpe\": 0.916965316185233, \"return\": 0.28273521400707735, \"returns_over_hodl\": 0.014094601311067656, \"returns_over_uniform_hodl\": 0.014094601311067656, \"sharpe\": 0.8889230197485417, \"sterling\": 2.11496491524274, \"ulcer\": -0.12557392900272024}], \"train_return\": 0.28273521400707735, \"train_returns_over_hodl\": 0.014094601311067656, \"train_sharpe\": 0.9169653161852329, \"validation_return\": 0.06139158698774194, \"validation_returns_over_hodl\": 0.010228330922488205, \"validation_sharpe\": 1.3966020058894195}, {\"centeredness_margin\": 0.32277724762761556, \"continuous_test_metrics\": [{\"annualised_returns\": -0.8379169904276723, \"annualised_returns_over_hodl\": -0.2754500749381291, \"annualised_returns_over_uniform_hodl\": -0.0588270059299385, \"calmar\": -1.4338809968758643, \"daily_log_sharpe\": -2.624558637339173, \"daily_returns\": 0.00869052495219643, \"fee_revenue_over_value\": 4.578836381493638e-05, \"jax_sharpe\": -2.1494763509947403, \"return\": -0.5194579490385254, \"returns_over_hodl\": -0.12169729943531715, \"returns_over_uniform_hodl\": -0.024121624766020977, \"sharpe\": -2.286266090819585, \"sterling\": -3.344304117151131, \"ulcer\": -0.11523867747786726}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": 0.26925765359260956, \"optuna_trial_number\": 40, \"price_ratio\": 1.0415385584555679, \"shift_exponent\": 5.7254842338588133e-05, \"step\": 40, \"test_objective\": [{\"annualised_returns\": -0.8379169904276723, \"annualised_returns_over_hodl\": -0.2754500749381291, \"annualised_returns_over_uniform_hodl\": -0.0588270059299385, \"calmar\": -1.4338809968758643, \"daily_log_sharpe\": -2.624558637339173, \"daily_returns\": 0.00869052495219643, \"fee_revenue_over_value\": 4.578836381493638e-05, \"jax_sharpe\": -2.1494763509947403, \"return\": -0.5194579490385254, \"returns_over_hodl\": -0.12169729943531715, \"returns_over_uniform_hodl\": -0.024121624766020977, \"sharpe\": -2.286266090819585, \"sterling\": -3.344304117151131, \"ulcer\": -0.11523867747786726}], \"train_objective\": [{\"annualised_returns\": 0.5002734463005729, \"annualised_returns_over_hodl\": 0.018750999682574987, \"annualised_returns_over_uniform_hodl\": 0.018750999682574987, \"calmar\": 0.8099864218123098, \"daily_log_sharpe\": 0.489996951639544, \"daily_returns\": 0.009021545641419512, \"fee_revenue_over_value\": 1.3614178960922425e-05, \"jax_sharpe\": 0.9111276347547683, \"return\": 0.2792541396328685, \"returns_over_hodl\": 0.011342561224306547, \"returns_over_uniform_hodl\": 0.011342561224306547, \"sharpe\": 0.8696761541545905, \"sterling\": 2.084435557779764, \"ulcer\": -0.12578582325305482}], \"train_return\": 0.2792541396328685, \"train_returns_over_hodl\": 0.011342561224306547, \"train_sharpe\": 0.9111276347547683, \"validation_return\": 0.051262819020999384, \"validation_returns_over_hodl\": 0.0032307400342324044, \"validation_sharpe\": 1.214930287423765}, {\"centeredness_margin\": 0.3543019819510827, \"continuous_test_metrics\": [{\"annualised_returns\": -0.8330349159357333, \"annualised_returns_over_hodl\": -0.13797315579024605, \"annualised_returns_over_uniform_hodl\": -0.030478095831493524, \"calmar\": -1.4327794673946008, \"daily_log_sharpe\": -2.6734996012276664, \"daily_returns\": 0.008030077580106917, \"fee_revenue_over_value\": 8.449002977159206e-05, \"jax_sharpe\": -2.1963191625903256, \"return\": -0.51368020142599, \"returns_over_hodl\": -0.05804146964801005, \"returns_over_uniform_hodl\": -0.012388252127036203, \"sharpe\": -2.3477020370180988, \"sterling\": -3.4237749748852524, \"ulcer\": -0.11326818956999965}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": 0.2502107702364681, \"optuna_trial_number\": 41, \"price_ratio\": 1.1589816466030654, \"shift_exponent\": 5.496440847786953e-05, \"step\": 41, \"test_objective\": [{\"annualised_returns\": -0.8330349159357333, \"annualised_returns_over_hodl\": -0.13797315579024605, \"annualised_returns_over_uniform_hodl\": -0.030478095831493524, \"calmar\": -1.4327794673946008, \"daily_log_sharpe\": -2.6734996012276664, \"daily_returns\": 0.008030077580106917, \"fee_revenue_over_value\": 8.449002977159206e-05, \"jax_sharpe\": -2.1963191625903256, \"return\": -0.51368020142599, \"returns_over_hodl\": -0.05804146964801005, \"returns_over_uniform_hodl\": -0.012388252127036203, \"sharpe\": -2.3477020370180988, \"sterling\": -3.4237749748852524, \"ulcer\": -0.11326818956999965}], \"train_objective\": [{\"annualised_returns\": 0.494707261038678, \"annualised_returns_over_hodl\": 0.014971317509330673, \"annualised_returns_over_uniform_hodl\": 0.014971317509330673, \"calmar\": 0.8060070634668675, \"daily_log_sharpe\": 0.5063307277854058, \"daily_returns\": 0.00892619782202271, \"fee_revenue_over_value\": 2.515546309804574e-05, \"jax_sharpe\": 0.9066179908132338, \"return\": 0.2763705294803207, \"returns_over_hodl\": 0.009062859649068944, \"returns_over_uniform_hodl\": 0.009062859649068944, \"sharpe\": 0.8847914636147434, \"sterling\": 2.0685883332353923, \"ulcer\": -0.12507460903270706}], \"train_return\": 0.2763705294803207, \"train_returns_over_hodl\": 0.009062859649068944, \"train_sharpe\": 0.9066179908132338, \"validation_return\": 0.06266166340105328, \"validation_returns_over_hodl\": 0.013652367399159315, \"validation_sharpe\": 1.3899948721177398}, {\"centeredness_margin\": 0.3896953399973982, \"continuous_test_metrics\": [{\"annualised_returns\": -0.8227234851284226, \"annualised_returns_over_hodl\": -0.01992733154209736, \"annualised_returns_over_uniform_hodl\": 0.02939764457874472, \"calmar\": -1.4385292625120358, \"daily_log_sharpe\": -2.9341925793312957, \"daily_returns\": 0.007216255347303855, \"fee_revenue_over_value\": 4.298836004599449e-05, \"jax_sharpe\": -2.542271758684213, \"return\": -0.5018003458144731, \"returns_over_hodl\": -0.008073762549881458, \"returns_over_uniform_hodl\": 0.011737199889047423, \"sharpe\": -2.654236650051916, \"sterling\": -3.64848477407704, \"ulcer\": -0.11263857541031942}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": 0.14989925125908363, \"optuna_trial_number\": 42, \"price_ratio\": 3.8156983518900685, \"shift_exponent\": 0.0009946084822200464, \"step\": 42, \"test_objective\": [{\"annualised_returns\": -0.8227234851284226, \"annualised_returns_over_hodl\": -0.01992733154209736, \"annualised_returns_over_uniform_hodl\": 0.02939764457874472, \"calmar\": -1.4385292625120358, \"daily_log_sharpe\": -2.9341925793312957, \"daily_returns\": 0.007216255347303855, \"fee_revenue_over_value\": 4.298836004599449e-05, \"jax_sharpe\": -2.542271758684213, \"return\": -0.5018003458144731, \"returns_over_hodl\": -0.008073762549881458, \"returns_over_uniform_hodl\": 0.011737199889047423, \"sharpe\": -2.654236650051916, \"sterling\": -3.64848477407704, \"ulcer\": -0.11263857541031942}], \"train_objective\": [{\"annualised_returns\": 0.45857871146785256, \"annualised_returns_over_hodl\": -0.009561540872664476, \"annualised_returns_over_uniform_hodl\": -0.009561540872664476, \"calmar\": 0.8520736870449754, \"daily_log_sharpe\": 0.5588489376683345, \"daily_returns\": 0.008885244385443978, \"fee_revenue_over_value\": 3.398509313574425e-05, \"jax_sharpe\": 0.9016432238744928, \"return\": 0.2575501984204771, \"returns_over_hodl\": -0.0058159679641948125, \"returns_over_uniform_hodl\": -0.0058159679641948125, \"sharpe\": 0.8820061902917039, \"sterling\": 2.2319258887727793, \"ulcer\": -0.10226185591858819}], \"train_return\": 0.2575501984204771, \"train_returns_over_hodl\": -0.0058159679641948125, \"train_sharpe\": 0.9016432238744927, \"validation_return\": 0.05069039310034107, \"validation_returns_over_hodl\": 0.004624077212784794, \"validation_sharpe\": 1.0360919005540623}, {\"centeredness_margin\": 0.17965021963531358, \"continuous_test_metrics\": [{\"annualised_returns\": -0.8296941043267907, \"annualised_returns_over_hodl\": -0.020843746248927153, \"annualised_returns_over_uniform_hodl\": -0.011078889999191599, \"calmar\": -1.4314711197825227, \"daily_log_sharpe\": -2.9140823784257113, \"daily_returns\": 0.006951029160017879, \"fee_revenue_over_value\": 4.416671465191439e-05, \"jax_sharpe\": -2.514347850676355, \"return\": -0.509784413423154, \"returns_over_hodl\": -0.008447405577164835, \"returns_over_uniform_hodl\": -0.004476737913333029, \"sharpe\": -2.624101340460753, \"sterling\": -3.587057603349221, \"ulcer\": -0.11537973350084013}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": 0.1848539247156815, \"optuna_trial_number\": 43, \"price_ratio\": 3.5919672095887107, \"shift_exponent\": 1.2866204121814264e-05, \"step\": 43, \"test_objective\": [{\"annualised_returns\": -0.8296941043267907, \"annualised_returns_over_hodl\": -0.020843746248927153, \"annualised_returns_over_uniform_hodl\": -0.011078889999191599, \"calmar\": -1.4314711197825227, \"daily_log_sharpe\": -2.9140823784257113, \"daily_returns\": 0.006951029160017879, \"fee_revenue_over_value\": 4.416671465191439e-05, \"jax_sharpe\": -2.514347850676355, \"return\": -0.509784413423154, \"returns_over_hodl\": -0.008447405577164835, \"returns_over_uniform_hodl\": -0.004476737913333029, \"sharpe\": -2.624101340460753, \"sterling\": -3.587057603349221, \"ulcer\": -0.11537973350084013}], \"train_objective\": [{\"annualised_returns\": 0.5477772571825077, \"annualised_returns_over_hodl\": 0.05100815583236673, \"annualised_returns_over_uniform_hodl\": 0.051008155832366286, \"calmar\": 1.0034630045359936, \"daily_log_sharpe\": 0.6342182659178693, \"daily_returns\": 0.008885109064618714, \"fee_revenue_over_value\": 5.6699788109140306e-05, \"jax_sharpe\": 0.9828472494074714, \"return\": 0.30369519106672405, \"returns_over_hodl\": 0.03066497323793871, \"returns_over_uniform_hodl\": 0.030664973237938487, \"sharpe\": 0.9661403155922857, \"sterling\": 2.598126094691133, \"ulcer\": -0.10507146024006378}], \"train_return\": 0.30369519106672405, \"train_returns_over_hodl\": 0.03066497323793871, \"train_sharpe\": 0.9828472494074713, \"validation_return\": 0.05386109543149131, \"validation_returns_over_hodl\": 0.005061953133945085, \"validation_sharpe\": 1.051768258672148}, {\"centeredness_margin\": 0.4956390571108398, \"continuous_test_metrics\": [{\"annualised_returns\": -0.8031490201735354, \"annualised_returns_over_hodl\": -0.12002890951278655, \"annualised_returns_over_uniform_hodl\": 0.14306136440676087, \"calmar\": -1.4588578197072049, \"daily_log_sharpe\": -2.5692331050911736, \"daily_returns\": 0.008690524952359922, \"fee_revenue_over_value\": 4.557027842062713e-05, \"jax_sharpe\": -2.057962838717037, \"return\": -0.48033615103904215, \"returns_over_hodl\": -0.050193087126162994, \"returns_over_uniform_hodl\": 0.0553263997962008, \"sharpe\": -2.2619792711878293, \"sterling\": -3.5737717088417837, \"ulcer\": -0.09892803751595598}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": 0.1999675668536461, \"optuna_trial_number\": 44, \"price_ratio\": 1.0246671970129546, \"shift_exponent\": 0.0005244297020637258, \"step\": 44, \"test_objective\": [{\"annualised_returns\": -0.8031490201735354, \"annualised_returns_over_hodl\": -0.12002890951278655, \"annualised_returns_over_uniform_hodl\": 0.14306136440676087, \"calmar\": -1.4588578197072049, \"daily_log_sharpe\": -2.5692331050911736, \"daily_returns\": 0.008690524952359922, \"fee_revenue_over_value\": 4.557027842062713e-05, \"jax_sharpe\": -2.057962838717037, \"return\": -0.48033615103904215, \"returns_over_hodl\": -0.050193087126162994, \"returns_over_uniform_hodl\": 0.0553263997962008, \"sharpe\": -2.2619792711878293, \"sterling\": -3.5737717088417837, \"ulcer\": -0.09892803751595598}], \"train_objective\": [{\"annualised_returns\": 0.33621209420463694, \"annualised_returns_over_hodl\": -0.09265380246808863, \"annualised_returns_over_uniform_hodl\": -0.09265380246808863, \"calmar\": 0.5427742486643841, \"daily_log_sharpe\": 0.33641141836383875, \"daily_returns\": 0.009098270242687384, \"fee_revenue_over_value\": 1.0421662109074028e-05, \"jax_sharpe\": 0.762239003010411, \"return\": 0.19239911809385646, \"returns_over_hodl\": -0.057322590770953696, \"returns_over_uniform_hodl\": -0.057322590770953696, \"sharpe\": 0.7165142800843302, \"sterling\": 1.3976998386042823, \"ulcer\": -0.1262809314986471}], \"train_return\": 0.19239911809385646, \"train_returns_over_hodl\": -0.057322590770953696, \"train_sharpe\": 0.762239003010411, \"validation_return\": 0.030486270507650515, \"validation_returns_over_hodl\": 1.134869975771835e-12, \"validation_sharpe\": 0.8017265757971355}, {\"centeredness_margin\": 0.7587167236717354, \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -Infinity, \"optuna_trial_number\": 45, \"price_ratio\": 1.7240148426959157, \"shift_exponent\": 88.52173587636129, \"step\": 45, \"test_objective\": -Infinity, \"train_objective\": -Infinity, \"train_return\": -Infinity, \"train_returns_over_hodl\": -Infinity, \"train_sharpe\": -Infinity, \"validation_return\": -Infinity, \"validation_returns_over_hodl\": -Infinity, \"validation_sharpe\": -Infinity}, {\"centeredness_margin\": 0.28585824911907054, \"continuous_test_metrics\": [{\"annualised_returns\": -0.8392577063544111, \"annualised_returns_over_hodl\": -0.2814433966753487, \"annualised_returns_over_uniform_hodl\": -0.06661218727803697, \"calmar\": -1.4327720375308524, \"daily_log_sharpe\": -2.6287469193149335, \"daily_returns\": 0.008690524952248424, \"fee_revenue_over_value\": 5.160322171636549e-05, \"jax_sharpe\": -2.155288589319053, \"return\": -0.5210627800496945, \"returns_over_hodl\": -0.12463050248926821, \"returns_over_uniform_hodl\": -0.02738069413689348, \"sharpe\": -2.2893963573792218, \"sterling\": -3.334498488947675, \"ulcer\": -0.11600885254160236}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": 0.27059999997384493, \"optuna_trial_number\": 46, \"price_ratio\": 1.044897403115403, \"shift_exponent\": 3.681037916507494e-05, \"step\": 46, \"test_objective\": [{\"annualised_returns\": -0.8392577063544111, \"annualised_returns_over_hodl\": -0.2814433966753487, \"annualised_returns_over_uniform_hodl\": -0.06661218727803697, \"calmar\": -1.4327720375308524, \"daily_log_sharpe\": -2.6287469193149335, \"daily_returns\": 0.008690524952248424, \"fee_revenue_over_value\": 5.160322171636549e-05, \"jax_sharpe\": -2.155288589319053, \"return\": -0.5210627800496945, \"returns_over_hodl\": -0.12463050248926821, \"returns_over_uniform_hodl\": -0.02738069413689348, \"sharpe\": -2.2893963573792218, \"sterling\": -3.334498488947675, \"ulcer\": -0.11600885254160236}], \"train_objective\": [{\"annualised_returns\": 0.5031893321864263, \"annualised_returns_over_hodl\": 0.02073101317178172, \"annualised_returns_over_uniform_hodl\": 0.02073101317178172, \"calmar\": 0.8146816223420116, \"daily_log_sharpe\": 0.49577199741766637, \"daily_returns\": 0.008999278938317524, \"fee_revenue_over_value\": 1.769087290033203e-05, \"jax_sharpe\": 0.9136356431080943, \"return\": 0.2807630615605117, \"returns_over_hodl\": 0.012535472718365304, \"returns_over_uniform_hodl\": 0.012535472718365304, \"sharpe\": 0.8755246579118057, \"sterling\": 2.096533249110199, \"ulcer\": -0.12579447110196254}], \"train_return\": 0.2807630615605117, \"train_returns_over_hodl\": 0.012535472718365304, \"train_sharpe\": 0.9136356431080943, \"validation_return\": 0.053421079207566224, \"validation_returns_over_hodl\": 0.0030190659439219836, \"validation_sharpe\": 1.2524560975803911}, {\"centeredness_margin\": 0.2545511269367291, \"continuous_test_metrics\": [{\"annualised_returns\": -0.8417232942490541, \"annualised_returns_over_hodl\": -0.23398920136220447, \"annualised_returns_over_uniform_hodl\": -0.0809292014244739, \"calmar\": -1.4285263128791987, \"daily_log_sharpe\": -2.668430016111086, \"daily_returns\": 0.008833732404336159, \"fee_revenue_over_value\": 6.718096335962129e-05, \"jax_sharpe\": -2.1836295067284106, \"return\": -0.5240350788833026, \"returns_over_hodl\": -0.1017918065314034, \"returns_over_uniform_hodl\": -0.03341679888703497, \"sharpe\": -2.3315484252884553, \"sterling\": -3.3426090822896337, \"ulcer\": -0.11747438601626717}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": 0.26687079302170613, \"optuna_trial_number\": 47, \"price_ratio\": 1.0844866114611427, \"shift_exponent\": 1.0443433908728175e-05, \"step\": 47, \"test_objective\": [{\"annualised_returns\": -0.8417232942490541, \"annualised_returns_over_hodl\": -0.23398920136220447, \"annualised_returns_over_uniform_hodl\": -0.0809292014244739, \"calmar\": -1.4285263128791987, \"daily_log_sharpe\": -2.668430016111086, \"daily_returns\": 0.008833732404336159, \"fee_revenue_over_value\": 6.718096335962129e-05, \"jax_sharpe\": -2.1836295067284106, \"return\": -0.5240350788833026, \"returns_over_hodl\": -0.1017918065314034, \"returns_over_uniform_hodl\": -0.03341679888703497, \"sharpe\": -2.3315484252884553, \"sterling\": -3.3426090822896337, \"ulcer\": -0.11747438601626717}], \"train_objective\": [{\"annualised_returns\": 0.5069474800255247, \"annualised_returns_over_hodl\": 0.02328295920367096, \"annualised_returns_over_uniform_hodl\": 0.023282959203671183, \"calmar\": 0.8221007520235338, \"daily_log_sharpe\": 0.5118190383605998, \"daily_returns\": 0.00895913836231686, \"fee_revenue_over_value\": 2.2064981521593356e-05, \"jax_sharpe\": 0.9169376665256113, \"return\": 0.28270614479024725, \"returns_over_hodl\": 0.01407162000087192, \"returns_over_uniform_hodl\": 0.014071620000872143, \"sharpe\": 0.891363783431784, \"sterling\": 2.1145217582318954, \"ulcer\": -0.12559649317467336}], \"train_return\": 0.28270614479024725, \"train_returns_over_hodl\": 0.01407162000087192, \"train_sharpe\": 0.9169376665256113, \"validation_return\": 0.06435200557791765, \"validation_returns_over_hodl\": 0.012666775740830749, \"validation_sharpe\": 1.4468791182517067}, {\"centeredness_margin\": 0.36972517591482373, \"continuous_test_metrics\": [{\"annualised_returns\": -0.8329829470501537, \"annualised_returns_over_hodl\": -0.2533937176911457, \"annualised_returns_over_uniform_hodl\": -0.030176326313710478, \"calmar\": -1.4378633700679249, \"daily_log_sharpe\": -2.645228856862493, \"daily_returns\": 0.008690524951785865, \"fee_revenue_over_value\": 3.932969393059401e-05, \"jax_sharpe\": -2.156850905708826, \"return\": -0.5136192446836376, \"returns_over_hodl\": -0.11102570514463272, \"returns_over_uniform_hodl\": -0.012264461989279218, \"sharpe\": -2.3161062412406537, \"sterling\": -3.4139507503025057, \"ulcer\": -0.11277314537459374}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": 0.25008768011783566, \"optuna_trial_number\": 48, \"price_ratio\": 1.0252293092076576, \"shift_exponent\": 0.00018125751636554547, \"step\": 48, \"test_objective\": [{\"annualised_returns\": -0.8329829470501537, \"annualised_returns_over_hodl\": -0.2533937176911457, \"annualised_returns_over_uniform_hodl\": -0.030176326313710478, \"calmar\": -1.4378633700679249, \"daily_log_sharpe\": -2.645228856862493, \"daily_returns\": 0.008690524951785865, \"fee_revenue_over_value\": 3.932969393059401e-05, \"jax_sharpe\": -2.156850905708826, \"return\": -0.5136192446836376, \"returns_over_hodl\": -0.11102570514463272, \"returns_over_uniform_hodl\": -0.012264461989279218, \"sharpe\": -2.3161062412406537, \"sterling\": -3.4139507503025057, \"ulcer\": -0.11277314537459374}], \"train_objective\": [{\"annualised_returns\": 0.45049873489464765, \"annualised_returns_over_hodl\": -0.015048196809728354, \"annualised_returns_over_uniform_hodl\": -0.015048196809728354, \"calmar\": 0.7277615548386465, \"daily_log_sharpe\": 0.44268487221278635, \"daily_returns\": 0.00913835924770349, \"fee_revenue_over_value\": 1.3330942260260735e-05, \"jax_sharpe\": 0.867784270391922, \"return\": 0.25331616138840296, \"returns_over_hodl\": -0.009163279279181369, \"returns_over_uniform_hodl\": -0.009163279279181369, \"sharpe\": 0.8225517536115937, \"sterling\": 1.873780840001201, \"ulcer\": -0.12616959448143428}], \"train_return\": 0.25331616138840296, \"train_returns_over_hodl\": -0.009163279279181369, \"train_sharpe\": 0.867784270391922, \"validation_return\": 0.032822781551564706, \"validation_returns_over_hodl\": 0.0022673868780500595, \"validation_sharpe\": 0.8502456995444336}, {\"centeredness_margin\": 0.2076436598526294, \"continuous_test_metrics\": [{\"annualised_returns\": -0.8322528064020767, \"annualised_returns_over_hodl\": -0.2501298139017025, \"annualised_returns_over_uniform_hodl\": -0.02593659346536281, \"calmar\": -1.4383236377249324, \"daily_log_sharpe\": -2.628173816566865, \"daily_returns\": 0.008739595404478757, \"fee_revenue_over_value\": 9.217363822356774e-05, \"jax_sharpe\": -2.1441414010495166, \"return\": -0.512764023434475, \"returns_over_hodl\": -0.10946258881095516, \"returns_over_uniform_hodl\": -0.010527690105469167, \"sharpe\": -2.2967344386589157, \"sterling\": -3.375077709666533, \"ulcer\": -0.11253255561144242}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": 0.25921843195344, \"optuna_trial_number\": 49, \"price_ratio\": 1.0743548090215316, \"shift_exponent\": 0.00010728908627115939, \"step\": 49, \"test_objective\": [{\"annualised_returns\": -0.8322528064020767, \"annualised_returns_over_hodl\": -0.2501298139017025, \"annualised_returns_over_uniform_hodl\": -0.02593659346536281, \"calmar\": -1.4383236377249324, \"daily_log_sharpe\": -2.628173816566865, \"daily_returns\": 0.008739595404478757, \"fee_revenue_over_value\": 9.217363822356774e-05, \"jax_sharpe\": -2.1441414010495166, \"return\": -0.512764023434475, \"returns_over_hodl\": -0.10946258881095516, \"returns_over_uniform_hodl\": -0.010527690105469167, \"sharpe\": -2.2967344386589157, \"sterling\": -3.375077709666533, \"ulcer\": -0.11253255561144242}], \"train_objective\": [{\"annualised_returns\": 0.48967066833898953, \"annualised_returns_over_hodl\": 0.011551251747016433, \"annualised_returns_over_uniform_hodl\": 0.011551251747016877, \"calmar\": 0.7940886215538232, \"daily_log_sharpe\": 0.4844367417622134, \"daily_returns\": 0.008963441381538626, \"fee_revenue_over_value\": 2.2429116213383827e-05, \"jax_sharpe\": 0.9021272331260283, \"return\": 0.27375764137452996, \"returns_over_hodl\": 0.006997183355958558, \"returns_over_uniform_hodl\": 0.00699718335595878, \"sharpe\": 0.8638450421978052, \"sterling\": 2.0426738078130176, \"ulcer\": -0.12557286718575436}], \"train_return\": 0.27375764137452996, \"train_returns_over_hodl\": 0.006997183355958558, \"train_sharpe\": 0.9021272331260283, \"validation_return\": 0.04779028123095386, \"validation_returns_over_hodl\": 0.0030590026923063007, \"validation_sharpe\": 1.1453009943774282}]" \ No newline at end of file diff --git a/results/run_dfb59b57a096e931ffaf48617ba78a5629d0cbdc9df7f15b86448fcb516266fb.json b/results/run_dfb59b57a096e931ffaf48617ba78a5629d0cbdc9df7f15b86448fcb516266fb.json new file mode 100644 index 0000000..5f3717c --- /dev/null +++ b/results/run_dfb59b57a096e931ffaf48617ba78a5629d0cbdc9df7f15b86448fcb516266fb.json @@ -0,0 +1 @@ +"[{\"alphabetic\": true, \"arb_fees\": 0.0, \"arb_frequency\": 4, \"arb_quality\": 1.0, \"bout_offset\": 10080, \"checkpoint_fused\": \"scan\", \"chunk_period\": 1440, \"do_arb\": true, \"do_trades\": false, \"endDateString\": \"2025-10-05 00:00:00\", \"endTestDateString\": \"2026-03-01 00:00:00\", \"ensemble_init_method\": \"gaussian\", \"ensemble_init_scale\": 0.5, \"ensemble_init_seed\": 42, \"fees\": 0.0025, \"freq\": \"minute\", \"gas_cost\": 1.0, \"initial_arc_length_speed\": 0.0001, \"initial_centeredness_margin\": 0.2, \"initial_daily_price_shift_base\": 0.999991935483871, \"initial_k_per_day\": 20, \"initial_log_amplitude\": 0.0, \"initial_memory_length\": 10.0, \"initial_memory_length_delta\": 0.0, \"initial_pool_value\": 5000000.0, \"initial_pre_exp_scaling\": 0.5, \"initial_price_ratio\": 4.0, \"initial_raw_exponents\": 0.0, \"initial_raw_width\": 0.0, \"initial_shift_exponent\": 1.0, \"initial_weights_logits\": 1.0, \"learnable_bounds_settings\": {\"freeze_bounds\": false, \"max_weights_per_asset\": null, \"min_weights_per_asset\": null}, \"max_memory_days\": 365, \"maximum_change\": 0.0003, \"minimum_weight\": null, \"n_ensemble_members\": 1, \"noise_arrays_path\": \"results/mm_noise/_sim_arrays/0x9d1fcf346ea1b0_2025-01-01_2026-03-01_mm.npz\", \"noise_model\": \"mm_observed\", \"noise_trader_ratio\": 0.0, \"numeraire\": null, \"optimisation_settings\": {\"base_lr\": 0.1, \"batch_size\": 8, \"bfgs_settings\": {\"compute_dtype\": \"float32\", \"maxiter\": 100, \"n_evaluation_points\": 20, \"tol\": 1e-06}, \"checkpoint_interval\": 10, \"clip_norm\": 10.0, \"cma_es_settings\": {\"compute_dtype\": \"float32\", \"memory_budget\": null, \"n_evaluation_points\": 20, \"n_generations\": 500, \"overfitting_penalty\": 1.0, \"population_size\": null, \"sigma0\": 0.5, \"tol\": 1e-08}, \"decay_lr_plateau\": 100, \"decay_lr_ratio\": 0.8, \"early_stopping\": true, \"early_stopping_metric\": \"daily_log_sharpe\", \"early_stopping_patience\": 200, \"force_scalar\": false, \"include_flipped_training_data\": false, \"initial_random_key\": 0, \"lr_decay_ratio\": 1000, \"lr_schedule_type\": \"constant\", \"max_mc_version\": 9, \"method\": \"cma_es\", \"min_lr\": 1e-06, \"n_cycles\": 5, \"n_iterations\": 1000, \"n_parameter_sets\": 1, \"noise_scale\": 0.1, \"optimiser\": \"adamw\", \"optuna_settings\": {\"early_stopping\": {\"enabled\": false, \"min_improvement\": 0.001, \"patience\": 100}, \"expand_around\": true, \"make_scalar\": false, \"multi_objective\": false, \"n_jobs\": 4, \"n_startup_trials\": 10, \"n_trials\": 20, \"overfitting_penalty\": 0.2, \"parameter_config\": {\"centeredness_margin\": {\"high\": 0.99, \"low\": 0.01, \"scalar\": true}, \"k_per_day\": {\"high\": 1000, \"log_scale\": true, \"low\": 0.1, \"scalar\": false}, \"log_amplitude\": {\"high\": 10, \"log_scale\": false, \"low\": -10, \"scalar\": false}, \"log_k\": {\"high\": 10.0, \"log_scale\": false, \"low\": -10.0, \"scalar\": false}, \"logit_lamb\": {\"high\": 10, \"log_scale\": false, \"low\": -10, \"scalar\": false}, \"memory_days_1\": {\"high\": 200, \"log_scale\": true, \"low\": 0.5, \"scalar\": false}, \"memory_days_2\": {\"high\": 200, \"log_scale\": true, \"low\": 0.5, \"scalar\": false}, \"memory_length\": {\"high\": 200, \"log_scale\": true, \"low\": 1, \"scalar\": false}, \"memory_length_delta\": {\"high\": 100, \"log_scale\": true, \"low\": 0.1, \"scalar\": false}, \"price_ratio\": {\"high\": 200.0, \"log_scale\": true, \"low\": 1.01, \"scalar\": true}, \"raw_exponents\": {\"high\": 10, \"log_scale\": false, \"low\": 0, \"scalar\": false}, \"raw_pre_exp_scaling\": {\"high\": 10, \"log_scale\": false, \"low\": -10, \"scalar\": false}, \"raw_width\": {\"high\": 10, \"log_scale\": false, \"low\": -10, \"scalar\": false}, \"shift_exponent\": {\"high\": 125.0, \"log_scale\": true, \"low\": 1e-05, \"scalar\": true}, \"weights_logits\": {\"high\": 10, \"log_scale\": false, \"low\": -10, \"scalar\": false}}, \"storage\": {\"type\": \"sqlite\", \"url\": null}, \"study_name\": null, \"timeout\": 7200}, \"parameter_init_method\": \"gaussian\", \"sample_method\": \"uniform\", \"swa_freq\": 10, \"swa_start_frac\": 0.75, \"track_checkpoints\": false, \"train_on_hessian_trace\": false, \"training_data_kind\": \"historic\", \"use_gradient_clipping\": true, \"use_plateau_decay\": false, \"use_swa\": false, \"val_fraction\": 0.2, \"warmup_steps\": 100, \"weight_decay\": 0.01}, \"price_noise_sigma\": 0.0, \"protocol_fee_split\": 0.25, \"reclamm_arc_length_speed\": null, \"reclamm_centeredness_scaling\": false, \"reclamm_interpolation_method\": \"geometric\", \"reclamm_learn_arc_length_speed\": false, \"reclamm_learn_fees\": false, \"reclamm_use_shift_exponent\": true, \"return_val\": \"returns_over_hodl\", \"rule\": \"reclamm\", \"startDateString\": \"2025-01-01 00:00:00\", \"ste_max_change\": false, \"ste_min_max_weight\": false, \"ste_temperature\": 10.0, \"subsidary_pools\": [], \"tokens\": [\"AAVE\", \"ETH\"], \"turnover_penalty\": 0.0, \"use_alt_lamb\": false, \"use_fused_reserves\": true, \"use_pre_exp_scaling\": true, \"weight_calculation_method\": \"auto\", \"weight_interpolation_method\": \"linear\", \"weight_interpolation_period\": 1440}, {\"centeredness_margin\": [[0.20000000298023224]], \"continuous_test_metrics\": [{\"annualised_returns\": -0.8683146834373474, \"annualised_returns_over_hodl\": 0.2207188606262207, \"annualised_returns_over_uniform_hodl\": 0.15674316883087158, \"calmar\": -1.252243995666504, \"daily_log_sharpe\": -2.3729705810546875, \"daily_returns\": [0.007377841975539923, 0.04151822254061699, -0.06430120021104813, 0.02899302914738655, -0.039948221296072006, -0.16681788861751556, 0.025247441604733467, 0.07772254943847656, 0.040408823639154434, -0.02544184774160385, -0.04616699740290642, -0.06265248358249664, -0.059542447328567505, 0.03388846293091774, 0.03024447336792946, 0.02223656140267849, -0.03911500424146652, -0.012379343621432781, 0.03265167400240898, 0.018174614757299423, -0.006208229344338179, 0.0606033131480217, -0.01898546703159809, -0.029204005375504494, -0.007865404710173607, -0.04665905982255936, 0.050147343426942825, -0.01679166406393051, 0.03181140124797821, -0.12229293584823608, -0.06198063865303993, 0.05948472023010254, -0.02179863303899765, 0.05059211701154709, -0.024013714864850044, 0.04785638302564621, 0.05189782381057739, -0.0715165063738823, -0.011242618784308434, -0.03936218097805977, -0.09157789498567581, 0.01892319694161415, -0.029286812990903854, -0.02148771844804287, 0.04667050763964653, -0.024929486215114594, -0.05209173634648323, -0.03912734612822533, 0.017582964152097702, 0.027091441676020622, 0.0609208382666111, 0.008004427887499332, 0.032461367547512054, -0.00028045065118931234, 0.00034887692891061306, -0.015004356391727924, -0.015750788152217865, -0.044844288378953934, 0.09850890934467316, 0.048474717885255814, -0.029147950932383537, -0.03886358067393303, 0.018362609669566154, 0.0007795272395014763, 0.025286972522735596, 0.061287615448236465, -0.01835661008954048, 0.011835403740406036, -0.053321484476327896, 0.024066608399152756, -0.029340799897909164, -0.003393191145732999, -0.02285926043987274, -0.03737886622548103, -0.021030232310295105, 0.04969651252031326, -0.004548367112874985, -0.05861100181937218, -0.06379177421331406, 0.0017948104068636894, -0.014393721707165241, 0.0005654048873111606, 0.027534782886505127, 0.015678875148296356, -0.01790344901382923, -0.019161798059940338, 0.002945210551843047, -0.01567578688263893, 0.018503237515687943, 0.09041647613048553, -0.01211423147469759, 0.013585954904556274, 0.03531237319111824, 0.023642349988222122, -0.0434902124106884, -0.019058957695961, 0.0037678121589124203, -0.0030066894832998514, 0.015833724290132523, -0.01234610378742218, 0.07886405289173126, 0.001018368755467236, -0.02716629020869732, 0.013502160087227821, -0.004190672654658556, -0.05742526426911354, 0.00765903340652585, -0.06519841402769089, 0.03265559300780296, -0.014823335222899914, 0.0007878829492256045, -0.00426988210529089, -0.05129724368453026, 0.04345105215907097, 0.031204581260681152, 0.005444302223622799, -0.07305841147899628, -0.05785023793578148, -0.07199855148792267, -0.05107537657022476, 0.038099005818367004, -0.0331219844520092, -0.017446568235754967, -0.171333909034729, 0.12488052248954773, 0.007087176665663719, -0.007186640985310078, 0.005198976490646601, -0.03451251611113548, -0.018773570656776428, 0.044042978435754776, 0.0398477241396904, 0.05622890219092369, -0.03336063399910927, 0.01358563918620348, -0.0003153432335238904, -0.026445893570780754, 0.009639001451432705, -0.04225323349237442, 0.0326518788933754, -0.018091760575771332, -0.03655212000012398, -0.003384660929441452, 0.0672789067029953, -0.02562740258872509, -0.023253994062542915], \"jax_sharpe\": -0.7301061749458313, \"return\": -0.5580182075500488, \"returns_over_hodl\": 0.08363592624664307, \"returns_over_uniform_hodl\": 0.06039559841156006, \"sharpe\": -1.9755969047546387, \"sterling\": -2.147171974182129, \"ulcer\": -0.1640462577342987}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0.0, \"objective\": [0.0822521448135376], \"price_ratio\": [[4.0]], \"shift_exponent\": [[1.0]], \"step\": 0, \"subsidary_params\": [], \"test_objective\": [{\"annualised_returns\": -0.8683146834373474, \"annualised_returns_over_hodl\": 0.2207188606262207, \"annualised_returns_over_uniform_hodl\": 0.15674316883087158, \"calmar\": -1.252243995666504, \"daily_log_sharpe\": -2.3729705810546875, \"daily_returns\": [0.007377841975539923, 0.04151822254061699, -0.06430120021104813, 0.02899302914738655, -0.039948221296072006, -0.16681788861751556, 0.025247441604733467, 0.07772254943847656, 0.040408823639154434, -0.02544184774160385, -0.04616699740290642, -0.06265248358249664, -0.059542447328567505, 0.03388846293091774, 0.03024447336792946, 0.02223656140267849, -0.03911500424146652, -0.012379343621432781, 0.03265167400240898, 0.018174614757299423, -0.006208229344338179, 0.0606033131480217, -0.01898546703159809, -0.029204005375504494, -0.007865404710173607, -0.04665905982255936, 0.050147343426942825, -0.01679166406393051, 0.03181140124797821, -0.12229293584823608, -0.06198063865303993, 0.05948472023010254, -0.02179863303899765, 0.05059211701154709, -0.024013714864850044, 0.04785638302564621, 0.05189782381057739, -0.0715165063738823, -0.011242618784308434, -0.03936218097805977, -0.09157789498567581, 0.01892319694161415, -0.029286812990903854, -0.02148771844804287, 0.04667050763964653, -0.024929486215114594, -0.05209173634648323, -0.03912734612822533, 0.017582964152097702, 0.027091441676020622, 0.0609208382666111, 0.008004427887499332, 0.032461367547512054, -0.00028045065118931234, 0.00034887692891061306, -0.015004356391727924, -0.015750788152217865, -0.044844288378953934, 0.09850890934467316, 0.048474717885255814, -0.029147950932383537, -0.03886358067393303, 0.018362609669566154, 0.0007795272395014763, 0.025286972522735596, 0.061287615448236465, -0.01835661008954048, 0.011835403740406036, -0.053321484476327896, 0.024066608399152756, -0.029340799897909164, -0.003393191145732999, -0.02285926043987274, -0.03737886622548103, -0.021030232310295105, 0.04969651252031326, -0.004548367112874985, -0.05861100181937218, -0.06379177421331406, 0.0017948104068636894, -0.014393721707165241, 0.0005654048873111606, 0.027534782886505127, 0.015678875148296356, -0.01790344901382923, -0.019161798059940338, 0.002945210551843047, -0.01567578688263893, 0.018503237515687943, 0.09041647613048553, -0.01211423147469759, 0.013585954904556274, 0.03531237319111824, 0.023642349988222122, -0.0434902124106884, -0.019058957695961, 0.0037678121589124203, -0.0030066894832998514, 0.015833724290132523, -0.01234610378742218, 0.07886405289173126, 0.001018368755467236, -0.02716629020869732, 0.013502160087227821, -0.004190672654658556, -0.05742526426911354, 0.00765903340652585, -0.06519841402769089, 0.03265559300780296, -0.014823335222899914, 0.0007878829492256045, -0.00426988210529089, -0.05129724368453026, 0.04345105215907097, 0.031204581260681152, 0.005444302223622799, -0.07305841147899628, -0.05785023793578148, -0.07199855148792267, -0.05107537657022476, 0.038099005818367004, -0.0331219844520092, -0.017446568235754967, -0.171333909034729, 0.12488052248954773, 0.007087176665663719, -0.007186640985310078, 0.005198976490646601, -0.03451251611113548, -0.018773570656776428, 0.044042978435754776, 0.0398477241396904, 0.05622890219092369, -0.03336063399910927, 0.01358563918620348, -0.0003153432335238904, -0.026445893570780754, 0.009639001451432705, -0.04225323349237442, 0.0326518788933754, -0.018091760575771332, -0.03655212000012398, -0.003384660929441452, 0.0672789067029953, -0.02562740258872509, -0.023253994062542915], \"jax_sharpe\": -0.7301061749458313, \"return\": -0.5580182075500488, \"returns_over_hodl\": 0.08363592624664307, \"returns_over_uniform_hodl\": 0.06039559841156006, \"sharpe\": -1.9755969047546387, \"sterling\": -2.147171974182129, \"ulcer\": -0.1640462577342987}], \"train_objective\": [{\"annualised_returns\": 0.37617599964141846, \"annualised_returns_over_hodl\": 0.13905000686645508, \"annualised_returns_over_uniform_hodl\": 0.13905000686645508, \"calmar\": 0.5932134985923767, \"daily_log_sharpe\": 0.40192538499832153, \"daily_returns\": [0.020421288907527924, 0.035962872207164764, 0.048238176852464676, 0.008971290662884712, -0.016146987676620483, 0.004184088669717312, -0.08850935101509094, -0.030941104516386986, -0.03548899292945862, 0.014550024643540382, 0.004035405348986387, -0.0006716030184179544, -0.016856059432029724, 0.027130819857120514, 0.07705484330654144, -0.03489557281136513, 0.06417116522789001, -0.05521048605442047, -0.026519374921917915, 0.05957577005028725, 0.04715580493211746, -0.040127191692590714, 0.007280868012458086, -0.011314702220261097, -0.007745887152850628, -0.029803674668073654, -0.02463424764573574, -0.04854476824402809, 0.019914627075195312, 0.062282055616378784, 0.03249235078692436, -0.08063136041164398, -0.10511143505573273, 0.03865548223257065, -0.02951349876821041, -0.012846032157540321, -0.05392550677061081, -0.02399067021906376, 0.007603588979691267, 0.006247932557016611, 0.031169509515166283, -0.032519035041332245, 0.04661732167005539, -0.004193802364170551, 0.021709272637963295, -0.023710092529654503, -0.0034753938671201468, 0.03682946041226387, -0.04250485822558403, 0.015616786666214466, 0.021766258403658867, -0.05153179168701172, 0.029823172837495804, 0.012372546829283237, -0.12874674797058105, -0.02163677290081978, -0.032419830560684204, -0.003995699808001518, -0.04554353281855583, 0.003485939232632518, 0.14421012997627258, -0.17334887385368347, 0.07711943238973618, 0.05236700922250748, -0.03591783717274666, -0.04157950356602669, 0.011163744144141674, -0.08222685754299164, -0.031015805900096893, 0.021016454324126244, -0.02305441163480282, -0.0479360893368721, 0.04892492666840553, 0.005808503367006779, -0.03701840341091156, 0.04315190017223358, -0.013180810958147049, 0.06955292820930481, -0.02677289769053459, -0.005145528819411993, 0.008706717751920223, 0.019672060385346413, 0.03596821427345276, -0.014198009856045246, -0.03731335699558258, 0.01942196674644947, -0.05031924694776535, -0.03968757018446922, -0.017462216317653656, -0.011313130147755146, 0.04413069784641266, -0.07842963933944702, 0.011074922047555447, 0.0017116409726440907, -0.0009165176306851208, -0.14134101569652557, 0.005726488307118416, -0.047912511974573135, 0.1355833262205124, -0.08271519094705582, 0.03284589573740959, 0.06928814202547073, -0.04688074439764023, 0.0028561826329678297, -0.024330925196409225, -0.002361747669056058, 0.021882696077227592, -0.0007564511033706367, 0.020807120949029922, -0.010353215970098972, 0.004568064119666815, 0.11399146169424057, 0.0413399375975132, -0.008907580748200417, 0.0037761249113827944, 0.029344134032726288, -0.026264557614922523, 0.001670962548814714, -0.0013975701294839382, -0.005378138739615679, 0.0393776074051857, 0.006561753805726767, 0.005972740706056356, -0.025549139827489853, 0.021534163504838943, 0.002599496627226472, -0.016573823988437653, 0.20605075359344482, 0.04501551762223244, 0.08773919194936752, -0.026661869138479233, -0.001937882392667234, 0.07043612003326416, -0.027089405804872513, -0.02522663027048111, 0.015165556222200394, -0.03125005215406418, 0.02908100187778473, 0.0412764772772789, 0.018378030508756638, -0.017151527106761932, 0.043477192521095276, -0.02730468474328518, 0.012424001470208168, 0.01781659759581089, 0.0005956162349320948, 0.029044922441244125, -0.006603931076824665, -0.03574374318122864, -0.016425445675849915, -0.009105853736400604, 0.0004911519936285913, 0.03632335737347603, 0.013438048772513866, -0.00020571383356582373, -0.07926122844219208, 0.032488543540239334, 0.022442100569605827, -0.009927531704306602, 0.0902770534157753, 0.0649227499961853, -0.013540420681238174, -0.04071209579706192, -0.019541868939995766, -0.029947133734822273, 0.0019153712783008814, -0.001842774567194283, -0.017067203298211098, -0.008970283903181553, -0.00572022283449769, -0.0415414422750473, -0.04288661479949951, -0.034076232463121414, 0.10380962491035461, 0.02145984210073948, -0.02015894465148449, -0.01031166035681963, 0.018449829891324043, 0.004174891393631697, 0.04549252241849899, -0.009832918643951416, -0.04093882068991661, 0.06844423711299896, 0.008791704662144184, -0.038725368678569794, 0.014659973792731762, 0.03345337510108948, -0.01231374591588974, 0.03162659704685211, 0.04788369685411453, 0.04706408083438873, -0.014079035259783268, 0.004732467234134674, 0.011732472106814384, 0.020784545689821243, 0.04062822833657265, 0.03584052249789238, 0.015790263190865517, 0.012021955102682114, -0.000585600733757019, 0.028797142207622528, -0.0007761775050312281, -0.023565884679555893, -0.04999855160713196, 0.000383137259632349, 0.019354600459337234, 0.004442065488547087, 0.030883895233273506, -0.03754398971796036, -0.013354683294892311, -0.01600055955350399, -0.04125162214040756, -0.03252372518181801, -0.025747137144207954, 0.03869842737913132, 0.04225340485572815, -0.04098682478070259, 0.028241977095603943, 0.07330566644668579, 0.024113111197948456, 0.06123456358909607], \"jax_sharpe\": 0.8026005029678345, \"return\": 0.21392595767974854, \"returns_over_hodl\": 0.0822521448135376, \"returns_over_uniform_hodl\": 0.0822521448135376, \"sharpe\": 0.8264546990394592, \"sterling\": 1.4456419944763184, \"ulcer\": -0.1321786493062973}]}, {\"centeredness_margin\": [[0.01005610078573227]], \"continuous_test_metrics\": [{\"annualised_returns\": -0.8359429836273193, \"annualised_returns_over_hodl\": 0.627217173576355, \"annualised_returns_over_uniform_hodl\": 0.44110095500946045, \"calmar\": -1.175484538078308, \"daily_log_sharpe\": -2.0463180541992188, \"daily_returns\": [0.009052466601133347, 0.043072573840618134, -0.06964011490345001, 0.03353288397192955, -0.04112468287348747, -0.17858324944972992, 0.04455387219786644, 0.06831236183643341, 0.045419808477163315, -0.024729883298277855, -0.04978106915950775, -0.07599632441997528, -0.07375985383987427, 0.04007485508918762, 0.03307604789733887, 0.028067095205187798, -0.04107646644115448, -0.010942080989480019, 0.03718072921037674, 0.018181394785642624, -0.007589240092784166, 0.06263700872659683, -0.02005554921925068, -0.027489429339766502, -0.005284452345222235, -0.05012977123260498, 0.05806365981698036, -0.019670844078063965, 0.03610578551888466, -0.1304740011692047, -0.055039625614881516, 0.0639680027961731, -0.02036418206989765, 0.05303158983588219, -0.024171873927116394, 0.0489690937101841, 0.053478702902793884, -0.06980038434267044, -0.011340238153934479, -0.03785958141088486, -0.0992208942770958, 0.02124970592558384, -0.030113093554973602, -0.021057013422250748, 0.051156964153051376, -0.02268858626484871, -0.050397127866744995, -0.04033282771706581, 0.0214625746011734, 0.030396059155464172, 0.06262938678264618, 0.009238568134605885, 0.03351394459605217, 0.0005753047298640013, 0.0006661097286269069, -0.014158088713884354, -0.015042399056255817, -0.042298268526792526, 0.09941517561674118, 0.05009772628545761, -0.02866872027516365, -0.03797502443194389, 0.020280249416828156, 0.0020522356498986483, 0.026846889406442642, 0.06237289309501648, -0.01889154687523842, 0.013993898406624794, -0.05233108997344971, 0.024237798526883125, -0.027660980820655823, -0.004947262816131115, -0.01996736228466034, -0.03702379763126373, -0.019630704075098038, 0.05074850097298622, -0.002686460502445698, -0.06801092624664307, -0.084217369556427, 0.007029050029814243, -0.016063811257481575, 0.005213885102421045, 0.03251461684703827, 0.0177655890583992, -0.02114853449165821, -0.02296571061015129, 0.0006956088473089039, -0.02095143496990204, 0.02098793350160122, 0.10098569095134735, -0.013308589346706867, 0.014700799249112606, 0.03730877861380577, 0.024510394781827927, -0.043786853551864624, -0.018651477992534637, 0.005413453560322523, -0.0026062466204166412, 0.01691295951604843, -0.011927087791264057, 0.0799243226647377, 0.0011229590745642781, -0.029070809483528137, 0.0171806737780571, -0.004224404692649841, -0.06412910670042038, 0.013999991118907928, -0.06269031018018723, 0.03546867519617081, -0.014733371324837208, 0.0012969651725143194, -0.004090380854904652, -0.05107986181974411, 0.04456538334488869, 0.0318426713347435, 0.007042294833809137, -0.07374799996614456, -0.058959074318408966, -0.06728536635637283, -0.04793521389365196, 0.03958785906434059, -0.03149983286857605, -0.017501825466752052, -0.16927076876163483, 0.1253006011247635, 0.0074472795240581036, -0.00722716748714447, 0.005501780658960342, -0.03361187502741814, -0.015467306599020958, 0.04194121062755585, 0.042937517166137695, 0.050087254494428635, -0.04004991799592972, 0.015280828811228275, 0.0013091746950522065, -0.022992515936493874, 0.006564990151673555, -0.027211928740143776, 0.027356375008821487, -0.014712910167872906, -0.03887493535876274, -0.0012105945497751236, 0.07848984748125076, -0.02086697518825531, -0.02188248373568058], \"jax_sharpe\": -0.5545719861984253, \"return\": -0.5171094536781311, \"returns_over_hodl\": 0.21662604808807373, \"returns_over_uniform_hodl\": 0.15854322910308838, \"sharpe\": -1.6276895999908447, \"sterling\": -2.0330612659454346, \"ulcer\": -0.1657232791185379}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0.0, \"objective\": [0.27172550559043884], \"price_ratio\": [[1.2961863279342651]], \"shift_exponent\": [[0.06752973049879074]], \"step\": 1, \"subsidary_params\": [], \"test_objective\": [{\"annualised_returns\": -0.8359429836273193, \"annualised_returns_over_hodl\": 0.627217173576355, \"annualised_returns_over_uniform_hodl\": 0.44110095500946045, \"calmar\": -1.175484538078308, \"daily_log_sharpe\": -2.0463180541992188, \"daily_returns\": [0.009052466601133347, 0.043072573840618134, -0.06964011490345001, 0.03353288397192955, -0.04112468287348747, -0.17858324944972992, 0.04455387219786644, 0.06831236183643341, 0.045419808477163315, -0.024729883298277855, -0.04978106915950775, -0.07599632441997528, -0.07375985383987427, 0.04007485508918762, 0.03307604789733887, 0.028067095205187798, -0.04107646644115448, -0.010942080989480019, 0.03718072921037674, 0.018181394785642624, -0.007589240092784166, 0.06263700872659683, -0.02005554921925068, -0.027489429339766502, -0.005284452345222235, -0.05012977123260498, 0.05806365981698036, -0.019670844078063965, 0.03610578551888466, -0.1304740011692047, -0.055039625614881516, 0.0639680027961731, -0.02036418206989765, 0.05303158983588219, -0.024171873927116394, 0.0489690937101841, 0.053478702902793884, -0.06980038434267044, -0.011340238153934479, -0.03785958141088486, -0.0992208942770958, 0.02124970592558384, -0.030113093554973602, -0.021057013422250748, 0.051156964153051376, -0.02268858626484871, -0.050397127866744995, -0.04033282771706581, 0.0214625746011734, 0.030396059155464172, 0.06262938678264618, 0.009238568134605885, 0.03351394459605217, 0.0005753047298640013, 0.0006661097286269069, -0.014158088713884354, -0.015042399056255817, -0.042298268526792526, 0.09941517561674118, 0.05009772628545761, -0.02866872027516365, -0.03797502443194389, 0.020280249416828156, 0.0020522356498986483, 0.026846889406442642, 0.06237289309501648, -0.01889154687523842, 0.013993898406624794, -0.05233108997344971, 0.024237798526883125, -0.027660980820655823, -0.004947262816131115, -0.01996736228466034, -0.03702379763126373, -0.019630704075098038, 0.05074850097298622, -0.002686460502445698, -0.06801092624664307, -0.084217369556427, 0.007029050029814243, -0.016063811257481575, 0.005213885102421045, 0.03251461684703827, 0.0177655890583992, -0.02114853449165821, -0.02296571061015129, 0.0006956088473089039, -0.02095143496990204, 0.02098793350160122, 0.10098569095134735, -0.013308589346706867, 0.014700799249112606, 0.03730877861380577, 0.024510394781827927, -0.043786853551864624, -0.018651477992534637, 0.005413453560322523, -0.0026062466204166412, 0.01691295951604843, -0.011927087791264057, 0.0799243226647377, 0.0011229590745642781, -0.029070809483528137, 0.0171806737780571, -0.004224404692649841, -0.06412910670042038, 0.013999991118907928, -0.06269031018018723, 0.03546867519617081, -0.014733371324837208, 0.0012969651725143194, -0.004090380854904652, -0.05107986181974411, 0.04456538334488869, 0.0318426713347435, 0.007042294833809137, -0.07374799996614456, -0.058959074318408966, -0.06728536635637283, -0.04793521389365196, 0.03958785906434059, -0.03149983286857605, -0.017501825466752052, -0.16927076876163483, 0.1253006011247635, 0.0074472795240581036, -0.00722716748714447, 0.005501780658960342, -0.03361187502741814, -0.015467306599020958, 0.04194121062755585, 0.042937517166137695, 0.050087254494428635, -0.04004991799592972, 0.015280828811228275, 0.0013091746950522065, -0.022992515936493874, 0.006564990151673555, -0.027211928740143776, 0.027356375008821487, -0.014712910167872906, -0.03887493535876274, -0.0012105945497751236, 0.07848984748125076, -0.02086697518825531, -0.02188248373568058], \"jax_sharpe\": -0.5545719861984253, \"return\": -0.5171094536781311, \"returns_over_hodl\": 0.21662604808807373, \"returns_over_uniform_hodl\": 0.15854322910308838, \"sharpe\": -1.6276895999908447, \"sterling\": -2.0330612659454346, \"ulcer\": -0.1657232791185379}], \"train_objective\": [{\"annualised_returns\": 0.6733834743499756, \"annualised_returns_over_hodl\": 0.38504672050476074, \"annualised_returns_over_uniform_hodl\": 0.38504624366760254, \"calmar\": 1.103244662284851, \"daily_log_sharpe\": 0.615865170955658, \"daily_returns\": [0.02060016244649887, 0.03601394221186638, 0.04909822717308998, 0.011901586316525936, -0.013317007571458817, 0.005673995707184076, -0.08829589933156967, -0.032319460064172745, -0.03522447496652603, 0.01605091616511345, 0.0041619520634412766, 0.0010923652444034815, -0.010526217520236969, 0.02902081049978733, 0.07871972024440765, -0.0340595468878746, 0.06321011483669281, -0.05204964429140091, -0.024179400876164436, 0.048779819160699844, 0.016994256526231766, -0.026117881760001183, 0.020770568400621414, -0.009629586711525917, -0.004578431136906147, -0.028755687177181244, -0.023322895169258118, -0.0510513111948967, 0.023061862215399742, 0.0658218115568161, 0.032142847776412964, -0.07884837687015533, -0.11146742850542068, 0.05129218474030495, -0.029349936172366142, -0.013127394951879978, -0.058586202561855316, -0.021119102835655212, 0.009261013008654118, 0.009885591454803944, 0.036394402384757996, -0.03301580250263214, 0.04583854600787163, 0.0002565096947364509, 0.0229007750749588, -0.024495908990502357, -0.0008373496821150184, 0.03875710442662239, -0.043571945279836655, 0.01633528247475624, 0.02474919520318508, -0.05584460869431496, 0.02661479264497757, 0.007603896781802177, -0.1424175500869751, -0.02993873879313469, -0.015136641450226307, 0.00035808529355563223, -0.05026397109031677, 0.00896268431097269, 0.14753222465515137, -0.1834607869386673, 0.09738463908433914, 0.05040142312645912, -0.03142625838518143, -0.04125401750206947, 0.008569324389100075, -0.08111274987459183, -0.023470325395464897, 0.028983747586607933, -0.0189263503998518, -0.05148414522409439, 0.057440582662820816, 0.006308133248239756, -0.039031852036714554, 0.05020325258374214, -0.015673654153943062, 0.07216746360063553, -0.02328014001250267, -0.003417643252760172, 0.01016751304268837, 0.02233200892806053, 0.03676314651966095, -0.014386115595698357, -0.03920796141028404, 0.02440447174012661, -0.048981521278619766, -0.038555845618247986, -0.01806037127971649, -0.014149678871035576, 0.044766366481781006, -0.08751529455184937, 0.011151296086609364, 0.00414664251729846, 0.0015003486769273877, -0.15016011893749237, 0.01896744966506958, -0.04715307801961899, 0.1380804479122162, -0.08055169135332108, 0.03410663455724716, 0.07409309595823288, -0.05094359442591667, -0.0023333528079092503, -0.023971982300281525, -0.0004760226875077933, 0.028862250968813896, -0.001347937504760921, 0.022984595969319344, -0.008643371053040028, 0.007595428731292486, 0.11636216938495636, 0.0449979230761528, -0.007170438766479492, 0.006136314943432808, 0.030756358057260513, -0.02400106005370617, 0.002495570806786418, -0.000681790872476995, -0.004370242357254028, 0.04009362682700157, 0.007336386479437351, 0.005635047797113657, -0.02259191684424877, 0.020804233849048615, 0.0031562272924929857, -0.01369465421885252, 0.20780499279499054, 0.04442564770579338, 0.08621490001678467, -0.027237819507718086, 0.00016025902004912496, 0.07012077420949936, -0.027045167982578278, -0.024639524519443512, 0.019778897985816002, -0.031233208253979683, 0.032081473618745804, 0.03956546261906624, 0.012293816544115543, -0.006141999736428261, 0.046238161623477936, -0.031530000269412994, 0.01131287869066, 0.015065431594848633, 0.005533609073609114, 0.036236535757780075, 0.0008911641780287027, -0.03125054016709328, -0.019465062767267227, -0.005649428348988295, 0.0025659422390162945, 0.03474736586213112, 0.0071837762370705605, 0.003857147414237261, -0.07608325779438019, 0.030457746237516403, 0.02187841571867466, -0.007749325595796108, 0.07976265251636505, 0.05290216952562332, -0.013454015366733074, -0.04432234913110733, -0.02301071397960186, -0.018308693543076515, 0.00565013661980629, -0.0014220725279301405, -0.011698668822646141, -0.0021959098521620035, -0.003625815035775304, -0.042144615203142166, -0.04111538082361221, -0.033293843269348145, 0.096823550760746, 0.01889338344335556, -0.014638197608292103, -0.005493170116096735, 0.012625635601580143, 0.005972878076136112, 0.03414243459701538, -0.006304830312728882, -0.034305866807699203, 0.07026974856853485, 0.008849497884511948, -0.034490007907152176, 0.009069266729056835, 0.02537684328854084, -0.010385831817984581, 0.03028767555952072, 0.056445859372615814, 0.05687648802995682, -0.00837139505892992, 0.004166149068623781, 0.012884422205388546, 0.019959794357419014, 0.04265288636088371, 0.04213852807879448, 0.015012022107839584, 0.011398217640817165, -0.004071991425007582, 0.02233387902379036, -0.0016617110231891274, -0.03305975720286369, -0.06193734332919121, -0.013900038786232471, 0.02907208539545536, 0.004369416739791632, 0.029487038031220436, -0.049136120826005936, -0.021234609186649323, -0.027268003672361374, -0.04762435331940651, -0.02204188145697117, -0.024271443486213684, 0.041529614478349686, 0.03495137393474579, -0.04592473804950714, 0.032660312950611115, 0.07984882593154907, 0.0230245478451252, 0.06084851175546646], \"jax_sharpe\": 1.0242515802383423, \"return\": 0.36694180965423584, \"returns_over_hodl\": 0.21867060661315918, \"returns_over_uniform_hodl\": 0.21867036819458008, \"sharpe\": 1.0534226894378662, \"sterling\": 2.617457866668701, \"ulcer\": -0.12644201517105103}]}]" \ No newline at end of file diff --git a/results/run_fc93de2085bafce3a9c0330ff03b4960d250393ba49dbe0637b1bbe7eb7ef348.json b/results/run_fc93de2085bafce3a9c0330ff03b4960d250393ba49dbe0637b1bbe7eb7ef348.json new file mode 100644 index 0000000..defc985 --- /dev/null +++ b/results/run_fc93de2085bafce3a9c0330ff03b4960d250393ba49dbe0637b1bbe7eb7ef348.json @@ -0,0 +1 @@ +"[{\"alphabetic\": true, \"arb_fees\": 0.0, \"arb_frequency\": 3, \"arb_quality\": 1.0, \"bout_offset\": 10080, \"checkpoint_fused\": \"scan\", \"chunk_period\": 1440, \"do_arb\": true, \"do_trades\": false, \"endDateString\": \"2025-10-05 00:00:00\", \"endTestDateString\": \"2026-03-01 00:00:00\", \"ensemble_init_method\": \"gaussian\", \"ensemble_init_scale\": 0.5, \"ensemble_init_seed\": 42, \"evaluation_starts\": [77760, 82489, 84681, 87218, 91947, 94849, 96677, 96743, 99003, 101406, 106135, 109990, 110864, 115594, 117272, 119753, 120323, 120373, 121327, 123651, 125052, 126360, 129781, 130380, 132786, 134511, 139240, 143969, 146028, 148698, 153428, 157946, 158157, 158231, 158726, 160217, 162886, 164053, 167448, 167616], \"fees\": 0.003, \"freq\": \"minute\", \"gas_cost\": 3.0, \"initial_arc_length_speed\": 0.0001, \"initial_centeredness_margin\": 0.2, \"initial_daily_price_shift_base\": 0.999991935483871, \"initial_k_per_day\": 20, \"initial_log_amplitude\": 0.0, \"initial_memory_length\": 10.0, \"initial_memory_length_delta\": 0.0, \"initial_pool_value\": 20000000.0, \"initial_pre_exp_scaling\": 0.5, \"initial_price_ratio\": 4.0, \"initial_raw_exponents\": 0.0, \"initial_raw_width\": 0.0, \"initial_shift_exponent\": 1.0, \"initial_weights_logits\": 1.0, \"learnable_bounds_settings\": {\"freeze_bounds\": false, \"max_weights_per_asset\": null, \"min_weights_per_asset\": null}, \"max_memory_days\": 365, \"maximum_change\": 0.0003, \"minimum_weight\": null, \"n_ensemble_members\": 1, \"noise_arrays_path\": \"results/mm_noise/_sim_arrays/0xd321300ef77067_2025-01-01_2026-03-01_mm.npz\", \"noise_model\": \"mm_observed\", \"noise_trader_ratio\": 0.0, \"numeraire\": null, \"optimisation_settings\": {\"base_lr\": 0.1, \"batch_size\": 8, \"bfgs_settings\": {\"compute_dtype\": \"float32\", \"maxiter\": 100, \"n_evaluation_points\": 20, \"tol\": 1e-06}, \"checkpoint_interval\": 10, \"clip_norm\": 10.0, \"cma_es_settings\": {\"compute_dtype\": \"float32\", \"memory_budget\": null, \"n_evaluation_points\": 20, \"n_generations\": 300, \"population_size\": null, \"sigma0\": 0.5, \"tol\": 1e-08}, \"decay_lr_plateau\": 100, \"decay_lr_ratio\": 0.8, \"early_stopping\": true, \"early_stopping_metric\": \"daily_log_sharpe\", \"early_stopping_patience\": 200, \"force_scalar\": false, \"include_flipped_training_data\": false, \"initial_random_key\": 0, \"lr_decay_ratio\": 1000, \"lr_schedule_type\": \"constant\", \"max_mc_version\": 9, \"method\": \"optuna\", \"min_lr\": 1e-06, \"n_cycles\": 5, \"n_iterations\": 1000, \"n_parameter_sets\": 1, \"noise_scale\": 0.1, \"optimiser\": \"adamw\", \"optuna_settings\": {\"early_stopping\": {\"enabled\": false, \"min_improvement\": 0.001, \"patience\": 100}, \"expand_around\": false, \"make_scalar\": true, \"min_train_returns_over_hodl\": -0.5, \"multi_objective\": false, \"n_jobs\": 4, \"n_startup_trials\": 10, \"n_trials\": 300, \"overfitting_penalty\": 1.0, \"parameter_config\": {\"centeredness_margin\": {\"high\": 0.99, \"low\": 0.01, \"scalar\": true}, \"k_per_day\": {\"high\": 1000, \"log_scale\": true, \"low\": 0.1, \"scalar\": true}, \"log_amplitude\": {\"high\": 10, \"log_scale\": false, \"low\": -10, \"scalar\": true}, \"log_k\": {\"high\": 10.0, \"log_scale\": false, \"low\": -10.0, \"scalar\": true}, \"logit_lamb\": {\"high\": 4.602848117654388, \"log_scale\": false, \"low\": -5.955817303419269, \"scalar\": true}, \"memory_days_1\": {\"high\": 200, \"log_scale\": true, \"low\": 0.5, \"scalar\": true}, \"memory_days_2\": {\"high\": 200, \"log_scale\": true, \"low\": 0.5, \"scalar\": true}, \"memory_length\": {\"high\": 200, \"log_scale\": true, \"low\": 1, \"scalar\": true}, \"memory_length_delta\": {\"high\": 100, \"log_scale\": true, \"low\": 0.1, \"scalar\": true}, \"price_ratio\": {\"high\": 200.0, \"log_scale\": true, \"low\": 1.01, \"scalar\": true}, \"raw_exponents\": {\"high\": 10, \"log_scale\": false, \"low\": 0, \"scalar\": true}, \"raw_pre_exp_scaling\": {\"high\": 10, \"log_scale\": false, \"low\": -10, \"scalar\": true}, \"raw_width\": {\"high\": 10, \"log_scale\": false, \"low\": -10, \"scalar\": true}, \"shift_exponent\": {\"high\": 125.0, \"log_scale\": true, \"low\": 1e-05, \"scalar\": true}, \"weights_logits\": {\"high\": 10, \"log_scale\": false, \"low\": -10, \"scalar\": true}}, \"storage\": {\"type\": \"sqlite\", \"url\": null}, \"study_name\": null, \"timeout\": 7200}, \"parameter_init_method\": \"gaussian\", \"sample_method\": \"uniform\", \"swa_freq\": 10, \"swa_start_frac\": 0.75, \"track_checkpoints\": false, \"train_on_hessian_trace\": false, \"training_data_kind\": \"historic\", \"use_gradient_clipping\": true, \"use_plateau_decay\": false, \"use_swa\": false, \"val_fraction\": 0.2, \"warmup_steps\": 100, \"weight_decay\": 0.01}, \"price_noise_sigma\": 0.0, \"protocol_fee_split\": 0.25, \"reclamm_arc_length_speed\": null, \"reclamm_centeredness_scaling\": false, \"reclamm_interpolation_method\": \"geometric\", \"reclamm_learn_arc_length_speed\": false, \"reclamm_learn_fees\": false, \"reclamm_use_shift_exponent\": true, \"return_val\": \"returns_over_hodl\", \"rule\": \"reclamm\", \"startDateString\": \"2025-01-01 00:00:00\", \"ste_max_change\": false, \"ste_min_max_weight\": false, \"ste_temperature\": 10.0, \"subsidary_pools\": [], \"tokens\": [\"COW\", \"ETH\"], \"training_method\": \"optuna\", \"turnover_penalty\": 0.0, \"use_alt_lamb\": false, \"use_fused_reserves\": true, \"use_pre_exp_scaling\": true, \"weight_calculation_method\": \"auto\", \"weight_interpolation_method\": \"linear\", \"weight_interpolation_period\": 1440}, {\"centeredness_margin\": 0.894516851700889, \"continuous_test_metrics\": [{\"annualised_returns\": -0.7348990540220761, \"annualised_returns_over_hodl\": -0.06956010441664784, \"annualised_returns_over_uniform_hodl\": -0.06431115489923211, \"calmar\": -1.2251289762230932, \"daily_log_sharpe\": -1.695318708296814, \"daily_returns\": 0.01821958339091797, \"fee_revenue_over_value\": 0.009030247611253279, \"jax_sharpe\": -1.1585905538853387, \"return\": -0.41415070334484383, \"returns_over_hodl\": -0.028619001794996435, \"returns_over_uniform_hodl\": -0.02641574042209549, \"sharpe\": -1.3139757821755411, \"sterling\": -2.2372844492858155, \"ulcer\": -0.1479194403058476}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -0.02636380966404217, \"optuna_trial_number\": 0, \"price_ratio\": 8.23790635466687, \"shift_exponent\": 0.1259440900128594, \"step\": 0, \"test_objective\": [{\"annualised_returns\": -0.7348990540220761, \"annualised_returns_over_hodl\": -0.06956010441664784, \"annualised_returns_over_uniform_hodl\": -0.06431115489923211, \"calmar\": -1.2251289762230932, \"daily_log_sharpe\": -1.695318708296814, \"daily_returns\": 0.01821958339091797, \"fee_revenue_over_value\": 0.009030247611253279, \"jax_sharpe\": -1.1585905538853387, \"return\": -0.41415070334484383, \"returns_over_hodl\": -0.028619001794996435, \"returns_over_uniform_hodl\": -0.02641574042209549, \"sharpe\": -1.3139757821755411, \"sterling\": -2.2372844492858155, \"ulcer\": -0.1479194403058476}], \"train_objective\": [{\"annualised_returns\": -0.36654675449870144, \"annualised_returns_over_hodl\": -0.2022681458528628, \"annualised_returns_over_uniform_hodl\": -0.20226814585286312, \"calmar\": -0.49540156628088355, \"daily_log_sharpe\": -0.4069661797677605, \"daily_returns\": 0.008063196240992023, \"fee_revenue_over_value\": 0.00551883377830708, \"jax_sharpe\": 0.06235106427937689, \"return\": -0.2420916837901943, \"returns_over_hodl\": -0.12820324945805162, \"returns_over_uniform_hodl\": -0.12820324945805184, \"sharpe\": 0.07819813550565796, \"sterling\": -1.207214104731144, \"ulcer\": -0.16474040877544324}], \"train_return\": -0.2420916837901943, \"train_returns_over_hodl\": -0.12820324945805162, \"train_sharpe\": 0.06235106427937689, \"validation_return\": -0.16462266954344285, \"validation_returns_over_hodl\": -0.02636380966404217, \"validation_sharpe\": -1.3264738566081835}, {\"centeredness_margin\": 0.06329074334102452, \"continuous_test_metrics\": [{\"annualised_returns\": -0.682863265476725, \"annualised_returns_over_hodl\": -0.2521117837801008, \"annualised_returns_over_uniform_hodl\": 0.11935211611739427, \"calmar\": -1.1392349657671752, \"daily_log_sharpe\": -1.3210173283556639, \"daily_returns\": 0.026528535228452856, \"fee_revenue_over_value\": 0.012014984569569473, \"jax_sharpe\": -0.711976601795051, \"return\": -0.37030082960261945, \"returns_over_hodl\": -0.11041128835016178, \"returns_over_uniform_hodl\": 0.04645546912556631, \"sharpe\": -0.9025322359479986, \"sterling\": -1.8798893031100963, \"ulcer\": -0.16414419681559805}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -0.03376056625906021, \"optuna_trial_number\": 1, \"price_ratio\": 14.246325050723597, \"shift_exponent\": 10.809989169712821, \"step\": 1, \"test_objective\": [{\"annualised_returns\": -0.682863265476725, \"annualised_returns_over_hodl\": -0.2521117837801008, \"annualised_returns_over_uniform_hodl\": 0.11935211611739427, \"calmar\": -1.1392349657671752, \"daily_log_sharpe\": -1.3210173283556639, \"daily_returns\": 0.026528535228452856, \"fee_revenue_over_value\": 0.012014984569569473, \"jax_sharpe\": -0.711976601795051, \"return\": -0.37030082960261945, \"returns_over_hodl\": -0.11041128835016178, \"returns_over_uniform_hodl\": 0.04645546912556631, \"sharpe\": -0.9025322359479986, \"sterling\": -1.8798893031100963, \"ulcer\": -0.16414419681559805}], \"train_objective\": [{\"annualised_returns\": -0.40344658556296087, \"annualised_returns_over_hodl\": -0.24873751176370107, \"annualised_returns_over_uniform_hodl\": -0.24873751176370107, \"calmar\": -0.5448827760070065, \"daily_log_sharpe\": -0.39999461830827454, \"daily_returns\": 0.008017241242263369, \"fee_revenue_over_value\": 0.006278835704084253, \"jax_sharpe\": 0.11931187657981081, \"return\": -0.2692111474682788, \"returns_over_hodl\": -0.15939786733640948, \"returns_over_uniform_hodl\": -0.15939786733640948, \"sharpe\": 0.14240751423010617, \"sterling\": -1.2526086702385555, \"ulcer\": -0.17391016123180264}], \"train_return\": -0.2692111474682788, \"train_returns_over_hodl\": -0.15939786733640948, \"train_sharpe\": 0.1193118765798108, \"validation_return\": -0.2764274469924858, \"validation_returns_over_hodl\": -0.03376056625906021, \"validation_sharpe\": -2.021253297215219}, {\"centeredness_margin\": 0.23119643599156, \"continuous_test_metrics\": [{\"annualised_returns\": -0.5771327880950652, \"annualised_returns_over_hodl\": -0.1302400636334211, \"annualised_returns_over_uniform_hodl\": 0.4925338409439681, \"calmar\": -0.9969723603520394, \"daily_log_sharpe\": -0.9376358362078887, \"daily_returns\": 0.029674181709410555, \"fee_revenue_over_value\": 9.686053809732994e-08, \"jax_sharpe\": -0.26527467854548364, \"return\": -0.29293682754157535, \"returns_over_hodl\": -0.054647350254686144, \"returns_over_uniform_hodl\": 0.17502159542224116, \"sharpe\": -0.4749584559335046, \"sterling\": -1.5175936208749985, \"ulcer\": -0.1695611127239332}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -0.057446363483213525, \"optuna_trial_number\": 2, \"price_ratio\": 18.574493157433157, \"shift_exponent\": 2.331786830443874e-05, \"step\": 2, \"test_objective\": [{\"annualised_returns\": -0.5771327880950652, \"annualised_returns_over_hodl\": -0.1302400636334211, \"annualised_returns_over_uniform_hodl\": 0.4925338409439681, \"calmar\": -0.9969723603520394, \"daily_log_sharpe\": -0.9376358362078887, \"daily_returns\": 0.029674181709410555, \"fee_revenue_over_value\": 9.686053809732994e-08, \"jax_sharpe\": -0.26527467854548364, \"return\": -0.29293682754157535, \"returns_over_hodl\": -0.054647350254686144, \"returns_over_uniform_hodl\": 0.17502159542224116, \"sharpe\": -0.4749584559335046, \"sterling\": -1.5175936208749985, \"ulcer\": -0.1695611127239332}], \"train_objective\": [{\"annualised_returns\": -0.39319491503308324, \"annualised_returns_over_hodl\": -0.2358271917077467, \"annualised_returns_over_uniform_hodl\": -0.2358271917077467, \"calmar\": -0.531853577293843, \"daily_log_sharpe\": -0.392271059923035, \"daily_returns\": 0.008001490948332298, \"fee_revenue_over_value\": 0.0048060608800750285, \"jax_sharpe\": 0.14956072605499066, \"return\": -0.2616121586480262, \"returns_over_hodl\": -0.15065700301388785, \"returns_over_uniform_hodl\": -0.15065700301388785, \"sharpe\": 0.14262707451086726, \"sterling\": -1.230745678528798, \"ulcer\": -0.1724059495000428}], \"train_return\": -0.2616121586480262, \"train_returns_over_hodl\": -0.15065700301388785, \"train_sharpe\": 0.14956072605499066, \"validation_return\": -0.2796002713937077, \"validation_returns_over_hodl\": -0.057446363483213525, \"validation_sharpe\": -1.9456092369308529}, {\"centeredness_margin\": 0.7803495453752572, \"continuous_test_metrics\": [{\"annualised_returns\": -0.7645905602949028, \"annualised_returns_over_hodl\": -0.19543471697471848, \"annualised_returns_over_uniform_hodl\": -0.1691090126029796, \"calmar\": -1.2504569012527158, \"daily_log_sharpe\": -1.8230885360041322, \"daily_returns\": 0.01874592436349971, \"fee_revenue_over_value\": 0.01097008237926549, \"jax_sharpe\": -1.309673021001379, \"return\": -0.4415173134593876, \"returns_over_hodl\": -0.0838513224904418, \"returns_over_uniform_hodl\": -0.07189450261852492, \"sharpe\": -1.447183816242503, \"sterling\": -2.321480675561088, \"ulcer\": -0.15256915936107646}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -0.027502903620667873, \"optuna_trial_number\": 3, \"price_ratio\": 7.009012980854623, \"shift_exponent\": 7.20736113549438, \"step\": 3, \"test_objective\": [{\"annualised_returns\": -0.7645905602949028, \"annualised_returns_over_hodl\": -0.19543471697471848, \"annualised_returns_over_uniform_hodl\": -0.1691090126029796, \"calmar\": -1.2504569012527158, \"daily_log_sharpe\": -1.8230885360041322, \"daily_returns\": 0.01874592436349971, \"fee_revenue_over_value\": 0.01097008237926549, \"jax_sharpe\": -1.309673021001379, \"return\": -0.4415173134593876, \"returns_over_hodl\": -0.0838513224904418, \"returns_over_uniform_hodl\": -0.07189450261852492, \"sharpe\": -1.447183816242503, \"sterling\": -2.321480675561088, \"ulcer\": -0.15256915936107646}], \"train_objective\": [{\"annualised_returns\": -0.4182955785676591, \"annualised_returns_over_hodl\": -0.26743741551505273, \"annualised_returns_over_uniform_hodl\": -0.26743741551505307, \"calmar\": -0.5577614492644617, \"daily_log_sharpe\": -0.4931614310833004, \"daily_returns\": 0.008049850076959937, \"fee_revenue_over_value\": 0.0064109451653867775, \"jax_sharpe\": -0.005046112373573273, \"return\": -0.2803094936569269, \"returns_over_hodl\": -0.17216392615476206, \"returns_over_uniform_hodl\": -0.17216392615476228, \"sharpe\": -0.007744838561838611, \"sterling\": -1.3640631230121127, \"ulcer\": -0.1669040976916745}], \"train_return\": -0.2803094936569269, \"train_returns_over_hodl\": -0.17216392615476206, \"train_sharpe\": -0.005046112373573272, \"validation_return\": -0.1712049092045348, \"validation_returns_over_hodl\": -0.027502903620667873, \"validation_sharpe\": -1.381384700040983}, {\"centeredness_margin\": 0.21703685166384784, \"continuous_test_metrics\": [{\"annualised_returns\": -0.663901421302509, \"annualised_returns_over_hodl\": -0.09630455917348879, \"annualised_returns_over_uniform_hodl\": 0.18627902205846292, \"calmar\": -1.1248061954494983, \"daily_log_sharpe\": -1.2962739119296673, \"daily_returns\": 0.023574513828713645, \"fee_revenue_over_value\": 0.0001420897434328616, \"jax_sharpe\": -0.6851076056098929, \"return\": -0.35540012088232353, \"returns_over_hodl\": -0.03996197959540271, \"returns_over_uniform_hodl\": 0.07121797298016164, \"sharpe\": -0.8805661340316725, \"sterling\": -1.8758297321290598, \"ulcer\": -0.15777573675022752}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -0.04028515781399644, \"optuna_trial_number\": 4, \"price_ratio\": 81.08191682482439, \"shift_exponent\": 1.2481202546305052e-05, \"step\": 4, \"test_objective\": [{\"annualised_returns\": -0.663901421302509, \"annualised_returns_over_hodl\": -0.09630455917348879, \"annualised_returns_over_uniform_hodl\": 0.18627902205846292, \"calmar\": -1.1248061954494983, \"daily_log_sharpe\": -1.2962739119296673, \"daily_returns\": 0.023574513828713645, \"fee_revenue_over_value\": 0.0001420897434328616, \"jax_sharpe\": -0.6851076056098929, \"return\": -0.35540012088232353, \"returns_over_hodl\": -0.03996197959540271, \"returns_over_uniform_hodl\": 0.07121797298016164, \"sharpe\": -0.8805661340316725, \"sterling\": -1.8758297321290598, \"ulcer\": -0.15777573675022752}], \"train_objective\": [{\"annualised_returns\": -0.34832663324700364, \"annualised_returns_over_hodl\": -0.17932285160719164, \"annualised_returns_over_uniform_hodl\": -0.17932285160719197, \"calmar\": -0.4742601345411767, \"daily_log_sharpe\": -0.3487785527118072, \"daily_returns\": 0.007954792831771643, \"fee_revenue_over_value\": 0.005904419893624842, \"jax_sharpe\": 0.13371732786049378, \"return\": -0.22893033245182182, \"returns_over_hodl\": -0.11306418437045085, \"returns_over_uniform_hodl\": -0.11306418437045107, \"sharpe\": 0.1631472461973307, \"sterling\": -1.1211075456594268, \"ulcer\": -0.16766061117169226}], \"train_return\": -0.22893033245182182, \"train_returns_over_hodl\": -0.11306418437045085, \"train_sharpe\": 0.13371732786049378, \"validation_return\": -0.22039523909732417, \"validation_returns_over_hodl\": -0.04028515781399644, \"validation_sharpe\": -1.6650973009595715}, {\"centeredness_margin\": 0.7645844286289964, \"continuous_test_metrics\": [{\"annualised_returns\": -0.5010579327985416, \"annualised_returns_over_hodl\": -0.03210802505591648, \"annualised_returns_over_uniform_hodl\": 0.7610443633452719, \"calmar\": -0.8643573927591739, \"daily_log_sharpe\": -0.7360915930027094, \"daily_returns\": 0.031016042751715742, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": -0.053860193440166, \"return\": -0.24422353572946753, \"returns_over_hodl\": -0.013057270376926167, \"returns_over_uniform_hodl\": 0.25597499830463755, \"sharpe\": -0.25175183380096505, \"sterling\": -1.2899969395506008, \"ulcer\": -0.17641031652031183}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -0.0531051072334191, \"optuna_trial_number\": 5, \"price_ratio\": 8.614625515051292, \"shift_exponent\": 2.9861595017395226e-05, \"step\": 5, \"test_objective\": [{\"annualised_returns\": -0.5010579327985416, \"annualised_returns_over_hodl\": -0.03210802505591648, \"annualised_returns_over_uniform_hodl\": 0.7610443633452719, \"calmar\": -0.8643573927591739, \"daily_log_sharpe\": -0.7360915930027094, \"daily_returns\": 0.031016042751715742, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": -0.053860193440166, \"return\": -0.24422353572946753, \"returns_over_hodl\": -0.013057270376926167, \"returns_over_uniform_hodl\": 0.25597499830463755, \"sharpe\": -0.25175183380096505, \"sterling\": -1.2899969395506008, \"ulcer\": -0.17641031652031183}], \"train_objective\": [{\"annualised_returns\": -0.44535127726864454, \"annualised_returns_over_hodl\": -0.3015096897409163, \"annualised_returns_over_uniform_hodl\": -0.3015096897409163, \"calmar\": -0.5971223008630377, \"daily_log_sharpe\": -0.44328939522444943, \"daily_returns\": 0.008043746313928428, \"fee_revenue_over_value\": 0.00015928173766757714, \"jax_sharpe\": 0.14474558114419728, \"return\": -0.30082182368916544, \"returns_over_hodl\": -0.195758577757977, \"returns_over_uniform_hodl\": -0.195758577757977, \"sharpe\": 0.11995896992215978, \"sterling\": -1.3508536756962968, \"ulcer\": -0.17846624880376452}], \"train_return\": -0.30082182368916544, \"train_returns_over_hodl\": -0.195758577757977, \"train_sharpe\": 0.14474558114419728, \"validation_return\": -0.3315895209508064, \"validation_returns_over_hodl\": -0.0531051072334191, \"validation_sharpe\": -2.1773626347983974}, {\"centeredness_margin\": 0.24332907432122444, \"continuous_test_metrics\": [{\"annualised_returns\": -0.6983991058240038, \"annualised_returns_over_hodl\": -0.13476407328700712, \"annualised_returns_over_uniform_hodl\": 0.06451748526162282, \"calmar\": -1.1787589129548592, \"daily_log_sharpe\": -1.468953559114008, \"daily_returns\": 0.02224953926183345, \"fee_revenue_over_value\": 0.01093408334504304, \"jax_sharpe\": -0.8926169654551082, \"return\": -0.3829109666944839, \"returns_over_hodl\": -0.056630777763194584, \"returns_over_uniform_hodl\": 0.02549951500246128, \"sharpe\": -1.070267507413206, \"sterling\": -2.025897352286203, \"ulcer\": -0.15395502623096624}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -0.028270247713321983, \"optuna_trial_number\": 6, \"price_ratio\": 17.585717220662847, \"shift_exponent\": 1.0464894221045176, \"step\": 6, \"test_objective\": [{\"annualised_returns\": -0.6983991058240038, \"annualised_returns_over_hodl\": -0.13476407328700712, \"annualised_returns_over_uniform_hodl\": 0.06451748526162282, \"calmar\": -1.1787589129548592, \"daily_log_sharpe\": -1.468953559114008, \"daily_returns\": 0.02224953926183345, \"fee_revenue_over_value\": 0.01093408334504304, \"jax_sharpe\": -0.8926169654551082, \"return\": -0.3829109666944839, \"returns_over_hodl\": -0.056630777763194584, \"returns_over_uniform_hodl\": 0.02549951500246128, \"sharpe\": -1.070267507413206, \"sterling\": -2.025897352286203, \"ulcer\": -0.15395502623096624}], \"train_objective\": [{\"annualised_returns\": -0.38313329949446595, \"annualised_returns_over_hodl\": -0.2231562151576385, \"annualised_returns_over_uniform_hodl\": -0.2231562151576385, \"calmar\": -0.5171981604310041, \"daily_log_sharpe\": -0.39304900774594564, \"daily_returns\": 0.007998388404174559, \"fee_revenue_over_value\": 0.006187745529236782, \"jax_sharpe\": 0.10342587720994244, \"return\": -0.25420293861544585, \"returns_over_hodl\": -0.14213442342173332, \"returns_over_uniform_hodl\": -0.14213442342173332, \"sharpe\": 0.12959860401592524, \"sterling\": -1.2118261762031048, \"ulcer\": -0.1707341814116167}], \"train_return\": -0.25420293861544585, \"train_returns_over_hodl\": -0.14213442342173332, \"train_sharpe\": 0.10342587720994244, \"validation_return\": -0.21933003774721205, \"validation_returns_over_hodl\": -0.028270247713321983, \"validation_sharpe\": -1.6983729955818465}, {\"centeredness_margin\": 0.43268965697333195, \"continuous_test_metrics\": [{\"annualised_returns\": -0.6939079024293592, \"annualised_returns_over_hodl\": -0.10419529297971808, \"annualised_returns_over_uniform_hodl\": 0.08036944271860458, \"calmar\": -1.1668237781221416, \"daily_log_sharpe\": -1.4439487472045558, \"daily_returns\": 0.021782700799502532, \"fee_revenue_over_value\": 2.2746536275847973e-05, \"jax_sharpe\": -0.8680727677312019, \"return\": -0.3792264547121078, \"returns_over_hodl\": -0.043346852748552545, \"returns_over_uniform_hodl\": 0.03162256215938064, \"sharpe\": -1.0416767612895976, \"sterling\": -2.0219206347625374, \"ulcer\": -0.15430777555219144}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -0.0353174390130947, \"optuna_trial_number\": 7, \"price_ratio\": 160.81873790839416, \"shift_exponent\": 0.0007125539816655857, \"step\": 7, \"test_objective\": [{\"annualised_returns\": -0.6939079024293592, \"annualised_returns_over_hodl\": -0.10419529297971808, \"annualised_returns_over_uniform_hodl\": 0.08036944271860458, \"calmar\": -1.1668237781221416, \"daily_log_sharpe\": -1.4439487472045558, \"daily_returns\": 0.021782700799502532, \"fee_revenue_over_value\": 2.2746536275847973e-05, \"jax_sharpe\": -0.8680727677312019, \"return\": -0.3792264547121078, \"returns_over_hodl\": -0.043346852748552545, \"returns_over_uniform_hodl\": 0.03162256215938064, \"sharpe\": -1.0416767612895976, \"sterling\": -2.0219206347625374, \"ulcer\": -0.15430777555219144}], \"train_objective\": [{\"annualised_returns\": -0.3424709587592951, \"annualised_returns_over_hodl\": -0.1719485771843574, \"annualised_returns_over_uniform_hodl\": -0.17194857718435752, \"calmar\": -0.4663743726663514, \"daily_log_sharpe\": -0.3471386172179339, \"daily_returns\": 0.007936174834615773, \"fee_revenue_over_value\": 0.0036919604632041736, \"jax_sharpe\": 0.14979454632572095, \"return\": -0.22473127294102757, \"returns_over_hodl\": -0.10823414575159873, \"returns_over_uniform_hodl\": -0.10823414575159884, \"sharpe\": 0.1585065499222202, \"sterling\": -1.1099331584752585, \"ulcer\": -0.16663147573462966}], \"train_return\": -0.22473127294102757, \"train_returns_over_hodl\": -0.10823414575159873, \"train_sharpe\": 0.14979454632572095, \"validation_return\": -0.2018316172228113, \"validation_returns_over_hodl\": -0.0353174390130947, \"validation_sharpe\": -1.5569242176479188}, {\"centeredness_margin\": 0.18090023644903946, \"continuous_test_metrics\": [{\"annualised_returns\": -0.6828581576576904, \"annualised_returns_over_hodl\": -0.10949029069961647, \"annualised_returns_over_uniform_hodl\": 0.11937014445477279, \"calmar\": -1.1580029913605638, \"daily_log_sharpe\": -1.3975983120250335, \"daily_returns\": 0.022693769372677895, \"fee_revenue_over_value\": 0.01078259271374394, \"jax_sharpe\": -0.8084985779885056, \"return\": -0.3702967450696769, \"returns_over_hodl\": -0.04562823315628184, \"returns_over_uniform_hodl\": 0.04646225694114148, \"sharpe\": -0.9934689048406828, \"sterling\": -1.9681985678080656, \"ulcer\": -0.15441897983161276}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -0.02757406955884767, \"optuna_trial_number\": 8, \"price_ratio\": 29.2564692989674, \"shift_exponent\": 0.11649669885799742, \"step\": 8, \"test_objective\": [{\"annualised_returns\": -0.6828581576576904, \"annualised_returns_over_hodl\": -0.10949029069961647, \"annualised_returns_over_uniform_hodl\": 0.11937014445477279, \"calmar\": -1.1580029913605638, \"daily_log_sharpe\": -1.3975983120250335, \"daily_returns\": 0.022693769372677895, \"fee_revenue_over_value\": 0.01078259271374394, \"jax_sharpe\": -0.8084985779885056, \"return\": -0.3702967450696769, \"returns_over_hodl\": -0.04562823315628184, \"returns_over_uniform_hodl\": 0.04646225694114148, \"sharpe\": -0.9934689048406828, \"sterling\": -1.9681985678080656, \"ulcer\": -0.15441897983161276}], \"train_objective\": [{\"annualised_returns\": -0.37398752235931665, \"annualised_returns_over_hodl\": -0.21163858887116316, \"annualised_returns_over_uniform_hodl\": -0.21163858887116294, \"calmar\": -0.5073638120145996, \"daily_log_sharpe\": -0.3759031365018018, \"daily_returns\": 0.00799376999935658, \"fee_revenue_over_value\": 0.006106372891714378, \"jax_sharpe\": 0.12448238873619058, \"return\": -0.24750922022744382, \"returns_over_hodl\": -0.13443486159493379, \"returns_over_uniform_hodl\": -0.13443486159493367, \"sharpe\": 0.1488468132314527, \"sterling\": -1.1843377748746278, \"ulcer\": -0.1703253395679857}], \"train_return\": -0.24750922022744382, \"train_returns_over_hodl\": -0.13443486159493379, \"train_sharpe\": 0.12448238873619058, \"validation_return\": -0.22576766910534185, \"validation_returns_over_hodl\": -0.02757406955884767, \"validation_sharpe\": -1.7354334557295275}, {\"centeredness_margin\": 0.41700298915292666, \"continuous_test_metrics\": [{\"annualised_returns\": -0.7590447101827992, \"annualised_returns_over_hodl\": -0.22195264182924612, \"annualised_returns_over_uniform_hodl\": -0.14953461965860948, \"calmar\": -1.2475185664349013, \"daily_log_sharpe\": -1.782203306227867, \"daily_returns\": 0.01982429526802593, \"fee_revenue_over_value\": 0.00999524824950905, \"jax_sharpe\": -1.2910896709104016, \"return\": -0.43625534509518904, \"returns_over_hodl\": -0.09613409045993648, \"returns_over_uniform_hodl\": -0.06314998486791978, \"sharpe\": -1.404773353456141, \"sterling\": -2.311465264935669, \"ulcer\": -0.15242526824982922}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -0.030530012392154693, \"optuna_trial_number\": 9, \"price_ratio\": 35.779245400962786, \"shift_exponent\": 35.030206923591976, \"step\": 9, \"test_objective\": [{\"annualised_returns\": -0.7590447101827992, \"annualised_returns_over_hodl\": -0.22195264182924612, \"annualised_returns_over_uniform_hodl\": -0.14953461965860948, \"calmar\": -1.2475185664349013, \"daily_log_sharpe\": -1.782203306227867, \"daily_returns\": 0.01982429526802593, \"fee_revenue_over_value\": 0.00999524824950905, \"jax_sharpe\": -1.2910896709104016, \"return\": -0.43625534509518904, \"returns_over_hodl\": -0.09613409045993648, \"returns_over_uniform_hodl\": -0.06314998486791978, \"sharpe\": -1.404773353456141, \"sterling\": -2.311465264935669, \"ulcer\": -0.15242526824982922}], \"train_objective\": [{\"annualised_returns\": -0.3460554057868839, \"annualised_returns_over_hodl\": -0.17646260816252024, \"annualised_returns_over_uniform_hodl\": -0.1764626081625199, \"calmar\": -0.46927210738900815, \"daily_log_sharpe\": -0.3584478496965519, \"daily_returns\": 0.007982515723778909, \"fee_revenue_over_value\": 0.006049926553747548, \"jax_sharpe\": 0.1326606187866886, \"return\": -0.22729989984037113, \"returns_over_hodl\": -0.11118875191742228, \"returns_over_uniform_hodl\": -0.11118875191742206, \"sharpe\": 0.14239451145370052, \"sterling\": -1.1232048987491352, \"ulcer\": -0.16656601252291345}], \"train_return\": -0.22729989984037113, \"train_returns_over_hodl\": -0.11118875191742228, \"train_sharpe\": 0.13266061878668858, \"validation_return\": -0.18652464574943672, \"validation_returns_over_hodl\": -0.030530012392154693, \"validation_sharpe\": -1.469425710561342}, {\"centeredness_margin\": 0.14864015906978406, \"continuous_test_metrics\": [{\"annualised_returns\": -0.4765156902618469, \"annualised_returns_over_hodl\": 0.015501188808750399, \"annualised_returns_over_uniform_hodl\": 0.8476676022425607, \"calmar\": -0.8220203618495606, \"daily_log_sharpe\": -0.6785127346952968, \"daily_returns\": 0.031016042754189937, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": 0.007713245366748069, \"return\": -0.22946587804943774, \"returns_over_hodl\": 0.006214251869546272, \"returns_over_uniform_hodl\": 0.28049977508178414, \"sharpe\": -0.18799657609838386, \"sterling\": -1.2268116868334051, \"ulcer\": -0.17641031651946057}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -6.966383025996947e-11, \"optuna_trial_number\": 10, \"price_ratio\": 5.746262431110513, \"shift_exponent\": 1.2474265686930841e-05, \"step\": 10, \"test_objective\": [{\"annualised_returns\": -0.4765156902618469, \"annualised_returns_over_hodl\": 0.015501188808750399, \"annualised_returns_over_uniform_hodl\": 0.8476676022425607, \"calmar\": -0.8220203618495606, \"daily_log_sharpe\": -0.6785127346952968, \"daily_returns\": 0.031016042754189937, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": 0.007713245366748069, \"return\": -0.22946587804943774, \"returns_over_hodl\": 0.006214251869546272, \"returns_over_uniform_hodl\": 0.28049977508178414, \"sharpe\": -0.18799657609838386, \"sterling\": -1.2268116868334051, \"ulcer\": -0.17641031651946057}], \"train_objective\": [{\"annualised_returns\": -0.47725663778006966, \"annualised_returns_over_hodl\": -0.3416893282205803, \"annualised_returns_over_uniform_hodl\": -0.3416893282205803, \"calmar\": -0.6372185694039789, \"daily_log_sharpe\": -0.4659018345383067, \"daily_returns\": 0.008079436384584998, \"fee_revenue_over_value\": 0.004070787077353499, \"jax_sharpe\": 0.16419114203671423, \"return\": -0.32552331738932483, \"returns_over_hodl\": -0.22417188512082975, \"returns_over_uniform_hodl\": -0.22417188512082975, \"sharpe\": 0.12400195111265325, \"sterling\": -1.4102314516576768, \"ulcer\": -0.1835213925268695}], \"train_return\": -0.32552331738932483, \"train_returns_over_hodl\": -0.22417188512082975, \"train_sharpe\": 0.16419114203671423, \"validation_return\": -0.33914272230863285, \"validation_returns_over_hodl\": -6.966383025996947e-11, \"validation_sharpe\": -2.243853205006505}, {\"centeredness_margin\": 0.12878765481220375, \"continuous_test_metrics\": [{\"annualised_returns\": -0.6919968515793578, \"annualised_returns_over_hodl\": -0.24964952197863965, \"annualised_returns_over_uniform_hodl\": 0.08711460523083336, \"calmar\": -1.1774476181339104, \"daily_log_sharpe\": -1.3946090621896947, \"daily_returns\": 0.025830069672550717, \"fee_revenue_over_value\": 0.01312792255368229, \"jax_sharpe\": -0.7667423348820767, \"return\": -0.3776684529428713, \"returns_over_hodl\": -0.10923291539211899, \"returns_over_uniform_hodl\": 0.03421170241709448, \"sharpe\": -0.9823754511114094, \"sterling\": -1.9184286262463215, \"ulcer\": -0.15669366773186907}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -0.035153299696644935, \"optuna_trial_number\": 11, \"price_ratio\": 3.853724516362949, \"shift_exponent\": 0.2515319513666142, \"step\": 11, \"test_objective\": [{\"annualised_returns\": -0.6919968515793578, \"annualised_returns_over_hodl\": -0.24964952197863965, \"annualised_returns_over_uniform_hodl\": 0.08711460523083336, \"calmar\": -1.1774476181339104, \"daily_log_sharpe\": -1.3946090621896947, \"daily_returns\": 0.025830069672550717, \"fee_revenue_over_value\": 0.01312792255368229, \"jax_sharpe\": -0.7667423348820767, \"return\": -0.3776684529428713, \"returns_over_hodl\": -0.10923291539211899, \"returns_over_uniform_hodl\": 0.03421170241709448, \"sharpe\": -0.9823754511114094, \"sterling\": -1.9184286262463215, \"ulcer\": -0.15669366773186907}], \"train_objective\": [{\"annualised_returns\": -0.45943745853169915, \"annualised_returns_over_hodl\": -0.3192489555456667, \"annualised_returns_over_uniform_hodl\": -0.31924895554566646, \"calmar\": -0.6071903969609777, \"daily_log_sharpe\": -0.48806062482091256, \"daily_returns\": 0.008176553697318604, \"fee_revenue_over_value\": 0.006468770368422821, \"jax_sharpe\": 0.030649072534893965, \"return\": -0.31165674403158716, \"returns_over_hodl\": -0.2082216265791611, \"returns_over_uniform_hodl\": -0.208221626579161, \"sharpe\": 0.06073629241275511, \"sterling\": -1.3951262489385445, \"ulcer\": -0.1782546868812537}], \"train_return\": -0.31165674403158716, \"train_returns_over_hodl\": -0.2082216265791611, \"train_sharpe\": 0.030649072534893972, \"validation_return\": -0.2702084914032351, \"validation_returns_over_hodl\": -0.035153299696644935, \"validation_sharpe\": -2.01499049023758}, {\"centeredness_margin\": 0.18372896309247183, \"continuous_test_metrics\": [{\"annualised_returns\": -0.6763126578195319, \"annualised_returns_over_hodl\": -0.09015325580537392, \"annualised_returns_over_uniform_hodl\": 0.14247285788184483, \"calmar\": -1.1420901222236117, \"daily_log_sharpe\": -1.3582703995971317, \"daily_returns\": 0.022633878155387508, \"fee_revenue_over_value\": 0.00045565760897151795, \"jax_sharpe\": -0.7635653286205274, \"return\": -0.365094488167659, \"returns_over_hodl\": -0.03733549175094142, \"returns_over_uniform_hodl\": 0.05510754415579244, \"sharpe\": -0.9489639606252168, \"sterling\": -1.9398398878814245, \"ulcer\": -0.15608437696134855}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -0.03738884762003947, \"optuna_trial_number\": 12, \"price_ratio\": 127.17419521113983, \"shift_exponent\": 2.9575832598205594e-05, \"step\": 12, \"test_objective\": [{\"annualised_returns\": -0.6763126578195319, \"annualised_returns_over_hodl\": -0.09015325580537392, \"annualised_returns_over_uniform_hodl\": 0.14247285788184483, \"calmar\": -1.1420901222236117, \"daily_log_sharpe\": -1.3582703995971317, \"daily_returns\": 0.022633878155387508, \"fee_revenue_over_value\": 0.00045565760897151795, \"jax_sharpe\": -0.7635653286205274, \"return\": -0.365094488167659, \"returns_over_hodl\": -0.03733549175094142, \"returns_over_uniform_hodl\": 0.05510754415579244, \"sharpe\": -0.9489639606252168, \"sterling\": -1.9398398878814245, \"ulcer\": -0.15608437696134855}], \"train_objective\": [{\"annualised_returns\": -0.3406957186561972, \"annualised_returns_over_hodl\": -0.16971294955270788, \"annualised_returns_over_uniform_hodl\": -0.16971294955270788, \"calmar\": -0.46437634334834926, \"daily_log_sharpe\": -0.3414399606512882, \"daily_returns\": 0.0079491562801761, \"fee_revenue_over_value\": 0.005876031041312832, \"jax_sharpe\": 0.1365288911597767, \"return\": -0.223461167278457, \"returns_over_hodl\": -0.10677318541405911, \"returns_over_uniform_hodl\": -0.10677318541405911, \"sharpe\": 0.16667452509546973, \"sterling\": -1.1018667836013496, \"ulcer\": -0.1668791364942103}], \"train_return\": -0.223461167278457, \"train_returns_over_hodl\": -0.10677318541405911, \"train_sharpe\": 0.13652889115977668, \"validation_return\": -0.21029090890147817, \"validation_returns_over_hodl\": -0.03738884762003947, \"validation_sharpe\": -1.6057188073800959}, {\"centeredness_margin\": 0.12765492249406363, \"continuous_test_metrics\": [{\"annualised_returns\": -0.7391549759886259, \"annualised_returns_over_hodl\": -0.3963447169146451, \"annualised_returns_over_uniform_hodl\": -0.07933267319306514, \"calmar\": -1.2570939599758868, \"daily_log_sharpe\": -1.5867216770349868, \"daily_returns\": 0.027056483237133833, \"fee_revenue_over_value\": 0.013694200031254733, \"jax_sharpe\": -0.9618475693814987, \"return\": -0.4179568518931387, \"returns_over_hodl\": -0.18395249492077104, \"returns_over_uniform_hodl\": -0.03274092735564971, \"sharpe\": -1.1803168667378534, \"sterling\": -2.0656698156662894, \"ulcer\": -0.15347490362748448}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -0.04900946293641584, \"optuna_trial_number\": 13, \"price_ratio\": 2.1336563597693754, \"shift_exponent\": 0.040237236208553344, \"step\": 13, \"test_objective\": [{\"annualised_returns\": -0.7391549759886259, \"annualised_returns_over_hodl\": -0.3963447169146451, \"annualised_returns_over_uniform_hodl\": -0.07933267319306514, \"calmar\": -1.2570939599758868, \"daily_log_sharpe\": -1.5867216770349868, \"daily_returns\": 0.027056483237133833, \"fee_revenue_over_value\": 0.013694200031254733, \"jax_sharpe\": -0.9618475693814987, \"return\": -0.4179568518931387, \"returns_over_hodl\": -0.18395249492077104, \"returns_over_uniform_hodl\": -0.03274092735564971, \"sharpe\": -1.1803168667378534, \"sterling\": -2.0656698156662894, \"ulcer\": -0.15347490362748448}], \"train_objective\": [{\"annualised_returns\": -0.47955771762596766, \"annualised_returns_over_hodl\": -0.3445871659142776, \"annualised_returns_over_uniform_hodl\": -0.34458716591427785, \"calmar\": -0.6252818039830781, \"daily_log_sharpe\": -0.5298038506872297, \"daily_returns\": 0.008437371546571718, \"fee_revenue_over_value\": 0.005320380989677101, \"jax_sharpe\": -0.04497487232191173, \"return\": -0.3273274222597935, \"returns_over_hodl\": -0.22624708700220864, \"returns_over_uniform_hodl\": -0.22624708700220875, \"sharpe\": 0.012949521758900458, \"sterling\": -1.4493569766488543, \"ulcer\": -0.17912470378399659}], \"train_return\": -0.3273274222597935, \"train_returns_over_hodl\": -0.22624708700220864, \"train_sharpe\": -0.044974872321911726, \"validation_return\": -0.2787411704478061, \"validation_returns_over_hodl\": -0.04900946293641584, \"validation_sharpe\": -2.0917054767276597}, {\"centeredness_margin\": 0.0730948789814423, \"continuous_test_metrics\": [{\"annualised_returns\": -0.4845064531195765, \"annualised_returns_over_hodl\": 8.546496843564455e-13, \"annualised_returns_over_uniform_hodl\": 0.8194637509049432, \"calmar\": -0.8358049680289292, \"daily_log_sharpe\": -0.6924635968939449, \"daily_returns\": 0.031016042751464027, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": 0.005019284028577983, \"return\": -0.23422459929918527, \"returns_over_hodl\": 3.4416913763379853e-13, \"returns_over_uniform_hodl\": 0.27259157047878335, \"sharpe\": -0.2021085296741413, \"sterling\": -1.2473843110521756, \"ulcer\": -0.1764103165138265}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -9.018408242411624e-11, \"optuna_trial_number\": 14, \"price_ratio\": 3.1979341371088617, \"shift_exponent\": 0.00010453202814290673, \"step\": 14, \"test_objective\": [{\"annualised_returns\": -0.4845064531195765, \"annualised_returns_over_hodl\": 8.546496843564455e-13, \"annualised_returns_over_uniform_hodl\": 0.8194637509049432, \"calmar\": -0.8358049680289292, \"daily_log_sharpe\": -0.6924635968939449, \"daily_returns\": 0.031016042751464027, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": 0.005019284028577983, \"return\": -0.23422459929918527, \"returns_over_hodl\": 3.4416913763379853e-13, \"returns_over_uniform_hodl\": 0.27259157047878335, \"sharpe\": -0.2021085296741413, \"sterling\": -1.2473843110521756, \"ulcer\": -0.1764103165138265}], \"train_objective\": [{\"annualised_returns\": -0.55512055791114, \"annualised_returns_over_hodl\": -0.43974633529798113, \"annualised_returns_over_uniform_hodl\": -0.43974633529798113, \"calmar\": -0.7256401544843263, \"daily_log_sharpe\": -0.5562248189082787, \"daily_returns\": 0.0082475140462718, \"fee_revenue_over_value\": 0.0027300530379502884, \"jax_sharpe\": 0.12013280469986792, \"return\": -0.3884381289608676, \"returns_over_hodl\": -0.2965407021874136, \"returns_over_uniform_hodl\": -0.2965407021874136, \"sharpe\": 0.08633082765457098, \"sterling\": -1.5410586000896485, \"ulcer\": -0.19783439574803532}], \"train_return\": -0.3884381289608676, \"train_returns_over_hodl\": -0.2965407021874136, \"train_sharpe\": 0.12013280469986792, \"validation_return\": -0.3391427222988741, \"validation_returns_over_hodl\": -9.018408242411624e-11, \"validation_sharpe\": -2.2438532050983166}, {\"centeredness_margin\": 0.13437516570040403, \"continuous_test_metrics\": [{\"annualised_returns\": -0.4845064531862212, \"annualised_returns_over_hodl\": 1.7541523789077473e-14, \"annualised_returns_over_uniform_hodl\": 0.8194637506697162, \"calmar\": -0.8358049681355809, \"daily_log_sharpe\": -0.6924635970610127, \"daily_returns\": 0.031016042748318543, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": 0.005019283828873965, \"return\": -0.23422459933905715, \"returns_over_hodl\": 7.105427357601002e-15, \"returns_over_uniform_hodl\": 0.2725915704125228, \"sharpe\": -0.2021085298699632, \"sterling\": -1.2473843112750846, \"ulcer\": -0.17641031650734362}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -1.1911083230842223e-10, \"optuna_trial_number\": 15, \"price_ratio\": 2.098198041440251, \"shift_exponent\": 4.119376470592507e-05, \"step\": 15, \"test_objective\": [{\"annualised_returns\": -0.4845064531862212, \"annualised_returns_over_hodl\": 1.7541523789077473e-14, \"annualised_returns_over_uniform_hodl\": 0.8194637506697162, \"calmar\": -0.8358049681355809, \"daily_log_sharpe\": -0.6924635970610127, \"daily_returns\": 0.031016042748318543, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": 0.005019283828873965, \"return\": -0.23422459933905715, \"returns_over_hodl\": 7.105427357601002e-15, \"returns_over_uniform_hodl\": 0.2725915704125228, \"sharpe\": -0.2021085298699632, \"sterling\": -1.2473843112750846, \"ulcer\": -0.17641031650734362}], \"train_objective\": [{\"annualised_returns\": -0.6247387057575501, \"annualised_returns_over_hodl\": -0.5274191265548258, \"annualised_returns_over_uniform_hodl\": -0.5274191265548259, \"calmar\": -0.7996363266723611, \"daily_log_sharpe\": -0.6655916896734188, \"daily_returns\": 0.008439834175088064, \"fee_revenue_over_value\": 0.0019677221983254237, \"jax_sharpe\": 0.08953389778605832, \"return\": -0.4484702671628007, \"returns_over_hodl\": -0.36559367586931546, \"returns_over_uniform_hodl\": -0.36559367586931557, \"sharpe\": 0.003095417812114551, \"sterling\": -1.689224150139296, \"ulcer\": -0.20615918113755213}], \"train_return\": -0.4484702671628007, \"train_returns_over_hodl\": -0.36559367586931546, \"train_sharpe\": 0.08953389778605832, \"validation_return\": -0.3391427222825164, \"validation_returns_over_hodl\": -1.1911083230842223e-10, \"validation_sharpe\": -2.243853205153143}, {\"centeredness_margin\": 0.22350707673254994, \"continuous_test_metrics\": [{\"annualised_returns\": -0.4845064531153722, \"annualised_returns_over_hodl\": 8.926193117986259e-13, \"annualised_returns_over_uniform_hodl\": 0.8194637509197831, \"calmar\": -0.8358049680222003, \"daily_log_sharpe\": -0.6924635968834001, \"daily_returns\": 0.03101604275166389, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": 0.0050192840411860205, \"return\": -0.23422459929666994, \"returns_over_hodl\": 3.594902153736257e-13, \"returns_over_uniform_hodl\": 0.27259157048296356, \"sharpe\": -0.20210852966177822, \"sterling\": -1.2473843110381018, \"ulcer\": -0.17641031651423583}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -8.84619044683177e-11, \"optuna_trial_number\": 16, \"price_ratio\": 3.3365773745140856, \"shift_exponent\": 3.070707145640013e-05, \"step\": 16, \"test_objective\": [{\"annualised_returns\": -0.4845064531153722, \"annualised_returns_over_hodl\": 8.926193117986259e-13, \"annualised_returns_over_uniform_hodl\": 0.8194637509197831, \"calmar\": -0.8358049680222003, \"daily_log_sharpe\": -0.6924635968834001, \"daily_returns\": 0.03101604275166389, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": 0.0050192840411860205, \"return\": -0.23422459929666994, \"returns_over_hodl\": 3.594902153736257e-13, \"returns_over_uniform_hodl\": 0.27259157048296356, \"sharpe\": -0.20210852966177822, \"sterling\": -1.2473843110381018, \"ulcer\": -0.17641031651423583}], \"train_objective\": [{\"annualised_returns\": -0.5513265649486356, \"annualised_returns_over_hodl\": -0.43496841512455997, \"annualised_returns_over_uniform_hodl\": -0.43496841512455997, \"calmar\": -0.7219977177712569, \"daily_log_sharpe\": -0.55234143193925, \"daily_returns\": 0.008218978386591153, \"fee_revenue_over_value\": 0.001976001900788659, \"jax_sharpe\": 0.15253923293855517, \"return\": -0.3852769810361668, \"returns_over_hodl\": -0.29290453877583056, \"returns_over_uniform_hodl\": -0.29290453877583056, \"sharpe\": 0.0867831561541071, \"sterling\": -1.5363901267001512, \"ulcer\": -0.1968779497733496}], \"train_return\": -0.3852769810361668, \"train_returns_over_hodl\": -0.29290453877583056, \"train_sharpe\": 0.15253923293855515, \"validation_return\": -0.3391427222999286, \"validation_returns_over_hodl\": -8.84619044683177e-11, \"validation_sharpe\": -2.2438532050948314}, {\"centeredness_margin\": 0.11084104976380732, \"continuous_test_metrics\": [{\"annualised_returns\": -0.4845064531194718, \"annualised_returns_over_hodl\": 8.271161533457416e-13, \"annualised_returns_over_uniform_hodl\": 0.8194637509053129, \"calmar\": -0.8358049680287547, \"daily_log_sharpe\": -0.692463596893675, \"daily_returns\": 0.03101604275146943, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": 0.005019284028912541, \"return\": -0.23422459929912265, \"returns_over_hodl\": 3.3306690738754696e-13, \"returns_over_uniform_hodl\": 0.2725915704788875, \"sharpe\": -0.20210852967382031, \"sterling\": -1.2473843110518077, \"ulcer\": -0.17641031651383882}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -9.075828977245237e-11, \"optuna_trial_number\": 17, \"price_ratio\": 3.210781790324509, \"shift_exponent\": 1.4292579167057252e-05, \"step\": 17, \"test_objective\": [{\"annualised_returns\": -0.4845064531194718, \"annualised_returns_over_hodl\": 8.271161533457416e-13, \"annualised_returns_over_uniform_hodl\": 0.8194637509053129, \"calmar\": -0.8358049680287547, \"daily_log_sharpe\": -0.692463596893675, \"daily_returns\": 0.03101604275146943, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": 0.005019284028912541, \"return\": -0.23422459929912265, \"returns_over_hodl\": 3.3306690738754696e-13, \"returns_over_uniform_hodl\": 0.2725915704788875, \"sharpe\": -0.20210852967382031, \"sterling\": -1.2473843110518077, \"ulcer\": -0.17641031651383882}], \"train_objective\": [{\"annualised_returns\": -0.5554670844524279, \"annualised_returns_over_hodl\": -0.4401827294000811, \"annualised_returns_over_uniform_hodl\": -0.4401827294000811, \"calmar\": -0.7261514806372675, \"daily_log_sharpe\": -0.5565902575361082, \"daily_returns\": 0.008243793378166818, \"fee_revenue_over_value\": 0.002378147566029059, \"jax_sharpe\": 0.15076618560445404, \"return\": -0.3887273810927395, \"returns_over_hodl\": -0.2968734193027427, \"returns_over_uniform_hodl\": -0.2968734193027427, \"sharpe\": 0.08624277793532228, \"sterling\": -1.541394082597014, \"ulcer\": -0.1979694696888407}], \"train_return\": -0.3887273810927395, \"train_returns_over_hodl\": -0.2968734193027427, \"train_sharpe\": 0.15076618560445407, \"validation_return\": -0.33914272229931586, \"validation_returns_over_hodl\": -9.075828977245237e-11, \"validation_sharpe\": -2.243853205102365}, {\"centeredness_margin\": 0.21124867561358432, \"continuous_test_metrics\": [{\"annualised_returns\": -0.4845064531778175, \"annualised_returns_over_hodl\": 8.617551117140465e-13, \"annualised_returns_over_uniform_hodl\": 0.8194637506993772, \"calmar\": -0.835804968122239, \"daily_log_sharpe\": -0.6924635970401571, \"daily_returns\": 0.03101604274869858, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": 0.0050192838535515726, \"return\": -0.2342245993340294, \"returns_over_hodl\": 3.4705571749782393e-13, \"returns_over_uniform_hodl\": 0.2725915704208779, \"sharpe\": -0.20210852984563485, \"sterling\": -1.2473843112473044, \"ulcer\": -0.17641031650811145}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -1.1629608387409007e-10, \"optuna_trial_number\": 18, \"price_ratio\": 2.231789094231183, \"shift_exponent\": 2.2731792373321223e-05, \"step\": 18, \"test_objective\": [{\"annualised_returns\": -0.4845064531778175, \"annualised_returns_over_hodl\": 8.617551117140465e-13, \"annualised_returns_over_uniform_hodl\": 0.8194637506993772, \"calmar\": -0.835804968122239, \"daily_log_sharpe\": -0.6924635970401571, \"daily_returns\": 0.03101604274869858, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": 0.0050192838535515726, \"return\": -0.2342245993340294, \"returns_over_hodl\": 3.4705571749782393e-13, \"returns_over_uniform_hodl\": 0.2725915704208779, \"sharpe\": -0.20210852984563485, \"sterling\": -1.2473843112473044, \"ulcer\": -0.17641031650811145}], \"train_objective\": [{\"annualised_returns\": -0.6177025888542367, \"annualised_returns_over_hodl\": -0.5185582759346128, \"annualised_returns_over_uniform_hodl\": -0.5185582759346128, \"calmar\": -0.79219843720152, \"daily_log_sharpe\": -0.6525982734097374, \"daily_returns\": 0.00838473722507001, \"fee_revenue_over_value\": 0.0019359502730676027, \"jax_sharpe\": 0.09249847471938677, \"return\": -0.4422148683742425, \"returns_over_hodl\": -0.3583982985122218, \"returns_over_uniform_hodl\": -0.3583982985122218, \"sharpe\": 0.014686799281569242, \"sterling\": -1.674430228641358, \"ulcer\": -0.20558087457355723}], \"train_return\": -0.4422148683742425, \"train_returns_over_hodl\": -0.3583982985122218, \"train_sharpe\": 0.09249847471938677, \"validation_return\": -0.3391427222846116, \"validation_returns_over_hodl\": -1.1629608387409007e-10, \"validation_sharpe\": -2.2438532051469866}, {\"centeredness_margin\": 0.45327093881243563, \"continuous_test_metrics\": [{\"annualised_returns\": -0.6280828108055581, \"annualised_returns_over_hodl\": -0.2730291676665765, \"annualised_returns_over_uniform_hodl\": 0.3127028421069853, \"calmar\": -1.0779497358265344, \"daily_log_sharpe\": -1.0869262169123939, \"daily_returns\": 0.03092160225429363, \"fee_revenue_over_value\": 0.000163349437186194, \"jax_sharpe\": -0.4228232848344021, \"return\": -0.3285673468473562, \"returns_over_hodl\": -0.12051658821862898, \"returns_over_uniform_hodl\": 0.11580958824778542, \"sharpe\": -0.6335112512469251, \"sterling\": -1.664998276050058, \"ulcer\": -0.16768553667001812}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -0.06396739384859065, \"optuna_trial_number\": 19, \"price_ratio\": 6.3978691605473275, \"shift_exponent\": 0.0021346061973333942, \"step\": 19, \"test_objective\": [{\"annualised_returns\": -0.6280828108055581, \"annualised_returns_over_hodl\": -0.2730291676665765, \"annualised_returns_over_uniform_hodl\": 0.3127028421069853, \"calmar\": -1.0779497358265344, \"daily_log_sharpe\": -1.0869262169123939, \"daily_returns\": 0.03092160225429363, \"fee_revenue_over_value\": 0.000163349437186194, \"jax_sharpe\": -0.4228232848344021, \"return\": -0.3285673468473562, \"returns_over_hodl\": -0.12051658821862898, \"returns_over_uniform_hodl\": 0.11580958824778542, \"sharpe\": -0.6335112512469251, \"sterling\": -1.664998276050058, \"ulcer\": -0.16768553667001812}], \"train_objective\": [{\"annualised_returns\": -0.4162222149054853, \"annualised_returns_over_hodl\": -0.2648263495045877, \"annualised_returns_over_uniform_hodl\": -0.26482634950458805, \"calmar\": -0.5571051699636095, \"daily_log_sharpe\": -0.41309535797401115, \"daily_returns\": 0.008070668566501963, \"fee_revenue_over_value\": 0.0022245878376768832, \"jax_sharpe\": 0.178409098951412, \"return\": -0.2787532028937766, \"returns_over_hodl\": -0.17037377660607, \"returns_over_uniform_hodl\": -0.17037377660607023, \"sharpe\": 0.1421402564768774, \"sterling\": -1.2767438035786955, \"ulcer\": -0.17575193441412765}], \"train_return\": -0.2787532028937766, \"train_returns_over_hodl\": -0.17037377660607, \"train_sharpe\": 0.178409098951412, \"validation_return\": -0.28916742889826874, \"validation_returns_over_hodl\": -0.06396739384859063, \"validation_sharpe\": -1.9918633909292565}, {\"centeredness_margin\": 0.37928889613953076, \"continuous_test_metrics\": [{\"annualised_returns\": -0.4845064531777177, \"annualised_returns_over_hodl\": 1.0327294575063206e-12, \"annualised_returns_over_uniform_hodl\": 0.8194637506997302, \"calmar\": -0.8358049681221001, \"daily_log_sharpe\": -0.6924635970399561, \"daily_returns\": 0.031016042748699544, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": 0.00501928385373447, \"return\": -0.23422459933396966, \"returns_over_hodl\": 4.1588954502458364e-13, \"returns_over_uniform_hodl\": 0.27259157042097737, \"sharpe\": -0.20210852984542413, \"sterling\": -1.2473843112470562, \"ulcer\": -0.17641031650810657}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -1.1157952339857502e-10, \"optuna_trial_number\": 20, \"price_ratio\": 2.139733267809586, \"shift_exponent\": 0.00037344761948449203, \"step\": 20, \"test_objective\": [{\"annualised_returns\": -0.4845064531777177, \"annualised_returns_over_hodl\": 1.0327294575063206e-12, \"annualised_returns_over_uniform_hodl\": 0.8194637506997302, \"calmar\": -0.8358049681221001, \"daily_log_sharpe\": -0.6924635970399561, \"daily_returns\": 0.031016042748699544, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": 0.00501928385373447, \"return\": -0.23422459933396966, \"returns_over_hodl\": 4.1588954502458364e-13, \"returns_over_uniform_hodl\": 0.27259157042097737, \"sharpe\": -0.20210852984542413, \"sterling\": -1.2473843112470562, \"ulcer\": -0.17641031650810657}], \"train_objective\": [{\"annualised_returns\": -0.6178690595890777, \"annualised_returns_over_hodl\": -0.518767918886021, \"annualised_returns_over_uniform_hodl\": -0.518767918886021, \"calmar\": -0.7916894831581495, \"daily_log_sharpe\": -0.6520945022886104, \"daily_returns\": 0.008465833648069366, \"fee_revenue_over_value\": 0.0005532924348000932, \"jax_sharpe\": 0.10117862303033343, \"return\": -0.4423623426115366, \"returns_over_hodl\": -0.35856793322675384, \"returns_over_uniform_hodl\": -0.35856793322675384, \"sharpe\": 0.016110321121136195, \"sterling\": -1.674956475293604, \"ulcer\": -0.20566705608147798}], \"train_return\": -0.4423623426115366, \"train_returns_over_hodl\": -0.35856793322675384, \"train_sharpe\": 0.10117862303033343, \"validation_return\": -0.33914272228250186, \"validation_returns_over_hodl\": -1.1157952339857502e-10, \"validation_sharpe\": -2.243853205126334}, {\"centeredness_margin\": 0.4710259047457446, \"continuous_test_metrics\": [{\"annualised_returns\": -0.48450645322783326, \"annualised_returns_over_hodl\": 1.0200729150255938e-12, \"annualised_returns_over_uniform_hodl\": 0.8194637505228444, \"calmar\": -0.8358049682023745, \"daily_log_sharpe\": -0.6924635971657976, \"daily_returns\": 0.031016042746313602, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": 0.005019283703138278, \"return\": -0.23422459936395257, \"returns_over_hodl\": 4.107825191113079e-13, \"returns_over_uniform_hodl\": 0.2725915703711508, \"sharpe\": -0.20210852999300521, \"sterling\": -1.2473843114149463, \"ulcer\": -0.1764103165031913}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -1.3493828276978093e-10, \"optuna_trial_number\": 21, \"price_ratio\": 1.442498139017253, \"shift_exponent\": 0.0003849423736936362, \"step\": 21, \"test_objective\": [{\"annualised_returns\": -0.48450645322783326, \"annualised_returns_over_hodl\": 1.0200729150255938e-12, \"annualised_returns_over_uniform_hodl\": 0.8194637505228444, \"calmar\": -0.8358049682023745, \"daily_log_sharpe\": -0.6924635971657976, \"daily_returns\": 0.031016042746313602, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": 0.005019283703138278, \"return\": -0.23422459936395257, \"returns_over_hodl\": 4.107825191113079e-13, \"returns_over_uniform_hodl\": 0.2725915703711508, \"sharpe\": -0.20210852999300521, \"sterling\": -1.2473843114149463, \"ulcer\": -0.1764103165031913}], \"train_objective\": [{\"annualised_returns\": -0.660761056840905, \"annualised_returns_over_hodl\": -0.5727834484278991, \"annualised_returns_over_uniform_hodl\": -0.5727834484278993, \"calmar\": -0.833312611038584, \"daily_log_sharpe\": -0.7339799496424206, \"daily_returns\": 0.009090090581800854, \"fee_revenue_over_value\": 5.5587823417862555e-05, \"jax_sharpe\": 0.05537637013707369, \"return\": -0.4812477764638239, \"returns_over_hodl\": -0.40329655560863764, \"returns_over_uniform_hodl\": -0.40329655560863775, \"sharpe\": -0.0570087894089922, \"sterling\": -1.7655653971222083, \"ulcer\": -0.20928586021069584}], \"train_return\": -0.4812477764638239, \"train_returns_over_hodl\": -0.40329655560863764, \"train_sharpe\": 0.05537637013707369, \"validation_return\": -0.33914272227096987, \"validation_returns_over_hodl\": -1.3493828276978093e-10, \"validation_sharpe\": -2.2438532051760256}, {\"centeredness_margin\": 0.9878043656047788, \"continuous_test_metrics\": [{\"annualised_returns\": -0.7315174762941064, \"annualised_returns_over_hodl\": -0.051277029323405166, \"annualised_returns_over_uniform_hodl\": -0.052375684253397115, \"calmar\": -1.2199271560015403, \"daily_log_sharpe\": -1.6792749699993434, \"daily_returns\": 0.018007594687310207, \"fee_revenue_over_value\": 0.009171588692333183, \"jax_sharpe\": -1.1577968146966144, \"return\": -0.4111524377233915, \"returns_over_hodl\": -0.020976361110435437, \"returns_over_uniform_hodl\": -0.02143312077614623, \"sharpe\": -1.297624711462083, \"sterling\": -2.256766571450341, \"ulcer\": -0.14874608234352593}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -0.025671159158350387, \"optuna_trial_number\": 22, \"price_ratio\": 124.42308335487866, \"shift_exponent\": 2.6232466908778043, \"step\": 22, \"test_objective\": [{\"annualised_returns\": -0.7315174762941064, \"annualised_returns_over_hodl\": -0.051277029323405166, \"annualised_returns_over_uniform_hodl\": -0.052375684253397115, \"calmar\": -1.2199271560015403, \"daily_log_sharpe\": -1.6792749699993434, \"daily_returns\": 0.018007594687310207, \"fee_revenue_over_value\": 0.009171588692333183, \"jax_sharpe\": -1.1577968146966144, \"return\": -0.4111524377233915, \"returns_over_hodl\": -0.020976361110435437, \"returns_over_uniform_hodl\": -0.02143312077614623, \"sharpe\": -1.297624711462083, \"sterling\": -2.256766571450341, \"ulcer\": -0.14874608234352593}], \"train_objective\": [{\"annualised_returns\": -0.33141313322041244, \"annualised_returns_over_hodl\": -0.15802303535058326, \"annualised_returns_over_uniform_hodl\": -0.15802303535058293, \"calmar\": -0.45116734059688435, \"daily_log_sharpe\": -0.3507696892838676, \"daily_returns\": 0.007873107202503412, \"fee_revenue_over_value\": 0.005662943007666622, \"jax_sharpe\": 0.10114588641412237, \"return\": -0.2168416432008632, \"returns_over_hodl\": -0.09915896683701575, \"returns_over_uniform_hodl\": -0.09915896683701553, \"sharpe\": 0.13545262450587275, \"sterling\": -1.0976565004347953, \"ulcer\": -0.1636002633919992}], \"train_return\": -0.2168416432008632, \"train_returns_over_hodl\": -0.09915896683701575, \"train_sharpe\": 0.10114588641412238, \"validation_return\": -0.15882981322923373, \"validation_returns_over_hodl\": -0.025671159158350387, \"validation_sharpe\": -1.259267492214066}, {\"centeredness_margin\": 0.3744533083529593, \"continuous_test_metrics\": [{\"annualised_returns\": -0.6198492323785214, \"annualised_returns_over_hodl\": -0.13876500464532826, \"annualised_returns_over_uniform_hodl\": 0.34176372478705774, \"calmar\": -1.0620273404183347, \"daily_log_sharpe\": -1.092136816110571, \"daily_returns\": 0.02739368333809898, \"fee_revenue_over_value\": 2.0736505919430812e-08, \"jax_sharpe\": -0.4287485010647075, \"return\": -0.32262004316600323, \"returns_over_hodl\": -0.05839004356140265, \"returns_over_uniform_hodl\": 0.1256930195058814, \"sharpe\": -0.6502861245869594, \"sterling\": -1.6643716616892505, \"ulcer\": -0.16441451539006627}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -0.05092029127765005, \"optuna_trial_number\": 23, \"price_ratio\": 22.407952075626998, \"shift_exponent\": 0.0005101248639115349, \"step\": 23, \"test_objective\": [{\"annualised_returns\": -0.6198492323785214, \"annualised_returns_over_hodl\": -0.13876500464532826, \"annualised_returns_over_uniform_hodl\": 0.34176372478705774, \"calmar\": -1.0620273404183347, \"daily_log_sharpe\": -1.092136816110571, \"daily_returns\": 0.02739368333809898, \"fee_revenue_over_value\": 2.0736505919430812e-08, \"jax_sharpe\": -0.4287485010647075, \"return\": -0.32262004316600323, \"returns_over_hodl\": -0.05839004356140265, \"returns_over_uniform_hodl\": 0.1256930195058814, \"sharpe\": -0.6502861245869594, \"sterling\": -1.6643716616892505, \"ulcer\": -0.16441451539006627}], \"train_objective\": [{\"annualised_returns\": -0.3844355031856028, \"annualised_returns_over_hodl\": -0.22479612997753295, \"annualised_returns_over_uniform_hodl\": -0.22479612997753295, \"calmar\": -0.5200741785611581, \"daily_log_sharpe\": -0.38649704230002624, \"daily_returns\": 0.008002664209869316, \"fee_revenue_over_value\": 0.0028554826640373415, \"jax_sharpe\": 0.146984021053644, \"return\": -0.25515917227596885, \"returns_over_hodl\": -0.14323434722540873, \"returns_over_uniform_hodl\": -0.14323434722540873, \"sharpe\": 0.14203206635487028, \"sterling\": -1.2116958479578728, \"ulcer\": -0.17120762217562002}], \"train_return\": -0.25515917227596885, \"train_returns_over_hodl\": -0.14323434722540873, \"train_sharpe\": 0.146984021053644, \"validation_return\": -0.25870393212412135, \"validation_returns_over_hodl\": -0.050920291277650076, \"validation_sharpe\": -1.8596387015112483}, {\"centeredness_margin\": 0.05383434068198714, \"continuous_test_metrics\": [{\"annualised_returns\": -0.4860855771705289, \"annualised_returns_over_hodl\": -0.003063324723540428, \"annualised_returns_over_uniform_hodl\": 0.8138901428815692, \"calmar\": -0.8385290639101347, \"daily_log_sharpe\": -0.7023121458660914, \"daily_returns\": 0.03101604275510654, \"fee_revenue_over_value\": 9.551922404807975e-05, \"jax_sharpe\": -0.01850669659611285, \"return\": -0.23517021485154954, \"returns_over_hodl\": -0.0012348471850496257, \"returns_over_uniform_hodl\": 0.27102011443599183, \"sharpe\": -0.21505430491336489, \"sterling\": -1.251449846113841, \"ulcer\": -0.17641031652134803}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -0.02812286977106515, \"optuna_trial_number\": 24, \"price_ratio\": 6.993259970565307, \"shift_exponent\": 0.00010475448391964948, \"step\": 24, \"test_objective\": [{\"annualised_returns\": -0.4860855771705289, \"annualised_returns_over_hodl\": -0.003063324723540428, \"annualised_returns_over_uniform_hodl\": 0.8138901428815692, \"calmar\": -0.8385290639101347, \"daily_log_sharpe\": -0.7023121458660914, \"daily_returns\": 0.03101604275510654, \"fee_revenue_over_value\": 9.551922404807975e-05, \"jax_sharpe\": -0.01850669659611285, \"return\": -0.23517021485154954, \"returns_over_hodl\": -0.0012348471850496257, \"returns_over_uniform_hodl\": 0.27102011443599183, \"sharpe\": -0.21505430491336489, \"sterling\": -1.251449846113841, \"ulcer\": -0.17641031652134803}], \"train_objective\": [{\"annualised_returns\": -0.45376347254628757, \"annualised_returns_over_hodl\": -0.3121034883897359, \"annualised_returns_over_uniform_hodl\": -0.3121034883897359, \"calmar\": -0.6082986255318855, \"daily_log_sharpe\": -0.4435444875981933, \"daily_returns\": 0.008091516674973768, \"fee_revenue_over_value\": 0.006154137527646601, \"jax_sharpe\": 0.16172786836593311, \"return\": -0.3072791967405816, \"returns_over_hodl\": -0.20318627939795142, \"returns_over_uniform_hodl\": -0.20318627939795142, \"sharpe\": 0.1317870405079869, \"sterling\": -1.3617522816733574, \"ulcer\": -0.18040999629249416}], \"train_return\": -0.3072791967405816, \"train_returns_over_hodl\": -0.20318627939795142, \"train_sharpe\": 0.1617278683659331, \"validation_return\": -0.33753160511719005, \"validation_returns_over_hodl\": -0.02812286977106515, \"validation_sharpe\": -2.229275907977457}, {\"centeredness_margin\": 0.088882883631205, \"continuous_test_metrics\": [{\"annualised_returns\": -0.5702098628511472, \"annualised_returns_over_hodl\": -0.166255058439281, \"annualised_returns_over_uniform_hodl\": 0.51696869877635, \"calmar\": -0.9869698117696147, \"daily_log_sharpe\": -0.9098005515473503, \"daily_returns\": 0.03101604275183328, \"fee_revenue_over_value\": 0.0021149353034814974, \"jax_sharpe\": -0.23225790427577747, \"return\": -0.2882974876397483, \"returns_over_hodl\": -0.07061194223679057, \"returns_over_uniform_hodl\": 0.18273140804647525, \"sharpe\": -0.44015971013196314, \"sterling\": -1.4825726149402019, \"ulcer\": -0.17314343293118986}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -0.04740658349188487, \"optuna_trial_number\": 25, \"price_ratio\": 6.660752738608886, \"shift_exponent\": 0.0027106007436802, \"step\": 25, \"test_objective\": [{\"annualised_returns\": -0.5702098628511472, \"annualised_returns_over_hodl\": -0.166255058439281, \"annualised_returns_over_uniform_hodl\": 0.51696869877635, \"calmar\": -0.9869698117696147, \"daily_log_sharpe\": -0.9098005515473503, \"daily_returns\": 0.03101604275183328, \"fee_revenue_over_value\": 0.0021149353034814974, \"jax_sharpe\": -0.23225790427577747, \"return\": -0.2882974876397483, \"returns_over_hodl\": -0.07061194223679057, \"returns_over_uniform_hodl\": 0.18273140804647525, \"sharpe\": -0.44015971013196314, \"sterling\": -1.4825726149402019, \"ulcer\": -0.17314343293118986}], \"train_objective\": [{\"annualised_returns\": -0.45590982760445276, \"annualised_returns_over_hodl\": -0.3148064752516203, \"annualised_returns_over_uniform_hodl\": -0.31480647525162053, \"calmar\": -0.6103029952940241, \"daily_log_sharpe\": -0.4506632367463309, \"daily_returns\": 0.008095488066608801, \"fee_revenue_over_value\": 0.0055212491814233535, \"jax_sharpe\": 0.11123917152304923, \"return\": -0.3089330245657589, \"returns_over_hodl\": -0.20508862258789762, \"returns_over_uniform_hodl\": -0.20508862258789773, \"sharpe\": 0.12405373322877539, \"sterling\": -1.3676140621931292, \"ulcer\": -0.1805659362469907}], \"train_return\": -0.3089330245657589, \"train_returns_over_hodl\": -0.20508862258789762, \"train_sharpe\": 0.11123917152304923, \"validation_return\": -0.33287434547771444, \"validation_returns_over_hodl\": -0.04740658349188487, \"validation_sharpe\": -2.1953181362555862}, {\"centeredness_margin\": 0.14460414877893568, \"continuous_test_metrics\": [{\"annualised_returns\": -0.576083596619282, \"annualised_returns_over_hodl\": -0.1776494470641946, \"annualised_returns_over_uniform_hodl\": 0.4962370218460348, \"calmar\": -0.9945433240334403, \"daily_log_sharpe\": -0.9263609582297324, \"daily_returns\": 0.031023912911179598, \"fee_revenue_over_value\": 0.00018553374876842204, \"jax_sharpe\": -0.24899003163242944, \"return\": -0.29223081909878823, \"returns_over_hodl\": -0.07574834578487866, \"returns_over_uniform_hodl\": 0.17619486423207187, \"sharpe\": -0.4587461128820449, \"sterling\": -1.503640611823393, \"ulcer\": -0.17206440993716557}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -0.061122760162403145, \"optuna_trial_number\": 26, \"price_ratio\": 11.567920236972281, \"shift_exponent\": 0.0012529088306350625, \"step\": 26, \"test_objective\": [{\"annualised_returns\": -0.576083596619282, \"annualised_returns_over_hodl\": -0.1776494470641946, \"annualised_returns_over_uniform_hodl\": 0.4962370218460348, \"calmar\": -0.9945433240334403, \"daily_log_sharpe\": -0.9263609582297324, \"daily_returns\": 0.031023912911179598, \"fee_revenue_over_value\": 0.00018553374876842204, \"jax_sharpe\": -0.24899003163242944, \"return\": -0.29223081909878823, \"returns_over_hodl\": -0.07574834578487866, \"returns_over_uniform_hodl\": 0.17619486423207187, \"sharpe\": -0.4587461128820449, \"sterling\": -1.503640611823393, \"ulcer\": -0.17206440993716557}], \"train_objective\": [{\"annualised_returns\": -0.41478130214204756, \"annualised_returns_over_hodl\": -0.2630117530547239, \"annualised_returns_over_uniform_hodl\": -0.2630117530547239, \"calmar\": -0.5592217102632536, \"daily_log_sharpe\": -0.41168441576869264, \"daily_returns\": 0.008038909226183279, \"fee_revenue_over_value\": 0.005634226635014451, \"jax_sharpe\": 0.15309581781525314, \"return\": -0.27767291584631393, \"returns_over_hodl\": -0.16913115831374126, \"returns_over_uniform_hodl\": -0.16913115831374126, \"sharpe\": 0.13699547046819915, \"sterling\": -1.278809883128652, \"ulcer\": -0.17525370674460858}], \"train_return\": -0.27767291584631393, \"train_returns_over_hodl\": -0.16913115831374126, \"train_sharpe\": 0.15309581781525314, \"validation_return\": -0.30406336443466886, \"validation_returns_over_hodl\": -0.061122760162403145, \"validation_sharpe\": -2.0568307249112587}, {\"centeredness_margin\": 0.018509171010221814, \"continuous_test_metrics\": [{\"annualised_returns\": -0.5650691537842636, \"annualised_returns_over_hodl\": -0.09866705775564555, \"annualised_returns_over_uniform_hodl\": 0.5351131233918398, \"calmar\": -0.9837212613843211, \"daily_log_sharpe\": -0.9130195565481998, \"daily_returns\": 0.029527442731275652, \"fee_revenue_over_value\": 0.009924887050896345, \"jax_sharpe\": -0.245141835338623, \"return\": -0.2848812875902841, \"returns_over_hodl\": -0.04097355837577843, \"returns_over_uniform_hodl\": 0.18840856532005534, \"sharpe\": -0.4519876641000324, \"sterling\": -1.4888714680378226, \"ulcer\": -0.1690376012442919}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -0.055520591536748265, \"optuna_trial_number\": 27, \"price_ratio\": 19.00292113822414, \"shift_exponent\": 0.00015871536126914988, \"step\": 27, \"test_objective\": [{\"annualised_returns\": -0.5650691537842636, \"annualised_returns_over_hodl\": -0.09866705775564555, \"annualised_returns_over_uniform_hodl\": 0.5351131233918398, \"calmar\": -0.9837212613843211, \"daily_log_sharpe\": -0.9130195565481998, \"daily_returns\": 0.029527442731275652, \"fee_revenue_over_value\": 0.009924887050896345, \"jax_sharpe\": -0.245141835338623, \"return\": -0.2848812875902841, \"returns_over_hodl\": -0.04097355837577843, \"returns_over_uniform_hodl\": 0.18840856532005534, \"sharpe\": -0.4519876641000324, \"sterling\": -1.4888714680378226, \"ulcer\": -0.1690376012442919}], \"train_objective\": [{\"annualised_returns\": -0.3894494149314196, \"annualised_returns_over_hodl\": -0.23111034044519774, \"annualised_returns_over_uniform_hodl\": -0.23111034044519774, \"calmar\": -0.5270737413083293, \"daily_log_sharpe\": -0.387125521230753, \"daily_returns\": 0.008003919649448302, \"fee_revenue_over_value\": 0.006208489072097602, \"jax_sharpe\": 0.12221008950170233, \"return\": -0.25884843086660037, \"returns_over_hodl\": -0.14747797878668478, \"returns_over_uniform_hodl\": -0.14747797878668478, \"sharpe\": 0.14721442928639664, \"sterling\": -1.220239808505262, \"ulcer\": -0.17220539833250423}], \"train_return\": -0.25884843086660037, \"train_returns_over_hodl\": -0.14747797878668478, \"train_sharpe\": 0.12221008950170233, \"validation_return\": -0.2769832586692619, \"validation_returns_over_hodl\": -0.055520591536748265, \"validation_sharpe\": -1.9287646878183602}, {\"centeredness_margin\": 0.9550156943844016, \"continuous_test_metrics\": [{\"annualised_returns\": -0.48450645323703634, \"annualised_returns_over_hodl\": 1.0156320229270932e-12, \"annualised_returns_over_uniform_hodl\": 0.8194637504903617, \"calmar\": -0.835804968217126, \"daily_log_sharpe\": -0.6924635971888785, \"daily_returns\": 0.03101604274588301, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": 0.005019283675485143, \"return\": -0.2342245993694585, \"returns_over_hodl\": 4.0900616227190767e-13, \"returns_over_uniform_hodl\": 0.27259157036200077, \"sharpe\": -0.20210853002008836, \"sterling\": -1.2473843114457956, \"ulcer\": -0.17641031650228753}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -1.457455267583896e-10, \"optuna_trial_number\": 28, \"price_ratio\": 1.0502686975004236, \"shift_exponent\": 4.4654596451099356e-05, \"step\": 28, \"test_objective\": [{\"annualised_returns\": -0.48450645323703634, \"annualised_returns_over_hodl\": 1.0156320229270932e-12, \"annualised_returns_over_uniform_hodl\": 0.8194637504903617, \"calmar\": -0.835804968217126, \"daily_log_sharpe\": -0.6924635971888785, \"daily_returns\": 0.03101604274588301, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": 0.005019283675485143, \"return\": -0.2342245993694585, \"returns_over_hodl\": 4.0900616227190767e-13, \"returns_over_uniform_hodl\": 0.27259157036200077, \"sharpe\": -0.20210853002008836, \"sterling\": -1.2473843114457956, \"ulcer\": -0.17641031650228753}], \"train_objective\": [{\"annualised_returns\": -0.6679531298624513, \"annualised_returns_over_hodl\": -0.5818407005414304, \"annualised_returns_over_uniform_hodl\": -0.5818407005414303, \"calmar\": -0.8320754516220533, \"daily_log_sharpe\": -0.7456770960837195, \"daily_returns\": 0.016251779317758525, \"fee_revenue_over_value\": 1.4816553381667066e-06, \"jax_sharpe\": 0.05154012824172365, \"return\": -0.4879528997448246, \"returns_over_hodl\": -0.41100923610486484, \"returns_over_uniform_hodl\": -0.41100923610486473, \"sharpe\": -0.06439277953790219, \"sterling\": -1.7674616560262428, \"ulcer\": -0.2127359326425834}], \"train_return\": -0.4879528997448246, \"train_returns_over_hodl\": -0.41100923610486484, \"train_sharpe\": 0.05154012824172365, \"validation_return\": -0.33914272227400477, \"validation_returns_over_hodl\": -1.457455267583896e-10, \"validation_sharpe\": -2.243853205237111}, {\"centeredness_margin\": 0.44188245938865, \"continuous_test_metrics\": [{\"annualised_returns\": -0.4843533421295676, \"annualised_returns_over_hodl\": 0.0002970182153290146, \"annualised_returns_over_uniform_hodl\": 0.8200041648400396, \"calmar\": -0.8355408412119624, \"daily_log_sharpe\": -0.692143260839257, \"daily_returns\": 0.03101604275227844, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": 0.00523852763899954, \"return\": -0.2341330049999294, \"returns_over_hodl\": 0.00011960986104031512, \"returns_over_uniform_hodl\": 0.27274378499629104, \"sharpe\": -0.2017630318123524, \"sterling\": -1.2469901190170258, \"ulcer\": -0.17641031651550887}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -8.22710788384029e-11, \"optuna_trial_number\": 29, \"price_ratio\": 3.7300689666697657, \"shift_exponent\": 1.998376427787445e-05, \"step\": 29, \"test_objective\": [{\"annualised_returns\": -0.4843533421295676, \"annualised_returns_over_hodl\": 0.0002970182153290146, \"annualised_returns_over_uniform_hodl\": 0.8200041648400396, \"calmar\": -0.8355408412119624, \"daily_log_sharpe\": -0.692143260839257, \"daily_returns\": 0.03101604275227844, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": 0.00523852763899954, \"return\": -0.2341330049999294, \"returns_over_hodl\": 0.00011960986104031512, \"returns_over_uniform_hodl\": 0.27274378499629104, \"sharpe\": -0.2017630318123524, \"sterling\": -1.2469901190170258, \"ulcer\": -0.17641031651550887}], \"train_objective\": [{\"annualised_returns\": -0.5352105317878021, \"annualised_returns_over_hodl\": -0.4146728793353096, \"annualised_returns_over_uniform_hodl\": -0.4146728793353093, \"calmar\": -0.705419457535789, \"daily_log_sharpe\": -0.5325020875398545, \"daily_returns\": 0.008222714390357046, \"fee_revenue_over_value\": 0.0018286379630518732, \"jax_sharpe\": 0.11663373425203497, \"return\": -0.37196453193446777, \"returns_over_hodl\": -0.2775916709521141, \"returns_over_uniform_hodl\": -0.2775916709521139, \"sharpe\": 0.09630826636200714, \"sterling\": -1.5124173482947805, \"ulcer\": -0.1934372131100802}], \"train_return\": -0.37196453193446777, \"train_returns_over_hodl\": -0.2775916709521141, \"train_sharpe\": 0.11663373425203497, \"validation_return\": -0.33914272230262366, \"validation_returns_over_hodl\": -8.22710788384029e-11, \"validation_sharpe\": -2.243853205078955}, {\"centeredness_margin\": 0.5915648754383668, \"continuous_test_metrics\": [{\"annualised_returns\": -0.7265839908793457, \"annualised_returns_over_hodl\": -0.0804510757709822, \"annualised_returns_over_uniform_hodl\": -0.03496266728723685, \"calmar\": -1.2187716616679676, \"daily_log_sharpe\": -1.6495593576867638, \"daily_returns\": 0.01901910217149718, \"fee_revenue_over_value\": 0.00948933082165234, \"jax_sharpe\": -1.121773280037095, \"return\": -0.40681836144576855, \"returns_over_hodl\": -0.033214316485419726, \"returns_over_uniform_hodl\": -0.014230605610906832, \"sharpe\": -1.2658606156721908, \"sterling\": -2.2188934115727514, \"ulcer\": -0.14845708939625638}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -0.02606405821491664, \"optuna_trial_number\": 30, \"price_ratio\": 96.03381390055674, \"shift_exponent\": 0.23515124919600006, \"step\": 30, \"test_objective\": [{\"annualised_returns\": -0.7265839908793457, \"annualised_returns_over_hodl\": -0.0804510757709822, \"annualised_returns_over_uniform_hodl\": -0.03496266728723685, \"calmar\": -1.2187716616679676, \"daily_log_sharpe\": -1.6495593576867638, \"daily_returns\": 0.01901910217149718, \"fee_revenue_over_value\": 0.00948933082165234, \"jax_sharpe\": -1.121773280037095, \"return\": -0.40681836144576855, \"returns_over_hodl\": -0.033214316485419726, \"returns_over_uniform_hodl\": -0.014230605610906832, \"sharpe\": -1.2658606156721908, \"sterling\": -2.2188934115727514, \"ulcer\": -0.14845708939625638}], \"train_objective\": [{\"annualised_returns\": -0.32133687474852135, \"annualised_returns_over_hodl\": -0.145333618395609, \"annualised_returns_over_uniform_hodl\": -0.145333618395609, \"calmar\": -0.4381229398809701, \"daily_log_sharpe\": -0.3277954587209086, \"daily_returns\": 0.007961867081545755, \"fee_revenue_over_value\": 0.005786742554799768, \"jax_sharpe\": 0.1303568390051166, \"return\": -0.20969687953292793, \"returns_over_hodl\": -0.09094058261312243, \"returns_over_uniform_hodl\": -0.09094058261312243, \"sharpe\": 0.16630503196180232, \"sterling\": -1.0548448349777164, \"ulcer\": -0.16456712481739721}], \"train_return\": -0.20969687953292793, \"train_returns_over_hodl\": -0.09094058261312243, \"train_sharpe\": 0.1303568390051166, \"validation_return\": -0.17141514885407216, \"validation_returns_over_hodl\": -0.02606405821491664, \"validation_sharpe\": -1.3565835350855533}, {\"centeredness_margin\": 0.89782391786892, \"continuous_test_metrics\": [{\"annualised_returns\": -0.7364938189698427, \"annualised_returns_over_hodl\": -0.11318662761612919, \"annualised_returns_over_uniform_hodl\": -0.06993996835622784, \"calmar\": -1.2282165657599302, \"daily_log_sharpe\": -1.6972290871047917, \"daily_returns\": 0.0189791006351972, \"fee_revenue_over_value\": 0.0033441122260135163, \"jax_sharpe\": -1.174274528294817, \"return\": -0.4155726252265861, \"returns_over_hodl\": -0.04722562728972912, \"returns_over_uniform_hodl\": -0.02877873850081225, \"sharpe\": -1.3151821755859938, \"sterling\": -2.2527137323357764, \"ulcer\": -0.1482886757607437}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -0.02822334929422965, \"optuna_trial_number\": 31, \"price_ratio\": 68.7451973073316, \"shift_exponent\": 0.004566837322500811, \"step\": 31, \"test_objective\": [{\"annualised_returns\": -0.7364938189698427, \"annualised_returns_over_hodl\": -0.11318662761612919, \"annualised_returns_over_uniform_hodl\": -0.06993996835622784, \"calmar\": -1.2282165657599302, \"daily_log_sharpe\": -1.6972290871047917, \"daily_returns\": 0.0189791006351972, \"fee_revenue_over_value\": 0.0033441122260135163, \"jax_sharpe\": -1.174274528294817, \"return\": -0.4155726252265861, \"returns_over_hodl\": -0.04722562728972912, \"returns_over_uniform_hodl\": -0.02877873850081225, \"sharpe\": -1.3151821755859938, \"sterling\": -2.2527137323357764, \"ulcer\": -0.1482886757607437}], \"train_objective\": [{\"annualised_returns\": -0.3224812507361675, \"annualised_returns_over_hodl\": -0.1467747747634227, \"annualised_returns_over_uniform_hodl\": -0.1467747747634227, \"calmar\": -0.43933994640485685, \"daily_log_sharpe\": -0.32932489689801997, \"daily_returns\": 0.007964612476011594, \"fee_revenue_over_value\": 0.0007171099710316152, \"jax_sharpe\": 0.13647698417777976, \"return\": -0.2105062132912221, \"returns_over_hodl\": -0.09187153234080614, \"returns_over_uniform_hodl\": -0.09187153234080614, \"sharpe\": 0.16709250821728708, \"sterling\": -1.0572364801898868, \"ulcer\": -0.16476698035420775}], \"train_return\": -0.2105062132912221, \"train_returns_over_hodl\": -0.09187153234080614, \"train_sharpe\": 0.13647698417777976, \"validation_return\": -0.16762073178490255, \"validation_returns_over_hodl\": -0.02822334929422965, \"validation_sharpe\": -1.326511687400525}, {\"centeredness_margin\": 0.4916278556481153, \"continuous_test_metrics\": [{\"annualised_returns\": -0.5093493790919459, \"annualised_returns_over_hodl\": -0.04819250632912453, \"annualised_returns_over_uniform_hodl\": 0.73177923274428, \"calmar\": -0.8788777417909254, \"daily_log_sharpe\": -0.7549905486703242, \"daily_returns\": 0.031016042752441953, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": -0.0714633074197504, \"return\": -0.24930706188882523, \"returns_over_hodl\": -0.019695673960787308, \"returns_over_uniform_hodl\": 0.24752702187083409, \"sharpe\": -0.27209863822005753, \"sterling\": -1.3117251435985522, \"ulcer\": -0.17629906609020693}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -0.05961080200131752, \"optuna_trial_number\": 32, \"price_ratio\": 9.582495187752238, \"shift_exponent\": 1.1686250003803163e-05, \"step\": 32, \"test_objective\": [{\"annualised_returns\": -0.5093493790919459, \"annualised_returns_over_hodl\": -0.04819250632912453, \"annualised_returns_over_uniform_hodl\": 0.73177923274428, \"calmar\": -0.8788777417909254, \"daily_log_sharpe\": -0.7549905486703242, \"daily_returns\": 0.031016042752441953, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": -0.0714633074197504, \"return\": -0.24930706188882523, \"returns_over_hodl\": -0.019695673960787308, \"returns_over_uniform_hodl\": 0.24752702187083409, \"sharpe\": -0.27209863822005753, \"sterling\": -1.3117251435985522, \"ulcer\": -0.17629906609020693}], \"train_objective\": [{\"annualised_returns\": -0.4357541886147501, \"annualised_returns_over_hodl\": -0.28942370963006103, \"annualised_returns_over_uniform_hodl\": -0.2894237096300608, \"calmar\": -0.5854264899141272, \"daily_log_sharpe\": -0.4336561352535436, \"daily_returns\": 0.008029996298253754, \"fee_revenue_over_value\": 0.00185917724978015, \"jax_sharpe\": 0.14666922026471674, \"return\": -0.2935017073291931, \"returns_over_hodl\": -0.18733849115947332, \"returns_over_uniform_hodl\": -0.1873384911594732, \"sharpe\": 0.12438005163690628, \"sterling\": -1.3294553805090306, \"ulcer\": -0.17730906480023229}], \"train_return\": -0.2935017073291931, \"train_returns_over_hodl\": -0.18733849115947332, \"train_sharpe\": 0.1466692202647167, \"validation_return\": -0.32670694590831795, \"validation_returns_over_hodl\": -0.05961080200131752, \"validation_sharpe\": -2.1408287723704285}, {\"centeredness_margin\": 0.976326242727485, \"continuous_test_metrics\": [{\"annualised_returns\": -0.6958567107505191, \"annualised_returns_over_hodl\": -0.11642586525860432, \"annualised_returns_over_uniform_hodl\": 0.07349101306750616, \"calmar\": -1.1706281391467248, \"daily_log_sharpe\": -1.4515363672652963, \"daily_returns\": 0.0219425220634969, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": -0.8728301970109331, \"return\": -0.3808212332242482, \"returns_over_hodl\": -0.048628755823250325, \"returns_over_uniform_hodl\": 0.028972305061185644, \"sharpe\": -1.0494574015033644, \"sterling\": -2.022755990494557, \"ulcer\": -0.1541804113311106}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -0.036472303074536416, \"optuna_trial_number\": 33, \"price_ratio\": 84.08365031110105, \"shift_exponent\": 0.000984096826881805, \"step\": 33, \"test_objective\": [{\"annualised_returns\": -0.6958567107505191, \"annualised_returns_over_hodl\": -0.11642586525860432, \"annualised_returns_over_uniform_hodl\": 0.07349101306750616, \"calmar\": -1.1706281391467248, \"daily_log_sharpe\": -1.4515363672652963, \"daily_returns\": 0.0219425220634969, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": -0.8728301970109331, \"return\": -0.3808212332242482, \"returns_over_hodl\": -0.048628755823250325, \"returns_over_uniform_hodl\": 0.028972305061185644, \"sharpe\": -1.0494574015033644, \"sterling\": -2.022755990494557, \"ulcer\": -0.1541804113311106}], \"train_objective\": [{\"annualised_returns\": -0.34445008731659743, \"annualised_returns_over_hodl\": -0.1744409693298311, \"annualised_returns_over_uniform_hodl\": -0.1744409693298311, \"calmar\": -0.46826294752279746, \"daily_log_sharpe\": -0.3495902361111672, \"daily_returns\": 0.00795313659163122, \"fee_revenue_over_value\": 2.873382446590157e-05, \"jax_sharpe\": 0.12889915634855487, \"return\": -0.22614884104289512, \"returns_over_hodl\": -0.10986472723282625, \"returns_over_uniform_hodl\": -0.10986472723282625, \"sharpe\": 0.15724081990491215, \"sterling\": -1.1146764265771154, \"ulcer\": -0.16683336749380753}], \"train_return\": -0.22614884104289512, \"train_returns_over_hodl\": -0.10986472723282625, \"train_sharpe\": 0.12889915634855484, \"validation_return\": -0.20207288942018875, \"validation_returns_over_hodl\": -0.036472303074536416, \"validation_sharpe\": -1.5564904629628193}, {\"centeredness_margin\": 0.33188981347825586, \"continuous_test_metrics\": [{\"annualised_returns\": -0.4762545883219823, \"annualised_returns_over_hodl\": 0.016007697463724213, \"annualised_returns_over_uniform_hodl\": 0.8485891763684628, \"calmar\": -0.8215699433666528, \"daily_log_sharpe\": -0.6777987658490615, \"daily_returns\": 0.031016042754039706, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": 0.008844004560698214, \"return\": -0.22931111900215828, \"returns_over_hodl\": 0.006416346438074427, \"returns_over_uniform_hodl\": 0.28075695892295083, \"sharpe\": -0.18714292593235823, \"sterling\": -1.226139466676818, \"ulcer\": -0.17641031651914385}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -6.151490428152329e-11, \"optuna_trial_number\": 34, \"price_ratio\": 5.491035424527673, \"shift_exponent\": 6.245516059036751e-05, \"step\": 34, \"test_objective\": [{\"annualised_returns\": -0.4762545883219823, \"annualised_returns_over_hodl\": 0.016007697463724213, \"annualised_returns_over_uniform_hodl\": 0.8485891763684628, \"calmar\": -0.8215699433666528, \"daily_log_sharpe\": -0.6777987658490615, \"daily_returns\": 0.031016042754039706, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": 0.008844004560698214, \"return\": -0.22931111900215828, \"returns_over_hodl\": 0.006416346438074427, \"returns_over_uniform_hodl\": 0.28075695892295083, \"sharpe\": -0.18714292593235823, \"sterling\": -1.226139466676818, \"ulcer\": -0.17641031651914385}], \"train_objective\": [{\"annualised_returns\": -0.4825095662112463, \"annualised_returns_over_hodl\": -0.34830454152458445, \"annualised_returns_over_uniform_hodl\": -0.34830454152458445, \"calmar\": -0.6436467515004068, \"daily_log_sharpe\": -0.47201838614148506, \"daily_returns\": 0.008123455963751088, \"fee_revenue_over_value\": 0.0019443449766646155, \"jax_sharpe\": 0.16581481853863733, \"return\": -0.3296463415960552, \"returns_over_hodl\": -0.22891446285610073, \"returns_over_uniform_hodl\": -0.22891446285610073, \"sharpe\": 0.1209603876964481, \"sterling\": -1.421689986236972, \"ulcer\": -0.18412310433607476}], \"train_return\": -0.3296463415960552, \"train_returns_over_hodl\": -0.22891446285610073, \"train_sharpe\": 0.1658148185386373, \"validation_return\": -0.3391427223076884, \"validation_returns_over_hodl\": -6.151490428152329e-11, \"validation_sharpe\": -2.2438532050073654}, {\"centeredness_margin\": 0.33456583188694045, \"continuous_test_metrics\": [{\"annualised_returns\": -0.4788009920392563, \"annualised_returns_over_hodl\": 0.011067958184165505, \"annualised_returns_over_uniform_hodl\": 0.8396014998266568, \"calmar\": -0.8259626618233281, \"daily_log_sharpe\": -0.6844957158725857, \"daily_returns\": 0.03101604275426897, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": 0.0009864175080211682, \"return\": -0.23082238279609757, \"returns_over_hodl\": 0.004442838546085737, \"returns_over_uniform_hodl\": 0.27824548942004323, \"sharpe\": -0.19487496851169483, \"sterling\": -1.2326953119891255, \"ulcer\": -0.17641031651961442}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": 6.469763832384068e-05, \"optuna_trial_number\": 35, \"price_ratio\": 6.074154897211842, \"shift_exponent\": 5.670437762734616e-05, \"step\": 35, \"test_objective\": [{\"annualised_returns\": -0.4788009920392563, \"annualised_returns_over_hodl\": 0.011067958184165505, \"annualised_returns_over_uniform_hodl\": 0.8396014998266568, \"calmar\": -0.8259626618233281, \"daily_log_sharpe\": -0.6844957158725857, \"daily_returns\": 0.03101604275426897, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": 0.0009864175080211682, \"return\": -0.23082238279609757, \"returns_over_hodl\": 0.004442838546085737, \"returns_over_uniform_hodl\": 0.27824548942004323, \"sharpe\": -0.19487496851169483, \"sterling\": -1.2326953119891255, \"ulcer\": -0.17641031651961442}], \"train_objective\": [{\"annualised_returns\": -0.4762699865114922, \"annualised_returns_over_hodl\": -0.3404468006126333, \"annualised_returns_over_uniform_hodl\": -0.3404468006126333, \"calmar\": -0.6359101061348627, \"daily_log_sharpe\": -0.46905343368518887, \"daily_returns\": 0.008095399997993987, \"fee_revenue_over_value\": 0.001963776265734488, \"jax_sharpe\": 0.15443743166005902, \"return\": -0.3247507138189881, \"returns_over_hodl\": -0.22328318490779464, \"returns_over_uniform_hodl\": -0.22328318490779464, \"sharpe\": 0.11631780632311614, \"sterling\": -1.413639057978347, \"ulcer\": -0.18269258824796053}], \"train_return\": -0.3247507138189881, \"train_returns_over_hodl\": -0.22328318490779464, \"train_sharpe\": 0.15443743166005902, \"validation_return\": -0.33909996635650497, \"validation_returns_over_hodl\": 6.469763832384068e-05, \"validation_sharpe\": -2.2434511513149817}, {\"centeredness_margin\": 0.39110556334284036, \"continuous_test_metrics\": [{\"annualised_returns\": -0.4834257732563416, \"annualised_returns_over_hodl\": 0.002096398377903208, \"annualised_returns_over_uniform_hodl\": 0.8232780718588875, \"calmar\": -0.8339407221446585, \"daily_log_sharpe\": -0.6902165976839367, \"daily_returns\": 0.031016042752799546, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": 0.006225098223387479, \"return\": -0.23357846025611784, \"returns_over_hodl\": 0.0008437709358430912, \"returns_over_uniform_hodl\": 0.27366534628669315, \"sharpe\": -0.19969563317940256, \"sterling\": -1.2446020458355154, \"ulcer\": -0.176410316516588}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -7.610423402582e-11, \"optuna_trial_number\": 36, \"price_ratio\": 4.156358050869001, \"shift_exponent\": 4.048563706178579e-05, \"step\": 36, \"test_objective\": [{\"annualised_returns\": -0.4834257732563416, \"annualised_returns_over_hodl\": 0.002096398377903208, \"annualised_returns_over_uniform_hodl\": 0.8232780718588875, \"calmar\": -0.8339407221446585, \"daily_log_sharpe\": -0.6902165976839367, \"daily_returns\": 0.031016042752799546, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": 0.006225098223387479, \"return\": -0.23357846025611784, \"returns_over_hodl\": 0.0008437709358430912, \"returns_over_uniform_hodl\": 0.27366534628669315, \"sharpe\": -0.19969563317940256, \"sterling\": -1.2446020458355154, \"ulcer\": -0.176410316516588}], \"train_objective\": [{\"annualised_returns\": -0.5205769690901287, \"annualised_returns_over_hodl\": -0.39624427519364913, \"annualised_returns_over_uniform_hodl\": -0.39624427519364913, \"calmar\": -0.6892185633255635, \"daily_log_sharpe\": -0.515367779492877, \"daily_returns\": 0.008180471270968371, \"fee_revenue_over_value\": 0.0019008236563698867, \"jax_sharpe\": 0.11410756701981956, \"return\": -0.36003295269321267, \"returns_over_hodl\": -0.2638671718420126, \"returns_over_uniform_hodl\": -0.2638671718420126, \"sharpe\": 0.1033588978219116, \"sterling\": -1.491489398218617, \"ulcer\": -0.19029181601850934}], \"train_return\": -0.36003295269321267, \"train_returns_over_hodl\": -0.2638671718420126, \"train_sharpe\": 0.11410756701981954, \"validation_return\": -0.33914272230430165, \"validation_returns_over_hodl\": -7.610423402582e-11, \"validation_sharpe\": -2.24385320505977}, {\"centeredness_margin\": 0.3441900533179566, \"continuous_test_metrics\": [{\"annualised_returns\": -0.47682064486109055, \"annualised_returns_over_hodl\": 0.014909610892445135, \"annualised_returns_over_uniform_hodl\": 0.846591247664791, \"calmar\": -0.8225464291328591, \"daily_log_sharpe\": -0.6796548541979415, \"daily_returns\": 0.031016042754148654, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": 0.005728673229365059, \"return\": -0.22964668760653173, \"returns_over_hodl\": 0.0059781388311801464, \"returns_over_uniform_hodl\": 0.2801992996186011, \"sharpe\": -0.18940469017708875, \"sterling\": -1.2275968078861395, \"ulcer\": -0.176410316519374}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -6.94099222542377e-11, \"optuna_trial_number\": 37, \"price_ratio\": 5.945571819754773, \"shift_exponent\": 1.1548100342348791e-05, \"step\": 37, \"test_objective\": [{\"annualised_returns\": -0.47682064486109055, \"annualised_returns_over_hodl\": 0.014909610892445135, \"annualised_returns_over_uniform_hodl\": 0.846591247664791, \"calmar\": -0.8225464291328591, \"daily_log_sharpe\": -0.6796548541979415, \"daily_returns\": 0.031016042754148654, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": 0.005728673229365059, \"return\": -0.22964668760653173, \"returns_over_hodl\": 0.0059781388311801464, \"returns_over_uniform_hodl\": 0.2801992996186011, \"sharpe\": -0.18940469017708875, \"sterling\": -1.2275968078861395, \"ulcer\": -0.176410316519374}], \"train_objective\": [{\"annualised_returns\": -0.4803380910807947, \"annualised_returns_over_hodl\": -0.3455699199967104, \"annualised_returns_over_uniform_hodl\": -0.3455699199967105, \"calmar\": -0.6409955522747013, \"daily_log_sharpe\": -0.47322169503941464, \"daily_returns\": 0.008069079439430995, \"fee_revenue_over_value\": 0.0019407909326986357, \"jax_sharpe\": 0.10326715363683604, \"return\": -0.32793996607740117, \"returns_over_hodl\": -0.22695167580052877, \"returns_over_uniform_hodl\": -0.22695167580052888, \"sharpe\": 0.11436614472433217, \"sterling\": -1.422166286448993, \"ulcer\": -0.18321434417631738}], \"train_return\": -0.32793996607740117, \"train_returns_over_hodl\": -0.22695167580052877, \"train_sharpe\": 0.10326715363683603, \"validation_return\": -0.33914272230725817, \"validation_returns_over_hodl\": -6.94099222542377e-11, \"validation_sharpe\": -2.2438532049955398}, {\"centeredness_margin\": 0.4664135297935122, \"continuous_test_metrics\": [{\"annualised_returns\": -0.4845064531560398, \"annualised_returns_over_hodl\": 1.0249578963339445e-12, \"annualised_returns_over_uniform_hodl\": 0.8194637507762448, \"calmar\": -0.8358049680873689, \"daily_log_sharpe\": -0.6924635969855503, \"daily_returns\": 0.031016042749726462, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": 0.005019283918884224, \"return\": -0.23422459932100037, \"returns_over_hodl\": 4.127809205556332e-13, \"returns_over_uniform_hodl\": 0.2725915704425306, \"sharpe\": -0.20210852978161045, \"sterling\": -1.2473843111744236, \"ulcer\": -0.17641031651023598}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -1.0490719404288029e-10, \"optuna_trial_number\": 38, \"price_ratio\": 2.5769588625457978, \"shift_exponent\": 2.483446064940032e-05, \"step\": 38, \"test_objective\": [{\"annualised_returns\": -0.4845064531560398, \"annualised_returns_over_hodl\": 1.0249578963339445e-12, \"annualised_returns_over_uniform_hodl\": 0.8194637507762448, \"calmar\": -0.8358049680873689, \"daily_log_sharpe\": -0.6924635969855503, \"daily_returns\": 0.031016042749726462, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": 0.005019283918884224, \"return\": -0.23422459932100037, \"returns_over_hodl\": 4.127809205556332e-13, \"returns_over_uniform_hodl\": 0.2725915704425306, \"sharpe\": -0.20210852978161045, \"sterling\": -1.2473843111744236, \"ulcer\": -0.17641031651023598}], \"train_objective\": [{\"annualised_returns\": -0.5976621411419748, \"annualised_returns_over_hodl\": -0.4933205750861629, \"annualised_returns_over_uniform_hodl\": -0.493320575086163, \"calmar\": -0.7707153288404421, \"daily_log_sharpe\": -0.6187524499982375, \"daily_returns\": 0.008314776701561886, \"fee_revenue_over_value\": 0.00043824400823337304, \"jax_sharpe\": 0.11740676493596468, \"return\": -0.4246413062041793, \"returns_over_hodl\": -0.33818401392444464, \"returns_over_uniform_hodl\": -0.33818401392444475, \"sharpe\": 0.04251953395141516, \"sterling\": -1.631688721011887, \"ulcer\": -0.2035462888175405}], \"train_return\": -0.4246413062041793, \"train_returns_over_hodl\": -0.33818401392444464, \"train_sharpe\": 0.11740676493596469, \"validation_return\": -0.3391427222894733, \"validation_returns_over_hodl\": -1.0490719404288029e-10, \"validation_sharpe\": -2.2438532051243096}, {\"centeredness_margin\": 0.25903957657743526, \"continuous_test_metrics\": [{\"annualised_returns\": -0.49754934466735945, \"annualised_returns_over_hodl\": -0.02530175530928469, \"annualised_returns_over_uniform_hodl\": 0.7734281244227357, \"calmar\": -0.8583048399630722, \"daily_log_sharpe\": -0.728123598367078, \"daily_returns\": 0.031016042751528517, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": -0.045747332204523934, \"return\": -0.24208759851302564, \"returns_over_hodl\": -0.010268022752202177, \"returns_over_uniform_hodl\": 0.25952457131811957, \"sharpe\": -0.2431583183879318, \"sterling\": -1.2809638999224526, \"ulcer\": -0.17641031652011235}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -0.050943346238995546, \"optuna_trial_number\": 39, \"price_ratio\": 8.388847309836132, \"shift_exponent\": 1.4241856681353848e-05, \"step\": 39, \"test_objective\": [{\"annualised_returns\": -0.49754934466735945, \"annualised_returns_over_hodl\": -0.02530175530928469, \"annualised_returns_over_uniform_hodl\": 0.7734281244227357, \"calmar\": -0.8583048399630722, \"daily_log_sharpe\": -0.728123598367078, \"daily_returns\": 0.031016042751528517, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": -0.045747332204523934, \"return\": -0.24208759851302564, \"returns_over_hodl\": -0.010268022752202177, \"returns_over_uniform_hodl\": 0.25952457131811957, \"sharpe\": -0.2431583183879318, \"sterling\": -1.2809638999224526, \"ulcer\": -0.17641031652011235}], \"train_objective\": [{\"annualised_returns\": -0.4442695606085866, \"annualised_returns_over_hodl\": -0.30014744265635507, \"annualised_returns_over_uniform_hodl\": -0.30014744265635507, \"calmar\": -0.5960006435891982, \"daily_log_sharpe\": -0.4399348453502157, \"daily_returns\": 0.008062146007168224, \"fee_revenue_over_value\": 0.0028363317238243187, \"jax_sharpe\": 0.10706623339233828, \"return\": -0.2999942767590501, \"returns_over_hodl\": -0.19480667802397822, \"returns_over_uniform_hodl\": -0.19480667802397822, \"sharpe\": 0.1247802540571149, \"sterling\": -1.3462293121860471, \"ulcer\": -0.17862069442286982}], \"train_return\": -0.2999942767590501, \"train_returns_over_hodl\": -0.19480667802397822, \"train_sharpe\": 0.10706623339233828, \"validation_return\": -0.3330349754751103, \"validation_returns_over_hodl\": -0.050943346238995546, \"validation_sharpe\": -2.18967728514072}, {\"centeredness_margin\": 0.4089775384114372, \"continuous_test_metrics\": [{\"annualised_returns\": -0.47643256375517473, \"annualised_returns_over_hodl\": 0.01566244496417646, \"annualised_returns_over_uniform_hodl\": 0.8479610019689872, \"calmar\": -0.8218769630105321, \"daily_log_sharpe\": -0.6787693660195168, \"daily_returns\": 0.031016042754150375, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": 0.006428837292296911, \"return\": -0.22941660271715947, \"returns_over_hodl\": 0.006278598847442307, \"returns_over_uniform_hodl\": 0.28058166250260297, \"sharpe\": -0.1884037710285781, \"sterling\": -1.2265976734352675, \"ulcer\": -0.17641031651937555}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -6.958322806838169e-11, \"optuna_trial_number\": 40, \"price_ratio\": 5.644758824346247, \"shift_exponent\": 7.194465368634824e-05, \"step\": 40, \"test_objective\": [{\"annualised_returns\": -0.47643256375517473, \"annualised_returns_over_hodl\": 0.01566244496417646, \"annualised_returns_over_uniform_hodl\": 0.8479610019689872, \"calmar\": -0.8218769630105321, \"daily_log_sharpe\": -0.6787693660195168, \"daily_returns\": 0.031016042754150375, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": 0.006428837292296911, \"return\": -0.22941660271715947, \"returns_over_hodl\": 0.006278598847442307, \"returns_over_uniform_hodl\": 0.28058166250260297, \"sharpe\": -0.1884037710285781, \"sterling\": -1.2265976734352675, \"ulcer\": -0.17641031651937555}], \"train_objective\": [{\"annualised_returns\": -0.4803633893848386, \"annualised_returns_over_hodl\": -0.3456017791167558, \"annualised_returns_over_uniform_hodl\": -0.34560177911675616, \"calmar\": -0.6408976143483879, \"daily_log_sharpe\": -0.47049810546031295, \"daily_returns\": 0.008094040074464065, \"fee_revenue_over_value\": 0.0018962785804950107, \"jax_sharpe\": 0.10973922363939911, \"return\": -0.32795982969184445, \"returns_over_hodl\": -0.22697452425614706, \"returns_over_uniform_hodl\": -0.22697452425614728, \"sharpe\": 0.1201105292525842, \"sterling\": -1.4186200780706966, \"ulcer\": -0.1836650020897846}], \"train_return\": -0.32795982969184445, \"train_returns_over_hodl\": -0.22697452425614706, \"train_sharpe\": 0.10973922363939911, \"validation_return\": -0.3391427223081289, \"validation_returns_over_hodl\": -6.958322806838169e-11, \"validation_sharpe\": -2.2438532050042674}, {\"centeredness_margin\": 0.2861435784846565, \"continuous_test_metrics\": [{\"annualised_returns\": -0.5549163198364339, \"annualised_returns_over_hodl\": -0.13658729040431206, \"annualised_returns_over_uniform_hodl\": 0.5709481274356796, \"calmar\": -0.9594781797405532, \"daily_log_sharpe\": -0.8688154297687901, \"daily_returns\": 0.03121932179500002, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": -0.18814995501975806, \"return\": -0.2782045110374972, \"returns_over_hodl\": -0.05743186807173517, \"returns_over_uniform_hodl\": 0.19950425937247718, \"sharpe\": -0.39645130884026253, \"sterling\": -1.4420571003699216, \"ulcer\": -0.17318190903138467}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -0.06309752342311756, \"optuna_trial_number\": 41, \"price_ratio\": 14.929645355814568, \"shift_exponent\": 2.3813025433610046e-05, \"step\": 41, \"test_objective\": [{\"annualised_returns\": -0.5549163198364339, \"annualised_returns_over_hodl\": -0.13658729040431206, \"annualised_returns_over_uniform_hodl\": 0.5709481274356796, \"calmar\": -0.9594781797405532, \"daily_log_sharpe\": -0.8688154297687901, \"daily_returns\": 0.03121932179500002, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": -0.18814995501975806, \"return\": -0.2782045110374972, \"returns_over_hodl\": -0.05743186807173517, \"returns_over_uniform_hodl\": 0.19950425937247718, \"sharpe\": -0.39645130884026253, \"sterling\": -1.4420571003699216, \"ulcer\": -0.17318190903138467}], \"train_objective\": [{\"annualised_returns\": -0.4053587353941688, \"annualised_returns_over_hodl\": -0.25114555504249036, \"annualised_returns_over_uniform_hodl\": -0.25114555504249025, \"calmar\": -0.5471761231961639, \"daily_log_sharpe\": -0.40439354394311444, \"daily_returns\": 0.007982638009669961, \"fee_revenue_over_value\": 0.0038905043703225295, \"jax_sharpe\": 0.11342171348100114, \"return\": -0.2706341766442101, \"returns_over_hodl\": -0.161034730509659, \"returns_over_uniform_hodl\": -0.16103473050965889, \"sharpe\": 0.1366418442572374, \"sterling\": -1.2596332910247592, \"ulcer\": -0.17378656862234854}], \"train_return\": -0.2706341766442101, \"train_returns_over_hodl\": -0.161034730509659, \"train_sharpe\": 0.11342171348100114, \"validation_return\": -0.2963252572328401, \"validation_returns_over_hodl\": -0.06309752342311759, \"validation_sharpe\": -2.0141414215683464}, {\"centeredness_margin\": 0.5308817563132542, \"continuous_test_metrics\": [{\"annualised_returns\": -0.7645551415140773, \"annualised_returns_over_hodl\": -0.2605951370483244, \"annualised_returns_over_uniform_hodl\": -0.16898400000446523, \"calmar\": -1.2618999808595934, \"daily_log_sharpe\": -1.8196215896696855, \"daily_returns\": 0.020473401525487135, \"fee_revenue_over_value\": 0.012342937217625008, \"jax_sharpe\": -1.2233557846303569, \"return\": -0.4414834740826913, \"returns_over_hodl\": -0.11448903373852148, \"returns_over_uniform_hodl\": -0.07183826719297537, \"sharpe\": -1.4441069978519478, \"sterling\": -2.242103398098311, \"ulcer\": -0.15056261974864255}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -0.04170434228454488, \"optuna_trial_number\": 42, \"price_ratio\": 2.5862980371039144, \"shift_exponent\": 1.184816573804723, \"step\": 42, \"test_objective\": [{\"annualised_returns\": -0.7645551415140773, \"annualised_returns_over_hodl\": -0.2605951370483244, \"annualised_returns_over_uniform_hodl\": -0.16898400000446523, \"calmar\": -1.2618999808595934, \"daily_log_sharpe\": -1.8196215896696855, \"daily_returns\": 0.020473401525487135, \"fee_revenue_over_value\": 0.012342937217625008, \"jax_sharpe\": -1.2233557846303569, \"return\": -0.4414834740826913, \"returns_over_hodl\": -0.11448903373852148, \"returns_over_uniform_hodl\": -0.07183826719297537, \"sharpe\": -1.4441069978519478, \"sterling\": -2.242103398098311, \"ulcer\": -0.15056261974864255}], \"train_objective\": [{\"annualised_returns\": -0.4731944231669998, \"annualised_returns_over_hodl\": -0.3365736262832363, \"annualised_returns_over_uniform_hodl\": -0.3365736262832363, \"calmar\": -0.6210935357768553, \"daily_log_sharpe\": -0.5864636944001299, \"daily_returns\": 0.008304462756480164, \"fee_revenue_over_value\": 0.0067534379905366, \"jax_sharpe\": -0.16064595926001832, \"return\": -0.3223460356515653, \"returns_over_hodl\": -0.22051716352021822, \"returns_over_uniform_hodl\": -0.22051716352021822, \"sharpe\": -0.09513349529267873, \"sterling\": -1.5009937173100165, \"ulcer\": -0.17196201466054314}], \"train_return\": -0.3223460356515653, \"train_returns_over_hodl\": -0.22051716352021822, \"train_sharpe\": -0.16064595926001832, \"validation_return\": -0.19297063711600648, \"validation_returns_over_hodl\": -0.04170434228454489, \"validation_sharpe\": -1.558620974441821}, {\"centeredness_margin\": 0.28786343470856557, \"continuous_test_metrics\": [{\"annualised_returns\": -0.4793211490043382, \"annualised_returns_over_hodl\": 0.010058911668626935, \"annualised_returns_over_uniform_hodl\": 0.8377655762763532, \"calmar\": -0.8268599677443927, \"daily_log_sharpe\": -0.6859475952457124, \"daily_returns\": 0.031016042754373332, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": -0.001859940107171791, \"return\": -0.2311316325611964, \"returns_over_hodl\": 0.0040389998146070916, \"returns_over_uniform_hodl\": 0.27773156765672047, \"sharpe\": -0.19658869908649515, \"sterling\": -1.2340344824525364, \"ulcer\": -0.17641031651983363}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -0.004793623620341747, \"optuna_trial_number\": 43, \"price_ratio\": 6.2744715872055705, \"shift_exponent\": 3.8790658431294e-05, \"step\": 43, \"test_objective\": [{\"annualised_returns\": -0.4793211490043382, \"annualised_returns_over_hodl\": 0.010058911668626935, \"annualised_returns_over_uniform_hodl\": 0.8377655762763532, \"calmar\": -0.8268599677443927, \"daily_log_sharpe\": -0.6859475952457124, \"daily_returns\": 0.031016042754373332, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": -0.001859940107171791, \"return\": -0.2311316325611964, \"returns_over_hodl\": 0.0040389998146070916, \"returns_over_uniform_hodl\": 0.27773156765672047, \"sharpe\": -0.19658869908649515, \"sterling\": -1.2340344824525364, \"ulcer\": -0.17641031651983363}], \"train_objective\": [{\"annualised_returns\": -0.4720966451753449, \"annualised_returns_over_hodl\": -0.33519115255065357, \"annualised_returns_over_uniform_hodl\": -0.33519115255065357, \"calmar\": -0.6307090375902261, \"daily_log_sharpe\": -0.4646422701539006, \"daily_returns\": 0.00806337891514582, \"fee_revenue_over_value\": 0.002224053853501355, \"jax_sharpe\": 0.10553275663540575, \"return\": -0.3214890569514324, \"returns_over_hodl\": -0.21953140939919769, \"returns_over_uniform_hodl\": -0.21953140939919769, \"sharpe\": 0.1185172137975726, \"sterling\": -1.404444594419379, \"ulcer\": -0.1822418430929154}], \"train_return\": -0.3214890569514324, \"train_returns_over_hodl\": -0.21953140939919769, \"train_sharpe\": 0.10553275663540575, \"validation_return\": -0.3390412453118492, \"validation_returns_over_hodl\": -0.004793623620341747, \"validation_sharpe\": -2.2429628885646498}, {\"centeredness_margin\": 0.5523703445755939, \"continuous_test_metrics\": [{\"annualised_returns\": -0.8041256500810712, \"annualised_returns_over_hodl\": -0.3741263599581397, \"annualised_returns_over_uniform_hodl\": -0.3086503573783228, \"calmar\": -1.2860915118361185, \"daily_log_sharpe\": -2.0209125036683933, \"daily_returns\": 0.020181689181914146, \"fee_revenue_over_value\": 0.016178360103754993, \"jax_sharpe\": -1.4092616655022254, \"return\": -0.4813760281885324, \"returns_over_hodl\": -0.1719863797210156, \"returns_over_uniform_hodl\": -0.13813306855836494, \"sharpe\": -1.6536359706802737, \"sterling\": -2.326984933861274, \"ulcer\": -0.15561479281776192}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -0.08465796502729268, \"optuna_trial_number\": 44, \"price_ratio\": 1.755212932045988, \"shift_exponent\": 2.291903198089958, \"step\": 44, \"test_objective\": [{\"annualised_returns\": -0.8041256500810712, \"annualised_returns_over_hodl\": -0.3741263599581397, \"annualised_returns_over_uniform_hodl\": -0.3086503573783228, \"calmar\": -1.2860915118361185, \"daily_log_sharpe\": -2.0209125036683933, \"daily_returns\": 0.020181689181914146, \"fee_revenue_over_value\": 0.016178360103754993, \"jax_sharpe\": -1.4092616655022254, \"return\": -0.4813760281885324, \"returns_over_hodl\": -0.1719863797210156, \"returns_over_uniform_hodl\": -0.13813306855836494, \"sharpe\": -1.6536359706802737, \"sterling\": -2.326984933861274, \"ulcer\": -0.15561479281776192}], \"train_objective\": [{\"annualised_returns\": -0.6528028872782257, \"annualised_returns_over_hodl\": -0.5627614216943719, \"annualised_returns_over_uniform_hodl\": -0.5627614216943723, \"calmar\": -0.8145158822283249, \"daily_log_sharpe\": -1.0264376578327914, \"daily_returns\": 0.008675686021282453, \"fee_revenue_over_value\": 0.006910871171682203, \"jax_sharpe\": -0.4724990941890383, \"return\": -0.47389318383351675, \"returns_over_hodl\": -0.3948368121020278, \"returns_over_uniform_hodl\": -0.39483681210202803, \"sharpe\": -0.5478823465346032, \"sterling\": -1.9919767996017075, \"ulcer\": -0.1777254170956409}], \"train_return\": -0.47389318383351675, \"train_returns_over_hodl\": -0.3948368121020278, \"train_sharpe\": -0.4724990941890383, \"validation_return\": -0.21409731168314428, \"validation_returns_over_hodl\": -0.04809943986807563, \"validation_sharpe\": -1.8023747471525389}, {\"centeredness_margin\": 0.1861987526147096, \"continuous_test_metrics\": [{\"annualised_returns\": -0.5013940783723937, \"annualised_returns_over_hodl\": -0.03276010999394885, \"annualised_returns_over_uniform_hodl\": 0.7598579184511463, \"calmar\": -0.8649372667128759, \"daily_log_sharpe\": -0.7368113114043103, \"daily_returns\": 0.031016042751953365, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": -0.05453571187825114, \"return\": -0.24442864271721654, \"returns_over_hodl\": -0.013325112605655942, \"returns_over_uniform_hodl\": 0.2556341445459287, \"sharpe\": -0.25254015369414184, \"sterling\": -1.2908623635394147, \"ulcer\": -0.17641031652062839}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -0.053695012410837406, \"optuna_trial_number\": 45, \"price_ratio\": 8.70981140290171, \"shift_exponent\": 1.2641243184470184e-05, \"step\": 45, \"test_objective\": [{\"annualised_returns\": -0.5013940783723937, \"annualised_returns_over_hodl\": -0.03276010999394885, \"annualised_returns_over_uniform_hodl\": 0.7598579184511463, \"calmar\": -0.8649372667128759, \"daily_log_sharpe\": -0.7368113114043103, \"daily_returns\": 0.031016042751953365, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": -0.05453571187825114, \"return\": -0.24442864271721654, \"returns_over_hodl\": -0.013325112605655942, \"returns_over_uniform_hodl\": 0.2556341445459287, \"sharpe\": -0.25254015369414184, \"sterling\": -1.2908623635394147, \"ulcer\": -0.17641031652062839}], \"train_objective\": [{\"annualised_returns\": -0.43900948326646405, \"annualised_returns_over_hodl\": -0.29352322645588724, \"annualised_returns_over_uniform_hodl\": -0.29352322645588724, \"calmar\": -0.5896901543815585, \"daily_log_sharpe\": -0.4338469654951087, \"daily_returns\": 0.008028033984308467, \"fee_revenue_over_value\": 0.004153517654298923, \"jax_sharpe\": 0.15151985974234206, \"return\": -0.29597913990045555, \"returns_over_hodl\": -0.19018819952012855, \"returns_over_uniform_hodl\": -0.19018819952012855, \"sharpe\": 0.1287174863689096, \"sterling\": -1.3336044179507591, \"ulcer\": -0.17806981560164203}], \"train_return\": -0.29597913990045555, \"train_returns_over_hodl\": -0.19018819952012855, \"train_sharpe\": 0.15151985974234206, \"validation_return\": -0.3311504027720088, \"validation_returns_over_hodl\": -0.053695012410837406, \"validation_sharpe\": -2.1737188099560685}, {\"centeredness_margin\": 0.33163603504613043, \"continuous_test_metrics\": [{\"annualised_returns\": -0.4775119782382655, \"annualised_returns_over_hodl\": 0.013568501242740671, \"annualised_returns_over_uniform_hodl\": 0.8441511472461287, \"calmar\": -0.8237390259126045, \"daily_log_sharpe\": -0.6795308454258652, \"daily_returns\": 0.031016042753821506, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": 0.009563565651849952, \"return\": -0.23005681687640356, \"returns_over_hodl\": 0.0054425649993314895, \"returns_over_uniform_hodl\": 0.27951773286786996, \"sharpe\": -0.18873190789807026, \"sterling\": -1.2293766806731794, \"ulcer\": -0.17641031651869438}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -6.349831771501613e-11, \"optuna_trial_number\": 46, \"price_ratio\": 5.177816853573624, \"shift_exponent\": 6.875429162436636e-05, \"step\": 46, \"test_objective\": [{\"annualised_returns\": -0.4775119782382655, \"annualised_returns_over_hodl\": 0.013568501242740671, \"annualised_returns_over_uniform_hodl\": 0.8441511472461287, \"calmar\": -0.8237390259126045, \"daily_log_sharpe\": -0.6795308454258652, \"daily_returns\": 0.031016042753821506, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": 0.009563565651849952, \"return\": -0.23005681687640356, \"returns_over_hodl\": 0.0054425649993314895, \"returns_over_uniform_hodl\": 0.27951773286786996, \"sharpe\": -0.18873190789807026, \"sterling\": -1.2293766806731794, \"ulcer\": -0.17641031651869438}], \"train_objective\": [{\"annualised_returns\": -0.4902466028408964, \"annualised_returns_over_hodl\": -0.35804808711379477, \"annualised_returns_over_uniform_hodl\": -0.35804808711379477, \"calmar\": -0.6530532648874636, \"daily_log_sharpe\": -0.4805188105240391, \"daily_returns\": 0.008140573122880168, \"fee_revenue_over_value\": 0.0019425590237890978, \"jax_sharpe\": 0.11164356464019426, \"return\": -0.3357492154533167, \"returns_over_hodl\": -0.23593439585318798, \"returns_over_uniform_hodl\": -0.23593439585318798, \"sharpe\": 0.11754222662069387, \"sterling\": -1.4367947234745322, \"ulcer\": -0.18524565814897276}], \"train_return\": -0.3357492154533167, \"train_returns_over_hodl\": -0.23593439585318798, \"train_sharpe\": 0.11164356464019425, \"validation_return\": -0.3391427223072335, \"validation_returns_over_hodl\": -6.349831771501613e-11, \"validation_sharpe\": -2.2438532050179356}, {\"centeredness_margin\": 0.8439409244886265, \"continuous_test_metrics\": [{\"annualised_returns\": -0.7434009236943253, \"annualised_returns_over_hodl\": -0.1445968544386368, \"annualised_returns_over_uniform_hodl\": -0.09431898676674433, \"calmar\": -1.2397542592903934, \"daily_log_sharpe\": -1.73660437554134, \"daily_returns\": 0.01919244023903746, \"fee_revenue_over_value\": 0.004303270426170924, \"jax_sharpe\": -1.205192400544677, \"return\": -0.42179122247821865, \"returns_over_hodl\": -0.060963155205327135, \"returns_over_uniform_hodl\": -0.03911301462849626, \"sharpe\": -1.3567566048544284, \"sterling\": -2.2590057029512582, \"ulcer\": -0.14748444725967014}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -0.03145947144238384, \"optuna_trial_number\": 47, \"price_ratio\": 8.862317174577761, \"shift_exponent\": 0.00813650017424945, \"step\": 47, \"test_objective\": [{\"annualised_returns\": -0.7434009236943253, \"annualised_returns_over_hodl\": -0.1445968544386368, \"annualised_returns_over_uniform_hodl\": -0.09431898676674433, \"calmar\": -1.2397542592903934, \"daily_log_sharpe\": -1.73660437554134, \"daily_returns\": 0.01919244023903746, \"fee_revenue_over_value\": 0.004303270426170924, \"jax_sharpe\": -1.205192400544677, \"return\": -0.42179122247821865, \"returns_over_hodl\": -0.060963155205327135, \"returns_over_uniform_hodl\": -0.03911301462849626, \"sharpe\": -1.3567566048544284, \"sterling\": -2.2590057029512582, \"ulcer\": -0.14748444725967014}], \"train_objective\": [{\"annualised_returns\": -0.34164313905958754, \"annualised_returns_over_hodl\": -0.17090607223448595, \"annualised_returns_over_uniform_hodl\": -0.17090607223448595, \"calmar\": -0.46336450920247085, \"daily_log_sharpe\": -0.3564836196144037, \"daily_returns\": 0.00805352606640159, \"fee_revenue_over_value\": 0.0014433531469192876, \"jax_sharpe\": 0.10871141594010414, \"return\": -0.22413883719275118, \"returns_over_hodl\": -0.1075526865972315, \"returns_over_uniform_hodl\": -0.1075526865972315, \"sharpe\": 0.14414804145433052, \"sterling\": -1.1101366833430215, \"ulcer\": -0.16650780570836574}], \"train_return\": -0.22413883719275118, \"train_returns_over_hodl\": -0.1075526865972315, \"train_sharpe\": 0.10871141594010413, \"validation_return\": -0.1676089803856926, \"validation_returns_over_hodl\": -0.03145947144238381, \"validation_sharpe\": -1.339125912131443}, {\"centeredness_margin\": 0.5540412818097149, \"continuous_test_metrics\": [{\"annualised_returns\": -0.5196317006095282, \"annualised_returns_over_hodl\": -0.06813906350947263, \"annualised_returns_over_uniform_hodl\": 0.695487194968818, \"calmar\": -0.896745879719903, \"daily_log_sharpe\": -0.7795362320734884, \"daily_returns\": 0.031016042751926702, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": -0.09844701337081127, \"return\": -0.2556829925918952, \"returns_over_hodl\": -0.028021784583076026, \"returns_over_uniform_hodl\": 0.23693128366971372, \"sharpe\": -0.29863066689511164, \"sterling\": -1.3387373812528918, \"ulcer\": -0.17614727592662013}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -0.056482602160408346, \"optuna_trial_number\": 48, \"price_ratio\": 6.862150117351989, \"shift_exponent\": 0.0006435680263680905, \"step\": 48, \"test_objective\": [{\"annualised_returns\": -0.5196317006095282, \"annualised_returns_over_hodl\": -0.06813906350947263, \"annualised_returns_over_uniform_hodl\": 0.695487194968818, \"calmar\": -0.896745879719903, \"daily_log_sharpe\": -0.7795362320734884, \"daily_returns\": 0.031016042751926702, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": -0.09844701337081127, \"return\": -0.2556829925918952, \"returns_over_hodl\": -0.028021784583076026, \"returns_over_uniform_hodl\": 0.23693128366971372, \"sharpe\": -0.29863066689511164, \"sterling\": -1.3387373812528918, \"ulcer\": -0.17614727592662013}], \"train_objective\": [{\"annualised_returns\": -0.44363495038906275, \"annualised_returns_over_hodl\": -0.299348253780654, \"annualised_returns_over_uniform_hodl\": -0.299348253780654, \"calmar\": -0.5938337759741334, \"daily_log_sharpe\": -0.43812036893265505, \"daily_returns\": 0.008080211445280872, \"fee_revenue_over_value\": 0.0017882342496308263, \"jax_sharpe\": 0.11468910871920845, \"return\": -0.2995090747207265, \"returns_over_hodl\": -0.194248566242751, \"returns_over_uniform_hodl\": -0.194248566242751, \"sharpe\": 0.12952265838813926, \"sterling\": -1.3415914438025596, \"ulcer\": -0.1788177655679343}], \"train_return\": -0.2995090747207265, \"train_returns_over_hodl\": -0.194248566242751, \"train_sharpe\": 0.11468910871920843, \"validation_return\": -0.3304370661385114, \"validation_returns_over_hodl\": -0.056482602160408346, \"validation_sharpe\": -2.16806024713001}, {\"centeredness_margin\": 0.39547573326269786, \"continuous_test_metrics\": [{\"annualised_returns\": -0.5204254544626832, \"annualised_returns_over_hodl\": -0.06967885748777891, \"annualised_returns_over_uniform_hodl\": 0.6926855956632674, \"calmar\": -0.8982132362473291, \"daily_log_sharpe\": -0.7811141526028851, \"daily_returns\": 0.031016042752429997, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": -0.09935276048750553, \"return\": -0.2561785640756967, \"returns_over_hodl\": -0.028668934500972743, \"returns_over_uniform_hodl\": 0.23610772614582598, \"sharpe\": -0.3002408772271641, \"sterling\": -1.3411327288290336, \"ulcer\": -0.17604774514237723}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -0.06067130350811967, \"optuna_trial_number\": 49, \"price_ratio\": 8.080129934480066, \"shift_exponent\": 0.00043403864321258544, \"step\": 49, \"test_objective\": [{\"annualised_returns\": -0.5204254544626832, \"annualised_returns_over_hodl\": -0.06967885748777891, \"annualised_returns_over_uniform_hodl\": 0.6926855956632674, \"calmar\": -0.8982132362473291, \"daily_log_sharpe\": -0.7811141526028851, \"daily_returns\": 0.031016042752429997, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": -0.09935276048750553, \"return\": -0.2561785640756967, \"returns_over_hodl\": -0.028668934500972743, \"returns_over_uniform_hodl\": 0.23610772614582598, \"sharpe\": -0.3002408772271641, \"sterling\": -1.3411327288290336, \"ulcer\": -0.17604774514237723}], \"train_objective\": [{\"annualised_returns\": -0.43742281481365064, \"annualised_returns_over_hodl\": -0.2915250743021668, \"annualised_returns_over_uniform_hodl\": -0.2915250743021668, \"calmar\": -0.586631999414013, \"daily_log_sharpe\": -0.43360725452970444, \"daily_returns\": 0.00805985485481126, \"fee_revenue_over_value\": 0.0020053279980329006, \"jax_sharpe\": 0.1544923222236314, \"return\": -0.2947709069307697, \"returns_over_hodl\": -0.18879840928516012, \"returns_over_uniform_hodl\": -0.18879840928516012, \"sharpe\": 0.12821393763563713, \"sterling\": -1.3301105089105445, \"ulcer\": -0.1778317729908861}], \"train_return\": -0.2947709069307697, \"train_returns_over_hodl\": -0.18879840928516012, \"train_sharpe\": 0.1544923222236314, \"validation_return\": -0.3267633424912545, \"validation_returns_over_hodl\": -0.06067130350811967, \"validation_sharpe\": -2.1422124111502314}, {\"centeredness_margin\": 0.37287916363754214, \"continuous_test_metrics\": [{\"annualised_returns\": -0.4794296317929665, \"annualised_returns_over_hodl\": 0.009848467174651931, \"annualised_returns_over_uniform_hodl\": 0.8373826801126658, \"calmar\": -0.8270471078861241, \"daily_log_sharpe\": -0.687277648212889, \"daily_returns\": 0.031016042753902212, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": -0.005856626868658898, \"return\": -0.2311961522610988, \"returns_over_hodl\": 0.003954745744947141, \"returns_over_uniform_hodl\": 0.2776243466280084, \"sharpe\": -0.19841193753032954, \"sterling\": -1.2343137768975123, \"ulcer\": -0.17641031651886765}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -6.20929974104456e-11, \"optuna_trial_number\": 50, \"price_ratio\": 4.6578964204324835, \"shift_exponent\": 0.0005791999797240649, \"step\": 50, \"test_objective\": [{\"annualised_returns\": -0.4794296317929665, \"annualised_returns_over_hodl\": 0.009848467174651931, \"annualised_returns_over_uniform_hodl\": 0.8373826801126658, \"calmar\": -0.8270471078861241, \"daily_log_sharpe\": -0.687277648212889, \"daily_returns\": 0.031016042753902212, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": -0.005856626868658898, \"return\": -0.2311961522610988, \"returns_over_hodl\": 0.003954745744947141, \"returns_over_uniform_hodl\": 0.2776243466280084, \"sharpe\": -0.19841193753032954, \"sterling\": -1.2343137768975123, \"ulcer\": -0.17641031651886765}], \"train_objective\": [{\"annualised_returns\": -0.48742997458209103, \"annualised_returns_over_hodl\": -0.35450099962265447, \"annualised_returns_over_uniform_hodl\": -0.35450099962265413, \"calmar\": -0.6479088763164988, \"daily_log_sharpe\": -0.47434472951728135, \"daily_returns\": 0.00815842235302628, \"fee_revenue_over_value\": 0.0019189156641530357, \"jax_sharpe\": 0.12147007543473484, \"return\": -0.3335233107413006, \"returns_over_hodl\": -0.23337401163065308, \"returns_over_uniform_hodl\": -0.23337401163065286, \"sharpe\": 0.1262805831728856, \"sterling\": -1.4267687711317258, \"ulcer\": -0.18522426317565266}], \"train_return\": -0.3335233107413006, \"train_returns_over_hodl\": -0.23337401163065308, \"train_sharpe\": 0.12147007543473483, \"validation_return\": -0.3391427223061435, \"validation_returns_over_hodl\": -6.20929974104456e-11, \"validation_sharpe\": -2.2438532050012987}, {\"centeredness_margin\": 0.38529111822771817, \"continuous_test_metrics\": [{\"annualised_returns\": -0.5734870987684377, \"annualised_returns_over_hodl\": -0.17261253074436256, \"annualised_returns_over_uniform_hodl\": 0.5054015084773469, \"calmar\": -0.9907275010897824, \"daily_log_sharpe\": -0.9185191869369767, \"daily_returns\": 0.031016042754499085, \"fee_revenue_over_value\": 2.1015145736195801e-07, \"jax_sharpe\": -0.23949907031981268, \"return\": -0.29048809237113304, \"returns_over_hodl\": -0.07347257833175047, \"returns_over_uniform_hodl\": 0.17909098669987666, \"sharpe\": -0.4497404227682348, \"sterling\": -1.4922836888306459, \"ulcer\": -0.17283598192610702}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -0.06587036623619658, \"optuna_trial_number\": 51, \"price_ratio\": 8.009503342033907, \"shift_exponent\": 0.001182651494780569, \"step\": 51, \"test_objective\": [{\"annualised_returns\": -0.5734870987684377, \"annualised_returns_over_hodl\": -0.17261253074436256, \"annualised_returns_over_uniform_hodl\": 0.5054015084773469, \"calmar\": -0.9907275010897824, \"daily_log_sharpe\": -0.9185191869369767, \"daily_returns\": 0.031016042754499085, \"fee_revenue_over_value\": 2.1015145736195801e-07, \"jax_sharpe\": -0.23949907031981268, \"return\": -0.29048809237113304, \"returns_over_hodl\": -0.07347257833175047, \"returns_over_uniform_hodl\": 0.17909098669987666, \"sharpe\": -0.4497404227682348, \"sterling\": -1.4922836888306459, \"ulcer\": -0.17283598192610702}], \"train_objective\": [{\"annualised_returns\": -0.42402335422777326, \"annualised_returns_over_hodl\": -0.27465062206175817, \"annualised_returns_over_uniform_hodl\": -0.27465062206175817, \"calmar\": -0.5686857737867671, \"daily_log_sharpe\": -0.4218685185197922, \"daily_returns\": 0.008055350457247354, \"fee_revenue_over_value\": 0.002263861353086196, \"jax_sharpe\": 0.11740091817823745, \"return\": -0.284620195810958, \"returns_over_hodl\": -0.17712238359620036, \"returns_over_uniform_hodl\": -0.17712238359620036, \"sharpe\": 0.1332757880362894, \"sterling\": -1.298922211681228, \"ulcer\": -0.17630877607976678}], \"train_return\": -0.284620195810958, \"train_returns_over_hodl\": -0.17712238359620036, \"train_sharpe\": 0.11740091817823743, \"validation_return\": -0.30758448489962986, \"validation_returns_over_hodl\": -0.06587036623619658, \"validation_sharpe\": -2.0631608891257214}, {\"centeredness_margin\": 0.4349445061596369, \"continuous_test_metrics\": [{\"annualised_returns\": -0.5198589395739874, \"annualised_returns_over_hodl\": -0.0685798817686537, \"annualised_returns_over_uniform_hodl\": 0.6946851421378395, \"calmar\": -0.8971688100655476, \"daily_log_sharpe\": -0.7798238038500308, \"daily_returns\": 0.03101604275221116, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": -0.0982269645589247, \"return\": -0.25582481674083957, \"returns_over_hodl\": -0.028206987909016767, \"returns_over_uniform_hodl\": 0.23669559548193475, \"sharpe\": -0.2988687666813264, \"sterling\": -1.3394816342771498, \"ulcer\": -0.17610208865939375}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -0.05861219262270656, \"optuna_trial_number\": 52, \"price_ratio\": 7.6112271575407195, \"shift_exponent\": 0.0005036668498561109, \"step\": 52, \"test_objective\": [{\"annualised_returns\": -0.5198589395739874, \"annualised_returns_over_hodl\": -0.0685798817686537, \"annualised_returns_over_uniform_hodl\": 0.6946851421378395, \"calmar\": -0.8971688100655476, \"daily_log_sharpe\": -0.7798238038500308, \"daily_returns\": 0.03101604275221116, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": -0.0982269645589247, \"return\": -0.25582481674083957, \"returns_over_hodl\": -0.028206987909016767, \"returns_over_uniform_hodl\": 0.23669559548193475, \"sharpe\": -0.2988687666813264, \"sterling\": -1.3394816342771498, \"ulcer\": -0.17610208865939375}], \"train_objective\": [{\"annualised_returns\": -0.44050572239333274, \"annualised_returns_over_hodl\": -0.29540749750730455, \"annualised_returns_over_uniform_hodl\": -0.29540749750730455, \"calmar\": -0.5902890187596934, \"daily_log_sharpe\": -0.43657229618945903, \"daily_returns\": 0.0080664655072458, \"fee_revenue_over_value\": 0.001899019879477651, \"jax_sharpe\": 0.11169509725250558, \"return\": -0.29711974237228633, \"returns_over_hodl\": -0.19150019664080264, \"returns_over_uniform_hodl\": -0.19150019664080264, \"sharpe\": 0.12746439186356595, \"sterling\": -1.3366030072561237, \"ulcer\": -0.17823257140863455}], \"train_return\": -0.29711974237228633, \"train_returns_over_hodl\": -0.19150019664080264, \"train_sharpe\": 0.11169509725250558, \"validation_return\": -0.32826713392259754, \"validation_returns_over_hodl\": -0.05861219262270656, \"validation_sharpe\": -2.1517595039382362}, {\"centeredness_margin\": 0.4086899018648005, \"continuous_test_metrics\": [{\"annualised_returns\": -0.5659399666684066, \"annualised_returns_over_hodl\": -0.1579719359939361, \"annualised_returns_over_uniform_hodl\": 0.5320395398598876, \"calmar\": -0.9804331352342927, \"daily_log_sharpe\": -0.8981636791763181, \"daily_returns\": 0.031016042752895167, \"fee_revenue_over_value\": 2.9298640775695647e-05, \"jax_sharpe\": -0.2214124891657016, \"return\": -0.28545827331433915, \"returns_over_hodl\": -0.06690430901489397, \"returns_over_uniform_hodl\": 0.18744971084647588, \"sharpe\": -0.4273603176738434, \"sterling\": -1.468347838144508, \"ulcer\": -0.17363460786235382}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -0.06535353814788827, \"optuna_trial_number\": 53, \"price_ratio\": 5.394579463901365, \"shift_exponent\": 0.0016451325906656027, \"step\": 53, \"test_objective\": [{\"annualised_returns\": -0.5659399666684066, \"annualised_returns_over_hodl\": -0.1579719359939361, \"annualised_returns_over_uniform_hodl\": 0.5320395398598876, \"calmar\": -0.9804331352342927, \"daily_log_sharpe\": -0.8981636791763181, \"daily_returns\": 0.031016042752895167, \"fee_revenue_over_value\": 2.9298640775695647e-05, \"jax_sharpe\": -0.2214124891657016, \"return\": -0.28545827331433915, \"returns_over_hodl\": -0.06690430901489397, \"returns_over_uniform_hodl\": 0.18744971084647588, \"sharpe\": -0.4273603176738434, \"sterling\": -1.468347838144508, \"ulcer\": -0.17363460786235382}], \"train_objective\": [{\"annualised_returns\": -0.43733982186719067, \"annualised_returns_over_hodl\": -0.29142055811643264, \"annualised_returns_over_uniform_hodl\": -0.29142055811643297, \"calmar\": -0.5839498478934458, \"daily_log_sharpe\": -0.4277746445599622, \"daily_returns\": 0.008089810385624214, \"fee_revenue_over_value\": 0.002131777933515054, \"jax_sharpe\": 0.185203694789836, \"return\": -0.29470774546012546, \"returns_over_hodl\": -0.18872575674435876, \"returns_over_uniform_hodl\": -0.18872575674435899, \"sharpe\": 0.14412764799995106, \"sterling\": -1.3197264651804936, \"ulcer\": -0.17890663937602327}], \"train_return\": -0.29470774546012546, \"train_returns_over_hodl\": -0.18872575674435876, \"train_sharpe\": 0.18520369478983598, \"validation_return\": -0.3228173273000806, \"validation_returns_over_hodl\": -0.06535353814788825, \"validation_sharpe\": -2.1264552412691176}, {\"centeredness_margin\": 0.40469633759957324, \"continuous_test_metrics\": [{\"annualised_returns\": -0.49655929444826175, \"annualised_returns_over_hodl\": -0.023381168356669146, \"annualised_returns_over_uniform_hodl\": 0.7769225629004337, \"calmar\": -0.856596936140202, \"daily_log_sharpe\": -0.7259177518901603, \"daily_returns\": 0.031016042755263477, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": -0.04397222158244772, \"return\": -0.24148649415360957, \"returns_over_hodl\": -0.009483061084134325, \"returns_over_uniform_hodl\": 0.2605235069591325, \"sharpe\": -0.24078949721039175, \"sterling\": -1.2784149650255614, \"ulcer\": -0.17641031652167807}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -0.04690929977789926, \"optuna_trial_number\": 54, \"price_ratio\": 7.89027687716456, \"shift_exponent\": 8.832894834181085e-05, \"step\": 54, \"test_objective\": [{\"annualised_returns\": -0.49655929444826175, \"annualised_returns_over_hodl\": -0.023381168356669146, \"annualised_returns_over_uniform_hodl\": 0.7769225629004337, \"calmar\": -0.856596936140202, \"daily_log_sharpe\": -0.7259177518901603, \"daily_returns\": 0.031016042755263477, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": -0.04397222158244772, \"return\": -0.24148649415360957, \"returns_over_hodl\": -0.009483061084134325, \"returns_over_uniform_hodl\": 0.2605235069591325, \"sharpe\": -0.24078949721039175, \"sterling\": -1.2784149650255614, \"ulcer\": -0.17641031652167807}], \"train_objective\": [{\"annualised_returns\": -0.4492619375738627, \"annualised_returns_over_hodl\": -0.3064345335527944, \"annualised_returns_over_uniform_hodl\": -0.3064345335527946, \"calmar\": -0.6020709739676934, \"daily_log_sharpe\": -0.4454705403984549, \"daily_returns\": 0.008080337144162423, \"fee_revenue_over_value\": 0.0019122911435043345, \"jax_sharpe\": 0.10526761183890765, \"return\": -0.30381890511231313, \"returns_over_hodl\": -0.19920602092481832, \"returns_over_uniform_hodl\": -0.19920602092481854, \"sharpe\": 0.12174207012268377, \"sterling\": -1.3579085934270327, \"ulcer\": -0.17913120033581714}], \"train_return\": -0.30381890511231313, \"train_returns_over_hodl\": -0.19920602092481832, \"train_sharpe\": 0.10526761183890765, \"validation_return\": -0.3342370850954405, \"validation_returns_over_hodl\": -0.04690929977789926, \"validation_sharpe\": -2.200342802389848}, {\"centeredness_margin\": 0.3192831580248914, \"continuous_test_metrics\": [{\"annualised_returns\": -0.5044434127381971, \"annualised_returns_over_hodl\": -0.03867547865626897, \"annualised_returns_over_uniform_hodl\": 0.7490951196216691, \"calmar\": -0.8701975757548683, \"daily_log_sharpe\": -0.7459872829933295, \"daily_returns\": 0.03101604275401578, \"fee_revenue_over_value\": 8.022408270276133e-08, \"jax_sharpe\": -0.06804510576021247, \"return\": -0.24629304899631754, \"returns_over_hodl\": -0.015759777236834638, \"returns_over_uniform_hodl\": 0.25253581086668, \"sharpe\": -0.26306164531395976, \"sterling\": -1.298713031136064, \"ulcer\": -0.17641031651909625}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": 8.550154530473364e-05, \"optuna_trial_number\": 55, \"price_ratio\": 4.05509593366626, \"shift_exponent\": 0.0013440567877262095, \"step\": 55, \"test_objective\": [{\"annualised_returns\": -0.5044434127381971, \"annualised_returns_over_hodl\": -0.03867547865626897, \"annualised_returns_over_uniform_hodl\": 0.7490951196216691, \"calmar\": -0.8701975757548683, \"daily_log_sharpe\": -0.7459872829933295, \"daily_returns\": 0.03101604275401578, \"fee_revenue_over_value\": 8.022408270276133e-08, \"jax_sharpe\": -0.06804510576021247, \"return\": -0.24629304899631754, \"returns_over_hodl\": -0.015759777236834638, \"returns_over_uniform_hodl\": 0.25253581086668, \"sharpe\": -0.26306164531395976, \"sterling\": -1.298713031136064, \"ulcer\": -0.17641031651909625}], \"train_objective\": [{\"annualised_returns\": -0.4843220590250029, \"annualised_returns_over_hodl\": -0.3505870829168891, \"annualised_returns_over_uniform_hodl\": -0.3505870829168891, \"calmar\": -0.6416919436778543, \"daily_log_sharpe\": -0.46856551318548756, \"daily_returns\": 0.008164107900025901, \"fee_revenue_over_value\": 0.002117878105750802, \"jax_sharpe\": 0.13372618179729168, \"return\": -0.3310727788408545, \"returns_over_hodl\": -0.2305552462177176, \"returns_over_uniform_hodl\": -0.2305552462177176, \"sharpe\": 0.1352515788731734, \"sterling\": -1.414975826213018, \"ulcer\": -0.185315292276844}], \"train_return\": -0.3310727788408545, \"train_returns_over_hodl\": -0.2305552462177176, \"train_sharpe\": 0.13372618179729168, \"validation_return\": -0.3390862179402876, \"validation_returns_over_hodl\": 8.550154530473364e-05, \"validation_sharpe\": -2.243326021098829}, {\"centeredness_margin\": 0.35717320170972827, \"continuous_test_metrics\": [{\"annualised_returns\": -0.4950567709844469, \"annualised_returns_over_hodl\": -0.0204664403327518, \"annualised_returns_over_uniform_hodl\": 0.782225804801014, \"calmar\": -0.8540049812103507, \"daily_log_sharpe\": -0.7240726856504699, \"daily_returns\": 0.03101604275411684, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": -0.045125347685261316, \"return\": -0.24057559134424777, \"returns_over_hodl\": -0.008293544128116448, \"returns_over_uniform_hodl\": 0.26203727618658257, \"sharpe\": -0.2392223784625486, \"sterling\": -1.2745466416509321, \"ulcer\": -0.17641031651930755}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": 6.299041454393972e-05, \"optuna_trial_number\": 56, \"price_ratio\": 4.359028153549723, \"shift_exponent\": 0.0010359528550598091, \"step\": 56, \"test_objective\": [{\"annualised_returns\": -0.4950567709844469, \"annualised_returns_over_hodl\": -0.0204664403327518, \"annualised_returns_over_uniform_hodl\": 0.782225804801014, \"calmar\": -0.8540049812103507, \"daily_log_sharpe\": -0.7240726856504699, \"daily_returns\": 0.03101604275411684, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": -0.045125347685261316, \"return\": -0.24057559134424777, \"returns_over_hodl\": -0.008293544128116448, \"returns_over_uniform_hodl\": 0.26203727618658257, \"sharpe\": -0.2392223784625486, \"sterling\": -1.2745466416509321, \"ulcer\": -0.17641031651930755}], \"train_objective\": [{\"annualised_returns\": -0.4806940716581496, \"annualised_returns_over_hodl\": -0.3460182199273376, \"annualised_returns_over_uniform_hodl\": -0.3460182199273373, \"calmar\": -0.6382003543393145, \"daily_log_sharpe\": -0.46509395122776315, \"daily_returns\": 0.008152106639651671, \"fee_revenue_over_value\": 0.001953886112785217, \"jax_sharpe\": 0.19578719449975693, \"return\": -0.3282195083391327, \"returns_over_hodl\": -0.22727322397489846, \"returns_over_uniform_hodl\": -0.22727322397489824, \"sharpe\": 0.13521001417743542, \"sterling\": -1.4093578573558292, \"ulcer\": -0.1846890024220188}], \"train_return\": -0.3282195083391327, \"train_returns_over_hodl\": -0.22727322397489846, \"train_sharpe\": 0.19578719449975693, \"validation_return\": -0.3391010945861386, \"validation_returns_over_hodl\": 6.299041454393972e-05, \"validation_sharpe\": -2.24345698324379}, {\"centeredness_margin\": 0.8995186828410029, \"continuous_test_metrics\": [{\"annualised_returns\": -0.759083313159307, \"annualised_returns_over_hodl\": -0.2128862315326876, \"annualised_returns_over_uniform_hodl\": -0.14967087105662957, \"calmar\": -1.2511591320192266, \"daily_log_sharpe\": -1.7916751421939157, \"daily_returns\": 0.01926043738212477, \"fee_revenue_over_value\": 0.0055627158343038614, \"jax_sharpe\": -1.1500642019332454, \"return\": -0.43629172067818756, \"returns_over_hodl\": -0.09190690626998688, \"returns_over_uniform_hodl\": -0.06321043504724477, \"sharpe\": -1.4122360476835893, \"sterling\": -2.1944716620768396, \"ulcer\": -0.14671631795321405}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -0.06888920637896125, \"optuna_trial_number\": 57, \"price_ratio\": 1.503676810486385, \"shift_exponent\": 0.026890377855948683, \"step\": 57, \"test_objective\": [{\"annualised_returns\": -0.759083313159307, \"annualised_returns_over_hodl\": -0.2128862315326876, \"annualised_returns_over_uniform_hodl\": -0.14967087105662957, \"calmar\": -1.2511591320192266, \"daily_log_sharpe\": -1.7916751421939157, \"daily_returns\": 0.01926043738212477, \"fee_revenue_over_value\": 0.0055627158343038614, \"jax_sharpe\": -1.1500642019332454, \"return\": -0.43629172067818756, \"returns_over_hodl\": -0.09190690626998688, \"returns_over_uniform_hodl\": -0.06321043504724477, \"sharpe\": -1.4122360476835893, \"sterling\": -2.1944716620768396, \"ulcer\": -0.14671631795321405}], \"train_objective\": [{\"annualised_returns\": -0.4779249808950211, \"annualised_returns_over_hodl\": -0.3425309982192474, \"annualised_returns_over_uniform_hodl\": -0.3425309982192474, \"calmar\": -0.630031669060285, \"daily_log_sharpe\": -0.6203280004674706, \"daily_returns\": 0.008153267291410634, \"fee_revenue_over_value\": 0.0019829335676284626, \"jax_sharpe\": -0.22299537652538087, \"return\": -0.3260469932415341, \"returns_over_hodl\": -0.22477425205166968, \"returns_over_uniform_hodl\": -0.22477425205166968, \"sharpe\": -0.13962108261228484, \"sterling\": -1.537018009071572, \"ulcer\": -0.17001651937711074}], \"train_return\": -0.3260469932415341, \"train_returns_over_hodl\": -0.22477425205166968, \"train_sharpe\": -0.22299537652538087, \"validation_return\": -0.17628366454833388, \"validation_returns_over_hodl\": -0.06888920637896123, \"validation_sharpe\": -1.4730117581524569}, {\"centeredness_margin\": 0.0757474792121356, \"continuous_test_metrics\": [{\"annualised_returns\": -0.6603283777589957, \"annualised_returns_over_hodl\": -0.05650624816364713, \"annualised_returns_over_uniform_hodl\": 0.19889028217446025, \"calmar\": -1.1258924339817658, \"daily_log_sharpe\": -1.3003906029147934, \"daily_returns\": 0.022919673228264038, \"fee_revenue_over_value\": 0.01024490486075326, \"jax_sharpe\": -0.7015177037395217, \"return\": -0.35264899105201786, \"returns_over_hodl\": -0.023153215426378937, \"returns_over_uniform_hodl\": 0.0757898939744055, \"sharpe\": -0.889195150386779, \"sterling\": -1.889708539564999, \"ulcer\": -0.1561492873911821}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -0.03724700961653474, \"optuna_trial_number\": 58, \"price_ratio\": 111.95930336481973, \"shift_exponent\": 0.015275088550274377, \"step\": 58, \"test_objective\": [{\"annualised_returns\": -0.6603283777589957, \"annualised_returns_over_hodl\": -0.05650624816364713, \"annualised_returns_over_uniform_hodl\": 0.19889028217446025, \"calmar\": -1.1258924339817658, \"daily_log_sharpe\": -1.3003906029147934, \"daily_returns\": 0.022919673228264038, \"fee_revenue_over_value\": 0.01024490486075326, \"jax_sharpe\": -0.7015177037395217, \"return\": -0.35264899105201786, \"returns_over_hodl\": -0.023153215426378937, \"returns_over_uniform_hodl\": 0.0757898939744055, \"sharpe\": -0.889195150386779, \"sterling\": -1.889708539564999, \"ulcer\": -0.1561492873911821}], \"train_objective\": [{\"annualised_returns\": -0.3425526584140025, \"annualised_returns_over_hodl\": -0.1720514646784158, \"annualised_returns_over_uniform_hodl\": -0.17205146467841603, \"calmar\": -0.46681513900672167, \"daily_log_sharpe\": -0.3431354387407267, \"daily_returns\": 0.00795436330982002, \"fee_revenue_over_value\": 0.005892943662845334, \"jax_sharpe\": 0.1358749217930636, \"return\": -0.22478975783123323, \"returns_over_hodl\": -0.10830141897732615, \"returns_over_uniform_hodl\": -0.10830141897732626, \"sharpe\": 0.16596408896241036, \"sterling\": -1.106507651682895, \"ulcer\": -0.16707726643574664}], \"train_return\": -0.22478975783123323, \"train_returns_over_hodl\": -0.10830141897732615, \"train_sharpe\": 0.1358749217930636, \"validation_return\": -0.21217670961150636, \"validation_returns_over_hodl\": -0.03724700961653471, \"validation_sharpe\": -1.6130287158763015}, {\"centeredness_margin\": 0.3638253190776354, \"continuous_test_metrics\": [{\"annualised_returns\": -0.47931762460117544, \"annualised_returns_over_hodl\": 0.01006574879600497, \"annualised_returns_over_uniform_hodl\": 0.8377780158574826, \"calmar\": -0.8268538878996778, \"daily_log_sharpe\": -0.6820548111225396, \"daily_returns\": 0.03101604275005633, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": 0.009659874110622397, \"return\": -0.23112953657132862, \"returns_over_hodl\": 0.004041736967703935, \"returns_over_uniform_hodl\": 0.2777350508438732, \"sharpe\": -0.1911926535908517, \"sterling\": -1.234025408767811, \"ulcer\": -0.17641031651090108}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -9.274780943258065e-11, \"optuna_trial_number\": 59, \"price_ratio\": 2.322505619215881, \"shift_exponent\": 0.0013540052871664936, \"step\": 59, \"test_objective\": [{\"annualised_returns\": -0.47931762460117544, \"annualised_returns_over_hodl\": 0.01006574879600497, \"annualised_returns_over_uniform_hodl\": 0.8377780158574826, \"calmar\": -0.8268538878996778, \"daily_log_sharpe\": -0.6820548111225396, \"daily_returns\": 0.03101604275005633, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": 0.009659874110622397, \"return\": -0.23112953657132862, \"returns_over_hodl\": 0.004041736967703935, \"returns_over_uniform_hodl\": 0.2777350508438732, \"sharpe\": -0.1911926535908517, \"sterling\": -1.234025408767811, \"ulcer\": -0.17641031651090108}], \"train_objective\": [{\"annualised_returns\": -0.5901091334029558, \"annualised_returns_over_hodl\": -0.48380878410423, \"annualised_returns_over_uniform_hodl\": -0.4838087841042302, \"calmar\": -0.7592102299775211, \"daily_log_sharpe\": -0.6083620810072157, \"daily_returns\": 0.008401563870068577, \"fee_revenue_over_value\": 0.0010604417079514506, \"jax_sharpe\": 0.12249687078849181, \"return\": -0.4181077056386463, \"returns_over_hodl\": -0.33066863030805227, \"returns_over_uniform_hodl\": -0.3306686303080524, \"sharpe\": 0.0487646541072964, \"sterling\": -1.61392901532104, \"ulcer\": -0.20184833459645957}], \"train_return\": -0.4181077056386463, \"train_returns_over_hodl\": -0.33066863030805227, \"train_sharpe\": 0.12249687078849181, \"validation_return\": -0.339142722284999, \"validation_returns_over_hodl\": -9.274780943258065e-11, \"validation_sharpe\": -2.243853205057081}, {\"centeredness_margin\": 0.3418482397966044, \"continuous_test_metrics\": [{\"annualised_returns\": -0.7326690171447642, \"annualised_returns_over_hodl\": -0.2339909541255275, \"annualised_returns_over_uniform_hodl\": -0.056440113086963506, \"calmar\": -1.2369770082302027, \"daily_log_sharpe\": -1.6358296172099804, \"daily_returns\": 0.022320484810760606, \"fee_revenue_over_value\": 0.011383770088597922, \"jax_sharpe\": -1.0413555569298072, \"return\": -0.41217090202285755, \"returns_over_hodl\": -0.10179263426029184, \"returns_over_uniform_hodl\": -0.02312563934118228, \"sharpe\": -1.245547349307221, \"sterling\": -2.121090803468597, \"ulcer\": -0.15029503560387503}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -0.036283640779871035, \"optuna_trial_number\": 60, \"price_ratio\": 4.378739857166645, \"shift_exponent\": 0.08620222906449881, \"step\": 60, \"test_objective\": [{\"annualised_returns\": -0.7326690171447642, \"annualised_returns_over_hodl\": -0.2339909541255275, \"annualised_returns_over_uniform_hodl\": -0.056440113086963506, \"calmar\": -1.2369770082302027, \"daily_log_sharpe\": -1.6358296172099804, \"daily_returns\": 0.022320484810760606, \"fee_revenue_over_value\": 0.011383770088597922, \"jax_sharpe\": -1.0413555569298072, \"return\": -0.41217090202285755, \"returns_over_hodl\": -0.10179263426029184, \"returns_over_uniform_hodl\": -0.02312563934118228, \"sharpe\": -1.245547349307221, \"sterling\": -2.121090803468597, \"ulcer\": -0.15029503560387503}], \"train_objective\": [{\"annualised_returns\": -0.3896455286128173, \"annualised_returns_over_hodl\": -0.2313573138908248, \"annualised_returns_over_uniform_hodl\": -0.2313573138908248, \"calmar\": -0.5214366295676265, \"daily_log_sharpe\": -0.41201603473940485, \"daily_returns\": 0.008164753207115578, \"fee_revenue_over_value\": 0.006060012183727838, \"jax_sharpe\": 0.05356728084924055, \"return\": -0.2589929735822051, \"returns_over_hodl\": -0.14764424146923316, \"returns_over_uniform_hodl\": -0.14764424146923316, \"sharpe\": 0.10181164903016679, \"sterling\": -1.2334797570402523, \"ulcer\": -0.17064490316679368}], \"train_return\": -0.2589929735822051, \"train_returns_over_hodl\": -0.14764424146923316, \"train_sharpe\": 0.05356728084924055, \"validation_return\": -0.21919016679867676, \"validation_returns_over_hodl\": -0.03628364077987101, \"validation_sharpe\": -1.7167465572669691}, {\"centeredness_margin\": 0.20572720692874621, \"continuous_test_metrics\": [{\"annualised_returns\": -0.47872809014946893, \"annualised_returns_over_hodl\": 0.011209379996053759, \"annualised_returns_over_uniform_hodl\": 0.8398588111870862, \"calmar\": -0.8258369011194869, \"daily_log_sharpe\": -0.6984838417648203, \"daily_returns\": 0.031016042747616667, \"fee_revenue_over_value\": 0.00022725453180246126, \"jax_sharpe\": -0.04339982655199975, \"return\": -0.23077905504749563, \"returns_over_hodl\": 0.00449941888639982, \"returns_over_uniform_hodl\": 0.27831749294429864, \"sharpe\": -0.21436899671091583, \"sterling\": -1.232507622487512, \"ulcer\": -0.17641031650587127}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -1.0246925530310591e-10, \"optuna_trial_number\": 61, \"price_ratio\": 1.4850530556842167, \"shift_exponent\": 0.0029428873819390164, \"step\": 61, \"test_objective\": [{\"annualised_returns\": -0.47872809014946893, \"annualised_returns_over_hodl\": 0.011209379996053759, \"annualised_returns_over_uniform_hodl\": 0.8398588111870862, \"calmar\": -0.8258369011194869, \"daily_log_sharpe\": -0.6984838417648203, \"daily_returns\": 0.031016042747616667, \"fee_revenue_over_value\": 0.00022725453180246126, \"jax_sharpe\": -0.04339982655199975, \"return\": -0.23077905504749563, \"returns_over_hodl\": 0.00449941888639982, \"returns_over_uniform_hodl\": 0.27831749294429864, \"sharpe\": -0.21436899671091583, \"sterling\": -1.232507622487512, \"ulcer\": -0.17641031650587127}], \"train_objective\": [{\"annualised_returns\": -0.6387070096040066, \"annualised_returns_over_hodl\": -0.5450099448288821, \"annualised_returns_over_uniform_hodl\": -0.5450099448288821, \"calmar\": -0.8072822040766963, \"daily_log_sharpe\": -0.6893064807261029, \"daily_returns\": 0.008805755631038949, \"fee_revenue_over_value\": 0.00031190434460418785, \"jax_sharpe\": 0.08237153719044435, \"return\": -0.4610269264204002, \"returns_over_hodl\": -0.38003718375056106, \"returns_over_uniform_hodl\": -0.38003718375056106, \"sharpe\": -0.015511293121821697, \"sterling\": -1.7186666279020049, \"ulcer\": -0.20680785451529327}], \"train_return\": -0.4610269264204002, \"train_returns_over_hodl\": -0.38003718375056106, \"train_sharpe\": 0.08237153719044435, \"validation_return\": -0.3391427222634773, \"validation_returns_over_hodl\": -1.0246925530310591e-10, \"validation_sharpe\": -2.243853205010497}, {\"centeredness_margin\": 0.2880866258693655, \"continuous_test_metrics\": [{\"annualised_returns\": -0.48510773113267347, \"annualised_returns_over_hodl\": -0.0011664122838880298, \"annualised_returns_over_uniform_hodl\": 0.8173415060084421, \"calmar\": -0.8368422132937795, \"daily_log_sharpe\": -0.7043193927549205, \"daily_returns\": 0.03101604275111743, \"fee_revenue_over_value\": 3.281108726722409e-05, \"jax_sharpe\": -0.03192273834446781, \"return\": -0.23458445406959827, \"returns_over_hodl\": -0.0004699220756417821, \"returns_over_uniform_hodl\": 0.2719935515988283, \"sharpe\": -0.2184373309908868, \"sterling\": -1.2489323318530037, \"ulcer\": -0.17641031651310762}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -7.909972676856114e-11, \"optuna_trial_number\": 62, \"price_ratio\": 2.4303839166496264, \"shift_exponent\": 0.0020162885662809073, \"step\": 62, \"test_objective\": [{\"annualised_returns\": -0.48510773113267347, \"annualised_returns_over_hodl\": -0.0011664122838880298, \"annualised_returns_over_uniform_hodl\": 0.8173415060084421, \"calmar\": -0.8368422132937795, \"daily_log_sharpe\": -0.7043193927549205, \"daily_returns\": 0.03101604275111743, \"fee_revenue_over_value\": 3.281108726722409e-05, \"jax_sharpe\": -0.03192273834446781, \"return\": -0.23458445406959827, \"returns_over_hodl\": -0.0004699220756417821, \"returns_over_uniform_hodl\": 0.2719935515988283, \"sharpe\": -0.2184373309908868, \"sterling\": -1.2489323318530037, \"ulcer\": -0.17641031651310762}], \"train_objective\": [{\"annualised_returns\": -0.5639508514880838, \"annualised_returns_over_hodl\": -0.45086666109601903, \"annualised_returns_over_uniform_hodl\": -0.4508666610960189, \"calmar\": -0.7283286589415581, \"daily_log_sharpe\": -0.570444953272692, \"daily_returns\": 0.008333246423215935, \"fee_revenue_over_value\": 0.0019306290203566056, \"jax_sharpe\": 0.12052810608086427, \"return\": -0.3958368136698456, \"returns_over_hodl\": -0.3050511633469213, \"returns_over_uniform_hodl\": -0.3050511633469212, \"sharpe\": 0.07479324380517252, \"sterling\": -1.5694609997238949, \"ulcer\": -0.19674793117981354}], \"train_return\": -0.3958368136698456, \"train_returns_over_hodl\": -0.3050511633469213, \"train_sharpe\": 0.12052810608086427, \"validation_return\": -0.339142722287714, \"validation_returns_over_hodl\": -7.909972676856114e-11, \"validation_sharpe\": -2.243853205010157}, {\"centeredness_margin\": 0.2778698606606887, \"continuous_test_metrics\": [{\"annualised_returns\": -0.48240436366945005, \"annualised_returns_over_hodl\": 0.004077819096854451, \"annualised_returns_over_uniform_hodl\": 0.8268831950062518, \"calmar\": -0.8321787215101799, \"daily_log_sharpe\": -0.6881623088849147, \"daily_returns\": 0.03101604275244761, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": 0.007329889374398268, \"return\": -0.23296850065810282, \"returns_over_hodl\": 0.0016402963947212967, \"returns_over_uniform_hodl\": 0.2746789978639781, \"sharpe\": -0.19754100009265796, \"sterling\": -1.241972375008425, \"ulcer\": -0.17641031651585967}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -7.831102433186743e-11, \"optuna_trial_number\": 63, \"price_ratio\": 3.676420159653109, \"shift_exponent\": 0.0003499358805782934, \"step\": 63, \"test_objective\": [{\"annualised_returns\": -0.48240436366945005, \"annualised_returns_over_hodl\": 0.004077819096854451, \"annualised_returns_over_uniform_hodl\": 0.8268831950062518, \"calmar\": -0.8321787215101799, \"daily_log_sharpe\": -0.6881623088849147, \"daily_returns\": 0.03101604275244761, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": 0.007329889374398268, \"return\": -0.23296850065810282, \"returns_over_hodl\": 0.0016402963947212967, \"returns_over_uniform_hodl\": 0.2746789978639781, \"sharpe\": -0.19754100009265796, \"sterling\": -1.241972375008425, \"ulcer\": -0.17641031651585967}], \"train_objective\": [{\"annualised_returns\": -0.5309215750336189, \"annualised_returns_over_hodl\": -0.4092716323637765, \"annualised_returns_over_uniform_hodl\": -0.4092716323637766, \"calmar\": -0.6998153854194525, \"daily_log_sharpe\": -0.5269316062079895, \"daily_returns\": 0.00817368901871596, \"fee_revenue_over_value\": 0.0019549378995770673, \"jax_sharpe\": 0.11826355400672404, \"return\": -0.3684524026479067, \"returns_over_hodl\": -0.2735517853432329, \"returns_over_uniform_hodl\": -0.273551785343233, \"sharpe\": 0.09969700198343596, \"sterling\": -1.5067485298366168, \"ulcer\": -0.19230748172220613}], \"train_return\": -0.3684524026479067, \"train_returns_over_hodl\": -0.2735517853432329, \"train_sharpe\": 0.11826355400672406, \"validation_return\": -0.33914272230187914, \"validation_returns_over_hodl\": -7.831102433186743e-11, \"validation_sharpe\": -2.24385320505981}, {\"centeredness_margin\": 0.6682623411970209, \"continuous_test_metrics\": [{\"annualised_returns\": -0.6854866431882228, \"annualised_returns_over_hodl\": -0.08950048626951757, \"annualised_returns_over_uniform_hodl\": 0.11009275549127606, \"calmar\": -1.1548464788430668, \"daily_log_sharpe\": -1.4043089285534818, \"daily_returns\": 0.022002979740993828, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": -0.8202706438906857, \"return\": -0.37240386250152735, \"returns_over_hodl\": -0.03705739495286464, \"returns_over_uniform_hodl\": 0.04296057762455874, \"sharpe\": -0.999193266581542, \"sterling\": -1.9874557987250685, \"ulcer\": -0.15500162700848433}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -0.03611063417238347, \"optuna_trial_number\": 64, \"price_ratio\": 192.123402414167, \"shift_exponent\": 1.1113926777981153e-05, \"step\": 64, \"test_objective\": [{\"annualised_returns\": -0.6854866431882228, \"annualised_returns_over_hodl\": -0.08950048626951757, \"annualised_returns_over_uniform_hodl\": 0.11009275549127606, \"calmar\": -1.1548464788430668, \"daily_log_sharpe\": -1.4043089285534818, \"daily_returns\": 0.022002979740993828, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": -0.8202706438906857, \"return\": -0.37240386250152735, \"returns_over_hodl\": -0.03705739495286464, \"returns_over_uniform_hodl\": 0.04296057762455874, \"sharpe\": -0.999193266581542, \"sterling\": -1.9874557987250685, \"ulcer\": -0.15500162700848433}], \"train_objective\": [{\"annualised_returns\": -0.3458934868938105, \"annualised_returns_over_hodl\": -0.17625869751921674, \"annualised_returns_over_uniform_hodl\": -0.17625869751921674, \"calmar\": -0.47088602034065064, \"daily_log_sharpe\": -0.35228018238875464, \"daily_returns\": 0.007934974535344657, \"fee_revenue_over_value\": 0.0017354692342198938, \"jax_sharpe\": 0.14392694230597397, \"return\": -0.22718374910879713, \"returns_over_hodl\": -0.11105514759062762, \"returns_over_uniform_hodl\": -0.11105514759062762, \"sharpe\": 0.15305975369010494, \"sterling\": -1.1212105465197555, \"ulcer\": -0.1666614045773914}], \"train_return\": -0.22718374910879713, \"train_returns_over_hodl\": -0.11105514759062762, \"train_sharpe\": 0.14392694230597397, \"validation_return\": -0.20383642304575167, \"validation_returns_over_hodl\": -0.03611063417238347, \"validation_sharpe\": -1.5682925799260035}, {\"centeredness_margin\": 0.24755291328616313, \"continuous_test_metrics\": [{\"annualised_returns\": -0.49206962329445536, \"annualised_returns_over_hodl\": -0.014671706935409312, \"annualised_returns_over_uniform_hodl\": 0.7927691122263538, \"calmar\": -0.8488519488665712, \"daily_log_sharpe\": -0.7158616903857439, \"daily_returns\": 0.031016042754957823, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": -0.03442206947091513, \"return\": -0.23876943391456462, \"returns_over_hodl\": -0.005934944704028333, \"returns_over_uniform_hodl\": 0.2650388099494443, \"sharpe\": -0.22988288718053868, \"sterling\": -1.2668560775121218, \"ulcer\": -0.1764103165210403}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -0.0359493494552231, \"optuna_trial_number\": 65, \"price_ratio\": 7.085113511033656, \"shift_exponent\": 0.000202230752547525, \"step\": 65, \"test_objective\": [{\"annualised_returns\": -0.49206962329445536, \"annualised_returns_over_hodl\": -0.014671706935409312, \"annualised_returns_over_uniform_hodl\": 0.7927691122263538, \"calmar\": -0.8488519488665712, \"daily_log_sharpe\": -0.7158616903857439, \"daily_returns\": 0.031016042754957823, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": -0.03442206947091513, \"return\": -0.23876943391456462, \"returns_over_hodl\": -0.005934944704028333, \"returns_over_uniform_hodl\": 0.2650388099494443, \"sharpe\": -0.22988288718053868, \"sterling\": -1.2668560775121218, \"ulcer\": -0.1764103165210403}], \"train_objective\": [{\"annualised_returns\": -0.4555836225537587, \"annualised_returns_over_hodl\": -0.31439567277839886, \"annualised_returns_over_uniform_hodl\": -0.3143956727783985, \"calmar\": -0.6098219465603085, \"daily_log_sharpe\": -0.44949063253114285, \"daily_returns\": 0.008087161816414121, \"fee_revenue_over_value\": 0.002727071104486984, \"jax_sharpe\": 0.15457488888810203, \"return\": -0.3086815093100893, \"returns_over_hodl\": -0.20479931294725295, \"returns_over_uniform_hodl\": -0.20479931294725273, \"sharpe\": 0.12357955248996774, \"sterling\": -1.3692387145275375, \"ulcer\": -0.1802015507613187}], \"train_return\": -0.3086815093100893, \"train_returns_over_hodl\": -0.20479931294725295, \"train_sharpe\": 0.15457488888810206, \"validation_return\": -0.3364856971332443, \"validation_returns_over_hodl\": -0.0359493494552231, \"validation_sharpe\": -2.220147280606491}, {\"centeredness_margin\": 0.22320198728693422, \"continuous_test_metrics\": [{\"annualised_returns\": -0.5335274997812669, \"annualised_returns_over_hodl\": -0.09509536440143773, \"annualised_returns_over_uniform_hodl\": 0.6464411825874106, \"calmar\": -0.921384649219693, \"daily_log_sharpe\": -0.813399151904189, \"daily_returns\": 0.031016042750995725, \"fee_revenue_over_value\": 7.087789635535335e-05, \"jax_sharpe\": -0.13351906312064626, \"return\": -0.26443048399186164, \"returns_over_hodl\": -0.03944483549774869, \"returns_over_uniform_hodl\": 0.22239440535233101, \"sharpe\": -0.33506326004076614, \"sterling\": -1.375503554959351, \"ulcer\": -0.1758808920559008}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -0.04293283175257445, \"optuna_trial_number\": 66, \"price_ratio\": 5.428794524014474, \"shift_exponent\": 0.001516489449762902, \"step\": 66, \"test_objective\": [{\"annualised_returns\": -0.5335274997812669, \"annualised_returns_over_hodl\": -0.09509536440143773, \"annualised_returns_over_uniform_hodl\": 0.6464411825874106, \"calmar\": -0.921384649219693, \"daily_log_sharpe\": -0.813399151904189, \"daily_returns\": 0.031016042750995725, \"fee_revenue_over_value\": 7.087789635535335e-05, \"jax_sharpe\": -0.13351906312064626, \"return\": -0.26443048399186164, \"returns_over_hodl\": -0.03944483549774869, \"returns_over_uniform_hodl\": 0.22239440535233101, \"sharpe\": -0.33506326004076614, \"sterling\": -1.375503554959351, \"ulcer\": -0.1758808920559008}], \"train_objective\": [{\"annualised_returns\": -0.4588676269606634, \"annualised_returns_over_hodl\": -0.3185313448949322, \"annualised_returns_over_uniform_hodl\": -0.3185313448949322, \"calmar\": -0.6121778792544835, \"daily_log_sharpe\": -0.4496335981401897, \"daily_returns\": 0.008121461328712679, \"fee_revenue_over_value\": 0.002948032857308238, \"jax_sharpe\": 0.12128394609051424, \"return\": -0.311216299467036, \"returns_over_hodl\": -0.20771499783270542, \"returns_over_uniform_hodl\": -0.20771499783270542, \"sharpe\": 0.13179414926550362, \"sterling\": -1.369222229127589, \"ulcer\": -0.1814284554765003}], \"train_return\": -0.311216299467036, \"train_returns_over_hodl\": -0.20771499783270542, \"train_sharpe\": 0.12128394609051425, \"validation_return\": -0.33535219753086287, \"validation_returns_over_hodl\": -0.04293283175257445, \"validation_sharpe\": -2.2102625627593624}, {\"centeredness_margin\": 0.26989063379101946, \"continuous_test_metrics\": [{\"annualised_returns\": -0.5114498905048661, \"annualised_returns_over_hodl\": -0.052267264256172785, \"annualised_returns_over_uniform_hodl\": 0.7243653584149141, \"calmar\": -0.8822842267097013, \"daily_log_sharpe\": -0.7619843665213752, \"daily_returns\": 0.031016042754082113, \"fee_revenue_over_value\": 7.636746816925688e-06, \"jax_sharpe\": -0.08372875365268109, \"return\": -0.2506030278162319, \"returns_over_hodl\": -0.021388031704982602, \"returns_over_uniform_hodl\": 0.24537334167513158, \"sharpe\": -0.28021153131201926, \"sterling\": -1.3167515680544561, \"ulcer\": -0.17641031651924055}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -0.00681747477645489, \"optuna_trial_number\": 67, \"price_ratio\": 4.196982770234914, \"shift_exponent\": 0.0014574734851643307, \"step\": 67, \"test_objective\": [{\"annualised_returns\": -0.5114498905048661, \"annualised_returns_over_hodl\": -0.052267264256172785, \"annualised_returns_over_uniform_hodl\": 0.7243653584149141, \"calmar\": -0.8822842267097013, \"daily_log_sharpe\": -0.7619843665213752, \"daily_returns\": 0.031016042754082113, \"fee_revenue_over_value\": 7.636746816925688e-06, \"jax_sharpe\": -0.08372875365268109, \"return\": -0.2506030278162319, \"returns_over_hodl\": -0.021388031704982602, \"returns_over_uniform_hodl\": 0.24537334167513158, \"sharpe\": -0.28021153131201926, \"sterling\": -1.3167515680544561, \"ulcer\": -0.17641031651924055}], \"train_objective\": [{\"annualised_returns\": -0.4809365644422603, \"annualised_returns_over_hodl\": -0.34632360034753384, \"annualised_returns_over_uniform_hodl\": -0.34632360034753384, \"calmar\": -0.6378955812173069, \"daily_log_sharpe\": -0.46618345052979565, \"daily_returns\": 0.008161604893621902, \"fee_revenue_over_value\": 0.002345467436370166, \"jax_sharpe\": 0.13149158208877926, \"return\": -0.3284099747077609, \"returns_over_hodl\": -0.22749231110945833, \"returns_over_uniform_hodl\": -0.22749231110945833, \"sharpe\": 0.1348120631004892, \"sterling\": -1.4092231547009637, \"ulcer\": -0.1847654283103881}], \"train_return\": -0.3284099747077609, \"train_returns_over_hodl\": -0.22749231110945833, \"train_sharpe\": 0.13149158208877926, \"validation_return\": -0.3389933498957812, \"validation_returns_over_hodl\": -0.00681747477645489, \"validation_sharpe\": -2.2425313621762095}, {\"centeredness_margin\": 0.2790811563625611, \"continuous_test_metrics\": [{\"annualised_returns\": -0.6082860615134649, \"annualised_returns_over_hodl\": -0.2401186381125776, \"annualised_returns_over_uniform_hodl\": 0.3825765930796077, \"calmar\": -1.0486249480123793, \"daily_log_sharpe\": -1.0192022760744173, \"daily_returns\": 0.03101604275313389, \"fee_revenue_over_value\": 0.00029805941239271794, \"jax_sharpe\": -0.3456501249943517, \"return\": -0.31439617597432956, \"returns_over_hodl\": -0.10469332996224057, \"returns_over_uniform_hodl\": 0.13935972132900476, \"sharpe\": -0.5579684175688266, \"sterling\": -1.5928470199810858, \"ulcer\": -0.1711046616587335}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -0.06428657795953163, \"optuna_trial_number\": 68, \"price_ratio\": 5.105964704524136, \"shift_exponent\": 0.002649969856122025, \"step\": 68, \"test_objective\": [{\"annualised_returns\": -0.6082860615134649, \"annualised_returns_over_hodl\": -0.2401186381125776, \"annualised_returns_over_uniform_hodl\": 0.3825765930796077, \"calmar\": -1.0486249480123793, \"daily_log_sharpe\": -1.0192022760744173, \"daily_returns\": 0.03101604275313389, \"fee_revenue_over_value\": 0.00029805941239271794, \"jax_sharpe\": -0.3456501249943517, \"return\": -0.31439617597432956, \"returns_over_hodl\": -0.10469332996224057, \"returns_over_uniform_hodl\": 0.13935972132900476, \"sharpe\": -0.5579684175688266, \"sterling\": -1.5928470199810858, \"ulcer\": -0.1711046616587335}], \"train_objective\": [{\"annualised_returns\": -0.44412113396880204, \"annualised_returns_over_hodl\": -0.2999605233226853, \"annualised_returns_over_uniform_hodl\": -0.2999605233226853, \"calmar\": -0.5922697850222972, \"daily_log_sharpe\": -0.4379091044387426, \"daily_returns\": 0.008130512487174486, \"fee_revenue_over_value\": 0.0028284229712169, \"jax_sharpe\": 0.12586794955848277, \"return\": -0.29988077515260936, \"returns_over_hodl\": -0.19467612089779296, \"returns_over_uniform_hodl\": -0.19467612089779296, \"sharpe\": 0.13563392153626797, \"sterling\": -1.336216194871599, \"ulcer\": -0.17959379614413196}], \"train_return\": -0.29988077515260936, \"train_returns_over_hodl\": -0.19467612089779296, \"train_sharpe\": 0.12586794955848277, \"validation_return\": -0.31896721951446905, \"validation_returns_over_hodl\": -0.06428657795953163, \"validation_sharpe\": -2.119501382345289}, {\"centeredness_margin\": 0.24432814676432707, \"continuous_test_metrics\": [{\"annualised_returns\": -0.48122767551203505, \"annualised_returns_over_hodl\": 0.006360462890352192, \"annualised_returns_over_uniform_hodl\": 0.8310363826872469, \"calmar\": -0.8301488548599929, \"daily_log_sharpe\": -0.6858545135096743, \"daily_returns\": 0.031016042753013548, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": 0.0083288433147925, \"return\": -0.23226670317204834, \"returns_over_hodl\": 0.0025567498068477246, \"returns_over_uniform_hodl\": 0.2758452687628852, \"sharpe\": -0.1951627207733204, \"sterling\": -1.2389429316343399, \"ulcer\": -0.1764103165170297}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -7.284228775716883e-11, \"optuna_trial_number\": 69, \"price_ratio\": 4.225003916177619, \"shift_exponent\": 0.0001997854840779956, \"step\": 69, \"test_objective\": [{\"annualised_returns\": -0.48122767551203505, \"annualised_returns_over_hodl\": 0.006360462890352192, \"annualised_returns_over_uniform_hodl\": 0.8310363826872469, \"calmar\": -0.8301488548599929, \"daily_log_sharpe\": -0.6858545135096743, \"daily_returns\": 0.031016042753013548, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": 0.0083288433147925, \"return\": -0.23226670317204834, \"returns_over_hodl\": 0.0025567498068477246, \"returns_over_uniform_hodl\": 0.2758452687628852, \"sharpe\": -0.1951627207733204, \"sterling\": -1.2389429316343399, \"ulcer\": -0.1764103165170297}], \"train_objective\": [{\"annualised_returns\": -0.514483191771209, \"annualised_returns_over_hodl\": -0.388570148785891, \"annualised_returns_over_uniform_hodl\": -0.38857014878589125, \"calmar\": -0.6815954266635351, \"daily_log_sharpe\": -0.5074609214661513, \"daily_returns\": 0.00816843933938198, \"fee_revenue_over_value\": 0.002102774165286719, \"jax_sharpe\": 0.16700522224561146, \"return\": -0.3551066458011498, \"returns_over_hodl\": -0.2582006047272061, \"returns_over_uniform_hodl\": -0.2582006047272062, \"sharpe\": 0.1081520218072099, \"sterling\": -1.4803652991441958, \"ulcer\": -0.18922331077051108}], \"train_return\": -0.3551066458011498, \"train_returns_over_hodl\": -0.2582006047272061, \"train_sharpe\": 0.16700522224561146, \"validation_return\": -0.3391427223045096, \"validation_returns_over_hodl\": -7.284228775716883e-11, \"validation_sharpe\": -2.243853205046828}, {\"centeredness_margin\": 0.32002672278520733, \"continuous_test_metrics\": [{\"annualised_returns\": -0.48450645313031027, \"annualised_returns_over_hodl\": 8.677503160470224e-13, \"annualised_returns_over_uniform_hodl\": 0.8194637508670577, \"calmar\": -0.8358049680461282, \"daily_log_sharpe\": -0.6924635969208986, \"daily_returns\": 0.031016042750954403, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": 0.005019283996313285, \"return\": -0.23422459930560702, \"returns_over_hodl\": 3.494982081519993e-13, \"returns_over_uniform_hodl\": 0.27259157046811144, \"sharpe\": -0.20210852970575635, \"sterling\": -1.2473843110881446, \"ulcer\": -0.17641031651277153}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -9.538936307507129e-11, \"optuna_trial_number\": 70, \"price_ratio\": 2.9705176198577785, \"shift_exponent\": 1.7201856416465563e-05, \"step\": 70, \"test_objective\": [{\"annualised_returns\": -0.48450645313031027, \"annualised_returns_over_hodl\": 8.677503160470224e-13, \"annualised_returns_over_uniform_hodl\": 0.8194637508670577, \"calmar\": -0.8358049680461282, \"daily_log_sharpe\": -0.6924635969208986, \"daily_returns\": 0.031016042750954403, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": 0.005019283996313285, \"return\": -0.23422459930560702, \"returns_over_hodl\": 3.494982081519993e-13, \"returns_over_uniform_hodl\": 0.27259157046811144, \"sharpe\": -0.20210852970575635, \"sterling\": -1.2473843110881446, \"ulcer\": -0.17641031651277153}], \"train_objective\": [{\"annualised_returns\": -0.568258868469226, \"annualised_returns_over_hodl\": -0.45629191133898583, \"annualised_returns_over_uniform_hodl\": -0.45629191133898583, \"calmar\": -0.7396943391456319, \"daily_log_sharpe\": -0.5736148441051767, \"daily_returns\": 0.00827283771881774, \"fee_revenue_over_value\": 0.0019146765996882705, \"jax_sharpe\": 0.11856901361994301, \"return\": -0.3994677419989344, \"returns_over_hodl\": -0.3092276995466825, \"returns_over_uniform_hodl\": -0.3092276995466825, \"sharpe\": 0.07631983235067082, \"sterling\": -1.5657647793977774, \"ulcer\": -0.2000539114777523}], \"train_return\": -0.3994677419989344, \"train_returns_over_hodl\": -0.3092276995466825, \"train_sharpe\": 0.118569013619943, \"validation_return\": -0.3391427222966864, \"validation_returns_over_hodl\": -9.538936307507129e-11, \"validation_sharpe\": -2.243853205111608}, {\"centeredness_margin\": 0.5394130842643139, \"continuous_test_metrics\": [{\"annualised_returns\": -0.7522318648526465, \"annualised_returns_over_hodl\": -0.2868798765020151, \"annualised_returns_over_uniform_hodl\": -0.12548829513379378, \"calmar\": -1.2658064470262953, \"daily_log_sharpe\": -1.7549479761746507, \"daily_returns\": 0.022251549432366877, \"fee_revenue_over_value\": 0.008944870980983479, \"jax_sharpe\": -1.1707962873080218, \"return\": -0.4298893102064699, \"returns_over_hodl\": -0.12730384572624376, \"returns_over_uniform_hodl\": -0.05257069186719954, \"sharpe\": -1.3720858929899638, \"sterling\": -2.212257741958712, \"ulcer\": -0.14698858713093976}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -0.03804610281503307, \"optuna_trial_number\": 71, \"price_ratio\": 3.495869535738078, \"shift_exponent\": 0.008155006653977961, \"step\": 71, \"test_objective\": [{\"annualised_returns\": -0.7522318648526465, \"annualised_returns_over_hodl\": -0.2868798765020151, \"annualised_returns_over_uniform_hodl\": -0.12548829513379378, \"calmar\": -1.2658064470262953, \"daily_log_sharpe\": -1.7549479761746507, \"daily_returns\": 0.022251549432366877, \"fee_revenue_over_value\": 0.008944870980983479, \"jax_sharpe\": -1.1707962873080218, \"return\": -0.4298893102064699, \"returns_over_hodl\": -0.12730384572624376, \"returns_over_uniform_hodl\": -0.05257069186719954, \"sharpe\": -1.3720858929899638, \"sterling\": -2.212257741958712, \"ulcer\": -0.14698858713093976}], \"train_objective\": [{\"annualised_returns\": -0.3911906571374474, \"annualised_returns_over_hodl\": -0.2333031532271963, \"annualised_returns_over_uniform_hodl\": -0.2333031532271963, \"calmar\": -0.5221002724628052, \"daily_log_sharpe\": -0.40102614945772685, \"daily_returns\": 0.008208030873606697, \"fee_revenue_over_value\": 0.002410027987171509, \"jax_sharpe\": 0.11194123413344541, \"return\": -0.2601324268221078, \"returns_over_hodl\": -0.14895491666660965, \"returns_over_uniform_hodl\": -0.14895491666660965, \"sharpe\": 0.1355399830201724, \"sterling\": -1.219921315818799, \"ulcer\": -0.17308068795189088}], \"train_return\": -0.2601324268221078, \"train_returns_over_hodl\": -0.14895491666660965, \"train_sharpe\": 0.11194123413344541, \"validation_return\": -0.20520336230980496, \"validation_returns_over_hodl\": -0.03804610281503307, \"validation_sharpe\": -1.6256656222592656}, {\"centeredness_margin\": 0.5564944625554049, \"continuous_test_metrics\": [{\"annualised_returns\": -0.7895429362885532, \"annualised_returns_over_hodl\": -0.4585465694990176, \"annualised_returns_over_uniform_hodl\": -0.25717984082991185, \"calmar\": -1.314757859589224, \"daily_log_sharpe\": -1.9315097307134794, \"daily_returns\": 0.02477052002989974, \"fee_revenue_over_value\": 0.007421523745457565, \"jax_sharpe\": -1.3010146188996385, \"return\": -0.4661584855109048, \"returns_over_hodl\": -0.2189209646752247, \"returns_over_uniform_hodl\": -0.11284403927219666, \"sharpe\": -1.5496078547811254, \"sterling\": -2.287556937282492, \"ulcer\": -0.14644882967885445}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -0.04727159477639564, \"optuna_trial_number\": 72, \"price_ratio\": 1.6923187506470063, \"shift_exponent\": 0.008646498356591493, \"step\": 72, \"test_objective\": [{\"annualised_returns\": -0.7895429362885532, \"annualised_returns_over_hodl\": -0.4585465694990176, \"annualised_returns_over_uniform_hodl\": -0.25717984082991185, \"calmar\": -1.314757859589224, \"daily_log_sharpe\": -1.9315097307134794, \"daily_returns\": 0.02477052002989974, \"fee_revenue_over_value\": 0.007421523745457565, \"jax_sharpe\": -1.3010146188996385, \"return\": -0.4661584855109048, \"returns_over_hodl\": -0.2189209646752247, \"returns_over_uniform_hodl\": -0.11284403927219666, \"sharpe\": -1.5496078547811254, \"sterling\": -2.287556937282492, \"ulcer\": -0.14644882967885445}], \"train_objective\": [{\"annualised_returns\": -0.46414890234414885, \"annualised_returns_over_hodl\": -0.32518225659812416, \"annualised_returns_over_uniform_hodl\": -0.3251822565981244, \"calmar\": -0.6073506222357921, \"daily_log_sharpe\": -0.4790211358114158, \"daily_returns\": 0.00865411468066851, \"fee_revenue_over_value\": 0.0011818601031019778, \"jax_sharpe\": 0.16906803317089322, \"return\": -0.31530541425750114, \"returns_over_hodl\": -0.21241857069326986, \"returns_over_uniform_hodl\": -0.21241857069327008, \"sharpe\": 0.10636796263762674, \"sterling\": -1.37071349143224, \"ulcer\": -0.18315627939452797}], \"train_return\": -0.31530541425750114, \"train_returns_over_hodl\": -0.21241857069326986, \"train_sharpe\": 0.16906803317089322, \"validation_return\": -0.22960548502896116, \"validation_returns_over_hodl\": -0.04727159477639564, \"validation_sharpe\": -1.8257556437017255}, {\"centeredness_margin\": 0.5292276674690122, \"continuous_test_metrics\": [{\"annualised_returns\": -0.745550128780455, \"annualised_returns_over_hodl\": -0.13845845221238795, \"annualised_returns_over_uniform_hodl\": -0.10190472818123564, \"calmar\": -1.2350931668675267, \"daily_log_sharpe\": -1.7339262517489586, \"daily_returns\": 0.018881887113518382, \"fee_revenue_over_value\": 0.009472740727828534, \"jax_sharpe\": -1.2356195835068218, \"return\": -0.4237465506289839, \"returns_over_hodl\": -0.058255075689044755, \"returns_over_uniform_hodl\": -0.04236244536224221, \"sharpe\": -1.3545873013924858, \"sterling\": -2.292229610622684, \"ulcer\": -0.1501920099860874}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -0.025724301237498665, \"optuna_trial_number\": 73, \"price_ratio\": 127.53283382704325, \"shift_exponent\": 47.24079897790655, \"step\": 73, \"test_objective\": [{\"annualised_returns\": -0.745550128780455, \"annualised_returns_over_hodl\": -0.13845845221238795, \"annualised_returns_over_uniform_hodl\": -0.10190472818123564, \"calmar\": -1.2350931668675267, \"daily_log_sharpe\": -1.7339262517489586, \"daily_returns\": 0.018881887113518382, \"fee_revenue_over_value\": 0.009472740727828534, \"jax_sharpe\": -1.2356195835068218, \"return\": -0.4237465506289839, \"returns_over_hodl\": -0.058255075689044755, \"returns_over_uniform_hodl\": -0.04236244536224221, \"sharpe\": -1.3545873013924858, \"sterling\": -2.292229610622684, \"ulcer\": -0.1501920099860874}], \"train_objective\": [{\"annualised_returns\": -0.32326551616035415, \"annualised_returns_over_hodl\": -0.14776243015144586, \"annualised_returns_over_uniform_hodl\": -0.14776243015144586, \"calmar\": -0.4405170995262515, \"daily_log_sharpe\": -0.3307977749260944, \"daily_returns\": 0.00792776195569106, \"fee_revenue_over_value\": 0.0058580779289800385, \"jax_sharpe\": 0.12722715554353334, \"return\": -0.21106117726792684, \"returns_over_hodl\": -0.09250988896153656, \"returns_over_uniform_hodl\": -0.09250988896153656, \"sharpe\": 0.16284482457916288, \"sterling\": -1.0614752102208842, \"ulcer\": -0.16457425341596035}], \"train_return\": -0.21106117726792684, \"train_returns_over_hodl\": -0.09250988896153656, \"train_sharpe\": 0.12722715554353334, \"validation_return\": -0.17231450808169024, \"validation_returns_over_hodl\": -0.025724301237498692, \"validation_sharpe\": -1.3686763628621983}, {\"centeredness_margin\": 0.5501101100266763, \"continuous_test_metrics\": [{\"annualised_returns\": -0.5951713537623864, \"annualised_returns_over_hodl\": -0.1227102523292618, \"annualised_returns_over_uniform_hodl\": 0.42886569893011184, \"calmar\": -1.0245248040378647, \"daily_log_sharpe\": -1.0024041413123996, \"daily_returns\": 0.02842714902468562, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": -0.3337215779281045, \"return\": -0.3052424540501101, \"returns_over_hodl\": -0.051359728389384185, \"returns_over_uniform_hodl\": 0.15457168732922866, \"sharpe\": -0.5496826330144782, \"sterling\": -1.5797680423023783, \"ulcer\": -0.16711287620110768}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -0.05386939202401264, \"optuna_trial_number\": 74, \"price_ratio\": 22.69993950583361, \"shift_exponent\": 2.686928478462507e-05, \"step\": 74, \"test_objective\": [{\"annualised_returns\": -0.5951713537623864, \"annualised_returns_over_hodl\": -0.1227102523292618, \"annualised_returns_over_uniform_hodl\": 0.42886569893011184, \"calmar\": -1.0245248040378647, \"daily_log_sharpe\": -1.0024041413123996, \"daily_returns\": 0.02842714902468562, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": -0.3337215779281045, \"return\": -0.3052424540501101, \"returns_over_hodl\": -0.051359728389384185, \"returns_over_uniform_hodl\": 0.15457168732922866, \"sharpe\": -0.5496826330144782, \"sterling\": -1.5797680423023783, \"ulcer\": -0.16711287620110768}], \"train_objective\": [{\"annualised_returns\": -0.39030475460465697, \"annualised_returns_over_hodl\": -0.23218750234831054, \"annualised_returns_over_uniform_hodl\": -0.23218750234831054, \"calmar\": -0.527938860207599, \"daily_log_sharpe\": -0.3928464035613581, \"daily_returns\": 0.008014724181958593, \"fee_revenue_over_value\": 0.0018230033767707976, \"jax_sharpe\": 0.11213540742483799, \"return\": -0.2594789802509708, \"returns_over_hodl\": -0.14820327879011974, \"returns_over_uniform_hodl\": -0.14820327879011974, \"sharpe\": 0.1374903233029087, \"sterling\": -1.2275638293792637, \"ulcer\": -0.17160690389875036}], \"train_return\": -0.2594789802509708, \"train_returns_over_hodl\": -0.14820327879011974, \"train_sharpe\": 0.11213540742483798, \"validation_return\": -0.2681170744251278, \"validation_returns_over_hodl\": -0.05386939202401264, \"validation_sharpe\": -1.8988704800701997}, {\"centeredness_margin\": 0.4377463360236712, \"continuous_test_metrics\": [{\"annualised_returns\": -0.4799634036390896, \"annualised_returns_over_hodl\": 0.008813009329082355, \"annualised_returns_over_uniform_hodl\": 0.8354987020664764, \"calmar\": -0.8279679004012201, \"daily_log_sharpe\": -0.6834153713401115, \"daily_returns\": 0.031016042753498403, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": 0.0086456090755812, \"return\": -0.2315137279299444, \"returns_over_hodl\": 0.003540034507923062, \"returns_over_uniform_hodl\": 0.2770965885950494, \"sharpe\": -0.19268030037048028, \"sterling\": -1.235687999578292, \"ulcer\": -0.17641031651802708}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -6.7495675715179e-11, \"optuna_trial_number\": 75, \"price_ratio\": 4.778433346162176, \"shift_exponent\": 9.230924827022931e-05, \"step\": 75, \"test_objective\": [{\"annualised_returns\": -0.4799634036390896, \"annualised_returns_over_hodl\": 0.008813009329082355, \"annualised_returns_over_uniform_hodl\": 0.8354987020664764, \"calmar\": -0.8279679004012201, \"daily_log_sharpe\": -0.6834153713401115, \"daily_returns\": 0.031016042753498403, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": 0.0086456090755812, \"return\": -0.2315137279299444, \"returns_over_hodl\": 0.003540034507923062, \"returns_over_uniform_hodl\": 0.2770965885950494, \"sharpe\": -0.19268030037048028, \"sterling\": -1.235687999578292, \"ulcer\": -0.17641031651802708}], \"train_objective\": [{\"annualised_returns\": -0.4999704723338889, \"annualised_returns_over_hodl\": -0.37029372717518694, \"annualised_returns_over_uniform_hodl\": -0.37029372717518705, \"calmar\": -0.6646434269577064, \"daily_log_sharpe\": -0.4911756119627469, \"daily_returns\": 0.008136386213118971, \"fee_revenue_over_value\": 0.0018813339445647905, \"jax_sharpe\": 0.11306855270509317, \"return\": -0.34347113857040146, \"returns_over_hodl\": -0.2448166674122001, \"returns_over_uniform_hodl\": -0.2448166674122002, \"sharpe\": 0.11400372746588758, \"sterling\": -1.4545884172113817, \"ulcer\": -0.18677822938635008}], \"train_return\": -0.34347113857040146, \"train_returns_over_hodl\": -0.2448166674122001, \"train_sharpe\": 0.11306855270509315, \"validation_return\": -0.3391427223063137, \"validation_returns_over_hodl\": -6.7495675715179e-11, \"validation_sharpe\": -2.2438532050313746}, {\"centeredness_margin\": 0.4998598025995129, \"continuous_test_metrics\": [{\"annualised_returns\": -0.48208797622729005, \"annualised_returns_over_hodl\": 0.004691575368305667, \"annualised_returns_over_uniform_hodl\": 0.8279999024524178, \"calmar\": -0.8316329317783722, \"daily_log_sharpe\": -0.6926917339568697, \"daily_returns\": 0.031016042754569127, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": -0.008202831690941652, \"return\": -0.232779708048103, \"returns_over_hodl\": 0.001886834195572673, \"returns_over_uniform_hodl\": 0.2749927398356249, \"sharpe\": -0.20426557795322153, \"sterling\": -1.2411578194478428, \"ulcer\": -0.1764103165202358}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -0.017613306788393235, \"optuna_trial_number\": 76, \"price_ratio\": 6.578888449696191, \"shift_exponent\": 5.76802907691236e-05, \"step\": 76, \"test_objective\": [{\"annualised_returns\": -0.48208797622729005, \"annualised_returns_over_hodl\": 0.004691575368305667, \"annualised_returns_over_uniform_hodl\": 0.8279999024524178, \"calmar\": -0.8316329317783722, \"daily_log_sharpe\": -0.6926917339568697, \"daily_returns\": 0.031016042754569127, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": -0.008202831690941652, \"return\": -0.232779708048103, \"returns_over_hodl\": 0.001886834195572673, \"returns_over_uniform_hodl\": 0.2749927398356249, \"sharpe\": -0.20426557795322153, \"sterling\": -1.2411578194478428, \"ulcer\": -0.1764103165202358}], \"train_objective\": [{\"annualised_returns\": -0.4670322049543053, \"annualised_returns_over_hodl\": -0.3288133096452154, \"annualised_returns_over_uniform_hodl\": -0.3288133096452156, \"calmar\": -0.6243701674665906, \"daily_log_sharpe\": -0.4606888159618253, \"daily_returns\": 0.008098313552829995, \"fee_revenue_over_value\": 0.0018534347733166925, \"jax_sharpe\": 0.10469044716158606, \"return\": -0.31754453998681564, \"returns_over_hodl\": -0.21499416261266358, \"returns_over_uniform_hodl\": -0.2149941626126637, \"sharpe\": 0.11871680684511121, \"sterling\": -1.3947392244199805, \"ulcer\": -0.1814683225174661}], \"train_return\": -0.31754453998681564, \"train_returns_over_hodl\": -0.21499416261266358, \"train_sharpe\": 0.10469044716158606, \"validation_return\": -0.33863877799179976, \"validation_returns_over_hodl\": -0.017613306788393235, \"validation_sharpe\": -2.2392764077315443}, {\"centeredness_margin\": 0.6144918312886363, \"continuous_test_metrics\": [{\"annualised_returns\": -0.4845064531843477, \"annualised_returns_over_hodl\": 4.996003610813204e-13, \"annualised_returns_over_uniform_hodl\": 0.8194637506763298, \"calmar\": -0.8358049681326563, \"daily_log_sharpe\": -0.6924635970564516, \"daily_returns\": 0.03101604274839589, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": 0.005019283834174652, \"return\": -0.23422459933793627, \"returns_over_hodl\": 2.0117241206207837e-13, \"returns_over_uniform_hodl\": 0.27259157041438575, \"sharpe\": -0.20210852986468697, \"sterling\": -1.2473843112690366, \"ulcer\": -0.1764103165074938}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -1.164049967528058e-10, \"optuna_trial_number\": 77, \"price_ratio\": 2.1378876500359687, \"shift_exponent\": 1.0852184213896753e-05, \"step\": 77, \"test_objective\": [{\"annualised_returns\": -0.4845064531843477, \"annualised_returns_over_hodl\": 4.996003610813204e-13, \"annualised_returns_over_uniform_hodl\": 0.8194637506763298, \"calmar\": -0.8358049681326563, \"daily_log_sharpe\": -0.6924635970564516, \"daily_returns\": 0.03101604274839589, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": 0.005019283834174652, \"return\": -0.23422459933793627, \"returns_over_hodl\": 2.0117241206207837e-13, \"returns_over_uniform_hodl\": 0.27259157041438575, \"sharpe\": -0.20210852986468697, \"sterling\": -1.2473843112690366, \"ulcer\": -0.1764103165074938}], \"train_objective\": [{\"annualised_returns\": -0.6242488006013052, \"annualised_returns_over_hodl\": -0.5268021702894301, \"annualised_returns_over_uniform_hodl\": -0.52680217028943, \"calmar\": -0.7990521951382744, \"daily_log_sharpe\": -0.6648431506816732, \"daily_returns\": 0.008368423894585935, \"fee_revenue_over_value\": 7.752490977096708e-05, \"jax_sharpe\": 0.0880073347636362, \"return\": -0.44803323703577336, \"returns_over_hodl\": -0.3650909746368808, \"returns_over_uniform_hodl\": -0.36509097463688067, \"sharpe\": 0.003619110192667169, \"sterling\": -1.6881000711605518, \"ulcer\": -0.20617456420505476}], \"train_return\": -0.44803323703577336, \"train_returns_over_hodl\": -0.3650909746368808, \"train_sharpe\": 0.0880073347636362, \"validation_return\": -0.33914272228236064, \"validation_returns_over_hodl\": -1.164049967528058e-10, \"validation_sharpe\": -2.2438532051457205}, {\"centeredness_margin\": 0.6568364847085794, \"continuous_test_metrics\": [{\"annualised_returns\": -0.7700740353292489, \"annualised_returns_over_hodl\": -0.18952357532652642, \"annualised_returns_over_uniform_hodl\": -0.18846324916784474, \"calmar\": -1.25159068404626, \"daily_log_sharpe\": -1.8407838603209876, \"daily_returns\": 0.01810486304721125, \"fee_revenue_over_value\": 0.010075795631649552, \"jax_sharpe\": -1.3502719599905633, \"return\": -0.4467934065917546, \"returns_over_hodl\": -0.0811464408538981, \"returns_over_uniform_hodl\": -0.08066249338862086, \"sharpe\": -1.4641418127286234, \"sterling\": -2.3656659525122423, \"ulcer\": -0.1541289493541716}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -0.03634338853582475, \"optuna_trial_number\": 78, \"price_ratio\": 22.180587200988203, \"shift_exponent\": 88.02228095385601, \"step\": 78, \"test_objective\": [{\"annualised_returns\": -0.7700740353292489, \"annualised_returns_over_hodl\": -0.18952357532652642, \"annualised_returns_over_uniform_hodl\": -0.18846324916784474, \"calmar\": -1.25159068404626, \"daily_log_sharpe\": -1.8407838603209876, \"daily_returns\": 0.01810486304721125, \"fee_revenue_over_value\": 0.010075795631649552, \"jax_sharpe\": -1.3502719599905633, \"return\": -0.4467934065917546, \"returns_over_hodl\": -0.0811464408538981, \"returns_over_uniform_hodl\": -0.08066249338862086, \"sharpe\": -1.4641418127286234, \"sterling\": -2.3656659525122423, \"ulcer\": -0.1541289493541716}], \"train_objective\": [{\"annualised_returns\": -0.3699902880653101, \"annualised_returns_over_hodl\": -0.20660471913023992, \"annualised_returns_over_uniform_hodl\": -0.20660471913023992, \"calmar\": -0.4959654772386713, \"daily_log_sharpe\": -0.411819114994209, \"daily_returns\": 0.007994915892138885, \"fee_revenue_over_value\": 0.006131189225786044, \"jax_sharpe\": 0.07111208873905947, \"return\": -0.2445957548766423, \"returns_over_hodl\": -0.13108359921751667, \"returns_over_uniform_hodl\": -0.13108359921751667, \"sharpe\": 0.07513175491501785, \"sterling\": -1.215472064207281, \"ulcer\": -0.1654402223474147}], \"train_return\": -0.2445957548766423, \"train_returns_over_hodl\": -0.13108359921751667, \"train_sharpe\": 0.07111208873905947, \"validation_return\": -0.16807465477139638, \"validation_returns_over_hodl\": -0.03634338853582475, \"validation_sharpe\": -1.3496943394867764}, {\"centeredness_margin\": 0.6259184555191416, \"continuous_test_metrics\": [{\"annualised_returns\": -0.4845064531472284, \"annualised_returns_over_hodl\": 8.910649995641506e-13, \"annualised_returns_over_uniform_hodl\": 0.8194637508073441, \"calmar\": -0.8358049680732349, \"daily_log_sharpe\": -0.6924635969633752, \"daily_returns\": 0.031016042750150585, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": 0.005019283945455962, \"return\": -0.2342245993157287, \"returns_over_hodl\": 3.588240815588506e-13, \"returns_over_uniform_hodl\": 0.2725915704512909, \"sharpe\": -0.20210852975558416, \"sterling\": -1.2473843111448393, \"ulcer\": -0.17641031651110958}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -1.0373712999722784e-10, \"optuna_trial_number\": 79, \"price_ratio\": 2.7191752407162597, \"shift_exponent\": 3.6066876555247735e-05, \"step\": 79, \"test_objective\": [{\"annualised_returns\": -0.4845064531472284, \"annualised_returns_over_hodl\": 8.910649995641506e-13, \"annualised_returns_over_uniform_hodl\": 0.8194637508073441, \"calmar\": -0.8358049680732349, \"daily_log_sharpe\": -0.6924635969633752, \"daily_returns\": 0.031016042750150585, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": 0.005019283945455962, \"return\": -0.2342245993157287, \"returns_over_hodl\": 3.588240815588506e-13, \"returns_over_uniform_hodl\": 0.2725915704512909, \"sharpe\": -0.20210852975558416, \"sterling\": -1.2473843111448393, \"ulcer\": -0.17641031651110958}], \"train_objective\": [{\"annualised_returns\": -0.5882298407442796, \"annualised_returns_over_hodl\": -0.4814421191171333, \"annualised_returns_over_uniform_hodl\": -0.4814421191171333, \"calmar\": -0.7608945164021698, \"daily_log_sharpe\": -0.6040466704374525, \"daily_returns\": 0.008269012819191386, \"fee_revenue_over_value\": 0.00013254248897832295, \"jax_sharpe\": 0.10574195898773261, \"return\": -0.4164894250618233, \"returns_over_hodl\": -0.3288071759366388, \"returns_over_uniform_hodl\": -0.3288071759366388, \"sharpe\": 0.05359156414941895, \"sterling\": -1.6104866739420813, \"ulcer\": -0.20241762007434608}], \"train_return\": -0.4164894250618233, \"train_returns_over_hodl\": -0.3288071759366388, \"train_sharpe\": 0.10574195898773261, \"validation_return\": -0.3391427222923413, \"validation_returns_over_hodl\": -1.0373712999722784e-10, \"validation_sharpe\": -2.243853205123879}, {\"centeredness_margin\": 0.5322903562398997, \"continuous_test_metrics\": [{\"annualised_returns\": -0.7266214933538651, \"annualised_returns_over_hodl\": -0.12461370170604913, \"annualised_returns_over_uniform_hodl\": -0.03509503440098605, \"calmar\": -1.2218465378736707, \"daily_log_sharpe\": -1.6388269560094026, \"daily_returns\": 0.01999361738283384, \"fee_revenue_over_value\": 0.00984739289161613, \"jax_sharpe\": -1.09608416405313, \"return\": -0.4068511304989214, \"returns_over_hodl\": -0.05218920450058462, \"returns_over_uniform_hodl\": -0.014285062336438337, \"sharpe\": -1.2531702858637528, \"sterling\": -2.187670986295773, \"ulcer\": -0.14876611796625314}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -0.03002466630871889, \"optuna_trial_number\": 80, \"price_ratio\": 18.508009924027277, \"shift_exponent\": 0.017116499900766607, \"step\": 80, \"test_objective\": [{\"annualised_returns\": -0.7266214933538651, \"annualised_returns_over_hodl\": -0.12461370170604913, \"annualised_returns_over_uniform_hodl\": -0.03509503440098605, \"calmar\": -1.2218465378736707, \"daily_log_sharpe\": -1.6388269560094026, \"daily_returns\": 0.01999361738283384, \"fee_revenue_over_value\": 0.00984739289161613, \"jax_sharpe\": -1.09608416405313, \"return\": -0.4068511304989214, \"returns_over_hodl\": -0.05218920450058462, \"returns_over_uniform_hodl\": -0.014285062336438337, \"sharpe\": -1.2531702858637528, \"sterling\": -2.187670986295773, \"ulcer\": -0.14876611796625314}], \"train_objective\": [{\"annualised_returns\": -0.3483478471809204, \"annualised_returns_over_hodl\": -0.1793495671240155, \"annualised_returns_over_uniform_hodl\": -0.1793495671240155, \"calmar\": -0.4718027000059366, \"daily_log_sharpe\": -0.3612855777125035, \"daily_returns\": 0.008008478256062277, \"fee_revenue_over_value\": 0.004504523473655761, \"jax_sharpe\": 0.13468984150795563, \"return\": -0.2289455716868285, \"returns_over_hodl\": -0.11308171355607333, \"returns_over_uniform_hodl\": -0.11308171355607333, \"sharpe\": 0.141187468613353, \"sterling\": -1.127505340444913, \"ulcer\": -0.16686686810265072}], \"train_return\": -0.2289455716868285, \"train_returns_over_hodl\": -0.11308171355607333, \"train_sharpe\": 0.13468984150795563, \"validation_return\": -0.18452797795314324, \"validation_returns_over_hodl\": -0.030024666308718917, \"validation_sharpe\": -1.461379383651342}, {\"centeredness_margin\": 0.42408062472574404, \"continuous_test_metrics\": [{\"annualised_returns\": -0.4837246567349063, \"annualised_returns_over_hodl\": 0.0015165977967970257, \"annualised_returns_over_uniform_hodl\": 0.8222231456463589, \"calmar\": -0.8344563163646168, \"daily_log_sharpe\": -0.6908374082932036, \"daily_returns\": 0.031016042751826206, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": 0.005970332079492517, \"return\": -0.2337570820107343, \"returns_over_hodl\": 0.000610514889502145, \"returns_over_uniform_hodl\": 0.2733685065879756, \"sharpe\": -0.2003621982345156, \"sterling\": -1.245371536544271, \"ulcer\": -0.1764103165145765}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -8.511014115697435e-11, \"optuna_trial_number\": 81, \"price_ratio\": 3.2875025642216023, \"shift_exponent\": 0.0004225466921951089, \"step\": 81, \"test_objective\": [{\"annualised_returns\": -0.4837246567349063, \"annualised_returns_over_hodl\": 0.0015165977967970257, \"annualised_returns_over_uniform_hodl\": 0.8222231456463589, \"calmar\": -0.8344563163646168, \"daily_log_sharpe\": -0.6908374082932036, \"daily_returns\": 0.031016042751826206, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": 0.005970332079492517, \"return\": -0.2337570820107343, \"returns_over_hodl\": 0.000610514889502145, \"returns_over_uniform_hodl\": 0.2733685065879756, \"sharpe\": -0.2003621982345156, \"sterling\": -1.245371536544271, \"ulcer\": -0.1764103165145765}], \"train_objective\": [{\"annualised_returns\": -0.5463519003234438, \"annualised_returns_over_hodl\": -0.42870363005415757, \"annualised_returns_over_uniform_hodl\": -0.42870363005415757, \"calmar\": -0.7156892877990888, \"daily_log_sharpe\": -0.5464530915123696, \"daily_returns\": 0.008223513459472805, \"fee_revenue_over_value\": 0.001788550007401499, \"jax_sharpe\": 0.11822026380913438, \"return\": -0.38114796688217734, \"returns_over_hodl\": -0.28815507100327575, \"returns_over_uniform_hodl\": -0.28815507100327575, \"sharpe\": 0.0893201215956671, \"sterling\": -1.5304570128517645, \"ulcer\": -0.1954307332371844}], \"train_return\": -0.38114796688217734, \"train_returns_over_hodl\": -0.28815507100327575, \"train_sharpe\": 0.11822026380913438, \"validation_return\": -0.3391427222989233, \"validation_returns_over_hodl\": -8.511014115697435e-11, \"validation_sharpe\": -2.243853205073594}, {\"centeredness_margin\": 0.3200600590858079, \"continuous_test_metrics\": [{\"annualised_returns\": -0.49906640765929267, \"annualised_returns_over_hodl\": -0.028244688352645153, \"annualised_returns_over_uniform_hodl\": 0.7680735644318963, \"calmar\": -0.860921876624001, \"daily_log_sharpe\": -0.7315127931870041, \"daily_returns\": 0.031016042751644317, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": -0.04829345426324245, \"return\": -0.24301005028810563, \"returns_over_hodl\": -0.011472621091059398, \"returns_over_uniform_hodl\": 0.2579916096271777, \"sharpe\": -0.24682451403666925, \"sterling\": -1.2848696561647512, \"ulcer\": -0.17641031652023453}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -0.05172095066073257, \"optuna_trial_number\": 82, \"price_ratio\": 8.545751077305132, \"shift_exponent\": 1.0587448592404632e-05, \"step\": 82, \"test_objective\": [{\"annualised_returns\": -0.49906640765929267, \"annualised_returns_over_hodl\": -0.028244688352645153, \"annualised_returns_over_uniform_hodl\": 0.7680735644318963, \"calmar\": -0.860921876624001, \"daily_log_sharpe\": -0.7315127931870041, \"daily_returns\": 0.031016042751644317, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": -0.04829345426324245, \"return\": -0.24301005028810563, \"returns_over_hodl\": -0.011472621091059398, \"returns_over_uniform_hodl\": 0.2579916096271777, \"sharpe\": -0.24682451403666925, \"sterling\": -1.2848696561647512, \"ulcer\": -0.17641031652023453}], \"train_objective\": [{\"annualised_returns\": -0.44355488555248823, \"annualised_returns_over_hodl\": -0.2992474250754795, \"annualised_returns_over_uniform_hodl\": -0.2992474250754794, \"calmar\": -0.5951231207803876, \"daily_log_sharpe\": -0.43980490923471366, \"daily_returns\": 0.008073199054229381, \"fee_revenue_over_value\": 0.002289691321332482, \"jax_sharpe\": 0.14836100489672452, \"return\": -0.2994478752383354, \"returns_over_hodl\": -0.19417817051183972, \"returns_over_uniform_hodl\": -0.1941781705118396, \"sharpe\": 0.12399615066737103, \"sterling\": -1.3453482081869472, \"ulcer\": -0.17843497006681172}], \"train_return\": -0.2994478752383354, \"train_returns_over_hodl\": -0.19417817051183972, \"train_sharpe\": 0.14836100489672455, \"validation_return\": -0.33231385957573667, \"validation_returns_over_hodl\": -0.05172095066073257, \"validation_sharpe\": -2.1833719939919844}, {\"centeredness_margin\": 0.36941088917526343, \"continuous_test_metrics\": [{\"annualised_returns\": -0.4841146913828215, \"annualised_returns_over_hodl\": 0.0007599740717465497, \"annualised_returns_over_uniform_hodl\": 0.8208464962047262, \"calmar\": -0.8351291525234626, \"daily_log_sharpe\": -0.6916441429222757, \"daily_returns\": 0.031016042751816273, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": 0.005530717011826488, \"return\": -0.23399027135741168, \"returns_over_hodl\": 0.00030600086838661333, \"returns_over_uniform_hodl\": 0.2729809846113813, \"sharpe\": -0.20122466901491892, \"sterling\": -1.2463757005546692, \"ulcer\": -0.17641031651455252}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -8.475142809771796e-11, \"optuna_trial_number\": 83, \"price_ratio\": 3.3133772717406846, \"shift_exponent\": 0.000329691270705226, \"step\": 83, \"test_objective\": [{\"annualised_returns\": -0.4841146913828215, \"annualised_returns_over_hodl\": 0.0007599740717465497, \"annualised_returns_over_uniform_hodl\": 0.8208464962047262, \"calmar\": -0.8351291525234626, \"daily_log_sharpe\": -0.6916441429222757, \"daily_returns\": 0.031016042751816273, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": 0.005530717011826488, \"return\": -0.23399027135741168, \"returns_over_hodl\": 0.00030600086838661333, \"returns_over_uniform_hodl\": 0.2729809846113813, \"sharpe\": -0.20122466901491892, \"sterling\": -1.2463757005546692, \"ulcer\": -0.17641031651455252}], \"train_objective\": [{\"annualised_returns\": -0.5472139754722605, \"annualised_returns_over_hodl\": -0.42978927419879465, \"annualised_returns_over_uniform_hodl\": -0.4297892741987944, \"calmar\": -0.7170364874080979, \"daily_log_sharpe\": -0.5474860105531141, \"daily_returns\": 0.008218096252359032, \"fee_revenue_over_value\": 0.001898168466591135, \"jax_sharpe\": 0.11790871136105364, \"return\": -0.38186221748601135, \"returns_over_hodl\": -0.28897664973803505, \"returns_over_uniform_hodl\": -0.28897664973803494, \"sharpe\": 0.08886096206229231, \"sterling\": -1.5314106974593085, \"ulcer\": -0.19568583056107614}], \"train_return\": -0.38186221748601135, \"train_returns_over_hodl\": -0.28897664973803505, \"train_sharpe\": 0.11790871136105364, \"validation_return\": -0.33914272229916576, \"validation_returns_over_hodl\": -8.475142809771796e-11, \"validation_sharpe\": -2.2438532050764035}, {\"centeredness_margin\": 0.38066204617583427, \"continuous_test_metrics\": [{\"annualised_returns\": -0.48089814531765285, \"annualised_returns_over_hodl\": 0.006999714703931126, \"annualised_returns_over_uniform_hodl\": 0.8321994782238158, \"calmar\": -0.8295803929934499, \"daily_log_sharpe\": -0.6852232984475344, \"daily_returns\": 0.031016042753467508, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": 0.008198240893620945, \"return\": -0.23207033601732152, \"returns_over_hodl\": 0.0028131789749885616, \"returns_over_uniform_hodl\": 0.27617159837022887, \"sharpe\": -0.19452351946414315, \"sterling\": -1.2380945394274359, \"ulcer\": -0.1764103165179658}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -6.838973831690964e-11, \"optuna_trial_number\": 84, \"price_ratio\": 4.8052132297812316, \"shift_exponent\": 1.6700153496923494e-05, \"step\": 84, \"test_objective\": [{\"annualised_returns\": -0.48089814531765285, \"annualised_returns_over_hodl\": 0.006999714703931126, \"annualised_returns_over_uniform_hodl\": 0.8321994782238158, \"calmar\": -0.8295803929934499, \"daily_log_sharpe\": -0.6852232984475344, \"daily_returns\": 0.031016042753467508, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": 0.008198240893620945, \"return\": -0.23207033601732152, \"returns_over_hodl\": 0.0028131789749885616, \"returns_over_uniform_hodl\": 0.27617159837022887, \"sharpe\": -0.19452351946414315, \"sterling\": -1.2380945394274359, \"ulcer\": -0.1764103165179658}], \"train_objective\": [{\"annualised_returns\": -0.5012845013929097, \"annualised_returns_over_hodl\": -0.37194853413229134, \"annualised_returns_over_uniform_hodl\": -0.37194853413229134, \"calmar\": -0.6665178625593196, \"daily_log_sharpe\": -0.4929289309527393, \"daily_returns\": 0.008141474711667226, \"fee_revenue_over_value\": 0.0019070263641788212, \"jax_sharpe\": 0.11214810895557663, \"return\": -0.3445191429096064, \"returns_over_hodl\": -0.2460221519779875, \"returns_over_uniform_hodl\": -0.2460221519779875, \"sharpe\": 0.11281491711091311, \"sterling\": -1.457504037257304, \"ulcer\": -0.18698931899859064}], \"train_return\": -0.3445191429096064, \"train_returns_over_hodl\": -0.2460221519779875, \"train_sharpe\": 0.11214810895557661, \"validation_return\": -0.3391427223065777, \"validation_returns_over_hodl\": -6.838973831690964e-11, \"validation_sharpe\": -2.2438532050360434}, {\"centeredness_margin\": 0.3636306051687943, \"continuous_test_metrics\": [{\"annualised_returns\": -0.48450645310913254, \"annualised_returns_over_hodl\": 9.112710586123285e-13, \"annualised_returns_over_uniform_hodl\": 0.8194637509418057, \"calmar\": -0.8358049680122078, \"daily_log_sharpe\": -0.6924635968677358, \"daily_returns\": 0.031016042751958323, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": 0.005019284059925752, \"return\": -0.23422459929293693, \"returns_over_hodl\": 3.6703973194107675e-13, \"returns_over_uniform_hodl\": 0.27259157048916705, \"sharpe\": -0.20210852964341508, \"sterling\": -1.2473843110172107, \"ulcer\": -0.1764103165148468}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -8.659462036320065e-11, \"optuna_trial_number\": 85, \"price_ratio\": 3.52987740983761, \"shift_exponent\": 3.195657235743984e-05, \"step\": 85, \"test_objective\": [{\"annualised_returns\": -0.48450645310913254, \"annualised_returns_over_hodl\": 9.112710586123285e-13, \"annualised_returns_over_uniform_hodl\": 0.8194637509418057, \"calmar\": -0.8358049680122078, \"daily_log_sharpe\": -0.6924635968677358, \"daily_returns\": 0.031016042751958323, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": 0.005019284059925752, \"return\": -0.23422459929293693, \"returns_over_hodl\": 3.6703973194107675e-13, \"returns_over_uniform_hodl\": 0.27259157048916705, \"sharpe\": -0.20210852964341508, \"sterling\": -1.2473843110172107, \"ulcer\": -0.1764103165148468}], \"train_objective\": [{\"annualised_returns\": -0.5440916933776536, \"annualised_returns_over_hodl\": -0.425857265163889, \"annualised_returns_over_uniform_hodl\": -0.4258572651638892, \"calmar\": -0.7146587774273935, \"daily_log_sharpe\": -0.5438956839802529, \"daily_returns\": 0.008157048687420369, \"fee_revenue_over_value\": 0.0019059827553885367, \"jax_sharpe\": 0.11590341558448533, \"return\": -0.3792778567495394, \"returns_over_hodl\": -0.2860039454622042, \"returns_over_uniform_hodl\": -0.2860039454622043, \"sharpe\": 0.0900104472261144, \"sterling\": -1.5263669535019009, \"ulcer\": -0.1951976597204875}], \"train_return\": -0.3792778567495394, \"train_returns_over_hodl\": -0.2860039454622042, \"train_sharpe\": 0.1159034155844853, \"validation_return\": -0.3391427223013619, \"validation_returns_over_hodl\": -8.659462036320065e-11, \"validation_sharpe\": -2.243853205088665}, {\"centeredness_margin\": 0.40294280427140466, \"continuous_test_metrics\": [{\"annualised_returns\": -0.478759888042531, \"annualised_returns_over_hodl\": 0.011147695375597344, \"annualised_returns_over_uniform_hodl\": 0.8397465787175786, \"calmar\": -0.8258917546496879, \"daily_log_sharpe\": -0.6813457152355483, \"daily_returns\": 0.03101604275375587, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": 0.008886449032561873, \"return\": -0.23079795303046569, \"returns_over_hodl\": 0.004474740552982137, \"returns_over_uniform_hodl\": 0.27828608763432827, \"sharpe\": -0.19056009474627486, \"sterling\": -1.2325894876751737, \"ulcer\": -0.1764103165185606}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -6.464251356419481e-11, \"optuna_trial_number\": 86, \"price_ratio\": 5.124002242351744, \"shift_exponent\": 3.16850741029658e-05, \"step\": 86, \"test_objective\": [{\"annualised_returns\": -0.478759888042531, \"annualised_returns_over_hodl\": 0.011147695375597344, \"annualised_returns_over_uniform_hodl\": 0.8397465787175786, \"calmar\": -0.8258917546496879, \"daily_log_sharpe\": -0.6813457152355483, \"daily_returns\": 0.03101604275375587, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": 0.008886449032561873, \"return\": -0.23079795303046569, \"returns_over_hodl\": 0.004474740552982137, \"returns_over_uniform_hodl\": 0.27828608763432827, \"sharpe\": -0.19056009474627486, \"sterling\": -1.2325894876751737, \"ulcer\": -0.1764103165185606}], \"train_objective\": [{\"annualised_returns\": -0.49226385923504823, \"annualised_returns_over_hodl\": -0.3605884950997438, \"annualised_returns_over_uniform_hodl\": -0.3605884950997438, \"calmar\": -0.6556394994780856, \"daily_log_sharpe\": -0.48283065784119283, \"daily_returns\": 0.008127588399274613, \"fee_revenue_over_value\": 0.0018987961552458315, \"jax_sharpe\": 0.16772084232571122, \"return\": -0.337346369026842, \"returns_over_hodl\": -0.23777154853476679, \"returns_over_uniform_hodl\": -0.23777154853476679, \"sharpe\": 0.11655195742018709, \"sterling\": -1.4406568336976735, \"ulcer\": -0.18555198721657457}], \"train_return\": -0.337346369026842, \"train_returns_over_hodl\": -0.23777154853476679, \"train_sharpe\": 0.16772084232571122, \"validation_return\": -0.3391427223072726, \"validation_returns_over_hodl\": -6.464251356419481e-11, \"validation_sharpe\": -2.243853205022999}, {\"centeredness_margin\": 0.4186232484706372, \"continuous_test_metrics\": [{\"annualised_returns\": -0.47950643135340687, \"annualised_returns_over_hodl\": 0.00969948460070258, \"annualised_returns_over_uniform_hodl\": 0.8371116117022996, \"calmar\": -0.8271795923184805, \"daily_log_sharpe\": -0.6826249516261107, \"daily_returns\": 0.031016042753668538, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": 0.00846977048930143, \"return\": -0.2312418333441829, \"returns_over_hodl\": 0.0038950923791070124, \"returns_over_uniform_hodl\": 0.2775484322525792, \"sharpe\": -0.191863773408198, \"sterling\": -1.2345115012621357, \"ulcer\": -0.17641031651838235}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -6.566680532671398e-11, \"optuna_trial_number\": 87, \"price_ratio\": 5.0384624141048535, \"shift_exponent\": 2.7867326044850853e-05, \"step\": 87, \"test_objective\": [{\"annualised_returns\": -0.47950643135340687, \"annualised_returns_over_hodl\": 0.00969948460070258, \"annualised_returns_over_uniform_hodl\": 0.8371116117022996, \"calmar\": -0.8271795923184805, \"daily_log_sharpe\": -0.6826249516261107, \"daily_returns\": 0.031016042753668538, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": 0.00846977048930143, \"return\": -0.2312418333441829, \"returns_over_hodl\": 0.0038950923791070124, \"returns_over_uniform_hodl\": 0.2775484322525792, \"sharpe\": -0.191863773408198, \"sterling\": -1.2345115012621357, \"ulcer\": -0.17641031651838235}], \"train_objective\": [{\"annualised_returns\": -0.494859946298991, \"annualised_returns_over_hodl\": -0.3638578466450304, \"annualised_returns_over_uniform_hodl\": -0.3638578466450304, \"calmar\": -0.6587268456547826, \"daily_log_sharpe\": -0.4858504654307565, \"daily_returns\": 0.008151157786238475, \"fee_revenue_over_value\": 0.0018952184222557162, \"jax_sharpe\": 0.11147851620030394, \"return\": -0.3394054826650492, \"returns_over_hodl\": -0.24014007852763308, \"returns_over_uniform_hodl\": -0.24014007852763308, \"sharpe\": 0.11520765421338727, \"sterling\": -1.4456454005767168, \"ulcer\": -0.18596255251120677}], \"train_return\": -0.3394054826650492, \"train_returns_over_hodl\": -0.24014007852763308, \"train_sharpe\": 0.11147851620030394, \"validation_return\": -0.33914272230700215, \"validation_returns_over_hodl\": -6.566680532671398e-11, \"validation_sharpe\": -2.243853205026159}, {\"centeredness_margin\": 0.3756520479860663, \"continuous_test_metrics\": [{\"annualised_returns\": -0.4816202172788463, \"annualised_returns_over_hodl\": 0.005598975570100029, \"annualised_returns_over_uniform_hodl\": 0.8296508842270782, \"calmar\": -0.8308260159571487, \"daily_log_sharpe\": -0.6914249675820832, \"daily_returns\": 0.031016042754564013, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": -0.007765452509456583, \"return\": -0.23250071619846258, \"returns_over_hodl\": 0.002251160144692399, \"returns_over_uniform_hodl\": 0.27545637796732736, \"sharpe\": -0.20277864917562488, \"sterling\": -1.2399535503012515, \"ulcer\": -0.17641031652023267}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -0.01637176660407247, \"optuna_trial_number\": 88, \"price_ratio\": 6.66225444002508, \"shift_exponent\": 1.420174836969977e-05, \"step\": 88, \"test_objective\": [{\"annualised_returns\": -0.4816202172788463, \"annualised_returns_over_hodl\": 0.005598975570100029, \"annualised_returns_over_uniform_hodl\": 0.8296508842270782, \"calmar\": -0.8308260159571487, \"daily_log_sharpe\": -0.6914249675820832, \"daily_returns\": 0.031016042754564013, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": -0.007765452509456583, \"return\": -0.23250071619846258, \"returns_over_hodl\": 0.002251160144692399, \"returns_over_uniform_hodl\": 0.27545637796732736, \"sharpe\": -0.20277864917562488, \"sterling\": -1.2399535503012515, \"ulcer\": -0.17641031652023267}], \"train_objective\": [{\"annualised_returns\": -0.466938691439997, \"annualised_returns_over_hodl\": -0.3286955445067671, \"annualised_returns_over_uniform_hodl\": -0.3286955445067673, \"calmar\": -0.6243743531676421, \"daily_log_sharpe\": -0.46047239148062463, \"daily_returns\": 0.008093308190603657, \"fee_revenue_over_value\": 0.001923063309508287, \"jax_sharpe\": 0.15775010600732928, \"return\": -0.3174718443598208, \"returns_over_hodl\": -0.2149105432486359, \"returns_over_uniform_hodl\": -0.214910543248636, \"sharpe\": 0.11873494586642896, \"sterling\": -1.3946285512291943, \"ulcer\": -0.18145894909102464}], \"train_return\": -0.3174718443598208, \"train_returns_over_hodl\": -0.2149105432486359, \"train_sharpe\": 0.15775010600732928, \"validation_return\": -0.3386699216656214, \"validation_returns_over_hodl\": -0.01637176660407247, \"validation_sharpe\": -2.2395628523910935}, {\"centeredness_margin\": 0.4375439880281195, \"continuous_test_metrics\": [{\"annualised_returns\": -0.47648428066530224, \"annualised_returns_over_hodl\": 0.015562119930845997, \"annualised_returns_over_uniform_hodl\": 0.847778464197456, \"calmar\": -0.8219661781734576, \"daily_log_sharpe\": -0.6779838669557556, \"daily_returns\": 0.031016042754017318, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": 0.009144111894273268, \"return\": -0.229447258670044, \"returns_over_hodl\": 0.006238566285458713, \"returns_over_uniform_hodl\": 0.2805307173988685, \"sharpe\": -0.18725020227056546, \"sterling\": -1.2267308212401054, \"ulcer\": -0.17641031651910138}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -6.208245029171167e-11, \"optuna_trial_number\": 89, \"price_ratio\": 5.480678549190386, \"shift_exponent\": 3.331783392653413e-05, \"step\": 89, \"test_objective\": [{\"annualised_returns\": -0.47648428066530224, \"annualised_returns_over_hodl\": 0.015562119930845997, \"annualised_returns_over_uniform_hodl\": 0.847778464197456, \"calmar\": -0.8219661781734576, \"daily_log_sharpe\": -0.6779838669557556, \"daily_returns\": 0.031016042754017318, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": 0.009144111894273268, \"return\": -0.229447258670044, \"returns_over_hodl\": 0.006238566285458713, \"returns_over_uniform_hodl\": 0.2805307173988685, \"sharpe\": -0.18725020227056546, \"sterling\": -1.2267308212401054, \"ulcer\": -0.17641031651910138}], \"train_objective\": [{\"annualised_returns\": -0.4840576663338607, \"annualised_returns_over_hodl\": -0.35025412310773874, \"annualised_returns_over_uniform_hodl\": -0.35025412310773874, \"calmar\": -0.645632205042786, \"daily_log_sharpe\": -0.47402374850053824, \"daily_returns\": 0.008123762284400971, \"fee_revenue_over_value\": 0.0018884856618569769, \"jax_sharpe\": 0.11049318923102397, \"return\": -0.3308645784411244, \"returns_over_hodl\": -0.23031576021648992, \"returns_over_uniform_hodl\": -0.23031576021648992, \"sharpe\": 0.11953703507848025, \"sterling\": -1.4252571953989903, \"ulcer\": -0.18428299718238822}], \"train_return\": -0.3308645784411244, \"train_returns_over_hodl\": -0.23031576021648992, \"train_sharpe\": 0.11049318923102396, \"validation_return\": -0.33914272230782416, \"validation_returns_over_hodl\": -6.208245029171167e-11, \"validation_sharpe\": -2.2438532050101436}, {\"centeredness_margin\": 0.34881765961568745, \"continuous_test_metrics\": [{\"annualised_returns\": -0.4934729384655725, \"annualised_returns_over_hodl\": -0.01739398189414887, \"annualised_returns_over_uniform_hodl\": 0.7878160316293332, \"calmar\": -0.8512727626885676, \"daily_log_sharpe\": -0.7189527221730523, \"daily_returns\": 0.031016042755109288, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": -0.037070464863463125, \"return\": -0.23961714599107808, \"returns_over_hodl\": -0.007041943019188923, \"returns_over_uniform_hodl\": 0.2636300532281173, \"sharpe\": -0.23321862432258958, \"sterling\": -1.2704689839846193, \"ulcer\": -0.17641031652135422}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -0.04205711587220817, \"optuna_trial_number\": 90, \"price_ratio\": 7.56183479573719, \"shift_exponent\": 0.00010307482941268508, \"step\": 90, \"test_objective\": [{\"annualised_returns\": -0.4934729384655725, \"annualised_returns_over_hodl\": -0.01739398189414887, \"annualised_returns_over_uniform_hodl\": 0.7878160316293332, \"calmar\": -0.8512727626885676, \"daily_log_sharpe\": -0.7189527221730523, \"daily_returns\": 0.031016042755109288, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": -0.037070464863463125, \"return\": -0.23961714599107808, \"returns_over_hodl\": -0.007041943019188923, \"returns_over_uniform_hodl\": 0.2636300532281173, \"sharpe\": -0.23321862432258958, \"sterling\": -1.2704689839846193, \"ulcer\": -0.17641031652135422}], \"train_objective\": [{\"annualised_returns\": -0.4520725671771676, \"annualised_returns_over_hodl\": -0.3099740667080646, \"annualised_returns_over_uniform_hodl\": -0.3099740667080646, \"calmar\": -0.6055943284680995, \"daily_log_sharpe\": -0.4472926331014312, \"daily_returns\": 0.008073812574722035, \"fee_revenue_over_value\": 0.002063610713425118, \"jax_sharpe\": 0.1064658879871761, \"return\": -0.30597810315844853, \"returns_over_hodl\": -0.20168967468627985, \"returns_over_uniform_hodl\": -0.20168967468627985, \"sharpe\": 0.12240820662455021, \"sterling\": -1.3631820647659707, \"ulcer\": -0.1795412954299943}], \"train_return\": -0.30597810315844853, \"train_returns_over_hodl\": -0.20168967468627985, \"train_sharpe\": 0.1064658879871761, \"validation_return\": -0.3354236148015548, \"validation_returns_over_hodl\": -0.04205711587220817, \"validation_sharpe\": -2.2109154530340875}, {\"centeredness_margin\": 0.9681599316296501, \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -Infinity, \"optuna_trial_number\": 91, \"price_ratio\": 7.850902964395367, \"shift_exponent\": 40.30225737336778, \"step\": 91, \"test_objective\": -Infinity, \"train_objective\": -Infinity, \"train_return\": -Infinity, \"train_returns_over_hodl\": -Infinity, \"train_sharpe\": -Infinity, \"validation_return\": -Infinity, \"validation_returns_over_hodl\": -Infinity, \"validation_sharpe\": -Infinity}, {\"centeredness_margin\": 0.5900278771779911, \"continuous_test_metrics\": [{\"annualised_returns\": -0.47684394055426604, \"annualised_returns_over_hodl\": 0.014864419844094368, \"annualised_returns_over_uniform_hodl\": 0.8465090241926605, \"calmar\": -0.8225866157773635, \"daily_log_sharpe\": -0.6799264225976668, \"daily_returns\": 0.031016042754179633, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": 0.00503235815660716, \"return\": -0.22966050236153446, \"returns_over_hodl\": 0.005960098612909581, \"returns_over_uniform_hodl\": 0.28017634178951867, \"sharpe\": -0.18977013362787437, \"sterling\": -1.2276567838270307, \"ulcer\": -0.17641031651943248}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -6.89961421329599e-11, \"optuna_trial_number\": 92, \"price_ratio\": 5.6770764677303935, \"shift_exponent\": 9.122373380331418e-05, \"step\": 92, \"test_objective\": [{\"annualised_returns\": -0.47684394055426604, \"annualised_returns_over_hodl\": 0.014864419844094368, \"annualised_returns_over_uniform_hodl\": 0.8465090241926605, \"calmar\": -0.8225866157773635, \"daily_log_sharpe\": -0.6799264225976668, \"daily_returns\": 0.031016042754179633, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": 0.00503235815660716, \"return\": -0.22966050236153446, \"returns_over_hodl\": 0.005960098612909581, \"returns_over_uniform_hodl\": 0.28017634178951867, \"sharpe\": -0.18977013362787437, \"sterling\": -1.2276567838270307, \"ulcer\": -0.17641031651943248}], \"train_objective\": [{\"annualised_returns\": -0.47967506823267514, \"annualised_returns_over_hodl\": -0.344734950013166, \"annualised_returns_over_uniform_hodl\": -0.344734950013166, \"calmar\": -0.6398721132777478, \"daily_log_sharpe\": -0.47015919164127384, \"daily_returns\": 0.008096866660680425, \"fee_revenue_over_value\": 0.0010568301162414359, \"jax_sharpe\": 0.10908344620475056, \"return\": -0.3274195120023138, \"returns_over_hodl\": -0.2263530147728513, \"returns_over_uniform_hodl\": -0.2263530147728513, \"sharpe\": 0.11974950741566184, \"sterling\": -1.4173607486726463, \"ulcer\": -0.18356347370233408}], \"train_return\": -0.3274195120023138, \"train_returns_over_hodl\": -0.2263530147728513, \"train_sharpe\": 0.10908344620475055, \"validation_return\": -0.3391427223079374, \"validation_returns_over_hodl\": -6.89961421329599e-11, \"validation_sharpe\": -2.2438532050002005}, {\"centeredness_margin\": 0.48909035187144795, \"continuous_test_metrics\": [{\"annualised_returns\": -0.4817188375127529, \"annualised_returns_over_hodl\": 0.005407663372419824, \"annualised_returns_over_uniform_hodl\": 0.8293027985104195, \"calmar\": -0.8309961425264286, \"daily_log_sharpe\": -0.6868307712566716, \"daily_returns\": 0.03101604275311887, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": 0.007920142395028536, \"return\": -0.23255952509710442, \"returns_over_hodl\": 0.002174363631816556, \"returns_over_uniform_hodl\": 0.2753586473421177, \"sharpe\": -0.19618083554779456, \"sterling\": -1.2402074531204001, \"ulcer\": -0.17641031651725236}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -7.207590080327009e-11, \"optuna_trial_number\": 93, \"price_ratio\": 4.435106512696091, \"shift_exponent\": 6.915834689100826e-05, \"step\": 93, \"test_objective\": [{\"annualised_returns\": -0.4817188375127529, \"annualised_returns_over_hodl\": 0.005407663372419824, \"annualised_returns_over_uniform_hodl\": 0.8293027985104195, \"calmar\": -0.8309961425264286, \"daily_log_sharpe\": -0.6868307712566716, \"daily_returns\": 0.03101604275311887, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": 0.007920142395028536, \"return\": -0.23255952509710442, \"returns_over_hodl\": 0.002174363631816556, \"returns_over_uniform_hodl\": 0.2753586473421177, \"sharpe\": -0.19618083554779456, \"sterling\": -1.2402074531204001, \"ulcer\": -0.17641031651725236}], \"train_objective\": [{\"annualised_returns\": -0.5117380220400287, \"annualised_returns_over_hodl\": -0.385113051746519, \"annualised_returns_over_uniform_hodl\": -0.385113051746519, \"calmar\": -0.6787128847696193, \"daily_log_sharpe\": -0.5052509053022716, \"daily_returns\": 0.008107110584496533, \"fee_revenue_over_value\": 0.001788207055665889, \"jax_sharpe\": 0.11230176166190867, \"return\": -0.35289534922990795, \"returns_over_hodl\": -0.25565702376356925, \"returns_over_uniform_hodl\": -0.25565702376356925, \"sharpe\": 0.10725787225229631, \"sterling\": -1.4770278115635298, \"ulcer\": -0.1885901657406318}], \"train_return\": -0.35289534922990795, \"train_returns_over_hodl\": -0.25565702376356925, \"train_sharpe\": 0.11230176166190868, \"validation_return\": -0.33914272230519016, \"validation_returns_over_hodl\": -7.207590080327009e-11, \"validation_sharpe\": -2.243853205046157}, {\"centeredness_margin\": 0.44502556673870053, \"continuous_test_metrics\": [{\"annualised_returns\": -0.4812733012267615, \"annualised_returns_over_hodl\": 0.006271954084468456, \"annualised_returns_over_uniform_hodl\": 0.8308753441358299, \"calmar\": -0.8302275623036123, \"daily_log_sharpe\": -0.6859550813941065, \"daily_returns\": 0.031016042753182003, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": 0.008111739544215568, \"return\": -0.2322938974219072, \"returns_over_hodl\": 0.0025212377599079794, \"returns_over_uniform_hodl\": 0.27580007643481563, \"sharpe\": -0.195275431138338, \"sterling\": -1.2390603973485648, \"ulcer\": -0.17641031651737601}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -7.121447875846343e-11, \"optuna_trial_number\": 94, \"price_ratio\": 4.4539886244832765, \"shift_exponent\": 0.00010634690297078086, \"step\": 94, \"test_objective\": [{\"annualised_returns\": -0.4812733012267615, \"annualised_returns_over_hodl\": 0.006271954084468456, \"annualised_returns_over_uniform_hodl\": 0.8308753441358299, \"calmar\": -0.8302275623036123, \"daily_log_sharpe\": -0.6859550813941065, \"daily_returns\": 0.031016042753182003, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": 0.008111739544215568, \"return\": -0.2322938974219072, \"returns_over_hodl\": 0.0025212377599079794, \"returns_over_uniform_hodl\": 0.27580007643481563, \"sharpe\": -0.195275431138338, \"sterling\": -1.2390603973485648, \"ulcer\": -0.17641031651737601}], \"train_objective\": [{\"annualised_returns\": -0.5092899167709586, \"annualised_returns_over_hodl\": -0.3820300593247231, \"annualised_returns_over_uniform_hodl\": -0.3820300593247231, \"calmar\": -0.6757098724760592, \"daily_log_sharpe\": -0.5017946332923442, \"daily_returns\": 0.008150505364993392, \"fee_revenue_over_value\": 0.001873575069569616, \"jax_sharpe\": 0.11390474736814846, \"return\": -0.350927461728741, \"returns_over_hodl\": -0.25339342816465993, \"returns_over_uniform_hodl\": -0.25339342816465993, \"sharpe\": 0.10983512793434991, \"sterling\": -1.4716492139771042, \"ulcer\": -0.188293445253797}], \"train_return\": -0.350927461728741, \"train_returns_over_hodl\": -0.25339342816465993, \"train_sharpe\": 0.11390474736814844, \"validation_return\": -0.3391427223052831, \"validation_returns_over_hodl\": -7.121447875846343e-11, \"validation_sharpe\": -2.2438532050430755}, {\"centeredness_margin\": 0.7016256921804543, \"continuous_test_metrics\": [{\"annualised_returns\": -0.5177096328776576, \"annualised_returns_over_hodl\": -0.06441046645992199, \"annualised_returns_over_uniform_hodl\": 0.7022712421913024, \"calmar\": -0.8935401734516826, \"daily_log_sharpe\": -0.7743144829472123, \"daily_returns\": 0.031016042753002366, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": -0.0917795035112565, \"return\": -0.254484993283857, \"returns_over_hodl\": -0.02645735812286154, \"returns_over_uniform_hodl\": 0.23892215960990937, \"sharpe\": -0.2928407507745618, \"sterling\": -1.3341607577933903, \"ulcer\": -0.17604015144009483}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -0.06193367466276123, \"optuna_trial_number\": 95, \"price_ratio\": 10.1828995896536, \"shift_exponent\": 4.229204821511261e-05, \"step\": 95, \"test_objective\": [{\"annualised_returns\": -0.5177096328776576, \"annualised_returns_over_hodl\": -0.06441046645992199, \"annualised_returns_over_uniform_hodl\": 0.7022712421913024, \"calmar\": -0.8935401734516826, \"daily_log_sharpe\": -0.7743144829472123, \"daily_returns\": 0.031016042753002366, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": -0.0917795035112565, \"return\": -0.254484993283857, \"returns_over_hodl\": -0.02645735812286154, \"returns_over_uniform_hodl\": 0.23892215960990937, \"sharpe\": -0.2928407507745618, \"sterling\": -1.3341607577933903, \"ulcer\": -0.17604015144009483}], \"train_objective\": [{\"annualised_returns\": -0.4322736279144339, \"annualised_returns_over_hodl\": -0.2850405066696947, \"annualised_returns_over_uniform_hodl\": -0.2850405066696947, \"calmar\": -0.5807539025512543, \"daily_log_sharpe\": -0.43157404018496737, \"daily_returns\": 0.008035970964777971, \"fee_revenue_over_value\": 0.0004103682193146182, \"jax_sharpe\": 0.10365489407104289, \"return\": -0.2908590402884972, \"returns_over_hodl\": -0.1842987190794355, \"returns_over_uniform_hodl\": -0.1842987190794355, \"sharpe\": 0.12343986342460037, \"sterling\": -1.322471072632003, \"ulcer\": -0.17683123449028193}], \"train_return\": -0.2908590402884972, \"train_returns_over_hodl\": -0.1842987190794355, \"train_sharpe\": 0.10365489407104288, \"validation_return\": -0.3218944973067467, \"validation_returns_over_hodl\": -0.06193367466276123, \"validation_sharpe\": -2.11070674168772}, {\"centeredness_margin\": 0.4486432396159621, \"continuous_test_metrics\": [{\"annualised_returns\": -0.4845064531821749, \"annualised_returns_over_hodl\": 8.628653347386717e-13, \"annualised_returns_over_uniform_hodl\": 0.8194637506839981, \"calmar\": -0.835804968129214, \"daily_log_sharpe\": -0.6924635970511077, \"daily_returns\": 0.031016042748493105, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": 0.005019283840455878, \"return\": -0.2342245993366363, \"returns_over_hodl\": 3.47499806707674e-13, \"returns_over_uniform_hodl\": 0.2725915704165458, \"sharpe\": -0.20210852985847816, \"sterling\": -1.2473843112619118, \"ulcer\": -0.17641031650768302}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -1.1400125288218987e-10, \"optuna_trial_number\": 96, \"price_ratio\": 2.1094204091609616, \"shift_exponent\": 0.00025748526953663407, \"step\": 96, \"test_objective\": [{\"annualised_returns\": -0.4845064531821749, \"annualised_returns_over_hodl\": 8.628653347386717e-13, \"annualised_returns_over_uniform_hodl\": 0.8194637506839981, \"calmar\": -0.835804968129214, \"daily_log_sharpe\": -0.6924635970511077, \"daily_returns\": 0.031016042748493105, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": 0.005019283840455878, \"return\": -0.2342245993366363, \"returns_over_hodl\": 3.47499806707674e-13, \"returns_over_uniform_hodl\": 0.2725915704165458, \"sharpe\": -0.20210852985847816, \"sterling\": -1.2473843112619118, \"ulcer\": -0.17641031650768302}], \"train_objective\": [{\"annualised_returns\": -0.6224120467131709, \"annualised_returns_over_hodl\": -0.5244890759999941, \"annualised_returns_over_uniform_hodl\": -0.5244890759999945, \"calmar\": -0.7967989208458637, \"daily_log_sharpe\": -0.6609323640470737, \"daily_returns\": 0.008476546546229608, \"fee_revenue_over_value\": 0.0002462303381445039, \"jax_sharpe\": 0.08942081759733712, \"return\": -0.44639671156796557, \"returns_over_hodl\": -0.3632085337736519, \"returns_over_uniform_hodl\": -0.36320853377365225, \"sharpe\": 0.007674696855463256, \"sterling\": -1.6844644278229224, \"ulcer\": -0.2059953964547339}], \"train_return\": -0.44639671156796557, \"train_returns_over_hodl\": -0.3632085337736519, \"train_sharpe\": 0.08942081759733711, \"validation_return\": -0.33914272228181264, \"validation_returns_over_hodl\": -1.1400125288218987e-10, \"validation_sharpe\": -2.2438532051336475}, {\"centeredness_margin\": 0.40532254411609603, \"continuous_test_metrics\": [{\"annualised_returns\": -0.4835065724141222, \"annualised_returns_over_hodl\": 0.0019396570383425349, \"annualised_returns_over_uniform_hodl\": 0.8229928866425544, \"calmar\": -0.8340801061573693, \"daily_log_sharpe\": -0.6903845267067497, \"daily_returns\": 0.031016042752387694, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": 0.005865822010179977, \"return\": -0.23362674217333657, \"returns_over_hodl\": 0.000780721232271242, \"returns_over_uniform_hodl\": 0.27358510975676165, \"sharpe\": -0.19987607053896023, \"sterling\": -1.24481006737673, \"ulcer\": -0.17641031651573366}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -7.970135662560551e-11, \"optuna_trial_number\": 97, \"price_ratio\": 3.6746402079917315, \"shift_exponent\": 0.00024509547838059916, \"step\": 97, \"test_objective\": [{\"annualised_returns\": -0.4835065724141222, \"annualised_returns_over_hodl\": 0.0019396570383425349, \"annualised_returns_over_uniform_hodl\": 0.8229928866425544, \"calmar\": -0.8340801061573693, \"daily_log_sharpe\": -0.6903845267067497, \"daily_returns\": 0.031016042752387694, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": 0.005865822010179977, \"return\": -0.23362674217333657, \"returns_over_hodl\": 0.000780721232271242, \"returns_over_uniform_hodl\": 0.27358510975676165, \"sharpe\": -0.19987607053896023, \"sterling\": -1.24481006737673, \"ulcer\": -0.17641031651573366}], \"train_objective\": [{\"annualised_returns\": -0.5323678818346222, \"annualised_returns_over_hodl\": -0.41109302173106443, \"annualised_returns_over_uniform_hodl\": -0.41109302173106455, \"calmar\": -0.7015857384679887, \"daily_log_sharpe\": -0.5287017831682532, \"daily_returns\": 0.008211813834830961, \"fee_revenue_over_value\": 0.0018851935912826902, \"jax_sharpe\": 0.11826097283420549, \"return\": -0.369635335053083, \"returns_over_hodl\": -0.2749124731795939, \"returns_over_uniform_hodl\": -0.274912473179594, \"sharpe\": 0.09878194345098786, \"sterling\": -1.5082796570453094, \"ulcer\": -0.1927415133063162}], \"train_return\": -0.369635335053083, \"train_returns_over_hodl\": -0.2749124731795939, \"train_sharpe\": 0.11826097283420549, \"validation_return\": -0.3391427223021277, \"validation_returns_over_hodl\": -7.970135662560551e-11, \"validation_sharpe\": -2.243853205066595}, {\"centeredness_margin\": 0.15664757013651887, \"continuous_test_metrics\": [{\"annualised_returns\": -0.7775017717117036, \"annualised_returns_over_hodl\": -0.41762744932598317, \"annualised_returns_over_uniform_hodl\": -0.21467986658418114, \"calmar\": -1.2364528647183812, \"daily_log_sharpe\": -1.7429966338836824, \"daily_returns\": 0.024222001432220985, \"fee_revenue_over_value\": 0.011570598248328796, \"jax_sharpe\": -1.3006045032315223, \"return\": -0.45406150498419673, \"returns_over_hodl\": -0.1956639754737135, \"returns_over_uniform_hodl\": -0.09274086615845345, \"sharpe\": -1.3617229081978588, \"sterling\": -2.252248154082229, \"ulcer\": -0.1646703565427249}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -0.03478608280592066, \"optuna_trial_number\": 98, \"price_ratio\": 10.44557580474009, \"shift_exponent\": 60.99055610898491, \"step\": 98, \"test_objective\": [{\"annualised_returns\": -0.7775017717117036, \"annualised_returns_over_hodl\": -0.41762744932598317, \"annualised_returns_over_uniform_hodl\": -0.21467986658418114, \"calmar\": -1.2364528647183812, \"daily_log_sharpe\": -1.7429966338836824, \"daily_returns\": 0.024222001432220985, \"fee_revenue_over_value\": 0.011570598248328796, \"jax_sharpe\": -1.3006045032315223, \"return\": -0.45406150498419673, \"returns_over_hodl\": -0.1956639754737135, \"returns_over_uniform_hodl\": -0.09274086615845345, \"sharpe\": -1.3617229081978588, \"sterling\": -2.252248154082229, \"ulcer\": -0.1646703565427249}], \"train_objective\": [{\"annualised_returns\": -0.4136289125410021, \"annualised_returns_over_hodl\": -0.2615605048376368, \"annualised_returns_over_uniform_hodl\": -0.2615605048376368, \"calmar\": -0.5560089099707296, \"daily_log_sharpe\": -0.4257740820714251, \"daily_returns\": 0.008014436778242446, \"fee_revenue_over_value\": 0.006347208788386112, \"jax_sharpe\": 0.09363971453641201, \"return\": -0.27680969346101336, \"returns_over_hodl\": -0.16813822228915254, \"returns_over_uniform_hodl\": -0.16813822228915254, \"sharpe\": 0.11296482926068176, \"sterling\": -1.284996375779464, \"ulcer\": -0.17387253016851337}], \"train_return\": -0.27680969346101336, \"train_returns_over_hodl\": -0.16813822228915254, \"train_sharpe\": 0.09363971453641201, \"validation_return\": -0.24164079700819852, \"validation_returns_over_hodl\": -0.03478608280592066, \"validation_sharpe\": -1.8688352733503875}, {\"centeredness_margin\": 0.39189244841668675, \"continuous_test_metrics\": [{\"annualised_returns\": -0.4845064532444131, \"annualised_returns_over_hodl\": 1.0260681193585697e-12, \"annualised_returns_over_uniform_hodl\": 0.8194637504643252, \"calmar\": -0.8358049682289451, \"daily_log_sharpe\": -0.6924635972074046, \"daily_returns\": 0.031016042745532567, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": 0.005019283653309949, \"return\": -0.23422459937387186, \"returns_over_hodl\": 4.1322500976548326e-13, \"returns_over_uniform_hodl\": 0.27259157035466663, \"sharpe\": -0.20210853004181492, \"sterling\": -1.2473843114705165, \"ulcer\": -0.17641031650156286}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -1.4253465074887117e-10, \"optuna_trial_number\": 99, \"price_ratio\": 1.279287441019413, \"shift_exponent\": 2.4013049558254187e-05, \"step\": 99, \"test_objective\": [{\"annualised_returns\": -0.4845064532444131, \"annualised_returns_over_hodl\": 1.0260681193585697e-12, \"annualised_returns_over_uniform_hodl\": 0.8194637504643252, \"calmar\": -0.8358049682289451, \"daily_log_sharpe\": -0.6924635972074046, \"daily_returns\": 0.031016042745532567, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": 0.005019283653309949, \"return\": -0.23422459937387186, \"returns_over_hodl\": 4.1322500976548326e-13, \"returns_over_uniform_hodl\": 0.27259157035466663, \"sharpe\": -0.20210853004181492, \"sterling\": -1.2473843114705165, \"ulcer\": -0.17641031650156286}], \"train_objective\": [{\"annualised_returns\": -0.6733050294396934, \"annualised_returns_over_hodl\": -0.5885805519878995, \"annualised_returns_over_uniform_hodl\": -0.5885805519878995, \"calmar\": -0.8442035486311029, \"daily_log_sharpe\": -0.7598686004142097, \"daily_returns\": 0.009715769472075229, \"fee_revenue_over_value\": 4.978666670921394e-05, \"jax_sharpe\": 0.03730431055135123, \"return\": -0.49297953358396684, \"returns_over_hodl\": -0.41679120597299224, \"returns_over_uniform_hodl\": -0.41679120597299235, \"sharpe\": -0.08005757644470855, \"sterling\": -1.790878212576524, \"ulcer\": -0.21081531379922153}], \"train_return\": -0.49297953358396684, \"train_returns_over_hodl\": -0.41679120597299224, \"train_sharpe\": 0.03730431055135123, \"validation_return\": -0.3391427222680148, \"validation_returns_over_hodl\": -1.4253465074887117e-10, \"validation_sharpe\": -2.2438532052017734}, {\"centeredness_margin\": 0.28965933162833374, \"continuous_test_metrics\": [{\"annualised_returns\": -0.6412361778357438, \"annualised_returns_over_hodl\": -0.22002854854169396, \"annualised_returns_over_uniform_hodl\": 0.26627728613524004, \"calmar\": -1.0950406843549316, \"daily_log_sharpe\": -1.154029947044778, \"daily_returns\": 0.028369103428741976, \"fee_revenue_over_value\": 0.00025917507662597103, \"jax_sharpe\": -0.49895890689799344, \"return\": -0.3382337696125076, \"returns_over_hodl\": -0.09523453883788868, \"returns_over_uniform_hodl\": 0.09974559857619547, \"sharpe\": -0.7136192372058009, \"sterling\": -1.7220360092157339, \"ulcer\": -0.16402720685253677}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -0.05179961119487431, \"optuna_trial_number\": 100, \"price_ratio\": 12.329009760940357, \"shift_exponent\": 0.002102082396163866, \"step\": 100, \"test_objective\": [{\"annualised_returns\": -0.6412361778357438, \"annualised_returns_over_hodl\": -0.22002854854169396, \"annualised_returns_over_uniform_hodl\": 0.26627728613524004, \"calmar\": -1.0950406843549316, \"daily_log_sharpe\": -1.154029947044778, \"daily_returns\": 0.028369103428741976, \"fee_revenue_over_value\": 0.00025917507662597103, \"jax_sharpe\": -0.49895890689799344, \"return\": -0.3382337696125076, \"returns_over_hodl\": -0.09523453883788868, \"returns_over_uniform_hodl\": 0.09974559857619547, \"sharpe\": -0.7136192372058009, \"sterling\": -1.7220360092157339, \"ulcer\": -0.16402720685253677}], \"train_objective\": [{\"annualised_returns\": -0.4029161956166549, \"annualised_returns_over_hodl\": -0.24806957145667374, \"annualised_returns_over_uniform_hodl\": -0.24806957145667374, \"calmar\": -0.5427867584739073, \"daily_log_sharpe\": -0.40507499846678363, \"daily_returns\": 0.008039457067421988, \"fee_revenue_over_value\": 0.003646446672581133, \"jax_sharpe\": 0.15006085536277258, \"return\": -0.2688167468689068, \"returns_over_hodl\": -0.15894420143304033, \"returns_over_uniform_hodl\": -0.15894420143304033, \"sharpe\": 0.13470573461060095, \"sterling\": -1.253300993440754, \"ulcer\": -0.17360853149593788}], \"train_return\": -0.2688167468689068, \"train_returns_over_hodl\": -0.15894420143304033, \"train_sharpe\": 0.15006085536277255, \"validation_return\": -0.2699831835125045, \"validation_returns_over_hodl\": -0.05179961119487431, \"validation_sharpe\": -1.9211105237458728}, {\"centeredness_margin\": 0.12696454309515126, \"continuous_test_metrics\": [{\"annualised_returns\": -0.7082231407276784, \"annualised_returns_over_hodl\": -0.1565735411871525, \"annualised_returns_over_uniform_hodl\": 0.02984299611810015, \"calmar\": -1.180228906286914, \"daily_log_sharpe\": -1.4998939261628872, \"daily_returns\": 0.02208509003796458, \"fee_revenue_over_value\": 0.01007977782225772, \"jax_sharpe\": -0.9528326131587803, \"return\": -0.3910863051310971, \"returns_over_hodl\": -0.06628052163089626, \"returns_over_uniform_hodl\": 0.011913459912778368, \"sharpe\": -1.1028403503784334, \"sterling\": -2.07784186631991, \"ulcer\": -0.15595493947919956}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -0.03644613991135026, \"optuna_trial_number\": 101, \"price_ratio\": 119.35652600199623, \"shift_exponent\": 24.517061217237913, \"step\": 101, \"test_objective\": [{\"annualised_returns\": -0.7082231407276784, \"annualised_returns_over_hodl\": -0.1565735411871525, \"annualised_returns_over_uniform_hodl\": 0.02984299611810015, \"calmar\": -1.180228906286914, \"daily_log_sharpe\": -1.4998939261628872, \"daily_returns\": 0.02208509003796458, \"fee_revenue_over_value\": 0.01007977782225772, \"jax_sharpe\": -0.9528326131587803, \"return\": -0.3910863051310971, \"returns_over_hodl\": -0.06628052163089626, \"returns_over_uniform_hodl\": 0.011913459912778368, \"sharpe\": -1.1028403503784334, \"sterling\": -2.07784186631991, \"ulcer\": -0.15595493947919956}], \"train_objective\": [{\"annualised_returns\": -0.3417568318198916, \"annualised_returns_over_hodl\": -0.1710492498677596, \"annualised_returns_over_uniform_hodl\": -0.1710492498677596, \"calmar\": -0.4657570080591183, \"daily_log_sharpe\": -0.3425277405052561, \"daily_returns\": 0.00794932627078553, \"fee_revenue_over_value\": 0.005884338328198543, \"jax_sharpe\": 0.13609550358179962, \"return\": -0.22422018497587926, \"returns_over_hodl\": -0.10764625824894458, \"returns_over_uniform_hodl\": -0.10764625824894458, \"sharpe\": 0.16606544702570383, \"sterling\": -1.104627568442496, \"ulcer\": -0.16697718316857046}], \"train_return\": -0.22422018497587926, \"train_returns_over_hodl\": -0.10764625824894458, \"train_sharpe\": 0.13609550358179962, \"validation_return\": -0.21049162612502792, \"validation_returns_over_hodl\": -0.03644613991135026, \"validation_sharpe\": -1.6141166831533178}, {\"centeredness_margin\": 0.35441299499351886, \"continuous_test_metrics\": [{\"annualised_returns\": -0.4954762988981556, \"annualised_returns_over_hodl\": -0.021280277728714214, \"annualised_returns_over_uniform_hodl\": 0.7807450572027075, \"calmar\": -0.8547286953258213, \"daily_log_sharpe\": -0.7241069299432348, \"daily_returns\": 0.03101604275470732, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": -0.04240837413724958, \"return\": -0.24082976648286525, \"returns_over_hodl\": -0.008625462789574989, \"returns_over_uniform_hodl\": 0.26161487930815874, \"sharpe\": -0.23899330802753138, \"sterling\": -1.2756267376753678, \"ulcer\": -0.1764103165205343}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -0.024313913303343315, \"optuna_trial_number\": 102, \"price_ratio\": 5.601150555822464, \"shift_exponent\": 0.0006099455557539805, \"step\": 102, \"test_objective\": [{\"annualised_returns\": -0.4954762988981556, \"annualised_returns_over_hodl\": -0.021280277728714214, \"annualised_returns_over_uniform_hodl\": 0.7807450572027075, \"calmar\": -0.8547286953258213, \"daily_log_sharpe\": -0.7241069299432348, \"daily_returns\": 0.03101604275470732, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": -0.04240837413724958, \"return\": -0.24082976648286525, \"returns_over_hodl\": -0.008625462789574989, \"returns_over_uniform_hodl\": 0.26161487930815874, \"sharpe\": -0.23899330802753138, \"sterling\": -1.2756267376753678, \"ulcer\": -0.1764103165205343}], \"train_objective\": [{\"annualised_returns\": -0.46226217670522707, \"annualised_returns_over_hodl\": -0.32280623097524974, \"annualised_returns_over_uniform_hodl\": -0.32280623097524974, \"calmar\": -0.617083211467881, \"daily_log_sharpe\": -0.4507145299345527, \"daily_returns\": 0.008089220646372186, \"fee_revenue_over_value\": 0.0020043055582523762, \"jax_sharpe\": 0.12168074807753933, \"return\": -0.313842775582432, \"returns_over_hodl\": -0.2107361460294016, \"returns_over_uniform_hodl\": -0.2107361460294016, \"sharpe\": 0.1330624550367918, \"sterling\": -1.3766158282923042, \"ulcer\": -0.18180194971846877}], \"train_return\": -0.313842775582432, \"train_returns_over_hodl\": -0.2107361460294016, \"train_sharpe\": 0.12168074807753931, \"validation_return\": -0.33805089069407146, \"validation_returns_over_hodl\": -0.024313913303343315, \"validation_sharpe\": -2.233844784649578}, {\"centeredness_margin\": 0.25615294155615176, \"continuous_test_metrics\": [{\"annualised_returns\": -0.5945621770193763, \"annualised_returns_over_hodl\": -0.1686557493284625, \"annualised_returns_over_uniform_hodl\": 0.43101582284244566, \"calmar\": -1.0241202605187778, \"daily_log_sharpe\": -0.9881211253208768, \"daily_returns\": 0.029750785579532996, \"fee_revenue_over_value\": 3.0876520461110896e-05, \"jax_sharpe\": -0.3175550303252145, \"return\": -0.3048215988268128, \"returns_over_hodl\": -0.07169063159075528, \"returns_over_uniform_hodl\": 0.15527107883366975, \"sharpe\": -0.5295441047162245, \"sterling\": -1.5692271857468885, \"ulcer\": -0.16860169529207975}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -0.056758384553265456, \"optuna_trial_number\": 103, \"price_ratio\": 15.134678563992653, \"shift_exponent\": 0.0008188201226947848, \"step\": 103, \"test_objective\": [{\"annualised_returns\": -0.5945621770193763, \"annualised_returns_over_hodl\": -0.1686557493284625, \"annualised_returns_over_uniform_hodl\": 0.43101582284244566, \"calmar\": -1.0241202605187778, \"daily_log_sharpe\": -0.9881211253208768, \"daily_returns\": 0.029750785579532996, \"fee_revenue_over_value\": 3.0876520461110896e-05, \"jax_sharpe\": -0.3175550303252145, \"return\": -0.3048215988268128, \"returns_over_hodl\": -0.07169063159075528, \"returns_over_uniform_hodl\": 0.15527107883366975, \"sharpe\": -0.5295441047162245, \"sterling\": -1.5692271857468885, \"ulcer\": -0.16860169529207975}], \"train_objective\": [{\"annualised_returns\": -0.4012279064019548, \"annualised_returns_over_hodl\": -0.24594344439813542, \"annualised_returns_over_uniform_hodl\": -0.24594344439813542, \"calmar\": -0.5416218732662494, \"daily_log_sharpe\": -0.4015134245293647, \"daily_returns\": 0.0080199527189049, \"fee_revenue_over_value\": 0.004097761912111285, \"jax_sharpe\": 0.11463978074756553, \"return\": -0.26756224201950185, \"returns_over_hodl\": -0.15750118619246412, \"returns_over_uniform_hodl\": -0.15750118619246412, \"sharpe\": 0.1372407025366036, \"sterling\": -1.2499131818239493, \"ulcer\": -0.17333342129235688}], \"train_return\": -0.26756224201950185, \"train_returns_over_hodl\": -0.15750118619246412, \"train_sharpe\": 0.11463978074756553, \"validation_return\": -0.28138371370574367, \"validation_returns_over_hodl\": -0.05675838455326543, \"validation_sharpe\": -1.9578055845199094}, {\"centeredness_margin\": 0.4495139041861852, \"continuous_test_metrics\": [{\"annualised_returns\": -0.4765715731326494, \"annualised_returns_over_hodl\": 0.01539278227760943, \"annualised_returns_over_uniform_hodl\": 0.8474703604762237, \"calmar\": -0.822116763576069, \"daily_log_sharpe\": -0.6780916487231644, \"daily_returns\": 0.031016042754024756, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": 0.009486211226672904, \"return\": -0.22949900665675393, \"returns_over_hodl\": 0.0061709903529478805, \"returns_over_uniform_hodl\": 0.2804447208369314, \"sharpe\": -0.1873311460955304, \"sterling\": -1.2269555601155062, \"ulcer\": -0.17641031651911457}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -6.094746929363737e-11, \"optuna_trial_number\": 104, \"price_ratio\": 5.520551241956628, \"shift_exponent\": 1.617995896688924e-05, \"step\": 104, \"test_objective\": [{\"annualised_returns\": -0.4765715731326494, \"annualised_returns_over_hodl\": 0.01539278227760943, \"annualised_returns_over_uniform_hodl\": 0.8474703604762237, \"calmar\": -0.822116763576069, \"daily_log_sharpe\": -0.6780916487231644, \"daily_returns\": 0.031016042754024756, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": 0.009486211226672904, \"return\": -0.22949900665675393, \"returns_over_hodl\": 0.0061709903529478805, \"returns_over_uniform_hodl\": 0.2804447208369314, \"sharpe\": -0.1873311460955304, \"sterling\": -1.2269555601155062, \"ulcer\": -0.17641031651911457}], \"train_objective\": [{\"annualised_returns\": -0.4843568277304352, \"annualised_returns_over_hodl\": -0.35063086847493585, \"annualised_returns_over_uniform_hodl\": -0.35063086847493585, \"calmar\": -0.6460104824257412, \"daily_log_sharpe\": -0.4748279225276967, \"daily_returns\": 0.008121816552880266, \"fee_revenue_over_value\": 0.0018800812971187107, \"jax_sharpe\": 0.10960878257422044, \"return\": -0.33110016115311247, \"returns_over_hodl\": -0.2305867431815828, \"returns_over_uniform_hodl\": -0.2305867431815828, \"sharpe\": 0.11834316030642811, \"sterling\": -1.426403426723099, \"ulcer\": -0.1842609335843337}], \"train_return\": -0.33110016115311247, \"train_returns_over_hodl\": -0.2305867431815828, \"train_sharpe\": 0.10960878257422044, \"validation_return\": -0.3391427223077951, \"validation_returns_over_hodl\": -6.094746929363737e-11, \"validation_sharpe\": -2.243853205009446}, {\"centeredness_margin\": 0.5085216553337342, \"continuous_test_metrics\": [{\"annualised_returns\": -0.5158135646076101, \"annualised_returns_over_hodl\": -0.06073230543829444, \"annualised_returns_over_uniform_hodl\": 0.7089635228366564, \"calmar\": -0.8901906138394141, \"daily_log_sharpe\": -0.7698317426022026, \"daily_returns\": 0.031016042752880627, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": -0.0862456504139225, \"return\": -0.25330598981896035, \"returns_over_hodl\": -0.02491773767473837, \"returns_over_uniform_hodl\": 0.24088146761277773, \"sharpe\": -0.28801219683867757, \"sterling\": -1.3290490415206546, \"ulcer\": -0.1761047338020596}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -0.06173037944005055, \"optuna_trial_number\": 105, \"price_ratio\": 10.131390159474206, \"shift_exponent\": 2.3462463081131458e-05, \"step\": 105, \"test_objective\": [{\"annualised_returns\": -0.5158135646076101, \"annualised_returns_over_hodl\": -0.06073230543829444, \"annualised_returns_over_uniform_hodl\": 0.7089635228366564, \"calmar\": -0.8901906138394141, \"daily_log_sharpe\": -0.7698317426022026, \"daily_returns\": 0.031016042752880627, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": -0.0862456504139225, \"return\": -0.25330598981896035, \"returns_over_hodl\": -0.02491773767473837, \"returns_over_uniform_hodl\": 0.24088146761277773, \"sharpe\": -0.28801219683867757, \"sterling\": -1.3290490415206546, \"ulcer\": -0.1761047338020596}], \"train_objective\": [{\"annualised_returns\": -0.4314529813073863, \"annualised_returns_over_hodl\": -0.28400703506924274, \"annualised_returns_over_uniform_hodl\": -0.2840070350692424, \"calmar\": -0.5799782611238364, \"daily_log_sharpe\": -0.42986286875280416, \"daily_returns\": 0.00804360806767792, \"fee_revenue_over_value\": 0.0018525076781881092, \"jax_sharpe\": 0.10567667586783103, \"return\": -0.2902368804727665, \"returns_over_hodl\": -0.18358306931801815, \"returns_over_uniform_hodl\": -0.18358306931801793, \"sharpe\": 0.12552021810331382, \"sterling\": -1.3199907552384802, \"ulcer\": -0.17677391421012179}], \"train_return\": -0.2902368804727665, \"train_returns_over_hodl\": -0.18358306931801815, \"train_sharpe\": 0.105676675867831, \"validation_return\": -0.3229525654818576, \"validation_returns_over_hodl\": -0.06173037944005055, \"validation_sharpe\": -2.116858398527463}, {\"centeredness_margin\": 0.3904960223953923, \"continuous_test_metrics\": [{\"annualised_returns\": -0.4768348079987442, \"annualised_returns_over_hodl\": 0.014882135991693746, \"annualised_returns_over_uniform_hodl\": 0.8465412580660541, \"calmar\": -0.8225708615016215, \"daily_log_sharpe\": -0.6783999802003844, \"daily_returns\": 0.03101604275398075, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": 0.009789926659828392, \"return\": -0.2296550865520065, \"returns_over_hodl\": 0.00596717093729926, \"returns_over_uniform_hodl\": 0.2801853419657405, \"sharpe\": -0.18759869895650408, \"sterling\": -1.2276332716037146, \"ulcer\": -0.17641031651902533}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -6.161604559906664e-11, \"optuna_trial_number\": 106, \"price_ratio\": 5.457651949921053, \"shift_exponent\": 1.2413255015270424e-05, \"step\": 106, \"test_objective\": [{\"annualised_returns\": -0.4768348079987442, \"annualised_returns_over_hodl\": 0.014882135991693746, \"annualised_returns_over_uniform_hodl\": 0.8465412580660541, \"calmar\": -0.8225708615016215, \"daily_log_sharpe\": -0.6783999802003844, \"daily_returns\": 0.03101604275398075, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": 0.009789926659828392, \"return\": -0.2296550865520065, \"returns_over_hodl\": 0.00596717093729926, \"returns_over_uniform_hodl\": 0.2801853419657405, \"sharpe\": -0.18759869895650408, \"sterling\": -1.2276332716037146, \"ulcer\": -0.17641031651902533}], \"train_objective\": [{\"annualised_returns\": -0.4858647743346889, \"annualised_returns_over_hodl\": -0.35252988319955714, \"annualised_returns_over_uniform_hodl\": -0.35252988319955714, \"calmar\": -0.6478934855764311, \"daily_log_sharpe\": -0.47640961051169795, \"daily_returns\": 0.008123909233444178, \"fee_revenue_over_value\": 0.0019038972703800895, \"jax_sharpe\": 0.10951141709169665, \"return\": -0.332288452868736, \"returns_over_hodl\": -0.23195359565465912, \"returns_over_uniform_hodl\": -0.23195359565465912, \"sharpe\": 0.11769433099084987, \"sterling\": -1.4294420034701476, \"ulcer\": -0.18446965375146954}], \"train_return\": -0.332288452868736, \"train_returns_over_hodl\": -0.23195359565465912, \"train_sharpe\": 0.10951141709169665, \"validation_return\": -0.3391427223077521, \"validation_returns_over_hodl\": -6.161604559906664e-11, \"validation_sharpe\": -2.2438532050121656}, {\"centeredness_margin\": 0.43377060318472005, \"continuous_test_metrics\": [{\"annualised_returns\": -0.5437856732371211, \"annualised_returns_over_hodl\": -0.11499507713936674, \"annualised_returns_over_uniform_hodl\": 0.6102343767672944, \"calmar\": -0.9408467717732715, \"daily_log_sharpe\": -0.8392955066787985, \"daily_returns\": 0.031016042754891608, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": -0.1561774355191002, \"return\": -0.27098841065245494, \"returns_over_hodl\": -0.04800860848389543, \"returns_over_uniform_hodl\": 0.2114962200874979, \"sharpe\": -0.36397797756145917, \"sterling\": -1.4080097356772558, \"ulcer\": -0.17427571710530348}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -0.06472372754029565, \"optuna_trial_number\": 107, \"price_ratio\": 13.012609123009474, \"shift_exponent\": 6.118033581338627e-05, \"step\": 107, \"test_objective\": [{\"annualised_returns\": -0.5437856732371211, \"annualised_returns_over_hodl\": -0.11499507713936674, \"annualised_returns_over_uniform_hodl\": 0.6102343767672944, \"calmar\": -0.9408467717732715, \"daily_log_sharpe\": -0.8392955066787985, \"daily_returns\": 0.031016042754891608, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": -0.1561774355191002, \"return\": -0.27098841065245494, \"returns_over_hodl\": -0.04800860848389543, \"returns_over_uniform_hodl\": 0.2114962200874979, \"sharpe\": -0.36397797756145917, \"sterling\": -1.4080097356772558, \"ulcer\": -0.17427571710530348}], \"train_objective\": [{\"annualised_returns\": -0.41530081397116725, \"annualised_returns_over_hodl\": -0.26366599413350644, \"annualised_returns_over_uniform_hodl\": -0.26366599413350644, \"calmar\": -0.5595463266029095, \"daily_log_sharpe\": -0.4155833345931219, \"daily_returns\": 0.00801659526638855, \"fee_revenue_over_value\": 0.0019305822344606826, \"jax_sharpe\": 0.10796906265555963, \"return\": -0.27806228580017833, \"returns_over_hodl\": -0.1695790376327534, \"returns_over_uniform_hodl\": -0.1695790376327534, \"sharpe\": 0.1296493572481718, \"sterling\": -1.2843816379666322, \"ulcer\": -0.17472669681806058}], \"train_return\": -0.27806228580017833, \"train_returns_over_hodl\": -0.1695790376327534, \"train_sharpe\": 0.10796906265555963, \"validation_return\": -0.3050087436755823, \"validation_returns_over_hodl\": -0.06472372754029565, \"validation_sharpe\": -2.0476856343503695}, {\"centeredness_margin\": 0.48835344374057266, \"continuous_test_metrics\": [{\"annualised_returns\": -0.48539909803193704, \"annualised_returns_over_hodl\": -0.0017316317303265327, \"annualised_returns_over_uniform_hodl\": 0.816313109989458, \"calmar\": -0.8373448409257284, \"daily_log_sharpe\": -0.7005092923691277, \"daily_returns\": 0.031016042754825602, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": -0.018224694717220633, \"return\": -0.23475892253680475, \"returns_over_hodl\": -0.0006977545624612391, \"returns_over_uniform_hodl\": 0.2717036139741955, \"sharpe\": -0.21301588992589784, \"sterling\": -1.2496824706816398, \"ulcer\": -0.1764103165207693}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -0.02937471621994414, \"optuna_trial_number\": 108, \"price_ratio\": 7.137724169523133, \"shift_exponent\": 2.3111668731194754e-05, \"step\": 108, \"test_objective\": [{\"annualised_returns\": -0.48539909803193704, \"annualised_returns_over_hodl\": -0.0017316317303265327, \"annualised_returns_over_uniform_hodl\": 0.816313109989458, \"calmar\": -0.8373448409257284, \"daily_log_sharpe\": -0.7005092923691277, \"daily_returns\": 0.031016042754825602, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": -0.018224694717220633, \"return\": -0.23475892253680475, \"returns_over_hodl\": -0.0006977545624612391, \"returns_over_uniform_hodl\": 0.2717036139741955, \"sharpe\": -0.21301588992589784, \"sterling\": -1.2496824706816398, \"ulcer\": -0.1764103165207693}], \"train_objective\": [{\"annualised_returns\": -0.45983347565789834, \"annualised_returns_over_hodl\": -0.31974767503064905, \"annualised_returns_over_uniform_hodl\": -0.31974767503064927, \"calmar\": -0.615430752883826, \"daily_log_sharpe\": -0.45447439625924296, \"daily_returns\": 0.008060883672099936, \"fee_revenue_over_value\": 0.0018657015519404614, \"jax_sharpe\": 0.15083428899352255, \"return\": -0.3119629482217461, \"returns_over_hodl\": -0.208573843083814, \"returns_over_uniform_hodl\": -0.2085738430838141, \"sharpe\": 0.1198262133340346, \"sterling\": -1.3800641340081787, \"ulcer\": -0.18049121191143913}], \"train_return\": -0.3119629482217461, \"train_returns_over_hodl\": -0.208573843083814, \"train_sharpe\": 0.15083428899352255, \"validation_return\": -0.33739910910595805, \"validation_returns_over_hodl\": -0.02937471621994414, \"validation_sharpe\": -2.2281524868774434}, {\"centeredness_margin\": 0.4001414251055238, \"continuous_test_metrics\": [{\"annualised_returns\": -0.5565475925032849, \"annualised_returns_over_hodl\": -0.13975177747798995, \"annualised_returns_over_uniform_hodl\": 0.5651904579107381, \"calmar\": -0.9624485524893373, \"daily_log_sharpe\": -0.8733890520021417, \"daily_returns\": 0.031216347355300517, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": -0.1941047781850776, \"return\": -0.2792711009692631, \"returns_over_hodl\": -0.058824691515944716, \"returns_over_uniform_hodl\": 0.19773176399709613, \"sharpe\": -0.40153213766317086, \"sterling\": -1.4472799487989336, \"ulcer\": -0.17298922476418846}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -0.0631806485290467, \"optuna_trial_number\": 109, \"price_ratio\": 14.702488717030144, \"shift_exponent\": 7.35414500819736e-05, \"step\": 109, \"test_objective\": [{\"annualised_returns\": -0.5565475925032849, \"annualised_returns_over_hodl\": -0.13975177747798995, \"annualised_returns_over_uniform_hodl\": 0.5651904579107381, \"calmar\": -0.9624485524893373, \"daily_log_sharpe\": -0.8733890520021417, \"daily_returns\": 0.031216347355300517, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": -0.1941047781850776, \"return\": -0.2792711009692631, \"returns_over_hodl\": -0.058824691515944716, \"returns_over_uniform_hodl\": 0.19773176399709613, \"sharpe\": -0.40153213766317086, \"sterling\": -1.4472799487989336, \"ulcer\": -0.17298922476418846}], \"train_objective\": [{\"annualised_returns\": -0.40812040467389343, \"annualised_returns_over_hodl\": -0.2546234305932146, \"annualised_returns_over_uniform_hodl\": -0.2546234305932146, \"calmar\": -0.5504380467570138, \"daily_log_sharpe\": -0.40877435375773696, \"daily_returns\": 0.008025480604461895, \"fee_revenue_over_value\": 0.0021763233363052584, \"jax_sharpe\": 0.10985136266412793, \"return\": -0.27269259883122265, \"returns_over_hodl\": -0.1634024651491931, \"returns_over_uniform_hodl\": -0.1634024651491931, \"sharpe\": 0.13243745063354195, \"sterling\": -1.2677988486403147, \"ulcer\": -0.17388681455116167}], \"train_return\": -0.27269259883122265, \"train_returns_over_hodl\": -0.1634024651491931, \"train_sharpe\": 0.10985136266412793, \"validation_return\": -0.29599256491638515, \"validation_returns_over_hodl\": -0.0631806485290467, \"validation_sharpe\": -2.012906258739026}, {\"centeredness_margin\": 0.4793150269533163, \"continuous_test_metrics\": [{\"annualised_returns\": -0.5199551512911131, \"annualised_returns_over_hodl\": -0.06876652179727505, \"annualised_returns_over_uniform_hodl\": 0.6943455574179498, \"calmar\": -0.8977174254095154, \"daily_log_sharpe\": -0.7795669495558775, \"daily_returns\": 0.03101604275324559, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": -0.09598631462530138, \"return\": -0.2558848763788729, \"returns_over_hodl\": -0.028285417754417286, \"returns_over_uniform_hodl\": 0.2365957863355228, \"sharpe\": -0.2984863477244846, \"sterling\": -1.3403843318437914, \"ulcer\": -0.17591734789955257}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -0.0632059111383696, \"optuna_trial_number\": 110, \"price_ratio\": 10.659171920651234, \"shift_exponent\": 1.3070688148996879e-05, \"step\": 110, \"test_objective\": [{\"annualised_returns\": -0.5199551512911131, \"annualised_returns_over_hodl\": -0.06876652179727505, \"annualised_returns_over_uniform_hodl\": 0.6943455574179498, \"calmar\": -0.8977174254095154, \"daily_log_sharpe\": -0.7795669495558775, \"daily_returns\": 0.03101604275324559, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": -0.09598631462530138, \"return\": -0.2558848763788729, \"returns_over_hodl\": -0.028285417754417286, \"returns_over_uniform_hodl\": 0.2365957863355228, \"sharpe\": -0.2984863477244846, \"sterling\": -1.3403843318437914, \"ulcer\": -0.17591734789955257}], \"train_objective\": [{\"annualised_returns\": -0.42821511309421834, \"annualised_returns_over_hodl\": -0.27992946402273167, \"annualised_returns_over_uniform_hodl\": -0.27992946402273167, \"calmar\": -0.5759286633448124, \"daily_log_sharpe\": -0.4270482124590458, \"daily_returns\": 0.00804335591607838, \"fee_revenue_over_value\": 0.0018639408829968986, \"jax_sharpe\": 0.1062179758569114, \"return\": -0.2877855780337337, \"returns_over_hodl\": -0.1807634175801518, \"returns_over_uniform_hodl\": -0.1807634175801518, \"sharpe\": 0.12615187631380487, \"sterling\": -1.3130495352391864, \"ulcer\": -0.17634923896798996}], \"train_return\": -0.2877855780337337, \"train_returns_over_hodl\": -0.1807634175801518, \"train_sharpe\": 0.10621797585691141, \"validation_return\": -0.3198751868132337, \"validation_returns_over_hodl\": -0.0632059111383696, \"validation_sharpe\": -2.1011403734409924}, {\"centeredness_margin\": 0.34800396554437374, \"continuous_test_metrics\": [{\"annualised_returns\": -0.47818205508454037, \"annualised_returns_over_hodl\": 0.012268626878089384, \"annualised_returns_over_uniform_hodl\": 0.8417860729605329, \"calmar\": -0.824894953748506, \"daily_log_sharpe\": -0.6806359418473106, \"daily_returns\": 0.03101604275384484, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": 0.009165925712046828, \"return\": -0.23045464527409198, \"returns_over_hodl\": 0.004923054451435593, \"returns_over_uniform_hodl\": 0.2788566081243329, \"sharpe\": -0.18985946970852705, \"sterling\": -1.2311018274501322, \"ulcer\": -0.17641031651874528}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -6.352929293740317e-11, \"optuna_trial_number\": 111, \"price_ratio\": 5.258095640938372, \"shift_exponent\": 1.6172615694279462e-05, \"step\": 111, \"test_objective\": [{\"annualised_returns\": -0.47818205508454037, \"annualised_returns_over_hodl\": 0.012268626878089384, \"annualised_returns_over_uniform_hodl\": 0.8417860729605329, \"calmar\": -0.824894953748506, \"daily_log_sharpe\": -0.6806359418473106, \"daily_returns\": 0.03101604275384484, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": 0.009165925712046828, \"return\": -0.23045464527409198, \"returns_over_hodl\": 0.004923054451435593, \"returns_over_uniform_hodl\": 0.2788566081243329, \"sharpe\": -0.18985946970852705, \"sterling\": -1.2311018274501322, \"ulcer\": -0.17641031651874528}], \"train_objective\": [{\"annualised_returns\": -0.4898144565151712, \"annualised_returns_over_hodl\": -0.3575038687486163, \"annualised_returns_over_uniform_hodl\": -0.3575038687486163, \"calmar\": -0.652731646755769, \"daily_log_sharpe\": -0.4803900284933118, \"daily_returns\": 0.008152396830728985, \"fee_revenue_over_value\": 0.0019303111734200445, \"jax_sharpe\": 0.11079691828969121, \"return\": -0.3354073887386382, \"returns_over_hodl\": -0.23554120394233058, \"returns_over_uniform_hodl\": -0.23554120394233058, \"sharpe\": 0.11690237265353788, \"sterling\": -1.4364875887006558, \"ulcer\": -0.18513027121942052}], \"train_return\": -0.3354073887386382, \"train_returns_over_hodl\": -0.23554120394233058, \"train_sharpe\": 0.1107969182896912, \"validation_return\": -0.3391427223075243, \"validation_returns_over_hodl\": -6.352929293740317e-11, \"validation_sharpe\": -2.2438532050192674}, {\"centeredness_margin\": 0.28381840151813664, \"continuous_test_metrics\": [{\"annualised_returns\": -0.4822775702501364, \"annualised_returns_over_hodl\": 0.004323784107052919, \"annualised_returns_over_uniform_hodl\": 0.8273307195808872, \"calmar\": -0.8319599942911159, \"daily_log_sharpe\": -0.6933433758482507, \"daily_returns\": 0.03101604275451982, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": -0.009208021112335056, \"return\": -0.2328928330583614, \"returns_over_hodl\": 0.0017391081005271314, \"returns_over_uniform_hodl\": 0.2748047448512818, \"sharpe\": -0.2050532803537591, \"sterling\": -1.2416459388818457, \"ulcer\": -0.17641031652013178}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -0.014798194828526068, \"optuna_trial_number\": 112, \"price_ratio\": 6.385962622955651, \"shift_exponent\": 0.00012826026449260208, \"step\": 112, \"test_objective\": [{\"annualised_returns\": -0.4822775702501364, \"annualised_returns_over_hodl\": 0.004323784107052919, \"annualised_returns_over_uniform_hodl\": 0.8273307195808872, \"calmar\": -0.8319599942911159, \"daily_log_sharpe\": -0.6933433758482507, \"daily_returns\": 0.03101604275451982, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": -0.009208021112335056, \"return\": -0.2328928330583614, \"returns_over_hodl\": 0.0017391081005271314, \"returns_over_uniform_hodl\": 0.2748047448512818, \"sharpe\": -0.2050532803537591, \"sterling\": -1.2416459388818457, \"ulcer\": -0.17641031652013178}], \"train_objective\": [{\"annualised_returns\": -0.46811258009487666, \"annualised_returns_over_hodl\": -0.33017386730307485, \"annualised_returns_over_uniform_hodl\": -0.3301738673030745, \"calmar\": -0.6255123834694593, \"daily_log_sharpe\": -0.46101962637093086, \"daily_returns\": 0.008102988541506375, \"fee_revenue_over_value\": 0.002272878686627201, \"jax_sharpe\": 0.15538184932441387, \"return\": -0.31838476677454874, \"returns_over_hodl\": -0.21596064756552347, \"returns_over_uniform_hodl\": -0.21596064756552324, \"sharpe\": 0.11980990391806971, \"sterling\": -1.3959437297979576, \"ulcer\": -0.18174951218405966}], \"train_return\": -0.31838476677454874, \"train_returns_over_hodl\": -0.21596064756552347, \"train_sharpe\": 0.15538184932441385, \"validation_return\": -0.33873122982966575, \"validation_returns_over_hodl\": -0.014798194828526068, \"validation_sharpe\": -2.2401192332113813}, {\"centeredness_margin\": 0.46679561532141317, \"continuous_test_metrics\": [{\"annualised_returns\": -0.4845064531401724, \"annualised_returns_over_hodl\": 8.815170815523743e-13, \"annualised_returns_over_uniform_hodl\": 0.8194637508322486, \"calmar\": -0.8358049680619295, \"daily_log_sharpe\": -0.6924635969456628, \"daily_returns\": 0.031016042750485754, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": 0.005019283966667013, \"return\": -0.2342245993115073, \"returns_over_hodl\": 3.5504932327512506e-13, \"returns_over_uniform_hodl\": 0.2725915704583062, \"sharpe\": -0.20210852973480226, \"sterling\": -1.2473843111211937, \"ulcer\": -0.17641031651180272}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -1.0125111860048719e-10, \"optuna_trial_number\": 113, \"price_ratio\": 2.82688205174573, \"shift_exponent\": 1.3562592091987633e-05, \"step\": 113, \"test_objective\": [{\"annualised_returns\": -0.4845064531401724, \"annualised_returns_over_hodl\": 8.815170815523743e-13, \"annualised_returns_over_uniform_hodl\": 0.8194637508322486, \"calmar\": -0.8358049680619295, \"daily_log_sharpe\": -0.6924635969456628, \"daily_returns\": 0.031016042750485754, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": 0.005019283966667013, \"return\": -0.2342245993115073, \"returns_over_hodl\": 3.5504932327512506e-13, \"returns_over_uniform_hodl\": 0.2725915704583062, \"sharpe\": -0.20210852973480226, \"sterling\": -1.2473843111211937, \"ulcer\": -0.17641031651180272}], \"train_objective\": [{\"annualised_returns\": -0.5803955948854672, \"annualised_returns_over_hodl\": -0.47157615423467525, \"annualised_returns_over_uniform_hodl\": -0.471576154234675, \"calmar\": -0.7527560032752991, \"daily_log_sharpe\": -0.5918983716110561, \"daily_returns\": 0.008265036269243568, \"fee_revenue_over_value\": 0.0007138497240757394, \"jax_sharpe\": 0.11050022648090495, \"return\": -0.40977429172074264, \"returns_over_hodl\": -0.32108298120094025, \"returns_over_uniform_hodl\": -0.32108298120094003, \"sharpe\": 0.06282584732115917, \"sterling\": -1.592870949231029, \"ulcer\": -0.2015067047044064}], \"train_return\": -0.40977429172074264, \"train_returns_over_hodl\": -0.32108298120094025, \"train_sharpe\": 0.11050022648090495, \"validation_return\": -0.3391427222944239, \"validation_returns_over_hodl\": -1.0125111860048719e-10, \"validation_sharpe\": -2.2438532051215954}, {\"centeredness_margin\": 0.4096955932744876, \"continuous_test_metrics\": [{\"annualised_returns\": -0.48180355080405257, \"annualised_returns_over_hodl\": 0.005243329030366839, \"annualised_returns_over_uniform_hodl\": 0.8290037981375291, \"calmar\": -0.8311422786832507, \"daily_log_sharpe\": -0.6869896412521275, \"daily_returns\": 0.031016042753196207, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": 0.007564749016106121, \"return\": -0.23261004638927352, \"returns_over_hodl\": 0.002108389596799265, \"returns_over_uniform_hodl\": 0.27527468934283306, \"sharpe\": -0.1963384274154892, \"sterling\": -1.2404255517857814, \"ulcer\": -0.17641031651740513}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -7.155520620472089e-11, \"optuna_trial_number\": 114, \"price_ratio\": 4.554366048410576, \"shift_exponent\": 1.0312757835141919e-05, \"step\": 114, \"test_objective\": [{\"annualised_returns\": -0.48180355080405257, \"annualised_returns_over_hodl\": 0.005243329030366839, \"annualised_returns_over_uniform_hodl\": 0.8290037981375291, \"calmar\": -0.8311422786832507, \"daily_log_sharpe\": -0.6869896412521275, \"daily_returns\": 0.031016042753196207, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": 0.007564749016106121, \"return\": -0.23261004638927352, \"returns_over_hodl\": 0.002108389596799265, \"returns_over_uniform_hodl\": 0.27527468934283306, \"sharpe\": -0.1963384274154892, \"sterling\": -1.2404255517857814, \"ulcer\": -0.17641031651740513}], \"train_objective\": [{\"annualised_returns\": -0.5093053121624187, \"annualised_returns_over_hodl\": -0.3820494473289079, \"annualised_returns_over_uniform_hodl\": -0.3820494473289079, \"calmar\": -0.676072968336728, \"daily_log_sharpe\": -0.5024471464207758, \"daily_returns\": 0.008120238532312066, \"fee_revenue_over_value\": 0.0018959476380906739, \"jax_sharpe\": 0.11183053866149037, \"return\": -0.35093982510812505, \"returns_over_hodl\": -0.2534076493491253, \"returns_over_uniform_hodl\": -0.2534076493491253, \"sharpe\": 0.1083794353862962, \"sterling\": -1.4725741268303771, \"ulcer\": -0.18823832330321455}], \"train_return\": -0.35093982510812505, \"train_returns_over_hodl\": -0.2534076493491253, \"train_sharpe\": 0.11183053866149037, \"validation_return\": -0.3391427223056618, \"validation_returns_over_hodl\": -7.155520620472089e-11, \"validation_sharpe\": -2.243853205045905}, {\"centeredness_margin\": 0.19352226779598064, \"continuous_test_metrics\": [{\"annualised_returns\": -0.48450645311663754, \"annualised_returns_over_hodl\": 9.179323967600794e-13, \"annualised_returns_over_uniform_hodl\": 0.819463750915316, \"calmar\": -0.8358049680242307, \"daily_log_sharpe\": -0.6924635968865948, \"daily_returns\": 0.03101604275159984, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": 0.005019284037367544, \"return\": -0.234224599297427, \"returns_over_hodl\": 3.6970426720017713e-13, \"returns_over_uniform_hodl\": 0.27259157048170524, \"sharpe\": -0.20210852966552684, \"sterling\": -1.2473843110423575, \"ulcer\": -0.17641031651410982}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -9.047695925801236e-11, \"optuna_trial_number\": 115, \"price_ratio\": 3.3076625085582325, \"shift_exponent\": 1.901393686447879e-05, \"step\": 115, \"test_objective\": [{\"annualised_returns\": -0.48450645311663754, \"annualised_returns_over_hodl\": 9.179323967600794e-13, \"annualised_returns_over_uniform_hodl\": 0.819463750915316, \"calmar\": -0.8358049680242307, \"daily_log_sharpe\": -0.6924635968865948, \"daily_returns\": 0.03101604275159984, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": 0.005019284037367544, \"return\": -0.234224599297427, \"returns_over_hodl\": 3.6970426720017713e-13, \"returns_over_uniform_hodl\": 0.27259157048170524, \"sharpe\": -0.20210852966552684, \"sterling\": -1.2473843110423575, \"ulcer\": -0.17641031651410982}], \"train_objective\": [{\"annualised_returns\": -0.5524840506211899, \"annualised_returns_over_hodl\": -0.4364260810190407, \"annualised_returns_over_uniform_hodl\": -0.4364260810190408, \"calmar\": -0.7232623715174222, \"daily_log_sharpe\": -0.5536127024865227, \"daily_returns\": 0.008195854498424153, \"fee_revenue_over_value\": 0.0019992377019817523, \"jax_sharpe\": 0.11820965346392824, \"return\": -0.3862402788416892, \"returns_over_hodl\": -0.2940125882307544, \"returns_over_uniform_hodl\": -0.2940125882307545, \"sharpe\": 0.08645688558930656, \"sterling\": -1.5375010387130885, \"ulcer\": -0.19720950206217674}], \"train_return\": -0.3862402788416892, \"train_returns_over_hodl\": -0.2940125882307544, \"train_sharpe\": 0.11820965346392823, \"validation_return\": -0.339142722299986, \"validation_returns_over_hodl\": -9.047695925801236e-11, \"validation_sharpe\": -2.2438532050997697}, {\"centeredness_margin\": 0.40395077437129423, \"continuous_test_metrics\": [{\"annualised_returns\": -0.4845064531194301, \"annualised_returns_over_hodl\": 8.844036614163997e-13, \"annualised_returns_over_uniform_hodl\": 0.8194637509054594, \"calmar\": -0.8358049680287017, \"daily_log_sharpe\": -0.6924635968935862, \"daily_returns\": 0.031016042751470237, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": 0.0050192840289988, \"return\": -0.23422459929909767, \"returns_over_hodl\": 3.561595462997502e-13, \"returns_over_uniform_hodl\": 0.2725915704789288, \"sharpe\": -0.20210852967372558, \"sterling\": -1.2473843110516996, \"ulcer\": -0.17641031651383862}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -9.056111416327894e-11, \"optuna_trial_number\": 116, \"price_ratio\": 3.2281146048486886, \"shift_exponent\": 1.2326101190184086e-05, \"step\": 116, \"test_objective\": [{\"annualised_returns\": -0.4845064531194301, \"annualised_returns_over_hodl\": 8.844036614163997e-13, \"annualised_returns_over_uniform_hodl\": 0.8194637509054594, \"calmar\": -0.8358049680287017, \"daily_log_sharpe\": -0.6924635968935862, \"daily_returns\": 0.031016042751470237, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": 0.0050192840289988, \"return\": -0.23422459929909767, \"returns_over_hodl\": 3.561595462997502e-13, \"returns_over_uniform_hodl\": 0.2725915704789288, \"sharpe\": -0.20210852967372558, \"sterling\": -1.2473843110516996, \"ulcer\": -0.17641031651383862}], \"train_objective\": [{\"annualised_returns\": -0.555696035029717, \"annualised_returns_over_hodl\": -0.4404710556022531, \"annualised_returns_over_uniform_hodl\": -0.44047105560225275, \"calmar\": -0.7266267559778811, \"daily_log_sharpe\": -0.5573904538204021, \"daily_returns\": 0.008233456402450599, \"fee_revenue_over_value\": 0.0018358807773628204, \"jax_sharpe\": 0.1189828292163786, \"return\": -0.38891853898775997, \"returns_over_hodl\": -0.297093301877132, \"returns_over_uniform_hodl\": -0.2970933018771318, \"sharpe\": 0.08500403981406063, \"sterling\": -1.5428566529571126, \"ulcer\": -0.1978251170468587}], \"train_return\": -0.38891853898775997, \"train_returns_over_hodl\": -0.297093301877132, \"train_sharpe\": 0.1189828292163786, \"validation_return\": -0.33914272229919196, \"validation_returns_over_hodl\": -9.056111416327894e-11, \"validation_sharpe\": -2.243853205100941}, {\"centeredness_margin\": 0.2093112959243744, \"continuous_test_metrics\": [{\"annualised_returns\": -0.4845064531255192, \"annualised_returns_over_hodl\": 8.610889778992714e-13, \"annualised_returns_over_uniform_hodl\": 0.8194637508839682, \"calmar\": -0.8358049680384518, \"daily_log_sharpe\": -0.6924635969088689, \"daily_returns\": 0.031016042751182183, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": 0.005019284010715448, \"return\": -0.23422459930274064, \"returns_over_hodl\": 3.468336728928989e-13, \"returns_over_uniform_hodl\": 0.27259157047287497, \"sharpe\": -0.2021085296916453, \"sterling\": -1.2473843110720892, \"ulcer\": -0.17641031651324218}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -9.316125648695106e-11, \"optuna_trial_number\": 117, \"price_ratio\": 3.0407836142463958, \"shift_exponent\": 6.876432916417435e-05, \"step\": 117, \"test_objective\": [{\"annualised_returns\": -0.4845064531255192, \"annualised_returns_over_hodl\": 8.610889778992714e-13, \"annualised_returns_over_uniform_hodl\": 0.8194637508839682, \"calmar\": -0.8358049680384518, \"daily_log_sharpe\": -0.6924635969088689, \"daily_returns\": 0.031016042751182183, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": 0.005019284010715448, \"return\": -0.23422459930274064, \"returns_over_hodl\": 3.468336728928989e-13, \"returns_over_uniform_hodl\": 0.27259157047287497, \"sharpe\": -0.2021085296916453, \"sterling\": -1.2473843110720892, \"ulcer\": -0.17641031651324218}], \"train_objective\": [{\"annualised_returns\": -0.5627198193979087, \"annualised_returns_over_hodl\": -0.44931637539019276, \"annualised_returns_over_uniform_hodl\": -0.44931637539019276, \"calmar\": -0.7337151302139144, \"daily_log_sharpe\": -0.5658453341401539, \"daily_returns\": 0.00824333537856574, \"fee_revenue_over_value\": 0.0019709702734216636, \"jax_sharpe\": 0.12058655559267872, \"return\": -0.3948018547324027, \"returns_over_hodl\": -0.3038606844732771, \"returns_over_uniform_hodl\": -0.3038606844732771, \"sharpe\": 0.0814416740228088, \"sterling\": -1.5547353728369344, \"ulcer\": -0.19917905991663637}], \"train_return\": -0.3948018547324027, \"train_returns_over_hodl\": -0.3038606844732771, \"train_sharpe\": 0.12058655559267872, \"validation_return\": -0.339142722297726, \"validation_returns_over_hodl\": -9.316125648695106e-11, \"validation_sharpe\": -2.243853205106255}, {\"centeredness_margin\": 0.27119271789859, \"continuous_test_metrics\": [{\"annualised_returns\": -0.4845064531219918, \"annualised_returns_over_hodl\": 8.562039965909207e-13, \"annualised_returns_over_uniform_hodl\": 0.8194637508964173, \"calmar\": -0.8358049680328007, \"daily_log_sharpe\": -0.6924635969000098, \"daily_returns\": 0.031016042751349733, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": 0.0050192840213194295, \"return\": -0.23422459930063033, \"returns_over_hodl\": 3.448352714485736e-13, \"returns_over_uniform_hodl\": 0.2725915704763817, \"sharpe\": -0.20210852968125742, \"sterling\": -1.2473843110602687, \"ulcer\": -0.17641031651358863}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -9.293388281150783e-11, \"optuna_trial_number\": 118, \"price_ratio\": 3.1271284063680835, \"shift_exponent\": 6.554348948011861e-05, \"step\": 118, \"test_objective\": [{\"annualised_returns\": -0.4845064531219918, \"annualised_returns_over_hodl\": 8.562039965909207e-13, \"annualised_returns_over_uniform_hodl\": 0.8194637508964173, \"calmar\": -0.8358049680328007, \"daily_log_sharpe\": -0.6924635969000098, \"daily_returns\": 0.031016042751349733, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": 0.0050192840213194295, \"return\": -0.23422459930063033, \"returns_over_hodl\": 3.448352714485736e-13, \"returns_over_uniform_hodl\": 0.2725915704763817, \"sharpe\": -0.20210852968125742, \"sterling\": -1.2473843110602687, \"ulcer\": -0.17641031651358863}], \"train_objective\": [{\"annualised_returns\": -0.5586514624927469, \"annualised_returns_over_hodl\": -0.44419293822993544, \"annualised_returns_over_uniform_hodl\": -0.44419293822993533, \"calmar\": -0.7295842715255816, \"daily_log_sharpe\": -0.5607307228547911, \"daily_returns\": 0.008256183047333802, \"fee_revenue_over_value\": 0.001940887622420436, \"jax_sharpe\": 0.12064160105568346, \"return\": -0.39138960088712704, \"returns_over_hodl\": -0.2999356822656063, \"returns_over_uniform_hodl\": -0.2999356822656062, \"sharpe\": 0.08397564133978888, \"sterling\": -1.5476371345115185, \"ulcer\": -0.19841932638974322}], \"train_return\": -0.39138960088712704, \"train_returns_over_hodl\": -0.2999356822656063, \"train_sharpe\": 0.12064160105568345, \"validation_return\": -0.3391427222988157, \"validation_returns_over_hodl\": -9.293388281150783e-11, \"validation_sharpe\": -2.243853205105704}, {\"centeredness_margin\": 0.34262872089413476, \"continuous_test_metrics\": [{\"annualised_returns\": -0.47627411955149057, \"annualised_returns_over_hodl\": 0.01596980905409362, \"annualised_returns_over_uniform_hodl\": 0.8485202397846467, \"calmar\": -0.8216036360568716, \"daily_log_sharpe\": -0.677816717819332, \"daily_returns\": 0.031016042754093306, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": 0.008921903127375092, \"return\": -0.22932269386092397, \"returns_over_hodl\": 0.006401231223558401, \"returns_over_uniform_hodl\": 0.2807377234295132, \"sharpe\": -0.18716186574462124, \"sterling\": -1.2261897508147648, \"ulcer\": -0.17641031651925873}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -6.083877845952657e-11, \"optuna_trial_number\": 119, \"price_ratio\": 5.656152085681313, \"shift_exponent\": 1.2804019229094669e-05, \"step\": 119, \"test_objective\": [{\"annualised_returns\": -0.47627411955149057, \"annualised_returns_over_hodl\": 0.01596980905409362, \"annualised_returns_over_uniform_hodl\": 0.8485202397846467, \"calmar\": -0.8216036360568716, \"daily_log_sharpe\": -0.677816717819332, \"daily_returns\": 0.031016042754093306, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": 0.008921903127375092, \"return\": -0.22932269386092397, \"returns_over_hodl\": 0.006401231223558401, \"returns_over_uniform_hodl\": 0.2807377234295132, \"sharpe\": -0.18716186574462124, \"sterling\": -1.2261897508147648, \"ulcer\": -0.17641031651925873}], \"train_objective\": [{\"annualised_returns\": -0.48149582938991975, \"annualised_returns_over_hodl\": -0.3470279040460692, \"annualised_returns_over_uniform_hodl\": -0.3470279040460692, \"calmar\": -0.6424437135596398, \"daily_log_sharpe\": -0.4717425875307182, \"daily_returns\": 0.008114842428280708, \"fee_revenue_over_value\": 0.0019378741084399417, \"jax_sharpe\": 0.10954468315571846, \"return\": -0.3288493836467561, \"returns_over_hodl\": -0.22799774861025102, \"returns_over_uniform_hodl\": -0.22799774861025102, \"sharpe\": 0.11951494664714288, \"sterling\": -1.4207203117464857, \"ulcer\": -0.18386527725730611}], \"train_return\": -0.3288493836467561, \"train_returns_over_hodl\": -0.22799774861025102, \"train_sharpe\": 0.10954468315571846, \"validation_return\": -0.33914272230784126, \"validation_returns_over_hodl\": -6.083877845952657e-11, \"validation_sharpe\": -2.243853205005135}, {\"centeredness_margin\": 0.30232337661418274, \"continuous_test_metrics\": [{\"annualised_returns\": -0.5095241560384218, \"annualised_returns_over_hodl\": -0.04853155410959842, \"annualised_returns_over_uniform_hodl\": 0.7311623475853262, \"calmar\": -0.8791298189001043, \"daily_log_sharpe\": -0.7554408594496558, \"daily_returns\": 0.031016042752365844, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": -0.0732038582841399, \"return\": -0.24941476859604172, \"returns_over_hodl\": -0.019836324476502187, \"returns_over_uniform_hodl\": 0.2473480312065186, \"sharpe\": -0.2725975351410814, \"sterling\": -1.3120772479354914, \"ulcer\": -0.17632761414211173}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -0.057422684310459604, \"optuna_trial_number\": 120, \"price_ratio\": 8.793083566422094, \"shift_exponent\": 0.00015476689473740973, \"step\": 120, \"test_objective\": [{\"annualised_returns\": -0.5095241560384218, \"annualised_returns_over_hodl\": -0.04853155410959842, \"annualised_returns_over_uniform_hodl\": 0.7311623475853262, \"calmar\": -0.8791298189001043, \"daily_log_sharpe\": -0.7554408594496558, \"daily_returns\": 0.031016042752365844, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": -0.0732038582841399, \"return\": -0.24941476859604172, \"returns_over_hodl\": -0.019836324476502187, \"returns_over_uniform_hodl\": 0.2473480312065186, \"sharpe\": -0.2725975351410814, \"sterling\": -1.3120772479354914, \"ulcer\": -0.17632761414211173}], \"train_objective\": [{\"annualised_returns\": -0.4380343783087719, \"annualised_returns_over_hodl\": -0.29229523955801884, \"annualised_returns_over_uniform_hodl\": -0.29229523955801884, \"calmar\": -0.5879542489213126, \"daily_log_sharpe\": -0.43452872008115423, \"daily_returns\": 0.0080252622921552, \"fee_revenue_over_value\": 0.0025529293851101393, \"jax_sharpe\": 0.14980980652680823, \"return\": -0.295236447964903, \"returns_over_hodl\": -0.18933390566661035, \"returns_over_uniform_hodl\": -0.18933390566661035, \"sharpe\": 0.1264397818762854, \"sterling\": -1.3326108062246091, \"ulcer\": -0.1778206355396753}], \"train_return\": -0.295236447964903, \"train_returns_over_hodl\": -0.18933390566661035, \"train_sharpe\": 0.14980980652680825, \"validation_return\": -0.3286994789925245, \"validation_returns_over_hodl\": -0.057422684310459604, \"validation_sharpe\": -2.154795509294128}, {\"centeredness_margin\": 0.25501285124486184, \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -Infinity, \"optuna_trial_number\": 121, \"price_ratio\": 1.4998478967305162, \"shift_exponent\": 26.925308742191618, \"step\": 121, \"test_objective\": -Infinity, \"train_objective\": -Infinity, \"train_return\": -Infinity, \"train_returns_over_hodl\": -Infinity, \"train_sharpe\": -Infinity, \"validation_return\": -Infinity, \"validation_returns_over_hodl\": -Infinity, \"validation_sharpe\": -Infinity}, {\"centeredness_margin\": 0.26559341379338147, \"continuous_test_metrics\": [{\"annualised_returns\": -0.5029831241258088, \"annualised_returns_over_hodl\": -0.035842681557566425, \"annualised_returns_over_uniform_hodl\": 0.754249291215433, \"calmar\": -0.8676784786244444, \"daily_log_sharpe\": -0.7405577666078687, \"daily_returns\": 0.0310160427516946, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": -0.05861416600602881, \"return\": -0.24539935541702573, \"returns_over_hodl\": -0.014592733237209221, \"returns_over_uniform_hodl\": 0.2540209812110876, \"sharpe\": -0.25661585265278736, \"sterling\": -1.294953443236328, \"ulcer\": -0.17641031652019953}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -0.05253873728838576, \"optuna_trial_number\": 122, \"price_ratio\": 8.25836621579745, \"shift_exponent\": 0.00015341029085431878, \"step\": 122, \"test_objective\": [{\"annualised_returns\": -0.5029831241258088, \"annualised_returns_over_hodl\": -0.035842681557566425, \"annualised_returns_over_uniform_hodl\": 0.754249291215433, \"calmar\": -0.8676784786244444, \"daily_log_sharpe\": -0.7405577666078687, \"daily_returns\": 0.0310160427516946, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": -0.05861416600602881, \"return\": -0.24539935541702573, \"returns_over_hodl\": -0.014592733237209221, \"returns_over_uniform_hodl\": 0.2540209812110876, \"sharpe\": -0.25661585265278736, \"sterling\": -1.294953443236328, \"ulcer\": -0.17641031652019953}], \"train_objective\": [{\"annualised_returns\": -0.4431386406574761, \"annualised_returns_over_hodl\": -0.2987232319890356, \"annualised_returns_over_uniform_hodl\": -0.2987232319890356, \"calmar\": -0.5944071178350872, \"daily_log_sharpe\": -0.4389140900905267, \"daily_returns\": 0.008027976127626624, \"fee_revenue_over_value\": 0.00277628262296183, \"jax_sharpe\": 0.15126889715047967, \"return\": -0.2991297641761558, \"returns_over_hodl\": -0.1938122579279714, \"returns_over_uniform_hodl\": -0.1938122579279714, \"sharpe\": 0.1254423381614958, \"sterling\": -1.3434045384312572, \"ulcer\": -0.1785109722542176}], \"train_return\": -0.2991297641761558, \"train_returns_over_hodl\": -0.1938122579279714, \"train_sharpe\": 0.15126889715047967, \"validation_return\": -0.3320206512276316, \"validation_returns_over_hodl\": -0.05253873728838576, \"validation_sharpe\": -2.1807642177381035}, {\"centeredness_margin\": 0.29975585232627877, \"continuous_test_metrics\": [{\"annualised_returns\": -0.5117929697383596, \"annualised_returns_over_hodl\": -0.052932799710102896, \"annualised_returns_over_uniform_hodl\": 0.7231544407752899, \"calmar\": -0.8831309473474726, \"daily_log_sharpe\": -0.7605555785825463, \"daily_returns\": 0.031016042752632103, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": -0.07835809206420351, \"return\": -0.2508150155682569, \"returns_over_hodl\": -0.02166485928490891, \"returns_over_uniform_hodl\": 0.2450210532286432, \"sharpe\": -0.2780574356493075, \"sterling\": -1.3182402591310869, \"ulcer\": -0.17623469780021525}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -0.06051318627902691, \"optuna_trial_number\": 123, \"price_ratio\": 9.791692761106408, \"shift_exponent\": 1.7295737975750024e-05, \"step\": 123, \"test_objective\": [{\"annualised_returns\": -0.5117929697383596, \"annualised_returns_over_hodl\": -0.052932799710102896, \"annualised_returns_over_uniform_hodl\": 0.7231544407752899, \"calmar\": -0.8831309473474726, \"daily_log_sharpe\": -0.7605555785825463, \"daily_returns\": 0.031016042752632103, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": -0.07835809206420351, \"return\": -0.2508150155682569, \"returns_over_hodl\": -0.02166485928490891, \"returns_over_uniform_hodl\": 0.2450210532286432, \"sharpe\": -0.2780574356493075, \"sterling\": -1.3182402591310869, \"ulcer\": -0.17623469780021525}], \"train_objective\": [{\"annualised_returns\": -0.4325446203709289, \"annualised_returns_over_hodl\": -0.28538177781529983, \"annualised_returns_over_uniform_hodl\": -0.28538177781529983, \"calmar\": -0.5812926897405902, \"daily_log_sharpe\": -0.4298151766583483, \"daily_returns\": 0.008056364533555006, \"fee_revenue_over_value\": 0.0026066078406238383, \"jax_sharpe\": 0.10738106361376298, \"return\": -0.2910645663802536, \"returns_over_hodl\": -0.18453512891306922, \"returns_over_uniform_hodl\": -0.18453512891306922, \"sharpe\": 0.12717610627660925, \"sterling\": -1.3211785768788868, \"ulcer\": -0.1770869851014312}], \"train_return\": -0.2910645663802536, \"train_returns_over_hodl\": -0.18453512891306922, \"train_sharpe\": 0.10738106361376298, \"validation_return\": -0.3253374908451092, \"validation_returns_over_hodl\": -0.06051318627902691, \"validation_sharpe\": -2.131141422710928}, {\"centeredness_margin\": 0.23480810736217542, \"continuous_test_metrics\": [{\"annualised_returns\": -0.48014051141269065, \"annualised_returns_over_hodl\": 0.008469439993868955, \"annualised_returns_over_uniform_hodl\": 0.8348735901208062, \"calmar\": -0.8282734232838638, \"daily_log_sharpe\": -0.6837398252710044, \"daily_returns\": 0.03101604275357576, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": 0.008565818899906805, \"return\": -0.23161914390356586, \"returns_over_hodl\": 0.0034023753790362044, \"returns_over_uniform_hodl\": 0.2769214047496271, \"sharpe\": -0.19299572766250736, \"sterling\": -1.2361439725190841, \"ulcer\": -0.17641031651818542}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -6.708744670902433e-11, \"optuna_trial_number\": 124, \"price_ratio\": 4.9215650039374745, \"shift_exponent\": 1.9291675293103576e-05, \"step\": 124, \"test_objective\": [{\"annualised_returns\": -0.48014051141269065, \"annualised_returns_over_hodl\": 0.008469439993868955, \"annualised_returns_over_uniform_hodl\": 0.8348735901208062, \"calmar\": -0.8282734232838638, \"daily_log_sharpe\": -0.6837398252710044, \"daily_returns\": 0.03101604275357576, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": 0.008565818899906805, \"return\": -0.23161914390356586, \"returns_over_hodl\": 0.0034023753790362044, \"returns_over_uniform_hodl\": 0.2769214047496271, \"sharpe\": -0.19299572766250736, \"sterling\": -1.2361439725190841, \"ulcer\": -0.17641031651818542}], \"train_objective\": [{\"annualised_returns\": -0.4978814014379308, \"annualised_returns_over_hodl\": -0.3676628804455935, \"annualised_returns_over_uniform_hodl\": -0.3676628804455935, \"calmar\": -0.6624001062613154, \"daily_log_sharpe\": -0.48899132191039724, \"daily_returns\": 0.008108089752223394, \"fee_revenue_over_value\": 0.0022693907329741827, \"jax_sharpe\": 0.11211312171665189, \"return\": -0.3418072241015756, \"returns_over_hodl\": -0.24290272189124806, \"returns_over_uniform_hodl\": -0.24290272189124806, \"sharpe\": 0.11450276677018448, \"sterling\": -1.4509015222394959, \"ulcer\": -0.18645954296732875}], \"train_return\": -0.3418072241015756, \"train_returns_over_hodl\": -0.24290272189124806, \"train_sharpe\": 0.11211312171665187, \"validation_return\": -0.3391427223068866, \"validation_returns_over_hodl\": -6.708744670902433e-11, \"validation_sharpe\": -2.2438532050318165}, {\"centeredness_margin\": 0.2845310633869889, \"continuous_test_metrics\": [{\"annualised_returns\": -0.4833864615177159, \"annualised_returns_over_hodl\": 0.002172658688823992, \"annualised_returns_over_uniform_hodl\": 0.8234168248730558, \"calmar\": -0.8338729067408397, \"daily_log_sharpe\": -0.6958355662437865, \"daily_returns\": 0.03101604275471637, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": -0.013188272453266937, \"return\": -0.23355497097268996, \"returns_over_hodl\": 0.0008744447590431914, \"returns_over_uniform_hodl\": 0.27370438157570787, \"sharpe\": -0.20781092617962255, \"sterling\": -1.2445008357370788, \"ulcer\": -0.1764103165205482}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -0.024076749630851824, \"optuna_trial_number\": 125, \"price_ratio\": 6.9539312969265845, \"shift_exponent\": 1.0896536601781505e-05, \"step\": 125, \"test_objective\": [{\"annualised_returns\": -0.4833864615177159, \"annualised_returns_over_hodl\": 0.002172658688823992, \"annualised_returns_over_uniform_hodl\": 0.8234168248730558, \"calmar\": -0.8338729067408397, \"daily_log_sharpe\": -0.6958355662437865, \"daily_returns\": 0.03101604275471637, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": -0.013188272453266937, \"return\": -0.23355497097268996, \"returns_over_hodl\": 0.0008744447590431914, \"returns_over_uniform_hodl\": 0.27370438157570787, \"sharpe\": -0.20781092617962255, \"sterling\": -1.2445008357370788, \"ulcer\": -0.1764103165205482}], \"train_objective\": [{\"annualised_returns\": -0.46233618887687666, \"annualised_returns_over_hodl\": -0.32289943732842397, \"annualised_returns_over_uniform_hodl\": -0.32289943732842397, \"calmar\": -0.6185772719456369, \"daily_log_sharpe\": -0.45627624136059214, \"daily_returns\": 0.00807676227832973, \"fee_revenue_over_value\": 0.00232707726583813, \"jax_sharpe\": 0.15295134698844878, \"return\": -0.31390011370598203, \"returns_over_hodl\": -0.21080210016755274, \"returns_over_uniform_hodl\": -0.21080210016755274, \"sharpe\": 0.11996607866689712, \"sterling\": -1.3849182320341655, \"ulcer\": -0.18087290087565858}], \"train_return\": -0.31390011370598203, \"train_returns_over_hodl\": -0.21080210016755274, \"train_sharpe\": 0.15295134698844878, \"validation_return\": -0.3380056650786656, \"validation_returns_over_hodl\": -0.024076749630851824, \"validation_sharpe\": -2.233458581695953}, {\"centeredness_margin\": 0.328279326231119, \"continuous_test_metrics\": [{\"annualised_returns\": -0.47985420442650273, \"annualised_returns_over_hodl\": 0.009024843619359624, \"annualised_returns_over_uniform_hodl\": 0.8358841268892154, \"calmar\": -0.8277795243779138, \"daily_log_sharpe\": -0.6831871883392776, \"daily_returns\": 0.031016042753635245, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": 0.008521221763059346, \"return\": -0.23144874236471635, \"returns_over_hodl\": 0.003624896940722211, \"returns_over_uniform_hodl\": 0.2772045838145831, \"sharpe\": -0.19243304788413562, \"sterling\": -1.2354068606675068, \"ulcer\": -0.17641031651831385}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -6.625489046285793e-11, \"optuna_trial_number\": 126, \"price_ratio\": 5.005502602173278, \"shift_exponent\": 1.3086179298321788e-05, \"step\": 126, \"test_objective\": [{\"annualised_returns\": -0.47985420442650273, \"annualised_returns_over_hodl\": 0.009024843619359624, \"annualised_returns_over_uniform_hodl\": 0.8358841268892154, \"calmar\": -0.8277795243779138, \"daily_log_sharpe\": -0.6831871883392776, \"daily_returns\": 0.031016042753635245, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": 0.008521221763059346, \"return\": -0.23144874236471635, \"returns_over_hodl\": 0.003624896940722211, \"returns_over_uniform_hodl\": 0.2772045838145831, \"sharpe\": -0.19243304788413562, \"sterling\": -1.2354068606675068, \"ulcer\": -0.17641031651831385}], \"train_objective\": [{\"annualised_returns\": -0.49616865022122514, \"annualised_returns_over_hodl\": -0.36550594745409193, \"annualised_returns_over_uniform_hodl\": -0.36550594745409215, \"calmar\": -0.6603324210713514, \"daily_log_sharpe\": -0.48735731861184617, \"daily_returns\": 0.00812226543760529, \"fee_revenue_over_value\": 0.0019413902172578411, \"jax_sharpe\": 0.1114124037322387, \"return\": -0.3404450708351413, \"returns_over_hodl\": -0.24133588225376235, \"returns_over_uniform_hodl\": -0.24133588225376246, \"sharpe\": 0.11456186026446523, \"sterling\": -1.4481024536478346, \"ulcer\": -0.1861436060874894}], \"train_return\": -0.3404450708351413, \"train_returns_over_hodl\": -0.24133588225376235, \"train_sharpe\": 0.11141240373223867, \"validation_return\": -0.33914272230701603, \"validation_returns_over_hodl\": -6.625489046285793e-11, \"validation_sharpe\": -2.2438532050285795}, {\"centeredness_margin\": 0.30148421136424064, \"continuous_test_metrics\": [{\"annualised_returns\": -0.4845064531210176, \"annualised_returns_over_hodl\": 8.546496843564455e-13, \"annualised_returns_over_uniform_hodl\": 0.8194637508998572, \"calmar\": -0.8358049680312396, \"daily_log_sharpe\": -0.6924635968975614, \"daily_returns\": 0.031016042751395932, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": 0.005019284024247835, \"return\": -0.23422459930004746, \"returns_over_hodl\": 3.4416913763379853e-13, \"returns_over_uniform_hodl\": 0.2725915704773507, \"sharpe\": -0.20210852967838722, \"sterling\": -1.2473843110570035, \"ulcer\": -0.17641031651368438}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -9.152545388246836e-11, \"optuna_trial_number\": 127, \"price_ratio\": 3.1528727443883384, \"shift_exponent\": 2.486323925767617e-05, \"step\": 127, \"test_objective\": [{\"annualised_returns\": -0.4845064531210176, \"annualised_returns_over_hodl\": 8.546496843564455e-13, \"annualised_returns_over_uniform_hodl\": 0.8194637508998572, \"calmar\": -0.8358049680312396, \"daily_log_sharpe\": -0.6924635968975614, \"daily_returns\": 0.031016042751395932, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": 0.005019284024247835, \"return\": -0.23422459930004746, \"returns_over_hodl\": 3.4416913763379853e-13, \"returns_over_uniform_hodl\": 0.2725915704773507, \"sharpe\": -0.20210852967838722, \"sterling\": -1.2473843110570035, \"ulcer\": -0.17641031651368438}], \"train_objective\": [{\"annualised_returns\": -0.5582949831397405, \"annualised_returns_over_hodl\": -0.4437440101720894, \"annualised_returns_over_uniform_hodl\": -0.4437440101720894, \"calmar\": -0.7292786651118554, \"daily_log_sharpe\": -0.560320740836043, \"daily_returns\": 0.008271996402813129, \"fee_revenue_over_value\": 0.0019328073947649808, \"jax_sharpe\": 0.11987288972943587, \"return\": -0.3910912009677192, \"returns_over_hodl\": -0.299592442754259, \"returns_over_uniform_hodl\": -0.299592442754259, \"sharpe\": 0.08410991414914885, \"sterling\": -1.54686887711256, \"ulcer\": -0.19836769005763755}], \"train_return\": -0.3910912009677192, \"train_returns_over_hodl\": -0.299592442754259, \"train_sharpe\": 0.11987288972943586, \"validation_return\": -0.33914272229900555, \"validation_returns_over_hodl\": -9.152545388246836e-11, \"validation_sharpe\": -2.2438532051043265}, {\"centeredness_margin\": 0.4224489077794831, \"continuous_test_metrics\": [{\"annualised_returns\": -0.7355722827433993, \"annualised_returns_over_hodl\": -0.2164458767629982, \"annualised_returns_over_uniform_hodl\": -0.06668735390682079, \"calmar\": -1.2398510609190372, \"daily_log_sharpe\": -1.6615038411889012, \"daily_returns\": 0.021614584514748746, \"fee_revenue_over_value\": 0.010868837694378047, \"jax_sharpe\": -1.085543914235636, \"return\": -0.41475034165333746, \"returns_over_hodl\": -0.09356309393234774, \"returns_over_uniform_hodl\": -0.02741223972989104, \"sharpe\": -1.274140840698573, \"sterling\": -2.15910564558881, \"ulcer\": -0.1491251343871423}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -0.036197355266607806, \"optuna_trial_number\": 128, \"price_ratio\": 5.0236400423050025, \"shift_exponent\": 0.030868915333967, \"step\": 128, \"test_objective\": [{\"annualised_returns\": -0.7355722827433993, \"annualised_returns_over_hodl\": -0.2164458767629982, \"annualised_returns_over_uniform_hodl\": -0.06668735390682079, \"calmar\": -1.2398510609190372, \"daily_log_sharpe\": -1.6615038411889012, \"daily_returns\": 0.021614584514748746, \"fee_revenue_over_value\": 0.010868837694378047, \"jax_sharpe\": -1.085543914235636, \"return\": -0.41475034165333746, \"returns_over_hodl\": -0.09356309393234774, \"returns_over_uniform_hodl\": -0.02741223972989104, \"sharpe\": -1.274140840698573, \"sterling\": -2.15910564558881, \"ulcer\": -0.1491251343871423}], \"train_objective\": [{\"annualised_returns\": -0.37686085963035754, \"annualised_returns_over_hodl\": -0.21525709218627898, \"annualised_returns_over_uniform_hodl\": -0.21525709218627898, \"calmar\": -0.50574798336357, \"daily_log_sharpe\": -0.39913167835017915, \"daily_returns\": 0.00812781687524823, \"fee_revenue_over_value\": 0.005122713416762821, \"jax_sharpe\": 0.10766596415799679, \"return\": -0.24960802752708067, \"returns_over_hodl\": -0.13684904988750624, \"returns_over_uniform_hodl\": -0.13684904988750624, \"sharpe\": 0.10809168941475675, \"sterling\": -1.203694460987232, \"ulcer\": -0.16923528463558185}], \"train_return\": -0.24960802752708067, \"train_returns_over_hodl\": -0.13684904988750624, \"train_sharpe\": 0.10766596415799679, \"validation_return\": -0.20690923979478482, \"validation_returns_over_hodl\": -0.036197355266607834, \"validation_sharpe\": -1.6340080470873446}, {\"centeredness_margin\": 0.9509961415348335, \"continuous_test_metrics\": [{\"annualised_returns\": -0.4779721222306802, \"annualised_returns_over_hodl\": 0.012675873205791222, \"annualised_returns_over_uniform_hodl\": 0.8425270428912595, \"calmar\": -0.8245328053900033, \"daily_log_sharpe\": -0.680285606286451, \"daily_returns\": 0.031016042753841306, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": 0.009338257268017599, \"return\": -0.23032997415150847, \"returns_over_hodl\": 0.005085858221983486, \"returns_over_uniform_hodl\": 0.27906379083030197, \"sharpe\": -0.18949275231264734, \"sterling\": -1.2305613446845791, \"ulcer\": -0.17641031651873773}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -6.32585095416971e-11, \"optuna_trial_number\": 129, \"price_ratio\": 5.306421240776586, \"shift_exponent\": 1.0680506481852595e-05, \"step\": 129, \"test_objective\": [{\"annualised_returns\": -0.4779721222306802, \"annualised_returns_over_hodl\": 0.012675873205791222, \"annualised_returns_over_uniform_hodl\": 0.8425270428912595, \"calmar\": -0.8245328053900033, \"daily_log_sharpe\": -0.680285606286451, \"daily_returns\": 0.031016042753841306, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": 0.009338257268017599, \"return\": -0.23032997415150847, \"returns_over_hodl\": 0.005085858221983486, \"returns_over_uniform_hodl\": 0.27906379083030197, \"sharpe\": -0.18949275231264734, \"sterling\": -1.2305613446845791, \"ulcer\": -0.17641031651873773}], \"train_objective\": [{\"annualised_returns\": -0.4910181300711296, \"annualised_returns_over_hodl\": -0.35901970080789114, \"annualised_returns_over_uniform_hodl\": -0.35901970080789114, \"calmar\": -0.6539587733127781, \"daily_log_sharpe\": -0.4827841332770432, \"daily_returns\": 0.00812360610847772, \"fee_revenue_over_value\": 3.102382840129516e-05, \"jax_sharpe\": 0.16170016842049278, \"return\": -0.3363597748663969, \"returns_over_hodl\": -0.23663670205692233, \"returns_over_uniform_hodl\": -0.23663670205692233, \"sharpe\": 0.11404357655632372, \"sterling\": -1.4399591630251714, \"ulcer\": -0.18518739607267656}], \"train_return\": -0.3363597748663969, \"train_returns_over_hodl\": -0.23663670205692233, \"train_sharpe\": 0.16170016842049278, \"validation_return\": -0.33914272230730325, \"validation_returns_over_hodl\": -6.32585095416971e-11, \"validation_sharpe\": -2.2438532050173685}, {\"centeredness_margin\": 0.44766132542405435, \"continuous_test_metrics\": [{\"annualised_returns\": -0.5282627550263925, \"annualised_returns_over_hodl\": -0.08488234670812611, \"annualised_returns_over_uniform_hodl\": 0.6650233982082063, \"calmar\": -0.911657740805312, \"daily_log_sharpe\": -0.8013009247971162, \"daily_returns\": 0.03101604275487409, \"fee_revenue_over_value\": 3.8709937721565315e-08, \"jax_sharpe\": -0.12251098929498555, \"return\": -0.2610982158402064, \"returns_over_hodl\": -0.03509334010679066, \"returns_over_uniform_hodl\": 0.22793208174738155, \"sharpe\": -0.32225340715983436, \"sterling\": -1.3610055774677106, \"ulcer\": -0.17613834388769176}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -0.03998714039461457, \"optuna_trial_number\": 130, \"price_ratio\": 4.466346178564167, \"shift_exponent\": 0.0014053298668556583, \"step\": 130, \"test_objective\": [{\"annualised_returns\": -0.5282627550263925, \"annualised_returns_over_hodl\": -0.08488234670812611, \"annualised_returns_over_uniform_hodl\": 0.6650233982082063, \"calmar\": -0.911657740805312, \"daily_log_sharpe\": -0.8013009247971162, \"daily_returns\": 0.03101604275487409, \"fee_revenue_over_value\": 3.8709937721565315e-08, \"jax_sharpe\": -0.12251098929498555, \"return\": -0.2610982158402064, \"returns_over_hodl\": -0.03509334010679066, \"returns_over_uniform_hodl\": 0.22793208174738155, \"sharpe\": -0.32225340715983436, \"sterling\": -1.3610055774677106, \"ulcer\": -0.17613834388769176}], \"train_objective\": [{\"annualised_returns\": -0.459509997411614, \"annualised_returns_over_hodl\": -0.3193403065263164, \"annualised_returns_over_uniform_hodl\": -0.3193403065263164, \"calmar\": -0.6109173468889452, \"daily_log_sharpe\": -0.4440019415528113, \"daily_returns\": 0.008159815355423154, \"fee_revenue_over_value\": 0.001889135881115175, \"jax_sharpe\": 0.1976926573977311, \"return\": -0.3117128251852015, \"returns_over_hodl\": -0.20828613486656467, \"returns_over_uniform_hodl\": -0.20828613486656467, \"sharpe\": 0.14601490422442884, \"sterling\": -1.362785288697802, \"ulcer\": -0.18224765836287274}], \"train_return\": -0.3117128251852015, \"train_returns_over_hodl\": -0.20828613486656467, \"train_sharpe\": 0.19769265739773112, \"validation_return\": -0.33628362102172127, \"validation_returns_over_hodl\": -0.039987140394614595, \"validation_sharpe\": -2.2182877203932003}, {\"centeredness_margin\": 0.4145259195934116, \"continuous_test_metrics\": [{\"annualised_returns\": -0.6818920184805002, \"annualised_returns_over_hodl\": -0.3412172271016133, \"annualised_returns_over_uniform_hodl\": 0.12278018755204179, \"calmar\": -1.1630026145226404, \"daily_log_sharpe\": -1.2999750443704543, \"daily_returns\": 0.02955727554108051, \"fee_revenue_over_value\": 0.008241554034178093, \"jax_sharpe\": -0.6569750471209879, \"return\": -0.36952486492959835, \"returns_over_hodl\": -0.15471990569543292, \"returns_over_uniform_hodl\": 0.047744993574865724, \"sharpe\": -0.8682869663894927, \"sterling\": -1.8502802090180792, \"ulcer\": -0.16134101414436747}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -0.0597684047678686, \"optuna_trial_number\": 131, \"price_ratio\": 4.392944777391983, \"shift_exponent\": 0.0038373379153291043, \"step\": 131, \"test_objective\": [{\"annualised_returns\": -0.6818920184805002, \"annualised_returns_over_hodl\": -0.3412172271016133, \"annualised_returns_over_uniform_hodl\": 0.12278018755204179, \"calmar\": -1.1630026145226404, \"daily_log_sharpe\": -1.2999750443704543, \"daily_returns\": 0.02955727554108051, \"fee_revenue_over_value\": 0.008241554034178093, \"jax_sharpe\": -0.6569750471209879, \"return\": -0.36952486492959835, \"returns_over_hodl\": -0.15471990569543292, \"returns_over_uniform_hodl\": 0.047744993574865724, \"sharpe\": -0.8682869663894927, \"sterling\": -1.8502802090180792, \"ulcer\": -0.16134101414436747}], \"train_objective\": [{\"annualised_returns\": -0.4217860222513313, \"annualised_returns_over_hodl\": -0.27183306449364264, \"annualised_returns_over_uniform_hodl\": -0.27183306449364264, \"calmar\": -0.5621760218738334, \"daily_log_sharpe\": -0.41698819826453415, \"daily_returns\": 0.008164518900819422, \"fee_revenue_over_value\": 0.002669724353500267, \"jax_sharpe\": 0.1959169785801389, \"return\": -0.2829343927326513, \"returns_over_hodl\": -0.17518326033512976, \"returns_over_uniform_hodl\": -0.17518326033512976, \"sharpe\": 0.14747214638120926, \"sterling\": -1.2830021781471197, \"ulcer\": -0.17700043584019115}], \"train_return\": -0.2829343927326513, \"train_returns_over_hodl\": -0.17518326033512976, \"train_sharpe\": 0.19591697858013887, \"validation_return\": -0.2769019512453904, \"validation_returns_over_hodl\": -0.0597684047678686, \"validation_sharpe\": -1.9555791485775595}, {\"centeredness_margin\": 0.44060519100002926, \"continuous_test_metrics\": [{\"annualised_returns\": -0.5466490888474071, \"annualised_returns_over_hodl\": -0.12054978407257178, \"annualised_returns_over_uniform_hodl\": 0.6001277887445748, \"calmar\": -0.946648148584437, \"daily_log_sharpe\": -0.8465366369322189, \"daily_returns\": 0.031016042752205864, \"fee_revenue_over_value\": 7.474627960944941e-08, \"jax_sharpe\": -0.16777454346226542, \"return\": -0.2728346512799582, \"returns_over_hodl\": -0.05041955117766306, \"returns_over_uniform_hodl\": 0.2084280747050713, \"sharpe\": -0.3709754315800331, \"sterling\": -1.4127251339849374, \"ulcer\": -0.17498003285044766}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -0.059956593800657626, \"optuna_trial_number\": 132, \"price_ratio\": 5.259339788355749, \"shift_exponent\": 0.0013984592927526152, \"step\": 132, \"test_objective\": [{\"annualised_returns\": -0.5466490888474071, \"annualised_returns_over_hodl\": -0.12054978407257178, \"annualised_returns_over_uniform_hodl\": 0.6001277887445748, \"calmar\": -0.946648148584437, \"daily_log_sharpe\": -0.8465366369322189, \"daily_returns\": 0.031016042752205864, \"fee_revenue_over_value\": 7.474627960944941e-08, \"jax_sharpe\": -0.16777454346226542, \"return\": -0.2728346512799582, \"returns_over_hodl\": -0.05041955117766306, \"returns_over_uniform_hodl\": 0.2084280747050713, \"sharpe\": -0.3709754315800331, \"sterling\": -1.4127251339849374, \"ulcer\": -0.17498003285044766}], \"train_objective\": [{\"annualised_returns\": -0.4421639872043218, \"annualised_returns_over_hodl\": -0.29749581368806777, \"annualised_returns_over_uniform_hodl\": -0.29749581368806777, \"calmar\": -0.5899983487730981, \"daily_log_sharpe\": -0.43058714389316577, \"daily_returns\": 0.008136637293140311, \"fee_revenue_over_value\": 0.0019457717743062057, \"jax_sharpe\": 0.18776198580539677, \"return\": -0.2983852601228316, \"returns_over_hodl\": -0.1929558796555978, \"returns_over_uniform_hodl\": -0.1929558796555978, \"sharpe\": 0.14536986235973362, \"sterling\": -1.3289477422084381, \"ulcer\": -0.17970039316263603}], \"train_return\": -0.2983852601228316, \"train_returns_over_hodl\": -0.1929558796555978, \"train_sharpe\": 0.18776198580539674, \"validation_return\": -0.3290152364671928, \"validation_returns_over_hodl\": -0.059956593800657654, \"validation_sharpe\": -2.1595327494513916}, {\"centeredness_margin\": 0.9745408964509071, \"continuous_test_metrics\": [{\"annualised_returns\": -0.568384242588128, \"annualised_returns_over_hodl\": -0.14139693913901563, \"annualised_returns_over_uniform_hodl\": 0.5234123291798387, \"calmar\": -0.9826175947368037, \"daily_log_sharpe\": -0.9081673397766153, \"daily_returns\": 0.030484252422576068, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": -0.23399276174797645, \"return\": -0.2870815118908171, \"returns_over_hodl\": -0.05955000533462884, \"returns_over_uniform_hodl\": 0.18475215784671728, \"sharpe\": -0.44068118565994646, \"sterling\": -1.4866826343347315, \"ulcer\": -0.17119287170519693}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -0.05964851998372489, \"optuna_trial_number\": 133, \"price_ratio\": 16.324172572198087, \"shift_exponent\": 8.800388158838882e-05, \"step\": 133, \"test_objective\": [{\"annualised_returns\": -0.568384242588128, \"annualised_returns_over_hodl\": -0.14139693913901563, \"annualised_returns_over_uniform_hodl\": 0.5234123291798387, \"calmar\": -0.9826175947368037, \"daily_log_sharpe\": -0.9081673397766153, \"daily_returns\": 0.030484252422576068, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": -0.23399276174797645, \"return\": -0.2870815118908171, \"returns_over_hodl\": -0.05955000533462884, \"returns_over_uniform_hodl\": 0.18475215784671728, \"sharpe\": -0.44068118565994646, \"sterling\": -1.4866826343347315, \"ulcer\": -0.17119287170519693}], \"train_objective\": [{\"annualised_returns\": -0.40512768533782995, \"annualised_returns_over_hodl\": -0.2508545848862034, \"annualised_returns_over_uniform_hodl\": -0.2508545848862034, \"calmar\": -0.5464468759821995, \"daily_log_sharpe\": -0.4077067454031675, \"daily_returns\": 0.008013674622096845, \"fee_revenue_over_value\": 2.3015371160810032e-05, \"jax_sharpe\": 0.10675517702904849, \"return\": -0.2704621329214406, \"returns_over_hodl\": -0.16083683433243845, \"returns_over_uniform_hodl\": -0.16083683433243845, \"sharpe\": 0.1301238507500889, \"sterling\": -1.2626295422271052, \"ulcer\": -0.17332362241465213}], \"train_return\": -0.2704621329214406, \"train_returns_over_hodl\": -0.16083683433243845, \"train_sharpe\": 0.10675517702904849, \"validation_return\": -0.2865342788248545, \"validation_returns_over_hodl\": -0.05964851998372489, \"validation_sharpe\": -1.9705602746010704}, {\"centeredness_margin\": 0.4493807104759316, \"continuous_test_metrics\": [{\"annualised_returns\": -0.482521180413919, \"annualised_returns_over_hodl\": 0.003851207613897456, \"annualised_returns_over_uniform_hodl\": 0.8264708836721018, \"calmar\": -0.8323802382975103, \"daily_log_sharpe\": -0.688397027564393, \"daily_returns\": 0.0310160427530454, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": 0.007153536395696364, \"return\": -0.2330382241908443, \"returns_over_hodl\": 0.0015492467994655534, \"returns_over_uniform_hodl\": 0.2745631289291055, \"sharpe\": -0.19778716231589544, \"sterling\": -1.2422731256362296, \"ulcer\": -0.17641031651709568}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -7.31990024149809e-11, \"optuna_trial_number\": 134, \"price_ratio\": 4.426655586620224, \"shift_exponent\": 1.0589618873360925e-05, \"step\": 134, \"test_objective\": [{\"annualised_returns\": -0.482521180413919, \"annualised_returns_over_hodl\": 0.003851207613897456, \"annualised_returns_over_uniform_hodl\": 0.8264708836721018, \"calmar\": -0.8323802382975103, \"daily_log_sharpe\": -0.688397027564393, \"daily_returns\": 0.0310160427530454, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": 0.007153536395696364, \"return\": -0.2330382241908443, \"returns_over_hodl\": 0.0015492467994655534, \"returns_over_uniform_hodl\": 0.2745631289291055, \"sharpe\": -0.19778716231589544, \"sterling\": -1.2422731256362296, \"ulcer\": -0.17641031651709568}], \"train_objective\": [{\"annualised_returns\": -0.5131307291331704, \"annualised_returns_over_hodl\": -0.386866940955527, \"annualised_returns_over_uniform_hodl\": -0.386866940955527, \"calmar\": -0.6805967164414579, \"daily_log_sharpe\": -0.5068365275312707, \"daily_returns\": 0.008166318486318259, \"fee_revenue_over_value\": 0.001870246503754744, \"jax_sharpe\": 0.11242713513556712, \"return\": -0.3540165942598723, \"returns_over_hodl\": -0.25694675466212, \"returns_over_uniform_hodl\": -0.25694675466212, \"sharpe\": 0.10664522748722756, \"sterling\": -1.4794645468217262, \"ulcer\": -0.18888524348294466}], \"train_return\": -0.3540165942598723, \"train_returns_over_hodl\": -0.25694675466212, \"train_sharpe\": 0.11242713513556712, \"validation_return\": -0.3391427223051029, \"validation_returns_over_hodl\": -7.31990024149809e-11, \"validation_sharpe\": -2.243853205050436}, {\"centeredness_margin\": 0.9363648730767274, \"continuous_test_metrics\": [{\"annualised_returns\": -0.48450645318132757, \"annualised_returns_over_hodl\": 1.1097789354153065e-12, \"annualised_returns_over_uniform_hodl\": 0.8194637506869888, \"calmar\": -0.8358049681278967, \"daily_log_sharpe\": -0.6924635970490487, \"daily_returns\": 0.031016042748526356, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": 0.00501928384283329, \"return\": -0.23422459933612938, \"returns_over_hodl\": 4.46975789714088e-13, \"returns_over_uniform_hodl\": 0.27259157041738824, \"sharpe\": -0.2021085298561053, \"sterling\": -1.247384311259199, \"ulcer\": -0.17641031650774747}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -1.1498202390214374e-10, \"optuna_trial_number\": 135, \"price_ratio\": 2.191177270563504, \"shift_exponent\": 1.6693431704219428e-05, \"step\": 135, \"test_objective\": [{\"annualised_returns\": -0.48450645318132757, \"annualised_returns_over_hodl\": 1.1097789354153065e-12, \"annualised_returns_over_uniform_hodl\": 0.8194637506869888, \"calmar\": -0.8358049681278967, \"daily_log_sharpe\": -0.6924635970490487, \"daily_returns\": 0.031016042748526356, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": 0.00501928384283329, \"return\": -0.23422459933612938, \"returns_over_hodl\": 4.46975789714088e-13, \"returns_over_uniform_hodl\": 0.27259157041738824, \"sharpe\": -0.2021085298561053, \"sterling\": -1.247384311259199, \"ulcer\": -0.17641031650774747}], \"train_objective\": [{\"annualised_returns\": -0.6226338744273998, \"annualised_returns_over_hodl\": -0.5247684321087993, \"annualised_returns_over_uniform_hodl\": -0.5247684321087995, \"calmar\": -0.7972727747219168, \"daily_log_sharpe\": -0.6620711988302035, \"daily_returns\": 0.008356087137908764, \"fee_revenue_over_value\": 2.172699988748444e-05, \"jax_sharpe\": 0.08720815139747412, \"return\": -0.44659419105784814, \"returns_over_hodl\": -0.3634356878685422, \"returns_over_uniform_hodl\": -0.3634356878685424, \"sharpe\": 0.005831808430561106, \"sterling\": -1.6849079084763046, \"ulcer\": -0.20603879996379523}], \"train_return\": -0.44659419105784814, \"train_returns_over_hodl\": -0.3634356878685422, \"train_sharpe\": 0.08720815139747412, \"validation_return\": -0.33914272228283815, \"validation_returns_over_hodl\": -1.1498202390214374e-10, \"validation_sharpe\": -2.243853205141145}, {\"centeredness_margin\": 0.8851286307288233, \"continuous_test_metrics\": [{\"annualised_returns\": -0.47642033799201844, \"annualised_returns_over_hodl\": 0.015686161587015413, \"annualised_returns_over_uniform_hodl\": 0.848004153494403, \"calmar\": -0.8218558727429398, \"daily_log_sharpe\": -0.677962894584919, \"daily_returns\": 0.031016042754017387, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": 0.009083417957878537, \"return\": -0.22940935598202006, \"returns_over_hodl\": 0.006288062114132575, \"returns_over_uniform_hodl\": 0.2805937053731933, \"sharpe\": -0.18724519544547588, \"sterling\": -1.2265661975914004, \"ulcer\": -0.17641031651909722}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -6.180611578088246e-11, \"optuna_trial_number\": 136, \"price_ratio\": 5.485415516599004, \"shift_exponent\": 4.6316843877585284e-05, \"step\": 136, \"test_objective\": [{\"annualised_returns\": -0.47642033799201844, \"annualised_returns_over_hodl\": 0.015686161587015413, \"annualised_returns_over_uniform_hodl\": 0.848004153494403, \"calmar\": -0.8218558727429398, \"daily_log_sharpe\": -0.677962894584919, \"daily_returns\": 0.031016042754017387, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": 0.009083417957878537, \"return\": -0.22940935598202006, \"returns_over_hodl\": 0.006288062114132575, \"returns_over_uniform_hodl\": 0.2805937053731933, \"sharpe\": -0.18724519544547588, \"sterling\": -1.2265661975914004, \"ulcer\": -0.17641031651909722}], \"train_objective\": [{\"annualised_returns\": -0.4851841624252373, \"annualised_returns_over_hodl\": -0.35167276263961467, \"annualised_returns_over_uniform_hodl\": -0.35167276263961467, \"calmar\": -0.6467885070995846, \"daily_log_sharpe\": -0.4761124785273027, \"daily_returns\": 0.008124472743857898, \"fee_revenue_over_value\": 4.4204264954386914e-05, \"jax_sharpe\": 0.10851771383516225, \"return\": -0.33175194793596985, \"returns_over_hodl\": -0.23133647185877004, \"returns_over_uniform_hodl\": -0.23133647185877004, \"sharpe\": 0.11728568943765831, \"sterling\": -1.4282481063418326, \"ulcer\": -0.18436332887386864}], \"train_return\": -0.33175194793596985, \"train_returns_over_hodl\": -0.23133647185877004, \"train_sharpe\": 0.10851771383516226, \"validation_return\": -0.33914272230763, \"validation_returns_over_hodl\": -6.180611578088246e-11, \"validation_sharpe\": -2.2438532050084574}, {\"centeredness_margin\": 0.9884209562941562, \"continuous_test_metrics\": [{\"annualised_returns\": -0.48062613247688424, \"annualised_returns_over_hodl\": 0.007527389282807606, \"annualised_returns_over_uniform_hodl\": 0.833159562994138, \"calmar\": -0.829111152451294, \"daily_log_sharpe\": -0.6846998131916577, \"daily_returns\": 0.031016042753491922, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": 0.008026633973706277, \"return\": -0.2319082995592766, \"returns_over_hodl\": 0.0030247768633560046, \"returns_over_uniform_hodl\": 0.2764408760597832, \"sharpe\": -0.193992038562585, \"sterling\": -1.237394228573657, \"ulcer\": -0.17641031651801306}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -6.779865557859921e-11, \"optuna_trial_number\": 137, \"price_ratio\": 4.879654438174508, \"shift_exponent\": 1.135220535727277e-05, \"step\": 137, \"test_objective\": [{\"annualised_returns\": -0.48062613247688424, \"annualised_returns_over_hodl\": 0.007527389282807606, \"annualised_returns_over_uniform_hodl\": 0.833159562994138, \"calmar\": -0.829111152451294, \"daily_log_sharpe\": -0.6846998131916577, \"daily_returns\": 0.031016042753491922, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": 0.008026633973706277, \"return\": -0.2319082995592766, \"returns_over_hodl\": 0.0030247768633560046, \"returns_over_uniform_hodl\": 0.2764408760597832, \"sharpe\": -0.193992038562585, \"sterling\": -1.237394228573657, \"ulcer\": -0.17641031651801306}], \"train_objective\": [{\"annualised_returns\": -0.501730441483344, \"annualised_returns_over_hodl\": -0.372510123511945, \"annualised_returns_over_uniform_hodl\": -0.372510123511945, \"calmar\": -0.6668509185044997, \"daily_log_sharpe\": -0.49460951106448714, \"daily_returns\": 0.008093005319456652, \"fee_revenue_over_value\": 6.007516830569751e-06, \"jax_sharpe\": 0.10866513699790918, \"return\": -0.3448750490702457, \"returns_over_hodl\": -0.2464315390076678, \"returns_over_uniform_hodl\": -0.2464315390076678, \"sharpe\": 0.10997563748530262, \"sterling\": -1.4597563112493626, \"ulcer\": -0.18686323754765405}], \"train_return\": -0.3448750490702457, \"train_returns_over_hodl\": -0.2464315390076678, \"train_sharpe\": 0.10866513699790918, \"validation_return\": -0.33914272230643816, \"validation_returns_over_hodl\": -6.779865557859921e-11, \"validation_sharpe\": -2.243853205033035}, {\"centeredness_margin\": 0.9485861314903443, \"continuous_test_metrics\": [{\"annualised_returns\": -0.5192919641620708, \"annualised_returns_over_hodl\": -0.0674800126728824, \"annualised_returns_over_uniform_hodl\": 0.6966863140552764, \"calmar\": -0.8960573623106446, \"daily_log_sharpe\": -0.7794690336109543, \"daily_returns\": 0.031016042751367674, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": -0.10008080333291405, \"return\": -0.25547103134640714, \"returns_over_hodl\": -0.027744991610924363, \"returns_over_uniform_hodl\": 0.2372835280667387, \"sharpe\": -0.29874383621796485, \"sterling\": -1.3374315547930389, \"ulcer\": -0.1762701244472646}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -0.046733283696926975, \"optuna_trial_number\": 138, \"price_ratio\": 5.24221296832196, \"shift_exponent\": 0.0009840425494864983, \"step\": 138, \"test_objective\": [{\"annualised_returns\": -0.5192919641620708, \"annualised_returns_over_hodl\": -0.0674800126728824, \"annualised_returns_over_uniform_hodl\": 0.6966863140552764, \"calmar\": -0.8960573623106446, \"daily_log_sharpe\": -0.7794690336109543, \"daily_returns\": 0.031016042751367674, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": -0.10008080333291405, \"return\": -0.25547103134640714, \"returns_over_hodl\": -0.027744991610924363, \"returns_over_uniform_hodl\": 0.2372835280667387, \"sharpe\": -0.29874383621796485, \"sterling\": -1.3374315547930389, \"ulcer\": -0.1762701244472646}], \"train_objective\": [{\"annualised_returns\": -0.4490984128299784, \"annualised_returns_over_hodl\": -0.30622860060026147, \"annualised_returns_over_uniform_hodl\": -0.3062286006002618, \"calmar\": -0.5996084959526424, \"daily_log_sharpe\": -0.4370005339448984, \"daily_returns\": 0.008112452904012312, \"fee_revenue_over_value\": 3.238329121182899e-05, \"jax_sharpe\": 0.12882367415330315, \"return\": -0.3036934146228122, \"returns_over_hodl\": -0.19906167338484426, \"returns_over_uniform_hodl\": -0.19906167338484448, \"sharpe\": 0.14139138872097376, \"sterling\": -1.34539612568457, \"ulcer\": -0.18045255998325727}], \"train_return\": -0.3036934146228122, \"train_returns_over_hodl\": -0.19906167338484426, \"train_sharpe\": 0.12882367415330317, \"validation_return\": -0.3347645358497169, \"validation_returns_over_hodl\": -0.046733283696926975, \"validation_sharpe\": -2.205015265895412}, {\"centeredness_margin\": 0.9434524562043749, \"continuous_test_metrics\": [{\"annualised_returns\": -0.5216290401082382, \"annualised_returns_over_hodl\": -0.07201367943882642, \"annualised_returns_over_uniform_hodl\": 0.6884374717702542, \"calmar\": -0.9005038750603706, \"daily_log_sharpe\": -0.78386096622652, \"daily_returns\": 0.031016042752677785, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": -0.10237503252474206, \"return\": -0.25693094502343095, \"returns_over_hodl\": -0.029651443144066536, \"returns_over_uniform_hodl\": 0.23485739393223026, \"sharpe\": -0.3031855047171761, \"sterling\": -1.3444712276618453, \"ulcer\": -0.17598102555259088}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -0.06139342231804945, \"optuna_trial_number\": 139, \"price_ratio\": 8.387691456455402, \"shift_exponent\": 0.00036012536203539866, \"step\": 139, \"test_objective\": [{\"annualised_returns\": -0.5216290401082382, \"annualised_returns_over_hodl\": -0.07201367943882642, \"annualised_returns_over_uniform_hodl\": 0.6884374717702542, \"calmar\": -0.9005038750603706, \"daily_log_sharpe\": -0.78386096622652, \"daily_returns\": 0.031016042752677785, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": -0.10237503252474206, \"return\": -0.25693094502343095, \"returns_over_hodl\": -0.029651443144066536, \"returns_over_uniform_hodl\": 0.23485739393223026, \"sharpe\": -0.3031855047171761, \"sterling\": -1.3444712276618453, \"ulcer\": -0.17598102555259088}], \"train_objective\": [{\"annualised_returns\": -0.4363318280979811, \"annualised_returns_over_hodl\": -0.2901511530826897, \"annualised_returns_over_uniform_hodl\": -0.2901511530826897, \"calmar\": -0.5852258367528836, \"daily_log_sharpe\": -0.43416091395879064, \"daily_returns\": 0.008070114005943695, \"fee_revenue_over_value\": 3.6693796930464344e-05, \"jax_sharpe\": 0.15005243360734782, \"return\": -0.293940907544747, \"returns_over_hodl\": -0.18784368857262734, \"returns_over_uniform_hodl\": -0.18784368857262734, \"sharpe\": 0.12527946370683385, \"sterling\": -1.3292870374786745, \"ulcer\": -0.1775331515934845}], \"train_return\": -0.293940907544747, \"train_returns_over_hodl\": -0.18784368857262734, \"train_sharpe\": 0.15005243360734782, \"validation_return\": -0.32477754494575484, \"validation_returns_over_hodl\": -0.06139342231804945, \"validation_sharpe\": -2.129492181884961}, {\"centeredness_margin\": 0.9530753091495316, \"continuous_test_metrics\": [{\"annualised_returns\": -0.4845064531153803, \"annualised_returns_over_hodl\": 9.234835118832052e-13, \"annualised_returns_over_uniform_hodl\": 0.819463750919754, \"calmar\": -0.8358049680222139, \"daily_log_sharpe\": -0.6924635968834276, \"daily_returns\": 0.031016042751662212, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": 0.005019284041142307, \"return\": -0.23422459929667483, \"returns_over_hodl\": 3.7192471324942744e-13, \"returns_over_uniform_hodl\": 0.27259157048295535, \"sharpe\": -0.2021085296618198, \"sterling\": -1.2473843110381488, \"ulcer\": -0.17641031651423314}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -8.820610908344406e-11, \"optuna_trial_number\": 140, \"price_ratio\": 3.3251015586672765, \"shift_exponent\": 5.930914881601038e-05, \"step\": 140, \"test_objective\": [{\"annualised_returns\": -0.4845064531153803, \"annualised_returns_over_hodl\": 9.234835118832052e-13, \"annualised_returns_over_uniform_hodl\": 0.819463750919754, \"calmar\": -0.8358049680222139, \"daily_log_sharpe\": -0.6924635968834276, \"daily_returns\": 0.031016042751662212, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": 0.005019284041142307, \"return\": -0.23422459929667483, \"returns_over_hodl\": 3.7192471324942744e-13, \"returns_over_uniform_hodl\": 0.27259157048295535, \"sharpe\": -0.2021085296618198, \"sterling\": -1.2473843110381488, \"ulcer\": -0.17641031651423314}], \"train_objective\": [{\"annualised_returns\": -0.5525080995694791, \"annualised_returns_over_hodl\": -0.4364563667776906, \"annualised_returns_over_uniform_hodl\": -0.4364563667776904, \"calmar\": -0.7231586164532156, \"daily_log_sharpe\": -0.5544328340557267, \"daily_returns\": 0.008200092814288984, \"fee_revenue_over_value\": 2.3579247211856504e-05, \"jax_sharpe\": 0.14888622133013824, \"return\": -0.3862603035440473, \"returns_over_hodl\": -0.2940356219804483, \"returns_over_uniform_hodl\": -0.2940356219804482, \"sharpe\": 0.08476564813323331, \"sterling\": -1.5391514239369317, \"ulcer\": -0.19697355275449713}], \"train_return\": -0.3862603035440473, \"train_returns_over_hodl\": -0.2940356219804483, \"train_sharpe\": 0.1488862213301382, \"validation_return\": -0.3391427222997472, \"validation_returns_over_hodl\": -8.820610908344406e-11, \"validation_sharpe\": -2.2438532050930076}, {\"centeredness_margin\": 0.904682373414767, \"continuous_test_metrics\": [{\"annualised_returns\": -0.48292950778940835, \"annualised_returns_over_hodl\": 0.0030590980484432873, \"annualised_returns_over_uniform_hodl\": 0.82502966900957, \"calmar\": -0.8330846306407345, \"daily_log_sharpe\": -0.6892079088679708, \"daily_returns\": 0.031016042752759706, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": 0.006444569869600127, \"return\": -0.23328201337028642, \"returns_over_hodl\": 0.0012308908225040494, \"returns_over_uniform_hodl\": 0.2741579917903989, \"sharpe\": -0.19862937168518938, \"sterling\": -1.2433243851958027, \"ulcer\": -0.1764103165165051}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -7.591816064689283e-11, \"optuna_trial_number\": 141, \"price_ratio\": 4.069509487152836, \"shift_exponent\": 0.00012088042247452847, \"step\": 141, \"test_objective\": [{\"annualised_returns\": -0.48292950778940835, \"annualised_returns_over_hodl\": 0.0030590980484432873, \"annualised_returns_over_uniform_hodl\": 0.82502966900957, \"calmar\": -0.8330846306407345, \"daily_log_sharpe\": -0.6892079088679708, \"daily_returns\": 0.031016042752759706, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": 0.006444569869600127, \"return\": -0.23328201337028642, \"returns_over_hodl\": 0.0012308908225040494, \"returns_over_uniform_hodl\": 0.2741579917903989, \"sharpe\": -0.19862937168518938, \"sterling\": -1.2433243851958027, \"ulcer\": -0.1764103165165051}], \"train_objective\": [{\"annualised_returns\": -0.5224733011568906, \"annualised_returns_over_hodl\": -0.39863239855782806, \"annualised_returns_over_uniform_hodl\": -0.39863239855782806, \"calmar\": -0.6909786268766135, \"daily_log_sharpe\": -0.5180526810572705, \"daily_returns\": 0.008176425136232793, \"fee_revenue_over_value\": 4.2058496365207416e-05, \"jax_sharpe\": 0.11264004308096237, \"return\": -0.36157098896393314, \"returns_over_hodl\": -0.2656363238546665, \"returns_over_uniform_hodl\": -0.2656363238546665, \"sharpe\": 0.10144594823659586, \"sterling\": -1.495421426129084, \"ulcer\": -0.19052990286408084}], \"train_return\": -0.36157098896393314, \"train_returns_over_hodl\": -0.2656363238546665, \"train_sharpe\": 0.11264004308096236, \"validation_return\": -0.33914272230373976, \"validation_returns_over_hodl\": -7.591816064689283e-11, \"validation_sharpe\": -2.2438532050566615}, {\"centeredness_margin\": 0.7169631524387339, \"continuous_test_metrics\": [{\"annualised_returns\": -0.48450645315797214, \"annualised_returns_over_hodl\": 9.35695965154082e-13, \"annualised_returns_over_uniform_hodl\": 0.8194637507694231, \"calmar\": -0.8358049680904528, \"daily_log_sharpe\": -0.6924635969903717, \"daily_returns\": 0.031016042749642758, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": 0.005019283913139343, \"return\": -0.23422459932215645, \"returns_over_hodl\": 3.7680969455777813e-13, \"returns_over_uniform_hodl\": 0.272591570440609, \"sharpe\": -0.2021085297872511, \"sterling\": -1.2473843111808616, \"ulcer\": -0.176410316510052}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -1.0722878140967396e-10, \"optuna_trial_number\": 142, \"price_ratio\": 2.559334767256033, \"shift_exponent\": 1.4217397699099999e-05, \"step\": 142, \"test_objective\": [{\"annualised_returns\": -0.48450645315797214, \"annualised_returns_over_hodl\": 9.35695965154082e-13, \"annualised_returns_over_uniform_hodl\": 0.8194637507694231, \"calmar\": -0.8358049680904528, \"daily_log_sharpe\": -0.6924635969903717, \"daily_returns\": 0.031016042749642758, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": 0.005019283913139343, \"return\": -0.23422459932215645, \"returns_over_hodl\": 3.7680969455777813e-13, \"returns_over_uniform_hodl\": 0.272591570440609, \"sharpe\": -0.2021085297872511, \"sterling\": -1.2473843111808616, \"ulcer\": -0.176410316510052}], \"train_objective\": [{\"annualised_returns\": -0.5986566501662272, \"annualised_returns_over_hodl\": -0.49457299826580137, \"annualised_returns_over_uniform_hodl\": -0.49457299826580137, \"calmar\": -0.7717893665943419, \"daily_log_sharpe\": -0.6201618357726839, \"daily_returns\": 0.008345767030041206, \"fee_revenue_over_value\": 5.4785715816665583e-05, \"jax_sharpe\": 0.10087231299179499, \"return\": -0.4255051656910478, \"returns_over_hodl\": -0.33917768278579596, \"returns_over_uniform_hodl\": -0.33917768278579596, \"sharpe\": 0.04168461626236313, \"sterling\": -1.6337281332232805, \"ulcer\": -0.2037116040815062}], \"train_return\": -0.4255051656910478, \"train_returns_over_hodl\": -0.33917768278579596, \"train_sharpe\": 0.10087231299179497, \"validation_return\": -0.3391427222893536, \"validation_returns_over_hodl\": -1.0722878140967396e-10, \"validation_sharpe\": -2.2438532051295024}, {\"centeredness_margin\": 0.8008009538687065, \"continuous_test_metrics\": [{\"annualised_returns\": -0.47641423009078276, \"annualised_returns_over_hodl\": 0.0156980102322819, \"annualised_returns_over_uniform_hodl\": 0.8480257116787089, \"calmar\": -0.8218453362008526, \"daily_log_sharpe\": -0.67801269071363, \"daily_returns\": 0.031016042754066414, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": 0.008617489626934134, \"return\": -0.2294057356043715, \"returns_over_hodl\": 0.0062927898411087035, \"returns_over_uniform_hodl\": 0.2805997218397358, \"sharpe\": -0.18733873195260772, \"sterling\": -1.226550472489733, \"ulcer\": -0.1764103165192012}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -6.129607932336967e-11, \"optuna_trial_number\": 143, \"price_ratio\": 5.571992032196449, \"shift_exponent\": 2.6510569965748923e-05, \"step\": 143, \"test_objective\": [{\"annualised_returns\": -0.47641423009078276, \"annualised_returns_over_hodl\": 0.0156980102322819, \"annualised_returns_over_uniform_hodl\": 0.8480257116787089, \"calmar\": -0.8218453362008526, \"daily_log_sharpe\": -0.67801269071363, \"daily_returns\": 0.031016042754066414, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": 0.008617489626934134, \"return\": -0.2294057356043715, \"returns_over_hodl\": 0.0062927898411087035, \"returns_over_uniform_hodl\": 0.2805997218397358, \"sharpe\": -0.18733873195260772, \"sterling\": -1.226550472489733, \"ulcer\": -0.1764103165192012}], \"train_objective\": [{\"annualised_returns\": -0.48417739547166205, \"annualised_returns_over_hodl\": -0.3504049025816337, \"annualised_returns_over_uniform_hodl\": -0.3504049025816335, \"calmar\": -0.6455496383103917, \"daily_log_sharpe\": -0.475220154577924, \"daily_returns\": 0.008123288644059158, \"fee_revenue_over_value\": 5.954108932192897e-05, \"jax_sharpe\": 0.16035850351683387, \"return\": -0.33095885593765484, \"returns_over_hodl\": -0.23042420448787038, \"returns_over_uniform_hodl\": -0.23042420448787015, \"sharpe\": 0.11721857418136761, \"sterling\": -1.426493625994479, \"ulcer\": -0.18420515746120336}], \"train_return\": -0.33095885593765484, \"train_returns_over_hodl\": -0.23042420448787038, \"train_sharpe\": 0.16035850351683387, \"validation_return\": -0.3391427223078415, \"validation_returns_over_hodl\": -6.129607932336967e-11, \"validation_sharpe\": -2.2438532050069453}, {\"centeredness_margin\": 0.44286328976307565, \"continuous_test_metrics\": [{\"annualised_returns\": -0.4917434211445393, \"annualised_returns_over_hodl\": -0.01403891114152378, \"annualised_returns_over_uniform_hodl\": 0.7939204612409656, \"calmar\": -0.8482892280446727, \"daily_log_sharpe\": -0.7150768596057263, \"daily_returns\": 0.031016042755036978, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": -0.03263115440748686, \"return\": -0.23857258274551452, \"returns_over_hodl\": -0.005677883471875922, \"returns_over_uniform_hodl\": 0.2653659439082301, \"sharpe\": -0.2290068451536738, \"sterling\": -1.2660162534467996, \"ulcer\": -0.17641031652120093}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -0.03919396105234241, \"optuna_trial_number\": 144, \"price_ratio\": 7.382316369090806, \"shift_exponent\": 0.0001043789642258817, \"step\": 144, \"test_objective\": [{\"annualised_returns\": -0.4917434211445393, \"annualised_returns_over_hodl\": -0.01403891114152378, \"annualised_returns_over_uniform_hodl\": 0.7939204612409656, \"calmar\": -0.8482892280446727, \"daily_log_sharpe\": -0.7150768596057263, \"daily_returns\": 0.031016042755036978, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": -0.03263115440748686, \"return\": -0.23857258274551452, \"returns_over_hodl\": -0.005677883471875922, \"returns_over_uniform_hodl\": 0.2653659439082301, \"sharpe\": -0.2290068451536738, \"sterling\": -1.2660162534467996, \"ulcer\": -0.17641031652120093}], \"train_objective\": [{\"annualised_returns\": -0.45443430454924594, \"annualised_returns_over_hodl\": -0.3129482927400833, \"annualised_returns_over_uniform_hodl\": -0.3129482927400832, \"calmar\": -0.6085614546195393, \"daily_log_sharpe\": -0.4496319087368724, \"daily_returns\": 0.00803889207910087, \"fee_revenue_over_value\": 0.0018818517645012827, \"jax_sharpe\": 0.15096084854520442, \"return\": -0.3077958172158436, \"returns_over_hodl\": -0.20378053076314184, \"returns_over_uniform_hodl\": -0.20378053076314173, \"sharpe\": 0.12142065596047898, \"sterling\": -1.3684252862749071, \"ulcer\": -0.179815353047499}], \"train_return\": -0.3077958172158436, \"train_returns_over_hodl\": -0.20378053076314184, \"train_sharpe\": 0.15096084854520442, \"validation_return\": -0.33594943200622096, \"validation_returns_over_hodl\": -0.03919396105234241, \"validation_sharpe\": -2.215515157279525}, {\"centeredness_margin\": 0.9083453598643247, \"continuous_test_metrics\": [{\"annualised_returns\": -0.5294621205249022, \"annualised_returns_over_hodl\": -0.08720898203615635, \"annualised_returns_over_uniform_hodl\": 0.6607901695638703, \"calmar\": -0.9168207883503259, \"daily_log_sharpe\": -0.8034684929916148, \"daily_returns\": 0.031016042754084663, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": -0.12209246394966064, \"return\": -0.26185538092654537, \"returns_over_hodl\": -0.03608209618996905, \"returns_over_uniform_hodl\": 0.22667379908975893, \"sharpe\": -0.32492805441310973, \"sterling\": -1.3676666908734536, \"ulcer\": -0.17510293981481162}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -0.06440911238876001, \"optuna_trial_number\": 145, \"price_ratio\": 11.96252883109041, \"shift_exponent\": 1.2431794331944908e-05, \"step\": 145, \"test_objective\": [{\"annualised_returns\": -0.5294621205249022, \"annualised_returns_over_hodl\": -0.08720898203615635, \"annualised_returns_over_uniform_hodl\": 0.6607901695638703, \"calmar\": -0.9168207883503259, \"daily_log_sharpe\": -0.8034684929916148, \"daily_returns\": 0.031016042754084663, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": -0.12209246394966064, \"return\": -0.26185538092654537, \"returns_over_hodl\": -0.03608209618996905, \"returns_over_uniform_hodl\": 0.22667379908975893, \"sharpe\": -0.32492805441310973, \"sterling\": -1.3676666908734536, \"ulcer\": -0.17510293981481162}], \"train_objective\": [{\"annualised_returns\": -0.42338380406605813, \"annualised_returns_over_hodl\": -0.2738452121282733, \"annualised_returns_over_uniform_hodl\": -0.2738452121282732, \"calmar\": -0.5695959654178747, \"daily_log_sharpe\": -0.42435695094171816, \"daily_returns\": 0.008038625973803457, \"fee_revenue_over_value\": 4.36170557337773e-05, \"jax_sharpe\": 0.1401165522564199, \"return\": -0.2841380401674901, \"returns_over_hodl\": -0.1765677759817429, \"returns_over_uniform_hodl\": -0.17656777598174278, \"sharpe\": 0.12442226876055901, \"sterling\": -1.3036580819144987, \"ulcer\": -0.175612978384563}], \"train_return\": -0.2841380401674901, \"train_returns_over_hodl\": -0.1765677759817429, \"train_sharpe\": 0.14011655225641986, \"validation_return\": -0.31222881650443746, \"validation_returns_over_hodl\": -0.06440911238876001, \"validation_sharpe\": -2.071011390657901}, {\"centeredness_margin\": 0.8598807650773129, \"continuous_test_metrics\": [{\"annualised_returns\": -0.5029822379195986, \"annualised_returns_over_hodl\": -0.03584096242099699, \"annualised_returns_over_uniform_hodl\": 0.7542524191305833, \"calmar\": -0.8676801490877619, \"daily_log_sharpe\": -0.7404087983398615, \"daily_returns\": 0.031016042751871767, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": -0.05833314048330445, \"return\": -0.24539881353716175, \"returns_over_hodl\": -0.01459202561667905, \"returns_over_uniform_hodl\": 0.2540218817254505, \"sharpe\": -0.2564079516553223, \"sterling\": -1.2949566004893258, \"ulcer\": -0.17640870943938683}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -0.054898378495642386, \"optuna_trial_number\": 146, \"price_ratio\": 8.878622043456563, \"shift_exponent\": 1.7788823328902014e-05, \"step\": 146, \"test_objective\": [{\"annualised_returns\": -0.5029822379195986, \"annualised_returns_over_hodl\": -0.03584096242099699, \"annualised_returns_over_uniform_hodl\": 0.7542524191305833, \"calmar\": -0.8676801490877619, \"daily_log_sharpe\": -0.7404087983398615, \"daily_returns\": 0.031016042751871767, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": -0.05833314048330445, \"return\": -0.24539881353716175, \"returns_over_hodl\": -0.01459202561667905, \"returns_over_uniform_hodl\": 0.2540218817254505, \"sharpe\": -0.2564079516553223, \"sterling\": -1.2949566004893258, \"ulcer\": -0.17640870943938683}], \"train_objective\": [{\"annualised_returns\": -0.4435707153277443, \"annualised_returns_over_hodl\": -0.29926736011576116, \"annualised_returns_over_uniform_hodl\": -0.29926736011576116, \"calmar\": -0.5949302698263734, \"daily_log_sharpe\": -0.4419157204623063, \"daily_returns\": 0.008080921557666924, \"fee_revenue_over_value\": 4.920760895027273e-05, \"jax_sharpe\": 0.14378784445740886, \"return\": -0.29945997483316167, \"returns_over_hodl\": -0.19419208827369394, \"returns_over_uniform_hodl\": -0.19419208827369394, \"sharpe\": 0.11995865849841002, \"sterling\": -1.3473518126981425, \"ulcer\": -0.17821095336812962}], \"train_return\": -0.29945997483316167, \"train_returns_over_hodl\": -0.19419208827369394, \"train_sharpe\": 0.14378784445740886, \"validation_return\": -0.33046477473378477, \"validation_returns_over_hodl\": -0.054898378495642386, \"validation_sharpe\": -2.1683025226553037}, {\"centeredness_margin\": 0.738240313557629, \"continuous_test_metrics\": [{\"annualised_returns\": -0.4840056040864278, \"annualised_returns_over_hodl\": 0.0009715912575751773, \"annualised_returns_over_uniform_hodl\": 0.8212315260129044, \"calmar\": -0.8349409695560083, \"daily_log_sharpe\": -0.691418354461335, \"daily_returns\": 0.031016042751944504, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": 0.005602215869378567, \"return\": -0.2339250409186393, \"returns_over_hodl\": 0.00039118307338403824, \"returns_over_uniform_hodl\": 0.27308938677008787, \"sharpe\": -0.20098320283849946, \"sterling\": -1.2460948497673061, \"ulcer\": -0.1764103165148176}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -8.36868352394049e-11, \"optuna_trial_number\": 147, \"price_ratio\": 3.3636635954300176, \"shift_exponent\": 0.0003059234151701213, \"step\": 147, \"test_objective\": [{\"annualised_returns\": -0.4840056040864278, \"annualised_returns_over_hodl\": 0.0009715912575751773, \"annualised_returns_over_uniform_hodl\": 0.8212315260129044, \"calmar\": -0.8349409695560083, \"daily_log_sharpe\": -0.691418354461335, \"daily_returns\": 0.031016042751944504, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": 0.005602215869378567, \"return\": -0.2339250409186393, \"returns_over_hodl\": 0.00039118307338403824, \"returns_over_uniform_hodl\": 0.27308938677008787, \"sharpe\": -0.20098320283849946, \"sterling\": -1.2460948497673061, \"ulcer\": -0.1764103165148176}], \"train_objective\": [{\"annualised_returns\": -0.5452676094952984, \"annualised_returns_over_hodl\": -0.42733814122145486, \"annualised_returns_over_uniform_hodl\": -0.42733814122145486, \"calmar\": -0.7148656544079134, \"daily_log_sharpe\": -0.5455841223512042, \"daily_returns\": 0.00822765023321997, \"fee_revenue_over_value\": 6.474977752481314e-05, \"jax_sharpe\": 0.11570906403929644, \"return\": -0.38025036178803384, \"returns_over_hodl\": -0.2871225856912559, \"returns_over_uniform_hodl\": -0.2871225856912559, \"sharpe\": 0.08891665230001684, \"sterling\": -1.5290755969368917, \"ulcer\": -0.1952148889894863}], \"train_return\": -0.38025036178803384, \"train_returns_over_hodl\": -0.2871225856912559, \"train_sharpe\": 0.11570906403929644, \"validation_return\": -0.3391427222998694, \"validation_returns_over_hodl\": -8.36868352394049e-11, \"validation_sharpe\": -2.2438532050746964}, {\"centeredness_margin\": 0.7283697193085712, \"continuous_test_metrics\": [{\"annualised_returns\": -0.4843492301582111, \"annualised_returns_over_hodl\": 0.0003049948807782865, \"annualised_returns_over_uniform_hodl\": 0.8200186782764798, \"calmar\": -0.8355337477895052, \"daily_log_sharpe\": -0.6980642999201347, \"daily_returns\": 0.031016042754737627, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": -0.014256667138298129, \"return\": -0.234130545353899, \"returns_over_hodl\": 0.00012282178851696912, \"returns_over_uniform_hodl\": 0.2727478725196064, \"sharpe\": -0.21030435217848156, \"sterling\": -1.2469795324977346, \"ulcer\": -0.1764103165205866}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -0.026212270659245696, \"optuna_trial_number\": 148, \"price_ratio\": 6.987880329152761, \"shift_exponent\": 2.997112190231961e-05, \"step\": 148, \"test_objective\": [{\"annualised_returns\": -0.4843492301582111, \"annualised_returns_over_hodl\": 0.0003049948807782865, \"annualised_returns_over_uniform_hodl\": 0.8200186782764798, \"calmar\": -0.8355337477895052, \"daily_log_sharpe\": -0.6980642999201347, \"daily_returns\": 0.031016042754737627, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": -0.014256667138298129, \"return\": -0.234130545353899, \"returns_over_hodl\": 0.00012282178851696912, \"returns_over_uniform_hodl\": 0.2727478725196064, \"sharpe\": -0.21030435217848156, \"sterling\": -1.2469795324977346, \"ulcer\": -0.1764103165205866}], \"train_objective\": [{\"annualised_returns\": -0.46366233650648814, \"annualised_returns_over_hodl\": -0.3245695056640996, \"annualised_returns_over_uniform_hodl\": -0.3245695056640996, \"calmar\": -0.6199470827309924, \"daily_log_sharpe\": -0.4589221324015466, \"daily_returns\": 0.00810563049932769, \"fee_revenue_over_value\": 0.00019865964073823383, \"jax_sharpe\": 0.10195773801291297, \"return\": -0.31492802244591356, \"returns_over_hodl\": -0.2119844694332803, \"returns_over_uniform_hodl\": -0.2119844694332803, \"sharpe\": 0.11692504686809628, \"sterling\": -1.3888729337487526, \"ulcer\": -0.18092045407573726}], \"train_return\": -0.31492802244591356, \"train_returns_over_hodl\": -0.2119844694332803, \"train_sharpe\": 0.10195773801291297, \"validation_return\": -0.33777461453146806, \"validation_returns_over_hodl\": -0.026212270659245696, \"validation_sharpe\": -2.2314307263621775}, {\"centeredness_margin\": 0.765539645669514, \"continuous_test_metrics\": [{\"annualised_returns\": -0.4835508994380696, \"annualised_returns_over_hodl\": 0.0018536675384237533, \"annualised_returns_over_uniform_hodl\": 0.822836431894, \"calmar\": -0.834156573273762, \"daily_log_sharpe\": -0.6904763162799854, \"daily_returns\": 0.031016042752784797, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": 0.006101337229118673, \"return\": -0.23365323191155696, \"returns_over_hodl\": 0.0007461291793104952, \"returns_over_uniform_hodl\": 0.2735410882100615, \"sharpe\": -0.1999743740143702, \"sterling\": -1.2449241895443013, \"ulcer\": -0.17641031651655323}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -7.622513731320169e-11, \"optuna_trial_number\": 149, \"price_ratio\": 4.190427212105222, \"shift_exponent\": 1.0565423679003417e-05, \"step\": 149, \"test_objective\": [{\"annualised_returns\": -0.4835508994380696, \"annualised_returns_over_hodl\": 0.0018536675384237533, \"annualised_returns_over_uniform_hodl\": 0.822836431894, \"calmar\": -0.834156573273762, \"daily_log_sharpe\": -0.6904763162799854, \"daily_returns\": 0.031016042752784797, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": 0.006101337229118673, \"return\": -0.23365323191155696, \"returns_over_hodl\": 0.0007461291793104952, \"returns_over_uniform_hodl\": 0.2735410882100615, \"sharpe\": -0.1999743740143702, \"sterling\": -1.2449241895443013, \"ulcer\": -0.17641031651655323}], \"train_objective\": [{\"annualised_returns\": -0.5220972159932618, \"annualised_returns_over_hodl\": -0.3981587801542136, \"annualised_returns_over_uniform_hodl\": -0.39815878015421335, \"calmar\": -0.6908722215367593, \"daily_log_sharpe\": -0.5180686700726523, \"daily_returns\": 0.00819404303318808, \"fee_revenue_over_value\": 6.420318471407414e-05, \"jax_sharpe\": 0.11107132021591479, \"return\": -0.36126577140336513, \"returns_over_hodl\": -0.26528524223724603, \"returns_over_uniform_hodl\": -0.2652852422372458, \"sharpe\": 0.10057253796648655, \"sterling\": -1.4952389443025211, \"ulcer\": -0.1904463011693485}], \"train_return\": -0.36126577140336513, \"train_returns_over_hodl\": -0.26528524223724603, \"train_sharpe\": 0.11107132021591477, \"validation_return\": -0.3391427223042035, \"validation_returns_over_hodl\": -7.622513731320169e-11, \"validation_sharpe\": -2.2438532050597657}, {\"centeredness_margin\": 0.7008427555550575, \"continuous_test_metrics\": [{\"annualised_returns\": -0.4769158259935431, \"annualised_returns_over_hodl\": 0.014724970119964986, \"annualised_returns_over_uniform_hodl\": 0.8462553004520361, \"calmar\": -0.8227106230154456, \"daily_log_sharpe\": -0.6785171101139036, \"daily_returns\": 0.03101604275388011, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": 0.010470713888413878, \"return\": -0.22970313400523168, \"returns_over_hodl\": 0.005904427407121604, \"returns_over_uniform_hodl\": 0.2801054950760371, \"sharpe\": -0.18769147442016243, \"sterling\": -1.2278418565343225, \"ulcer\": -0.17641031651881744}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -6.256906104340487e-11, \"optuna_trial_number\": 150, \"price_ratio\": 5.276273012823205, \"shift_exponent\": 5.953205889897899e-05, \"step\": 150, \"test_objective\": [{\"annualised_returns\": -0.4769158259935431, \"annualised_returns_over_hodl\": 0.014724970119964986, \"annualised_returns_over_uniform_hodl\": 0.8462553004520361, \"calmar\": -0.8227106230154456, \"daily_log_sharpe\": -0.6785171101139036, \"daily_returns\": 0.03101604275388011, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": 0.010470713888413878, \"return\": -0.22970313400523168, \"returns_over_hodl\": 0.005904427407121604, \"returns_over_uniform_hodl\": 0.2801054950760371, \"sharpe\": -0.18769147442016243, \"sterling\": -1.2278418565343225, \"ulcer\": -0.17641031651881744}], \"train_objective\": [{\"annualised_returns\": -0.48963710153124684, \"annualised_returns_over_hodl\": -0.3572805188468312, \"annualised_returns_over_uniform_hodl\": -0.3572805188468312, \"calmar\": -0.6521894189203175, \"daily_log_sharpe\": -0.4808344592884929, \"daily_returns\": 0.008130523925648912, \"fee_revenue_over_value\": 0.00019807732647967682, \"jax_sharpe\": 0.10899258556221866, \"return\": -0.3352671342933564, \"returns_over_hodl\": -0.23537987391463389, \"returns_over_uniform_hodl\": -0.23537987391463389, \"sharpe\": 0.11563792204836106, \"sterling\": -1.4367274958954583, \"ulcer\": -0.18503354226255353}], \"train_return\": -0.3352671342933564, \"train_returns_over_hodl\": -0.23537987391463389, \"train_sharpe\": 0.10899258556221866, \"validation_return\": -0.3391427223073028, \"validation_returns_over_hodl\": -6.256906104340487e-11, \"validation_sharpe\": -2.2438532050145414}, {\"centeredness_margin\": 0.8586452924649106, \"continuous_test_metrics\": [{\"annualised_returns\": -0.484113087866452, \"annualised_returns_over_hodl\": 0.0007630845967545596, \"annualised_returns_over_uniform_hodl\": 0.8208521559068234, \"calmar\": -0.8351263863567256, \"daily_log_sharpe\": -0.6976346597876316, \"daily_returns\": 0.031016042754701215, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": -0.01494860403856667, \"return\": -0.2339893124503024, \"returns_over_hodl\": 0.00030725302502365714, \"returns_over_uniform_hodl\": 0.27298257815578486, \"sharpe\": -0.20984953745674434, \"sterling\": -1.246371572173235, \"ulcer\": -0.1764103165205112}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -0.024310255310296958, \"optuna_trial_number\": 151, \"price_ratio\": 6.804045867129126, \"shift_exponent\": 6.657249516299172e-05, \"step\": 151, \"test_objective\": [{\"annualised_returns\": -0.484113087866452, \"annualised_returns_over_hodl\": 0.0007630845967545596, \"annualised_returns_over_uniform_hodl\": 0.8208521559068234, \"calmar\": -0.8351263863567256, \"daily_log_sharpe\": -0.6976346597876316, \"daily_returns\": 0.031016042754701215, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": -0.01494860403856667, \"return\": -0.2339893124503024, \"returns_over_hodl\": 0.00030725302502365714, \"returns_over_uniform_hodl\": 0.27298257815578486, \"sharpe\": -0.20984953745674434, \"sterling\": -1.246371572173235, \"ulcer\": -0.1764103165205112}], \"train_objective\": [{\"annualised_returns\": -0.4649184483069607, \"annualised_returns_over_hodl\": -0.3261513752065224, \"annualised_returns_over_uniform_hodl\": -0.3261513752065224, \"calmar\": -0.6214633224356639, \"daily_log_sharpe\": -0.45989573132398165, \"daily_returns\": 0.008063012207639645, \"fee_revenue_over_value\": 4.820288926378646e-05, \"jax_sharpe\": 0.1026185847725208, \"return\": -0.31590256702190045, \"returns_over_hodl\": -0.2131054556745352, \"returns_over_uniform_hodl\": -0.2131054556745352, \"sharpe\": 0.11707599456727619, \"sterling\": -1.3911794075259927, \"ulcer\": -0.18112330953603656}], \"train_return\": -0.31590256702190045, \"train_returns_over_hodl\": -0.2131054556745352, \"train_sharpe\": 0.1026185847725208, \"validation_return\": -0.3379780136983219, \"validation_returns_over_hodl\": -0.024310255310296958, \"validation_sharpe\": -2.233200486003279}, {\"centeredness_margin\": 0.865000719024426, \"continuous_test_metrics\": [{\"annualised_returns\": -0.5083440538071329, \"annualised_returns_over_hodl\": -0.04624228737027769, \"annualised_returns_over_uniform_hodl\": 0.7353275854339594, \"calmar\": -0.8770598707710796, \"daily_log_sharpe\": -0.7526638539665526, \"daily_returns\": 0.03101604275216371, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": -0.07038932244296049, \"return\": -0.2486879723403862, \"returns_over_hodl\": -0.018887226013715286, \"returns_over_uniform_hodl\": 0.24855584590983293, \"sharpe\": -0.26959341722964747, \"sterling\": -1.3089758157506775, \"ulcer\": -0.17634589003976484}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -0.057756332784932196, \"optuna_trial_number\": 152, \"price_ratio\": 8.971294986295215, \"shift_exponent\": 8.367965021128664e-05, \"step\": 152, \"test_objective\": [{\"annualised_returns\": -0.5083440538071329, \"annualised_returns_over_hodl\": -0.04624228737027769, \"annualised_returns_over_uniform_hodl\": 0.7353275854339594, \"calmar\": -0.8770598707710796, \"daily_log_sharpe\": -0.7526638539665526, \"daily_returns\": 0.03101604275216371, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": -0.07038932244296049, \"return\": -0.2486879723403862, \"returns_over_hodl\": -0.018887226013715286, \"returns_over_uniform_hodl\": 0.24855584590983293, \"sharpe\": -0.26959341722964747, \"sterling\": -1.3089758157506775, \"ulcer\": -0.17634589003976484}], \"train_objective\": [{\"annualised_returns\": -0.43984582212598844, \"annualised_returns_over_hodl\": -0.2945764599089351, \"annualised_returns_over_uniform_hodl\": -0.2945764599089351, \"calmar\": -0.5901899670639387, \"daily_log_sharpe\": -0.43799218515288907, \"daily_returns\": 0.00805624960008033, \"fee_revenue_over_value\": 4.900184241597281e-05, \"jax_sharpe\": 0.15061739806089414, \"return\": -0.29661654431948514, \"returns_over_hodl\": -0.19092138464213648, \"returns_over_uniform_hodl\": -0.19092138464213648, \"sharpe\": 0.12235084554068887, \"sterling\": -1.3383681090351724, \"ulcer\": -0.17786684633848462}], \"train_return\": -0.29661654431948514, \"train_returns_over_hodl\": -0.19092138464213648, \"train_sharpe\": 0.1506173980608941, \"validation_return\": -0.328489599289047, \"validation_returns_over_hodl\": -0.057756332784932196, \"validation_sharpe\": -2.152778323129638}, {\"centeredness_margin\": 0.24372600757353546, \"continuous_test_metrics\": [{\"annualised_returns\": -0.48450645316313146, \"annualised_returns_over_hodl\": 1.1550760348200129e-12, \"annualised_returns_over_uniform_hodl\": 0.8194637507512135, \"calmar\": -0.8358049680987496, \"daily_log_sharpe\": -0.6924635970033799, \"daily_returns\": 0.0310160427493881, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": 0.005019283897483386, \"return\": -0.2342245993252431, \"returns_over_hodl\": 4.651834473179406e-13, \"returns_over_uniform_hodl\": 0.27259157043547955, \"sharpe\": -0.20210852980254246, \"sterling\": -1.247384311198244, \"ulcer\": -0.1764103165095297}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -1.04725894622959e-10, \"optuna_trial_number\": 153, \"price_ratio\": 2.340560999870833, \"shift_exponent\": 0.0006491377838060304, \"step\": 153, \"test_objective\": [{\"annualised_returns\": -0.48450645316313146, \"annualised_returns_over_hodl\": 1.1550760348200129e-12, \"annualised_returns_over_uniform_hodl\": 0.8194637507512135, \"calmar\": -0.8358049680987496, \"daily_log_sharpe\": -0.6924635970033799, \"daily_returns\": 0.0310160427493881, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": 0.005019283897483386, \"return\": -0.2342245993252431, \"returns_over_hodl\": 4.651834473179406e-13, \"returns_over_uniform_hodl\": 0.27259157043547955, \"sharpe\": -0.20210852980254246, \"sterling\": -1.247384311198244, \"ulcer\": -0.1764103165095297}], \"train_objective\": [{\"annualised_returns\": -0.6030792529627255, \"annualised_returns_over_hodl\": -0.5001425508003612, \"annualised_returns_over_uniform_hodl\": -0.5001425508003612, \"calmar\": -0.7753347310021036, \"daily_log_sharpe\": -0.6275882445283167, \"daily_returns\": 0.008378067217882956, \"fee_revenue_over_value\": 0.0019279862514401341, \"jax_sharpe\": 0.09981932948182448, \"return\": -0.4293569986055221, \"returns_over_hodl\": -0.3436083181893791, \"returns_over_uniform_hodl\": -0.3436083181893791, \"sharpe\": 0.03553049205331552, \"sterling\": -1.6418190358275662, \"ulcer\": -0.2040226390998662}], \"train_return\": -0.4293569986055221, \"train_returns_over_hodl\": -0.3436083181893791, \"train_sharpe\": 0.09981932948182447, \"validation_return\": -0.3391427222849317, \"validation_returns_over_hodl\": -1.04725894622959e-10, \"validation_sharpe\": -2.243853205103017}, {\"centeredness_margin\": 0.6435242204068301, \"continuous_test_metrics\": [{\"annualised_returns\": -0.4925850169687096, \"annualised_returns_over_hodl\": -0.015671513201356846, \"annualised_returns_over_uniform_hodl\": 0.7909500009815547, \"calmar\": -0.8497410378975763, \"daily_log_sharpe\": -0.7168684627836774, \"daily_returns\": 0.03101604275515248, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": -0.03470979214581451, \"return\": -0.23908060926340735, \"returns_over_hodl\": -0.006341297991145356, \"returns_over_uniform_hodl\": 0.26452168818565336, \"sharpe\": -0.2309196099074307, \"sterling\": -1.2681829848020576, \"ulcer\": -0.1764103165214489}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -0.04345445125349612, \"optuna_trial_number\": 154, \"price_ratio\": 7.902244824605556, \"shift_exponent\": 1.3750954289353587e-05, \"step\": 154, \"test_objective\": [{\"annualised_returns\": -0.4925850169687096, \"annualised_returns_over_hodl\": -0.015671513201356846, \"annualised_returns_over_uniform_hodl\": 0.7909500009815547, \"calmar\": -0.8497410378975763, \"daily_log_sharpe\": -0.7168684627836774, \"daily_returns\": 0.03101604275515248, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": -0.03470979214581451, \"return\": -0.23908060926340735, \"returns_over_hodl\": -0.006341297991145356, \"returns_over_uniform_hodl\": 0.26452168818565336, \"sharpe\": -0.2309196099074307, \"sterling\": -1.2681829848020576, \"ulcer\": -0.1764103165214489}], \"train_objective\": [{\"annualised_returns\": -0.45228336860859275, \"annualised_returns_over_hodl\": -0.3102395369980063, \"annualised_returns_over_uniform_hodl\": -0.3102395369980061, \"calmar\": -0.6059025938205906, \"daily_log_sharpe\": -0.44896623996111873, \"daily_returns\": 0.008082231780050372, \"fee_revenue_over_value\": 0.0008581175034476862, \"jax_sharpe\": 0.14786019625013533, \"return\": -0.3061402214584581, \"returns_over_hodl\": -0.20187615397952552, \"returns_over_uniform_hodl\": -0.2018761539795253, \"sharpe\": 0.1192173955717525, \"sterling\": -1.365247535528037, \"ulcer\": -0.17940191761488294}], \"train_return\": -0.3061402214584581, \"train_returns_over_hodl\": -0.20187615397952552, \"train_sharpe\": 0.14786019625013533, \"validation_return\": -0.3350917783638713, \"validation_returns_over_hodl\": -0.04345445125349612, \"validation_sharpe\": -2.2079732149102194}, {\"centeredness_margin\": 0.7932440871394677, \"continuous_test_metrics\": [{\"annualised_returns\": -0.5367185167343917, \"annualised_returns_over_hodl\": -0.1012855815487167, \"annualised_returns_over_uniform_hodl\": 0.6351783070191914, \"calmar\": -0.9293762356890856, \"daily_log_sharpe\": -0.8216105204120938, \"daily_returns\": 0.03101604275430557, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": -0.14051436185130362, \"return\": -0.2664611511385042, \"returns_over_hodl\": -0.042096614557787126, \"returns_over_uniform_hodl\": 0.21901977371634396, \"sharpe\": -0.344716522728144, \"sterling\": -1.3878772949814033, \"ulcer\": -0.17471789340002772}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -0.06533942502169657, \"optuna_trial_number\": 155, \"price_ratio\": 11.532034535395004, \"shift_exponent\": 0.00014435635129477333, \"step\": 155, \"test_objective\": [{\"annualised_returns\": -0.5367185167343917, \"annualised_returns_over_hodl\": -0.1012855815487167, \"annualised_returns_over_uniform_hodl\": 0.6351783070191914, \"calmar\": -0.9293762356890856, \"daily_log_sharpe\": -0.8216105204120938, \"daily_returns\": 0.03101604275430557, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": -0.14051436185130362, \"return\": -0.2664611511385042, \"returns_over_hodl\": -0.042096614557787126, \"returns_over_uniform_hodl\": 0.21901977371634396, \"sharpe\": -0.344716522728144, \"sterling\": -1.3878772949814033, \"ulcer\": -0.17471789340002772}], \"train_objective\": [{\"annualised_returns\": -0.42150979998989146, \"annualised_returns_over_hodl\": -0.2714852072550221, \"annualised_returns_over_uniform_hodl\": -0.2714852072550221, \"calmar\": -0.5670581876294895, \"daily_log_sharpe\": -0.4219537097892836, \"daily_returns\": 0.008000783829309575, \"fee_revenue_over_value\": 0.0001371713211238238, \"jax_sharpe\": 0.1056626896614986, \"return\": -0.2827264404145836, \"returns_over_hodl\": -0.1749440596939945, \"returns_over_uniform_hodl\": -0.1749440596939945, \"sharpe\": 0.12665566989350913, \"sterling\": -1.2984508128548373, \"ulcer\": -0.17547617987995842}], \"train_return\": -0.2827264404145836, \"train_returns_over_hodl\": -0.1749440596939945, \"train_sharpe\": 0.1056626896614986, \"validation_return\": -0.3102052205654655, \"validation_returns_over_hodl\": -0.06533942502169654, \"validation_sharpe\": -2.0653054483751263}, {\"centeredness_margin\": 0.7403189492757059, \"continuous_test_metrics\": [{\"annualised_returns\": -0.47586750786673093, \"annualised_returns_over_hodl\": 0.01675859040077121, \"annualised_returns_over_uniform_hodl\": 0.8499553988193076, \"calmar\": -0.8209022034366938, \"daily_log_sharpe\": -0.6771533601399072, \"daily_returns\": 0.03101604275365488, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": 0.00889470152902515, \"return\": -0.22908177536320284, \"returns_over_hodl\": 0.006715838487066694, \"returns_over_uniform_hodl\": 0.2811380899718334, \"sharpe\": -0.18647051334217002, \"sterling\": -1.2251429084629912, \"ulcer\": -0.17641031651835207}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -6.46597220210765e-11, \"optuna_trial_number\": 156, \"price_ratio\": 4.582485044603461, \"shift_exponent\": 0.00035184809773905896, \"step\": 156, \"test_objective\": [{\"annualised_returns\": -0.47586750786673093, \"annualised_returns_over_hodl\": 0.01675859040077121, \"annualised_returns_over_uniform_hodl\": 0.8499553988193076, \"calmar\": -0.8209022034366938, \"daily_log_sharpe\": -0.6771533601399072, \"daily_returns\": 0.03101604275365488, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": 0.00889470152902515, \"return\": -0.22908177536320284, \"returns_over_hodl\": 0.006715838487066694, \"returns_over_uniform_hodl\": 0.2811380899718334, \"sharpe\": -0.18647051334217002, \"sterling\": -1.2251429084629912, \"ulcer\": -0.17641031651835207}], \"train_objective\": [{\"annualised_returns\": -0.4971395034869013, \"annualised_returns_over_hodl\": -0.3667285800338965, \"annualised_returns_over_uniform_hodl\": -0.3667285800338965, \"calmar\": -0.6602424942251198, \"daily_log_sharpe\": -0.4870952284793896, \"daily_returns\": 0.00813568357167128, \"fee_revenue_over_value\": 0.0001151753319212003, \"jax_sharpe\": 0.11492596863668979, \"return\": -0.3412169679506627, \"returns_over_hodl\": -0.24222376985529925, \"returns_over_uniform_hodl\": -0.24222376985529925, \"sharpe\": 0.11731826525669205, \"sterling\": -1.4477872135253755, \"ulcer\": -0.18641926295380534}], \"train_return\": -0.3412169679506627, \"train_returns_over_hodl\": -0.24222376985529925, \"train_sharpe\": 0.11492596863668979, \"validation_return\": -0.3391427223061665, \"validation_returns_over_hodl\": -6.46597220210765e-11, \"validation_sharpe\": -2.2438532050188655}, {\"centeredness_margin\": 0.8347533213874032, \"continuous_test_metrics\": [{\"annualised_returns\": -0.7400102626534575, \"annualised_returns_over_hodl\": -0.07351045988886318, \"annualised_returns_over_uniform_hodl\": -0.08235145605215399, \"calmar\": -1.2287231237589749, \"daily_log_sharpe\": -1.7178915674804405, \"daily_returns\": 0.017871727923603316, \"fee_revenue_over_value\": 0.00964350272918733, \"jax_sharpe\": -1.204627187605178, \"return\": -0.4187262170842416, \"returns_over_hodl\": -0.030282075177937906, \"returns_over_uniform_hodl\": -0.034019484561746194, \"sharpe\": -1.3373060980536007, \"sterling\": -2.2914976069678406, \"ulcer\": -0.14907204698131266}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -0.029981637981977283, \"optuna_trial_number\": 157, \"price_ratio\": 160.55191167757465, \"shift_exponent\": 121.8619977674801, \"step\": 157, \"test_objective\": [{\"annualised_returns\": -0.7400102626534575, \"annualised_returns_over_hodl\": -0.07351045988886318, \"annualised_returns_over_uniform_hodl\": -0.08235145605215399, \"calmar\": -1.2287231237589749, \"daily_log_sharpe\": -1.7178915674804405, \"daily_returns\": 0.017871727923603316, \"fee_revenue_over_value\": 0.00964350272918733, \"jax_sharpe\": -1.204627187605178, \"return\": -0.4187262170842416, \"returns_over_hodl\": -0.030282075177937906, \"returns_over_uniform_hodl\": -0.034019484561746194, \"sharpe\": -1.3373060980536007, \"sterling\": -2.2914976069678406, \"ulcer\": -0.14907204698131266}], \"train_objective\": [{\"annualised_returns\": -0.3612631848864465, \"annualised_returns_over_hodl\": -0.19561434493979935, \"annualised_returns_over_uniform_hodl\": -0.19561434493979935, \"calmar\": -0.4892201783090728, \"daily_log_sharpe\": -0.3973248369485896, \"daily_returns\": 0.007936996564334881, \"fee_revenue_over_value\": 0.0058097925737076, \"jax_sharpe\": 0.05781021001930832, \"return\": -0.23825994715695142, \"returns_over_hodl\": -0.12379573013896161, \"returns_over_uniform_hodl\": -0.12379573013896161, \"sharpe\": 0.0884492736420754, \"sterling\": -1.1923736605611066, \"ulcer\": -0.16479511274252856}], \"train_return\": -0.23825994715695142, \"train_returns_over_hodl\": -0.12379573013896161, \"train_sharpe\": 0.05781021001930831, \"validation_return\": -0.16237641980595907, \"validation_returns_over_hodl\": -0.029981637981977283, \"validation_sharpe\": -1.2969972244725545}, {\"centeredness_margin\": 0.6651338157596944, \"continuous_test_metrics\": [{\"annualised_returns\": -0.4810165839339541, \"annualised_returns_over_hodl\": 0.006769957009010064, \"annualised_returns_over_uniform_hodl\": 0.8317814424009196, \"calmar\": -0.8297847076141524, \"daily_log_sharpe\": -0.6854388373245017, \"daily_returns\": 0.031016042753127616, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": 0.008435942313801867, \"return\": -0.23214090505589913, \"returns_over_hodl\": 0.0027210252779485966, \"returns_over_uniform_hodl\": 0.27605432434503996, \"sharpe\": -0.1947320583386483, \"sterling\": -1.238399465657653, \"ulcer\": -0.17641031651726355}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -7.156153447596125e-11, \"optuna_trial_number\": 158, \"price_ratio\": 4.360385455793663, \"shift_exponent\": 0.00014804573131437505, \"step\": 158, \"test_objective\": [{\"annualised_returns\": -0.4810165839339541, \"annualised_returns_over_hodl\": 0.006769957009010064, \"annualised_returns_over_uniform_hodl\": 0.8317814424009196, \"calmar\": -0.8297847076141524, \"daily_log_sharpe\": -0.6854388373245017, \"daily_returns\": 0.031016042753127616, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": 0.008435942313801867, \"return\": -0.23214090505589913, \"returns_over_hodl\": 0.0027210252779485966, \"returns_over_uniform_hodl\": 0.27605432434503996, \"sharpe\": -0.1947320583386483, \"sterling\": -1.238399465657653, \"ulcer\": -0.17641031651726355}], \"train_objective\": [{\"annualised_returns\": -0.5118677097803801, \"annualised_returns_over_hodl\": -0.3852763724687751, \"annualised_returns_over_uniform_hodl\": -0.3852763724687751, \"calmar\": -0.6785237318614986, \"daily_log_sharpe\": -0.5052021959795181, \"daily_returns\": 0.008144642450359185, \"fee_revenue_over_value\": 0.00021343799624964818, \"jax_sharpe\": 0.11255248744561303, \"return\": -0.3529997055439501, \"returns_over_hodl\": -0.2557770613638103, \"returns_over_uniform_hodl\": -0.2557770613638103, \"sharpe\": 0.10767944561647146, \"sterling\": -1.4767449189005977, \"ulcer\": -0.1886596930709571}], \"train_return\": -0.3529997055439501, \"train_returns_over_hodl\": -0.2557770613638103, \"train_sharpe\": 0.11255248744561304, \"validation_return\": -0.3391427223049196, \"validation_returns_over_hodl\": -7.156153447596125e-11, \"validation_sharpe\": -2.243853205043029}, {\"centeredness_margin\": 0.8083841900280004, \"continuous_test_metrics\": [{\"annualised_returns\": -0.48450645315965324, \"annualised_returns_over_hodl\": 9.50572953684059e-13, \"annualised_returns_over_uniform_hodl\": 0.8194637507634899, \"calmar\": -0.8358049680931458, \"daily_log_sharpe\": -0.6924635969945908, \"daily_returns\": 0.031016042749559034, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": 0.005019283908077281, \"return\": -0.2342245993231622, \"returns_over_hodl\": 3.82804898890754e-13, \"returns_over_uniform_hodl\": 0.27259157043893767, \"sharpe\": -0.20210852979219726, \"sterling\": -1.2473843111865024, \"ulcer\": -0.17641031650988537}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -1.0555467611084168e-10, \"optuna_trial_number\": 159, \"price_ratio\": 2.4869521316437955, \"shift_exponent\": 0.00013842213789506006, \"step\": 159, \"test_objective\": [{\"annualised_returns\": -0.48450645315965324, \"annualised_returns_over_hodl\": 9.50572953684059e-13, \"annualised_returns_over_uniform_hodl\": 0.8194637507634899, \"calmar\": -0.8358049680931458, \"daily_log_sharpe\": -0.6924635969945908, \"daily_returns\": 0.031016042749559034, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": 0.005019283908077281, \"return\": -0.2342245993231622, \"returns_over_hodl\": 3.82804898890754e-13, \"returns_over_uniform_hodl\": 0.27259157043893767, \"sharpe\": -0.20210852979219726, \"sterling\": -1.2473843111865024, \"ulcer\": -0.17641031650988537}], \"train_objective\": [{\"annualised_returns\": -0.6018495750615396, \"annualised_returns_over_hodl\": -0.49859397047642473, \"annualised_returns_over_uniform_hodl\": -0.49859397047642495, \"calmar\": -0.7748907033000696, \"daily_log_sharpe\": -0.6256243085155071, \"daily_returns\": 0.0083815153337555, \"fee_revenue_over_value\": 4.5847296766002706e-05, \"jax_sharpe\": 0.09846513089745948, \"return\": -0.4282843348868577, \"returns_over_hodl\": -0.34237446875884037, \"returns_over_uniform_hodl\": -0.3423744687588406, \"sharpe\": 0.03702716749324717, \"sterling\": -1.640642388643729, \"ulcer\": -0.20399635008235567}], \"train_return\": -0.4282843348868577, \"train_returns_over_hodl\": -0.34237446875884037, \"train_sharpe\": 0.0984651308974595, \"validation_return\": -0.33914272228800746, \"validation_returns_over_hodl\": -1.0555467611084168e-10, \"validation_sharpe\": -2.2438532051217748}, {\"centeredness_margin\": 0.9891986274593765, \"continuous_test_metrics\": [{\"annualised_returns\": -0.4812702495007891, \"annualised_returns_over_hodl\": 0.006277874036263675, \"annualised_returns_over_uniform_hodl\": 0.8308861153759708, \"calmar\": -0.8302222978734846, \"daily_log_sharpe\": -0.6907065618614554, \"daily_returns\": 0.031016042754551176, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": -0.0065040782839457375, \"return\": -0.23229207846009803, \"returns_over_hodl\": 0.002523613057557883, \"returns_over_uniform_hodl\": 0.27580309924733126, \"sharpe\": -0.20202515518388095, \"sterling\": -1.2390525405030208, \"ulcer\": -0.17641031652020525}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -0.016487680496262036, \"optuna_trial_number\": 160, \"price_ratio\": 6.67283165840985, \"shift_exponent\": 1.2249895043668584e-05, \"step\": 160, \"test_objective\": [{\"annualised_returns\": -0.4812702495007891, \"annualised_returns_over_hodl\": 0.006277874036263675, \"annualised_returns_over_uniform_hodl\": 0.8308861153759708, \"calmar\": -0.8302222978734846, \"daily_log_sharpe\": -0.6907065618614554, \"daily_returns\": 0.031016042754551176, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": -0.0065040782839457375, \"return\": -0.23229207846009803, \"returns_over_hodl\": 0.002523613057557883, \"returns_over_uniform_hodl\": 0.27580309924733126, \"sharpe\": -0.20202515518388095, \"sterling\": -1.2390525405030208, \"ulcer\": -0.17641031652020525}], \"train_objective\": [{\"annualised_returns\": -0.46909581739785744, \"annualised_returns_over_hodl\": -0.3314120955738177, \"annualised_returns_over_uniform_hodl\": -0.33141209557381746, \"calmar\": -0.6267559850154807, \"daily_log_sharpe\": -0.4639518519510345, \"daily_returns\": 0.008084786008286026, \"fee_revenue_over_value\": 6.364401400310328e-06, \"jax_sharpe\": 0.1014803870214909, \"return\": -0.31915003073431125, \"returns_over_hodl\": -0.21684090526841315, \"returns_over_uniform_hodl\": -0.21684090526841304, \"sharpe\": 0.11537578013014037, \"sterling\": -1.4001609606854026, \"ulcer\": -0.18163576429258446}], \"train_return\": -0.31915003073431125, \"train_returns_over_hodl\": -0.21684090526841315, \"train_sharpe\": 0.10148038702149088, \"validation_return\": -0.338663477714122, \"validation_returns_over_hodl\": -0.016487680496262036, \"validation_sharpe\": -2.2395011919279733}, {\"centeredness_margin\": 0.5211489717069385, \"continuous_test_metrics\": [{\"annualised_returns\": -0.7118061475803599, \"annualised_returns_over_hodl\": -0.11160240862014204, \"annualised_returns_over_uniform_hodl\": 0.017196569936532624, \"calmar\": -1.1958813815494884, \"daily_log_sharpe\": -1.5515917712569207, \"daily_returns\": 0.020732235026421738, \"fee_revenue_over_value\": 0.006648067058964483, \"jax_sharpe\": -0.997478964766396, \"return\": -0.39410886654241917, \"returns_over_hodl\": -0.046540511557802366, \"returns_over_uniform_hodl\": 0.00689046469801613, \"sharpe\": -1.1593605884201137, \"sterling\": -2.1182774615300275, \"ulcer\": -0.15131438261636432}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -0.03167687734077118, \"optuna_trial_number\": 161, \"price_ratio\": 92.64392876484318, \"shift_exponent\": 0.0030426221236702984, \"step\": 161, \"test_objective\": [{\"annualised_returns\": -0.7118061475803599, \"annualised_returns_over_hodl\": -0.11160240862014204, \"annualised_returns_over_uniform_hodl\": 0.017196569936532624, \"calmar\": -1.1958813815494884, \"daily_log_sharpe\": -1.5515917712569207, \"daily_returns\": 0.020732235026421738, \"fee_revenue_over_value\": 0.006648067058964483, \"jax_sharpe\": -0.997478964766396, \"return\": -0.39410886654241917, \"returns_over_hodl\": -0.046540511557802366, \"returns_over_uniform_hodl\": 0.00689046469801613, \"sharpe\": -1.1593605884201137, \"sterling\": -2.1182774615300275, \"ulcer\": -0.15131438261636432}], \"train_objective\": [{\"annualised_returns\": -0.3398316256982453, \"annualised_returns_over_hodl\": -0.1686247643039973, \"annualised_returns_over_uniform_hodl\": -0.1686247643039973, \"calmar\": -0.4622531993295322, \"daily_log_sharpe\": -0.34541483703907394, \"daily_returns\": 0.00794682847578162, \"fee_revenue_over_value\": 0.0029760101118553057, \"jax_sharpe\": 0.13119002506913824, \"return\": -0.2228434332565029, \"returns_over_hodl\": -0.10606262649615206, \"returns_over_uniform_hodl\": -0.10606262649615206, \"sharpe\": 0.15963333896293036, \"sterling\": -1.1019338594701156, \"ulcer\": -0.16655224783203637}], \"train_return\": -0.2228434332565029, \"train_returns_over_hodl\": -0.10606262649615206, \"train_sharpe\": 0.13119002506913824, \"validation_return\": -0.1901792375867979, \"validation_returns_over_hodl\": -0.03167687734077118, \"validation_sharpe\": -1.4856255759630967}, {\"centeredness_margin\": 0.38887278053334096, \"continuous_test_metrics\": [{\"annualised_returns\": -0.5080028985530367, \"annualised_returns_over_hodl\": -0.04558048420775973, \"annualised_returns_over_uniform_hodl\": 0.7365317122790287, \"calmar\": -0.8764038855561316, \"daily_log_sharpe\": -0.7521341298589319, \"daily_returns\": 0.031016042751957646, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": -0.07042372354694292, \"return\": -0.24847805729898942, \"returns_over_hodl\": -0.018613105110129813, \"returns_over_uniform_hodl\": 0.24890468985539949, \"sharpe\": -0.2690911651352353, \"sterling\": -1.308001875211548, \"ulcer\": -0.17637381889024617}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -0.054048794192332106, \"optuna_trial_number\": 162, \"price_ratio\": 7.808566273572916, \"shift_exponent\": 0.0002990203823938153, \"step\": 162, \"test_objective\": [{\"annualised_returns\": -0.5080028985530367, \"annualised_returns_over_hodl\": -0.04558048420775973, \"annualised_returns_over_uniform_hodl\": 0.7365317122790287, \"calmar\": -0.8764038855561316, \"daily_log_sharpe\": -0.7521341298589319, \"daily_returns\": 0.031016042751957646, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": -0.07042372354694292, \"return\": -0.24847805729898942, \"returns_over_hodl\": -0.018613105110129813, \"returns_over_uniform_hodl\": 0.24890468985539949, \"sharpe\": -0.2690911651352353, \"sterling\": -1.308001875211548, \"ulcer\": -0.17637381889024617}], \"train_objective\": [{\"annualised_returns\": -0.44441166609090765, \"annualised_returns_over_hodl\": -0.30032640151512136, \"annualised_returns_over_uniform_hodl\": -0.30032640151512113, \"calmar\": -0.5956410309403559, \"daily_log_sharpe\": -0.44047788049472897, \"daily_returns\": 0.008051517983001247, \"fee_revenue_over_value\": 0.0019592917893657843, \"jax_sharpe\": 0.10861879206528621, \"return\": -0.3001029557472149, \"returns_over_hodl\": -0.1949316878526185, \"returns_over_uniform_hodl\": -0.19493168785261838, \"sharpe\": 0.12481842122832151, \"sterling\": -1.3463179313647757, \"ulcer\": -0.17859342447640553}], \"train_return\": -0.3001029557472149, \"train_returns_over_hodl\": -0.1949316878526185, \"train_sharpe\": 0.1086187920652862, \"validation_return\": -0.33148852002295137, \"validation_returns_over_hodl\": -0.054048794192332106, \"validation_sharpe\": -2.1768680478499314}, {\"centeredness_margin\": 0.3828760190957401, \"continuous_test_metrics\": [{\"annualised_returns\": -0.4845064531409411, \"annualised_returns_over_hodl\": 8.821832153671494e-13, \"annualised_returns_over_uniform_hodl\": 0.8194637508295355, \"calmar\": -0.8358049680631612, \"daily_log_sharpe\": -0.6924635969475892, \"daily_returns\": 0.03101604275044927, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": 0.005019283964355778, \"return\": -0.23422459931196715, \"returns_over_hodl\": 3.552713678800501e-13, \"returns_over_uniform_hodl\": 0.2725915704575419, \"sharpe\": -0.20210852973706647, \"sterling\": -1.24738431112377, \"ulcer\": -0.1764103165117272}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -9.861456096160737e-11, \"optuna_trial_number\": 163, \"price_ratio\": 2.739999575989077, \"shift_exponent\": 0.0003016363998944638, \"step\": 163, \"test_objective\": [{\"annualised_returns\": -0.4845064531409411, \"annualised_returns_over_hodl\": 8.821832153671494e-13, \"annualised_returns_over_uniform_hodl\": 0.8194637508295355, \"calmar\": -0.8358049680631612, \"daily_log_sharpe\": -0.6924635969475892, \"daily_returns\": 0.03101604275044927, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": 0.005019283964355778, \"return\": -0.23422459931196715, \"returns_over_hodl\": 3.552713678800501e-13, \"returns_over_uniform_hodl\": 0.2725915704575419, \"sharpe\": -0.20210852973706647, \"sterling\": -1.24738431112377, \"ulcer\": -0.1764103165117272}], \"train_objective\": [{\"annualised_returns\": -0.58020215855901, \"annualised_returns_over_hodl\": -0.47133255248433303, \"annualised_returns_over_uniform_hodl\": -0.47133255248433303, \"calmar\": -0.7518006587430037, \"daily_log_sharpe\": -0.5919448339808325, \"daily_returns\": 0.008321747665277162, \"fee_revenue_over_value\": 0.0017729572886908138, \"jax_sharpe\": 0.13263893128509754, \"return\": -0.4096091136735067, \"returns_over_hodl\": -0.3208929823821368, \"returns_over_uniform_hodl\": -0.3208929823821368, \"sharpe\": 0.06235730995898789, \"sterling\": -1.5922961560708901, \"ulcer\": -0.20133740078886364}], \"train_return\": -0.4096091136735067, \"train_returns_over_hodl\": -0.3208929823821368, \"train_sharpe\": 0.13263893128509754, \"validation_return\": -0.33914272229261555, \"validation_returns_over_hodl\": -9.861456096160737e-11, \"validation_sharpe\": -2.2438532051058764}, {\"centeredness_margin\": 0.39373152254342, \"continuous_test_metrics\": [{\"annualised_returns\": -0.7131386992785437, \"annualised_returns_over_hodl\": -0.07734417473686528, \"annualised_returns_over_uniform_hodl\": 0.012493253036205632, \"calmar\": -1.2000421533269225, \"daily_log_sharpe\": -1.5711748985633807, \"daily_returns\": 0.0199062668006935, \"fee_revenue_over_value\": 0.00974317121179772, \"jax_sharpe\": -1.027198114333237, \"return\": -0.3952387069797558, \"returns_over_hodl\": -0.031900097990101406, \"returns_over_uniform_hodl\": 0.005012857484171462, \"sharpe\": -1.1820948250971748, \"sterling\": -2.1465874308284194, \"ulcer\": -0.14993479967602336}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -0.02643777215840229, \"optuna_trial_number\": 164, \"price_ratio\": 90.30861897559271, \"shift_exponent\": 0.07869452253438124, \"step\": 164, \"test_objective\": [{\"annualised_returns\": -0.7131386992785437, \"annualised_returns_over_hodl\": -0.07734417473686528, \"annualised_returns_over_uniform_hodl\": 0.012493253036205632, \"calmar\": -1.2000421533269225, \"daily_log_sharpe\": -1.5711748985633807, \"daily_returns\": 0.0199062668006935, \"fee_revenue_over_value\": 0.00974317121179772, \"jax_sharpe\": -1.027198114333237, \"return\": -0.3952387069797558, \"returns_over_hodl\": -0.031900097990101406, \"returns_over_uniform_hodl\": 0.005012857484171462, \"sharpe\": -1.1820948250971748, \"sterling\": -2.1465874308284194, \"ulcer\": -0.14993479967602336}], \"train_objective\": [{\"annualised_returns\": -0.339682288668665, \"annualised_returns_over_hodl\": -0.1684366984817035, \"annualised_returns_over_uniform_hodl\": -0.1684366984817035, \"calmar\": -0.4620743801412231, \"daily_log_sharpe\": -0.3467522952669391, \"daily_returns\": 0.00795300002531844, \"fee_revenue_over_value\": 0.0054821380783478275, \"jax_sharpe\": 0.12552225244899562, \"return\": -0.22273670545881874, \"returns_over_hodl\": -0.10593986105708308, \"returns_over_uniform_hodl\": -0.10593986105708308, \"sharpe\": 0.1562768078360985, \"sterling\": -1.1027682899758549, \"ulcer\": -0.16640043954187594}], \"train_return\": -0.22273670545881874, \"train_returns_over_hodl\": -0.10593986105708308, \"train_sharpe\": 0.12552225244899562, \"validation_return\": -0.18476146979815022, \"validation_returns_over_hodl\": -0.026437772158402262, \"validation_sharpe\": -1.4561599899327748}, {\"centeredness_margin\": 0.7785259316268119, \"continuous_test_metrics\": [{\"annualised_returns\": -0.4807308611383794, \"annualised_returns_over_hodl\": 0.007324227377085046, \"annualised_returns_over_uniform_hodl\": 0.8327899172353797, \"calmar\": -0.8292918164721207, \"daily_log_sharpe\": -0.6848859362534099, \"daily_returns\": 0.031016042753013177, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": 0.00858451486498486, \"return\": -0.23197067980826047, \"returns_over_hodl\": 0.002943316629846704, \"returns_over_uniform_hodl\": 0.27633721044327375, \"sharpe\": -0.19416787201183003, \"sterling\": -1.2376638578313996, \"ulcer\": -0.17641031651703032}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -7.248834865691833e-11, \"optuna_trial_number\": 165, \"price_ratio\": 4.141899518996293, \"shift_exponent\": 0.0002517752771464106, \"step\": 165, \"test_objective\": [{\"annualised_returns\": -0.4807308611383794, \"annualised_returns_over_hodl\": 0.007324227377085046, \"annualised_returns_over_uniform_hodl\": 0.8327899172353797, \"calmar\": -0.8292918164721207, \"daily_log_sharpe\": -0.6848859362534099, \"daily_returns\": 0.031016042753013177, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": 0.00858451486498486, \"return\": -0.23197067980826047, \"returns_over_hodl\": 0.002943316629846704, \"returns_over_uniform_hodl\": 0.27633721044327375, \"sharpe\": -0.19416787201183003, \"sterling\": -1.2376638578313996, \"ulcer\": -0.17641031651703032}], \"train_objective\": [{\"annualised_returns\": -0.5147411099784358, \"annualised_returns_over_hodl\": -0.38889495503028315, \"annualised_returns_over_uniform_hodl\": -0.38889495503028304, \"calmar\": -0.6815178464663003, \"daily_log_sharpe\": -0.5077919643254841, \"daily_returns\": 0.008184417611722214, \"fee_revenue_over_value\": 5.8659179436491085e-05, \"jax_sharpe\": 0.17203999324274868, \"return\": -0.35531465689263253, \"returns_over_hodl\": -0.2584398729734475, \"returns_over_uniform_hodl\": -0.2584398729734474, \"sharpe\": 0.10799977415440525, \"sterling\": -1.4806134773030055, \"ulcer\": -0.18927233218806744}], \"train_return\": -0.35531465689263253, \"train_returns_over_hodl\": -0.2584398729734475, \"train_sharpe\": 0.17203999324274866, \"validation_return\": -0.33914272230427644, \"validation_returns_over_hodl\": -7.248834865691833e-11, \"validation_sharpe\": -2.2438532050443776}, {\"centeredness_margin\": 0.5936853190029849, \"continuous_test_metrics\": [{\"annualised_returns\": -0.4966596152542634, \"annualised_returns_over_hodl\": -0.023575779520365492, \"annualised_returns_over_uniform_hodl\": 0.7765684749179815, \"calmar\": -0.856769996338069, \"daily_log_sharpe\": -0.7266497258860103, \"daily_returns\": 0.031016042754925813, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": -0.04568387986935547, \"return\": -0.24154737129739934, \"returns_over_hodl\": -0.00956255846615095, \"returns_over_uniform_hodl\": 0.2604223392538876, \"sharpe\": -0.24172294831462157, \"sterling\": -1.278673246071747, \"ulcer\": -0.17641031652098113}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -0.03657620462274647, \"optuna_trial_number\": 166, \"price_ratio\": 6.394968169026925, \"shift_exponent\": 0.000413189805657448, \"step\": 166, \"test_objective\": [{\"annualised_returns\": -0.4966596152542634, \"annualised_returns_over_hodl\": -0.023575779520365492, \"annualised_returns_over_uniform_hodl\": 0.7765684749179815, \"calmar\": -0.856769996338069, \"daily_log_sharpe\": -0.7266497258860103, \"daily_returns\": 0.031016042754925813, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": -0.04568387986935547, \"return\": -0.24154737129739934, \"returns_over_hodl\": -0.00956255846615095, \"returns_over_uniform_hodl\": 0.2604223392538876, \"sharpe\": -0.24172294831462157, \"sterling\": -1.278673246071747, \"ulcer\": -0.17641031652098113}], \"train_objective\": [{\"annualised_returns\": -0.4579494757919105, \"annualised_returns_over_hodl\": -0.3173750820776866, \"annualised_returns_over_uniform_hodl\": -0.3173750820776866, \"calmar\": -0.6121593044016709, \"daily_log_sharpe\": -0.45130079717224375, \"daily_returns\": 0.008104486747031545, \"fee_revenue_over_value\": 0.0013378857607433778, \"jax_sharpe\": 0.1591747255630508, \"return\": -0.310507008538667, \"returns_over_hodl\": -0.20689912404782163, \"returns_over_uniform_hodl\": -0.20689912404782163, \"sharpe\": 0.12442785355404695, \"sterling\": -1.3732285222240734, \"ulcer\": -0.18054418470907838}], \"train_return\": -0.310507008538667, \"train_returns_over_hodl\": -0.20689912404782163, \"train_sharpe\": 0.1591747255630508, \"validation_return\": -0.3364657673018243, \"validation_returns_over_hodl\": -0.03657620462274647, \"validation_sharpe\": -2.219971226696969}, {\"centeredness_margin\": 0.7480299261381045, \"continuous_test_metrics\": [{\"annualised_returns\": -0.4845064531343908, \"annualised_returns_over_hodl\": 8.733014311701481e-13, \"annualised_returns_over_uniform_hodl\": 0.8194637508526552, \"calmar\": -0.8358049680526659, \"daily_log_sharpe\": -0.6924635969311426, \"daily_returns\": 0.031016042750760635, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": 0.0050192839840465815, \"return\": -0.2342245993080483, \"returns_over_hodl\": 3.517186542012496e-13, \"returns_over_uniform_hodl\": 0.27259157046405447, \"sharpe\": -0.20210852971777454, \"sterling\": -1.247384311101819, \"ulcer\": -0.17641031651237069}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -9.575551462859266e-11, \"optuna_trial_number\": 167, \"price_ratio\": 2.855777161708536, \"shift_exponent\": 0.00017125303064390718, \"step\": 167, \"test_objective\": [{\"annualised_returns\": -0.4845064531343908, \"annualised_returns_over_hodl\": 8.733014311701481e-13, \"annualised_returns_over_uniform_hodl\": 0.8194637508526552, \"calmar\": -0.8358049680526659, \"daily_log_sharpe\": -0.6924635969311426, \"daily_returns\": 0.031016042750760635, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": 0.0050192839840465815, \"return\": -0.2342245993080483, \"returns_over_hodl\": 3.517186542012496e-13, \"returns_over_uniform_hodl\": 0.27259157046405447, \"sharpe\": -0.20210852971777454, \"sterling\": -1.247384311101819, \"ulcer\": -0.17641031651237069}], \"train_objective\": [{\"annualised_returns\": -0.5745202751462554, \"annualised_returns_over_hodl\": -0.46417713979666464, \"annualised_returns_over_uniform_hodl\": -0.46417713979666464, \"calmar\": -0.7458649457004656, \"daily_log_sharpe\": -0.5835489147281064, \"daily_returns\": 0.008299231951064906, \"fee_revenue_over_value\": 5.411720182042448e-05, \"jax_sharpe\": 0.11317605619659665, \"return\": -0.4047705296923535, \"returns_over_hodl\": -0.3153273200166825, \"returns_over_uniform_hodl\": -0.3153273200166825, \"sharpe\": 0.06829888380530329, \"sterling\": -1.579678549801793, \"ulcer\": -0.20071493524681108}], \"train_return\": -0.4047705296923535, \"train_returns_over_hodl\": -0.3153273200166825, \"train_sharpe\": 0.11317605619659665, \"validation_return\": -0.3391427222947888, \"validation_returns_over_hodl\": -9.575551462859266e-11, \"validation_sharpe\": -2.243853205105973}, {\"centeredness_margin\": 0.6699824792459848, \"continuous_test_metrics\": [{\"annualised_returns\": -0.48450645317285945, \"annualised_returns_over_hodl\": 1.3700152123874432e-12, \"annualised_returns_over_uniform_hodl\": 0.8194637507168774, \"calmar\": -0.8358049681143633, \"daily_log_sharpe\": -0.6924635970278629, \"daily_returns\": 0.031016042748924336, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": 0.005019283868103014, \"return\": -0.2342245993310631, \"returns_over_hodl\": 5.517808432387028e-13, \"returns_over_uniform_hodl\": 0.2725915704258075, \"sharpe\": -0.20210852983129074, \"sterling\": -1.2473843112309375, \"ulcer\": -0.17641031650855876}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -1.0770540015414554e-10, \"optuna_trial_number\": 168, \"price_ratio\": 2.1356880109844205, \"shift_exponent\": 0.0006417837970732386, \"step\": 168, \"test_objective\": [{\"annualised_returns\": -0.48450645317285945, \"annualised_returns_over_hodl\": 1.3700152123874432e-12, \"annualised_returns_over_uniform_hodl\": 0.8194637507168774, \"calmar\": -0.8358049681143633, \"daily_log_sharpe\": -0.6924635970278629, \"daily_returns\": 0.031016042748924336, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": 0.005019283868103014, \"return\": -0.2342245993310631, \"returns_over_hodl\": 5.517808432387028e-13, \"returns_over_uniform_hodl\": 0.2725915704258075, \"sharpe\": -0.20210852983129074, \"sterling\": -1.2473843112309375, \"ulcer\": -0.17641031650855876}], \"train_objective\": [{\"annualised_returns\": -0.6135338008350508, \"annualised_returns_over_hodl\": -0.5133083620385055, \"annualised_returns_over_uniform_hodl\": -0.5133083620385055, \"calmar\": -0.786496870616097, \"daily_log_sharpe\": -0.6444885150540162, \"daily_returns\": 0.008434694908858112, \"fee_revenue_over_value\": 5.74915018270399e-05, \"jax_sharpe\": 0.09601197778484045, \"return\": -0.43852998018329625, \"returns_over_hodl\": -0.35415969407647363, \"returns_over_uniform_hodl\": -0.35415969407647363, \"sharpe\": 0.022553681576329653, \"sterling\": -1.6656386535010548, \"ulcer\": -0.2052581654927693}], \"train_return\": -0.43852998018329625, \"train_returns_over_hodl\": -0.35415969407647363, \"train_sharpe\": 0.09601197778484045, \"validation_return\": -0.33914272228239584, \"validation_returns_over_hodl\": -1.0770540015414554e-10, \"validation_sharpe\": -2.2438532051093865}, {\"centeredness_margin\": 0.9473408752468008, \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -Infinity, \"optuna_trial_number\": 169, \"price_ratio\": 1.0278502463493482, \"shift_exponent\": 2.569764907422807, \"step\": 169, \"test_objective\": -Infinity, \"train_objective\": -Infinity, \"train_return\": -Infinity, \"train_returns_over_hodl\": -Infinity, \"train_sharpe\": -Infinity, \"validation_return\": -Infinity, \"validation_returns_over_hodl\": -Infinity, \"validation_sharpe\": -Infinity}, {\"centeredness_margin\": 0.7888041332985213, \"continuous_test_metrics\": [{\"annualised_returns\": -0.7277053201348034, \"annualised_returns_over_hodl\": -0.3254491167617912, \"annualised_returns_over_uniform_hodl\": -0.03892046257969173, \"calmar\": -1.2277382741457397, \"daily_log_sharpe\": -1.5594853454419457, \"daily_returns\": 0.02542760386980139, \"fee_revenue_over_value\": 0.006841862339752485, \"jax_sharpe\": -0.9301176774157126, \"return\": -0.40779932318095147, \"returns_over_hodl\": -0.14662923895574287, \"returns_over_uniform_hodl\": -0.015860801140840763, \"sharpe\": -1.156058299385664, \"sterling\": -2.044396505166924, \"ulcer\": -0.15415871132785622}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -0.05462525125928175, \"optuna_trial_number\": 170, \"price_ratio\": 4.3904123342448385, \"shift_exponent\": 0.003569767398470309, \"step\": 170, \"test_objective\": [{\"annualised_returns\": -0.7277053201348034, \"annualised_returns_over_hodl\": -0.3254491167617912, \"annualised_returns_over_uniform_hodl\": -0.03892046257969173, \"calmar\": -1.2277382741457397, \"daily_log_sharpe\": -1.5594853454419457, \"daily_returns\": 0.02542760386980139, \"fee_revenue_over_value\": 0.006841862339752485, \"jax_sharpe\": -0.9301176774157126, \"return\": -0.40779932318095147, \"returns_over_hodl\": -0.14662923895574287, \"returns_over_uniform_hodl\": -0.015860801140840763, \"sharpe\": -1.156058299385664, \"sterling\": -2.044396505166924, \"ulcer\": -0.15415871132785622}], \"train_objective\": [{\"annualised_returns\": -0.36669413020949715, \"annualised_returns_over_hodl\": -0.20245374171153807, \"annualised_returns_over_uniform_hodl\": -0.20245374171153807, \"calmar\": -0.4911455127769894, \"daily_log_sharpe\": -0.3561577890968763, \"daily_returns\": 0.00818595903463149, \"fee_revenue_over_value\": 0.000417275326696949, \"jax_sharpe\": 0.22908707281349402, \"return\": -0.24219874284386245, \"returns_over_hodl\": -0.12832639592987138, \"returns_over_uniform_hodl\": -0.12832639592987138, \"sharpe\": 0.18856123279253242, \"sterling\": -1.1442857758211886, \"ulcer\": -0.17193595219777086}], \"train_return\": -0.24219874284386245, \"train_returns_over_hodl\": -0.12832639592987138, \"train_sharpe\": 0.22908707281349405, \"validation_return\": -0.22933992170963946, \"validation_returns_over_hodl\": -0.05462525125928175, \"validation_sharpe\": -1.7159077039920652}, {\"centeredness_margin\": 0.1949656119376541, \"continuous_test_metrics\": [{\"annualised_returns\": -0.6826250307809567, \"annualised_returns_over_hodl\": -0.3843279491601006, \"annualised_returns_over_uniform_hodl\": 0.12019297900652548, \"calmar\": -1.1629265116543908, \"daily_log_sharpe\": -1.2962341278510354, \"daily_returns\": 0.031131315725331503, \"fee_revenue_over_value\": 0.011242576765467206, \"jax_sharpe\": -0.650557186437783, \"return\": -0.37011036397937636, \"returns_over_hodl\": -0.17744858945452213, \"returns_over_uniform_hodl\": 0.0467719913674447, \"sharpe\": -0.8622246483555596, \"sterling\": -1.84990386097414, \"ulcer\": -0.1626421395388853}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -0.05604345485946127, \"optuna_trial_number\": 171, \"price_ratio\": 2.9988350386976927, \"shift_exponent\": 0.006522166895224774, \"step\": 171, \"test_objective\": [{\"annualised_returns\": -0.6826250307809567, \"annualised_returns_over_hodl\": -0.3843279491601006, \"annualised_returns_over_uniform_hodl\": 0.12019297900652548, \"calmar\": -1.1629265116543908, \"daily_log_sharpe\": -1.2962341278510354, \"daily_returns\": 0.031131315725331503, \"fee_revenue_over_value\": 0.011242576765467206, \"jax_sharpe\": -0.650557186437783, \"return\": -0.37011036397937636, \"returns_over_hodl\": -0.17744858945452213, \"returns_over_uniform_hodl\": 0.0467719913674447, \"sharpe\": -0.8622246483555596, \"sterling\": -1.84990386097414, \"ulcer\": -0.1626421395388853}], \"train_objective\": [{\"annualised_returns\": -0.4887024187210701, \"annualised_returns_over_hodl\": -0.35610343710244474, \"annualised_returns_over_uniform_hodl\": -0.35610343710244485, \"calmar\": -0.6417529081703652, \"daily_log_sharpe\": -0.4927763708446591, \"daily_returns\": 0.008223050038514305, \"fee_revenue_over_value\": 0.0033464052302850443, \"jax_sharpe\": 0.10668808604864295, \"return\": -0.334528292072092, \"returns_over_hodl\": -0.23453000825953352, \"returns_over_uniform_hodl\": -0.23453000825953363, \"sharpe\": 0.10424610213768164, \"sterling\": -1.4304221842079783, \"ulcer\": -0.18476389198034995}], \"train_return\": -0.334528292072092, \"train_returns_over_hodl\": -0.23453000825953352, \"train_sharpe\": 0.10668808604864297, \"validation_return\": -0.31439614948607475, \"validation_returns_over_hodl\": -0.05604345485946127, \"validation_sharpe\": -2.1624905498148923}, {\"centeredness_margin\": 0.49682745630420977, \"continuous_test_metrics\": [{\"annualised_returns\": -0.4770516502832366, \"annualised_returns_over_hodl\": 0.014461486133005241, \"annualised_returns_over_uniform_hodl\": 0.8457759009074919, \"calmar\": -0.8229449290913764, \"daily_log_sharpe\": -0.6800793853309464, \"daily_returns\": 0.031016042754128975, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": 0.005430193715728094, \"return\": -0.22978369412277244, \"returns_over_hodl\": 0.005799226690815118, \"returns_over_uniform_hodl\": 0.2799716175364799, \"sharpe\": -0.18984119821524223, \"sterling\": -1.2281915430403447, \"ulcer\": -0.17641031651933436}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -6.910905181456428e-11, \"optuna_trial_number\": 172, \"price_ratio\": 5.922174263036517, \"shift_exponent\": 1.6562648104185294e-05, \"step\": 172, \"test_objective\": [{\"annualised_returns\": -0.4770516502832366, \"annualised_returns_over_hodl\": 0.014461486133005241, \"annualised_returns_over_uniform_hodl\": 0.8457759009074919, \"calmar\": -0.8229449290913764, \"daily_log_sharpe\": -0.6800793853309464, \"daily_returns\": 0.031016042754128975, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": 0.005430193715728094, \"return\": -0.22978369412277244, \"returns_over_hodl\": 0.005799226690815118, \"returns_over_uniform_hodl\": 0.2799716175364799, \"sharpe\": -0.18984119821524223, \"sterling\": -1.2281915430403447, \"ulcer\": -0.17641031651933436}], \"train_objective\": [{\"annualised_returns\": -0.47950218085418705, \"annualised_returns_over_hodl\": -0.34451722633745774, \"annualised_returns_over_uniform_hodl\": -0.34451722633745785, \"calmar\": -0.6399808456185301, \"daily_log_sharpe\": -0.47165880882340167, \"daily_returns\": 0.008105180103016533, \"fee_revenue_over_value\": 0.0018532706858679214, \"jax_sharpe\": 0.16159538539503185, \"return\": -0.3272838431645384, \"returns_over_hodl\": -0.22619695941708762, \"returns_over_uniform_hodl\": -0.22619695941708773, \"sharpe\": 0.11614075085159324, \"sterling\": -1.4196053041507735, \"ulcer\": -0.18321757319264315}], \"train_return\": -0.3272838431645384, \"train_returns_over_hodl\": -0.22619695941708762, \"train_sharpe\": 0.16159538539503185, \"validation_return\": -0.33914272230714904, \"validation_returns_over_hodl\": -6.910905181456428e-11, \"validation_sharpe\": -2.2438532049958244}, {\"centeredness_margin\": 0.7002446374494266, \"continuous_test_metrics\": [{\"annualised_returns\": -0.5876410961340233, \"annualised_returns_over_hodl\": -0.1835127047748144, \"annualised_returns_over_uniform_hodl\": 0.4554441709065229, \"calmar\": -1.0137686631790035, \"daily_log_sharpe\": -0.9626600686097719, \"daily_returns\": 0.030608860930003572, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": -0.29206064219337863, \"return\": -0.3000663993785032, \"returns_over_hodl\": -0.07840797984966041, \"returns_over_uniform_hodl\": 0.16317343078742286, \"sharpe\": -0.49966175341928476, \"sterling\": -1.5437820505145101, \"ulcer\": -0.17001685254818483}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -0.061349507169384615, \"optuna_trial_number\": 173, \"price_ratio\": 11.027892764719653, \"shift_exponent\": 0.0008020748280719702, \"step\": 173, \"test_objective\": [{\"annualised_returns\": -0.5876410961340233, \"annualised_returns_over_hodl\": -0.1835127047748144, \"annualised_returns_over_uniform_hodl\": 0.4554441709065229, \"calmar\": -1.0137686631790035, \"daily_log_sharpe\": -0.9626600686097719, \"daily_returns\": 0.030608860930003572, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": -0.29206064219337863, \"return\": -0.3000663993785032, \"returns_over_hodl\": -0.07840797984966041, \"returns_over_uniform_hodl\": 0.16317343078742286, \"sharpe\": -0.49966175341928476, \"sterling\": -1.5437820505145101, \"ulcer\": -0.17001685254818483}], \"train_objective\": [{\"annualised_returns\": -0.40576855782129884, \"annualised_returns_over_hodl\": -0.2516616600699526, \"annualised_returns_over_uniform_hodl\": -0.2516616600699526, \"calmar\": -0.5461165340449419, \"daily_log_sharpe\": -0.4055733274993356, \"daily_returns\": 0.008039428377879973, \"fee_revenue_over_value\": 0.0005527568246986937, \"jax_sharpe\": 0.11640355066517794, \"return\": -0.27093940187384347, \"returns_over_hodl\": -0.16138582094855058, \"returns_over_uniform_hodl\": -0.16138582094855058, \"sharpe\": 0.13698777198126844, \"sterling\": -1.2592772158779026, \"ulcer\": -0.1739075435805472}], \"train_return\": -0.27093940187384347, \"train_returns_over_hodl\": -0.16138582094855058, \"train_sharpe\": 0.11640355066517795, \"validation_return\": -0.28694910073142743, \"validation_returns_over_hodl\": -0.06134950716938459, \"validation_sharpe\": -1.9748129088613444}, {\"centeredness_margin\": 0.4137764208012362, \"continuous_test_metrics\": [{\"annualised_returns\": -0.5139607193806623, \"annualised_returns_over_hodl\": -0.057137992301591445, \"annualised_returns_over_uniform_hodl\": 0.7155032452965582, \"calmar\": -0.8869647440022476, \"daily_log_sharpe\": -0.7656004302456649, \"daily_returns\": 0.0310160427529902, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": -0.08317800330401161, \"return\": -0.25215652406980227, \"returns_over_hodl\": -0.02341668952343412, \"returns_over_uniform_hodl\": 0.2427916888363817, \"sharpe\": -0.2834997582367361, \"sterling\": -1.324099293840568, \"ulcer\": -0.17615530302569374}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -0.0614894664291209, \"optuna_trial_number\": 174, \"price_ratio\": 10.042701039123024, \"shift_exponent\": 1.2253127579234431e-05, \"step\": 174, \"test_objective\": [{\"annualised_returns\": -0.5139607193806623, \"annualised_returns_over_hodl\": -0.057137992301591445, \"annualised_returns_over_uniform_hodl\": 0.7155032452965582, \"calmar\": -0.8869647440022476, \"daily_log_sharpe\": -0.7656004302456649, \"daily_returns\": 0.0310160427529902, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": -0.08317800330401161, \"return\": -0.25215652406980227, \"returns_over_hodl\": -0.02341668952343412, \"returns_over_uniform_hodl\": 0.2427916888363817, \"sharpe\": -0.2834997582367361, \"sterling\": -1.324099293840568, \"ulcer\": -0.17615530302569374}], \"train_objective\": [{\"annualised_returns\": -0.43226452875464316, \"annualised_returns_over_hodl\": -0.28502904775040916, \"annualised_returns_over_uniform_hodl\": -0.2850290477504095, \"calmar\": -0.5810397825441208, \"daily_log_sharpe\": -0.4305142808421789, \"daily_returns\": 0.008034600969947786, \"fee_revenue_over_value\": 0.0019149340770256494, \"jax_sharpe\": 0.1057848773598833, \"return\": -0.2908521399728541, \"returns_over_hodl\": -0.18429078187565118, \"returns_over_uniform_hodl\": -0.1842907818756514, \"sharpe\": 0.12534156462975998, \"sterling\": -1.3218435244069582, \"ulcer\": -0.17686209526866595}], \"train_return\": -0.2908521399728541, \"train_returns_over_hodl\": -0.18429078187565118, \"train_sharpe\": 0.10578487735988329, \"validation_return\": -0.32385895634060524, \"validation_returns_over_hodl\": -0.0614894664291209, \"validation_sharpe\": -2.1220698035429466}, {\"centeredness_margin\": 0.40118775567300186, \"continuous_test_metrics\": [{\"annualised_returns\": -0.47671056865432215, \"annualised_returns_over_hodl\": 0.01512314646118984, \"annualised_returns_over_uniform_hodl\": 0.8469797678883011, \"calmar\": -0.8223565402402887, \"daily_log_sharpe\": -0.6791412559203548, \"daily_returns\": 0.031016042754103433, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": 0.006704290137302511, \"return\": -0.2295814154525212, \"returns_over_hodl\": 0.006063375513525537, \"returns_over_uniform_hodl\": 0.2803077711011073, \"sharpe\": -0.18871979391652954, \"sterling\": -1.2273134111204165, \"ulcer\": -0.17641031651927758}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -6.06683592252466e-11, \"optuna_trial_number\": 175, \"price_ratio\": 5.492949735677299, \"shift_exponent\": 0.00010042914666246712, \"step\": 175, \"test_objective\": [{\"annualised_returns\": -0.47671056865432215, \"annualised_returns_over_hodl\": 0.01512314646118984, \"annualised_returns_over_uniform_hodl\": 0.8469797678883011, \"calmar\": -0.8223565402402887, \"daily_log_sharpe\": -0.6791412559203548, \"daily_returns\": 0.031016042754103433, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": 0.006704290137302511, \"return\": -0.2295814154525212, \"returns_over_hodl\": 0.006063375513525537, \"returns_over_uniform_hodl\": 0.2803077711011073, \"sharpe\": -0.18871979391652954, \"sterling\": -1.2273134111204165, \"ulcer\": -0.17641031651927758}], \"train_objective\": [{\"annualised_returns\": -0.4811837844232646, \"annualised_returns_over_hodl\": -0.346634933907239, \"annualised_returns_over_uniform_hodl\": -0.3466349339072391, \"calmar\": -0.6418951577349143, \"daily_log_sharpe\": -0.47038069281557343, \"daily_returns\": 0.00812103165862232, \"fee_revenue_over_value\": 0.0018992055973054988, \"jax_sharpe\": 0.11280199854526085, \"return\": -0.3286041900522273, \"returns_over_hodl\": -0.22771571056634488, \"returns_over_uniform_hodl\": -0.227715710566345, \"sharpe\": 0.12201335359381989, \"sterling\": -1.4187968335561654, \"ulcer\": -0.18396057534047836}], \"train_return\": -0.3286041900522273, \"train_returns_over_hodl\": -0.22771571056634488, \"train_sharpe\": 0.11280199854526084, \"validation_return\": -0.3391427223078536, \"validation_returns_over_hodl\": -6.06683592252466e-11, \"validation_sharpe\": -2.2438532050044624}, {\"centeredness_margin\": 0.38747674972244806, \"continuous_test_metrics\": [{\"annualised_returns\": -0.4794686126380262, \"annualised_returns_over_hodl\": 0.009772848663956557, \"annualised_returns_over_uniform_hodl\": 0.8372450950061299, \"calmar\": -0.8271143524798891, \"daily_log_sharpe\": -0.6861732954157526, \"daily_returns\": 0.03101604275433725, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": -0.0023884285414700933, \"return\": -0.23121933792227356, \"returns_over_hodl\": 0.003924468372960455, \"returns_over_uniform_hodl\": 0.2775858159087661, \"sharpe\": -0.19684116351914488, \"sterling\": -1.2344141350517968, \"ulcer\": -0.17641031651975803}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -0.003417228689967855, \"optuna_trial_number\": 176, \"price_ratio\": 6.191991572777945, \"shift_exponent\": 5.787181501452889e-05, \"step\": 176, \"test_objective\": [{\"annualised_returns\": -0.4794686126380262, \"annualised_returns_over_hodl\": 0.009772848663956557, \"annualised_returns_over_uniform_hodl\": 0.8372450950061299, \"calmar\": -0.8271143524798891, \"daily_log_sharpe\": -0.6861732954157526, \"daily_returns\": 0.03101604275433725, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": -0.0023884285414700933, \"return\": -0.23121933792227356, \"returns_over_hodl\": 0.003924468372960455, \"returns_over_uniform_hodl\": 0.2775858159087661, \"sharpe\": -0.19684116351914488, \"sterling\": -1.2344141350517968, \"ulcer\": -0.17641031651975803}], \"train_objective\": [{\"annualised_returns\": -0.4737564504361995, \"annualised_returns_over_hodl\": -0.33728140867873924, \"annualised_returns_over_uniform_hodl\": -0.337281408678739, \"calmar\": -0.6327267199850828, \"daily_log_sharpe\": -0.4666641939110619, \"daily_returns\": 0.008108320783658345, \"fee_revenue_over_value\": 0.0019096281091805843, \"jax_sharpe\": 0.10449829445342951, \"return\": -0.3227850529065047, \"returns_over_hodl\": -0.22102215047985752, \"returns_over_uniform_hodl\": -0.2210221504798574, \"sharpe\": 0.1172163667869807, \"sterling\": -1.4083144700608166, \"ulcer\": -0.18239405416186344}], \"train_return\": -0.3227850529065047, \"train_returns_over_hodl\": -0.22102215047985752, \"train_sharpe\": 0.10449829445342951, \"validation_return\": -0.33905464039530386, \"validation_returns_over_hodl\": -0.003417228689967855, \"validation_sharpe\": -2.2430663852213963}, {\"centeredness_margin\": 0.3437893649652679, \"continuous_test_metrics\": [{\"annualised_returns\": -0.4860042106811425, \"annualised_returns_over_hodl\": -0.002905482791021541, \"annualised_returns_over_uniform_hodl\": 0.8141773305270257, \"calmar\": -0.8383887012066276, \"daily_log_sharpe\": -0.7020748837136483, \"daily_returns\": 0.031016042754715013, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": -0.0201996247495395, \"return\": -0.235121448366873, \"returns_over_hodl\": -0.001171164688088866, \"returns_over_uniform_hodl\": 0.2711011562365828, \"sharpe\": -0.21478900555603947, \"sterling\": -1.2512403639568361, \"ulcer\": -0.17641031652053393}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -0.02488554332651416, \"optuna_trial_number\": 177, \"price_ratio\": 6.618548406682326, \"shift_exponent\": 0.00016157774739975, \"step\": 177, \"test_objective\": [{\"annualised_returns\": -0.4860042106811425, \"annualised_returns_over_hodl\": -0.002905482791021541, \"annualised_returns_over_uniform_hodl\": 0.8141773305270257, \"calmar\": -0.8383887012066276, \"daily_log_sharpe\": -0.7020748837136483, \"daily_returns\": 0.031016042754715013, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": -0.0201996247495395, \"return\": -0.235121448366873, \"returns_over_hodl\": -0.001171164688088866, \"returns_over_uniform_hodl\": 0.2711011562365828, \"sharpe\": -0.21478900555603947, \"sterling\": -1.2512403639568361, \"ulcer\": -0.17641031652053393}], \"train_objective\": [{\"annualised_returns\": -0.4632151649548246, \"annualised_returns_over_hodl\": -0.3240063654583092, \"annualised_returns_over_uniform_hodl\": -0.3240063654583092, \"calmar\": -0.6192890218088773, \"daily_log_sharpe\": -0.45682637129085535, \"daily_returns\": 0.008071373339370951, \"fee_revenue_over_value\": 0.0020095722944501, \"jax_sharpe\": 0.10733384776444549, \"return\": -0.3145813043593132, \"returns_over_hodl\": -0.21158565113984373, \"returns_over_uniform_hodl\": -0.21158565113984373, \"sharpe\": 0.12087691977860467, \"sterling\": -1.3858347917319747, \"ulcer\": -0.18107430964342755}], \"train_return\": -0.3145813043593132, \"train_returns_over_hodl\": -0.21158565113984373, \"train_sharpe\": 0.1073338477644455, \"validation_return\": -0.33792839809093134, \"validation_returns_over_hodl\": -0.02488554332651416, \"validation_sharpe\": -2.232767045486906}, {\"centeredness_margin\": 0.28699735292509704, \"continuous_test_metrics\": [{\"annualised_returns\": -0.48146287791664333, \"annualised_returns_over_hodl\": 0.00590419639652473, \"annualised_returns_over_uniform_hodl\": 0.830206222441982, \"calmar\": -0.8305545949166032, \"daily_log_sharpe\": -0.6914806269486466, \"daily_returns\": 0.031016042754365914, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": -0.009086427064192477, \"return\": -0.23240690593249014, \"returns_over_hodl\": 0.002373663777471835, \"returns_over_uniform_hodl\": 0.2756122750535901, \"sharpe\": -0.20303171246951965, \"sterling\": -1.239548472134333, \"ulcer\": -0.17641031651981784}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -0.007342126269559435, \"optuna_trial_number\": 178, \"price_ratio\": 6.004193587963551, \"shift_exponent\": 0.00021943711531071278, \"step\": 178, \"test_objective\": [{\"annualised_returns\": -0.48146287791664333, \"annualised_returns_over_hodl\": 0.00590419639652473, \"annualised_returns_over_uniform_hodl\": 0.830206222441982, \"calmar\": -0.8305545949166032, \"daily_log_sharpe\": -0.6914806269486466, \"daily_returns\": 0.031016042754365914, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": -0.009086427064192477, \"return\": -0.23240690593249014, \"returns_over_hodl\": 0.002373663777471835, \"returns_over_uniform_hodl\": 0.2756122750535901, \"sharpe\": -0.20303171246951965, \"sterling\": -1.239548472134333, \"ulcer\": -0.17641031651981784}], \"train_objective\": [{\"annualised_returns\": -0.47268773580441825, \"annualised_returns_over_hodl\": -0.33593553554473954, \"annualised_returns_over_uniform_hodl\": -0.33593553554473954, \"calmar\": -0.6310408916950171, \"daily_log_sharpe\": -0.4646884969111207, \"daily_returns\": 0.008087958825036021, \"fee_revenue_over_value\": 0.0022282795606242433, \"jax_sharpe\": 0.10755541033195198, \"return\": -0.3219504038468768, \"returns_over_hodl\": -0.220062081402287, \"returns_over_uniform_hodl\": -0.220062081402287, \"sharpe\": 0.11982345244574374, \"sterling\": -1.4046180111652808, \"ulcer\": -0.18242495097474065}], \"train_return\": -0.3219504038468768, \"train_returns_over_hodl\": -0.220062081402287, \"train_sharpe\": 0.10755541033195197, \"validation_return\": -0.3389869843451908, \"validation_returns_over_hodl\": -0.007342126269559435, \"validation_sharpe\": -2.242469882893456}, {\"centeredness_margin\": 0.33155253402345475, \"continuous_test_metrics\": [{\"annualised_returns\": -0.4816232399359236, \"annualised_returns_over_hodl\": 0.005593112006464951, \"annualised_returns_over_uniform_hodl\": 0.8296402155872529, \"calmar\": -0.8308312302416354, \"daily_log_sharpe\": -0.6866356841648601, \"daily_returns\": 0.031016042753247687, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": 0.008445215222545654, \"return\": -0.23250251856027582, \"returns_over_hodl\": 0.0022488065235155563, \"returns_over_uniform_hodl\": 0.27545338274124864, \"sharpe\": -0.19597329191807772, \"sterling\": -1.2399613323066212, \"ulcer\": -0.17641031651751415}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -7.100431353990189e-11, \"optuna_trial_number\": 179, \"price_ratio\": 4.5953392949428125, \"shift_exponent\": 1.1415402278674371e-05, \"step\": 179, \"test_objective\": [{\"annualised_returns\": -0.4816232399359236, \"annualised_returns_over_hodl\": 0.005593112006464951, \"annualised_returns_over_uniform_hodl\": 0.8296402155872529, \"calmar\": -0.8308312302416354, \"daily_log_sharpe\": -0.6866356841648601, \"daily_returns\": 0.031016042753247687, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": 0.008445215222545654, \"return\": -0.23250251856027582, \"returns_over_hodl\": 0.0022488065235155563, \"returns_over_uniform_hodl\": 0.27545338274124864, \"sharpe\": -0.19597329191807772, \"sterling\": -1.2399613323066212, \"ulcer\": -0.17641031651751415}], \"train_objective\": [{\"annualised_returns\": -0.5080195466666546, \"annualised_returns_over_hodl\": -0.38043023375597484, \"annualised_returns_over_uniform_hodl\": -0.38043023375597484, \"calmar\": -0.6745291757693134, \"daily_log_sharpe\": -0.5009379145394276, \"daily_returns\": 0.008157998935084601, \"fee_revenue_over_value\": 0.0019278704859912136, \"jax_sharpe\": 0.16873200668652605, \"return\": -0.349907806391327, \"returns_over_hodl\": -0.2522205525135842, \"returns_over_uniform_hodl\": -0.2522205525135842, \"sharpe\": 0.10904075818022715, \"sterling\": -1.4702239722298756, \"ulcer\": -0.1880196047520581}], \"train_return\": -0.349907806391327, \"train_returns_over_hodl\": -0.2522205525135842, \"train_sharpe\": 0.16873200668652605, \"validation_return\": -0.33914272230588594, \"validation_returns_over_hodl\": -7.100431353990189e-11, \"validation_sharpe\": -2.2438532050443545}, {\"centeredness_margin\": 0.2688663821526238, \"continuous_test_metrics\": [{\"annualised_returns\": -0.6163832562711222, \"annualised_returns_over_hodl\": -0.2558262930552666, \"annualised_returns_over_uniform_hodl\": 0.3539970843064508, \"calmar\": -1.0615500246432308, \"daily_log_sharpe\": -1.0446911819087141, \"daily_returns\": 0.031016042753459254, \"fee_revenue_over_value\": 0.000449295504979162, \"jax_sharpe\": -0.37262636956314094, \"return\": -0.32013950396006596, \"returns_over_hodl\": -0.11219334626231614, \"returns_over_uniform_hodl\": 0.12981526380992792, \"sharpe\": -0.5857002293246195, \"sterling\": -1.6189673963802051, \"ulcer\": -0.170310456707863}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -0.06338186870486837, \"optuna_trial_number\": 180, \"price_ratio\": 5.268040190610262, \"shift_exponent\": 0.002815080355732074, \"step\": 180, \"test_objective\": [{\"annualised_returns\": -0.6163832562711222, \"annualised_returns_over_hodl\": -0.2558262930552666, \"annualised_returns_over_uniform_hodl\": 0.3539970843064508, \"calmar\": -1.0615500246432308, \"daily_log_sharpe\": -1.0446911819087141, \"daily_returns\": 0.031016042753459254, \"fee_revenue_over_value\": 0.000449295504979162, \"jax_sharpe\": -0.37262636956314094, \"return\": -0.32013950396006596, \"returns_over_hodl\": -0.11219334626231614, \"returns_over_uniform_hodl\": 0.12981526380992792, \"sharpe\": -0.5857002293246195, \"sterling\": -1.6189673963802051, \"ulcer\": -0.170310456707863}], \"train_objective\": [{\"annualised_returns\": -0.443693877693102, \"annualised_returns_over_hodl\": -0.29942246318418153, \"annualised_returns_over_uniform_hodl\": -0.29942246318418153, \"calmar\": -0.5918932772299871, \"daily_log_sharpe\": -0.4390855998552453, \"daily_returns\": 0.00813408772082586, \"fee_revenue_over_value\": 0.0029409050281498615, \"jax_sharpe\": 0.12349173401694725, \"return\": -0.2995541194325083, \"returns_over_hodl\": -0.19430037967786007, \"returns_over_uniform_hodl\": -0.19430037967786007, \"sharpe\": 0.132548651233677, \"sterling\": -1.3368196579340446, \"ulcer\": -0.17941404830092583}], \"train_return\": -0.2995541194325083, \"train_returns_over_hodl\": -0.19430037967786007, \"train_sharpe\": 0.12349173401694728, \"validation_return\": -0.31546088392736804, \"validation_returns_over_hodl\": -0.0633818687048684, \"validation_sharpe\": -2.107105709258646}, {\"centeredness_margin\": 0.3704109210968077, \"continuous_test_metrics\": [{\"annualised_returns\": -0.4812028785657173, \"annualised_returns_over_hodl\": 0.00640856619821939, \"annualised_returns_over_uniform_hodl\": 0.831123904917604, \"calmar\": -0.8301060784509801, \"daily_log_sharpe\": -0.6857986577476558, \"daily_returns\": 0.031016042753084994, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": 0.008350900429477209, \"return\": -0.2322519240716795, \"returns_over_hodl\": 0.0025760493291944186, \"returns_over_uniform_hodl\": 0.2758698291738506, \"sharpe\": -0.19509869098548227, \"sterling\": -1.2388790906370615, \"ulcer\": -0.1764103165171776}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -7.210620989184235e-11, \"optuna_trial_number\": 181, \"price_ratio\": 4.323476829922492, \"shift_exponent\": 0.00015395254984324077, \"step\": 181, \"test_objective\": [{\"annualised_returns\": -0.4812028785657173, \"annualised_returns_over_hodl\": 0.00640856619821939, \"annualised_returns_over_uniform_hodl\": 0.831123904917604, \"calmar\": -0.8301060784509801, \"daily_log_sharpe\": -0.6857986577476558, \"daily_returns\": 0.031016042753084994, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": 0.008350900429477209, \"return\": -0.2322519240716795, \"returns_over_hodl\": 0.0025760493291944186, \"returns_over_uniform_hodl\": 0.2758698291738506, \"sharpe\": -0.19509869098548227, \"sterling\": -1.2388790906370615, \"ulcer\": -0.1764103165171776}], \"train_objective\": [{\"annualised_returns\": -0.5122414010480325, \"annualised_returns_over_hodl\": -0.3857469761477996, \"annualised_returns_over_uniform_hodl\": -0.3857469761477996, \"calmar\": -0.6790947220633669, \"daily_log_sharpe\": -0.5050904449226964, \"daily_returns\": 0.008164193257576885, \"fee_revenue_over_value\": 0.0019110282604643748, \"jax_sharpe\": 0.16759555985833585, \"return\": -0.35330046601098486, \"returns_over_hodl\": -0.2561230161346507, \"returns_over_uniform_hodl\": -0.2561230161346507, \"sharpe\": 0.10870503658700582, \"sterling\": -1.4768707493995945, \"ulcer\": -0.18877914912783603}], \"train_return\": -0.35330046601098486, \"train_returns_over_hodl\": -0.2561230161346507, \"train_sharpe\": 0.16759555985833585, \"validation_return\": -0.3391427223048181, \"validation_returns_over_hodl\": -7.210620989184235e-11, \"validation_sharpe\": -2.2438532050450353}, {\"centeredness_margin\": 0.38866563113878394, \"continuous_test_metrics\": [{\"annualised_returns\": -0.48450645314578533, \"annualised_returns_over_hodl\": 1.2536638394067268e-12, \"annualised_returns_over_uniform_hodl\": 0.819463750812438, \"calmar\": -0.8358049680709803, \"daily_log_sharpe\": -0.6924635969598651, \"daily_returns\": 0.031016042750213534, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": 0.00501928394954315, \"return\": -0.2342245993148654, \"returns_over_hodl\": 5.049294315995212e-13, \"returns_over_uniform_hodl\": 0.27259157045272575, \"sharpe\": -0.20210852975152557, \"sterling\": -1.247384311140169, \"ulcer\": -0.17641031651122552}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -1.001498883823615e-10, \"optuna_trial_number\": 182, \"price_ratio\": 2.705843445303841, \"shift_exponent\": 0.00014174454194960644, \"step\": 182, \"test_objective\": [{\"annualised_returns\": -0.48450645314578533, \"annualised_returns_over_hodl\": 1.2536638394067268e-12, \"annualised_returns_over_uniform_hodl\": 0.819463750812438, \"calmar\": -0.8358049680709803, \"daily_log_sharpe\": -0.6924635969598651, \"daily_returns\": 0.031016042750213534, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": 0.00501928394954315, \"return\": -0.2342245993148654, \"returns_over_hodl\": 5.049294315995212e-13, \"returns_over_uniform_hodl\": 0.27259157045272575, \"sharpe\": -0.20210852975152557, \"sterling\": -1.247384311140169, \"ulcer\": -0.17641031651122552}], \"train_objective\": [{\"annualised_returns\": -0.5860508986693928, \"annualised_returns_over_hodl\": -0.4786980941810808, \"annualised_returns_over_uniform_hodl\": -0.4786980941810809, \"calmar\": -0.7584526602313904, \"daily_log_sharpe\": -0.6005311742177557, \"daily_returns\": 0.008290317925351084, \"fee_revenue_over_value\": 0.0016628791503777833, \"jax_sharpe\": 0.10808006550018527, \"return\": -0.41461674104052126, \"returns_over_hodl\": -0.32665308973696816, \"returns_over_uniform_hodl\": -0.32665308973696827, \"sharpe\": 0.056434772180273665, \"sterling\": -1.6055112985643571, \"ulcer\": -0.20211789077490933}], \"train_return\": -0.41461674104052126, \"train_returns_over_hodl\": -0.32665308973696816, \"train_sharpe\": 0.10808006550018526, \"validation_return\": -0.3391427222916198, \"validation_returns_over_hodl\": -1.001498883823615e-10, \"validation_sharpe\": -2.243853205112656}, {\"centeredness_margin\": 0.3527529840331277, \"continuous_test_metrics\": [{\"annualised_returns\": -0.48450645312589335, \"annualised_returns_over_hodl\": 8.617551117140465e-13, \"annualised_returns_over_uniform_hodl\": 0.8194637508826479, \"calmar\": -0.8358049680390515, \"daily_log_sharpe\": -0.692463596909809, \"daily_returns\": 0.03101604275116435, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": 0.005019284009591943, \"return\": -0.23422459930296446, \"returns_over_hodl\": 3.4705571749782393e-13, \"returns_over_uniform_hodl\": 0.27259157047250304, \"sharpe\": -0.20210852969274698, \"sterling\": -1.2473843110733431, \"ulcer\": -0.1764103165132054}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -9.196976513692334e-11, \"optuna_trial_number\": 183, \"price_ratio\": 2.9663325226921624, \"shift_exponent\": 0.0002635101125702716, \"step\": 183, \"test_objective\": [{\"annualised_returns\": -0.48450645312589335, \"annualised_returns_over_hodl\": 8.617551117140465e-13, \"annualised_returns_over_uniform_hodl\": 0.8194637508826479, \"calmar\": -0.8358049680390515, \"daily_log_sharpe\": -0.692463596909809, \"daily_returns\": 0.03101604275116435, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": 0.005019284009591943, \"return\": -0.23422459930296446, \"returns_over_hodl\": 3.4705571749782393e-13, \"returns_over_uniform_hodl\": 0.27259157047250304, \"sharpe\": -0.20210852969274698, \"sterling\": -1.2473843110733431, \"ulcer\": -0.1764103165132054}], \"train_objective\": [{\"annualised_returns\": -0.5633345279340246, \"annualised_returns_over_hodl\": -0.4500905013162312, \"annualised_returns_over_uniform_hodl\": -0.4500905013162313, \"calmar\": -0.7337187542379449, \"daily_log_sharpe\": -0.5669729185267092, \"daily_returns\": 0.008276839737218662, \"fee_revenue_over_value\": 0.0018958311844020123, \"jax_sharpe\": 0.14809771391663382, \"return\": -0.39531851201980106, \"returns_over_hodl\": -0.30445497818224454, \"returns_over_uniform_hodl\": -0.30445497818224465, \"sharpe\": 0.08036090564241277, \"sterling\": -1.5564391060947793, \"ulcer\": -0.1991119378470533}], \"train_return\": -0.39531851201980106, \"train_returns_over_hodl\": -0.30445497818224454, \"train_sharpe\": 0.14809771391663382, \"validation_return\": -0.3391427222967426, \"validation_returns_over_hodl\": -9.196976513692334e-11, \"validation_sharpe\": -2.243853205097567}, {\"centeredness_margin\": 0.8956521713517578, \"continuous_test_metrics\": [{\"annualised_returns\": -0.7311243533889449, \"annualised_returns_over_hodl\": -0.058096918613894544, \"annualised_returns_over_uniform_hodl\": -0.05098813463241714, \"calmar\": -1.220849466344258, \"daily_log_sharpe\": -1.6783363096868869, \"daily_returns\": 0.018222012325824193, \"fee_revenue_over_value\": 0.008001594539026028, \"jax_sharpe\": -1.1522475212671963, \"return\": -0.4108053431444726, \"returns_over_hodl\": -0.023816819207169604, \"returns_over_uniform_hodl\": -0.02085630721581677, \"sharpe\": -1.2965905080282583, \"sterling\": -2.247290092489074, \"ulcer\": -0.1478455586108326}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -0.02620153924945645, \"optuna_trial_number\": 184, \"price_ratio\": 33.06383882709604, \"shift_exponent\": 0.08189246663796182, \"step\": 184, \"test_objective\": [{\"annualised_returns\": -0.7311243533889449, \"annualised_returns_over_hodl\": -0.058096918613894544, \"annualised_returns_over_uniform_hodl\": -0.05098813463241714, \"calmar\": -1.220849466344258, \"daily_log_sharpe\": -1.6783363096868869, \"daily_returns\": 0.018222012325824193, \"fee_revenue_over_value\": 0.008001594539026028, \"jax_sharpe\": -1.1522475212671963, \"return\": -0.4108053431444726, \"returns_over_hodl\": -0.023816819207169604, \"returns_over_uniform_hodl\": -0.02085630721581677, \"sharpe\": -1.2965905080282583, \"sterling\": -2.247290092489074, \"ulcer\": -0.1478455586108326}], \"train_objective\": [{\"annualised_returns\": -0.32961720728830357, \"annualised_returns_over_hodl\": -0.15576135726477813, \"annualised_returns_over_uniform_hodl\": -0.15576135726477858, \"calmar\": -0.4488792315426385, \"daily_log_sharpe\": -0.3475122408275347, \"daily_returns\": 0.007987908405470314, \"fee_revenue_over_value\": 0.005046828303105391, \"jax_sharpe\": 0.12097877302141512, \"return\": -0.2155651257395802, \"returns_over_hodl\": -0.0976906312204836, \"returns_over_uniform_hodl\": -0.09769063122048383, \"sharpe\": 0.1396687622365658, \"sterling\": -1.0890226718121248, \"ulcer\": -0.16386394788968026}], \"train_return\": -0.2155651257395802, \"train_returns_over_hodl\": -0.0976906312204836, \"train_sharpe\": 0.12097877302141512, \"validation_return\": -0.16083787774115077, \"validation_returns_over_hodl\": -0.02620153924945645, \"validation_sharpe\": -1.2805637647560337}, {\"centeredness_margin\": 0.5245741264353212, \"continuous_test_metrics\": [{\"annualised_returns\": -0.48149013130079277, \"annualised_returns_over_hodl\": 0.005851327914382587, \"annualised_returns_over_uniform_hodl\": 0.8301100300747848, \"calmar\": -0.8306016088444699, \"daily_log_sharpe\": -0.6863817827615047, \"daily_returns\": 0.031016042753292162, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": 0.00796679291741387, \"return\": -0.23242315396872404, \"returns_over_hodl\": 0.0023524460383752555, \"returns_over_uniform_hodl\": 0.2755852735151114, \"sharpe\": -0.19571684928921781, \"sterling\": -1.2396186373718479, \"ulcer\": -0.17641031651760053}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -7.032496807113375e-11, \"optuna_trial_number\": 185, \"price_ratio\": 4.637006159566315, \"shift_exponent\": 2.458900640820119e-05, \"step\": 185, \"test_objective\": [{\"annualised_returns\": -0.48149013130079277, \"annualised_returns_over_hodl\": 0.005851327914382587, \"annualised_returns_over_uniform_hodl\": 0.8301100300747848, \"calmar\": -0.8306016088444699, \"daily_log_sharpe\": -0.6863817827615047, \"daily_returns\": 0.031016042753292162, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": 0.00796679291741387, \"return\": -0.23242315396872404, \"returns_over_hodl\": 0.0023524460383752555, \"returns_over_uniform_hodl\": 0.2755852735151114, \"sharpe\": -0.19571684928921781, \"sterling\": -1.2396186373718479, \"ulcer\": -0.17641031651760053}], \"train_objective\": [{\"annualised_returns\": -0.5058406626060687, \"annualised_returns_over_hodl\": -0.37768628187954656, \"annualised_returns_over_uniform_hodl\": -0.37768628187954656, \"calmar\": -0.6718847527116842, \"daily_log_sharpe\": -0.49803266699200816, \"daily_returns\": 0.008148415886597541, \"fee_revenue_over_value\": 0.0017089831769416116, \"jax_sharpe\": 0.11263336564122939, \"return\": -0.34816134280915156, \"returns_over_hodl\": -0.25021165349068575, \"returns_over_uniform_hodl\": -0.25021165349068575, \"sharpe\": 0.11097165401790114, \"sterling\": -1.4656607958477987, \"ulcer\": -0.18772789531865774}], \"train_return\": -0.34816134280915156, \"train_returns_over_hodl\": -0.25021165349068575, \"train_sharpe\": 0.11263336564122937, \"validation_return\": -0.3391427223058996, \"validation_returns_over_hodl\": -7.032496807113375e-11, \"validation_sharpe\": -2.24385320504164}, {\"centeredness_margin\": 0.5873484233831054, \"continuous_test_metrics\": [{\"annualised_returns\": -0.4806865429420476, \"annualised_returns_over_hodl\": 0.0074101997199960135, \"annualised_returns_over_uniform_hodl\": 0.8329463408263627, \"calmar\": -0.8292153645879711, \"daily_log_sharpe\": -0.6848083364430952, \"daily_returns\": 0.031016042753408107, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": 0.00828370258680098, \"return\": -0.23194428134082712, \"returns_over_hodl\": 0.0029777894823774798, \"returns_over_uniform_hodl\": 0.27638108031307973, \"sharpe\": -0.1940958968282649, \"sterling\": -1.237549758381071, \"ulcer\": -0.1764103165178429}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -6.871903046601346e-11, \"optuna_trial_number\": 186, \"price_ratio\": 4.72174493933035, \"shift_exponent\": 5.8383964118506764e-05, \"step\": 186, \"test_objective\": [{\"annualised_returns\": -0.4806865429420476, \"annualised_returns_over_hodl\": 0.0074101997199960135, \"annualised_returns_over_uniform_hodl\": 0.8329463408263627, \"calmar\": -0.8292153645879711, \"daily_log_sharpe\": -0.6848083364430952, \"daily_returns\": 0.031016042753408107, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": 0.00828370258680098, \"return\": -0.23194428134082712, \"returns_over_hodl\": 0.0029777894823774798, \"returns_over_uniform_hodl\": 0.27638108031307973, \"sharpe\": -0.1940958968282649, \"sterling\": -1.237549758381071, \"ulcer\": -0.1764103165178429}], \"train_objective\": [{\"annualised_returns\": -0.5041154306007298, \"annualised_returns_over_hodl\": -0.37551363135446514, \"annualised_returns_over_uniform_hodl\": -0.3755136313544649, \"calmar\": -0.6695821661825673, \"daily_log_sharpe\": -0.49676916883844474, \"daily_returns\": 0.0081095469367518, \"fee_revenue_over_value\": 0.0007013725199834512, \"jax_sharpe\": 0.1684534194465409, \"return\": -0.3467806448702899, \"returns_over_hodl\": -0.2486234825327539, \"returns_over_uniform_hodl\": -0.2486234825327538, \"sharpe\": 0.11017481113531745, \"sterling\": -1.4634853638884067, \"ulcer\": -0.18729699524399387}], \"train_return\": -0.3467806448702899, \"train_returns_over_hodl\": -0.2486234825327539, \"train_sharpe\": 0.1684534194465409, \"validation_return\": -0.3391427223061273, \"validation_returns_over_hodl\": -6.871903046601346e-11, \"validation_sharpe\": -2.2438532050356423}, {\"centeredness_margin\": 0.47796787433411847, \"continuous_test_metrics\": [{\"annualised_returns\": -0.4845064531109248, \"annualised_returns_over_hodl\": 9.587886040662852e-13, \"annualised_returns_over_uniform_hodl\": 0.8194637509354799, \"calmar\": -0.8358049680150857, \"daily_log_sharpe\": -0.6924635968722539, \"daily_returns\": 0.03101604275187203, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": 0.005019284054507553, \"return\": -0.23422459929400918, \"returns_over_hodl\": 3.8613556796462944e-13, \"returns_over_uniform_hodl\": 0.27259157048738514, \"sharpe\": -0.20210852964871812, \"sterling\": -1.247384311023237, \"ulcer\": -0.1764103165146677}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -8.592471179014183e-11, \"optuna_trial_number\": 187, \"price_ratio\": 3.480288806842023, \"shift_exponent\": 5.084827106765752e-05, \"step\": 187, \"test_objective\": [{\"annualised_returns\": -0.4845064531109248, \"annualised_returns_over_hodl\": 9.587886040662852e-13, \"annualised_returns_over_uniform_hodl\": 0.8194637509354799, \"calmar\": -0.8358049680150857, \"daily_log_sharpe\": -0.6924635968722539, \"daily_returns\": 0.03101604275187203, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": 0.005019284054507553, \"return\": -0.23422459929400918, \"returns_over_hodl\": 3.8613556796462944e-13, \"returns_over_uniform_hodl\": 0.27259157048738514, \"sharpe\": -0.20210852964871812, \"sterling\": -1.247384311023237, \"ulcer\": -0.1764103165146677}], \"train_objective\": [{\"annualised_returns\": -0.5459712080479694, \"annualised_returns_over_hodl\": -0.4282242097387191, \"annualised_returns_over_uniform_hodl\": -0.4282242097387191, \"calmar\": -0.7165637819954463, \"daily_log_sharpe\": -0.5462097219030447, \"daily_returns\": 0.00817799642225016, \"fee_revenue_over_value\": 0.001433715436458549, \"jax_sharpe\": 0.11542292250599624, \"return\": -0.3808327236650486, \"returns_over_hodl\": -0.2877924572062698, \"returns_over_uniform_hodl\": -0.2877924572062698, \"sharpe\": 0.08893274087090204, \"sterling\": -1.529084328102256, \"ulcer\": -0.19560196241229713}], \"train_return\": -0.3808327236650486, \"train_returns_over_hodl\": -0.2877924572062698, \"train_sharpe\": 0.11542292250599626, \"validation_return\": -0.3391427223005644, \"validation_returns_over_hodl\": -8.592471179014183e-11, \"validation_sharpe\": -2.2438532050868174}, {\"centeredness_margin\": 0.4620623109683175, \"continuous_test_metrics\": [{\"annualised_returns\": -0.4807624322063325, \"annualised_returns_over_hodl\": 0.007262983015448032, \"annualised_returns_over_uniform_hodl\": 0.8326784853579774, \"calmar\": -0.8293462786996231, \"daily_log_sharpe\": -0.6849551446091103, \"daily_returns\": 0.031016042753262783, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": 0.008538233717432585, \"return\": -0.23198948617342452, \"returns_over_hodl\": 0.0029187580343668085, \"returns_over_uniform_hodl\": 0.27630595738688535, \"sharpe\": -0.19424560839956523, \"sterling\": -1.2377451391429133, \"ulcer\": -0.1764103165175415}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -7.024558712487305e-11, \"optuna_trial_number\": 188, \"price_ratio\": 4.505629644255582, \"shift_exponent\": 0.00012150408351339503, \"step\": 188, \"test_objective\": [{\"annualised_returns\": -0.4807624322063325, \"annualised_returns_over_hodl\": 0.007262983015448032, \"annualised_returns_over_uniform_hodl\": 0.8326784853579774, \"calmar\": -0.8293462786996231, \"daily_log_sharpe\": -0.6849551446091103, \"daily_returns\": 0.031016042753262783, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": 0.008538233717432585, \"return\": -0.23198948617342452, \"returns_over_hodl\": 0.0029187580343668085, \"returns_over_uniform_hodl\": 0.27630595738688535, \"sharpe\": -0.19424560839956523, \"sterling\": -1.2377451391429133, \"ulcer\": -0.1764103165175415}], \"train_objective\": [{\"annualised_returns\": -0.50753881085875, \"annualised_returns_over_hodl\": -0.37982482480098345, \"annualised_returns_over_uniform_hodl\": -0.37982482480098345, \"calmar\": -0.6735672965339099, \"daily_log_sharpe\": -0.4998096149534049, \"daily_returns\": 0.008165088143365657, \"fee_revenue_over_value\": 0.0018620730598559601, \"jax_sharpe\": 0.16943932347962276, \"return\": -0.3495222163822377, \"returns_over_hodl\": -0.2517770211394377, \"returns_over_uniform_hodl\": -0.2517770211394377, \"sharpe\": 0.11059452363079517, \"sterling\": -1.4685459843277808, \"ulcer\": -0.18799281714878413}], \"train_return\": -0.3495222163822377, \"train_returns_over_hodl\": -0.2517770211394377, \"train_sharpe\": 0.16943932347962273, \"validation_return\": -0.339142722305528, \"validation_returns_over_hodl\": -7.024558712487305e-11, \"validation_sharpe\": -2.2438532050397484}, {\"centeredness_margin\": 0.3828875566776849, \"continuous_test_metrics\": [{\"annualised_returns\": -0.47923914644941856, \"annualised_returns_over_hodl\": 0.010217987483241098, \"annualised_returns_over_uniform_hodl\": 0.8380550089512342, \"calmar\": -0.8267185077943882, \"daily_log_sharpe\": -0.6854140775619632, \"daily_returns\": 0.031016042754327046, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": 4.092918909237838e-05, \"return\": -0.23108286720129756, \"returns_over_hodl\": 0.004102680837057893, \"returns_over_uniform_hodl\": 0.27781260758811777, \"sharpe\": -0.19588305688894697, \"sterling\": -1.2338233627198087, \"ulcer\": -0.17641031651974148}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -0.002733954079499723, \"optuna_trial_number\": 189, \"price_ratio\": 6.278375598737785, \"shift_exponent\": 1.133373935574766e-05, \"step\": 189, \"test_objective\": [{\"annualised_returns\": -0.47923914644941856, \"annualised_returns_over_hodl\": 0.010217987483241098, \"annualised_returns_over_uniform_hodl\": 0.8380550089512342, \"calmar\": -0.8267185077943882, \"daily_log_sharpe\": -0.6854140775619632, \"daily_returns\": 0.031016042754327046, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": 4.092918909237838e-05, \"return\": -0.23108286720129756, \"returns_over_hodl\": 0.004102680837057893, \"returns_over_uniform_hodl\": 0.27781260758811777, \"sharpe\": -0.19588305688894697, \"sterling\": -1.2338233627198087, \"ulcer\": -0.17641031651974148}], \"train_objective\": [{\"annualised_returns\": -0.4740305290087439, \"annualised_returns_over_hodl\": -0.33762656628809806, \"annualised_returns_over_uniform_hodl\": -0.33762656628809795, \"calmar\": -0.6331949829591109, \"daily_log_sharpe\": -0.46727504250077906, \"daily_returns\": 0.008070581852962612, \"fee_revenue_over_value\": 0.0019136068429381925, \"jax_sharpe\": 0.10487256479729334, \"return\": -0.32299921115430463, \"returns_over_hodl\": -0.2212684895957382, \"returns_over_uniform_hodl\": -0.22126848959573808, \"sharpe\": 0.11620966582251849, \"sterling\": -1.4095035006703338, \"ulcer\": -0.1823579341018243}], \"train_return\": -0.32299921115430463, \"train_returns_over_hodl\": -0.2212684895957382, \"train_sharpe\": 0.10487256479729333, \"validation_return\": -0.3390668922785305, \"validation_returns_over_hodl\": -0.002733954079499723, \"validation_sharpe\": -2.2431845928519762}, {\"centeredness_margin\": 0.2797309873299349, \"continuous_test_metrics\": [{\"annualised_returns\": -0.5411924316121426, \"annualised_returns_over_hodl\": -0.1099644775524502, \"annualised_returns_over_uniform_hodl\": 0.619387370364479, \"calmar\": -0.93653103736325, \"daily_log_sharpe\": -0.8327504808380741, \"daily_returns\": 0.03101604275494793, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": -0.15103025518903215, \"return\": -0.26932233275819184, \"returns_over_hodl\": -0.04583293413830869, \"returns_over_uniform_hodl\": 0.21426496492059122, \"sharpe\": -0.35688987100862624, \"sterling\": -1.4007919965356948, \"ulcer\": -0.1743907987764512}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -0.06452862939676482, \"optuna_trial_number\": 190, \"price_ratio\": 13.179571265843505, \"shift_exponent\": 1.192757984183425e-05, \"step\": 190, \"test_objective\": [{\"annualised_returns\": -0.5411924316121426, \"annualised_returns_over_hodl\": -0.1099644775524502, \"annualised_returns_over_uniform_hodl\": 0.619387370364479, \"calmar\": -0.93653103736325, \"daily_log_sharpe\": -0.8327504808380741, \"daily_returns\": 0.03101604275494793, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": -0.15103025518903215, \"return\": -0.26932233275819184, \"returns_over_hodl\": -0.04583293413830869, \"returns_over_uniform_hodl\": 0.21426496492059122, \"sharpe\": -0.35688987100862624, \"sterling\": -1.4007919965356948, \"ulcer\": -0.1743907987764512}], \"train_objective\": [{\"annualised_returns\": -0.41211098049599637, \"annualised_returns_over_hodl\": -0.25964891506628207, \"annualised_returns_over_uniform_hodl\": -0.25964891506628185, \"calmar\": -0.5557377155103678, \"daily_log_sharpe\": -0.41060634358239156, \"daily_returns\": 0.007985604739476931, \"fee_revenue_over_value\": 0.0037689730839123455, \"jax_sharpe\": 0.11245562560142597, \"return\": -0.27567367008039145, \"returns_over_hodl\": -0.1668314923449311, \"returns_over_uniform_hodl\": -0.166831492344931, \"sharpe\": 0.13447471730602997, \"sterling\": -1.274988574558146, \"ulcer\": -0.17460547868602655}], \"train_return\": -0.27567367008039145, \"train_returns_over_hodl\": -0.1668314923449311, \"train_sharpe\": 0.11245562560142597, \"validation_return\": -0.30544814023950906, \"validation_returns_over_hodl\": -0.06452862939676485, \"validation_sharpe\": -2.048594404039409}, {\"centeredness_margin\": 0.30112982856863646, \"continuous_test_metrics\": [{\"annualised_returns\": -0.4993993394179588, \"annualised_returns_over_hodl\": -0.028890538839579483, \"annualised_returns_over_uniform_hodl\": 0.766898462880995, \"calmar\": -0.8614962065167829, \"daily_log_sharpe\": -0.7323239502989095, \"daily_returns\": 0.031016042751620898, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": -0.050194608526114175, \"return\": -0.24321271298621905, \"returns_over_hodl\": -0.011737271401963678, \"returns_over_uniform_hodl\": 0.2576548178720053, \"sharpe\": -0.2477017710534074, \"sterling\": -1.2857268060086826, \"ulcer\": -0.1764103165201975}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -0.05189223188265357, \"optuna_trial_number\": 191, \"price_ratio\": 8.467566706975505, \"shift_exponent\": 3.2547826298377843e-05, \"step\": 191, \"test_objective\": [{\"annualised_returns\": -0.4993993394179588, \"annualised_returns_over_hodl\": -0.028890538839579483, \"annualised_returns_over_uniform_hodl\": 0.766898462880995, \"calmar\": -0.8614962065167829, \"daily_log_sharpe\": -0.7323239502989095, \"daily_returns\": 0.031016042751620898, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": -0.050194608526114175, \"return\": -0.24321271298621905, \"returns_over_hodl\": -0.011737271401963678, \"returns_over_uniform_hodl\": 0.2576548178720053, \"sharpe\": -0.2477017710534074, \"sterling\": -1.2857268060086826, \"ulcer\": -0.1764103165201975}], \"train_objective\": [{\"annualised_returns\": -0.4445335054551969, \"annualised_returns_over_hodl\": -0.30047983847779014, \"annualised_returns_over_uniform_hodl\": -0.30047983847779025, \"calmar\": -0.5962385494371495, \"daily_log_sharpe\": -0.44103822067671267, \"daily_returns\": 0.008040703336859533, \"fee_revenue_over_value\": 0.0024351632705968307, \"jax_sharpe\": 0.14777264143862093, \"return\": -0.30019614437286835, \"returns_over_hodl\": -0.1950388796320125, \"returns_over_uniform_hodl\": -0.1950388796320126, \"sharpe\": 0.12304184043436263, \"sterling\": -1.3477025258317994, \"ulcer\": -0.17853347961393048}], \"train_return\": -0.30019614437286835, \"train_returns_over_hodl\": -0.1950388796320125, \"train_sharpe\": 0.14777264143862093, \"validation_return\": -0.3323849747648243, \"validation_returns_over_hodl\": -0.05189223188265357, \"validation_sharpe\": -2.183903808487823}, {\"centeredness_margin\": 0.2893619093564198, \"continuous_test_metrics\": [{\"annualised_returns\": -0.4766026663196061, \"annualised_returns_over_hodl\": 0.015332464957605207, \"annualised_returns_over_uniform_hodl\": 0.8473606153069388, \"calmar\": -0.8221704014241357, \"daily_log_sharpe\": -0.678719837365695, \"daily_returns\": 0.031016042754131137, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": 0.00752203507762417, \"return\": -0.2295174403034358, \"returns_over_hodl\": 0.006146918480160801, \"returns_over_uniform_hodl\": 0.28041408717677596, \"sharpe\": -0.1882174770731975, \"sterling\": -1.227035611097435, \"ulcer\": -0.17641031651933606}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -6.987732614760489e-11, \"optuna_trial_number\": 192, \"price_ratio\": 5.768345647553483, \"shift_exponent\": 1.2381121198497533e-05, \"step\": 192, \"test_objective\": [{\"annualised_returns\": -0.4766026663196061, \"annualised_returns_over_hodl\": 0.015332464957605207, \"annualised_returns_over_uniform_hodl\": 0.8473606153069388, \"calmar\": -0.8221704014241357, \"daily_log_sharpe\": -0.678719837365695, \"daily_returns\": 0.031016042754131137, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": 0.00752203507762417, \"return\": -0.2295174403034358, \"returns_over_hodl\": 0.006146918480160801, \"returns_over_uniform_hodl\": 0.28041408717677596, \"sharpe\": -0.1882174770731975, \"sterling\": -1.227035611097435, \"ulcer\": -0.17641031651933606}], \"train_objective\": [{\"annualised_returns\": -0.4810810037395383, \"annualised_returns_over_hodl\": -0.34650549826856825, \"annualised_returns_over_uniform_hodl\": -0.34650549826856847, \"calmar\": -0.6417353877932455, \"daily_log_sharpe\": -0.4723042367432282, \"daily_returns\": 0.008108807501463366, \"fee_revenue_over_value\": 0.0021400104841973206, \"jax_sharpe\": 0.10695345088845024, \"return\": -0.3285234414118732, \"returns_over_hodl\": -0.22762282808865486, \"returns_over_uniform_hodl\": -0.22762282808865508, \"sharpe\": 0.11753768006779433, \"sterling\": -1.4212348942409871, \"ulcer\": -0.1836379717855138}], \"train_return\": -0.3285234414118732, \"train_returns_over_hodl\": -0.22762282808865486, \"train_sharpe\": 0.10695345088845022, \"validation_return\": -0.3391427223079918, \"validation_returns_over_hodl\": -6.987732614760489e-11, \"validation_sharpe\": -2.2438532050041156}, {\"centeredness_margin\": 0.2923961046971624, \"continuous_test_metrics\": [{\"annualised_returns\": -0.6075591813275529, \"annualised_returns_over_hodl\": -0.11798957286611256, \"annualised_returns_over_uniform_hodl\": 0.3851421579785699, \"calmar\": -1.0430509700053558, \"daily_log_sharpe\": -1.0499746010587883, \"daily_returns\": 0.027564534174729774, \"fee_revenue_over_value\": 2.064025285153857e-08, \"jax_sharpe\": -0.3848015446120505, \"return\": -0.313884082831153, \"returns_over_hodl\": -0.04930720086955087, \"returns_over_uniform_hodl\": 0.14021073510760096, \"sharpe\": -0.6041395638943471, \"sterling\": -1.6247421237671313, \"ulcer\": -0.1654419794276308}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -0.051671955481011306, \"optuna_trial_number\": 193, \"price_ratio\": 26.257930307500168, \"shift_exponent\": 1.6690408282667712e-05, \"step\": 193, \"test_objective\": [{\"annualised_returns\": -0.6075591813275529, \"annualised_returns_over_hodl\": -0.11798957286611256, \"annualised_returns_over_uniform_hodl\": 0.3851421579785699, \"calmar\": -1.0430509700053558, \"daily_log_sharpe\": -1.0499746010587883, \"daily_returns\": 0.027564534174729774, \"fee_revenue_over_value\": 2.064025285153857e-08, \"jax_sharpe\": -0.3848015446120505, \"return\": -0.313884082831153, \"returns_over_hodl\": -0.04930720086955087, \"returns_over_uniform_hodl\": 0.14021073510760096, \"sharpe\": -0.6041395638943471, \"sterling\": -1.6247421237671313, \"ulcer\": -0.1654419794276308}], \"train_objective\": [{\"annualised_returns\": -0.38049368239990367, \"annualised_returns_over_hodl\": -0.21983204458303307, \"annualised_returns_over_uniform_hodl\": -0.2198320445830334, \"calmar\": -0.5155664022157377, \"daily_log_sharpe\": -0.3808993391104758, \"daily_returns\": 0.008002355307651803, \"fee_revenue_over_value\": 0.004170101463701098, \"jax_sharpe\": 0.12007455009061417, \"return\": -0.25226704576509396, \"returns_over_hodl\": -0.13990763020646124, \"returns_over_uniform_hodl\": -0.13990763020646146, \"sharpe\": 0.14641187847256149, \"sterling\": -1.20141028322676, \"ulcer\": -0.17086018313603157}], \"train_return\": -0.25226704576509396, \"train_returns_over_hodl\": -0.13990763020646124, \"train_sharpe\": 0.12007455009061418, \"validation_return\": -0.2603488274302581, \"validation_returns_over_hodl\": -0.051671955481011334, \"validation_sharpe\": -1.8662972749543913}, {\"centeredness_margin\": 0.3014364351797992, \"continuous_test_metrics\": [{\"annualised_returns\": -0.48135073991154653, \"annualised_returns_over_hodl\": 0.006121731614117998, \"annualised_returns_over_uniform_hodl\": 0.8306020198997908, \"calmar\": -0.8303611492750461, \"daily_log_sharpe\": -0.6908716270884369, \"daily_returns\": 0.031016042754564203, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": -0.006325312469915807, \"return\": -0.2323400563952518, \"returns_over_hodl\": 0.0024609603068110886, \"returns_over_uniform_hodl\": 0.2757233678851201, \"sharpe\": -0.2022223079108443, \"sterling\": -1.23925976714833, \"ulcer\": -0.17641031652023126}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -0.0166304382830873, \"optuna_trial_number\": 194, \"price_ratio\": 6.680858526834812, \"shift_exponent\": 1.174480189459815e-05, \"step\": 194, \"test_objective\": [{\"annualised_returns\": -0.48135073991154653, \"annualised_returns_over_hodl\": 0.006121731614117998, \"annualised_returns_over_uniform_hodl\": 0.8306020198997908, \"calmar\": -0.8303611492750461, \"daily_log_sharpe\": -0.6908716270884369, \"daily_returns\": 0.031016042754564203, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": -0.006325312469915807, \"return\": -0.2323400563952518, \"returns_over_hodl\": 0.0024609603068110886, \"returns_over_uniform_hodl\": 0.2757233678851201, \"sharpe\": -0.2022223079108443, \"sterling\": -1.23925976714833, \"ulcer\": -0.17641031652023126}], \"train_objective\": [{\"annualised_returns\": -0.4664029827518832, \"annualised_returns_over_hodl\": -0.32802090610513823, \"annualised_returns_over_uniform_hodl\": -0.32802090610513823, \"calmar\": -0.6236890642960693, \"daily_log_sharpe\": -0.4598370425877034, \"daily_returns\": 0.008095137973675786, \"fee_revenue_over_value\": 0.002200721384004936, \"jax_sharpe\": 0.1047911940881063, \"return\": -0.31705549114124754, \"returns_over_hodl\": -0.2144316259768939, \"returns_over_uniform_hodl\": -0.2144316259768939, \"sharpe\": 0.11915718283462687, \"sterling\": -1.3933454523274669, \"ulcer\": -0.18141301160087991}], \"train_return\": -0.31705549114124754, \"train_returns_over_hodl\": -0.2144316259768939, \"train_sharpe\": 0.1047911940881063, \"validation_return\": -0.33866731271430506, \"validation_returns_over_hodl\": -0.0166304382830873, \"validation_sharpe\": -2.2395451739968677}, {\"centeredness_margin\": 0.30519013936204914, \"continuous_test_metrics\": [{\"annualised_returns\": -0.4845064531185471, \"annualised_returns_over_hodl\": 8.391065620116933e-13, \"annualised_returns_over_uniform_hodl\": 0.8194637509085763, \"calmar\": -0.8358049680272812, \"daily_log_sharpe\": -0.6924635968913527, \"daily_returns\": 0.031016042751514313, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": 0.005019284031683154, \"return\": -0.23422459929856942, \"returns_over_hodl\": 3.3795188869589765e-13, \"returns_over_uniform_hodl\": 0.27259157047980676, \"sharpe\": -0.20210852967110285, \"sterling\": -1.2473843110487168, \"ulcer\": -0.17641031651392858}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -9.001011047615748e-11, \"optuna_trial_number\": 195, \"price_ratio\": 3.258291524799387, \"shift_exponent\": 1.9534304917883592e-05, \"step\": 195, \"test_objective\": [{\"annualised_returns\": -0.4845064531185471, \"annualised_returns_over_hodl\": 8.391065620116933e-13, \"annualised_returns_over_uniform_hodl\": 0.8194637509085763, \"calmar\": -0.8358049680272812, \"daily_log_sharpe\": -0.6924635968913527, \"daily_returns\": 0.031016042751514313, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": 0.005019284031683154, \"return\": -0.23422459929856942, \"returns_over_hodl\": 3.3795188869589765e-13, \"returns_over_uniform_hodl\": 0.27259157047980676, \"sharpe\": -0.20210852967110285, \"sterling\": -1.2473843110487168, \"ulcer\": -0.17641031651392858}], \"train_objective\": [{\"annualised_returns\": -0.5546282049888951, \"annualised_returns_over_hodl\": -0.43912629646741763, \"annualised_returns_over_uniform_hodl\": -0.43912629646741763, \"calmar\": -0.7255740166506363, \"daily_log_sharpe\": -0.5562983225797054, \"daily_returns\": 0.008222311569637165, \"fee_revenue_over_value\": 0.0019323096084415251, \"jax_sharpe\": 0.11789221454793813, \"return\": -0.3880273050280838, \"returns_over_hodl\": -0.2960681450693746, \"returns_over_uniform_hodl\": -0.2960681450693746, \"sharpe\": 0.08512007026601896, \"sterling\": -1.54158771839643, \"ulcer\": -0.19754315182326643}], \"train_return\": -0.3880273050280838, \"train_returns_over_hodl\": -0.2960681450693746, \"train_sharpe\": 0.11789221454793813, \"validation_return\": -0.33914272229930265, \"validation_returns_over_hodl\": -9.001011047615748e-11, \"validation_sharpe\": -2.2438532050989033}, {\"centeredness_margin\": 0.4007140752474109, \"continuous_test_metrics\": [{\"annualised_returns\": -0.48058872335510494, \"annualised_returns_over_hodl\": 0.007599958808950813, \"annualised_returns_over_uniform_hodl\": 0.8332916006140885, \"calmar\": -0.8290466191868602, \"daily_log_sharpe\": -0.6846200219581565, \"daily_returns\": 0.03101604275346127, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": 0.008441487816876887, \"return\": -0.2318860191292681, \"returns_over_hodl\": 0.003053872118146117, \"returns_over_uniform_hodl\": 0.2764779024351267, \"sharpe\": -0.19390486408791488, \"sterling\": -1.2372979168931055, \"ulcer\": -0.17641031651795008}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -6.829703469435344e-11, \"optuna_trial_number\": 196, \"price_ratio\": 4.796614842594811, \"shift_exponent\": 3.202297131778075e-05, \"step\": 196, \"test_objective\": [{\"annualised_returns\": -0.48058872335510494, \"annualised_returns_over_hodl\": 0.007599958808950813, \"annualised_returns_over_uniform_hodl\": 0.8332916006140885, \"calmar\": -0.8290466191868602, \"daily_log_sharpe\": -0.6846200219581565, \"daily_returns\": 0.03101604275346127, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": 0.008441487816876887, \"return\": -0.2318860191292681, \"returns_over_hodl\": 0.003053872118146117, \"returns_over_uniform_hodl\": 0.2764779024351267, \"sharpe\": -0.19390486408791488, \"sterling\": -1.2372979168931055, \"ulcer\": -0.17641031651795008}], \"train_objective\": [{\"annualised_returns\": -0.5012461833610979, \"annualised_returns_over_hodl\": -0.37190027877203924, \"annualised_returns_over_uniform_hodl\": -0.37190027877203924, \"calmar\": -0.6664448183034165, \"daily_log_sharpe\": -0.4928967049852115, \"daily_returns\": 0.00813367566253591, \"fee_revenue_over_value\": 0.0019016427919607208, \"jax_sharpe\": 0.11209184744230823, \"return\": -0.34448856700480857, \"returns_over_hodl\": -0.24598698153074783, \"returns_over_uniform_hodl\": -0.24598698153074783, \"sharpe\": 0.11281207596514262, \"sterling\": -1.45745365726053, \"ulcer\": -0.1869410700961635}], \"train_return\": -0.34448856700480857, \"train_returns_over_hodl\": -0.24598698153074783, \"train_sharpe\": 0.11209184744230823, \"validation_return\": -0.33914272230642906, \"validation_returns_over_hodl\": -6.829703469435344e-11, \"validation_sharpe\": -2.2438532050350792}, {\"centeredness_margin\": 0.28026064783843296, \"continuous_test_metrics\": [{\"annualised_returns\": -0.484506453161743, \"annualised_returns_over_hodl\": 1.0436096431476471e-12, \"annualised_returns_over_uniform_hodl\": 0.819463750756114, \"calmar\": -0.8358049680965105, \"daily_log_sharpe\": -0.6924635969998646, \"daily_returns\": 0.031016042749456362, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": 0.005019283901732656, \"return\": -0.23422459932441242, \"returns_over_hodl\": 4.2033043712308427e-13, \"returns_over_uniform_hodl\": 0.27259157043686, \"sharpe\": -0.20210852979839988, \"sterling\": -1.2473843111935454, \"ulcer\": -0.17641031650967437}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -1.0725165200398123e-10, \"optuna_trial_number\": 197, \"price_ratio\": 2.4871417188773335, \"shift_exponent\": 1.6985223578307168e-05, \"step\": 197, \"test_objective\": [{\"annualised_returns\": -0.484506453161743, \"annualised_returns_over_hodl\": 1.0436096431476471e-12, \"annualised_returns_over_uniform_hodl\": 0.819463750756114, \"calmar\": -0.8358049680965105, \"daily_log_sharpe\": -0.6924635969998646, \"daily_returns\": 0.031016042749456362, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": 0.005019283901732656, \"return\": -0.23422459932441242, \"returns_over_hodl\": 4.2033043712308427e-13, \"returns_over_uniform_hodl\": 0.27259157043686, \"sharpe\": -0.20210852979839988, \"sterling\": -1.2473843111935454, \"ulcer\": -0.17641031650967437}], \"train_objective\": [{\"annualised_returns\": -0.6020325635139963, \"annualised_returns_over_hodl\": -0.4988244148201907, \"annualised_returns_over_uniform_hodl\": -0.49882441482019046, \"calmar\": -0.7755903723059762, \"daily_log_sharpe\": -0.6253951832619293, \"daily_returns\": 0.008268426901199393, \"fee_revenue_over_value\": 0.0019168902875792305, \"jax_sharpe\": 0.10156262509968263, \"return\": -0.428443875530341, \"returns_over_hodl\": -0.3425579830594405, \"returns_over_uniform_hodl\": -0.3425579830594404, \"sharpe\": 0.03783472447635731, \"sterling\": -1.6417316965901476, \"ulcer\": -0.20402901993907313}], \"train_return\": -0.428443875530341, \"train_returns_over_hodl\": -0.3425579830594405, \"train_sharpe\": 0.10156262509968263, \"validation_return\": -0.3391427222880087, \"validation_returns_over_hodl\": -1.0725165200398123e-10, \"validation_sharpe\": -2.243853205128456}, {\"centeredness_margin\": 0.2728230360357184, \"continuous_test_metrics\": [{\"annualised_returns\": -0.4766558148193545, \"annualised_returns_over_hodl\": 0.015229362795288859, \"annualised_returns_over_uniform_hodl\": 0.8471730246585354, \"calmar\": -0.8222620861763452, \"daily_log_sharpe\": -0.6794385180977673, \"daily_returns\": 0.03101604275411291, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": 0.005552206600992389, \"return\": -0.2295489509928913, \"returns_over_hodl\": 0.006105769744919831, \"returns_over_uniform_hodl\": 0.28036172164277695, \"sharpe\": -0.18919758290127808, \"sterling\": -1.2271724446005967, \"ulcer\": -0.17641031651929392}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -6.078826331190612e-11, \"optuna_trial_number\": 198, \"price_ratio\": 5.4362469537494915, \"shift_exponent\": 0.00016041904983886827, \"step\": 198, \"test_objective\": [{\"annualised_returns\": -0.4766558148193545, \"annualised_returns_over_hodl\": 0.015229362795288859, \"annualised_returns_over_uniform_hodl\": 0.8471730246585354, \"calmar\": -0.8222620861763452, \"daily_log_sharpe\": -0.6794385180977673, \"daily_returns\": 0.03101604275411291, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": 0.005552206600992389, \"return\": -0.2295489509928913, \"returns_over_hodl\": 0.006105769744919831, \"returns_over_uniform_hodl\": 0.28036172164277695, \"sharpe\": -0.18919758290127808, \"sterling\": -1.2271724446005967, \"ulcer\": -0.17641031651929392}], \"train_objective\": [{\"annualised_returns\": -0.4815827950150958, \"annualised_returns_over_hodl\": -0.34713742317776763, \"annualised_returns_over_uniform_hodl\": -0.34713742317776763, \"calmar\": -0.6421941097586895, \"daily_log_sharpe\": -0.47075481954513576, \"daily_returns\": 0.00811096719903501, \"fee_revenue_over_value\": 0.002210695231923087, \"jax_sharpe\": 0.11290329680629967, \"return\": -0.32891772840366906, \"returns_over_hodl\": -0.22807636331300463, \"returns_over_uniform_hodl\": -0.22807636331300463, \"sharpe\": 0.12203842229966064, \"sterling\": -1.4194166361303984, \"ulcer\": -0.18402812924999057}], \"train_return\": -0.32891772840366906, \"train_returns_over_hodl\": -0.22807636331300463, \"train_sharpe\": 0.11290329680629967, \"validation_return\": -0.33914272230780784, \"validation_returns_over_hodl\": -6.078826331190612e-11, \"validation_sharpe\": -2.243853205003542}, {\"centeredness_margin\": 0.41038984121663286, \"continuous_test_metrics\": [{\"annualised_returns\": -0.4845064531970259, \"annualised_returns_over_hodl\": 2.2086776851892864e-12, \"annualised_returns_over_uniform_hodl\": 0.8194637506315807, \"calmar\": -0.8358049681531975, \"daily_log_sharpe\": -0.6924635970887839, \"daily_returns\": 0.03101604274775645, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": 0.0050192837949057535, \"return\": -0.2342245993455213, \"returns_over_hodl\": 8.895106873296754e-13, \"returns_over_uniform_hodl\": 0.2725915704017805, \"sharpe\": -0.2021085299028708, \"sterling\": -1.2473843113122842, \"ulcer\": -0.17641031650612835}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -1.2171519347958792e-10, \"optuna_trial_number\": 199, \"price_ratio\": 1.9410746594140944, \"shift_exponent\": 1.05426071604084e-05, \"step\": 199, \"test_objective\": [{\"annualised_returns\": -0.4845064531970259, \"annualised_returns_over_hodl\": 2.2086776851892864e-12, \"annualised_returns_over_uniform_hodl\": 0.8194637506315807, \"calmar\": -0.8358049681531975, \"daily_log_sharpe\": -0.6924635970887839, \"daily_returns\": 0.03101604274775645, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": 0.0050192837949057535, \"return\": -0.2342245993455213, \"returns_over_hodl\": 8.895106873296754e-13, \"returns_over_uniform_hodl\": 0.2725915704017805, \"sharpe\": -0.2021085299028708, \"sterling\": -1.2473843113122842, \"ulcer\": -0.17641031650612835}], \"train_objective\": [{\"annualised_returns\": -0.6364939281437253, \"annualised_returns_over_hodl\": -0.5422229268615312, \"annualised_returns_over_uniform_hodl\": -0.5422229268615312, \"calmar\": -0.8115610137909389, \"daily_log_sharpe\": -0.6888047632608928, \"daily_returns\": 0.008540175622469406, \"fee_revenue_over_value\": 0.00023532444659711297, \"jax_sharpe\": 0.07916322096090993, \"return\": -0.4590249495556977, \"returns_over_hodl\": -0.3777343762895796, \"returns_over_uniform_hodl\": -0.3777343762895796, \"sharpe\": -0.01890874809537435, \"sterling\": -1.714671915077153, \"ulcer\": -0.207055268093324}], \"train_return\": -0.4590249495556977, \"train_returns_over_hodl\": -0.3777343762895796, \"train_sharpe\": 0.07916322096090991, \"validation_return\": -0.3391427222787665, \"validation_returns_over_hodl\": -1.2171519347958792e-10, \"validation_sharpe\": -2.243853205154609}, {\"centeredness_margin\": 0.3952984953204264, \"continuous_test_metrics\": [{\"annualised_returns\": -0.568968395482919, \"annualised_returns_over_hodl\": -0.16384675023577688, \"annualised_returns_over_uniform_hodl\": 0.5213505283610116, \"calmar\": -0.9834227346797046, \"daily_log_sharpe\": -0.9062750393804619, \"daily_returns\": 0.031016042754406482, \"fee_revenue_over_value\": 7.051000960625035e-08, \"jax_sharpe\": -0.227998365411514, \"return\": -0.2874702589867191, \"returns_over_hodl\": -0.06953169255515734, \"returns_over_uniform_hodl\": 0.18410612471893573, \"sharpe\": -0.43654722452343303, \"sterling\": -1.4792153692845122, \"ulcer\": -0.17308368199755994}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -0.06584353507093663, \"optuna_trial_number\": 200, \"price_ratio\": 8.050083498207378, \"shift_exponent\": 0.0011036215700120815, \"step\": 200, \"test_objective\": [{\"annualised_returns\": -0.568968395482919, \"annualised_returns_over_hodl\": -0.16384675023577688, \"annualised_returns_over_uniform_hodl\": 0.5213505283610116, \"calmar\": -0.9834227346797046, \"daily_log_sharpe\": -0.9062750393804619, \"daily_returns\": 0.031016042754406482, \"fee_revenue_over_value\": 7.051000960625035e-08, \"jax_sharpe\": -0.227998365411514, \"return\": -0.2874702589867191, \"returns_over_hodl\": -0.06953169255515734, \"returns_over_uniform_hodl\": 0.18410612471893573, \"sharpe\": -0.43654722452343303, \"sterling\": -1.4792153692845122, \"ulcer\": -0.17308368199755994}], \"train_objective\": [{\"annualised_returns\": -0.4238009519617757, \"annualised_returns_over_hodl\": -0.27437054239797765, \"annualised_returns_over_uniform_hodl\": -0.27437054239797765, \"calmar\": -0.5685402371520196, \"daily_log_sharpe\": -0.42116769703426626, \"daily_returns\": 0.008062031576549908, \"fee_revenue_over_value\": 0.0021927377639795716, \"jax_sharpe\": 0.11770261705237334, \"return\": -0.28445250332520744, \"returns_over_hodl\": -0.17692949250232948, \"returns_over_uniform_hodl\": -0.17692949250232948, \"sharpe\": 0.1341553026156477, \"sterling\": -1.2981573203505112, \"ulcer\": -0.17630307583440294}], \"train_return\": -0.28445250332520744, \"train_returns_over_hodl\": -0.17692949250232948, \"train_sharpe\": 0.11770261705237332, \"validation_return\": -0.3086287665249213, \"validation_returns_over_hodl\": -0.06584353507093665, \"validation_sharpe\": -2.065761942391346}, {\"centeredness_margin\": 0.4573190889245448, \"continuous_test_metrics\": [{\"annualised_returns\": -0.4845064531207932, \"annualised_returns_over_hodl\": 8.077982727172639e-13, \"annualised_returns_over_uniform_hodl\": 0.8194637509006479, \"calmar\": -0.8358049680308781, \"daily_log_sharpe\": -0.6924635968969833, \"daily_returns\": 0.031016042751407766, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": 0.005019284024955794, \"return\": -0.23422459929991324, \"returns_over_hodl\": 3.2529534621517087e-13, \"returns_over_uniform_hodl\": 0.27259157047757343, \"sharpe\": -0.20210852967770232, \"sterling\": -1.2473843110562313, \"ulcer\": -0.17641031651370964}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -9.127576472423016e-11, \"optuna_trial_number\": 201, \"price_ratio\": 3.1851287886837913, \"shift_exponent\": 1.0812379581760403e-05, \"step\": 201, \"test_objective\": [{\"annualised_returns\": -0.4845064531207932, \"annualised_returns_over_hodl\": 8.077982727172639e-13, \"annualised_returns_over_uniform_hodl\": 0.8194637509006479, \"calmar\": -0.8358049680308781, \"daily_log_sharpe\": -0.6924635968969833, \"daily_returns\": 0.031016042751407766, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": 0.005019284024955794, \"return\": -0.23422459929991324, \"returns_over_hodl\": 3.2529534621517087e-13, \"returns_over_uniform_hodl\": 0.27259157047757343, \"sharpe\": -0.20210852967770232, \"sterling\": -1.2473843110562313, \"ulcer\": -0.17641031651370964}], \"train_objective\": [{\"annualised_returns\": -0.5584512653742241, \"annualised_returns_over_hodl\": -0.44394082235605137, \"annualised_returns_over_uniform_hodl\": -0.44394082235605137, \"calmar\": -0.7294439806139168, \"daily_log_sharpe\": -0.5612214276600346, \"daily_returns\": 0.008232881903577122, \"fee_revenue_over_value\": 0.0013401204211956866, \"jax_sharpe\": 0.14745195241071032, \"return\": -0.39122200925199924, \"returns_over_hodl\": -0.29974290717685514, \"returns_over_uniform_hodl\": -0.29974290717685514, \"sharpe\": 0.08248586424047492, \"sterling\": -1.5479522904827239, \"ulcer\": -0.19826679600740427}], \"train_return\": -0.39122200925199924, \"train_returns_over_hodl\": -0.29974290717685514, \"train_sharpe\": 0.14745195241071032, \"validation_return\": -0.3391427222989708, \"validation_returns_over_hodl\": -9.127576472423016e-11, \"validation_sharpe\": -2.2438532051029214}, {\"centeredness_margin\": 0.8726745623321772, \"continuous_test_metrics\": [{\"annualised_returns\": -0.48404318155290427, \"annualised_returns_over_hodl\": 0.0008986951394942544, \"annualised_returns_over_uniform_hodl\": 0.8210988942107833, \"calmar\": -0.8350057932298369, \"daily_log_sharpe\": -0.6914964585551525, \"daily_returns\": 0.031016042752513483, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": 0.005571124880705624, \"return\": -0.23394751001680758, \"returns_over_hodl\": 0.00036184143441841954, \"returns_over_uniform_hodl\": 0.27305204685958806, \"sharpe\": -0.20106703780576168, \"sterling\": -1.246191594854045, \"ulcer\": -0.17641031651599412}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -7.922074107824528e-11, \"optuna_trial_number\": 202, \"price_ratio\": 3.9639101741912586, \"shift_exponent\": 1.6955308873681934e-05, \"step\": 202, \"test_objective\": [{\"annualised_returns\": -0.48404318155290427, \"annualised_returns_over_hodl\": 0.0008986951394942544, \"annualised_returns_over_uniform_hodl\": 0.8210988942107833, \"calmar\": -0.8350057932298369, \"daily_log_sharpe\": -0.6914964585551525, \"daily_returns\": 0.031016042752513483, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": 0.005571124880705624, \"return\": -0.23394751001680758, \"returns_over_hodl\": 0.00036184143441841954, \"returns_over_uniform_hodl\": 0.27305204685958806, \"sharpe\": -0.20106703780576168, \"sterling\": -1.246191594854045, \"ulcer\": -0.17641031651599412}], \"train_objective\": [{\"annualised_returns\": -0.5294641106711142, \"annualised_returns_over_hodl\": -0.40743619185335, \"annualised_returns_over_uniform_hodl\": -0.40743619185335, \"calmar\": -0.6991430519053368, \"daily_log_sharpe\": -0.5268503174452152, \"daily_returns\": 0.008185385572612792, \"fee_revenue_over_value\": 4.3913647770855244e-05, \"jax_sharpe\": 0.11221644068478577, \"return\": -0.367261793179132, \"returns_over_hodl\": -0.27218226683572666, \"returns_over_uniform_hodl\": -0.27218226683572666, \"sharpe\": 0.09666731547215128, \"sterling\": -1.5062712306424606, \"ulcer\": -0.1919359672087795}], \"train_return\": -0.367261793179132, \"train_returns_over_hodl\": -0.27218226683572666, \"train_sharpe\": 0.11221644068478577, \"validation_return\": -0.3391427223031933, \"validation_returns_over_hodl\": -7.922074107824528e-11, \"validation_sharpe\": -2.2438532050684485}, {\"centeredness_margin\": 0.7979417221043004, \"continuous_test_metrics\": [{\"annualised_returns\": -0.48441317512769877, \"annualised_returns_over_hodl\": 0.00018094887516695302, \"annualised_returns_over_uniform_hodl\": 0.8197929808749511, \"calmar\": -0.8356440571824241, \"daily_log_sharpe\": -0.6922684314536841, \"daily_returns\": 0.03101604275217044, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": 0.005146345440334658, \"return\": -0.23416879645711253, \"returns_over_hodl\": 7.287101857689215e-05, \"returns_over_uniform_hodl\": 0.2726843055370858, \"sharpe\": -0.20189803674717072, \"sterling\": -1.2471441621119501, \"ulcer\": -0.17641031651528488}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -8.331046963405697e-11, \"optuna_trial_number\": 203, \"price_ratio\": 3.6722010984547997, \"shift_exponent\": 1.3407912219392103e-05, \"step\": 203, \"test_objective\": [{\"annualised_returns\": -0.48441317512769877, \"annualised_returns_over_hodl\": 0.00018094887516695302, \"annualised_returns_over_uniform_hodl\": 0.8197929808749511, \"calmar\": -0.8356440571824241, \"daily_log_sharpe\": -0.6922684314536841, \"daily_returns\": 0.03101604275217044, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": 0.005146345440334658, \"return\": -0.23416879645711253, \"returns_over_hodl\": 7.287101857689215e-05, \"returns_over_uniform_hodl\": 0.2726843055370858, \"sharpe\": -0.20189803674717072, \"sterling\": -1.2471441621119501, \"ulcer\": -0.17641031651528488}], \"train_objective\": [{\"annualised_returns\": -0.539332378771163, \"annualised_returns_over_hodl\": -0.4198636785930274, \"annualised_returns_over_uniform_hodl\": -0.4198636785930274, \"calmar\": -0.7096844638931974, \"daily_log_sharpe\": -0.5382358419802076, \"daily_returns\": 0.008207486064154128, \"fee_revenue_over_value\": 5.048965532691851e-05, \"jax_sharpe\": 0.11437087018933982, \"return\": -0.37535183710370357, \"returns_over_hodl\": -0.2814879755267905, \"returns_over_uniform_hodl\": -0.2814879755267905, \"sharpe\": 0.09241140641758563, \"sterling\": -1.5196134083678268, \"ulcer\": -0.19417678413440878}], \"train_return\": -0.37535183710370357, \"train_returns_over_hodl\": -0.2814879755267905, \"train_sharpe\": 0.11437087018933982, \"validation_return\": -0.3391427223021134, \"validation_returns_over_hodl\": -8.331046963405697e-11, \"validation_sharpe\": -2.2438532050814275}, {\"centeredness_margin\": 0.12194298260271563, \"continuous_test_metrics\": [{\"annualised_returns\": -0.47649507697542426, \"annualised_returns_over_hodl\": 0.015541176315577276, \"annualised_returns_over_uniform_hodl\": 0.8477403580077103, \"calmar\": -0.8219848025362769, \"daily_log_sharpe\": -0.6777540838299725, \"daily_returns\": 0.031016042753474284, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": 0.010348186865731954, \"return\": -0.22945365856856437, \"returns_over_hodl\": 0.006230208885055211, \"returns_over_uniform_hodl\": 0.28052008183013477, \"sharpe\": -0.18690822705476073, \"sterling\": -1.2267586168956752, \"ulcer\": -0.176410316517982}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -6.72131239554119e-11, \"optuna_trial_number\": 204, \"price_ratio\": 4.660527902567478, \"shift_exponent\": 0.00037306482292051617, \"step\": 204, \"test_objective\": [{\"annualised_returns\": -0.47649507697542426, \"annualised_returns_over_hodl\": 0.015541176315577276, \"annualised_returns_over_uniform_hodl\": 0.8477403580077103, \"calmar\": -0.8219848025362769, \"daily_log_sharpe\": -0.6777540838299725, \"daily_returns\": 0.031016042753474284, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": 0.010348186865731954, \"return\": -0.22945365856856437, \"returns_over_hodl\": 0.006230208885055211, \"returns_over_uniform_hodl\": 0.28052008183013477, \"sharpe\": -0.18690822705476073, \"sterling\": -1.2267586168956752, \"ulcer\": -0.176410316517982}], \"train_objective\": [{\"annualised_returns\": -0.49962213938565014, \"annualised_returns_over_hodl\": -0.3698550581958545, \"annualised_returns_over_uniform_hodl\": -0.3698550581958544, \"calmar\": -0.6640806159581251, \"daily_log_sharpe\": -0.48946349583775817, \"daily_returns\": 0.00816909199967368, \"fee_revenue_over_value\": 0.003705781036183833, \"jax_sharpe\": 0.11694732306004947, \"return\": -0.3431935070047061, \"returns_over_hodl\": -0.24449731704798805, \"returns_over_uniform_hodl\": -0.24449731704798794, \"sharpe\": 0.1171193269722367, \"sterling\": -1.4516006462658702, \"ulcer\": -0.18701594840667848}], \"train_return\": -0.3431935070047061, \"train_returns_over_hodl\": -0.24449731704798805, \"train_sharpe\": 0.11694732306004947, \"validation_return\": -0.33914272230587317, \"validation_returns_over_hodl\": -6.72131239554119e-11, \"validation_sharpe\": -2.243853205028411}, {\"centeredness_margin\": 0.1205109095001792, \"continuous_test_metrics\": [{\"annualised_returns\": -0.49629026148642685, \"annualised_returns_over_hodl\": -0.02285927439993818, \"annualised_returns_over_uniform_hodl\": 0.7778721300188176, \"calmar\": -0.8561328360227792, \"daily_log_sharpe\": -0.7256388056583681, \"daily_returns\": 0.031016042755017886, \"fee_revenue_over_value\": 2.261978542297018e-05, \"jax_sharpe\": -0.04424727303356907, \"return\": -0.24132327406491683, \"returns_over_hodl\": -0.009269917528463845, \"returns_over_uniform_hodl\": 0.26079475164629917, \"sharpe\": -0.24057161353544726, \"sterling\": -1.277722325925491, \"ulcer\": -0.1764103165211735}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -0.03133145186520203, \"optuna_trial_number\": 205, \"price_ratio\": 6.751086437558815, \"shift_exponent\": 0.0005198247505170217, \"step\": 205, \"test_objective\": [{\"annualised_returns\": -0.49629026148642685, \"annualised_returns_over_hodl\": -0.02285927439993818, \"annualised_returns_over_uniform_hodl\": 0.7778721300188176, \"calmar\": -0.8561328360227792, \"daily_log_sharpe\": -0.7256388056583681, \"daily_returns\": 0.031016042755017886, \"fee_revenue_over_value\": 2.261978542297018e-05, \"jax_sharpe\": -0.04424727303356907, \"return\": -0.24132327406491683, \"returns_over_hodl\": -0.009269917528463845, \"returns_over_uniform_hodl\": 0.26079475164629917, \"sharpe\": -0.24057161353544726, \"sterling\": -1.277722325925491, \"ulcer\": -0.1764103165211735}], \"train_objective\": [{\"annualised_returns\": -0.4584677262549389, \"annualised_returns_over_hodl\": -0.3180277346699979, \"annualised_returns_over_uniform_hodl\": -0.3180277346699979, \"calmar\": -0.6138046692805743, \"daily_log_sharpe\": -0.4502452065654292, \"daily_returns\": 0.008043554247896281, \"fee_revenue_over_value\": 0.004473419840306154, \"jax_sharpe\": 0.11166267630855203, \"return\": -0.3109073097569909, \"returns_over_hodl\": -0.2073595772370913, \"returns_over_uniform_hodl\": -0.2073595772370913, \"sharpe\": 0.1265138108020988, \"sterling\": -1.3731417904649266, \"ulcer\": -0.1808371839673112}], \"train_return\": -0.3109073097569909, \"train_returns_over_hodl\": -0.2073595772370913, \"train_sharpe\": 0.11166267630855205, \"validation_return\": -0.3372403802321935, \"validation_returns_over_hodl\": -0.03133145186520203, \"validation_sharpe\": -2.2267361516530912}, {\"centeredness_margin\": 0.12094127322633291, \"continuous_test_metrics\": [{\"annualised_returns\": -0.4771853471468299, \"annualised_returns_over_hodl\": 0.014202129120557183, \"annualised_returns_over_uniform_hodl\": 0.8453040102341953, \"calmar\": -0.8231755652136059, \"daily_log_sharpe\": -0.6807675370618501, \"daily_returns\": 0.031016042754255582, \"fee_revenue_over_value\": 3.970234797273147e-08, \"jax_sharpe\": 0.0035934770095450734, \"return\": -0.22986300462689668, \"returns_over_hodl\": 0.005695657804120735, \"returns_over_uniform_hodl\": 0.2798398166469418, \"sharpe\": -0.19073755764828518, \"sterling\": -1.2285357523825842, \"ulcer\": -0.17641031651958783}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -6.904232741078431e-11, \"optuna_trial_number\": 206, \"price_ratio\": 5.645564115959451, \"shift_exponent\": 0.0001639114166507019, \"step\": 206, \"test_objective\": [{\"annualised_returns\": -0.4771853471468299, \"annualised_returns_over_hodl\": 0.014202129120557183, \"annualised_returns_over_uniform_hodl\": 0.8453040102341953, \"calmar\": -0.8231755652136059, \"daily_log_sharpe\": -0.6807675370618501, \"daily_returns\": 0.031016042754255582, \"fee_revenue_over_value\": 3.970234797273147e-08, \"jax_sharpe\": 0.0035934770095450734, \"return\": -0.22986300462689668, \"returns_over_hodl\": 0.005695657804120735, \"returns_over_uniform_hodl\": 0.2798398166469418, \"sharpe\": -0.19073755764828518, \"sterling\": -1.2285357523825842, \"ulcer\": -0.17641031651958783}], \"train_objective\": [{\"annualised_returns\": -0.4764020684835225, \"annualised_returns_over_hodl\": -0.3406131364822428, \"annualised_returns_over_uniform_hodl\": -0.34061313648224256, \"calmar\": -0.6362294002918845, \"daily_log_sharpe\": -0.4640798838850638, \"daily_returns\": 0.008056775880568072, \"fee_revenue_over_value\": 0.004198222433710903, \"jax_sharpe\": 0.11562072939937106, \"return\": -0.324854108277569, \"returns_over_hodl\": -0.2234021161176616, \"returns_over_uniform_hodl\": -0.22340211611766148, \"sharpe\": 0.12646831499315608, \"sterling\": -1.4073793903454943, \"ulcer\": -0.1835555437304938}], \"train_return\": -0.324854108277569, \"train_returns_over_hodl\": -0.2234021161176616, \"train_sharpe\": 0.11562072939937104, \"validation_return\": -0.3391427223089447, \"validation_returns_over_hodl\": -6.904232741078431e-11, \"validation_sharpe\": -2.2438532050052182}, {\"centeredness_margin\": 0.15100504333641318, \"continuous_test_metrics\": [{\"annualised_returns\": -0.5170186110167232, \"annualised_returns_over_hodl\": -0.06306996110903795, \"annualised_returns_over_uniform_hodl\": 0.7047102430957015, \"calmar\": -0.8923126163020556, \"daily_log_sharpe\": -0.7727529136956662, \"daily_returns\": 0.03101604275308799, \"fee_revenue_over_value\": 1.4914310943659576e-06, \"jax_sharpe\": -0.0903835031700213, \"return\": -0.25405498553159245, \"returns_over_hodl\": -0.02589582565519999, \"returns_over_uniform_hodl\": 0.23963676109785426, \"sharpe\": -0.2911793701247076, \"sterling\": -1.332243684423596, \"ulcer\": -0.17607908233961334}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -0.06109344862653576, \"optuna_trial_number\": 207, \"price_ratio\": 9.713984698627295, \"shift_exponent\": 0.00013877290584015734, \"step\": 207, \"test_objective\": [{\"annualised_returns\": -0.5170186110167232, \"annualised_returns_over_hodl\": -0.06306996110903795, \"annualised_returns_over_uniform_hodl\": 0.7047102430957015, \"calmar\": -0.8923126163020556, \"daily_log_sharpe\": -0.7727529136956662, \"daily_returns\": 0.03101604275308799, \"fee_revenue_over_value\": 1.4914310943659576e-06, \"jax_sharpe\": -0.0903835031700213, \"return\": -0.25405498553159245, \"returns_over_hodl\": -0.02589582565519999, \"returns_over_uniform_hodl\": 0.23963676109785426, \"sharpe\": -0.2911793701247076, \"sterling\": -1.332243684423596, \"ulcer\": -0.17607908233961334}], \"train_objective\": [{\"annualised_returns\": -0.4281183457061408, \"annualised_returns_over_hodl\": -0.2798076011568289, \"annualised_returns_over_uniform_hodl\": -0.2798076011568289, \"calmar\": -0.57615157880831, \"daily_log_sharpe\": -0.42288006583065474, \"daily_returns\": 0.008061651339332067, \"fee_revenue_over_value\": 0.005205059223329435, \"jax_sharpe\": 0.11396831570765331, \"return\": -0.28771240211162596, \"returns_over_hodl\": -0.18067924574861594, \"returns_over_uniform_hodl\": -0.18067924574861594, \"sharpe\": 0.13404274065906488, \"sterling\": -1.308670270059861, \"ulcer\": -0.1768668120631286}], \"train_return\": -0.28771240211162596, \"train_returns_over_hodl\": -0.18067924574861594, \"train_sharpe\": 0.11396831570765331, \"validation_return\": -0.32396707092516885, \"validation_returns_over_hodl\": -0.06109344862653576, \"validation_sharpe\": -2.1239513898614084}, {\"centeredness_margin\": 0.26086994829242344, \"continuous_test_metrics\": [{\"annualised_returns\": -0.4766321815549245, \"annualised_returns_over_hodl\": 0.015275208694218723, \"annualised_returns_over_uniform_hodl\": 0.8472564396075803, \"calmar\": -0.8222213171987146, \"daily_log_sharpe\": -0.6792216220422985, \"daily_returns\": 0.031016042754083543, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": 0.006128880781383945, \"return\": -0.2295349390640231, \"returns_over_hodl\": 0.006124067445816728, \"returns_over_uniform_hodl\": 0.2803850071420859, \"sharpe\": -0.18888958194982683, \"sterling\": -1.2271115995655262, \"ulcer\": -0.1764103165192328}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -6.075029368446394e-11, \"optuna_trial_number\": 208, \"price_ratio\": 5.441276747699455, \"shift_exponent\": 0.00014640889374780737, \"step\": 208, \"test_objective\": [{\"annualised_returns\": -0.4766321815549245, \"annualised_returns_over_hodl\": 0.015275208694218723, \"annualised_returns_over_uniform_hodl\": 0.8472564396075803, \"calmar\": -0.8222213171987146, \"daily_log_sharpe\": -0.6792216220422985, \"daily_returns\": 0.031016042754083543, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": 0.006128880781383945, \"return\": -0.2295349390640231, \"returns_over_hodl\": 0.006124067445816728, \"returns_over_uniform_hodl\": 0.2803850071420859, \"sharpe\": -0.18888958194982683, \"sterling\": -1.2271115995655262, \"ulcer\": -0.1764103165192328}], \"train_objective\": [{\"annualised_returns\": -0.4813603896102042, \"annualised_returns_over_hodl\": -0.3468573395610577, \"annualised_returns_over_uniform_hodl\": -0.3468573395610577, \"calmar\": -0.6420038625612541, \"daily_log_sharpe\": -0.4701024865488741, \"daily_returns\": 0.008096976554998784, \"fee_revenue_over_value\": 0.0022577898733247424, \"jax_sharpe\": 0.11359612409255211, \"return\": -0.32874295287136934, \"returns_over_hodl\": -0.22787532482606043, \"returns_over_uniform_hodl\": -0.22787532482606043, \"sharpe\": 0.12289618635365741, \"sterling\": -1.418516554490225, \"ulcer\": -0.18406906438682022}], \"train_return\": -0.32874295287136934, \"train_returns_over_hodl\": -0.22787532482606043, \"train_sharpe\": 0.1135961240925521, \"validation_return\": -0.33914272230766906, \"validation_returns_over_hodl\": -6.075029368446394e-11, \"validation_sharpe\": -2.243853205004117}, {\"centeredness_margin\": 0.12225870551440504, \"continuous_test_metrics\": [{\"annualised_returns\": -0.664057729225729, \"annualised_returns_over_hodl\": -0.13683852081582282, \"annualised_returns_over_uniform_hodl\": 0.18572732436602957, \"calmar\": -1.1317698528579796, \"daily_log_sharpe\": -1.2976193629860822, \"daily_returns\": 0.024614990759755023, \"fee_revenue_over_value\": 0.01149531535908753, \"jax_sharpe\": -0.6758844776151796, \"return\": -0.35552087092203055, \"returns_over_hodl\": -0.05754233357743044, \"returns_over_uniform_hodl\": 0.07101730646289628, \"sharpe\": -0.8827765107305342, \"sterling\": -1.8585310813564921, \"ulcer\": -0.15719614500020376}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -0.027934227169198023, \"optuna_trial_number\": 209, \"price_ratio\": 16.090974336867287, \"shift_exponent\": 0.06990677520324526, \"step\": 209, \"test_objective\": [{\"annualised_returns\": -0.664057729225729, \"annualised_returns_over_hodl\": -0.13683852081582282, \"annualised_returns_over_uniform_hodl\": 0.18572732436602957, \"calmar\": -1.1317698528579796, \"daily_log_sharpe\": -1.2976193629860822, \"daily_returns\": 0.024614990759755023, \"fee_revenue_over_value\": 0.01149531535908753, \"jax_sharpe\": -0.6758844776151796, \"return\": -0.35552087092203055, \"returns_over_hodl\": -0.05754233357743044, \"returns_over_uniform_hodl\": 0.07101730646289628, \"sharpe\": -0.8827765107305342, \"sterling\": -1.8585310813564921, \"ulcer\": -0.15719614500020376}], \"train_objective\": [{\"annualised_returns\": -0.39818628088515673, \"annualised_returns_over_hodl\": -0.24211300927073065, \"annualised_returns_over_uniform_hodl\": -0.24211300927073065, \"calmar\": -0.5382566775228341, \"daily_log_sharpe\": -0.3987143295634341, \"daily_returns\": 0.007986797875263182, \"fee_revenue_over_value\": 0.006235576893908033, \"jax_sharpe\": 0.11828731584666065, \"return\": -0.265305624880361, \"returns_over_hodl\": -0.15490547448559355, \"returns_over_uniform_hodl\": -0.15490547448559355, \"sharpe\": 0.13948005617117262, \"sterling\": -1.241422735688637, \"ulcer\": -0.17313153324471747}], \"train_return\": -0.265305624880361, \"train_returns_over_hodl\": -0.15490547448559355, \"train_sharpe\": 0.11828731584666065, \"validation_return\": -0.2519791840167944, \"validation_returns_over_hodl\": -0.027934227169198023, \"validation_sharpe\": -1.895853501366615}, {\"centeredness_margin\": 0.4676967758522801, \"continuous_test_metrics\": [{\"annualised_returns\": -0.5543115333955877, \"annualised_returns_over_hodl\": -0.13541407214567192, \"annualised_returns_over_uniform_hodl\": 0.5730827555271805, \"calmar\": -0.9581588337380627, \"daily_log_sharpe\": -0.8665840706883958, \"daily_returns\": 0.031016042755184502, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": -0.18646087314340354, \"return\": -0.2778096713319608, \"returns_over_hodl\": -0.05691626035492647, \"returns_over_uniform_hodl\": 0.20016041740588508, \"sharpe\": -0.3936224446524852, \"sterling\": -1.4380451957307658, \"ulcer\": -0.17368500476464327}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -0.06445070597957234, \"optuna_trial_number\": 210, \"price_ratio\": 12.366995957097652, \"shift_exponent\": 0.0002588048840932338, \"step\": 210, \"test_objective\": [{\"annualised_returns\": -0.5543115333955877, \"annualised_returns_over_hodl\": -0.13541407214567192, \"annualised_returns_over_uniform_hodl\": 0.5730827555271805, \"calmar\": -0.9581588337380627, \"daily_log_sharpe\": -0.8665840706883958, \"daily_returns\": 0.031016042755184502, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": -0.18646087314340354, \"return\": -0.2778096713319608, \"returns_over_hodl\": -0.05691626035492647, \"returns_over_uniform_hodl\": 0.20016041740588508, \"sharpe\": -0.3936224446524852, \"sterling\": -1.4380451957307658, \"ulcer\": -0.17368500476464327}], \"train_objective\": [{\"annualised_returns\": -0.41355736650741903, \"annualised_returns_over_hodl\": -0.26147040418627465, \"annualised_returns_over_uniform_hodl\": -0.26147040418627465, \"calmar\": -0.5570336736905405, \"daily_log_sharpe\": -0.41349079986515713, \"daily_returns\": 0.008035418523282009, \"fee_revenue_over_value\": 0.001884586898572037, \"jax_sharpe\": 0.1103136526739917, \"return\": -0.2767561223403042, \"returns_over_hodl\": -0.16807660120919043, \"returns_over_uniform_hodl\": -0.16807660120919043, \"sharpe\": 0.13159895355589796, \"sterling\": -1.2794640850994519, \"ulcer\": -0.17460642792968664}], \"train_return\": -0.2767561223403042, \"train_returns_over_hodl\": -0.16807660120919043, \"train_sharpe\": 0.11031365267399171, \"validation_return\": -0.30182237880648943, \"validation_returns_over_hodl\": -0.06445070597957236, \"validation_sharpe\": -2.036919201989298}, {\"centeredness_margin\": 0.5214107059415007, \"continuous_test_metrics\": [{\"annualised_returns\": -0.547616767651701, \"annualised_returns_over_hodl\": -0.12242697310889417, \"annualised_returns_over_uniform_hodl\": 0.5967123114460007, \"calmar\": -0.9473017794674664, \"daily_log_sharpe\": -0.849359237669065, \"daily_returns\": 0.031016042754846637, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": -0.16870980137636618, \"return\": -0.27346015426732817, \"returns_over_hodl\": -0.051236374238794236, \"returns_over_uniform_hodl\": 0.2073885925954242, \"sharpe\": -0.3749617392285909, \"sterling\": -1.4187685678152262, \"ulcer\": -0.17408806581862127}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -0.06491746910560989, \"optuna_trial_number\": 211, \"price_ratio\": 11.94578382440951, \"shift_exponent\": 0.0002260478140403103, \"step\": 211, \"test_objective\": [{\"annualised_returns\": -0.547616767651701, \"annualised_returns_over_hodl\": -0.12242697310889417, \"annualised_returns_over_uniform_hodl\": 0.5967123114460007, \"calmar\": -0.9473017794674664, \"daily_log_sharpe\": -0.849359237669065, \"daily_returns\": 0.031016042754846637, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": -0.16870980137636618, \"return\": -0.27346015426732817, \"returns_over_hodl\": -0.051236374238794236, \"returns_over_uniform_hodl\": 0.2073885925954242, \"sharpe\": -0.3749617392285909, \"sterling\": -1.4187685678152262, \"ulcer\": -0.17408806581862127}], \"train_objective\": [{\"annualised_returns\": -0.4160943473265396, \"annualised_returns_over_hodl\": -0.2646653209810751, \"annualised_returns_over_uniform_hodl\": -0.26466532098107476, \"calmar\": -0.5602825752300182, \"daily_log_sharpe\": -0.41583221610286086, \"daily_returns\": 0.00803071585591164, \"fee_revenue_over_value\": 0.0018448321542475282, \"jax_sharpe\": 0.10971208414887018, \"return\": -0.2786572951617581, \"returns_over_hodl\": -0.1702634571289815, \"returns_over_uniform_hodl\": -0.17026345712898128, \"sharpe\": 0.13071633120158363, \"sterling\": -1.2852525592956972, \"ulcer\": -0.17491518959313182}], \"train_return\": -0.2786572951617581, \"train_returns_over_hodl\": -0.1702634571289815, \"train_sharpe\": 0.10971208414887017, \"validation_return\": -0.3053356917564247, \"validation_returns_over_hodl\": -0.06491746910560992, \"validation_sharpe\": -2.04900379546735}, {\"centeredness_margin\": 0.40970660529189973, \"continuous_test_metrics\": [{\"annualised_returns\": -0.4822808861601333, \"annualised_returns_over_hodl\": 0.004317351615829912, \"annualised_returns_over_uniform_hodl\": 0.8273190158882269, \"calmar\": -0.8319657144598145, \"daily_log_sharpe\": -0.6936417161881229, \"daily_returns\": 0.031016042754410614, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": -0.01188512179189867, \"return\": -0.23289481178154847, \"returns_over_hodl\": 0.001736524155124597, \"returns_over_uniform_hodl\": 0.2748014565418819, \"sharpe\": -0.20549607369435058, \"sterling\": -1.241654475862156, \"ulcer\": -0.17641031651991365}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -0.004718833713959647, \"optuna_trial_number\": 212, \"price_ratio\": 5.63883990215555, \"shift_exponent\": 0.00032464434512521147, \"step\": 212, \"test_objective\": [{\"annualised_returns\": -0.4822808861601333, \"annualised_returns_over_hodl\": 0.004317351615829912, \"annualised_returns_over_uniform_hodl\": 0.8273190158882269, \"calmar\": -0.8319657144598145, \"daily_log_sharpe\": -0.6936417161881229, \"daily_returns\": 0.031016042754410614, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": -0.01188512179189867, \"return\": -0.23289481178154847, \"returns_over_hodl\": 0.001736524155124597, \"returns_over_uniform_hodl\": 0.2748014565418819, \"sharpe\": -0.20549607369435058, \"sterling\": -1.241654475862156, \"ulcer\": -0.17641031651991365}], \"train_objective\": [{\"annualised_returns\": -0.4714009666722294, \"annualised_returns_over_hodl\": -0.33431505805414274, \"annualised_returns_over_uniform_hodl\": -0.33431505805414274, \"calmar\": -0.629083259782505, \"daily_log_sharpe\": -0.4605628718673332, \"daily_returns\": 0.008107192905453676, \"fee_revenue_over_value\": 0.001899542984802391, \"jax_sharpe\": 0.16679548710717834, \"return\": -0.32094633901265546, \"returns_over_hodl\": -0.2189071389889552, \"returns_over_uniform_hodl\": -0.2189071389889552, \"sharpe\": 0.1265363772518878, \"sterling\": -1.3981260332101881, \"ulcer\": -0.18271343673348225}], \"train_return\": -0.32094633901265546, \"train_returns_over_hodl\": -0.2189071389889552, \"train_sharpe\": 0.16679548710717834, \"validation_return\": -0.33903345073040103, \"validation_returns_over_hodl\": -0.004718833713959647, \"validation_sharpe\": -2.2428822794194914}, {\"centeredness_margin\": 0.2509671038347465, \"continuous_test_metrics\": [{\"annualised_returns\": -0.48084299107190087, \"annualised_returns_over_hodl\": 0.007106707809278134, \"annualised_returns_over_uniform_hodl\": 0.8323941482666544, \"calmar\": -0.8294852481890459, \"daily_log_sharpe\": -0.6851191284570469, \"daily_returns\": 0.031016042753014467, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": 0.008031813292132711, \"return\": -0.2320374768576544, \"returns_over_hodl\": 0.0028560886393194096, \"returns_over_uniform_hodl\": 0.27622620483782634, \"sharpe\": -0.19441979094993986, \"sterling\": -1.2379525420318664, \"ulcer\": -0.17641031651703026}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -7.252276557068171e-11, \"optuna_trial_number\": 213, \"price_ratio\": 4.182329742772325, \"shift_exponent\": 0.0002568688054595043, \"step\": 213, \"test_objective\": [{\"annualised_returns\": -0.48084299107190087, \"annualised_returns_over_hodl\": 0.007106707809278134, \"annualised_returns_over_uniform_hodl\": 0.8323941482666544, \"calmar\": -0.8294852481890459, \"daily_log_sharpe\": -0.6851191284570469, \"daily_returns\": 0.031016042753014467, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": 0.008031813292132711, \"return\": -0.2320374768576544, \"returns_over_hodl\": 0.0028560886393194096, \"returns_over_uniform_hodl\": 0.27622620483782634, \"sharpe\": -0.19441979094993986, \"sterling\": -1.2379525420318664, \"ulcer\": -0.17641031651703026}], \"train_objective\": [{\"annualised_returns\": -0.5148535454328721, \"annualised_returns_over_hodl\": -0.38903654929028, \"annualised_returns_over_uniform_hodl\": -0.38903654929028, \"calmar\": -0.6817881882663285, \"daily_log_sharpe\": -0.5078645610706658, \"daily_returns\": 0.008175678822115173, \"fee_revenue_over_value\": 0.002076015178871701, \"jax_sharpe\": 0.1159556597511843, \"return\": -0.35540534970616866, \"returns_over_hodl\": -0.2585441939030999, \"returns_over_uniform_hodl\": -0.2585441939030999, \"sharpe\": 0.10804874934591129, \"sterling\": -1.4809905273828294, \"ulcer\": -0.1892672864707216}], \"train_return\": -0.35540534970616866, \"train_returns_over_hodl\": -0.2585441939030999, \"train_sharpe\": 0.11595565975118431, \"validation_return\": -0.3391427223043044, \"validation_returns_over_hodl\": -7.252276557068171e-11, \"validation_sharpe\": -2.2438532050447195}, {\"centeredness_margin\": 0.3158265150546742, \"continuous_test_metrics\": [{\"annualised_returns\": -0.4827143432133475, \"annualised_returns_over_hodl\": 0.0034764933353261807, \"annualised_returns_over_uniform_hodl\": 0.825789104600938, \"calmar\": -0.8327134571918283, \"daily_log_sharpe\": -0.6887737564826484, \"daily_returns\": 0.031016042752718687, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": 0.006971453586682971, \"return\": -0.23315353644692416, \"returns_over_hodl\": 0.00139866445918857, \"returns_over_uniform_hodl\": 0.2743714991053665, \"sharpe\": -0.19817255486181556, \"sterling\": -1.2427704330731268, \"ulcer\": -0.17641031651642244}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -7.620626352178306e-11, \"optuna_trial_number\": 214, \"price_ratio\": 3.992583501642289, \"shift_exponent\": 0.0001798857895897211, \"step\": 214, \"test_objective\": [{\"annualised_returns\": -0.4827143432133475, \"annualised_returns_over_hodl\": 0.0034764933353261807, \"annualised_returns_over_uniform_hodl\": 0.825789104600938, \"calmar\": -0.8327134571918283, \"daily_log_sharpe\": -0.6887737564826484, \"daily_returns\": 0.031016042752718687, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": 0.006971453586682971, \"return\": -0.23315353644692416, \"returns_over_hodl\": 0.00139866445918857, \"returns_over_uniform_hodl\": 0.2743714991053665, \"sharpe\": -0.19817255486181556, \"sterling\": -1.2427704330731268, \"ulcer\": -0.17641031651642244}], \"train_objective\": [{\"annualised_returns\": -0.5227619621567061, \"annualised_returns_over_hodl\": -0.3989959203745339, \"annualised_returns_over_uniform_hodl\": -0.3989959203745339, \"calmar\": -0.6912464884609333, \"daily_log_sharpe\": -0.5174549318786958, \"daily_returns\": 0.008182040688560916, \"fee_revenue_over_value\": 0.0019325840479818468, \"jax_sharpe\": 0.11568811183145587, \"return\": -0.3618053201979968, \"returns_over_hodl\": -0.26590586728629007, \"returns_over_uniform_hodl\": -0.26590586728629007, \"sharpe\": 0.1034000453554243, \"sterling\": -1.4945635948729652, \"ulcer\": -0.19072308039741154}], \"train_return\": -0.3618053201979968, \"train_returns_over_hodl\": -0.26590586728629007, \"train_sharpe\": 0.11568811183145589, \"validation_return\": -0.3391427223034933, \"validation_returns_over_hodl\": -7.620626352178306e-11, \"validation_sharpe\": -2.24385320505715}, {\"centeredness_margin\": 0.22312309373592695, \"continuous_test_metrics\": [{\"annualised_returns\": -0.4829150565021012, \"annualised_returns_over_hodl\": 0.003087131856958969, \"annualised_returns_over_uniform_hodl\": 0.8250806756488729, \"calmar\": -0.8330597011974682, \"daily_log_sharpe\": -0.6947188132639969, \"daily_returns\": 0.03101604275465099, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": -0.012504178758972192, \"return\": -0.23327338336172798, \"returns_over_hodl\": 0.0012421604254579943, \"returns_over_uniform_hodl\": 0.27417233343173963, \"sharpe\": -0.20659559088543175, \"sterling\": -1.2432871795922484, \"ulcer\": -0.17641031652040517}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -0.021294458082977585, \"optuna_trial_number\": 215, \"price_ratio\": 6.770764839066893, \"shift_exponent\": 5.326819699193239e-05, \"step\": 215, \"test_objective\": [{\"annualised_returns\": -0.4829150565021012, \"annualised_returns_over_hodl\": 0.003087131856958969, \"annualised_returns_over_uniform_hodl\": 0.8250806756488729, \"calmar\": -0.8330597011974682, \"daily_log_sharpe\": -0.6947188132639969, \"daily_returns\": 0.03101604275465099, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": -0.012504178758972192, \"return\": -0.23327338336172798, \"returns_over_hodl\": 0.0012421604254579943, \"returns_over_uniform_hodl\": 0.27417233343173963, \"sharpe\": -0.20659559088543175, \"sterling\": -1.2432871795922484, \"ulcer\": -0.17641031652040517}], \"train_objective\": [{\"annualised_returns\": -0.46288646592465055, \"annualised_returns_over_hodl\": -0.3235924222215204, \"annualised_returns_over_uniform_hodl\": -0.3235924222215204, \"calmar\": -0.6192538228827886, \"daily_log_sharpe\": -0.4557620754270137, \"daily_returns\": 0.008095256437948471, \"fee_revenue_over_value\": 0.0029037551328419064, \"jax_sharpe\": 0.10725284658132964, \"return\": -0.3143265172612406, \"returns_over_hodl\": -0.21129257800760726, \"returns_over_uniform_hodl\": -0.21129257800760726, \"sharpe\": 0.12190092645699033, \"sterling\": -1.384691440929475, \"ulcer\": -0.18112394827162695}], \"train_return\": -0.3143265172612406, \"train_returns_over_hodl\": -0.21129257800760726, \"train_sharpe\": 0.10725284658132962, \"validation_return\": -0.3383756262315639, \"validation_returns_over_hodl\": -0.021294458082977585, \"validation_sharpe\": -2.2368804310683084}, {\"centeredness_margin\": 0.2498797086768612, \"continuous_test_metrics\": [{\"annualised_returns\": -0.4845064531580646, \"annualised_returns_over_hodl\": 9.00390872971002e-13, \"annualised_returns_over_uniform_hodl\": 0.8194637507690976, \"calmar\": -0.8358049680905888, \"daily_log_sharpe\": -0.6924635969905851, \"daily_returns\": 0.031016042749634064, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": 0.005019283912885968, \"return\": -0.23422459932221174, \"returns_over_hodl\": 3.6259883984257613e-13, \"returns_over_uniform_hodl\": 0.2725915704405173, \"sharpe\": -0.20210852978749855, \"sterling\": -1.2473843111811522, \"ulcer\": -0.17641031651004432}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -1.0436163044857949e-10, \"optuna_trial_number\": 216, \"price_ratio\": 2.491728212567217, \"shift_exponent\": 0.00023588313156233578, \"step\": 216, \"test_objective\": [{\"annualised_returns\": -0.4845064531580646, \"annualised_returns_over_hodl\": 9.00390872971002e-13, \"annualised_returns_over_uniform_hodl\": 0.8194637507690976, \"calmar\": -0.8358049680905888, \"daily_log_sharpe\": -0.6924635969905851, \"daily_returns\": 0.031016042749634064, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": 0.005019283912885968, \"return\": -0.23422459932221174, \"returns_over_hodl\": 3.6259883984257613e-13, \"returns_over_uniform_hodl\": 0.2725915704405173, \"sharpe\": -0.20210852978749855, \"sterling\": -1.2473843111811522, \"ulcer\": -0.17641031651004432}], \"train_objective\": [{\"annualised_returns\": -0.5984542011760453, \"annualised_returns_over_hodl\": -0.49431804652397193, \"annualised_returns_over_uniform_hodl\": -0.49431804652397193, \"calmar\": -0.7712242045536932, \"daily_log_sharpe\": -0.619548395441647, \"daily_returns\": 0.008381563944066622, \"fee_revenue_over_value\": 0.0019332366413566493, \"jax_sharpe\": 0.10236039539941015, \"return\": -0.4253292444921245, \"returns_over_hodl\": -0.33897532647660567, \"returns_over_uniform_hodl\": -0.33897532647660567, \"sharpe\": 0.042553879984545004, \"sterling\": -1.6325619667791462, \"ulcer\": -0.20364041488404522}], \"train_return\": -0.4253292444921245, \"train_returns_over_hodl\": -0.33897532647660567, \"train_sharpe\": 0.10236039539941015, \"validation_return\": -0.3391427222880651, \"validation_returns_over_hodl\": -1.0436163044857949e-10, \"validation_sharpe\": -2.243853205116873}, {\"centeredness_margin\": 0.2419282658917492, \"continuous_test_metrics\": [{\"annualised_returns\": -0.4777698473179548, \"annualised_returns_over_hodl\": 0.013068263969144622, \"annualised_returns_over_uniform_hodl\": 0.8432409836838237, \"calmar\": -0.8241838674988518, \"daily_log_sharpe\": -0.682566043704875, \"daily_returns\": 0.031016042754138575, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": 0.0010896042771370302, \"return\": -0.23020987911116764, \"returns_over_hodl\": 0.005242686237693306, \"returns_over_uniform_hodl\": 0.27926336884737357, \"sharpe\": -0.19289714779042694, \"sterling\": -1.2300405776724839, \"ulcer\": -0.17641031651935088}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -6.933931206987154e-11, \"optuna_trial_number\": 217, \"price_ratio\": 5.375075846497645, \"shift_exponent\": 0.0002730565031674799, \"step\": 217, \"test_objective\": [{\"annualised_returns\": -0.4777698473179548, \"annualised_returns_over_hodl\": 0.013068263969144622, \"annualised_returns_over_uniform_hodl\": 0.8432409836838237, \"calmar\": -0.8241838674988518, \"daily_log_sharpe\": -0.682566043704875, \"daily_returns\": 0.031016042754138575, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": 0.0010896042771370302, \"return\": -0.23020987911116764, \"returns_over_hodl\": 0.005242686237693306, \"returns_over_uniform_hodl\": 0.27926336884737357, \"sharpe\": -0.19289714779042694, \"sterling\": -1.2300405776724839, \"ulcer\": -0.17641031651935088}], \"train_objective\": [{\"annualised_returns\": -0.4803497371299057, \"annualised_returns_over_hodl\": -0.3455845863109439, \"annualised_returns_over_uniform_hodl\": -0.3455845863109439, \"calmar\": -0.6404713050989563, \"daily_log_sharpe\": -0.4689511428348463, \"daily_returns\": 0.008125550680109282, \"fee_revenue_over_value\": 0.0024474165585175044, \"jax_sharpe\": 0.11461930545288239, \"return\": -0.32794911022536666, \"returns_over_hodl\": -0.22696219401006978, \"returns_over_uniform_hodl\": -0.22696219401006978, \"sharpe\": 0.1237979212519845, \"sterling\": -1.4162276639904383, \"ulcer\": -0.18394780748906164}], \"train_return\": -0.32794911022536666, \"train_returns_over_hodl\": -0.22696219401006978, \"train_sharpe\": 0.11461930545288239, \"validation_return\": -0.3391427223078326, \"validation_returns_over_hodl\": -6.933931206987154e-11, \"validation_sharpe\": -2.2438532050019835}, {\"centeredness_margin\": 0.3157797257903784, \"continuous_test_metrics\": [{\"annualised_returns\": -0.7819574443319484, \"annualised_returns_over_hodl\": -0.42414570869276524, \"annualised_returns_over_uniform_hodl\": -0.23040641615496593, \"calmar\": -1.310536175204002, \"daily_log_sharpe\": -1.8726698071271743, \"daily_returns\": 0.02406459476480258, \"fee_revenue_over_value\": 0.011556400247146052, \"jax_sharpe\": -1.2371598431000657, \"return\": -0.4584911631297609, \"returns_over_hodl\": -0.19930185702034908, \"returns_over_uniform_hodl\": -0.10010222251827972, \"sharpe\": -1.4918017314275873, \"sterling\": -2.266986241494896, \"ulcer\": -0.1456806332672436}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -0.05994846239768195, \"optuna_trial_number\": 218, \"price_ratio\": 1.75490066727623, \"shift_exponent\": 0.027332006149779178, \"step\": 218, \"test_objective\": [{\"annualised_returns\": -0.7819574443319484, \"annualised_returns_over_hodl\": -0.42414570869276524, \"annualised_returns_over_uniform_hodl\": -0.23040641615496593, \"calmar\": -1.310536175204002, \"daily_log_sharpe\": -1.8726698071271743, \"daily_returns\": 0.02406459476480258, \"fee_revenue_over_value\": 0.011556400247146052, \"jax_sharpe\": -1.2371598431000657, \"return\": -0.4584911631297609, \"returns_over_hodl\": -0.19930185702034908, \"returns_over_uniform_hodl\": -0.10010222251827972, \"sharpe\": -1.4918017314275873, \"sterling\": -2.266986241494896, \"ulcer\": -0.1456806332672436}], \"train_objective\": [{\"annualised_returns\": -0.45797581276234633, \"annualised_returns_over_hodl\": -0.31740824922997113, \"annualised_returns_over_uniform_hodl\": -0.3174082492299708, \"calmar\": -0.6033443934904975, \"daily_log_sharpe\": -0.527631481730743, \"daily_returns\": 0.008623376315831299, \"fee_revenue_over_value\": 0.004090848331012708, \"jax_sharpe\": -0.09644632506975871, \"return\": -0.31052734781886726, \"returns_over_hodl\": -0.20692251964595387, \"returns_over_uniform_hodl\": -0.20692251964595365, \"sharpe\": -0.011540419786775669, \"sterling\": -1.4157865304569135, \"ulcer\": -0.17665050157456866}], \"train_return\": -0.31052734781886726, \"train_returns_over_hodl\": -0.20692251964595387, \"train_sharpe\": -0.09644632506975871, \"validation_return\": -0.23457171562407564, \"validation_returns_over_hodl\": -0.05994846239768192, \"validation_sharpe\": -1.865787156022816}, {\"centeredness_margin\": 0.4989785640752483, \"continuous_test_metrics\": [{\"annualised_returns\": -0.5208491597477781, \"annualised_returns_over_hodl\": -0.07050079851713886, \"annualised_returns_over_uniform_hodl\": 0.6911901037954005, \"calmar\": -0.8995053039735706, \"daily_log_sharpe\": -0.7817390575333893, \"daily_returns\": 0.031016042753346452, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": -0.09955817456255493, \"return\": -0.25644330037249286, \"returns_over_hodl\": -0.029014644618755248, \"returns_over_uniform_hodl\": 0.23566777837603037, \"sharpe\": -0.3008573516146443, \"sterling\": -1.3429265609075847, \"ulcer\": -0.1758498540731741}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -0.06283945650340161, \"optuna_trial_number\": 219, \"price_ratio\": 10.785923851593713, \"shift_exponent\": 1.0755315229436546e-05, \"step\": 219, \"test_objective\": [{\"annualised_returns\": -0.5208491597477781, \"annualised_returns_over_hodl\": -0.07050079851713886, \"annualised_returns_over_uniform_hodl\": 0.6911901037954005, \"calmar\": -0.8995053039735706, \"daily_log_sharpe\": -0.7817390575333893, \"daily_returns\": 0.031016042753346452, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": -0.09955817456255493, \"return\": -0.25644330037249286, \"returns_over_hodl\": -0.029014644618755248, \"returns_over_uniform_hodl\": 0.23566777837603037, \"sharpe\": -0.3008573516146443, \"sterling\": -1.3429265609075847, \"ulcer\": -0.1758498540731741}], \"train_objective\": [{\"annualised_returns\": -0.42778924466165147, \"annualised_returns_over_hodl\": -0.279393151648066, \"annualised_returns_over_uniform_hodl\": -0.27939315164806633, \"calmar\": -0.5754473335600657, \"daily_log_sharpe\": -0.42675217921643405, \"daily_returns\": 0.008041288277289964, \"fee_revenue_over_value\": 0.0018544814286976848, \"jax_sharpe\": 0.10680328061019669, \"return\": -0.2874635708597053, \"returns_over_hodl\": -0.1803930234282929, \"returns_over_uniform_hodl\": -0.1803930234282931, \"sharpe\": 0.12609413723556237, \"sterling\": -1.3122820630317458, \"ulcer\": -0.1762687399440475}], \"train_return\": -0.2874635708597053, \"train_returns_over_hodl\": -0.1803930234282929, \"train_sharpe\": 0.10680328061019667, \"validation_return\": -0.31901218641411844, \"validation_returns_over_hodl\": -0.06283945650340161, \"validation_sharpe\": -2.0973870048189793}, {\"centeredness_margin\": 0.3669364111750287, \"continuous_test_metrics\": [{\"annualised_returns\": -0.4763900719487313, \"annualised_returns_over_hodl\": 0.015744874334749248, \"annualised_returns_over_uniform_hodl\": 0.8481109792131287, \"calmar\": -0.8218036617739923, \"daily_log_sharpe\": -0.6777520868469079, \"daily_returns\": 0.031016042754048876, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": 0.009743584579215652, \"return\": -0.22939141643247107, \"returns_over_hodl\": 0.006311488760003314, \"returns_over_uniform_hodl\": 0.2806235179259511, \"sharpe\": -0.18698125888627704, \"sterling\": -1.2264882761305897, \"ulcer\": -0.17641031651916705}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -6.214062597820202e-11, \"optuna_trial_number\": 220, \"price_ratio\": 5.547059592228541, \"shift_exponent\": 1.066718059432692e-05, \"step\": 220, \"test_objective\": [{\"annualised_returns\": -0.4763900719487313, \"annualised_returns_over_hodl\": 0.015744874334749248, \"annualised_returns_over_uniform_hodl\": 0.8481109792131287, \"calmar\": -0.8218036617739923, \"daily_log_sharpe\": -0.6777520868469079, \"daily_returns\": 0.031016042754048876, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": 0.009743584579215652, \"return\": -0.22939141643247107, \"returns_over_hodl\": 0.006311488760003314, \"returns_over_uniform_hodl\": 0.2806235179259511, \"sharpe\": -0.18698125888627704, \"sterling\": -1.2264882761305897, \"ulcer\": -0.17641031651916705}], \"train_objective\": [{\"annualised_returns\": -0.4837654938665965, \"annualised_returns_over_hodl\": -0.34988617916602427, \"annualised_returns_over_uniform_hodl\": -0.3498861791660244, \"calmar\": -0.6453518377078968, \"daily_log_sharpe\": -0.4741653600218125, \"daily_returns\": 0.008086964729955142, \"fee_revenue_over_value\": 0.0019164488072419762, \"jax_sharpe\": 0.10973732308941408, \"return\": -0.3306345511453658, \"returns_over_hodl\": -0.230051167462077, \"returns_over_uniform_hodl\": -0.2300511674620771, \"sharpe\": 0.11865841256881875, \"sterling\": -1.4252679961122483, \"ulcer\": -0.18416601602590754}], \"train_return\": -0.3306345511453658, \"train_returns_over_hodl\": -0.230051167462077, \"train_sharpe\": 0.1097373230894141, \"validation_return\": -0.33914272230800846, \"validation_returns_over_hodl\": -6.214062597820202e-11, \"validation_sharpe\": -2.243853205009839}, {\"centeredness_margin\": 0.7662927284493444, \"continuous_test_metrics\": [{\"annualised_returns\": -0.505616153029812, \"annualised_returns_over_hodl\": -0.040950464040929724, \"annualised_returns_over_uniform_hodl\": 0.7449558661572329, \"calmar\": -0.8722634436945715, \"daily_log_sharpe\": -0.7463720531752945, \"daily_returns\": 0.031016042752121224, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": -0.06419919813226972, \"return\": -0.24701190306087517, \"returns_over_hodl\": -0.016698504257722346, \"returns_over_uniform_hodl\": 0.2513411947662889, \"sharpe\": -0.2628092639438228, \"sterling\": -1.3018275837400768, \"ulcer\": -0.1763823153171826}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -0.05694861865348588, \"optuna_trial_number\": 221, \"price_ratio\": 9.170684108794557, \"shift_exponent\": 1.0476966130762983e-05, \"step\": 221, \"test_objective\": [{\"annualised_returns\": -0.505616153029812, \"annualised_returns_over_hodl\": -0.040950464040929724, \"annualised_returns_over_uniform_hodl\": 0.7449558661572329, \"calmar\": -0.8722634436945715, \"daily_log_sharpe\": -0.7463720531752945, \"daily_returns\": 0.031016042752121224, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": -0.06419919813226972, \"return\": -0.24701190306087517, \"returns_over_hodl\": -0.016698504257722346, \"returns_over_uniform_hodl\": 0.2513411947662889, \"sharpe\": -0.2628092639438228, \"sterling\": -1.3018275837400768, \"ulcer\": -0.1763823153171826}], \"train_objective\": [{\"annualised_returns\": -0.441180989129599, \"annualised_returns_over_hodl\": -0.2962578866866028, \"annualised_returns_over_uniform_hodl\": -0.2962578866866028, \"calmar\": -0.5919910659720139, \"daily_log_sharpe\": -0.43977278650941076, \"daily_returns\": 0.008041505699009192, \"fee_revenue_over_value\": 0.00015897619526912942, \"jax_sharpe\": 0.10179076747182211, \"return\": -0.2976348993595921, \"returns_over_hodl\": -0.19209276460442937, \"returns_over_uniform_hodl\": -0.19209276460442937, \"sharpe\": 0.12055020719795649, \"sterling\": -1.3421531035910945, \"ulcer\": -0.17790621825869932}], \"train_return\": -0.2976348993595921, \"train_returns_over_hodl\": -0.19209276460442937, \"train_sharpe\": 0.10179076747182211, \"validation_return\": -0.3290122948102494, \"validation_returns_over_hodl\": -0.05694861865348588, \"validation_sharpe\": -2.1572061339821973}, {\"centeredness_margin\": 0.4129900769857213, \"continuous_test_metrics\": [{\"annualised_returns\": -0.4768611326262536, \"annualised_returns_over_hodl\": 0.014831069149610077, \"annualised_returns_over_uniform_hodl\": 0.8464483437981658, \"calmar\": -0.8226162732623963, \"daily_log_sharpe\": -0.6784928124486775, \"daily_returns\": 0.031016042753981257, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": 0.00928527216210854, \"return\": -0.22967069779765303, \"returns_over_hodl\": 0.005946784743567468, \"returns_over_uniform_hodl\": 0.2801593986674884, \"sharpe\": -0.1876800076976956, \"sterling\": -1.227701045689825, \"ulcer\": -0.17641031651902897}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -6.156097853704523e-11, \"optuna_trial_number\": 222, \"price_ratio\": 5.460483312093954, \"shift_exponent\": 1.417631240714094e-05, \"step\": 222, \"test_objective\": [{\"annualised_returns\": -0.4768611326262536, \"annualised_returns_over_hodl\": 0.014831069149610077, \"annualised_returns_over_uniform_hodl\": 0.8464483437981658, \"calmar\": -0.8226162732623963, \"daily_log_sharpe\": -0.6784928124486775, \"daily_returns\": 0.031016042753981257, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": 0.00928527216210854, \"return\": -0.22967069779765303, \"returns_over_hodl\": 0.005946784743567468, \"returns_over_uniform_hodl\": 0.2801593986674884, \"sharpe\": -0.1876800076976956, \"sterling\": -1.227701045689825, \"ulcer\": -0.17641031651902897}], \"train_objective\": [{\"annualised_returns\": -0.4857156126697647, \"annualised_returns_over_hodl\": -0.35234203822066945, \"annualised_returns_over_uniform_hodl\": -0.35234203822066923, \"calmar\": -0.6476680011539706, \"daily_log_sharpe\": -0.4762281271695604, \"daily_returns\": 0.008127048273082707, \"fee_revenue_over_value\": 0.0018950953660017858, \"jax_sharpe\": 0.10968410810558754, \"return\": -0.3321708495626139, \"returns_over_hodl\": -0.23181832047963147, \"returns_over_uniform_hodl\": -0.23181832047963136, \"sharpe\": 0.1179187151264269, \"sterling\": -1.4289207761802118, \"ulcer\": -0.18447670795193272}], \"train_return\": -0.3321708495626139, \"train_returns_over_hodl\": -0.23181832047963147, \"train_sharpe\": 0.10968410810558754, \"validation_return\": -0.3391427223077398, \"validation_returns_over_hodl\": -6.156097853704523e-11, \"validation_sharpe\": -2.2438532050118725}, {\"centeredness_margin\": 0.6256983073810827, \"continuous_test_metrics\": [{\"annualised_returns\": -0.7362168235220122, \"annualised_returns_over_hodl\": -0.127314240152824, \"annualised_returns_over_uniform_hodl\": -0.06896229719129643, \"calmar\": -1.2335658579138011, \"daily_log_sharpe\": -1.693753823276094, \"daily_returns\": 0.019354253417500158, \"fee_revenue_over_value\": 0.009970393112279506, \"jax_sharpe\": -1.158169625083427, \"return\": -0.41532528285778414, \"returns_over_hodl\": -0.053367882736667815, \"returns_over_uniform_hodl\": -0.02836769655134297, \"sharpe\": -1.312007481586653, \"sterling\": -2.2355562826230986, \"ulcer\": -0.14807225424390313}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -0.028819567273633967, \"optuna_trial_number\": 223, \"price_ratio\": 15.975113514378114, \"shift_exponent\": 0.40904254511889565, \"step\": 223, \"test_objective\": [{\"annualised_returns\": -0.7362168235220122, \"annualised_returns_over_hodl\": -0.127314240152824, \"annualised_returns_over_uniform_hodl\": -0.06896229719129643, \"calmar\": -1.2335658579138011, \"daily_log_sharpe\": -1.693753823276094, \"daily_returns\": 0.019354253417500158, \"fee_revenue_over_value\": 0.009970393112279506, \"jax_sharpe\": -1.158169625083427, \"return\": -0.41532528285778414, \"returns_over_hodl\": -0.053367882736667815, \"returns_over_uniform_hodl\": -0.02836769655134297, \"sharpe\": -1.312007481586653, \"sterling\": -2.2355562826230986, \"ulcer\": -0.14807225424390313}], \"train_objective\": [{\"annualised_returns\": -0.33531330475947385, \"annualised_returns_over_hodl\": -0.16293467025883135, \"annualised_returns_over_uniform_hodl\": -0.16293467025883135, \"calmar\": -0.4551046847479917, \"daily_log_sharpe\": -0.34905371600428875, \"daily_returns\": 0.008014807634434223, \"fee_revenue_over_value\": 0.006142505522275495, \"jax_sharpe\": 0.13170420186037415, \"return\": -0.21961847591611106, \"returns_over_hodl\": -0.10235306523411192, \"returns_over_uniform_hodl\": -0.10235306523411192, \"sharpe\": 0.14466224843662195, \"sterling\": -1.096383247682152, \"ulcer\": -0.16534091807808857}], \"train_return\": -0.21961847591611106, \"train_returns_over_hodl\": -0.10235306523411192, \"train_sharpe\": 0.13170420186037415, \"validation_return\": -0.17635340890112972, \"validation_returns_over_hodl\": -0.028819567273633995, \"validation_sharpe\": -1.4014951938857418}, {\"centeredness_margin\": 0.4031875473477637, \"continuous_test_metrics\": [{\"annualised_returns\": -0.47637089702876745, \"annualised_returns_over_hodl\": 0.01578207154148803, \"annualised_returns_over_uniform_hodl\": 0.8481786581821713, \"calmar\": -0.821770583741893, \"daily_log_sharpe\": -0.6781072895012364, \"daily_returns\": 0.031016042754045795, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": 0.00864633167885299, \"return\": -0.22938005122943272, \"returns_over_hodl\": 0.006326330191409157, \"returns_over_uniform_hodl\": 0.28064240500638005, \"sharpe\": -0.1874840703715488, \"sterling\": -1.2264389093287307, \"ulcer\": -0.176410316519157}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -6.149281084333325e-11, \"optuna_trial_number\": 224, \"price_ratio\": 5.458942430478813, \"shift_exponent\": 7.679319490048774e-05, \"step\": 224, \"test_objective\": [{\"annualised_returns\": -0.47637089702876745, \"annualised_returns_over_hodl\": 0.01578207154148803, \"annualised_returns_over_uniform_hodl\": 0.8481786581821713, \"calmar\": -0.821770583741893, \"daily_log_sharpe\": -0.6781072895012364, \"daily_returns\": 0.031016042754045795, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": 0.00864633167885299, \"return\": -0.22938005122943272, \"returns_over_hodl\": 0.006326330191409157, \"returns_over_uniform_hodl\": 0.28064240500638005, \"sharpe\": -0.1874840703715488, \"sterling\": -1.2264389093287307, \"ulcer\": -0.176410316519157}], \"train_objective\": [{\"annualised_returns\": -0.48365219488090416, \"annualised_returns_over_hodl\": -0.3497434974281516, \"annualised_returns_over_uniform_hodl\": -0.3497434974281516, \"calmar\": -0.6450106856615703, \"daily_log_sharpe\": -0.4736528025588951, \"daily_returns\": 0.008109929911333788, \"fee_revenue_over_value\": 0.0018985872670945556, \"jax_sharpe\": 0.16596715219214406, \"return\": -0.33054536465146056, \"returns_over_hodl\": -0.22994857920184053, \"returns_over_uniform_hodl\": -0.22994857920184053, \"sharpe\": 0.11956330478816243, \"sterling\": -1.4245252392611198, \"ulcer\": -0.18420412753236062}], \"train_return\": -0.33054536465146056, \"train_returns_over_hodl\": -0.22994857920184053, \"train_sharpe\": 0.16596715219214406, \"validation_return\": -0.3391427223077508, \"validation_returns_over_hodl\": -6.149281084333325e-11, \"validation_sharpe\": -2.2438532050074915}, {\"centeredness_margin\": 0.41695492558774705, \"continuous_test_metrics\": [{\"annualised_returns\": -0.48451947474105317, \"annualised_returns_over_hodl\": -2.5260627496193067e-05, \"annualised_returns_over_uniform_hodl\": 0.8194177903523836, \"calmar\": -0.8358274312157367, \"daily_log_sharpe\": -0.6984500757493743, \"daily_returns\": 0.031016042754761917, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": -0.016419417420102715, \"return\": -0.2342323898639045, \"returns_over_hodl\": -1.0173486881814853e-05, \"returns_over_uniform_hodl\": 0.2725786238537258, \"sharpe\": -0.21073759189269492, \"sterling\": -1.2474178358243209, \"ulcer\": -0.17641031652064043}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -0.02738160223641517, \"optuna_trial_number\": 225, \"price_ratio\": 7.02162209680804, \"shift_exponent\": 2.9636899073355663e-05, \"step\": 225, \"test_objective\": [{\"annualised_returns\": -0.48451947474105317, \"annualised_returns_over_hodl\": -2.5260627496193067e-05, \"annualised_returns_over_uniform_hodl\": 0.8194177903523836, \"calmar\": -0.8358274312157367, \"daily_log_sharpe\": -0.6984500757493743, \"daily_returns\": 0.031016042754761917, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": -0.016419417420102715, \"return\": -0.2342323898639045, \"returns_over_hodl\": -1.0173486881814853e-05, \"returns_over_uniform_hodl\": 0.2725786238537258, \"sharpe\": -0.21073759189269492, \"sterling\": -1.2474178358243209, \"ulcer\": -0.17641031652064043}], \"train_objective\": [{\"annualised_returns\": -0.4612326113679508, \"annualised_returns_over_hodl\": -0.3215096600423454, \"annualised_returns_over_uniform_hodl\": -0.32150966004234527, \"calmar\": -0.6171906443444821, \"daily_log_sharpe\": -0.4556328547447463, \"daily_returns\": 0.008102961549517727, \"fee_revenue_over_value\": 0.0018929328381222345, \"jax_sharpe\": 0.15109723322982216, \"return\": -0.313045480158063, \"returns_over_hodl\": -0.2098190435971935, \"returns_over_uniform_hodl\": -0.2098190435971934, \"sharpe\": 0.11973026245411904, \"sterling\": -1.3829228733431054, \"ulcer\": -0.18068915263220253}], \"train_return\": -0.313045480158063, \"train_returns_over_hodl\": -0.2098190435971935, \"train_sharpe\": 0.15109723322982213, \"validation_return\": -0.3376726655872774, \"validation_returns_over_hodl\": -0.02738160223641517, \"validation_sharpe\": -2.2305087750173933}, {\"centeredness_margin\": 0.4077707928150969, \"continuous_test_metrics\": [{\"annualised_returns\": -0.5119998785857414, \"annualised_returns_over_hodl\": -0.05333417980261501, \"annualised_returns_over_uniform_hodl\": 0.7224241442471759, \"calmar\": -0.8834954857056029, \"daily_log_sharpe\": -0.7610674560187582, \"daily_returns\": 0.03101604275263443, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": -0.07888045944812305, \"return\": -0.2509429070120941, \"returns_over_hodl\": -0.021831868363319518, \"returns_over_uniform_hodl\": 0.24480851888343658, \"sharpe\": -0.27861686434264227, \"sterling\": -1.3187926171657485, \"ulcer\": -0.17622907738308224}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -0.06064030339807158, \"optuna_trial_number\": 226, \"price_ratio\": 9.83046501377246, \"shift_exponent\": 1.3294126643721543e-05, \"step\": 226, \"test_objective\": [{\"annualised_returns\": -0.5119998785857414, \"annualised_returns_over_hodl\": -0.05333417980261501, \"annualised_returns_over_uniform_hodl\": 0.7224241442471759, \"calmar\": -0.8834954857056029, \"daily_log_sharpe\": -0.7610674560187582, \"daily_returns\": 0.03101604275263443, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": -0.07888045944812305, \"return\": -0.2509429070120941, \"returns_over_hodl\": -0.021831868363319518, \"returns_over_uniform_hodl\": 0.24480851888343658, \"sharpe\": -0.27861686434264227, \"sterling\": -1.3187926171657485, \"ulcer\": -0.17622907738308224}], \"train_objective\": [{\"annualised_returns\": -0.43417954212055243, \"annualised_returns_over_hodl\": -0.2874406971877633, \"annualised_returns_over_uniform_hodl\": -0.2874406971877633, \"calmar\": -0.5834256742394005, \"daily_log_sharpe\": -0.4325329768809862, \"daily_returns\": 0.008066993398942124, \"fee_revenue_over_value\": 0.0019194122111863676, \"jax_sharpe\": 0.1460060388880679, \"return\": -0.2923053427828909, \"returns_over_hodl\": -0.18596235277753037, \"returns_over_uniform_hodl\": -0.18596235277753037, \"sharpe\": 0.12430027899560929, \"sterling\": -1.3263054155218268, \"ulcer\": -0.177075113848711}], \"train_return\": -0.2923053427828909, \"train_returns_over_hodl\": -0.18596235277753037, \"train_sharpe\": 0.1460060388880679, \"validation_return\": -0.32512725901993267, \"validation_returns_over_hodl\": -0.06064030339807158, \"validation_sharpe\": -2.12983729023914}, {\"centeredness_margin\": 0.40363173251209966, \"continuous_test_metrics\": [{\"annualised_returns\": -0.5174054921049215, \"annualised_returns_over_hodl\": -0.06382046727131885, \"annualised_returns_over_uniform_hodl\": 0.7033447243221933, \"calmar\": -0.8929815597080154, \"daily_log_sharpe\": -0.7736439290031656, \"daily_returns\": 0.031016042752861815, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": -0.09123460759187535, \"return\": -0.2542956878420818, \"returns_over_hodl\": -0.026210150596159276, \"returns_over_uniform_hodl\": 0.23923675382281884, \"sharpe\": -0.292118384603005, \"sterling\": -1.3332749120987475, \"ulcer\": -0.17606928244108944}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -0.06173123175222639, \"optuna_trial_number\": 227, \"price_ratio\": 9.728643779044337, \"shift_exponent\": 0.000107335986576868, \"step\": 227, \"test_objective\": [{\"annualised_returns\": -0.5174054921049215, \"annualised_returns_over_hodl\": -0.06382046727131885, \"annualised_returns_over_uniform_hodl\": 0.7033447243221933, \"calmar\": -0.8929815597080154, \"daily_log_sharpe\": -0.7736439290031656, \"daily_returns\": 0.031016042752861815, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": -0.09123460759187535, \"return\": -0.2542956878420818, \"returns_over_hodl\": -0.026210150596159276, \"returns_over_uniform_hodl\": 0.23923675382281884, \"sharpe\": -0.292118384603005, \"sterling\": -1.3332749120987475, \"ulcer\": -0.17606928244108944}], \"train_objective\": [{\"annualised_returns\": -0.43230504094046496, \"annualised_returns_over_hodl\": -0.2850800662925681, \"annualised_returns_over_uniform_hodl\": -0.2850800662925681, \"calmar\": -0.580913866374056, \"daily_log_sharpe\": -0.4304443521430717, \"daily_returns\": 0.008027822964471538, \"fee_revenue_over_value\": 0.0019470554142599246, \"jax_sharpe\": 0.10667736554308051, \"return\": -0.29088286257961304, \"returns_over_hodl\": -0.1843261210692818, \"returns_over_uniform_hodl\": -0.1843261210692818, \"sharpe\": 0.1258850603798171, \"sterling\": -1.3214023566649007, \"ulcer\": -0.1769171187133276}], \"train_return\": -0.29088286257961304, \"train_returns_over_hodl\": -0.1843261210692818, \"train_sharpe\": 0.10667736554308051, \"validation_return\": -0.3233607514032676, \"validation_returns_over_hodl\": -0.06173123175222639, \"validation_sharpe\": -2.119585271880332}, {\"centeredness_margin\": 0.41017161944057157, \"continuous_test_metrics\": [{\"annualised_returns\": -0.48524553090914035, \"annualised_returns_over_hodl\": -0.001433728627423636, \"annualised_returns_over_uniform_hodl\": 0.8168551338711356, \"calmar\": -0.8370799272440866, \"daily_log_sharpe\": -0.7003788536166907, \"daily_returns\": 0.031016042754617612, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": -0.018847028284496612, \"return\": -0.23466696026343803, \"returns_over_hodl\": -0.0005776641601160648, \"returns_over_uniform_hodl\": 0.2718564400035792, \"sharpe\": -0.21292765736345198, \"sterling\": -1.2492871043229428, \"ulcer\": -0.17641031652033642}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -0.02123874493184319, \"optuna_trial_number\": 228, \"price_ratio\": 6.373528332063868, \"shift_exponent\": 0.00020281012340162542, \"step\": 228, \"test_objective\": [{\"annualised_returns\": -0.48524553090914035, \"annualised_returns_over_hodl\": -0.001433728627423636, \"annualised_returns_over_uniform_hodl\": 0.8168551338711356, \"calmar\": -0.8370799272440866, \"daily_log_sharpe\": -0.7003788536166907, \"daily_returns\": 0.031016042754617612, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": -0.018847028284496612, \"return\": -0.23466696026343803, \"returns_over_hodl\": -0.0005776641601160648, \"returns_over_uniform_hodl\": 0.2718564400035792, \"sharpe\": -0.21292765736345198, \"sterling\": -1.2492871043229428, \"ulcer\": -0.17641031652033642}], \"train_objective\": [{\"annualised_returns\": -0.466134237802899, \"annualised_returns_over_hodl\": -0.32768246533154, \"annualised_returns_over_uniform_hodl\": -0.32768246533154, \"calmar\": -0.6228571950990762, \"daily_log_sharpe\": -0.45947067419591286, \"daily_returns\": 0.008079377610091663, \"fee_revenue_over_value\": 0.0019011907101324167, \"jax_sharpe\": 0.10690047257932707, \"return\": -0.31684668421238105, \"returns_over_hodl\": -0.21419144230535603, \"returns_over_uniform_hodl\": -0.21419144230535603, \"sharpe\": 0.12025722484187713, \"sterling\": -1.3918154761556512, \"ulcer\": -0.18149027009559165}], \"train_return\": -0.31684668421238105, \"train_returns_over_hodl\": -0.21419144230535603, \"train_sharpe\": 0.10690047257932708, \"validation_return\": -0.3384076338016637, \"validation_returns_over_hodl\": -0.02123874493184319, \"validation_sharpe\": -2.2371710529306243}, {\"centeredness_margin\": 0.3394717064423575, \"continuous_test_metrics\": [{\"annualised_returns\": -0.5259925986654974, \"annualised_returns_over_hodl\": -0.08047849642944782, \"annualised_returns_over_uniform_hodl\": 0.673036044016347, \"calmar\": -0.9077526392602432, \"daily_log_sharpe\": -0.7954665881542242, \"daily_returns\": 0.031016042750930665, \"fee_revenue_over_value\": 1.5323232044769283e-07, \"jax_sharpe\": -0.11455952841106555, \"return\": -0.25966819701000987, \"returns_over_hodl\": -0.033225927215676965, \"returns_over_uniform_hodl\": 0.23030853561005293, \"sharpe\": -0.3159209216872068, \"sterling\": -1.3550844206625372, \"ulcer\": -0.17615871298865232}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -0.04102756597157953, \"optuna_trial_number\": 229, \"price_ratio\": 4.914942596911606, \"shift_exponent\": 0.0013156235625713217, \"step\": 229, \"test_objective\": [{\"annualised_returns\": -0.5259925986654974, \"annualised_returns_over_hodl\": -0.08047849642944782, \"annualised_returns_over_uniform_hodl\": 0.673036044016347, \"calmar\": -0.9077526392602432, \"daily_log_sharpe\": -0.7954665881542242, \"daily_returns\": 0.031016042750930665, \"fee_revenue_over_value\": 1.5323232044769283e-07, \"jax_sharpe\": -0.11455952841106555, \"return\": -0.25966819701000987, \"returns_over_hodl\": -0.033225927215676965, \"returns_over_uniform_hodl\": 0.23030853561005293, \"sharpe\": -0.3159209216872068, \"sterling\": -1.3550844206625372, \"ulcer\": -0.17615871298865232}], \"train_objective\": [{\"annualised_returns\": -0.4581664069362139, \"annualised_returns_over_hodl\": -0.31764827174905796, \"annualised_returns_over_uniform_hodl\": -0.31764827174905796, \"calmar\": -0.6103433136199367, \"daily_log_sharpe\": -0.44530453795667746, \"daily_returns\": 0.0081293155846167, \"fee_revenue_over_value\": 0.002180602300511578, \"jax_sharpe\": 0.1866857684428091, \"return\": -0.3106745497582595, \"returns_over_hodl\": -0.2070918411452578, \"returns_over_uniform_hodl\": -0.2070918411452578, \"sharpe\": 0.1401967573441701, \"sterling\": -1.3636742100734067, \"ulcer\": -0.18171289784366557}], \"train_return\": -0.3106745497582595, \"train_returns_over_hodl\": -0.2070918411452578, \"train_sharpe\": 0.1866857684428091, \"validation_return\": -0.33591190149130623, \"validation_returns_over_hodl\": -0.04102756597157953, \"validation_sharpe\": -2.215059078798064}, {\"centeredness_margin\": 0.3372424474206692, \"continuous_test_metrics\": [{\"annualised_returns\": -0.4998540884874999, \"annualised_returns_over_hodl\": -0.02977270139747068, \"annualised_returns_over_uniform_hodl\": 0.7652934002128113, \"calmar\": -0.8622806795496759, \"daily_log_sharpe\": -0.7335271455468603, \"daily_returns\": 0.031016042755189023, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": -0.05163972763106344, \"return\": -0.24348965860755267, \"returns_over_hodl\": -0.0120989252801601, \"returns_over_uniform_hodl\": 0.2571945802320035, \"sharpe\": -0.2490617863908754, \"sterling\": -1.2868975807422072, \"ulcer\": -0.17641031652152103}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -0.045883831049147616, \"optuna_trial_number\": 230, \"price_ratio\": 7.364898612458036, \"shift_exponent\": 0.00026641223729021444, \"step\": 230, \"test_objective\": [{\"annualised_returns\": -0.4998540884874999, \"annualised_returns_over_hodl\": -0.02977270139747068, \"annualised_returns_over_uniform_hodl\": 0.7652934002128113, \"calmar\": -0.8622806795496759, \"daily_log_sharpe\": -0.7335271455468603, \"daily_returns\": 0.031016042755189023, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": -0.05163972763106344, \"return\": -0.24348965860755267, \"returns_over_hodl\": -0.0120989252801601, \"returns_over_uniform_hodl\": 0.2571945802320035, \"sharpe\": -0.2490617863908754, \"sterling\": -1.2868975807422072, \"ulcer\": -0.17641031652152103}], \"train_objective\": [{\"annualised_returns\": -0.4504799704655248, \"annualised_returns_over_hodl\": -0.30796844887168906, \"annualised_returns_over_uniform_hodl\": -0.30796844887168906, \"calmar\": -0.6033389695063879, \"daily_log_sharpe\": -0.445558367027222, \"daily_returns\": 0.008089955757684817, \"fee_revenue_over_value\": 0.0021654773316485986, \"jax_sharpe\": 0.1535566040183936, \"return\": -0.30475409653148133, \"returns_over_hodl\": -0.20028174053750558, \"returns_over_uniform_hodl\": -0.20028174053750558, \"sharpe\": 0.12393556964731302, \"sterling\": -1.359037270046322, \"ulcer\": -0.17942087291256917}], \"train_return\": -0.30475409653148133, \"train_returns_over_hodl\": -0.20028174053750558, \"train_sharpe\": 0.15355660401839363, \"validation_return\": -0.3345590114089856, \"validation_returns_over_hodl\": -0.045883831049147616, \"validation_sharpe\": -2.2031977528842415}, {\"centeredness_margin\": 0.3147281575074217, \"continuous_test_metrics\": [{\"annualised_returns\": -0.6290761418326728, \"annualised_returns_over_hodl\": -0.2804490757159587, \"annualised_returns_over_uniform_hodl\": 0.30919682383106006, \"calmar\": -1.0811134596493697, \"daily_log_sharpe\": -1.0856842098587152, \"daily_returns\": 0.031016042754240736, \"fee_revenue_over_value\": 0.0004891478343894426, \"jax_sharpe\": -0.4186251289947858, \"return\": -0.3292901488932092, \"returns_over_hodl\": -0.12414286165538002, \"returns_over_uniform_hodl\": 0.11460841125798482, \"sharpe\": -0.6300970078104924, \"sterling\": -1.6605399052011462, \"ulcer\": -0.16900036294811133}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -0.06655334712030433, \"optuna_trial_number\": 231, \"price_ratio\": 5.191654985596628, \"shift_exponent\": 0.002831601106789234, \"step\": 231, \"test_objective\": [{\"annualised_returns\": -0.6290761418326728, \"annualised_returns_over_hodl\": -0.2804490757159587, \"annualised_returns_over_uniform_hodl\": 0.30919682383106006, \"calmar\": -1.0811134596493697, \"daily_log_sharpe\": -1.0856842098587152, \"daily_returns\": 0.031016042754240736, \"fee_revenue_over_value\": 0.0004891478343894426, \"jax_sharpe\": -0.4186251289947858, \"return\": -0.3292901488932092, \"returns_over_hodl\": -0.12414286165538002, \"returns_over_uniform_hodl\": 0.11460841125798482, \"sharpe\": -0.6300970078104924, \"sterling\": -1.6605399052011462, \"ulcer\": -0.16900036294811133}], \"train_objective\": [{\"annualised_returns\": -0.435216917556007, \"annualised_returns_over_hodl\": -0.28874710367545453, \"annualised_returns_over_uniform_hodl\": -0.28874710367545453, \"calmar\": -0.5809321171938142, \"daily_log_sharpe\": -0.42996382736320643, \"daily_returns\": 0.00813566515266458, \"fee_revenue_over_value\": 0.002755661777525801, \"jax_sharpe\": 0.12754703902782072, \"return\": -0.2930933592239685, \"returns_over_hodl\": -0.1868687819036029, \"returns_over_uniform_hodl\": -0.1868687819036029, \"sharpe\": 0.13824672379839775, \"sterling\": -1.3167147521007534, \"ulcer\": -0.17841145618482637}], \"train_return\": -0.2930933592239685, \"train_returns_over_hodl\": -0.1868687819036029, \"train_sharpe\": 0.12754703902782075, \"validation_return\": -0.3080089928705373, \"validation_returns_over_hodl\": -0.06655334712030436, \"validation_sharpe\": -2.0810790256882608}, {\"centeredness_margin\": 0.9264518747714119, \"continuous_test_metrics\": [{\"annualised_returns\": -0.7303525250350722, \"annualised_returns_over_hodl\": -0.05107365005259623, \"annualised_returns_over_uniform_hodl\": -0.04826392262182999, \"calmar\": -1.2195626194005533, \"daily_log_sharpe\": -1.6754742175579815, \"daily_returns\": 0.018126834008420092, \"fee_revenue_over_value\": 0.006928388593224734, \"jax_sharpe\": -1.15578245683873, \"return\": -0.4101247640699188, \"returns_over_hodl\": -0.020891841971026892, \"returns_over_uniform_hodl\": -0.019725297793825924, \"sharpe\": -1.2935728888639428, \"sterling\": -2.2635530282850045, \"ulcer\": -0.1479182736745382}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -0.02516822257377871, \"optuna_trial_number\": 232, \"price_ratio\": 172.4625872177212, \"shift_exponent\": 0.0521145161004542, \"step\": 232, \"test_objective\": [{\"annualised_returns\": -0.7303525250350722, \"annualised_returns_over_hodl\": -0.05107365005259623, \"annualised_returns_over_uniform_hodl\": -0.04826392262182999, \"calmar\": -1.2195626194005533, \"daily_log_sharpe\": -1.6754742175579815, \"daily_returns\": 0.018126834008420092, \"fee_revenue_over_value\": 0.006928388593224734, \"jax_sharpe\": -1.15578245683873, \"return\": -0.4101247640699188, \"returns_over_hodl\": -0.020891841971026892, \"returns_over_uniform_hodl\": -0.019725297793825924, \"sharpe\": -1.2935728888639428, \"sterling\": -2.2635530282850045, \"ulcer\": -0.1479182736745382}], \"train_objective\": [{\"annualised_returns\": -0.3168555839388608, \"annualised_returns_over_hodl\": -0.13969015780565852, \"annualised_returns_over_uniform_hodl\": -0.13969015780565852, \"calmar\": -0.4327809062617403, \"daily_log_sharpe\": -0.3278679009483093, \"daily_returns\": 0.007946201535608807, \"fee_revenue_over_value\": 0.004362838206929942, \"jax_sharpe\": 0.12141662913787399, \"return\": -0.20653273632592317, \"returns_over_hodl\": -0.08730097382783975, \"returns_over_uniform_hodl\": -0.08730097382783975, \"sharpe\": 0.15972884923566644, \"sterling\": -1.0490060091075062, \"ulcer\": -0.16342090696159342}], \"train_return\": -0.20653273632592317, \"train_returns_over_hodl\": -0.08730097382783975, \"train_sharpe\": 0.12141662913787397, \"validation_return\": -0.15880807560851895, \"validation_returns_over_hodl\": -0.02516822257377871, \"validation_sharpe\": -1.2592498218157058}, {\"centeredness_margin\": 0.5007088756137138, \"continuous_test_metrics\": [{\"annualised_returns\": -0.48311358357996625, \"annualised_returns_over_hodl\": 0.0027020115742020234, \"annualised_returns_over_uniform_hodl\": 0.8243799630523057, \"calmar\": -0.8334021738299664, \"daily_log_sharpe\": -0.6895741655271913, \"daily_returns\": 0.031016042751936618, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": 0.006512996776173055, \"return\": -0.2333919522145339, \"returns_over_hodl\": 0.00108732544582546, \"returns_over_uniform_hodl\": 0.2739752916849507, \"sharpe\": -0.19901030348277451, \"sterling\": -1.2437982976769968, \"ulcer\": -0.176410316514803}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -8.248435268143339e-11, \"optuna_trial_number\": 233, \"price_ratio\": 3.302189614784127, \"shift_exponent\": 0.0004785868559531022, \"step\": 233, \"test_objective\": [{\"annualised_returns\": -0.48311358357996625, \"annualised_returns_over_hodl\": 0.0027020115742020234, \"annualised_returns_over_uniform_hodl\": 0.8243799630523057, \"calmar\": -0.8334021738299664, \"daily_log_sharpe\": -0.6895741655271913, \"daily_returns\": 0.031016042751936618, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": 0.006512996776173055, \"return\": -0.2333919522145339, \"returns_over_hodl\": 0.00108732544582546, \"returns_over_uniform_hodl\": 0.2739752916849507, \"sharpe\": -0.19901030348277451, \"sterling\": -1.2437982976769968, \"ulcer\": -0.176410316514803}], \"train_objective\": [{\"annualised_returns\": -0.5449807303250271, \"annualised_returns_over_hodl\": -0.4269768633307156, \"annualised_returns_over_uniform_hodl\": -0.4269768633307157, \"calmar\": -0.7140550973146161, \"daily_log_sharpe\": -0.5452948189848563, \"daily_returns\": 0.008236207465737942, \"fee_revenue_over_value\": 0.0008775175379195847, \"jax_sharpe\": 0.1518501328016575, \"return\": -0.3800130162183438, \"returns_over_hodl\": -0.286849574969529, \"returns_over_uniform_hodl\": -0.2868495749695291, \"sharpe\": 0.08897489356438686, \"sterling\": -1.5294807981956047, \"ulcer\": -0.1949697034809054}], \"train_return\": -0.3800130162183438, \"train_returns_over_hodl\": -0.286849574969529, \"train_sharpe\": 0.15185013280165746, \"validation_return\": -0.33914272229898756, \"validation_returns_over_hodl\": -8.248435268143339e-11, \"validation_sharpe\": -2.2438532050666145}, {\"centeredness_margin\": 0.4891022025500859, \"continuous_test_metrics\": [{\"annualised_returns\": -0.4787958234211308, \"annualised_returns_over_hodl\": 0.011077984725913703, \"annualised_returns_over_uniform_hodl\": 0.8396197427578804, \"calmar\": -0.8259537456077755, \"daily_log_sharpe\": -0.6843986013483464, \"daily_returns\": 0.031016042754294884, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": -0.0002924998175486366, \"return\": -0.23081931081431262, \"returns_over_hodl\": 0.004446850141981207, \"returns_over_uniform_hodl\": 0.2782505945437137, \"sharpe\": -0.19477006236439265, \"sterling\": -1.2326820051194427, \"ulcer\": -0.17641031651967243}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -0.0009568202293943617, \"optuna_trial_number\": 234, \"price_ratio\": 6.233327269571332, \"shift_exponent\": 1.1756438153902085e-05, \"step\": 234, \"test_objective\": [{\"annualised_returns\": -0.4787958234211308, \"annualised_returns_over_hodl\": 0.011077984725913703, \"annualised_returns_over_uniform_hodl\": 0.8396197427578804, \"calmar\": -0.8259537456077755, \"daily_log_sharpe\": -0.6843986013483464, \"daily_returns\": 0.031016042754294884, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": -0.0002924998175486366, \"return\": -0.23081931081431262, \"returns_over_hodl\": 0.004446850141981207, \"returns_over_uniform_hodl\": 0.2782505945437137, \"sharpe\": -0.19477006236439265, \"sterling\": -1.2326820051194427, \"ulcer\": -0.17641031651967243}], \"train_objective\": [{\"annualised_returns\": -0.47423500313133105, \"annualised_returns_over_hodl\": -0.3378840683564748, \"annualised_returns_over_uniform_hodl\": -0.3378840683564748, \"calmar\": -0.6334650374925016, \"daily_log_sharpe\": -0.46697050135835355, \"daily_returns\": 0.008106239952570798, \"fee_revenue_over_value\": 0.0018597723042795334, \"jax_sharpe\": 0.10419135694010492, \"return\": -0.32315901075587583, \"returns_over_hodl\": -0.2214523017672213, \"returns_over_uniform_hodl\": -0.2214523017672213, \"sharpe\": 0.1170213468438198, \"sterling\": -1.409564052204993, \"ulcer\": -0.18242388595608422}], \"train_return\": -0.32315901075587583, \"train_returns_over_hodl\": -0.2214523017672213, \"train_sharpe\": 0.10419135694010492, \"validation_return\": -0.3390749497855795, \"validation_returns_over_hodl\": -0.0009568202293943617, \"validation_sharpe\": -2.2432352811642833}, {\"centeredness_margin\": 0.3925701254978954, \"continuous_test_metrics\": [{\"annualised_returns\": -0.48313898034212244, \"annualised_returns_over_hodl\": 0.0026527446464241766, \"annualised_returns_over_uniform_hodl\": 0.8242903237378796, \"calmar\": -0.8334459849654061, \"daily_log_sharpe\": -0.6896372891784034, \"daily_returns\": 0.03101604275293879, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": 0.006060860402070033, \"return\": -0.23340712220814264, \"returns_over_hodl\": 0.0010675154516011087, \"returns_over_uniform_hodl\": 0.27395008167432233, \"sharpe\": -0.19908642450819983, \"sterling\": -1.243863682914205, \"ulcer\": -0.17641031651687564}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -7.450884353943366e-11, \"optuna_trial_number\": 235, \"price_ratio\": 4.303252552190091, \"shift_exponent\": 2.1531570504148525e-05, \"step\": 235, \"test_objective\": [{\"annualised_returns\": -0.48313898034212244, \"annualised_returns_over_hodl\": 0.0026527446464241766, \"annualised_returns_over_uniform_hodl\": 0.8242903237378796, \"calmar\": -0.8334459849654061, \"daily_log_sharpe\": -0.6896372891784034, \"daily_returns\": 0.03101604275293879, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": 0.006060860402070033, \"return\": -0.23340712220814264, \"returns_over_hodl\": 0.0010675154516011087, \"returns_over_uniform_hodl\": 0.27395008167432233, \"sharpe\": -0.19908642450819983, \"sterling\": -1.243863682914205, \"ulcer\": -0.17641031651687564}], \"train_objective\": [{\"annualised_returns\": -0.5161561555873594, \"annualised_returns_over_hodl\": -0.39067697598498896, \"annualised_returns_over_uniform_hodl\": -0.3906769759849885, \"calmar\": -0.684108632046507, \"daily_log_sharpe\": -0.510086854444876, \"daily_returns\": 0.008173985930043894, \"fee_revenue_over_value\": 0.0019037961879991635, \"jax_sharpe\": 0.1135880358138174, \"return\": -0.3564566653165754, \"returns_over_hodl\": -0.25975348731411607, \"returns_over_uniform_hodl\": -0.25975348731411574, \"sharpe\": 0.10577825988284682, \"sterling\": -1.4843669289437555, \"ulcer\": -0.18948094341207938}], \"train_return\": -0.3564566653165754, \"train_returns_over_hodl\": -0.25975348731411607, \"train_sharpe\": 0.1135880358138174, \"validation_return\": -0.33914272230478926, \"validation_returns_over_hodl\": -7.450884353943366e-11, \"validation_sharpe\": -2.2438532050549616}, {\"centeredness_margin\": 0.41833310237723054, \"continuous_test_metrics\": [{\"annualised_returns\": -0.47766540176237304, \"annualised_returns_over_hodl\": 0.013270876699811529, \"annualised_returns_over_uniform_hodl\": 0.8436096302041811, \"calmar\": -0.824003691858517, \"daily_log_sharpe\": -0.681558799124556, \"daily_returns\": 0.031016042754156592, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": 0.0041348476255438525, \"return\": -0.2301478783764469, \"returns_over_hodl\": 0.005323650884019537, \"returns_over_uniform_hodl\": 0.2793664037741901, \"sharpe\": -0.19148336639411798, \"sterling\": -1.229771677299919, \"ulcer\": -0.17641031651938974}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -6.89490686767158e-11, \"optuna_trial_number\": 236, \"price_ratio\": 5.974435173189385, \"shift_exponent\": 2.2958427617330644e-05, \"step\": 236, \"test_objective\": [{\"annualised_returns\": -0.47766540176237304, \"annualised_returns_over_hodl\": 0.013270876699811529, \"annualised_returns_over_uniform_hodl\": 0.8436096302041811, \"calmar\": -0.824003691858517, \"daily_log_sharpe\": -0.681558799124556, \"daily_returns\": 0.031016042754156592, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": 0.0041348476255438525, \"return\": -0.2301478783764469, \"returns_over_hodl\": 0.005323650884019537, \"returns_over_uniform_hodl\": 0.2793664037741901, \"sharpe\": -0.19148336639411798, \"sterling\": -1.229771677299919, \"ulcer\": -0.17641031651938974}], \"train_objective\": [{\"annualised_returns\": -0.47945673866736127, \"annualised_returns_over_hodl\": -0.3444599992568319, \"annualised_returns_over_uniform_hodl\": -0.3444599992568319, \"calmar\": -0.6398980902686964, \"daily_log_sharpe\": -0.47227578386104335, \"daily_returns\": 0.008097516136027992, \"fee_revenue_over_value\": 0.001892240646900215, \"jax_sharpe\": 0.15500476977445574, \"return\": -0.3272481865382151, \"returns_over_hodl\": -0.2261559447847118, \"returns_over_uniform_hodl\": -0.2261559447847118, \"sharpe\": 0.11470408760285852, \"sterling\": -1.4205297455263939, \"ulcer\": -0.18307993919361643}], \"train_return\": -0.3272481865382151, \"train_returns_over_hodl\": -0.2261559447847118, \"train_sharpe\": 0.15500476977445574, \"validation_return\": -0.3391427223070408, \"validation_returns_over_hodl\": -6.89490686767158e-11, \"validation_sharpe\": -2.243853204992736}, {\"centeredness_margin\": 0.4861617339936223, \"continuous_test_metrics\": [{\"annualised_returns\": -0.7748109754501544, \"annualised_returns_over_hodl\": -0.29024212737337407, \"annualised_returns_over_uniform_hodl\": -0.20518254835665495, \"calmar\": -1.264310113079314, \"daily_log_sharpe\": -1.857752576440937, \"daily_returns\": 0.020331663998707443, \"fee_revenue_over_value\": 0.010838412924970316, \"jax_sharpe\": -1.3583748901230388, \"return\": -0.45141203409310593, \"returns_over_hodl\": -0.12896330079290352, \"returns_over_uniform_hodl\": -0.08833788544224663, \"sharpe\": -1.4850047068902015, \"sterling\": -2.3391424722425995, \"ulcer\": -0.15299697353048614}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -0.03300025339132029, \"optuna_trial_number\": 237, \"price_ratio\": 6.972114494918989, \"shift_exponent\": 12.133237386493388, \"step\": 237, \"test_objective\": [{\"annualised_returns\": -0.7748109754501544, \"annualised_returns_over_hodl\": -0.29024212737337407, \"annualised_returns_over_uniform_hodl\": -0.20518254835665495, \"calmar\": -1.264310113079314, \"daily_log_sharpe\": -1.857752576440937, \"daily_returns\": 0.020331663998707443, \"fee_revenue_over_value\": 0.010838412924970316, \"jax_sharpe\": -1.3583748901230388, \"return\": -0.45141203409310593, \"returns_over_hodl\": -0.12896330079290352, \"returns_over_uniform_hodl\": -0.08833788544224663, \"sharpe\": -1.4850047068902015, \"sterling\": -2.3391424722425995, \"ulcer\": -0.15299697353048614}], \"train_objective\": [{\"annualised_returns\": -0.3781194167351283, \"annualised_returns_over_hodl\": -0.2168420411937536, \"annualised_returns_over_uniform_hodl\": -0.2168420411937536, \"calmar\": -0.5072581048597792, \"daily_log_sharpe\": -0.40857494539433353, \"daily_returns\": 0.008087362832682358, \"fee_revenue_over_value\": 0.006437310749338819, \"jax_sharpe\": 0.09556411095689109, \"return\": -0.2505285280476832, \"returns_over_hodl\": -0.1379078710477577, \"returns_over_uniform_hodl\": -0.1379078710477577, \"sharpe\": 0.0918686129039466, \"sterling\": -1.2173040011346952, \"ulcer\": -0.16806932716933878}], \"train_return\": -0.2505285280476832, \"train_returns_over_hodl\": -0.1379078710477577, \"train_sharpe\": 0.0955641109568911, \"validation_return\": -0.19244811901987258, \"validation_returns_over_hodl\": -0.03300025339132029, \"validation_sharpe\": -1.52982588136569}, {\"centeredness_margin\": 0.27481432422625623, \"continuous_test_metrics\": [{\"annualised_returns\": -0.48401266979278423, \"annualised_returns_over_hodl\": 0.0009578845489905952, \"annualised_returns_over_uniform_hodl\": 0.8212065872010266, \"calmar\": -0.8349531583792182, \"daily_log_sharpe\": -0.6914335884920112, \"daily_returns\": 0.031016042752582764, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": 0.005666427018917546, \"return\": -0.23392926571772987, \"returns_over_hodl\": 0.00038566604170653385, \"returns_over_uniform_hodl\": 0.27308236585546375, \"sharpe\": -0.20100005994982484, \"sterling\": -1.2461130407767602, \"ulcer\": -0.17641031651613887}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -7.873623975029886e-11, \"optuna_trial_number\": 238, \"price_ratio\": 3.9953231908310722, \"shift_exponent\": 1.1284851155358613e-05, \"step\": 238, \"test_objective\": [{\"annualised_returns\": -0.48401266979278423, \"annualised_returns_over_hodl\": 0.0009578845489905952, \"annualised_returns_over_uniform_hodl\": 0.8212065872010266, \"calmar\": -0.8349531583792182, \"daily_log_sharpe\": -0.6914335884920112, \"daily_returns\": 0.031016042752582764, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": 0.005666427018917546, \"return\": -0.23392926571772987, \"returns_over_hodl\": 0.00038566604170653385, \"returns_over_uniform_hodl\": 0.27308236585546375, \"sharpe\": -0.20100005994982484, \"sterling\": -1.2461130407767602, \"ulcer\": -0.17641031651613887}], \"train_objective\": [{\"annualised_returns\": -0.5262400613855934, \"annualised_returns_over_hodl\": -0.4033760235099628, \"annualised_returns_over_uniform_hodl\": -0.40337602350996304, \"calmar\": -0.6956462550754328, \"daily_log_sharpe\": -0.5219728778993619, \"daily_returns\": 0.008187230058421523, \"fee_revenue_over_value\": 0.001960424315550216, \"jax_sharpe\": 0.1609400976955207, \"return\": -0.36463318763899466, \"returns_over_hodl\": -0.2691586692325122, \"returns_over_uniform_hodl\": -0.2691586692325123, \"sharpe\": 0.10064386303740984, \"sterling\": -1.4995267749331331, \"ulcer\": -0.1915229387597338}], \"train_return\": -0.36463318763899466, \"train_returns_over_hodl\": -0.2691586692325122, \"train_sharpe\": 0.1609400976955207, \"validation_return\": -0.33914272230364795, \"validation_returns_over_hodl\": -7.873623975029886e-11, \"validation_sharpe\": -2.243853205068035}, {\"centeredness_margin\": 0.3251626130408091, \"continuous_test_metrics\": [{\"annualised_returns\": -0.48126666345963554, \"annualised_returns_over_hodl\": 0.006284830605556513, \"annualised_returns_over_uniform_hodl\": 0.8308987725118897, \"calmar\": -0.8302161117069077, \"daily_log_sharpe\": -0.6859359917217609, \"daily_returns\": 0.031016042753351, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": 0.007782484670262978, \"return\": -0.23228994103129952, \"returns_over_hodl\": 0.002526404272777283, \"returns_over_uniform_hodl\": 0.2758066512991064, \"sharpe\": -0.19525038763320762, \"sterling\": -1.2390433080776366, \"ulcer\": -0.17641031651772665}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -6.967593169093789e-11, \"optuna_trial_number\": 239, \"price_ratio\": 4.706343227730422, \"shift_exponent\": 1.4299854368433432e-05, \"step\": 239, \"test_objective\": [{\"annualised_returns\": -0.48126666345963554, \"annualised_returns_over_hodl\": 0.006284830605556513, \"annualised_returns_over_uniform_hodl\": 0.8308987725118897, \"calmar\": -0.8302161117069077, \"daily_log_sharpe\": -0.6859359917217609, \"daily_returns\": 0.031016042753351, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": 0.007782484670262978, \"return\": -0.23228994103129952, \"returns_over_hodl\": 0.002526404272777283, \"returns_over_uniform_hodl\": 0.2758066512991064, \"sharpe\": -0.19525038763320762, \"sterling\": -1.2390433080776366, \"ulcer\": -0.17641031651772665}], \"train_objective\": [{\"annualised_returns\": -0.5041565591104522, \"annualised_returns_over_hodl\": -0.3755654260568404, \"annualised_returns_over_uniform_hodl\": -0.3755654260568402, \"calmar\": -0.6699514818396263, \"daily_log_sharpe\": -0.49614550640695954, \"daily_returns\": 0.00815923045956001, \"fee_revenue_over_value\": 0.001938334178766376, \"jax_sharpe\": 0.11216769279495259, \"return\": -0.34681353791156844, \"returns_over_hodl\": -0.24866131830508797, \"returns_over_uniform_hodl\": -0.24866131830508775, \"sharpe\": 0.11164748615568808, \"sterling\": -1.4626852916819655, \"ulcer\": -0.18743894729059965}], \"train_return\": -0.34681353791156844, \"train_returns_over_hodl\": -0.24866131830508797, \"train_sharpe\": 0.11216769279495259, \"validation_return\": -0.3391427223061402, \"validation_returns_over_hodl\": -6.967593169093789e-11, \"validation_sharpe\": -2.2438532050395463}, {\"centeredness_margin\": 0.8664003064318216, \"continuous_test_metrics\": [{\"annualised_returns\": -0.47626447335374567, \"annualised_returns_over_hodl\": 0.01598852161413089, \"annualised_returns_over_uniform_hodl\": 0.8485542865874383, \"calmar\": -0.8215869957134974, \"daily_log_sharpe\": -0.67763200516059, \"daily_returns\": 0.03101604275380236, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": 0.00928720812676675, \"return\": -0.2293169771698269, \"returns_over_hodl\": 0.006408696460449725, \"returns_over_uniform_hodl\": 0.28074722362094606, \"sharpe\": -0.18688136724479296, \"sterling\": -1.2261649161951458, \"ulcer\": -0.17641031651865782}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -6.329248236625062e-11, \"optuna_trial_number\": 240, \"price_ratio\": 4.929835523948817, \"shift_exponent\": 0.00020816888444601879, \"step\": 240, \"test_objective\": [{\"annualised_returns\": -0.47626447335374567, \"annualised_returns_over_hodl\": 0.01598852161413089, \"annualised_returns_over_uniform_hodl\": 0.8485542865874383, \"calmar\": -0.8215869957134974, \"daily_log_sharpe\": -0.67763200516059, \"daily_returns\": 0.03101604275380236, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": 0.00928720812676675, \"return\": -0.2293169771698269, \"returns_over_hodl\": 0.006408696460449725, \"returns_over_uniform_hodl\": 0.28074722362094606, \"sharpe\": -0.18688136724479296, \"sterling\": -1.2261649161951458, \"ulcer\": -0.17641031651865782}], \"train_objective\": [{\"annualised_returns\": -0.4922118570445818, \"annualised_returns_over_hodl\": -0.36052300675610527, \"annualised_returns_over_uniform_hodl\": -0.36052300675610527, \"calmar\": -0.6548180299094463, \"daily_log_sharpe\": -0.4823396105349923, \"daily_returns\": 0.008140638698213116, \"fee_revenue_over_value\": 4.505213653071568e-05, \"jax_sharpe\": 0.11238851396355728, \"return\": -0.33730516525864995, \"returns_over_hodl\": -0.2377241532094332, \"returns_over_uniform_hodl\": -0.2377241532094332, \"sharpe\": 0.11761862043465551, \"sterling\": -1.4398625216420675, \"ulcer\": -0.18558760203585056}], \"train_return\": -0.33730516525864995, \"train_returns_over_hodl\": -0.2377241532094332, \"train_sharpe\": 0.11238851396355731, \"validation_return\": -0.3391427223069068, \"validation_returns_over_hodl\": -6.329248236625062e-11, \"validation_sharpe\": -2.2438532050158693}, {\"centeredness_margin\": 0.34760699832417463, \"continuous_test_metrics\": [{\"annualised_returns\": -0.48434945770168514, \"annualised_returns_over_hodl\": 0.0003045535793035903, \"annualised_returns_over_uniform_hodl\": 0.8200178751488649, \"calmar\": -0.8355341403106074, \"daily_log_sharpe\": -0.6921351351034037, \"daily_returns\": 0.031016042752106582, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": 0.005244066479223181, \"return\": -0.2341306814626155, \"returns_over_hodl\": 0.00012264409206563798, \"returns_over_uniform_hodl\": 0.2727476463295184, \"sharpe\": -0.20175426755404133, \"sterling\": -1.2469801183629337, \"ulcer\": -0.1764103165151497}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -8.34478042222031e-11, \"optuna_trial_number\": 241, \"price_ratio\": 3.581188205569742, \"shift_exponent\": 9.808982942844375e-05, \"step\": 241, \"test_objective\": [{\"annualised_returns\": -0.48434945770168514, \"annualised_returns_over_hodl\": 0.0003045535793035903, \"annualised_returns_over_uniform_hodl\": 0.8200178751488649, \"calmar\": -0.8355341403106074, \"daily_log_sharpe\": -0.6921351351034037, \"daily_returns\": 0.031016042752106582, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": 0.005244066479223181, \"return\": -0.2341306814626155, \"returns_over_hodl\": 0.00012264409206563798, \"returns_over_uniform_hodl\": 0.2727476463295184, \"sharpe\": -0.20175426755404133, \"sterling\": -1.2469801183629337, \"ulcer\": -0.1764103165151497}], \"train_objective\": [{\"annualised_returns\": -0.5397983040421106, \"annualised_returns_over_hodl\": -0.42045043607343624, \"annualised_returns_over_uniform_hodl\": -0.42045043607343624, \"calmar\": -0.7099549747699547, \"daily_log_sharpe\": -0.5381201970138116, \"daily_returns\": 0.008220513429788258, \"fee_revenue_over_value\": 0.001917758660979769, \"jax_sharpe\": 0.15595926645688682, \"return\": -0.37573547888076986, \"returns_over_hodl\": -0.2819292659143814, \"returns_over_uniform_hodl\": -0.2819292659143814, \"sharpe\": 0.09364573460570122, \"sterling\": -1.5194741094586435, \"ulcer\": -0.19434998432560419}], \"train_return\": -0.37573547888076986, \"train_returns_over_hodl\": -0.2819292659143814, \"train_sharpe\": 0.1559592664568868, \"validation_return\": -0.339142722301505, \"validation_returns_over_hodl\": -8.34478042222031e-11, \"validation_sharpe\": -2.243853205079961}, {\"centeredness_margin\": 0.4998693857193871, \"continuous_test_metrics\": [{\"annualised_returns\": -0.4845064531151454, \"annualised_returns_over_hodl\": 9.312550730555813e-13, \"annualised_returns_over_uniform_hodl\": 0.8194637509205824, \"calmar\": -0.8358049680218446, \"daily_log_sharpe\": -0.6924635968828482, \"daily_returns\": 0.031016042751672065, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": 0.0050192840418418535, \"return\": -0.23422459929653427, \"returns_over_hodl\": 3.750333377183779e-13, \"returns_over_uniform_hodl\": 0.2725915704831887, \"sharpe\": -0.20210852966113593, \"sterling\": -1.2473843110373621, \"ulcer\": -0.17641031651425526}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -8.803691109449119e-11, \"optuna_trial_number\": 242, \"price_ratio\": 3.3439426534681385, \"shift_exponent\": 5.350532646215363e-05, \"step\": 242, \"test_objective\": [{\"annualised_returns\": -0.4845064531151454, \"annualised_returns_over_hodl\": 9.312550730555813e-13, \"annualised_returns_over_uniform_hodl\": 0.8194637509205824, \"calmar\": -0.8358049680218446, \"daily_log_sharpe\": -0.6924635968828482, \"daily_returns\": 0.031016042751672065, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": 0.0050192840418418535, \"return\": -0.23422459929653427, \"returns_over_hodl\": 3.750333377183779e-13, \"returns_over_uniform_hodl\": 0.2725915704831887, \"sharpe\": -0.20210852966113593, \"sterling\": -1.2473843110373621, \"ulcer\": -0.17641031651425526}], \"train_objective\": [{\"annualised_returns\": -0.5514289871402707, \"annualised_returns_over_hodl\": -0.4350973993004543, \"annualised_returns_over_uniform_hodl\": -0.43509739930045455, \"calmar\": -0.722040204559255, \"daily_log_sharpe\": -0.5528658332791577, \"daily_returns\": 0.008217728709517667, \"fee_revenue_over_value\": 0.000899390323963866, \"jax_sharpe\": 0.11638186245727201, \"return\": -0.3853621807608165, \"returns_over_hodl\": -0.2930025411878743, \"returns_over_uniform_hodl\": -0.2930025411878744, \"sharpe\": 0.08588939361851468, \"sterling\": -1.5372272140261185, \"ulcer\": -0.19679980837037933}], \"train_return\": -0.3853621807608165, \"train_returns_over_hodl\": -0.2930025411878743, \"train_sharpe\": 0.11638186245727201, \"validation_return\": -0.3391427222997756, \"validation_returns_over_hodl\": -8.803691109449119e-11, \"validation_sharpe\": -2.2438532050926607}, {\"centeredness_margin\": 0.024399861286088453, \"continuous_test_metrics\": [{\"annualised_returns\": -0.6555925423442189, \"annualised_returns_over_hodl\": -0.05746992103322601, \"annualised_returns_over_uniform_hodl\": 0.21560568224024856, \"calmar\": -1.1192376360445062, \"daily_log_sharpe\": -1.2783355252385078, \"daily_returns\": 0.023234551538487896, \"fee_revenue_over_value\": 0.010342493820676308, \"jax_sharpe\": -0.674906164086616, \"return\": -0.3490290547876843, \"returns_over_hodl\": -0.02355516538975344, \"returns_over_uniform_hodl\": 0.0818056270097649, \"sharpe\": -0.86495921657124, \"sterling\": -1.8652276715293294, \"ulcer\": -0.15669438492819307}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -0.03811181453509305, \"optuna_trial_number\": 243, \"price_ratio\": 95.89217749883699, \"shift_exponent\": 0.00024119669075799765, \"step\": 243, \"test_objective\": [{\"annualised_returns\": -0.6555925423442189, \"annualised_returns_over_hodl\": -0.05746992103322601, \"annualised_returns_over_uniform_hodl\": 0.21560568224024856, \"calmar\": -1.1192376360445062, \"daily_log_sharpe\": -1.2783355252385078, \"daily_returns\": 0.023234551538487896, \"fee_revenue_over_value\": 0.010342493820676308, \"jax_sharpe\": -0.674906164086616, \"return\": -0.3490290547876843, \"returns_over_hodl\": -0.02355516538975344, \"returns_over_uniform_hodl\": 0.0818056270097649, \"sharpe\": -0.86495921657124, \"sterling\": -1.8652276715293294, \"ulcer\": -0.15669438492819307}], \"train_objective\": [{\"annualised_returns\": -0.345070063107882, \"annualised_returns_over_hodl\": -0.17522172851138118, \"annualised_returns_over_uniform_hodl\": -0.17522172851138118, \"calmar\": -0.47005334313570746, \"daily_log_sharpe\": -0.3455353394129563, \"daily_returns\": 0.007960983221548718, \"fee_revenue_over_value\": 0.005914090117215896, \"jax_sharpe\": 0.13511391513728715, \"return\": -0.22659324983927354, \"returns_over_hodl\": -0.11037591590341966, \"returns_over_uniform_hodl\": -0.11037591590341966, \"sharpe\": 0.16484452892873605, \"sterling\": -1.1127921192370895, \"ulcer\": -0.16734442723197138}], \"train_return\": -0.22659324983927354, \"train_returns_over_hodl\": -0.11037591590341966, \"train_sharpe\": 0.13511391513728715, \"validation_return\": -0.21552615387163165, \"validation_returns_over_hodl\": -0.03811181453509305, \"validation_sharpe\": -1.6333142635102225}, {\"centeredness_margin\": 0.9817455995226733, \"continuous_test_metrics\": [{\"annualised_returns\": -0.4884367137242108, \"annualised_returns_over_hodl\": -0.0076242674489243045, \"annualised_returns_over_uniform_hodl\": 0.8055916728837595, \"calmar\": -0.8425849336749729, \"daily_log_sharpe\": -0.707957723148051, \"daily_returns\": 0.031016042754594388, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": -0.027296640694412823, \"return\": -0.23658135011581416, \"returns_over_hodl\": -0.0030776006300594627, \"returns_over_uniform_hodl\": 0.26867504192456737, \"sharpe\": -0.22135408698356906, \"sterling\": -1.257502966775222, \"ulcer\": -0.17641031652029876}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -0.018667000074870765, \"optuna_trial_number\": 244, \"price_ratio\": 5.694002691555482, \"shift_exponent\": 0.0004214881926397572, \"step\": 244, \"test_objective\": [{\"annualised_returns\": -0.4884367137242108, \"annualised_returns_over_hodl\": -0.0076242674489243045, \"annualised_returns_over_uniform_hodl\": 0.8055916728837595, \"calmar\": -0.8425849336749729, \"daily_log_sharpe\": -0.707957723148051, \"daily_returns\": 0.031016042754594388, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": -0.027296640694412823, \"return\": -0.23658135011581416, \"returns_over_hodl\": -0.0030776006300594627, \"returns_over_uniform_hodl\": 0.26867504192456737, \"sharpe\": -0.22135408698356906, \"sterling\": -1.257502966775222, \"ulcer\": -0.17641031652029876}], \"train_objective\": [{\"annualised_returns\": -0.46801238605838846, \"annualised_returns_over_hodl\": -0.33004768912801596, \"annualised_returns_over_uniform_hodl\": -0.33004768912801596, \"calmar\": -0.6242728781507277, \"daily_log_sharpe\": -0.4595281323191285, \"daily_returns\": 0.00808853012071214, \"fee_revenue_over_value\": 1.2652293739409673e-05, \"jax_sharpe\": 0.16300477232662333, \"return\": -0.31830681591588994, \"returns_over_hodl\": -0.21587098328314602, \"returns_over_uniform_hodl\": -0.21587098328314602, \"sharpe\": 0.12373599198650131, \"sterling\": -1.392663260177449, \"ulcer\": -0.1820751934255902}], \"train_return\": -0.31830681591588994, \"train_returns_over_hodl\": -0.21587098328314602, \"train_sharpe\": 0.16300477232662333, \"validation_return\": -0.33857023221196314, \"validation_returns_over_hodl\": -0.018667000074870765, \"validation_sharpe\": -2.23863945274476}, {\"centeredness_margin\": 0.8763544268718417, \"continuous_test_metrics\": [{\"annualised_returns\": -0.49203497269340246, \"annualised_returns_over_hodl\": -0.01460448863569963, \"annualised_returns_over_uniform_hodl\": 0.7928914134907328, \"calmar\": -0.8487921742308545, \"daily_log_sharpe\": -0.7157525897015193, \"daily_returns\": 0.031016042755022455, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": -0.033833698270838486, \"return\": -0.23874851994963242, \"returns_over_hodl\": -0.00590763387095905, \"returns_over_uniform_hodl\": 0.26507356548671224, \"sharpe\": -0.22974131332590547, \"sterling\": -1.266766867772123, \"ulcer\": -0.17641031652117128}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -0.038757760131385655, \"optuna_trial_number\": 245, \"price_ratio\": 7.27012580143908, \"shift_exponent\": 0.0001232758799496467, \"step\": 245, \"test_objective\": [{\"annualised_returns\": -0.49203497269340246, \"annualised_returns_over_hodl\": -0.01460448863569963, \"annualised_returns_over_uniform_hodl\": 0.7928914134907328, \"calmar\": -0.8487921742308545, \"daily_log_sharpe\": -0.7157525897015193, \"daily_returns\": 0.031016042755022455, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": -0.033833698270838486, \"return\": -0.23874851994963242, \"returns_over_hodl\": -0.00590763387095905, \"returns_over_uniform_hodl\": 0.26507356548671224, \"sharpe\": -0.22974131332590547, \"sterling\": -1.266766867772123, \"ulcer\": -0.17641031652117128}], \"train_objective\": [{\"annualised_returns\": -0.4565362395130149, \"annualised_returns_over_hodl\": -0.315595339681357, \"annualised_returns_over_uniform_hodl\": -0.315595339681357, \"calmar\": -0.6108615841540811, \"daily_log_sharpe\": -0.45250807480616, \"daily_returns\": 0.008088264117436707, \"fee_revenue_over_value\": 4.586967276345353e-05, \"jax_sharpe\": 0.10343760246545733, \"return\": -0.30941617575826996, \"returns_over_hodl\": -0.20564437534933722, \"returns_over_uniform_hodl\": -0.20564437534933722, \"sharpe\": 0.11905960153748399, \"sterling\": -1.3734459107678758, \"ulcer\": -0.1800500909412871}], \"train_return\": -0.30941617575826996, \"train_returns_over_hodl\": -0.20564437534933722, \"train_sharpe\": 0.10343760246545733, \"validation_return\": -0.3360481858262009, \"validation_returns_over_hodl\": -0.038757760131385655, \"validation_sharpe\": -2.2163799140701865}, {\"centeredness_margin\": 0.8121404445871665, \"continuous_test_metrics\": [{\"annualised_returns\": -0.5726439974824776, \"annualised_returns_over_hodl\": -0.17097700825238726, \"annualised_returns_over_uniform_hodl\": 0.5083772823496466, \"calmar\": -0.9908797533633615, \"daily_log_sharpe\": -0.9165528154668707, \"daily_returns\": 0.031016042754038662, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": -0.23995675811734263, \"return\": -0.28992357993905404, \"returns_over_hodl\": -0.07273540077998364, \"returns_over_uniform_hodl\": 0.18002911263319454, \"sharpe\": -0.4474700633944758, \"sterling\": -1.4886796681908836, \"ulcer\": -0.17307894680909167}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -0.06805885501028677, \"optuna_trial_number\": 246, \"price_ratio\": 5.87414311380116, \"shift_exponent\": 0.0014080551372324317, \"step\": 246, \"test_objective\": [{\"annualised_returns\": -0.5726439974824776, \"annualised_returns_over_hodl\": -0.17097700825238726, \"annualised_returns_over_uniform_hodl\": 0.5083772823496466, \"calmar\": -0.9908797533633615, \"daily_log_sharpe\": -0.9165528154668707, \"daily_returns\": 0.031016042754038662, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": -0.23995675811734263, \"return\": -0.28992357993905404, \"returns_over_hodl\": -0.07273540077998364, \"returns_over_uniform_hodl\": 0.18002911263319454, \"sharpe\": -0.4474700633944758, \"sterling\": -1.4886796681908836, \"ulcer\": -0.17307894680909167}], \"train_objective\": [{\"annualised_returns\": -0.4259998791833006, \"annualised_returns_over_hodl\": -0.27713973539212444, \"annualised_returns_over_uniform_hodl\": -0.27713973539212444, \"calmar\": -0.569786015766205, \"daily_log_sharpe\": -0.4189378579697778, \"daily_returns\": 0.008102008037855527, \"fee_revenue_over_value\": 6.510049130720493e-05, \"jax_sharpe\": 0.13043963336730713, \"return\": -0.2861116254213534, \"returns_over_hodl\": -0.17883792551624034, \"returns_over_uniform_hodl\": -0.17883792551624034, \"sharpe\": 0.1434667671655882, \"sterling\": -1.2968882118948462, \"ulcer\": -0.1772362692379679}], \"train_return\": -0.2861116254213534, \"train_returns_over_hodl\": -0.17883792551624034, \"train_sharpe\": 0.13043963336730713, \"validation_return\": -0.3138303370940433, \"validation_returns_over_hodl\": -0.06805885501028675, \"validation_sharpe\": -2.081756738268797}, {\"centeredness_margin\": 0.7584961963342122, \"continuous_test_metrics\": [{\"annualised_returns\": -0.6951553377125604, \"annualised_returns_over_hodl\": -0.2406467854523382, \"annualised_returns_over_uniform_hodl\": 0.0759665490391015, \"calmar\": -1.1758557209383098, \"daily_log_sharpe\": -1.4043175601033677, \"daily_returns\": 0.02528055661702891, \"fee_revenue_over_value\": 0.00018635051584532943, \"jax_sharpe\": -0.775852187794346, \"return\": -0.3802465738656018, \"returns_over_hodl\": -0.10494399496852358, \"returns_over_uniform_hodl\": 0.02992729350171386, \"sharpe\": -0.9907355848814375, \"sterling\": -1.9331461700496917, \"ulcer\": -0.15723868117809614}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -0.04999684984802977, \"optuna_trial_number\": 247, \"price_ratio\": 9.13248381042343, \"shift_exponent\": 0.002325942718664205, \"step\": 247, \"test_objective\": [{\"annualised_returns\": -0.6951553377125604, \"annualised_returns_over_hodl\": -0.2406467854523382, \"annualised_returns_over_uniform_hodl\": 0.0759665490391015, \"calmar\": -1.1758557209383098, \"daily_log_sharpe\": -1.4043175601033677, \"daily_returns\": 0.02528055661702891, \"fee_revenue_over_value\": 0.00018635051584532943, \"jax_sharpe\": -0.775852187794346, \"return\": -0.3802465738656018, \"returns_over_hodl\": -0.10494399496852358, \"returns_over_uniform_hodl\": 0.02992729350171386, \"sharpe\": -0.9907355848814375, \"sterling\": -1.9331461700496917, \"ulcer\": -0.15723868117809614}], \"train_objective\": [{\"annualised_returns\": -0.3688001727665917, \"annualised_returns_over_hodl\": -0.2051059615018943, \"annualised_returns_over_uniform_hodl\": -0.2051059615018943, \"calmar\": -0.49676605057194345, \"daily_log_sharpe\": -0.36689288770385536, \"daily_returns\": 0.008032471576010483, \"fee_revenue_over_value\": 0.0002974958775849142, \"jax_sharpe\": 0.14343391954050533, \"return\": -0.24372971937072552, \"returns_over_hodl\": -0.13008742735376433, \"returns_over_uniform_hodl\": -0.13008742735376433, \"sharpe\": 0.16320183214309827, \"sterling\": -1.1637340884034684, \"ulcer\": -0.17051236594690677}], \"train_return\": -0.24372971937072552, \"train_returns_over_hodl\": -0.13008742735376433, \"train_sharpe\": 0.1434339195405053, \"validation_return\": -0.2332094278944148, \"validation_returns_over_hodl\": -0.04999684984802977, \"validation_sharpe\": -1.7369661838735724}, {\"centeredness_margin\": 0.8386234562997144, \"continuous_test_metrics\": [{\"annualised_returns\": -0.4953632197641903, \"annualised_returns_over_hodl\": -0.02106091681870903, \"annualised_returns_over_uniform_hodl\": 0.7811441764283087, \"calmar\": -0.8545336261476912, \"daily_log_sharpe\": -0.7241083601286402, \"daily_returns\": 0.031016042754726265, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": -0.04259266076155498, \"return\": -0.24076124394730536, \"returns_over_hodl\": -0.008535981545794935, \"returns_over_uniform_hodl\": 0.26172875238512283, \"sharpe\": -0.23907139929401644, \"sterling\": -1.2753356096705841, \"ulcer\": -0.17641031652057196}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -0.023609863606800507, \"optuna_trial_number\": 248, \"price_ratio\": 5.329949720731044, \"shift_exponent\": 0.0006444764759640167, \"step\": 248, \"test_objective\": [{\"annualised_returns\": -0.4953632197641903, \"annualised_returns_over_hodl\": -0.02106091681870903, \"annualised_returns_over_uniform_hodl\": 0.7811441764283087, \"calmar\": -0.8545336261476912, \"daily_log_sharpe\": -0.7241083601286402, \"daily_returns\": 0.031016042754726265, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": -0.04259266076155498, \"return\": -0.24076124394730536, \"returns_over_hodl\": -0.008535981545794935, \"returns_over_uniform_hodl\": 0.26172875238512283, \"sharpe\": -0.23907139929401644, \"sterling\": -1.2753356096705841, \"ulcer\": -0.17641031652057196}], \"train_objective\": [{\"annualised_returns\": -0.4626782419067227, \"annualised_returns_over_hodl\": -0.3233301977667885, \"annualised_returns_over_uniform_hodl\": -0.3233301977667885, \"calmar\": -0.6173435147085797, \"daily_log_sharpe\": -0.45083829962826694, \"daily_returns\": 0.008112630777128808, \"fee_revenue_over_value\": 5.15169753405147e-05, \"jax_sharpe\": 0.12181488921548728, \"return\": -0.314165146613272, \"returns_over_hodl\": -0.21110695871365948, \"returns_over_uniform_hodl\": -0.21110695871365948, \"sharpe\": 0.13353205248382294, \"sterling\": -1.3769287908367587, \"ulcer\": -0.18191965938978594}], \"train_return\": -0.314165146613272, \"train_returns_over_hodl\": -0.21110695871365948, \"train_sharpe\": 0.12181488921548726, \"validation_return\": -0.33819759716614084, \"validation_returns_over_hodl\": -0.023609863606800507, \"validation_sharpe\": -2.235218078344551}, {\"centeredness_margin\": 0.8498455301340831, \"continuous_test_metrics\": [{\"annualised_returns\": -0.47762503266995204, \"annualised_returns_over_hodl\": 0.013349188250267208, \"annualised_returns_over_uniform_hodl\": 0.8437521152086223, \"calmar\": -0.8239340524457488, \"daily_log_sharpe\": -0.6797397628125501, \"daily_returns\": 0.031016042753854, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": 0.009560556238233901, \"return\": -0.23012391655108377, \"returns_over_hodl\": 0.005354941820228776, \"returns_over_uniform_hodl\": 0.27940622434944284, \"sharpe\": -0.18893903544885213, \"sterling\": -1.2296677450320426, \"ulcer\": -0.1764103165187621}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -6.317302236880096e-11, \"optuna_trial_number\": 249, \"price_ratio\": 5.269923861665702, \"shift_exponent\": 3.069256553461651e-05, \"step\": 249, \"test_objective\": [{\"annualised_returns\": -0.47762503266995204, \"annualised_returns_over_hodl\": 0.013349188250267208, \"annualised_returns_over_uniform_hodl\": 0.8437521152086223, \"calmar\": -0.8239340524457488, \"daily_log_sharpe\": -0.6797397628125501, \"daily_returns\": 0.031016042753854, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": 0.009560556238233901, \"return\": -0.23012391655108377, \"returns_over_hodl\": 0.005354941820228776, \"returns_over_uniform_hodl\": 0.27940622434944284, \"sharpe\": -0.18893903544885213, \"sterling\": -1.2296677450320426, \"ulcer\": -0.1764103165187621}], \"train_objective\": [{\"annualised_returns\": -0.49109795393635014, \"annualised_returns_over_hodl\": -0.3591202260486407, \"annualised_returns_over_uniform_hodl\": -0.3591202260486407, \"calmar\": -0.6540296239130148, \"daily_log_sharpe\": -0.4827251554062518, \"daily_returns\": 0.008099155086919538, \"fee_revenue_over_value\": 4.835746227786173e-05, \"jax_sharpe\": 0.1080977455622225, \"return\": -0.33642296540445715, \"returns_over_hodl\": -0.23670938803300645, \"returns_over_uniform_hodl\": -0.23670938803300645, \"sharpe\": 0.11435213070076101, \"sterling\": -1.4399645661391023, \"ulcer\": -0.18520769932772255}], \"train_return\": -0.33642296540445715, \"train_returns_over_hodl\": -0.23670938803300645, \"train_sharpe\": 0.1080977455622225, \"validation_return\": -0.33914272230737597, \"validation_returns_over_hodl\": -6.317302236880096e-11, \"validation_sharpe\": -2.24385320501727}, {\"centeredness_margin\": 0.7660623760016714, \"continuous_test_metrics\": [{\"annualised_returns\": -0.4844364070176065, \"annualised_returns_over_hodl\": 0.00013588160967414886, \"annualised_returns_over_uniform_hodl\": 0.8197109826000784, \"calmar\": -0.8356841337640472, \"daily_log_sharpe\": -0.6923170363387183, \"daily_returns\": 0.03101604275191459, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": 0.005114742605550218, \"return\": -0.23418269419287363, \"returns_over_hodl\": 5.4722443243004903e-05, \"returns_over_uniform_hodl\": 0.27266120980776076, \"sharpe\": -0.2019504595615197, \"sterling\": -1.247203973796878, \"ulcer\": -0.17641031651475297}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -8.510891991164726e-11, \"optuna_trial_number\": 250, \"price_ratio\": 3.490600748417983, \"shift_exponent\": 9.282557510276094e-05, \"step\": 250, \"test_objective\": [{\"annualised_returns\": -0.4844364070176065, \"annualised_returns_over_hodl\": 0.00013588160967414886, \"annualised_returns_over_uniform_hodl\": 0.8197109826000784, \"calmar\": -0.8356841337640472, \"daily_log_sharpe\": -0.6923170363387183, \"daily_returns\": 0.03101604275191459, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": 0.005114742605550218, \"return\": -0.23418269419287363, \"returns_over_hodl\": 5.4722443243004903e-05, \"returns_over_uniform_hodl\": 0.27266120980776076, \"sharpe\": -0.2019504595615197, \"sterling\": -1.247203973796878, \"ulcer\": -0.17641031651475297}], \"train_objective\": [{\"annualised_returns\": -0.5458007389184161, \"annualised_returns_over_hodl\": -0.4280095314562111, \"annualised_returns_over_uniform_hodl\": -0.4280095314562111, \"calmar\": -0.7162109596419426, \"daily_log_sharpe\": -0.5467274839836878, \"daily_returns\": 0.008193970218701352, \"fee_revenue_over_value\": 5.5036802709487326e-05, \"jax_sharpe\": 0.1139215069171729, \"return\": -0.3806915954501213, \"returns_over_hodl\": -0.28763012211038563, \"returns_over_uniform_hodl\": -0.28763012211038563, \"sharpe\": 0.08745125440872285, \"sterling\": -1.5301760463074436, \"ulcer\": -0.1953624805663125}], \"train_return\": -0.3806915954501213, \"train_returns_over_hodl\": -0.28763012211038563, \"train_sharpe\": 0.11392150691717291, \"validation_return\": -0.3391427223004887, \"validation_returns_over_hodl\": -8.510891991164726e-11, \"validation_sharpe\": -2.243853205083103}, {\"centeredness_margin\": 0.6391094005810982, \"continuous_test_metrics\": [{\"annualised_returns\": -0.4843021320334764, \"annualised_returns_over_hodl\": 0.0003963600941423451, \"annualised_returns_over_uniform_hodl\": 0.820184913782644, \"calmar\": -0.8354525003306461, \"daily_log_sharpe\": -0.6920361404613, \"daily_returns\": 0.031016042752212102, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": 0.005303523947162856, \"return\": -0.23410237364014086, \"returns_over_hodl\": 0.00015961031101796586, \"returns_over_uniform_hodl\": 0.27279468923038297, \"sharpe\": -0.20164749283556546, \"sterling\": -1.2468582760243458, \"ulcer\": -0.17641031651537312}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -8.231948456227656e-11, \"optuna_trial_number\": 251, \"price_ratio\": 3.6781756140534148, \"shift_exponent\": 7.575923687052778e-05, \"step\": 251, \"test_objective\": [{\"annualised_returns\": -0.4843021320334764, \"annualised_returns_over_hodl\": 0.0003963600941423451, \"annualised_returns_over_uniform_hodl\": 0.820184913782644, \"calmar\": -0.8354525003306461, \"daily_log_sharpe\": -0.6920361404613, \"daily_returns\": 0.031016042752212102, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": 0.005303523947162856, \"return\": -0.23410237364014086, \"returns_over_hodl\": 0.00015961031101796586, \"returns_over_uniform_hodl\": 0.27279468923038297, \"sharpe\": -0.20164749283556546, \"sterling\": -1.2468582760243458, \"ulcer\": -0.17641031651537312}], \"train_objective\": [{\"annualised_returns\": -0.5379462002563233, \"annualised_returns_over_hodl\": -0.41811801107190405, \"annualised_returns_over_uniform_hodl\": -0.41811801107190405, \"calmar\": -0.7079449934043981, \"daily_log_sharpe\": -0.5367301683591464, \"daily_returns\": 0.008170896154715699, \"fee_revenue_over_value\": 0.00020990483559876606, \"jax_sharpe\": 0.1547335375258605, \"return\": -0.3742113613830531, \"returns_over_hodl\": -0.28017612420378735, \"returns_over_uniform_hodl\": -0.28017612420378735, \"sharpe\": 0.09279282220231834, \"sterling\": -1.5180651144305055, \"ulcer\": -0.19379878192792493}], \"train_return\": -0.3742113613830531, \"train_returns_over_hodl\": -0.28017612420378735, \"train_sharpe\": 0.1547335375258605, \"validation_return\": -0.3391427223019359, \"validation_returns_over_hodl\": -8.231948456227656e-11, \"validation_sharpe\": -2.243853205076673}, {\"centeredness_margin\": 0.3046271087398741, \"continuous_test_metrics\": [{\"annualised_returns\": -0.5576248139886737, \"annualised_returns_over_hodl\": -0.14184146703899303, \"annualised_returns_over_uniform_hodl\": 0.5613883434978182, \"calmar\": -0.9638357153863271, \"daily_log_sharpe\": -0.8758059729086773, \"daily_returns\": 0.031180977444495165, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": -0.19609593491209554, \"return\": -0.2799767157781389, \"returns_over_hodl\": -0.05974612984783412, \"returns_over_uniform_hodl\": 0.19655914934146912, \"sharpe\": -0.4039224815788298, \"sterling\": -1.4492825154551874, \"ulcer\": -0.17317249398589873}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -0.06328838629372902, \"optuna_trial_number\": 252, \"price_ratio\": 13.838410514606615, \"shift_exponent\": 0.0001997860596022055, \"step\": 252, \"test_objective\": [{\"annualised_returns\": -0.5576248139886737, \"annualised_returns_over_hodl\": -0.14184146703899303, \"annualised_returns_over_uniform_hodl\": 0.5613883434978182, \"calmar\": -0.9638357153863271, \"daily_log_sharpe\": -0.8758059729086773, \"daily_returns\": 0.031180977444495165, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": -0.19609593491209554, \"return\": -0.2799767157781389, \"returns_over_hodl\": -0.05974612984783412, \"returns_over_uniform_hodl\": 0.19655914934146912, \"sharpe\": -0.4039224815788298, \"sterling\": -1.4492825154551874, \"ulcer\": -0.17317249398589873}], \"train_objective\": [{\"annualised_returns\": -0.40883563926269073, \"annualised_returns_over_hodl\": -0.2555241528149784, \"annualised_returns_over_uniform_hodl\": -0.2555241528149784, \"calmar\": -0.5513265002034412, \"daily_log_sharpe\": -0.40850020146952587, \"daily_returns\": 0.008026077832073653, \"fee_revenue_over_value\": 0.003219836888621542, \"jax_sharpe\": 0.11176515928641007, \"return\": -0.27322631679023, \"returns_over_hodl\": -0.1640163831817797, \"returns_over_uniform_hodl\": -0.1640163831817797, \"sharpe\": 0.13414388182994935, \"sterling\": -1.2679975625991697, \"ulcer\": -0.17415472156443115}], \"train_return\": -0.27322631679023, \"train_returns_over_hodl\": -0.1640163831817797, \"train_sharpe\": 0.11176515928641007, \"validation_return\": -0.2978852067904393, \"validation_returns_over_hodl\": -0.06328838629372902, \"validation_sharpe\": -2.0209081287661537}, {\"centeredness_margin\": 0.9308307401264188, \"continuous_test_metrics\": [{\"annualised_returns\": -0.48357566237962646, \"annualised_returns_over_hodl\": 0.0018056301930882146, \"annualised_returns_over_uniform_hodl\": 0.8227490296853779, \"calmar\": -0.8341992910233537, \"daily_log_sharpe\": -0.6905278851599845, \"daily_returns\": 0.03101604275278044, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": 0.0060762713728931194, \"return\": -0.23366803078563492, \"returns_over_hodl\": 0.0007268038339824923, \"returns_over_uniform_hodl\": 0.27351649493847474, \"sharpe\": -0.20002986824486, \"sterling\": -1.244987942994812, \"ulcer\": -0.1764103165165513}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -7.628686571337084e-11, \"optuna_trial_number\": 253, \"price_ratio\": 4.1801668588267935, \"shift_exponent\": 1.2484808816157707e-05, \"step\": 253, \"test_objective\": [{\"annualised_returns\": -0.48357566237962646, \"annualised_returns_over_hodl\": 0.0018056301930882146, \"annualised_returns_over_uniform_hodl\": 0.8227490296853779, \"calmar\": -0.8341992910233537, \"daily_log_sharpe\": -0.6905278851599845, \"daily_returns\": 0.03101604275278044, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": 0.0060762713728931194, \"return\": -0.23366803078563492, \"returns_over_hodl\": 0.0007268038339824923, \"returns_over_uniform_hodl\": 0.27351649493847474, \"sharpe\": -0.20002986824486, \"sterling\": -1.244987942994812, \"ulcer\": -0.1764103165165513}], \"train_objective\": [{\"annualised_returns\": -0.5226115982524212, \"annualised_returns_over_hodl\": -0.39880656136972337, \"annualised_returns_over_uniform_hodl\": -0.39880656136972337, \"calmar\": -0.6914700502423552, \"daily_log_sharpe\": -0.5187907936174316, \"daily_returns\": 0.008167858173360198, \"fee_revenue_over_value\": 3.58099312841906e-05, \"jax_sharpe\": 0.11099922529920773, \"return\": -0.36168324979562283, \"returns_over_hodl\": -0.26576545375888705, \"returns_over_uniform_hodl\": -0.26576545375888705, \"sharpe\": 0.10004886692832829, \"sterling\": -1.4962418622537088, \"ulcer\": -0.19052806188998905}], \"train_return\": -0.36168324979562283, \"train_returns_over_hodl\": -0.26576545375888705, \"train_sharpe\": 0.11099922529920773, \"validation_return\": -0.33914272230423104, \"validation_returns_over_hodl\": -7.628686571337084e-11, \"validation_sharpe\": -2.2438532050601045}, {\"centeredness_margin\": 0.87379377653938, \"continuous_test_metrics\": [{\"annualised_returns\": -0.4767741805481496, \"annualised_returns_over_hodl\": 0.014999746480339526, \"annualised_returns_over_uniform_hodl\": 0.8467552460962324, \"calmar\": -0.8224662750541636, \"daily_log_sharpe\": -0.6794645635561954, \"daily_returns\": 0.031016042754105203, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": 0.00611803779570357, \"return\": -0.22961913455042893, \"returns_over_hodl\": 0.006014119428951137, \"returns_over_uniform_hodl\": 0.28024508822307337, \"sharpe\": -0.18915234530061667, \"sterling\": -1.2274771831611149, \"ulcer\": -0.1764103165192797}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -6.911926586639083e-11, \"optuna_trial_number\": 254, \"price_ratio\": 5.793144828798534, \"shift_exponent\": 3.978168778091728e-05, \"step\": 254, \"test_objective\": [{\"annualised_returns\": -0.4767741805481496, \"annualised_returns_over_hodl\": 0.014999746480339526, \"annualised_returns_over_uniform_hodl\": 0.8467552460962324, \"calmar\": -0.8224662750541636, \"daily_log_sharpe\": -0.6794645635561954, \"daily_returns\": 0.031016042754105203, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": 0.00611803779570357, \"return\": -0.22961913455042893, \"returns_over_hodl\": 0.006014119428951137, \"returns_over_uniform_hodl\": 0.28024508822307337, \"sharpe\": -0.18915234530061667, \"sterling\": -1.2274771831611149, \"ulcer\": -0.1764103165192797}], \"train_objective\": [{\"annualised_returns\": -0.48260424197346685, \"annualised_returns_over_hodl\": -0.3484237703261973, \"annualised_returns_over_uniform_hodl\": -0.3484237703261973, \"calmar\": -0.6432712503068948, \"daily_log_sharpe\": -0.4754510105273788, \"daily_returns\": 0.008100431303169658, \"fee_revenue_over_value\": 4.488097872046911e-05, \"jax_sharpe\": 0.15746013492716865, \"return\": -0.3297208030664317, \"returns_over_hodl\": -0.2290001134110905, \"returns_over_uniform_hodl\": -0.2290001134110905, \"sharpe\": 0.11376847174695476, \"sterling\": -1.425939756559844, \"ulcer\": -0.18365058873795073}], \"train_return\": -0.3297208030664317, \"train_returns_over_hodl\": -0.2290001134110905, \"train_sharpe\": 0.15746013492716865, \"validation_return\": -0.33914272230729814, \"validation_returns_over_hodl\": -6.911926586639083e-11, \"validation_sharpe\": -2.243853204999124}, {\"centeredness_margin\": 0.9480235090095832, \"continuous_test_metrics\": [{\"annualised_returns\": -0.4761776191100666, \"annualised_returns_over_hodl\": 0.01615700915172913, \"annualised_returns_over_uniform_hodl\": 0.8488608435733453, \"calmar\": -0.8214371662773994, \"daily_log_sharpe\": -0.6777336688793679, \"daily_returns\": 0.0310160427540504, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": 0.008592722462604991, \"return\": -0.2292655069868026, \"returns_over_hodl\": 0.006475909614370723, \"returns_over_uniform_hodl\": 0.2808327585192827, \"sharpe\": -0.18712259794813335, \"sterling\": -1.2259413055452884, \"ulcer\": -0.17641031651916947}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -6.134392993573101e-11, \"optuna_trial_number\": 255, \"price_ratio\": 5.464915093482907, \"shift_exponent\": 7.550157463220652e-05, \"step\": 255, \"test_objective\": [{\"annualised_returns\": -0.4761776191100666, \"annualised_returns_over_hodl\": 0.01615700915172913, \"annualised_returns_over_uniform_hodl\": 0.8488608435733453, \"calmar\": -0.8214371662773994, \"daily_log_sharpe\": -0.6777336688793679, \"daily_returns\": 0.0310160427540504, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": 0.008592722462604991, \"return\": -0.2292655069868026, \"returns_over_hodl\": 0.006475909614370723, \"returns_over_uniform_hodl\": 0.2808327585192827, \"sharpe\": -0.18712259794813335, \"sterling\": -1.2259413055452884, \"ulcer\": -0.17641031651916947}], \"train_objective\": [{\"annualised_returns\": -0.48491705476186886, \"annualised_returns_over_hodl\": -0.35133638376229537, \"annualised_returns_over_uniform_hodl\": -0.3513363837622956, \"calmar\": -0.6463178279313059, \"daily_log_sharpe\": -0.47589837543931296, \"daily_returns\": 0.0081094277553921, \"fee_revenue_over_value\": 3.2825905932198126e-05, \"jax_sharpe\": 0.1084916752402033, \"return\": -0.3315414715266637, \"returns_over_hodl\": -0.23109436783935955, \"returns_over_uniform_hodl\": -0.23109436783935977, \"sharpe\": 0.11720015388662017, \"sterling\": -1.4278409064302926, \"ulcer\": -0.1843011104441581}], \"train_return\": -0.3315414715266637, \"train_returns_over_hodl\": -0.23109436783935955, \"train_sharpe\": 0.10849167524020331, \"validation_return\": -0.3391427223077135, \"validation_returns_over_hodl\": -6.134392993573101e-11, \"validation_sharpe\": -2.2438532050068414}, {\"centeredness_margin\": 0.8797105854563602, \"continuous_test_metrics\": [{\"annualised_returns\": -0.4797283697547845, \"annualised_returns_over_hodl\": 0.009268948851698156, \"annualised_returns_over_uniform_hodl\": 0.8363282675097601, \"calmar\": -0.8275624510629038, \"daily_log_sharpe\": -0.6829411431018593, \"daily_returns\": 0.0310160427536197, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": 0.008468116239155386, \"return\": -0.2313738669723161, \"returns_over_hodl\": 0.0037226741643328065, \"returns_over_uniform_hodl\": 0.2773290142848197, \"sharpe\": -0.19217914126935212, \"sterling\": -1.2350828929267061, \"ulcer\": -0.17641031651827918}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -6.72220057396089e-11, \"optuna_trial_number\": 256, \"price_ratio\": 4.979246985931547, \"shift_exponent\": 3.213132512324567e-05, \"step\": 256, \"test_objective\": [{\"annualised_returns\": -0.4797283697547845, \"annualised_returns_over_hodl\": 0.009268948851698156, \"annualised_returns_over_uniform_hodl\": 0.8363282675097601, \"calmar\": -0.8275624510629038, \"daily_log_sharpe\": -0.6829411431018593, \"daily_returns\": 0.0310160427536197, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": 0.008468116239155386, \"return\": -0.2313738669723161, \"returns_over_hodl\": 0.0037226741643328065, \"returns_over_uniform_hodl\": 0.2773290142848197, \"sharpe\": -0.19217914126935212, \"sterling\": -1.2350828929267061, \"ulcer\": -0.17641031651827918}], \"train_objective\": [{\"annualised_returns\": -0.49817635100863256, \"annualised_returns_over_hodl\": -0.36803432169968253, \"annualised_returns_over_uniform_hodl\": -0.36803432169968253, \"calmar\": -0.662488364655694, \"daily_log_sharpe\": -0.49044452240623565, \"daily_returns\": 0.008127225685486813, \"fee_revenue_over_value\": 4.4407375467381155e-05, \"jax_sharpe\": 0.10901787722808437, \"return\": -0.3420419820073015, \"returns_over_hodl\": -0.24317275610910605, \"returns_over_uniform_hodl\": -0.24317275610910605, \"sharpe\": 0.11185824180328179, \"sterling\": -1.4527739799208976, \"ulcer\": -0.18635088416634935}], \"train_return\": -0.3420419820073015, \"train_returns_over_hodl\": -0.24317275610910605, \"train_sharpe\": 0.10901787722808437, \"validation_return\": -0.339142722306925, \"validation_returns_over_hodl\": -6.72220057396089e-11, \"validation_sharpe\": -2.2438532050287883}, {\"centeredness_margin\": 0.8153753363576228, \"continuous_test_metrics\": [{\"annualised_returns\": -0.4770135239259381, \"annualised_returns_over_hodl\": 0.014535447022820724, \"annualised_returns_over_uniform_hodl\": 0.845910470050945, \"calmar\": -0.822879158548907, \"daily_log_sharpe\": -0.678761646568692, \"daily_returns\": 0.031016042753935068, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": 0.010436674962474824, \"return\": -0.22976107935206924, \"returns_over_hodl\": 0.005828758552415048, \"returns_over_uniform_hodl\": 0.2800091995305465, \"sharpe\": -0.18797212521707368, \"sterling\": -1.2280933848063733, \"ulcer\": -0.17641031651893224}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -6.189404544443278e-11, \"optuna_trial_number\": 257, \"price_ratio\": 5.398050116423862, \"shift_exponent\": 3.2437823583409425e-05, \"step\": 257, \"test_objective\": [{\"annualised_returns\": -0.4770135239259381, \"annualised_returns_over_hodl\": 0.014535447022820724, \"annualised_returns_over_uniform_hodl\": 0.845910470050945, \"calmar\": -0.822879158548907, \"daily_log_sharpe\": -0.678761646568692, \"daily_returns\": 0.031016042753935068, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": 0.010436674962474824, \"return\": -0.22976107935206924, \"returns_over_hodl\": 0.005828758552415048, \"returns_over_uniform_hodl\": 0.2800091995305465, \"sharpe\": -0.18797212521707368, \"sterling\": -1.2280933848063733, \"ulcer\": -0.17641031651893224}], \"train_objective\": [{\"annualised_returns\": -0.48806144010287145, \"annualised_returns_over_hodl\": -0.3552962282591787, \"annualised_returns_over_uniform_hodl\": -0.3552962282591787, \"calmar\": -0.650331245008654, \"daily_log_sharpe\": -0.4794125056710316, \"daily_returns\": 0.008132419544616281, \"fee_revenue_over_value\": 5.353734648837997e-05, \"jax_sharpe\": 0.16771816317068905, \"return\": -0.33402192199971537, \"returns_over_hodl\": -0.2339475475921573, \"returns_over_uniform_hodl\": -0.2339475475921573, \"sharpe\": 0.11561693649808821, \"sterling\": -1.4341136539998867, \"ulcer\": -0.1847511824742855}], \"train_return\": -0.33402192199971537, \"train_returns_over_hodl\": -0.2339475475921573, \"train_sharpe\": 0.16771816317068902, \"validation_return\": -0.3391427223074467, \"validation_returns_over_hodl\": -6.189404544443278e-11, \"validation_sharpe\": -2.243853205011983}, {\"centeredness_margin\": 0.8091776589677293, \"continuous_test_metrics\": [{\"annualised_returns\": -0.48046002003058297, \"annualised_returns_over_hodl\": 0.00784962891065355, \"annualised_returns_over_uniform_hodl\": 0.8337458663076294, \"calmar\": -0.8288245972501606, \"daily_log_sharpe\": -0.6843631539309349, \"daily_returns\": 0.031016042753376733, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": 0.008648998940214958, \"return\": -0.23180937227303733, \"returns_over_hodl\": 0.0031539626371275276, \"returns_over_uniform_hodl\": 0.27660527678412516, \"sharpe\": -0.1936361547811154, \"sterling\": -1.2369665636591287, \"ulcer\": -0.1764103165177796}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -6.907063809791225e-11, \"optuna_trial_number\": 258, \"price_ratio\": 4.649036242056285, \"shift_exponent\": 8.190885741129762e-05, \"step\": 258, \"test_objective\": [{\"annualised_returns\": -0.48046002003058297, \"annualised_returns_over_hodl\": 0.00784962891065355, \"annualised_returns_over_uniform_hodl\": 0.8337458663076294, \"calmar\": -0.8288245972501606, \"daily_log_sharpe\": -0.6843631539309349, \"daily_returns\": 0.031016042753376733, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": 0.008648998940214958, \"return\": -0.23180937227303733, \"returns_over_hodl\": 0.0031539626371275276, \"returns_over_uniform_hodl\": 0.27660527678412516, \"sharpe\": -0.1936361547811154, \"sterling\": -1.2369665636591287, \"ulcer\": -0.1764103165177796}], \"train_objective\": [{\"annualised_returns\": -0.5054873934496217, \"annualised_returns_over_hodl\": -0.3772413965447775, \"annualised_returns_over_uniform_hodl\": -0.3772413965447775, \"calmar\": -0.6711053948399585, \"daily_log_sharpe\": -0.49829886034301996, \"daily_returns\": 0.008156122962562967, \"fee_revenue_over_value\": 5.322664067557292e-05, \"jax_sharpe\": 0.11069196605881465, \"return\": -0.34787846857865534, \"returns_over_hodl\": -0.24988627266336905, \"returns_over_uniform_hodl\": -0.24988627266336905, \"sharpe\": 0.10964811313913718, \"sterling\": -1.465779698244655, \"ulcer\": -0.18754268136971144}], \"train_return\": -0.34787846857865534, \"train_returns_over_hodl\": -0.24988627266336905, \"train_sharpe\": 0.11069196605881465, \"validation_return\": -0.33914272230602305, \"validation_returns_over_hodl\": -6.907063809791225e-11, \"validation_sharpe\": -2.243853205036727}, {\"centeredness_margin\": 0.9384642357671041, \"continuous_test_metrics\": [{\"annualised_returns\": -0.4763359122499826, \"annualised_returns_over_hodl\": 0.015849938112329598, \"annualised_returns_over_uniform_hodl\": 0.8483021389458352, \"calmar\": -0.8217102326353696, \"daily_log_sharpe\": -0.6783168732127877, \"daily_returns\": 0.031016042754020513, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": 0.007450263474377634, \"return\": -0.22935931597557302, \"returns_over_hodl\": 0.0063534076532312245, \"returns_over_uniform_hodl\": 0.2806768635555179, \"sharpe\": -0.18781995724078324, \"sterling\": -1.2263488392455333, \"ulcer\": -0.17641031651910627}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -6.234812666150447e-11, \"optuna_trial_number\": 259, \"price_ratio\": 5.291564234596056, \"shift_exponent\": 0.0001467411180422725, \"step\": 259, \"test_objective\": [{\"annualised_returns\": -0.4763359122499826, \"annualised_returns_over_hodl\": 0.015849938112329598, \"annualised_returns_over_uniform_hodl\": 0.8483021389458352, \"calmar\": -0.8217102326353696, \"daily_log_sharpe\": -0.6783168732127877, \"daily_returns\": 0.031016042754020513, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": 0.007450263474377634, \"return\": -0.22935931597557302, \"returns_over_hodl\": 0.0063534076532312245, \"returns_over_uniform_hodl\": 0.2806768635555179, \"sharpe\": -0.18781995724078324, \"sterling\": -1.2263488392455333, \"ulcer\": -0.17641031651910627}], \"train_objective\": [{\"annualised_returns\": -0.48583849869302587, \"annualised_returns_over_hodl\": -0.3524967932809382, \"annualised_returns_over_uniform_hodl\": -0.3524967932809382, \"calmar\": -0.6472517502658276, \"daily_log_sharpe\": -0.47618055428993944, \"daily_returns\": 0.00812089417658228, \"fee_revenue_over_value\": 3.563764164445382e-05, \"jax_sharpe\": 0.11001172808674087, \"return\": -0.3322677354316975, \"returns_over_hodl\": -0.2319297650752825, \"returns_over_uniform_hodl\": -0.2319297650752825, \"sharpe\": 0.11836771653808321, \"sterling\": -1.4287272746155426, \"ulcer\": -0.18454176510378478}], \"train_return\": -0.3322677354316975, \"train_returns_over_hodl\": -0.2319297650752825, \"train_sharpe\": 0.11001172808674087, \"validation_return\": -0.3391427223075959, \"validation_returns_over_hodl\": -6.234812666150447e-11, \"validation_sharpe\": -2.243853205007684}, {\"centeredness_margin\": 0.8983592277133811, \"continuous_test_metrics\": [{\"annualised_returns\": -0.48263947995154, \"annualised_returns_over_hodl\": 0.0036217197030103954, \"annualised_returns_over_uniform_hodl\": 0.8260533387353068, \"calmar\": -0.8325843130019275, \"daily_log_sharpe\": -0.6886444383343489, \"daily_returns\": 0.03101604275302304, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": 0.007092124566174863, \"return\": -0.2331088423282306, \"returns_over_hodl\": 0.001457028980712538, \"returns_over_uniform_hodl\": 0.2744457733098489, \"sharpe\": -0.19805251598884221, \"sterling\": -1.24257769380068, \"ulcer\": -0.17641031651705102}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -7.325806627989095e-11, \"optuna_trial_number\": 260, \"price_ratio\": 4.377578517090034, \"shift_exponent\": 4.154043698549107e-05, \"step\": 260, \"test_objective\": [{\"annualised_returns\": -0.48263947995154, \"annualised_returns_over_hodl\": 0.0036217197030103954, \"annualised_returns_over_uniform_hodl\": 0.8260533387353068, \"calmar\": -0.8325843130019275, \"daily_log_sharpe\": -0.6886444383343489, \"daily_returns\": 0.03101604275302304, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": 0.007092124566174863, \"return\": -0.2331088423282306, \"returns_over_hodl\": 0.001457028980712538, \"returns_over_uniform_hodl\": 0.2744457733098489, \"sharpe\": -0.19805251598884221, \"sterling\": -1.24257769380068, \"ulcer\": -0.17641031651705102}], \"train_objective\": [{\"annualised_returns\": -0.5154752278687227, \"annualised_returns_over_hodl\": -0.3898194577144831, \"annualised_returns_over_uniform_hodl\": -0.3898194577144831, \"calmar\": -0.6830790373124985, \"daily_log_sharpe\": -0.5102798380233101, \"daily_returns\": 0.008153939152966815, \"fee_revenue_over_value\": 4.272848231296593e-05, \"jax_sharpe\": 0.1103049309041793, \"return\": -0.3559069610352773, \"returns_over_hodl\": -0.25912118074623003, \"returns_over_uniform_hodl\": -0.25912118074623003, \"sharpe\": 0.1039237341698407, \"sterling\": -1.4845814298463633, \"ulcer\": -0.18915793755439822}], \"train_return\": -0.3559069610352773, \"train_returns_over_hodl\": -0.25912118074623003, \"train_sharpe\": 0.11030493090417928, \"validation_return\": -0.3391427223048995, \"validation_returns_over_hodl\": -7.325806627989095e-11, \"validation_sharpe\": -2.2438532050499425}, {\"centeredness_margin\": 0.8927447672570269, \"continuous_test_metrics\": [{\"annualised_returns\": -0.48450645318932384, \"annualised_returns_over_hodl\": 1.0316192344816955e-12, \"annualised_returns_over_uniform_hodl\": 0.8194637506587656, \"calmar\": -0.8358049681406873, \"daily_log_sharpe\": -0.6924635970690984, \"daily_returns\": 0.031016042748151028, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": 0.005019283818856019, \"return\": -0.23422459934091333, \"returns_over_hodl\": 4.154454558147336e-13, \"returns_over_uniform_hodl\": 0.27259157040943816, \"sharpe\": -0.2021085298796028, \"sterling\": -1.247384311285943, \"ulcer\": -0.1764103165069703}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -1.1784639930567664e-10, \"optuna_trial_number\": 261, \"price_ratio\": 2.0388762556489888, \"shift_exponent\": 0.0001052359711693127, \"step\": 261, \"test_objective\": [{\"annualised_returns\": -0.48450645318932384, \"annualised_returns_over_hodl\": 1.0316192344816955e-12, \"annualised_returns_over_uniform_hodl\": 0.8194637506587656, \"calmar\": -0.8358049681406873, \"daily_log_sharpe\": -0.6924635970690984, \"daily_returns\": 0.031016042748151028, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": 0.005019283818856019, \"return\": -0.23422459934091333, \"returns_over_hodl\": 4.154454558147336e-13, \"returns_over_uniform_hodl\": 0.27259157040943816, \"sharpe\": -0.2021085298796028, \"sterling\": -1.247384311285943, \"ulcer\": -0.1764103165069703}], \"train_objective\": [{\"annualised_returns\": -0.6293309143118142, \"annualised_returns_over_hodl\": -0.5332022700948426, \"annualised_returns_over_uniform_hodl\": -0.5332022700948426, \"calmar\": -0.8040590277575895, \"daily_log_sharpe\": -0.6746880121780401, \"daily_returns\": 0.008378255074595064, \"fee_revenue_over_value\": 3.448154027117451e-05, \"jax_sharpe\": 0.08476799150835354, \"return\": -0.45257779958627453, \"returns_over_hodl\": -0.37031843392110464, \"returns_over_uniform_hodl\": -0.37031843392110464, \"sharpe\": -0.005629399698893018, \"sterling\": -1.699174708441642, \"ulcer\": -0.20654538328700292}], \"train_return\": -0.45257779958627453, \"train_returns_over_hodl\": -0.37031843392110464, \"train_sharpe\": 0.08476799150835353, \"validation_return\": -0.3391427222805623, \"validation_returns_over_hodl\": -1.1784639930567664e-10, \"validation_sharpe\": -2.2438532051451614}, {\"centeredness_margin\": 0.9872049178854048, \"continuous_test_metrics\": [{\"annualised_returns\": -0.4765363069351197, \"annualised_returns_over_hodl\": 0.015461194799533118, \"annualised_returns_over_uniform_hodl\": 0.8475948345233304, \"calmar\": -0.8220559270012451, \"daily_log_sharpe\": -0.6777786857610563, \"daily_returns\": 0.031016042753274495, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": 0.010332232222123485, \"return\": -0.22947809982548095, \"returns_over_hodl\": 0.006198291883877882, \"returns_over_uniform_hodl\": 0.2804794645192423, \"sharpe\": -0.1869077679129946, \"sterling\": -1.226864765516998, \"ulcer\": -0.1764103165175644}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -6.89615031745916e-11, \"optuna_trial_number\": 262, \"price_ratio\": 4.090580288181697, \"shift_exponent\": 0.00045435613256785784, \"step\": 262, \"test_objective\": [{\"annualised_returns\": -0.4765363069351197, \"annualised_returns_over_hodl\": 0.015461194799533118, \"annualised_returns_over_uniform_hodl\": 0.8475948345233304, \"calmar\": -0.8220559270012451, \"daily_log_sharpe\": -0.6777786857610563, \"daily_returns\": 0.031016042753274495, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": 0.010332232222123485, \"return\": -0.22947809982548095, \"returns_over_hodl\": 0.006198291883877882, \"returns_over_uniform_hodl\": 0.2804794645192423, \"sharpe\": -0.1869077679129946, \"sterling\": -1.226864765516998, \"ulcer\": -0.1764103165175644}], \"train_objective\": [{\"annualised_returns\": -0.5083292957095709, \"annualised_returns_over_hodl\": -0.3808203125503936, \"annualised_returns_over_uniform_hodl\": -0.3808203125503937, \"calmar\": -0.6732716928369132, \"daily_log_sharpe\": -0.49910804670551184, \"daily_returns\": 0.008160336687779296, \"fee_revenue_over_value\": 6.343194017137677e-06, \"jax_sharpe\": 0.11859282532201286, \"return\": -0.3501563292446941, \"returns_over_hodl\": -0.25250642009329916, \"returns_over_uniform_hodl\": -0.25250642009329927, \"sharpe\": 0.11395650585711725, \"sterling\": -1.4672254096426325, \"ulcer\": -0.18831786654149424}], \"train_return\": -0.3501563292446941, \"train_returns_over_hodl\": -0.25250642009329916, \"train_sharpe\": 0.11859282532201286, \"validation_return\": -0.33914272230480913, \"validation_returns_over_hodl\": -6.89615031745916e-11, \"validation_sharpe\": -2.2438532050319333}, {\"centeredness_margin\": 0.9272974195558309, \"continuous_test_metrics\": [{\"annualised_returns\": -0.503293363453663, \"annualised_returns_over_hodl\": -0.03644451126469861, \"annualised_returns_over_uniform_hodl\": 0.7531542838878884, \"calmar\": -0.8682136625532741, \"daily_log_sharpe\": -0.7411652436794466, \"daily_returns\": 0.031016042751901177, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": -0.05922717177677799, \"return\": -0.24558908990549155, \"returns_over_hodl\": -0.014840501042007292, \"returns_over_uniform_hodl\": 0.25370567399381505, \"sharpe\": -0.25722941741991073, \"sterling\": -1.2957521702835586, \"ulcer\": -0.17641031652043165}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -0.054972054512994695, \"optuna_trial_number\": 263, \"price_ratio\": 8.851530083149663, \"shift_exponent\": 2.7974389985992383e-05, \"step\": 263, \"test_objective\": [{\"annualised_returns\": -0.503293363453663, \"annualised_returns_over_hodl\": -0.03644451126469861, \"annualised_returns_over_uniform_hodl\": 0.7531542838878884, \"calmar\": -0.8682136625532741, \"daily_log_sharpe\": -0.7411652436794466, \"daily_returns\": 0.031016042751901177, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": -0.05922717177677799, \"return\": -0.24558908990549155, \"returns_over_hodl\": -0.014840501042007292, \"returns_over_uniform_hodl\": 0.25370567399381505, \"sharpe\": -0.25722941741991073, \"sterling\": -1.2957521702835586, \"ulcer\": -0.17641031652043165}], \"train_objective\": [{\"annualised_returns\": -0.44331420841456404, \"annualised_returns_over_hodl\": -0.29894433116783803, \"annualised_returns_over_uniform_hodl\": -0.29894433116783803, \"calmar\": -0.5946382242835331, \"daily_log_sharpe\": -0.44164236829958353, \"daily_returns\": 0.008052000242889662, \"fee_revenue_over_value\": 4.1638837961161766e-05, \"jax_sharpe\": 0.14402793370514957, \"return\": -0.29926392870995666, \"returns_over_hodl\": -0.19396658293286173, \"returns_over_uniform_hodl\": -0.19396658293286173, \"sharpe\": 0.12018243458982451, \"sterling\": -1.3466577980396421, \"ulcer\": -0.17819142372888275}], \"train_return\": -0.29926392870995666, \"train_returns_over_hodl\": -0.19396658293286173, \"train_sharpe\": 0.14402793370514957, \"validation_return\": -0.3303642900623682, \"validation_returns_over_hodl\": -0.054972054512994695, \"validation_sharpe\": -2.1670176332385758}, {\"centeredness_margin\": 0.800515406531506, \"continuous_test_metrics\": [{\"annualised_returns\": -0.4972303647977576, \"annualised_returns_over_hodl\": -0.02468296992346075, \"annualised_returns_over_uniform_hodl\": 0.7745539819887921, \"calmar\": -0.8577545780354265, \"daily_log_sharpe\": -0.7275051493006196, \"daily_returns\": 0.031016042751289843, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": -0.045493514374703635, \"return\": -0.24189385419475584, \"returns_over_hodl\": -0.010015018646370444, \"returns_over_uniform_hodl\": 0.2598465422067542, \"sharpe\": -0.2425060368217291, \"sterling\": -1.2801426699481209, \"ulcer\": -0.17641031651975667}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -0.04729849911637496, \"optuna_trial_number\": 264, \"price_ratio\": 7.830606409750932, \"shift_exponent\": 0.00010552374268633913, \"step\": 264, \"test_objective\": [{\"annualised_returns\": -0.4972303647977576, \"annualised_returns_over_hodl\": -0.02468296992346075, \"annualised_returns_over_uniform_hodl\": 0.7745539819887921, \"calmar\": -0.8577545780354265, \"daily_log_sharpe\": -0.7275051493006196, \"daily_returns\": 0.031016042751289843, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": -0.045493514374703635, \"return\": -0.24189385419475584, \"returns_over_hodl\": -0.010015018646370444, \"returns_over_uniform_hodl\": 0.2598465422067542, \"sharpe\": -0.2425060368217291, \"sterling\": -1.2801426699481209, \"ulcer\": -0.17641031651975667}], \"train_objective\": [{\"annualised_returns\": -0.4507215715351759, \"annualised_returns_over_hodl\": -0.30827270632182524, \"annualised_returns_over_uniform_hodl\": -0.30827270632182524, \"calmar\": -0.6037292744380693, \"daily_log_sharpe\": -0.44783354983441687, \"daily_returns\": 0.008067511530564923, \"fee_revenue_over_value\": 9.633502306665508e-05, \"jax_sharpe\": 0.10298519216020095, \"return\": -0.30493969176123914, \"returns_over_hodl\": -0.20049522456289293, \"returns_over_uniform_hodl\": -0.20049522456289293, \"sharpe\": 0.11952044777872642, \"sterling\": -1.3616872796326545, \"ulcer\": -0.17922810947595091}], \"train_return\": -0.30493969176123914, \"train_returns_over_hodl\": -0.20049522456289293, \"train_sharpe\": 0.10298519216020095, \"validation_return\": -0.334161930557594, \"validation_returns_over_hodl\": -0.04729849911637496, \"validation_sharpe\": -2.1996765291009206}, {\"centeredness_margin\": 0.9035372835012259, \"continuous_test_metrics\": [{\"annualised_returns\": -0.524511387407089, \"annualised_returns_over_hodl\": -0.07760511181904106, \"annualised_returns_over_uniform_hodl\": 0.6782640632775294, \"calmar\": -0.906819845848117, \"daily_log_sharpe\": -0.7907876404352396, \"daily_returns\": 0.031016042753648634, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": -0.10779303278913129, \"return\": -0.25873735513100715, \"returns_over_hodl\": -0.03201037255459671, \"returns_over_uniform_hodl\": 0.23185544026066363, \"sharpe\": -0.31079359689268143, \"sterling\": -1.3533532434893907, \"ulcer\": -0.17555943557894094}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -0.06407127613289954, \"optuna_trial_number\": 265, \"price_ratio\": 11.1987500202863, \"shift_exponent\": 2.104675949895932e-05, \"step\": 265, \"test_objective\": [{\"annualised_returns\": -0.524511387407089, \"annualised_returns_over_hodl\": -0.07760511181904106, \"annualised_returns_over_uniform_hodl\": 0.6782640632775294, \"calmar\": -0.906819845848117, \"daily_log_sharpe\": -0.7907876404352396, \"daily_returns\": 0.031016042753648634, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": -0.10779303278913129, \"return\": -0.25873735513100715, \"returns_over_hodl\": -0.03201037255459671, \"returns_over_uniform_hodl\": 0.23185544026066363, \"sharpe\": -0.31079359689268143, \"sterling\": -1.3533532434893907, \"ulcer\": -0.17555943557894094}], \"train_objective\": [{\"annualised_returns\": -0.42715524741649313, \"annualised_returns_over_hodl\": -0.278594734714384, \"annualised_returns_over_uniform_hodl\": -0.2785947347143837, \"calmar\": -0.5743227124434439, \"daily_log_sharpe\": -0.4274762109129919, \"daily_returns\": 0.008025667101474437, \"fee_revenue_over_value\": 4.387340790707361e-05, \"jax_sharpe\": 0.14614643309045716, \"return\": -0.2869843679221067, \"returns_over_hodl\": -0.17984181221327733, \"returns_over_uniform_hodl\": -0.1798418122132771, \"sharpe\": 0.12386553970999772, \"sterling\": -1.3117460034047395, \"ulcer\": -0.1761181246620913}], \"train_return\": -0.2869843679221067, \"train_returns_over_hodl\": -0.17984181221327733, \"train_sharpe\": 0.14614643309045716, \"validation_return\": -0.3163002724580728, \"validation_returns_over_hodl\": -0.06407127613289954, \"validation_sharpe\": -2.0868487037951287}, {\"centeredness_margin\": 0.9651492119871795, \"continuous_test_metrics\": [{\"annualised_returns\": -0.4719801511326175, \"annualised_returns_over_hodl\": 0.02429962917652695, \"annualised_returns_over_uniform_hodl\": 0.8636760451927841, \"calmar\": -0.814196250634094, \"daily_log_sharpe\": -0.6693889771283691, \"daily_returns\": 0.03101604274939333, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": 0.01380574799011369, \"return\": -0.22678412014280735, \"returns_over_hodl\": 0.00971626821151883, \"returns_over_uniform_hodl\": 0.2849564114570442, \"sharpe\": -0.1781614741950592, \"sterling\": -1.215134712094082, \"ulcer\": -0.1764103165095544}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -9.585832128067295e-11, \"optuna_trial_number\": 266, \"price_ratio\": 1.8859134589590592, \"shift_exponent\": 0.001865467062765756, \"step\": 266, \"test_objective\": [{\"annualised_returns\": -0.4719801511326175, \"annualised_returns_over_hodl\": 0.02429962917652695, \"annualised_returns_over_uniform_hodl\": 0.8636760451927841, \"calmar\": -0.814196250634094, \"daily_log_sharpe\": -0.6693889771283691, \"daily_returns\": 0.03101604274939333, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": 0.01380574799011369, \"return\": -0.22678412014280735, \"returns_over_hodl\": 0.00971626821151883, \"returns_over_uniform_hodl\": 0.2849564114570442, \"sharpe\": -0.1781614741950592, \"sterling\": -1.215134712094082, \"ulcer\": -0.1764103165095544}], \"train_objective\": [{\"annualised_returns\": -0.6039856669498722, \"annualised_returns_over_hodl\": -0.5012840325367427, \"annualised_returns_over_uniform_hodl\": -0.5012840325367428, \"calmar\": -0.7729015789059902, \"daily_log_sharpe\": -0.6288153177458117, \"daily_returns\": 0.008494893799468885, \"fee_revenue_over_value\": 1.0184121566056644e-05, \"jax_sharpe\": 0.11507744897584865, \"return\": -0.4301485111611726, \"returns_over_hodl\": -0.3445187687798673, \"returns_over_uniform_hodl\": -0.3445187687798674, \"sharpe\": 0.03498473651064116, \"sterling\": -1.6413167679343619, \"ulcer\": -0.2036526442955821}], \"train_return\": -0.4301485111611726, \"train_returns_over_hodl\": -0.3445187687798673, \"train_sharpe\": 0.11507744897584864, \"validation_return\": -0.3391427222797706, \"validation_returns_over_hodl\": -9.585832128067295e-11, \"validation_sharpe\": -2.2438532050506086}, {\"centeredness_margin\": 0.8508662961607388, \"continuous_test_metrics\": [{\"annualised_returns\": -0.541663513176294, \"annualised_returns_over_hodl\": -0.1108783232533963, \"annualised_returns_over_uniform_hodl\": 0.6177246612289711, \"calmar\": -0.9374009482491722, \"daily_log_sharpe\": -0.8340228462732387, \"daily_returns\": 0.03101604275472402, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": -0.15288189384641004, \"return\": -0.2696245693220509, \"returns_over_hodl\": -0.04622761456552793, \"returns_over_uniform_hodl\": 0.21376269793329228, \"sharpe\": -0.3582427551620888, \"sterling\": -1.4019786934579173, \"ulcer\": -0.17439972518784713}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -0.065285182885047, \"optuna_trial_number\": 267, \"price_ratio\": 12.517474568130975, \"shift_exponent\": 8.78650874716195e-05, \"step\": 267, \"test_objective\": [{\"annualised_returns\": -0.541663513176294, \"annualised_returns_over_hodl\": -0.1108783232533963, \"annualised_returns_over_uniform_hodl\": 0.6177246612289711, \"calmar\": -0.9374009482491722, \"daily_log_sharpe\": -0.8340228462732387, \"daily_returns\": 0.03101604275472402, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": -0.15288189384641004, \"return\": -0.2696245693220509, \"returns_over_hodl\": -0.04622761456552793, \"returns_over_uniform_hodl\": 0.21376269793329228, \"sharpe\": -0.3582427551620888, \"sterling\": -1.4019786934579173, \"ulcer\": -0.17439972518784713}], \"train_objective\": [{\"annualised_returns\": -0.4187170163736744, \"annualised_returns_over_hodl\": -0.26796814823256276, \"annualised_returns_over_uniform_hodl\": -0.26796814823256276, \"calmar\": -0.5636299386874752, \"daily_log_sharpe\": -0.41986319724606364, \"daily_returns\": 0.008034758753075969, \"fee_revenue_over_value\": 5.603238594571927e-05, \"jax_sharpe\": 0.10493252140794256, \"return\": -0.28062609609598055, \"returns_over_hodl\": -0.1725281034195809, \"returns_over_uniform_hodl\": -0.1725281034195809, \"sharpe\": 0.12638065061514284, \"sterling\": -1.292958220071379, \"ulcer\": -0.17505046212766684}], \"train_return\": -0.28062609609598055, \"train_returns_over_hodl\": -0.1725281034195809, \"train_sharpe\": 0.10493252140794256, \"validation_return\": -0.30658052646462786, \"validation_returns_over_hodl\": -0.06528518288504703, \"validation_sharpe\": -2.0528077026401896}, {\"centeredness_margin\": 0.9833371564225495, \"continuous_test_metrics\": [{\"annualised_returns\": -0.47600607625299185, \"annualised_returns_over_hodl\": 0.016489783175325456, \"annualised_returns_over_uniform_hodl\": 0.8494663138300793, \"calmar\": -0.8211412432621891, \"daily_log_sharpe\": -0.6774218010360628, \"daily_returns\": 0.03101604275383351, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": 0.00839048234957443, \"return\": -0.22916386495547492, \"returns_over_hodl\": 0.006608640482979267, \"returns_over_uniform_hodl\": 0.2810016706992191, \"sharpe\": -0.18676732774708726, \"sterling\": -1.2254996597823244, \"ulcer\": -0.1764103165187188}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -6.288780607377475e-11, \"optuna_trial_number\": 268, \"price_ratio\": 4.891439287950473, \"shift_exponent\": 0.0002448178347623253, \"step\": 268, \"test_objective\": [{\"annualised_returns\": -0.47600607625299185, \"annualised_returns_over_hodl\": 0.016489783175325456, \"annualised_returns_over_uniform_hodl\": 0.8494663138300793, \"calmar\": -0.8211412432621891, \"daily_log_sharpe\": -0.6774218010360628, \"daily_returns\": 0.03101604275383351, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": 0.00839048234957443, \"return\": -0.22916386495547492, \"returns_over_hodl\": 0.006608640482979267, \"returns_over_uniform_hodl\": 0.2810016706992191, \"sharpe\": -0.18676732774708726, \"sterling\": -1.2254996597823244, \"ulcer\": -0.1764103165187188}], \"train_objective\": [{\"annualised_returns\": -0.4913699870020426, \"annualised_returns_over_hodl\": -0.35946280767313366, \"annualised_returns_over_uniform_hodl\": -0.3594628076731339, \"calmar\": -0.6537354605905069, \"daily_log_sharpe\": -0.48114465927623284, \"daily_returns\": 0.00813210549544912, \"fee_revenue_over_value\": 1.0195377135521605e-05, \"jax_sharpe\": 0.11358324982694129, \"return\": -0.3366383427347992, \"returns_over_hodl\": -0.2369571294190247, \"returns_over_uniform_hodl\": -0.2369571294190248, \"sharpe\": 0.11860060431706042, \"sterling\": -1.4378856457044544, \"ulcer\": -0.18548338490798247}], \"train_return\": -0.3366383427347992, \"train_returns_over_hodl\": -0.2369571294190247, \"train_sharpe\": 0.11358324982694128, \"validation_return\": -0.33914272230696607, \"validation_returns_over_hodl\": -6.288780607377475e-11, \"validation_sharpe\": -2.2438532050144393}, {\"centeredness_margin\": 0.9184181575286415, \"continuous_test_metrics\": [{\"annualised_returns\": -0.48039981467946913, \"annualised_returns_over_hodl\": 0.007966420538108698, \"annualised_returns_over_uniform_hodl\": 0.8339583645137165, \"calmar\": -0.8287207389516602, \"daily_log_sharpe\": -0.6885813756288934, \"daily_returns\": 0.031016042754358777, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": -0.0051417653009988165, \"return\": -0.23177352199612067, \"returns_over_hodl\": 0.0032007782758667336, \"returns_over_uniform_hodl\": 0.27666485399196117, \"sharpe\": -0.19959822883508707, \"sterling\": -1.2368115619140243, \"ulcer\": -0.1764103165198011}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -0.006534393273667782, \"optuna_trial_number\": 269, \"price_ratio\": 6.105598819124744, \"shift_exponent\": 0.00011491653232932222, \"step\": 269, \"test_objective\": [{\"annualised_returns\": -0.48039981467946913, \"annualised_returns_over_hodl\": 0.007966420538108698, \"annualised_returns_over_uniform_hodl\": 0.8339583645137165, \"calmar\": -0.8287207389516602, \"daily_log_sharpe\": -0.6885813756288934, \"daily_returns\": 0.031016042754358777, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": -0.0051417653009988165, \"return\": -0.23177352199612067, \"returns_over_hodl\": 0.0032007782758667336, \"returns_over_uniform_hodl\": 0.27666485399196117, \"sharpe\": -0.19959822883508707, \"sterling\": -1.2368115619140243, \"ulcer\": -0.1764103165198011}], \"train_objective\": [{\"annualised_returns\": -0.47537313374126144, \"annualised_returns_over_hodl\": -0.3393173596817135, \"annualised_returns_over_uniform_hodl\": -0.3393173596817135, \"calmar\": -0.6343516480183127, \"daily_log_sharpe\": -0.4695059580861657, \"daily_returns\": 0.00810174354912762, \"fee_revenue_over_value\": 4.187000923789279e-05, \"jax_sharpe\": 0.10244138185878848, \"return\": -0.32404892352896064, \"returns_over_hodl\": -0.22247593885794592, \"returns_over_uniform_hodl\": -0.22247593885794592, \"sharpe\": 0.11443680852871578, \"sterling\": -1.4125619032950334, \"ulcer\": -0.18248927400203432}], \"train_return\": -0.32404892352896064, \"train_returns_over_hodl\": -0.22247593885794592, \"train_sharpe\": 0.10244138185878848, \"validation_return\": -0.33902140146206305, \"validation_returns_over_hodl\": -0.006534393273667782, \"validation_sharpe\": -2.2427891424471476}, {\"centeredness_margin\": 0.9555298627657763, \"continuous_test_metrics\": [{\"annualised_returns\": -0.4845064531146601, \"annualised_returns_over_hodl\": 8.595346656647962e-13, \"annualised_returns_over_uniform_hodl\": 0.8194637509222953, \"calmar\": -0.8358049680210576, \"daily_log_sharpe\": -0.6924635968816049, \"daily_returns\": 0.031016042751695862, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": 0.005019284043349912, \"return\": -0.23422459929624395, \"returns_over_hodl\": 3.461675390781238e-13, \"returns_over_uniform_hodl\": 0.2725915704836712, \"sharpe\": -0.20210852965966877, \"sterling\": -1.2473843110357075, \"ulcer\": -0.1764103165143071}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -8.774658777355171e-11, \"optuna_trial_number\": 270, \"price_ratio\": 3.3916635792556953, \"shift_exponent\": 2.7148943409845346e-05, \"step\": 270, \"test_objective\": [{\"annualised_returns\": -0.4845064531146601, \"annualised_returns_over_hodl\": 8.595346656647962e-13, \"annualised_returns_over_uniform_hodl\": 0.8194637509222953, \"calmar\": -0.8358049680210576, \"daily_log_sharpe\": -0.6924635968816049, \"daily_returns\": 0.031016042751695862, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": 0.005019284043349912, \"return\": -0.23422459929624395, \"returns_over_hodl\": 3.461675390781238e-13, \"returns_over_uniform_hodl\": 0.2725915704836712, \"sharpe\": -0.20210852965966877, \"sterling\": -1.2473843110357075, \"ulcer\": -0.1764103165143071}], \"train_objective\": [{\"annualised_returns\": -0.5514737429292533, \"annualised_returns_over_hodl\": -0.4351537619740733, \"annualised_returns_over_uniform_hodl\": -0.4351537619740735, \"calmar\": -0.7222871855663031, \"daily_log_sharpe\": -0.5536711939423203, \"daily_returns\": 0.00821278944103334, \"fee_revenue_over_value\": 2.1835147536266333e-05, \"jax_sharpe\": 0.11428994332542045, \"return\": -0.38539941318674376, \"returns_over_hodl\": -0.29304536841017015, \"returns_over_uniform_hodl\": -0.29304536841017026, \"sharpe\": 0.084240855600384, \"sterling\": -1.5383703748195992, \"ulcer\": -0.19664101556362426}], \"train_return\": -0.38539941318674376, \"train_returns_over_hodl\": -0.29304536841017015, \"train_sharpe\": 0.11428994332542043, \"validation_return\": -0.3391427222998574, \"validation_returns_over_hodl\": -8.774658777355171e-11, \"validation_sharpe\": -2.2438532050919413}, {\"centeredness_margin\": 0.7334080133890396, \"continuous_test_metrics\": [{\"annualised_returns\": -0.47945842992885934, \"annualised_returns_over_hodl\": 0.009792602010384677, \"annualised_returns_over_uniform_hodl\": 0.8372810354564566, \"calmar\": -0.8270967866165951, \"daily_log_sharpe\": -0.6825780630236259, \"daily_returns\": 0.03101604275370454, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": 0.008115066127696441, \"return\": -0.23121328118479778, \"returns_over_hodl\": 0.003932377670475606, \"returns_over_uniform_hodl\": 0.2775958812008208, \"sharpe\": -0.19181883316969892, \"sterling\": -1.234387919161153, \"ulcer\": -0.17641031651845812}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -6.52651266364046e-11, \"optuna_trial_number\": 271, \"price_ratio\": 5.101763103433419, \"shift_exponent\": 1.2516078909151721e-05, \"step\": 271, \"test_objective\": [{\"annualised_returns\": -0.47945842992885934, \"annualised_returns_over_hodl\": 0.009792602010384677, \"annualised_returns_over_uniform_hodl\": 0.8372810354564566, \"calmar\": -0.8270967866165951, \"daily_log_sharpe\": -0.6825780630236259, \"daily_returns\": 0.03101604275370454, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": 0.008115066127696441, \"return\": -0.23121328118479778, \"returns_over_hodl\": 0.003932377670475606, \"returns_over_uniform_hodl\": 0.2775958812008208, \"sharpe\": -0.19181883316969892, \"sterling\": -1.234387919161153, \"ulcer\": -0.17641031651845812}], \"train_objective\": [{\"annualised_returns\": -0.4950789146910577, \"annualised_returns_over_hodl\": -0.36413360190028154, \"annualised_returns_over_uniform_hodl\": -0.36413360190028166, \"calmar\": -0.6588059457744692, \"daily_log_sharpe\": -0.4867932815103695, \"daily_returns\": 0.008138498338709494, \"fee_revenue_over_value\": 0.0001308778335302754, \"jax_sharpe\": 0.10933483720807474, \"return\": -0.33957934964313796, \"returns_over_hodl\": -0.24034007193485973, \"returns_over_uniform_hodl\": -0.24034007193485984, \"sharpe\": 0.11354275638306656, \"sterling\": -1.4468261999726997, \"ulcer\": -0.1859112278714672}], \"train_return\": -0.33957934964313796, \"train_returns_over_hodl\": -0.24034007193485973, \"train_sharpe\": 0.10933483720807476, \"validation_return\": -0.33914272230712583, \"validation_returns_over_hodl\": -6.52651266364046e-11, \"validation_sharpe\": -2.2438532050250313}, {\"centeredness_margin\": 0.9634419522618639, \"continuous_test_metrics\": [{\"annualised_returns\": -0.542720733055345, \"annualised_returns_over_hodl\": -0.11292921192606808, \"annualised_returns_over_uniform_hodl\": 0.613993143621602, \"calmar\": -0.9395513913619071, \"daily_log_sharpe\": -0.8367834432862195, \"daily_returns\": 0.031016042754472492, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": -0.1555422317049599, \"return\": -0.2703035366642742, \"returns_over_hodl\": -0.04711425487780185, \"returns_over_uniform_hodl\": 0.21263436694283078, \"sharpe\": -0.36120272404325543, \"sterling\": -1.4047163074967686, \"ulcer\": -0.174406633919023}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -0.06525204118710223, \"optuna_trial_number\": 272, \"price_ratio\": 11.01844167351684, \"shift_exponent\": 0.0002691207017665209, \"step\": 272, \"test_objective\": [{\"annualised_returns\": -0.542720733055345, \"annualised_returns_over_hodl\": -0.11292921192606808, \"annualised_returns_over_uniform_hodl\": 0.613993143621602, \"calmar\": -0.9395513913619071, \"daily_log_sharpe\": -0.8367834432862195, \"daily_returns\": 0.031016042754472492, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": -0.1555422317049599, \"return\": -0.2703035366642742, \"returns_over_hodl\": -0.04711425487780185, \"returns_over_uniform_hodl\": 0.21263436694283078, \"sharpe\": -0.36120272404325543, \"sterling\": -1.4047163074967686, \"ulcer\": -0.174406633919023}], \"train_objective\": [{\"annualised_returns\": -0.4207600714629782, \"annualised_returns_over_hodl\": -0.2705410455001821, \"annualised_returns_over_uniform_hodl\": -0.2705410455001822, \"calmar\": -0.5659024461575444, \"daily_log_sharpe\": -0.42092627307310065, \"daily_returns\": 0.008039093149667277, \"fee_revenue_over_value\": 2.9695535077471852e-05, \"jax_sharpe\": 0.145171261764005, \"return\": -0.2821622082121209, \"returns_over_hodl\": -0.17429504214116154, \"returns_over_uniform_hodl\": -0.17429504214116165, \"sharpe\": 0.12771010826392223, \"sterling\": -1.2962515377107768, \"ulcer\": -0.17544003941343492}], \"train_return\": -0.2821622082121209, \"train_returns_over_hodl\": -0.17429504214116154, \"train_sharpe\": 0.14517126176400502, \"validation_return\": -0.30888273146965295, \"validation_returns_over_hodl\": -0.06525204118710226, \"validation_sharpe\": -2.0622266292108997}, {\"centeredness_margin\": 0.9736620348205046, \"continuous_test_metrics\": [{\"annualised_returns\": -0.4770326950147372, \"annualised_returns_over_hodl\": 0.014498257252829605, \"annualised_returns_over_uniform_hodl\": 0.8458428046042159, \"calmar\": -0.8229122299719531, \"daily_log_sharpe\": -0.6787636657027897, \"daily_returns\": 0.031016042753824656, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": 0.009713072270745183, \"return\": -0.22977245062158647, \"returns_over_hodl\": 0.005813909200883982, \"returns_over_uniform_hodl\": 0.279990302368637, \"sharpe\": -0.18796882598847378, \"sterling\": -1.228142741746842, \"ulcer\": -0.17641031651870123}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -6.327027790575812e-11, \"optuna_trial_number\": 273, \"price_ratio\": 5.137980808884979, \"shift_exponent\": 9.494283643147573e-05, \"step\": 273, \"test_objective\": [{\"annualised_returns\": -0.4770326950147372, \"annualised_returns_over_hodl\": 0.014498257252829605, \"annualised_returns_over_uniform_hodl\": 0.8458428046042159, \"calmar\": -0.8229122299719531, \"daily_log_sharpe\": -0.6787636657027897, \"daily_returns\": 0.031016042753824656, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": 0.009713072270745183, \"return\": -0.22977245062158647, \"returns_over_hodl\": 0.005813909200883982, \"returns_over_uniform_hodl\": 0.279990302368637, \"sharpe\": -0.18796882598847378, \"sterling\": -1.228142741746842, \"ulcer\": -0.17641031651870123}], \"train_objective\": [{\"annualised_returns\": -0.49123196080824694, \"annualised_returns_over_hodl\": -0.35928898601813464, \"annualised_returns_over_uniform_hodl\": -0.35928898601813464, \"calmar\": -0.6540274929360207, \"daily_log_sharpe\": -0.48214547415278625, \"daily_returns\": 0.008087398315287361, \"fee_revenue_over_value\": 1.6322856654160705e-05, \"jax_sharpe\": 0.11034646914456529, \"return\": -0.336529057302129, \"returns_over_hodl\": -0.23683142201742247, \"returns_over_uniform_hodl\": -0.23683142201742247, \"sharpe\": 0.11595805785637216, \"sterling\": -1.4391693152562126, \"ulcer\": -0.18533645075434912}], \"train_return\": -0.336529057302129, \"train_returns_over_hodl\": -0.23683142201742247, \"train_sharpe\": 0.11034646914456528, \"validation_return\": -0.33914272230713893, \"validation_returns_over_hodl\": -6.327027790575812e-11, \"validation_sharpe\": -2.243853205016551}, {\"centeredness_margin\": 0.9500278376278921, \"continuous_test_metrics\": [{\"annualised_returns\": -0.4987561222242505, \"annualised_returns_over_hodl\": -0.02764276927884679, \"annualised_returns_over_uniform_hodl\": 0.7691687344973785, \"calmar\": -0.8603866131823472, \"daily_log_sharpe\": -0.731110466379127, \"daily_returns\": 0.031016042755219946, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": -0.049628905522957754, \"return\": -0.2428212453056623, \"returns_over_hodl\": -0.011226067127692696, \"returns_over_uniform_hodl\": 0.25830537215977123, \"sharpe\": -0.24646511855262498, \"sterling\": -1.284070810429177, \"ulcer\": -0.17641031652158456}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -0.04657161427903167, \"optuna_trial_number\": 274, \"price_ratio\": 7.4180210646806, \"shift_exponent\": 0.0002128500970217322, \"step\": 274, \"test_objective\": [{\"annualised_returns\": -0.4987561222242505, \"annualised_returns_over_hodl\": -0.02764276927884679, \"annualised_returns_over_uniform_hodl\": 0.7691687344973785, \"calmar\": -0.8603866131823472, \"daily_log_sharpe\": -0.731110466379127, \"daily_returns\": 0.031016042755219946, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": -0.049628905522957754, \"return\": -0.2428212453056623, \"returns_over_hodl\": -0.011226067127692696, \"returns_over_uniform_hodl\": 0.25830537215977123, \"sharpe\": -0.24646511855262498, \"sterling\": -1.284070810429177, \"ulcer\": -0.17641031652158456}], \"train_objective\": [{\"annualised_returns\": -0.4510372293593399, \"annualised_returns_over_hodl\": -0.3086702262700374, \"annualised_returns_over_uniform_hodl\": -0.3086702262700374, \"calmar\": -0.6038825710075805, \"daily_log_sharpe\": -0.44713023276445757, \"daily_returns\": 0.00809225360926113, \"fee_revenue_over_value\": 3.389488208052175e-05, \"jax_sharpe\": 0.10545100904909187, \"return\": -0.3051822248913515, \"returns_over_hodl\": -0.20077420236298904, \"returns_over_uniform_hodl\": -0.20077420236298904, \"sharpe\": 0.12158320088986997, \"sterling\": -1.3610935639011668, \"ulcer\": -0.17939826188989905}], \"train_return\": -0.3051822248913515, \"train_returns_over_hodl\": -0.20077420236298904, \"train_sharpe\": 0.10545100904909188, \"validation_return\": -0.33436720696322186, \"validation_returns_over_hodl\": -0.04657161427903167, \"validation_sharpe\": -2.2014627172902803}, {\"centeredness_margin\": 0.8461306343115431, \"continuous_test_metrics\": [{\"annualised_returns\": -0.4835805577763653, \"annualised_returns_over_hodl\": 0.0017961336785217163, \"annualised_returns_over_uniform_hodl\": 0.8227317511045564, \"calmar\": -0.8342077359133712, \"daily_log_sharpe\": -0.6905410521250309, \"daily_returns\": 0.031016042752556695, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": 0.005958625266000401, \"return\": -0.2336709564316941, \"returns_over_hodl\": 0.0007229833362090154, \"returns_over_uniform_hodl\": 0.27351163300048387, \"sharpe\": -0.20004677043780492, \"sterling\": -1.2450005464459886, \"ulcer\": -0.1764103165160839}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -7.801526091810729e-11, \"optuna_trial_number\": 275, \"price_ratio\": 3.8415902644249553, \"shift_exponent\": 0.00018085243382811057, \"step\": 275, \"test_objective\": [{\"annualised_returns\": -0.4835805577763653, \"annualised_returns_over_hodl\": 0.0017961336785217163, \"annualised_returns_over_uniform_hodl\": 0.8227317511045564, \"calmar\": -0.8342077359133712, \"daily_log_sharpe\": -0.6905410521250309, \"daily_returns\": 0.031016042752556695, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": 0.005958625266000401, \"return\": -0.2336709564316941, \"returns_over_hodl\": 0.0007229833362090154, \"returns_over_uniform_hodl\": 0.27351163300048387, \"sharpe\": -0.20004677043780492, \"sterling\": -1.2450005464459886, \"ulcer\": -0.1764103165160839}], \"train_objective\": [{\"annualised_returns\": -0.529220553158076, \"annualised_returns_over_hodl\": -0.4071294705793693, \"annualised_returns_over_uniform_hodl\": -0.40712947057936943, \"calmar\": -0.6982719022417846, \"daily_log_sharpe\": -0.5261930570937531, \"daily_returns\": 0.008162970738147915, \"fee_revenue_over_value\": 4.55358796706601e-05, \"jax_sharpe\": 0.11378894689428026, \"return\": -0.36706297128089926, \"returns_over_hodl\": -0.27195356861312026, \"returns_over_uniform_hodl\": -0.27195356861312037, \"sharpe\": 0.09766562670595766, \"sterling\": -1.5058239667218158, \"ulcer\": -0.1918255914194219}], \"train_return\": -0.36706297128089926, \"train_returns_over_hodl\": -0.27195356861312026, \"train_sharpe\": 0.11378894689428028, \"validation_return\": -0.33914272230288267, \"validation_returns_over_hodl\": -7.801526091810729e-11, \"validation_sharpe\": -2.243853205062263}, {\"centeredness_margin\": 0.9380915441573112, \"continuous_test_metrics\": [{\"annualised_returns\": -0.4763869750800951, \"annualised_returns_over_hodl\": 0.015750881915927106, \"annualised_returns_over_uniform_hodl\": 0.8481219097868662, \"calmar\": -0.8217983194663522, \"daily_log_sharpe\": -0.6787551977457017, \"daily_returns\": 0.03101604275401664, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": 0.0060162694225041405, \"return\": -0.22938958086477945, \"returns_over_hodl\": 0.006313885765836913, \"returns_over_uniform_hodl\": 0.28062656833468647, \"sharpe\": -0.1884195556086188, \"sterling\": -1.226480303086074, \"ulcer\": -0.1764103165190986}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -6.196776425326789e-11, \"optuna_trial_number\": 276, \"price_ratio\": 5.168487118487445, \"shift_exponent\": 0.00020924668141850957, \"step\": 276, \"test_objective\": [{\"annualised_returns\": -0.4763869750800951, \"annualised_returns_over_hodl\": 0.015750881915927106, \"annualised_returns_over_uniform_hodl\": 0.8481219097868662, \"calmar\": -0.8217983194663522, \"daily_log_sharpe\": -0.6787551977457017, \"daily_returns\": 0.03101604275401664, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": 0.0060162694225041405, \"return\": -0.22938958086477945, \"returns_over_hodl\": 0.006313885765836913, \"returns_over_uniform_hodl\": 0.28062656833468647, \"sharpe\": -0.1884195556086188, \"sterling\": -1.226480303086074, \"ulcer\": -0.1764103165190986}], \"train_objective\": [{\"annualised_returns\": -0.4859087812904338, \"annualised_returns_over_hodl\": -0.352585302838893, \"annualised_returns_over_uniform_hodl\": -0.352585302838893, \"calmar\": -0.6472221226176289, \"daily_log_sharpe\": -0.47566850206553923, \"daily_returns\": 0.008124552766981399, \"fee_revenue_over_value\": 3.5536822779604e-05, \"jax_sharpe\": 0.11190382304501037, \"return\": -0.33232315176704486, \"returns_over_hodl\": -0.23199350864432566, \"returns_over_uniform_hodl\": -0.23199350864432566, \"sharpe\": 0.11976453657216649, \"sterling\": -1.4280321836232222, \"ulcer\": -0.1846280671167043}], \"train_return\": -0.33232315176704486, \"train_returns_over_hodl\": -0.23199350864432566, \"train_sharpe\": 0.11190382304501037, \"validation_return\": -0.3391427223075354, \"validation_returns_over_hodl\": -6.196776425326789e-11, \"validation_sharpe\": -2.2438532050073063}, {\"centeredness_margin\": 0.9838275140464893, \"continuous_test_metrics\": [{\"annualised_returns\": -0.49701946827818555, \"annualised_returns_over_hodl\": -0.024273854191442013, \"annualised_returns_over_uniform_hodl\": 0.7752983532323825, \"calmar\": -0.8573907672245108, \"daily_log_sharpe\": -0.7270977852926849, \"daily_returns\": 0.03101604275125341, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": -0.04379106756022284, \"return\": -0.24176579864226877, \"returns_over_hodl\": -0.009847795264027237, \"returns_over_uniform_hodl\": 0.2600593492732899, \"sharpe\": -0.24209573869606624, \"sterling\": -1.2795997060808566, \"ulcer\": -0.17641031651978256}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -0.04713782957214496, \"optuna_trial_number\": 277, \"price_ratio\": 7.77828623410807, \"shift_exponent\": 0.00011673136427230587, \"step\": 277, \"test_objective\": [{\"annualised_returns\": -0.49701946827818555, \"annualised_returns_over_hodl\": -0.024273854191442013, \"annualised_returns_over_uniform_hodl\": 0.7752983532323825, \"calmar\": -0.8573907672245108, \"daily_log_sharpe\": -0.7270977852926849, \"daily_returns\": 0.03101604275125341, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": -0.04379106756022284, \"return\": -0.24176579864226877, \"returns_over_hodl\": -0.009847795264027237, \"returns_over_uniform_hodl\": 0.2600593492732899, \"sharpe\": -0.24209573869606624, \"sterling\": -1.2795997060808566, \"ulcer\": -0.17641031651978256}], \"train_objective\": [{\"annualised_returns\": -0.4505480635086735, \"annualised_returns_over_hodl\": -0.3080542010403793, \"annualised_returns_over_uniform_hodl\": -0.30805420104037895, \"calmar\": -0.603470760694302, \"daily_log_sharpe\": -0.4473870590271552, \"daily_returns\": 0.008061377643875954, \"fee_revenue_over_value\": 1.2039540327983282e-05, \"jax_sharpe\": 0.15452813142685673, \"return\": -0.30480640159791605, \"returns_over_hodl\": -0.20034190531730744, \"returns_over_uniform_hodl\": -0.20034190531730722, \"sharpe\": 0.12012572424937881, \"sterling\": -1.3609472748683042, \"ulcer\": -0.17924922627690815}], \"train_return\": -0.30480640159791605, \"train_returns_over_hodl\": -0.20034190531730744, \"train_sharpe\": 0.15452813142685673, \"validation_return\": -0.33416567978397027, \"validation_returns_over_hodl\": -0.04713782957214496, \"validation_sharpe\": -2.1996786090266456}, {\"centeredness_margin\": 0.963606002075507, \"continuous_test_metrics\": [{\"annualised_returns\": -0.48131934320997816, \"annualised_returns_over_hodl\": 0.006182637769861721, \"annualised_returns_over_uniform_hodl\": 0.8307128363411367, \"calmar\": -0.8303069878382205, \"daily_log_sharpe\": -0.6860358768922087, \"daily_returns\": 0.031016042753209898, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": 0.007788140921361392, \"return\": -0.2323213412281221, \"returns_over_hodl\": 0.0024853998281264555, \"returns_over_uniform_hodl\": 0.27575446938551784, \"sharpe\": -0.19535099066509937, \"sterling\": -1.2391789347694493, \"ulcer\": -0.17641031651743305}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -7.100908749890777e-11, \"optuna_trial_number\": 278, \"price_ratio\": 4.506535567436535, \"shift_exponent\": 7.300688956691701e-05, \"step\": 278, \"test_objective\": [{\"annualised_returns\": -0.48131934320997816, \"annualised_returns_over_hodl\": 0.006182637769861721, \"annualised_returns_over_uniform_hodl\": 0.8307128363411367, \"calmar\": -0.8303069878382205, \"daily_log_sharpe\": -0.6860358768922087, \"daily_returns\": 0.031016042753209898, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": 0.007788140921361392, \"return\": -0.2323213412281221, \"returns_over_hodl\": 0.0024853998281264555, \"returns_over_uniform_hodl\": 0.27575446938551784, \"sharpe\": -0.19535099066509937, \"sterling\": -1.2391789347694493, \"ulcer\": -0.17641031651743305}], \"train_objective\": [{\"annualised_returns\": -0.5104489974914612, \"annualised_returns_over_hodl\": -0.383489733923976, \"annualised_returns_over_uniform_hodl\": -0.38348973392397634, \"calmar\": -0.6769982280661222, \"daily_log_sharpe\": -0.5042952848092259, \"daily_returns\": 0.00815965458816915, \"fee_revenue_over_value\": 2.098313537401792e-05, \"jax_sharpe\": 0.11046688117055553, \"return\": -0.35185869643252476, \"returns_over_hodl\": -0.25446459649912334, \"returns_over_uniform_hodl\": -0.25446459649912356, \"sharpe\": 0.1066835529620319, \"sterling\": -1.4752856435427795, \"ulcer\": -0.1883155205899838}], \"train_return\": -0.35185869643252476, \"train_returns_over_hodl\": -0.25446459649912334, \"train_sharpe\": 0.11046688117055552, \"validation_return\": -0.3391427223054584, \"validation_returns_over_hodl\": -7.100908749890777e-11, \"validation_sharpe\": -2.2438532050426923}, {\"centeredness_margin\": 0.9513708471161825, \"continuous_test_metrics\": [{\"annualised_returns\": -0.7479290584066411, \"annualised_returns_over_hodl\": -0.15159074830912023, \"annualised_returns_over_uniform_hodl\": -0.110301295406942, \"calmar\": -1.2436225923872675, \"daily_log_sharpe\": -1.7595182236365754, \"daily_returns\": 0.01904387217881512, \"fee_revenue_over_value\": 0.0017745585728404037, \"jax_sharpe\": -1.230681811194083, \"return\": -0.42592241704783795, \"returns_over_hodl\": -0.0640628360391351, \"returns_over_uniform_hodl\": -0.04597837408048955, \"sharpe\": -1.3799312560531278, \"sterling\": -2.268937029896159, \"ulcer\": -0.1474002592781228}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -0.032042375524960115, \"optuna_trial_number\": 279, \"price_ratio\": 5.984643475009294, \"shift_exponent\": 0.008890436523970448, \"step\": 279, \"test_objective\": [{\"annualised_returns\": -0.7479290584066411, \"annualised_returns_over_hodl\": -0.15159074830912023, \"annualised_returns_over_uniform_hodl\": -0.110301295406942, \"calmar\": -1.2436225923872675, \"daily_log_sharpe\": -1.7595182236365754, \"daily_returns\": 0.01904387217881512, \"fee_revenue_over_value\": 0.0017745585728404037, \"jax_sharpe\": -1.230681811194083, \"return\": -0.42592241704783795, \"returns_over_hodl\": -0.0640628360391351, \"returns_over_uniform_hodl\": -0.04597837408048955, \"sharpe\": -1.3799312560531278, \"sterling\": -2.268937029896159, \"ulcer\": -0.1474002592781228}], \"train_objective\": [{\"annualised_returns\": -0.34917669019999664, \"annualised_returns_over_hodl\": -0.18039336077903334, \"annualised_returns_over_uniform_hodl\": -0.18039336077903334, \"calmar\": -0.47316678929354017, \"daily_log_sharpe\": -0.370403579568877, \"daily_returns\": 0.008074027971440231, \"fee_revenue_over_value\": 0.0007478762812084827, \"jax_sharpe\": 0.13787472107686624, \"return\": -0.22954113176247004, \"returns_over_hodl\": -0.1137667665203862, \"returns_over_uniform_hodl\": -0.1137667665203862, \"sharpe\": 0.12834753059530132, \"sterling\": -1.1368907120414529, \"ulcer\": -0.16638283444130697}], \"train_return\": -0.22954113176247004, \"train_returns_over_hodl\": -0.1137667665203862, \"train_sharpe\": 0.13787472107686624, \"validation_return\": -0.16454061301938294, \"validation_returns_over_hodl\": -0.03204237552496014, \"validation_sharpe\": -1.3267322937155919}, {\"centeredness_margin\": 0.9543825654520767, \"continuous_test_metrics\": [{\"annualised_returns\": -0.4775612573984387, \"annualised_returns_over_hodl\": 0.013472905186536765, \"annualised_returns_over_uniform_hodl\": 0.8439772136515127, \"calmar\": -0.82382403579215, \"daily_log_sharpe\": -0.6794809575862729, \"daily_returns\": 0.031016042753348447, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": 0.009774506478151644, \"return\": -0.23008606380661922, \"returns_over_hodl\": 0.0054043724376477975, \"returns_over_uniform_hodl\": 0.2794691293258642, \"sharpe\": -0.18866062950926937, \"sterling\": -1.2295035523740858, \"ulcer\": -0.1764103165177188}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -6.838551946941607e-11, \"optuna_trial_number\": 280, \"price_ratio\": 4.301799186184494, \"shift_exponent\": 0.0003426739255116666, \"step\": 280, \"test_objective\": [{\"annualised_returns\": -0.4775612573984387, \"annualised_returns_over_hodl\": 0.013472905186536765, \"annualised_returns_over_uniform_hodl\": 0.8439772136515127, \"calmar\": -0.82382403579215, \"daily_log_sharpe\": -0.6794809575862729, \"daily_returns\": 0.031016042753348447, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": 0.009774506478151644, \"return\": -0.23008606380661922, \"returns_over_hodl\": 0.0054043724376477975, \"returns_over_uniform_hodl\": 0.2794691293258642, \"sharpe\": -0.18866062950926937, \"sterling\": -1.2295035523740858, \"ulcer\": -0.1764103165177188}], \"train_objective\": [{\"annualised_returns\": -0.5059954256551691, \"annualised_returns_over_hodl\": -0.37788118089536016, \"annualised_returns_over_uniform_hodl\": -0.3778811808953605, \"calmar\": -0.6708642197224525, \"daily_log_sharpe\": -0.4971985870993278, \"daily_returns\": 0.008157709992433607, \"fee_revenue_over_value\": 2.7190121103465935e-05, \"jax_sharpe\": 0.17052979109691693, \"return\": -0.3482852916975836, \"returns_over_hodl\": -0.2503542277781783, \"returns_over_uniform_hodl\": -0.2503542277781785, \"sharpe\": 0.11326861694891284, \"sterling\": -1.4641346823335937, \"ulcer\": -0.1878216710245771}], \"train_return\": -0.3482852916975836, \"train_returns_over_hodl\": -0.2503542277781783, \"train_sharpe\": 0.17052979109691693, \"validation_return\": -0.3391427223052478, \"validation_returns_over_hodl\": -6.838551946941607e-11, \"validation_sharpe\": -2.2438532050309496}, {\"centeredness_margin\": 0.6848634846449195, \"continuous_test_metrics\": [{\"annualised_returns\": -0.4818569270188131, \"annualised_returns_over_hodl\": 0.005139785071588854, \"annualised_returns_over_uniform_hodl\": 0.8288154037560578, \"calmar\": -0.8312343562670357, \"daily_log_sharpe\": -0.6922825480482059, \"daily_returns\": 0.031016042754509355, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": -0.009418348675828546, \"return\": -0.2326418814165745, \"returns_over_hodl\": 0.002066817297846857, \"returns_over_uniform_hodl\": 0.2752217848131864, \"sharpe\": -0.20383730297579225, \"sterling\": -1.2405629715376036, \"ulcer\": -0.17641031652011416}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -0.016076420868051944, \"optuna_trial_number\": 281, \"price_ratio\": 6.451635499447358, \"shift_exponent\": 8.477814814456127e-05, \"step\": 281, \"test_objective\": [{\"annualised_returns\": -0.4818569270188131, \"annualised_returns_over_hodl\": 0.005139785071588854, \"annualised_returns_over_uniform_hodl\": 0.8288154037560578, \"calmar\": -0.8312343562670357, \"daily_log_sharpe\": -0.6922825480482059, \"daily_returns\": 0.031016042754509355, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": -0.009418348675828546, \"return\": -0.2326418814165745, \"returns_over_hodl\": 0.002066817297846857, \"returns_over_uniform_hodl\": 0.2752217848131864, \"sharpe\": -0.20383730297579225, \"sterling\": -1.2405629715376036, \"ulcer\": -0.17641031652011416}], \"train_objective\": [{\"annualised_returns\": -0.46994444431271154, \"annualised_returns_over_hodl\": -0.33248080384403855, \"annualised_returns_over_uniform_hodl\": -0.33248080384403855, \"calmar\": -0.627663175656495, \"daily_log_sharpe\": -0.46432339189153393, \"daily_returns\": 0.008047984692618667, \"fee_revenue_over_value\": 0.00027677281538377764, \"jax_sharpe\": 0.1027623814041331, \"return\": -0.319810973807946, \"returns_over_hodl\": -0.21760116612261426, \"returns_over_uniform_hodl\": -0.21760116612261426, \"sharpe\": 0.11616208706456697, \"sterling\": -1.401297350056286, \"ulcer\": -0.1818068978718283}], \"train_return\": -0.319810973807946, \"train_returns_over_hodl\": -0.21760116612261426, \"train_sharpe\": 0.1027623814041331, \"validation_return\": -0.33872773625607056, \"validation_returns_over_hodl\": -0.016076420868051944, \"validation_sharpe\": -2.2401049711951293}, {\"centeredness_margin\": 0.8948570178445495, \"continuous_test_metrics\": [{\"annualised_returns\": -0.48450645316861674, \"annualised_returns_over_hodl\": 7.884803920887862e-13, \"annualised_returns_over_uniform_hodl\": 0.8194637507318527, \"calmar\": -0.8358049681074845, \"daily_log_sharpe\": -0.6924635970170476, \"daily_returns\": 0.031016042749138266, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": 0.005019283881251556, \"return\": -0.2342245993285248, \"returns_over_hodl\": 3.1752378504279477e-13, \"returns_over_uniform_hodl\": 0.2725915704300259, \"sharpe\": -0.20210852981851837, \"sterling\": -1.2473843112164482, \"ulcer\": -0.17641031650901723}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -1.0950884643534664e-10, \"optuna_trial_number\": 282, \"price_ratio\": 2.3454482211290393, \"shift_exponent\": 0.00010552150142230868, \"step\": 282, \"test_objective\": [{\"annualised_returns\": -0.48450645316861674, \"annualised_returns_over_hodl\": 7.884803920887862e-13, \"annualised_returns_over_uniform_hodl\": 0.8194637507318527, \"calmar\": -0.8358049681074845, \"daily_log_sharpe\": -0.6924635970170476, \"daily_returns\": 0.031016042749138266, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": 0.005019283881251556, \"return\": -0.2342245993285248, \"returns_over_hodl\": 3.1752378504279477e-13, \"returns_over_uniform_hodl\": 0.2725915704300259, \"sharpe\": -0.20210852981851837, \"sterling\": -1.2473843112164482, \"ulcer\": -0.17641031650901723}], \"train_objective\": [{\"annualised_returns\": -0.6098044725364069, \"annualised_returns_over_hodl\": -0.5086118765448582, \"annualised_returns_over_uniform_hodl\": -0.5086118765448584, \"calmar\": -0.7835781834076035, \"daily_log_sharpe\": -0.6384251480192656, \"daily_returns\": 0.008355970074864627, \"fee_revenue_over_value\": 3.634953496397957e-05, \"jax_sharpe\": 0.10724177553050658, \"return\": -0.43524675367259436, \"returns_over_hodl\": -0.3503831077241333, \"returns_over_uniform_hodl\": -0.3503831077241334, \"sharpe\": 0.027203826645501607, \"sterling\": -1.6586933409568252, \"ulcer\": -0.20491408160203925}], \"train_return\": -0.43524675367259436, \"train_returns_over_hodl\": -0.3503831077241333, \"train_sharpe\": 0.1072417755305066, \"validation_return\": -0.339142722285968, \"validation_returns_over_hodl\": -1.0950884643534664e-10, \"validation_sharpe\": -2.243853205130274}, {\"centeredness_margin\": 0.7382013299385912, \"continuous_test_metrics\": [{\"annualised_returns\": -0.6345223109403908, \"annualised_returns_over_hodl\": -0.12260210313240372, \"annualised_returns_over_uniform_hodl\": 0.2899742606529998, \"calmar\": -1.0834917487708489, \"daily_log_sharpe\": -1.1574832180966592, \"daily_returns\": 0.026076106970061005, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": -0.5062876300534016, \"return\": -0.3332737745100165, \"returns_over_hodl\": -0.05131263193280888, \"returns_over_uniform_hodl\": 0.10798828690396034, \"sharpe\": -0.7249767731853042, \"sterling\": -1.7285276875798081, \"ulcer\": -0.1619940902687588}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -0.04772156230777702, \"optuna_trial_number\": 283, \"price_ratio\": 30.24735327514589, \"shift_exponent\": 0.00032182429637765117, \"step\": 283, \"test_objective\": [{\"annualised_returns\": -0.6345223109403908, \"annualised_returns_over_hodl\": -0.12260210313240372, \"annualised_returns_over_uniform_hodl\": 0.2899742606529998, \"calmar\": -1.0834917487708489, \"daily_log_sharpe\": -1.1574832180966592, \"daily_returns\": 0.026076106970061005, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": -0.5062876300534016, \"return\": -0.3332737745100165, \"returns_over_hodl\": -0.05131263193280888, \"returns_over_uniform_hodl\": 0.10798828690396034, \"sharpe\": -0.7249767731853042, \"sterling\": -1.7285276875798081, \"ulcer\": -0.1619940902687588}], \"train_objective\": [{\"annualised_returns\": -0.37588482263469514, \"annualised_returns_over_hodl\": -0.21402793153741417, \"annualised_returns_over_uniform_hodl\": -0.21402793153741417, \"calmar\": -0.5090866769638431, \"daily_log_sharpe\": -0.37957267132299366, \"daily_returns\": 0.007960215604771496, \"fee_revenue_over_value\": 0.0007909724352466115, \"jax_sharpe\": 0.1457723845924912, \"return\": -0.24889466316662812, \"returns_over_hodl\": -0.13602849056905864, \"returns_over_uniform_hodl\": -0.13602849056905864, \"sharpe\": 0.1431081409637577, \"sterling\": -1.1928220335145308, \"ulcer\": -0.17007125728295175}], \"train_return\": -0.24889466316662812, \"train_returns_over_hodl\": -0.13602849056905864, \"train_sharpe\": 0.1457723845924912, \"validation_return\": -0.24556597982252693, \"validation_returns_over_hodl\": -0.04772156230777702, \"validation_sharpe\": -1.7982333555038168}, {\"centeredness_margin\": 0.8547488756938211, \"continuous_test_metrics\": [{\"annualised_returns\": -0.48214455366938946, \"annualised_returns_over_hodl\": 0.004581821499940686, \"annualised_returns_over_uniform_hodl\": 0.8278002091572405, \"calmar\": -0.8317305316887688, \"daily_log_sharpe\": -0.6876543907251066, \"daily_returns\": 0.031016042753093515, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": 0.0076303509372880945, \"return\": -0.2328134635044109, \"returns_over_hodl\": 0.0018427541197998387, \"returns_over_uniform_hodl\": 0.2749366438718268, \"sharpe\": -0.19701951442109136, \"sterling\": -1.2413034809675605, \"ulcer\": -0.17641031651719258}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -7.246392375037658e-11, \"optuna_trial_number\": 284, \"price_ratio\": 4.491915444696264, \"shift_exponent\": 1.0724192329895575e-05, \"step\": 284, \"test_objective\": [{\"annualised_returns\": -0.48214455366938946, \"annualised_returns_over_hodl\": 0.004581821499940686, \"annualised_returns_over_uniform_hodl\": 0.8278002091572405, \"calmar\": -0.8317305316887688, \"daily_log_sharpe\": -0.6876543907251066, \"daily_returns\": 0.031016042753093515, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": 0.0076303509372880945, \"return\": -0.2328134635044109, \"returns_over_hodl\": 0.0018427541197998387, \"returns_over_uniform_hodl\": 0.2749366438718268, \"sharpe\": -0.19701951442109136, \"sterling\": -1.2413034809675605, \"ulcer\": -0.17641031651719258}], \"train_objective\": [{\"annualised_returns\": -0.5138224649591525, \"annualised_returns_over_hodl\": -0.3877380702882123, \"annualised_returns_over_uniform_hodl\": -0.3877380702882123, \"calmar\": -0.6812077900098358, \"daily_log_sharpe\": -0.5089271552254664, \"daily_returns\": 0.008146185453771434, \"fee_revenue_over_value\": 4.5317352665853355e-05, \"jax_sharpe\": 0.10855310922706447, \"return\": -0.3545739674441015, \"returns_over_hodl\": -0.25758788251420683, \"returns_over_uniform_hodl\": -0.25758788251420683, \"sharpe\": 0.10343679727412573, \"sterling\": -1.4824250958652834, \"ulcer\": -0.18877155354191}], \"train_return\": -0.3545739674441015, \"train_returns_over_hodl\": -0.25758788251420683, \"train_sharpe\": 0.10855310922706446, \"validation_return\": -0.33914272230513265, \"validation_returns_over_hodl\": -7.246392375037658e-11, \"validation_sharpe\": -2.243853205047506}, {\"centeredness_margin\": 0.3968609700727899, \"continuous_test_metrics\": [{\"annualised_returns\": -0.4746174761800447, \"annualised_returns_over_hodl\": 0.01918351253463757, \"annualised_returns_over_uniform_hodl\": 0.8543674566523718, \"calmar\": -0.8187458143622477, \"daily_log_sharpe\": -0.6764213925158011, \"daily_returns\": 0.03101604275248494, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": 0.005228366992766763, \"return\": -0.22834182661771596, \"returns_over_hodl\": 0.00768211235750238, \"returns_over_uniform_hodl\": 0.282367761929474, \"sharpe\": -0.18630162376517473, \"sterling\": -1.2219246386710692, \"ulcer\": -0.17641031651593203}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -7.324751916115702e-11, \"optuna_trial_number\": 285, \"price_ratio\": 3.261887805956083, \"shift_exponent\": 0.001087730290603936, \"step\": 285, \"test_objective\": [{\"annualised_returns\": -0.4746174761800447, \"annualised_returns_over_hodl\": 0.01918351253463757, \"annualised_returns_over_uniform_hodl\": 0.8543674566523718, \"calmar\": -0.8187458143622477, \"daily_log_sharpe\": -0.6764213925158011, \"daily_returns\": 0.03101604275248494, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": 0.005228366992766763, \"return\": -0.22834182661771596, \"returns_over_hodl\": 0.00768211235750238, \"returns_over_uniform_hodl\": 0.282367761929474, \"sharpe\": -0.18630162376517473, \"sterling\": -1.2219246386710692, \"ulcer\": -0.17641031651593203}], \"train_objective\": [{\"annualised_returns\": -0.5296003538597461, \"annualised_returns_over_hodl\": -0.40760776810188015, \"annualised_returns_over_uniform_hodl\": -0.40760776810188015, \"calmar\": -0.6950594156297885, \"daily_log_sharpe\": -0.5239609389922187, \"daily_returns\": 0.00826026496717166, \"fee_revenue_over_value\": 0.0018676513984703753, \"jax_sharpe\": 0.12538873742844492, \"return\": -0.36737302942925165, \"returns_over_hodl\": -0.27231021819781465, \"returns_over_uniform_hodl\": -0.27231021819781465, \"sharpe\": 0.10369992127726291, \"sterling\": -1.5029168343094008, \"ulcer\": -0.1919838964682472}], \"train_return\": -0.36737302942925165, \"train_returns_over_hodl\": -0.27231021819781465, \"train_sharpe\": 0.1253887374284449, \"validation_return\": -0.3391427222989277, \"validation_returns_over_hodl\": -7.324751916115702e-11, \"validation_sharpe\": -2.2438532050276687}, {\"centeredness_margin\": 0.4170874629499336, \"continuous_test_metrics\": [{\"annualised_returns\": -0.73969610520922, \"annualised_returns_over_hodl\": -0.3059077372650755, \"annualised_returns_over_uniform_hodl\": -0.08124261951030809, \"calmar\": -1.2480821798880797, \"daily_log_sharpe\": -1.662060856681915, \"daily_returns\": 0.023861687406010777, \"fee_revenue_over_value\": 0.010142321530786688, \"jax_sharpe\": -1.0536912683983453, \"return\": -0.4184434442651451, \"returns_over_hodl\": -0.13675770716849578, \"returns_over_uniform_hodl\": -0.03354956308658086, \"sharpe\": -1.2716533928307399, \"sterling\": -2.1288058046476523, \"ulcer\": -0.14944215692715596}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -0.03505335154884115, \"optuna_trial_number\": 286, \"price_ratio\": 3.454881508868377, \"shift_exponent\": 0.007409281846125157, \"step\": 286, \"test_objective\": [{\"annualised_returns\": -0.73969610520922, \"annualised_returns_over_hodl\": -0.3059077372650755, \"annualised_returns_over_uniform_hodl\": -0.08124261951030809, \"calmar\": -1.2480821798880797, \"daily_log_sharpe\": -1.662060856681915, \"daily_returns\": 0.023861687406010777, \"fee_revenue_over_value\": 0.010142321530786688, \"jax_sharpe\": -1.0536912683983453, \"return\": -0.4184434442651451, \"returns_over_hodl\": -0.13675770716849578, \"returns_over_uniform_hodl\": -0.03354956308658086, \"sharpe\": -1.2716533928307399, \"sterling\": -2.1288058046476523, \"ulcer\": -0.14944215692715596}], \"train_objective\": [{\"annualised_returns\": -0.42067362397316865, \"annualised_returns_over_hodl\": -0.2704321788761308, \"annualised_returns_over_uniform_hodl\": -0.2704321788761309, \"calmar\": -0.5586858650893805, \"daily_log_sharpe\": -0.42768251563344534, \"daily_returns\": 0.00820598308146838, \"fee_revenue_over_value\": 0.0032536912498323475, \"jax_sharpe\": 0.11407582902644386, \"return\": -0.2820971678405608, \"returns_over_hodl\": -0.1742202283630735, \"returns_over_uniform_hodl\": -0.1742202283630736, \"sharpe\": 0.1279972310319929, \"sterling\": -1.28696817117921, \"ulcer\": -0.17623273242159895}], \"train_return\": -0.2820971678405608, \"train_returns_over_hodl\": -0.1742202283630735, \"train_sharpe\": 0.11407582902644385, \"validation_return\": -0.22872716612187705, \"validation_returns_over_hodl\": -0.03505335154884115, \"validation_sharpe\": -1.7760349404236557}, {\"centeredness_margin\": 0.365602338214404, \"continuous_test_metrics\": [{\"annualised_returns\": -0.48773911760364375, \"annualised_returns_over_hodl\": -0.006271008644161524, \"annualised_returns_over_uniform_hodl\": 0.8080538780109132, \"calmar\": -0.8413815331113574, \"daily_log_sharpe\": -0.7100220216933838, \"daily_returns\": 0.031016042751696497, \"fee_revenue_over_value\": 2.1266329599591613e-07, \"jax_sharpe\": -0.036952827461307076, \"return\": -0.23616225451059303, \"returns_over_hodl\": -0.0025303179134436027, \"returns_over_uniform_hodl\": 0.2693715092359239, \"sharpe\": -0.22461029460535117, \"sterling\": -1.2557069701120884, \"ulcer\": -0.1764103165143109}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -7.694977988137452e-11, \"optuna_trial_number\": 287, \"price_ratio\": 2.5807949094624507, \"shift_exponent\": 0.0019040598978260013, \"step\": 287, \"test_objective\": [{\"annualised_returns\": -0.48773911760364375, \"annualised_returns_over_hodl\": -0.006271008644161524, \"annualised_returns_over_uniform_hodl\": 0.8080538780109132, \"calmar\": -0.8413815331113574, \"daily_log_sharpe\": -0.7100220216933838, \"daily_returns\": 0.031016042751696497, \"fee_revenue_over_value\": 2.1266329599591613e-07, \"jax_sharpe\": -0.036952827461307076, \"return\": -0.23616225451059303, \"returns_over_hodl\": -0.0025303179134436027, \"returns_over_uniform_hodl\": 0.2693715092359239, \"sharpe\": -0.22461029460535117, \"sterling\": -1.2557069701120884, \"ulcer\": -0.1764103165143109}], \"train_objective\": [{\"annualised_returns\": -0.5494290090139808, \"annualised_returns_over_hodl\": -0.43257875049682226, \"annualised_returns_over_uniform_hodl\": -0.43257875049682226, \"calmar\": -0.712937832453783, \"daily_log_sharpe\": -0.5493001610971207, \"daily_returns\": 0.008311495871913268, \"fee_revenue_over_value\": 0.0017461880485499337, \"jax_sharpe\": 0.12800219030541438, \"return\": -0.38369988193144067, \"returns_over_hodl\": -0.29109045408329326, \"returns_over_uniform_hodl\": -0.29109045408329326, \"sharpe\": 0.09005835134295757, \"sterling\": -1.538229624033967, \"ulcer\": -0.1949518734245658}], \"train_return\": -0.38369988193144067, \"train_returns_over_hodl\": -0.29109045408329326, \"train_sharpe\": 0.1280021903054144, \"validation_return\": -0.3391427222917677, \"validation_returns_over_hodl\": -7.694977988137452e-11, \"validation_sharpe\": -2.243853205010559}, {\"centeredness_margin\": 0.2580189154749575, \"continuous_test_metrics\": [{\"annualised_returns\": -0.7042801360428135, \"annualised_returns_over_hodl\": -0.11580580168831145, \"annualised_returns_over_uniform_hodl\": 0.04376005509425185, \"calmar\": -1.184713342909766, \"daily_log_sharpe\": -1.5053773536202082, \"daily_returns\": 0.021381060100286158, \"fee_revenue_over_value\": 0.01037641133901916, \"jax_sharpe\": -0.946069111449891, \"return\": -0.3877855678300055, \"returns_over_hodl\": -0.04835992771794928, \"returns_over_uniform_hodl\": 0.017398737269415987, \"sharpe\": -1.1103809998878527, \"sterling\": -2.0730390881975533, \"ulcer\": -0.15308059521738082}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -0.026934181950427627, \"optuna_trial_number\": 288, \"price_ratio\": 40.78402482395381, \"shift_exponent\": 1.8332024532254974, \"step\": 288, \"test_objective\": [{\"annualised_returns\": -0.7042801360428135, \"annualised_returns_over_hodl\": -0.11580580168831145, \"annualised_returns_over_uniform_hodl\": 0.04376005509425185, \"calmar\": -1.184713342909766, \"daily_log_sharpe\": -1.5053773536202082, \"daily_returns\": 0.021381060100286158, \"fee_revenue_over_value\": 0.01037641133901916, \"jax_sharpe\": -0.946069111449891, \"return\": -0.3877855678300055, \"returns_over_hodl\": -0.04835992771794928, \"returns_over_uniform_hodl\": 0.017398737269415987, \"sharpe\": -1.1103809998878527, \"sterling\": -2.0730390881975533, \"ulcer\": -0.15308059521738082}], \"train_objective\": [{\"annualised_returns\": -0.36141235396805016, \"annualised_returns_over_hodl\": -0.19580219925879505, \"annualised_returns_over_uniform_hodl\": -0.19580219925879505, \"calmar\": -0.4905684290415064, \"daily_log_sharpe\": -0.3665892778059658, \"daily_returns\": 0.007978675679519084, \"fee_revenue_over_value\": 0.00604258082609343, \"jax_sharpe\": 0.12277190216618988, \"return\": -0.23836795595051796, \"returns_over_hodl\": -0.12391996906502634, \"returns_over_uniform_hodl\": -0.12391996906502634, \"sharpe\": 0.14925070747320404, \"sterling\": -1.155573042130476, \"ulcer\": -0.16879937261493225}], \"train_return\": -0.23836795595051796, \"train_returns_over_hodl\": -0.12391996906502634, \"train_sharpe\": 0.1227719021661899, \"validation_return\": -0.20663962211698317, \"validation_returns_over_hodl\": -0.026934181950427627, \"validation_sharpe\": -1.6112709440612834}, {\"centeredness_margin\": 0.30482936911470376, \"continuous_test_metrics\": [{\"annualised_returns\": -0.5471687320018479, \"annualised_returns_over_hodl\": -0.12155783388083341, \"annualised_returns_over_uniform_hodl\": 0.5982936787181115, \"calmar\": -0.9476264089050771, \"daily_log_sharpe\": -0.8481379390193781, \"daily_returns\": 0.0310160427536881, \"fee_revenue_over_value\": 3.583185207584019e-08, \"jax_sharpe\": -0.16817581443706517, \"return\": -0.2731704469065297, \"returns_over_hodl\": -0.050858055257378965, \"returns_over_uniform_hodl\": 0.20787003812752203, \"sharpe\": -0.37333747824068814, \"sterling\": -1.416497275569722, \"ulcer\": -0.17434580375687347}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -0.06393710027470348, \"optuna_trial_number\": 289, \"price_ratio\": 8.775189422934945, \"shift_exponent\": 0.0007898872563351987, \"step\": 289, \"test_objective\": [{\"annualised_returns\": -0.5471687320018479, \"annualised_returns_over_hodl\": -0.12155783388083341, \"annualised_returns_over_uniform_hodl\": 0.5982936787181115, \"calmar\": -0.9476264089050771, \"daily_log_sharpe\": -0.8481379390193781, \"daily_returns\": 0.0310160427536881, \"fee_revenue_over_value\": 3.583185207584019e-08, \"jax_sharpe\": -0.16817581443706517, \"return\": -0.2731704469065297, \"returns_over_hodl\": -0.050858055257378965, \"returns_over_uniform_hodl\": 0.20787003812752203, \"sharpe\": -0.37333747824068814, \"sterling\": -1.416497275569722, \"ulcer\": -0.17434580375687347}], \"train_objective\": [{\"annualised_returns\": -0.4282246994155188, \"annualised_returns_over_hodl\": -0.27994153644310316, \"annualised_returns_over_uniform_hodl\": -0.27994153644310316, \"calmar\": -0.5748576448934917, \"daily_log_sharpe\": -0.42600357332544636, \"daily_returns\": 0.008018352495582153, \"fee_revenue_over_value\": 0.002672158560193228, \"jax_sharpe\": 0.1127634346480092, \"return\": -0.2877928275166942, \"returns_over_hodl\": -0.18077175641949794, \"returns_over_uniform_hodl\": -0.18077175641949794, \"sharpe\": 0.1300677741903995, \"sterling\": -1.309744294478476, \"ulcer\": -0.17673265280482361}], \"train_return\": -0.2877928275166942, \"train_returns_over_hodl\": -0.18077175641949794, \"train_sharpe\": 0.1127634346480092, \"validation_return\": -0.3154529304986836, \"validation_returns_over_hodl\": -0.06393710027470345, \"validation_sharpe\": -2.0877193243669803}, {\"centeredness_margin\": 0.3484324941991378, \"continuous_test_metrics\": [{\"annualised_returns\": -0.5001508073238803, \"annualised_returns_over_hodl\": -0.030348302850466857, \"annualised_returns_over_uniform_hodl\": 0.7642461142278132, \"calmar\": -0.8627925396958237, \"daily_log_sharpe\": -0.7344222632479971, \"daily_returns\": 0.031016042755103914, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": -0.05334232966240343, \"return\": -0.24367044371106128, \"returns_over_hodl\": -0.012335006384712632, \"returns_over_uniform_hodl\": 0.25689414540661915, \"sharpe\": -0.2500712945780023, \"sterling\": -1.287661498571934, \"ulcer\": -0.17641031652134864}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -0.043481282385658626, \"optuna_trial_number\": 290, \"price_ratio\": 6.9417345945978, \"shift_exponent\": 0.0003670045429705073, \"step\": 290, \"test_objective\": [{\"annualised_returns\": -0.5001508073238803, \"annualised_returns_over_hodl\": -0.030348302850466857, \"annualised_returns_over_uniform_hodl\": 0.7642461142278132, \"calmar\": -0.8627925396958237, \"daily_log_sharpe\": -0.7344222632479971, \"daily_returns\": 0.031016042755103914, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": -0.05334232966240343, \"return\": -0.24367044371106128, \"returns_over_hodl\": -0.012335006384712632, \"returns_over_uniform_hodl\": 0.25689414540661915, \"sharpe\": -0.2500712945780023, \"sterling\": -1.287661498571934, \"ulcer\": -0.17641031652134864}], \"train_objective\": [{\"annualised_returns\": -0.453229104660948, \"annualised_returns_over_hodl\": -0.3114305385122257, \"annualised_returns_over_uniform_hodl\": -0.3114305385122256, \"calmar\": -0.6065116173278132, \"daily_log_sharpe\": -0.44778905490288756, \"daily_returns\": 0.008106593197241324, \"fee_revenue_over_value\": 0.0020955695253119548, \"jax_sharpe\": 0.1559405429329541, \"return\": -0.3068678482166939, \"returns_over_hodl\": -0.20271311886019816, \"returns_over_uniform_hodl\": -0.20271311886019805, \"sharpe\": 0.12407386292069374, \"sterling\": -1.3641708648422226, \"ulcer\": -0.17984430916249713}], \"train_return\": -0.3068678482166939, \"train_returns_over_hodl\": -0.20271311886019816, \"train_sharpe\": 0.15594054293295412, \"validation_return\": -0.33519504391201904, \"validation_returns_over_hodl\": -0.043481282385658626, \"validation_sharpe\": -2.2088526402827133}, {\"centeredness_margin\": 0.705018659355038, \"continuous_test_metrics\": [{\"annualised_returns\": -0.4890608768195741, \"annualised_returns_over_hodl\": -0.008835074271363319, \"annualised_returns_over_uniform_hodl\": 0.8033886537896529, \"calmar\": -0.8436616572965581, \"daily_log_sharpe\": -0.7088411834259886, \"daily_returns\": 0.03101604275498798, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": -0.026953551184391068, \"return\": -0.23695661870691198, \"returns_over_hodl\": -0.0035676511013443823, \"returns_over_uniform_hodl\": 0.26805140783386716, \"sharpe\": -0.22216144783406885, \"sterling\": -1.2591099064291922, \"ulcer\": -0.17641031652110803}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -0.03736115838131426, \"optuna_trial_number\": 291, \"price_ratio\": 7.559490630434806, \"shift_exponent\": 1.3072584179631861e-05, \"step\": 291, \"test_objective\": [{\"annualised_returns\": -0.4890608768195741, \"annualised_returns_over_hodl\": -0.008835074271363319, \"annualised_returns_over_uniform_hodl\": 0.8033886537896529, \"calmar\": -0.8436616572965581, \"daily_log_sharpe\": -0.7088411834259886, \"daily_returns\": 0.03101604275498798, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": -0.026953551184391068, \"return\": -0.23695661870691198, \"returns_over_hodl\": -0.0035676511013443823, \"returns_over_uniform_hodl\": 0.26805140783386716, \"sharpe\": -0.22216144783406885, \"sterling\": -1.2591099064291922, \"ulcer\": -0.17641031652110803}], \"train_objective\": [{\"annualised_returns\": -0.45691012927194563, \"annualised_returns_over_hodl\": -0.31606619332802577, \"annualised_returns_over_uniform_hodl\": -0.31606619332802577, \"calmar\": -0.6116650003756795, \"daily_log_sharpe\": -0.45334208252506336, \"daily_returns\": 0.008085532150221486, \"fee_revenue_over_value\": 0.0002608002466614169, \"jax_sharpe\": 0.10176184775700149, \"return\": -0.30970466101295346, \"returns_over_hodl\": -0.2059762103513202, \"returns_over_uniform_hodl\": -0.2059762103513202, \"sharpe\": 0.11761127695584822, \"sterling\": -1.3753167745614128, \"ulcer\": -0.17997394591734875}], \"train_return\": -0.30970466101295346, \"train_returns_over_hodl\": -0.2059762103513202, \"train_sharpe\": 0.10176184775700148, \"validation_return\": -0.3362201724189233, \"validation_returns_over_hodl\": -0.03736115838131426, \"validation_sharpe\": -2.217844140738167}, {\"centeredness_margin\": 0.7515657776433486, \"continuous_test_metrics\": [{\"annualised_returns\": -0.4812902307938811, \"annualised_returns_over_hodl\": 0.006239112559366822, \"annualised_returns_over_uniform_hodl\": 0.8308155902671752, \"calmar\": -0.8302567669550899, \"daily_log_sharpe\": -0.6909911203203797, \"daily_returns\": 0.031016042754464054, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": -0.008108376297373215, \"return\": -0.2323039882985305, \"returns_over_hodl\": 0.002508060406872792, \"returns_over_uniform_hodl\": 0.275783307073306, \"sharpe\": -0.2023925870914849, \"sterling\": -1.2391039833571234, \"ulcer\": -0.17641031652001354}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -0.012891983456667755, \"optuna_trial_number\": 292, \"price_ratio\": 6.315256513204293, \"shift_exponent\": 0.000103719200364898, \"step\": 292, \"test_objective\": [{\"annualised_returns\": -0.4812902307938811, \"annualised_returns_over_hodl\": 0.006239112559366822, \"annualised_returns_over_uniform_hodl\": 0.8308155902671752, \"calmar\": -0.8302567669550899, \"daily_log_sharpe\": -0.6909911203203797, \"daily_returns\": 0.031016042754464054, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": -0.008108376297373215, \"return\": -0.2323039882985305, \"returns_over_hodl\": 0.002508060406872792, \"returns_over_uniform_hodl\": 0.275783307073306, \"sharpe\": -0.2023925870914849, \"sterling\": -1.2391039833571234, \"ulcer\": -0.17641031652001354}], \"train_objective\": [{\"annualised_returns\": -0.4715828121846003, \"annualised_returns_over_hodl\": -0.33454406304982665, \"annualised_returns_over_uniform_hodl\": -0.33454406304982665, \"calmar\": -0.6296512480349803, \"daily_log_sharpe\": -0.4656862426561373, \"daily_returns\": 0.008066004807354865, \"fee_revenue_over_value\": 0.00013782844717788705, \"jax_sharpe\": 0.10291880111341879, \"return\": -0.3210881745966886, \"returns_over_hodl\": -0.21907028774801574, \"returns_over_uniform_hodl\": -0.21907028774801574, \"sharpe\": 0.11594364002765743, \"sterling\": -1.4045577387739914, \"ulcer\": -0.18202693130264094}], \"train_return\": -0.3210881745966886, \"train_returns_over_hodl\": -0.21907028774801574, \"train_sharpe\": 0.10291880111341878, \"validation_return\": -0.33883122444893254, \"validation_returns_over_hodl\": -0.012891983456667755, \"validation_sharpe\": -2.241043308567328}, {\"centeredness_margin\": 0.5479043531055927, \"continuous_test_metrics\": [{\"annualised_returns\": -0.49678918396420846, \"annualised_returns_over_hodl\": -0.023827128382055363, \"annualised_returns_over_uniform_hodl\": 0.7761111547974426, \"calmar\": -0.856993511157237, \"daily_log_sharpe\": -0.7264699798943649, \"daily_returns\": 0.031016042755231194, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": -0.04471925179419966, \"return\": -0.24162600751689922, \"returns_over_hodl\": -0.00966524683044312, \"returns_over_uniform_hodl\": 0.2602916589134403, \"sharpe\": -0.24138025590795112, \"sterling\": -1.2790068273305368, \"ulcer\": -0.17641031652160724}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -0.046585819943568474, \"optuna_trial_number\": 293, \"price_ratio\": 7.8090749467606955, \"shift_exponent\": 0.00010670100475423315, \"step\": 293, \"test_objective\": [{\"annualised_returns\": -0.49678918396420846, \"annualised_returns_over_hodl\": -0.023827128382055363, \"annualised_returns_over_uniform_hodl\": 0.7761111547974426, \"calmar\": -0.856993511157237, \"daily_log_sharpe\": -0.7264699798943649, \"daily_returns\": 0.031016042755231194, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": -0.04471925179419966, \"return\": -0.24162600751689922, \"returns_over_hodl\": -0.00966524683044312, \"returns_over_uniform_hodl\": 0.2602916589134403, \"sharpe\": -0.24138025590795112, \"sterling\": -1.2790068273305368, \"ulcer\": -0.17641031652160724}], \"train_objective\": [{\"annualised_returns\": -0.4494924231832381, \"annualised_returns_over_hodl\": -0.30672479287947296, \"annualised_returns_over_uniform_hodl\": -0.30672479287947296, \"calmar\": -0.6023699477915192, \"daily_log_sharpe\": -0.4455995529283137, \"daily_returns\": 0.008071162072213619, \"fee_revenue_over_value\": 0.0018284717996671124, \"jax_sharpe\": 0.10552989810625045, \"return\": -0.3039958068862145, \"returns_over_hodl\": -0.1994095051568212, \"returns_over_uniform_hodl\": -0.1994095051568212, \"sharpe\": 0.12189934095376424, \"sterling\": -1.3583732667169843, \"ulcer\": -0.1791314473877524}], \"train_return\": -0.3039958068862145, \"train_returns_over_hodl\": -0.1994095051568212, \"train_sharpe\": 0.10552989810625045, \"validation_return\": -0.3343137667879119, \"validation_returns_over_hodl\": -0.046585819943568474, \"validation_sharpe\": -2.2010134769603527}, {\"centeredness_margin\": 0.09067810854559288, \"continuous_test_metrics\": [{\"annualised_returns\": -0.47776692675176213, \"annualised_returns_over_hodl\": 0.013073929542488205, \"annualised_returns_over_uniform_hodl\": 0.843251291988073, \"calmar\": -0.8241788293247853, \"daily_log_sharpe\": -0.6799906566823699, \"daily_returns\": 0.031016042754124538, \"fee_revenue_over_value\": 4.000133580275137e-08, \"jax_sharpe\": 0.00932042738866626, \"return\": -0.23020814531061662, \"returns_over_hodl\": 0.005244950348985977, \"returns_over_uniform_hodl\": 0.2792662501360179, \"sharpe\": -0.18920583454496248, \"sterling\": -1.2300330585271364, \"ulcer\": -0.1764103165193224}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -6.340816760541657e-11, \"optuna_trial_number\": 294, \"price_ratio\": 5.28227079626578, \"shift_exponent\": 1.3939601696924552e-05, \"step\": 294, \"test_objective\": [{\"annualised_returns\": -0.47776692675176213, \"annualised_returns_over_hodl\": 0.013073929542488205, \"annualised_returns_over_uniform_hodl\": 0.843251291988073, \"calmar\": -0.8241788293247853, \"daily_log_sharpe\": -0.6799906566823699, \"daily_returns\": 0.031016042754124538, \"fee_revenue_over_value\": 4.000133580275137e-08, \"jax_sharpe\": 0.00932042738866626, \"return\": -0.23020814531061662, \"returns_over_hodl\": 0.005244950348985977, \"returns_over_uniform_hodl\": 0.2792662501360179, \"sharpe\": -0.18920583454496248, \"sterling\": -1.2300330585271364, \"ulcer\": -0.1764103165193224}], \"train_objective\": [{\"annualised_returns\": -0.48477875674801396, \"annualised_returns_over_hodl\": -0.3511622197939196, \"annualised_returns_over_uniform_hodl\": -0.3511622197939196, \"calmar\": -0.6467541127858325, \"daily_log_sharpe\": -0.47245301014609364, \"daily_returns\": 0.008124322567346905, \"fee_revenue_over_value\": 0.004402312301056231, \"jax_sharpe\": 0.11684744676402431, \"return\": -0.3314325118343707, \"returns_over_hodl\": -0.23096903512608913, \"returns_over_uniform_hodl\": -0.23096903512608913, \"sharpe\": 0.12426219917175854, \"sterling\": -1.4230930403751616, \"ulcer\": -0.18484853861122844}], \"train_return\": -0.3314325118343707, \"train_returns_over_hodl\": -0.23096903512608913, \"train_sharpe\": 0.11684744676402428, \"validation_return\": -0.33914272230988296, \"validation_returns_over_hodl\": -6.340816760541657e-11, \"validation_sharpe\": -2.2438532050234903}, {\"centeredness_margin\": 0.953576262768314, \"continuous_test_metrics\": [{\"annualised_returns\": -0.4821113265234297, \"annualised_returns_over_hodl\": 0.004646278395696646, \"annualised_returns_over_uniform_hodl\": 0.8279174862560237, \"calmar\": -0.8316732126185717, \"daily_log_sharpe\": -0.692802846056417, \"daily_returns\": 0.031016042754612352, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": -0.010106743363653737, \"return\": -0.23279363912681617, \"returns_over_hodl\": 0.0018686420738900367, \"returns_over_uniform_hodl\": 0.2749695886958494, \"sharpe\": -0.20440808948378097, \"sterling\": -1.2412179359694377, \"ulcer\": -0.17641031652033268}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -0.0202556354113842, \"optuna_trial_number\": 295, \"price_ratio\": 6.804840160602159, \"shift_exponent\": 1.1918513441615355e-05, \"step\": 295, \"test_objective\": [{\"annualised_returns\": -0.4821113265234297, \"annualised_returns_over_hodl\": 0.004646278395696646, \"annualised_returns_over_uniform_hodl\": 0.8279174862560237, \"calmar\": -0.8316732126185717, \"daily_log_sharpe\": -0.692802846056417, \"daily_returns\": 0.031016042754612352, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": -0.010106743363653737, \"return\": -0.23279363912681617, \"returns_over_hodl\": 0.0018686420738900367, \"returns_over_uniform_hodl\": 0.2749695886958494, \"sharpe\": -0.20440808948378097, \"sterling\": -1.2412179359694377, \"ulcer\": -0.17641031652033268}], \"train_objective\": [{\"annualised_returns\": -0.4670857343331043, \"annualised_returns_over_hodl\": -0.3288807212354221, \"annualised_returns_over_uniform_hodl\": -0.328880721235422, \"calmar\": -0.6243324049576047, \"daily_log_sharpe\": -0.46221173119463194, \"daily_returns\": 0.008036150222925698, \"fee_revenue_over_value\": 3.183103829475238e-05, \"jax_sharpe\": 0.14891305164187355, \"return\": -0.3175861549627996, \"returns_over_hodl\": -0.21504203093665597, \"returns_over_uniform_hodl\": -0.21504203093665586, \"sharpe\": 0.1157217777907697, \"sterling\": -1.3961721369117708, \"ulcer\": -0.18134621647165095}], \"train_return\": -0.3175861549627996, \"train_returns_over_hodl\": -0.21504203093665597, \"train_sharpe\": 0.14891305164187355, \"validation_return\": -0.33844391501995763, \"validation_returns_over_hodl\": -0.0202556354113842, \"validation_sharpe\": -2.237494892736116}, {\"centeredness_margin\": 0.915565082791383, \"continuous_test_metrics\": [{\"annualised_returns\": -0.4799763189222067, \"annualised_returns_over_hodl\": 0.008787955080826526, \"annualised_returns_over_uniform_hodl\": 0.8354531168411978, \"calmar\": -0.8279901801401175, \"daily_log_sharpe\": -0.6875359379967307, \"daily_returns\": 0.031016042754447075, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": -0.0037479037650101224, \"return\": -0.23152141448561858, \"returns_over_hodl\": 0.0035299968811226545, \"returns_over_uniform_hodl\": 0.27708381481575595, \"sharpe\": -0.19842849203296867, \"sterling\": -1.2357212506140423, \"ulcer\": -0.17641031651999145}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -0.010724241584595728, \"optuna_trial_number\": 296, \"price_ratio\": 6.499955110298099, \"shift_exponent\": 1.1263400786222663e-05, \"step\": 296, \"test_objective\": [{\"annualised_returns\": -0.4799763189222067, \"annualised_returns_over_hodl\": 0.008787955080826526, \"annualised_returns_over_uniform_hodl\": 0.8354531168411978, \"calmar\": -0.8279901801401175, \"daily_log_sharpe\": -0.6875359379967307, \"daily_returns\": 0.031016042754447075, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": -0.0037479037650101224, \"return\": -0.23152141448561858, \"returns_over_hodl\": 0.0035299968811226545, \"returns_over_uniform_hodl\": 0.27708381481575595, \"sharpe\": -0.19842849203296867, \"sterling\": -1.2357212506140423, \"ulcer\": -0.17641031651999145}], \"train_objective\": [{\"annualised_returns\": -0.47169618796870794, \"annualised_returns_over_hodl\": -0.33468684150287553, \"annualised_returns_over_uniform_hodl\": -0.33468684150287586, \"calmar\": -0.6300403601733094, \"daily_log_sharpe\": -0.46616772326910816, \"daily_returns\": 0.008071810583469122, \"fee_revenue_over_value\": 4.207294242730805e-05, \"jax_sharpe\": 0.1504045058202208, \"return\": -0.32117661497222927, \"returns_over_hodl\": -0.21917201777309125, \"returns_over_uniform_hodl\": -0.21917201777309148, \"sharpe\": 0.11500075789497384, \"sterling\": -1.4054094637126378, \"ulcer\": -0.18199188737349856}], \"train_return\": -0.32117661497222927, \"train_returns_over_hodl\": -0.21917201777309125, \"train_sharpe\": 0.1504045058202208, \"validation_return\": -0.33887890933807596, \"validation_returns_over_hodl\": -0.010724241584595728, \"validation_sharpe\": -2.2414770867561207}, {\"centeredness_margin\": 0.25496617130854404, \"continuous_test_metrics\": [{\"annualised_returns\": -0.5072913413185413, \"annualised_returns_over_hodl\": -0.044200142528801334, \"annualised_returns_over_uniform_hodl\": 0.7390431939507074, \"calmar\": -0.8752160706263972, \"daily_log_sharpe\": -0.75026932208214, \"daily_returns\": 0.03101604275222184, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": -0.06800769596650896, \"return\": -0.24804051110452197, \"returns_over_hodl\": -0.018041728450483574, \"returns_over_uniform_hodl\": 0.24963181898264075, \"sharpe\": -0.2670078618296691, \"sterling\": -1.3062116505610526, \"ulcer\": -0.17636154401738555}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -0.0578188813976559, \"optuna_trial_number\": 297, \"price_ratio\": 9.11822145734875, \"shift_exponent\": 5.8592352995673325e-05, \"step\": 297, \"test_objective\": [{\"annualised_returns\": -0.5072913413185413, \"annualised_returns_over_hodl\": -0.044200142528801334, \"annualised_returns_over_uniform_hodl\": 0.7390431939507074, \"calmar\": -0.8752160706263972, \"daily_log_sharpe\": -0.75026932208214, \"daily_returns\": 0.03101604275222184, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": -0.06800769596650896, \"return\": -0.24804051110452197, \"returns_over_hodl\": -0.018041728450483574, \"returns_over_uniform_hodl\": 0.24963181898264075, \"sharpe\": -0.2670078618296691, \"sterling\": -1.3062116505610526, \"ulcer\": -0.17636154401738555}], \"train_objective\": [{\"annualised_returns\": -0.43632335010718504, \"annualised_returns_over_hodl\": -0.29014047642523244, \"annualised_returns_over_uniform_hodl\": -0.29014047642523244, \"calmar\": -0.5860773151536233, \"daily_log_sharpe\": -0.43243931925714457, \"daily_returns\": 0.008069344111894975, \"fee_revenue_over_value\": 0.003139270051261631, \"jax_sharpe\": 0.14999205951086145, \"return\": -0.29393446014310187, \"returns_over_hodl\": -0.1878362723407645, \"returns_over_uniform_hodl\": -0.1878362723407645, \"sharpe\": 0.12759242634665488, \"sterling\": -1.3285292071662793, \"ulcer\": -0.17765918303158368}], \"train_return\": -0.29393446014310187, \"train_returns_over_hodl\": -0.1878362723407645, \"train_sharpe\": 0.14999205951086145, \"validation_return\": -0.32860395637374096, \"validation_returns_over_hodl\": -0.0578188813976559, \"validation_sharpe\": -2.15420578908149}, {\"centeredness_margin\": 0.9515658231377965, \"continuous_test_metrics\": [{\"annualised_returns\": -0.47707889101032364, \"annualised_returns_over_hodl\": 0.014408642168773467, \"annualised_returns_over_uniform_hodl\": 0.8456797532141149, \"calmar\": -0.8229919211853477, \"daily_log_sharpe\": -0.6787745671921386, \"daily_returns\": 0.031016042753917582, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": 0.00988909241716519, \"return\": -0.22979985265026992, \"returns_over_hodl\": 0.005778125823910463, \"returns_over_uniform_hodl\": 0.2799447647466595, \"sharpe\": -0.18795506596188427, \"sterling\": -1.2282616756747788, \"ulcer\": -0.1764103165188942}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -6.221234638559281e-11, \"optuna_trial_number\": 298, \"price_ratio\": 5.411011431254992, \"shift_exponent\": 1.0836580603540571e-05, \"step\": 298, \"test_objective\": [{\"annualised_returns\": -0.47707889101032364, \"annualised_returns_over_hodl\": 0.014408642168773467, \"annualised_returns_over_uniform_hodl\": 0.8456797532141149, \"calmar\": -0.8229919211853477, \"daily_log_sharpe\": -0.6787745671921386, \"daily_returns\": 0.031016042753917582, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": 0.00988909241716519, \"return\": -0.22979985265026992, \"returns_over_hodl\": 0.005778125823910463, \"returns_over_uniform_hodl\": 0.2799447647466595, \"sharpe\": -0.18795506596188427, \"sterling\": -1.2282616756747788, \"ulcer\": -0.1764103165188942}], \"train_objective\": [{\"annualised_returns\": -0.4881475012985118, \"annualised_returns_over_hodl\": -0.35540460840823607, \"annualised_returns_over_uniform_hodl\": -0.3554046084082362, \"calmar\": -0.6505087481702377, \"daily_log_sharpe\": -0.4794258493360141, \"daily_returns\": 0.008126139075371915, \"fee_revenue_over_value\": 3.086436063065807e-05, \"jax_sharpe\": 0.10825527249574803, \"return\": -0.334089895453913, \"returns_over_hodl\": -0.23402573519773595, \"returns_over_uniform_hodl\": -0.23402573519773606, \"sharpe\": 0.11572547494822452, \"sterling\": -1.4341926433085812, \"ulcer\": -0.18478594537667403}], \"train_return\": -0.334089895453913, \"train_returns_over_hodl\": -0.23402573519773595, \"train_sharpe\": 0.10825527249574801, \"validation_return\": -0.339142722307448, \"validation_returns_over_hodl\": -6.221234638559281e-11, \"validation_sharpe\": -2.243853205013407}, {\"centeredness_margin\": 0.2955501706773337, \"continuous_test_metrics\": [{\"annualised_returns\": -0.47897275365734115, \"annualised_returns_over_hodl\": 0.010734759824504003, \"annualised_returns_over_uniform_hodl\": 0.8389952574405135, \"calmar\": -0.826258962221277, \"daily_log_sharpe\": -0.6816876054405954, \"daily_returns\": 0.031016042753618866, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": 0.008551791398837485, \"return\": -0.23092448011674727, \"returns_over_hodl\": 0.004309513145020594, \"returns_over_uniform_hodl\": 0.2780758206248477, \"sharpe\": -0.19089990978805146, \"sterling\": -1.233137520988546, \"ulcer\": -0.176410316518275}], \"hessian_trace\": 0, \"iterations_since_improvement\": 0, \"local_learning_rate\": 0, \"objective\": -6.606637459327658e-11, \"optuna_trial_number\": 299, \"price_ratio\": 4.900049539715327, \"shift_exponent\": 9.627679127672071e-05, \"step\": 299, \"test_objective\": [{\"annualised_returns\": -0.47897275365734115, \"annualised_returns_over_hodl\": 0.010734759824504003, \"annualised_returns_over_uniform_hodl\": 0.8389952574405135, \"calmar\": -0.826258962221277, \"daily_log_sharpe\": -0.6816876054405954, \"daily_returns\": 0.031016042753618866, \"fee_revenue_over_value\": 0.0, \"jax_sharpe\": 0.008551791398837485, \"return\": -0.23092448011674727, \"returns_over_hodl\": 0.004309513145020594, \"returns_over_uniform_hodl\": 0.2780758206248477, \"sharpe\": -0.19089990978805146, \"sterling\": -1.233137520988546, \"ulcer\": -0.176410316518275}], \"train_objective\": [{\"annualised_returns\": -0.49642512022495666, \"annualised_returns_over_hodl\": -0.3658289299205375, \"annualised_returns_over_uniform_hodl\": -0.3658289299205375, \"calmar\": -0.6604366600430333, \"daily_log_sharpe\": -0.487067622632687, \"daily_returns\": 0.008143886221948015, \"fee_revenue_over_value\": 0.0019837431639639375, \"jax_sharpe\": 0.11314822518702625, \"return\": -0.3406489258162082, \"returns_over_hodl\": -0.2415703698643017, \"returns_over_uniform_hodl\": -0.2415703698643017, \"sharpe\": 0.11578506480177461, \"sterling\": -1.4478521812448872, \"ulcer\": -0.1862405062188718}], \"train_return\": -0.3406489258162082, \"train_returns_over_hodl\": -0.2415703698643017, \"train_sharpe\": 0.11314822518702625, \"validation_return\": -0.33914272230669773, \"validation_returns_over_hodl\": -6.606637459327658e-11, \"validation_sharpe\": -2.243853205026703}]" \ No newline at end of file diff --git a/scripts/benchmark_reclamm_interpolation.py b/scripts/benchmark_reclamm_interpolation.py new file mode 100644 index 0000000..f462bbf --- /dev/null +++ b/scripts/benchmark_reclamm_interpolation.py @@ -0,0 +1,648 @@ +"""Benchmark reClAMM range shift interpolation: current vs optimal midpoint. + +Compares total arb loss during a range shift under different interpolation methods: + Geometric VB -- exponential decay of overvalued virtual (what contracts do) + Linear VB -- uniform steps in VB + Linear Z -- uniform steps in Z = sqrt(P)*VA - VB/sqrt(P) (optimal, from note) + Optimal 2-step -- exact midpoint via quadratic formula (Section 5 of note) + Brute-force optimal -- JAX gradient-optimised Z-target sequence + +Key result: per-step loss ~ (DeltaZ)^2 / (4X). Equal Z-increments minimise +total loss, analogous to TFMM optimal intermediate for G3M weight changes. + +Usage: + cd + source ~/miniconda3/etc/profile.d/conda.sh && conda activate qsim-reclamm + python scripts/benchmark_reclamm_interpolation.py +""" + +import numpy as np +import matplotlib.pyplot as plt + +import jax +import jax.numpy as jnp +from scipy.optimize import minimize as scipy_minimize + +jax.config.update("jax_enable_x64", True) + + +# ── Core reClAMM mechanics ───────────────────────────────────────────────── + + +def compute_VA_from_VB(RA, RB, VB, Q): + """Contract rule (eq 15): VA = RA*(VB + RB) / ((Q-1)*VB - RB).""" + return RA * (VB + RB) / ((Q - 1) * VB - RB) + + +def compute_Z(VA, VB, P): + """Z = sqrt(P)*VA - VB/sqrt(P) (eq 12).""" + sqP = np.sqrt(P) + return sqP * VA - VB / sqP + + +def pool_value(RA, RB, P): + """Real pool value: P*RA + RB (eq 3).""" + return P * RA + RB + + +def micro_step(RA, RB, VA_new, VB_new, P): + """Virtual-balance update then arb to equilibrium Y/X = P. + + Returns (RA_new, RB_new, arb_loss). + """ + val_before = pool_value(RA, RB, P) + X = RA + VA_new + Y = RB + VB_new + L = X * Y + X_eq = np.sqrt(L / P) + Y_eq = P * X_eq + RA_new = X_eq - VA_new + RB_new = Y_eq - VB_new + return RA_new, RB_new, val_before - pool_value(RA_new, RB_new, P) + + +def solve_VB_for_Z(RA, RB, Z_star, Q, P): + """Solve quadratic for VB achieving Z(VB) = Z_star. + + Derived by substituting VA = RA*(VB+RB)/((Q-1)*VB-RB) into + Z = sqrt(P)*VA - VB/sqrt(P), then collecting terms in VB. + + NOTE: The research note (eq 28) has a sign error: the RB/sqrt(P) + term in b should be positive, not negative. Re-derived here from + scratch. + + Returns the physically valid root (VB > RB/(Q-1), positive). + Raises ValueError if no valid root exists. + """ + sqP = np.sqrt(P) + a = -(Q - 1) / sqP + b = sqP * RA + RB / sqP - (Q - 1) * Z_star # +RB/sqP, not minus + c = sqP * RA * RB + Z_star * RB + disc = b * b - 4 * a * c + if disc < -1e-6: + raise ValueError(f"negative discriminant: {disc:.4e}") + disc = max(disc, 0.0) + sd = np.sqrt(disc) + r1, r2 = (-b + sd) / (2 * a), (-b - sd) / (2 * a) + floor = RB / (Q - 1) + 1e-12 + ok = [r for r in (r1, r2) if r > floor] + if not ok: + raise ValueError(f"no valid root: r1={r1:.4f}, r2={r2:.4f}, floor={floor:.4f}") + return min(ok) + + +# ── Interpolation methods ────────────────────────────────────────────────── + + +def run_shift(RA, RB, VA_stale, VB_start, VB_end, Q, P, N, schedule): + """Execute N-step range shift (B overvalued, VB decreasing). + + schedule: "geometric" | "linear_VB" | "linear_Z" + + VA_stale: the current (possibly stale) VA -- used only for Z_start + in the linear_Z schedule. All micro-steps compute VA from the + contract rule with current reserves. + """ + # For linear_Z, precompute Z endpoints using contract-rule VA + if schedule == "linear_Z": + VA_start_cr = compute_VA_from_VB(RA, RB, VB_start, Q) + Z0 = compute_Z(VA_start_cr, VB_start, P) + VA_end_approx = compute_VA_from_VB(RA, RB, VB_end, Q) + Z_end = compute_Z(VA_end_approx, VB_end, P) + + total_loss = 0.0 + RA_c, RB_c = RA, RB + + for i in range(1, N + 1): + frac = i / N + if schedule == "geometric": + VB_i = VB_start * (VB_end / VB_start) ** frac + elif schedule == "linear_VB": + VB_i = VB_start + frac * (VB_end - VB_start) + elif schedule == "linear_Z": + Z_i = Z0 + frac * (Z_end - Z0) + VB_i = solve_VB_for_Z(RA_c, RB_c, Z_i, Q, P) + else: + raise ValueError(schedule) + + VA_i = compute_VA_from_VB(RA_c, RB_c, VB_i, Q) + RA_c, RB_c, loss = micro_step(RA_c, RB_c, VA_i, VB_i, P) + total_loss += loss + + return total_loss, RA_c, RB_c + + +def run_shift_optimal_2step(RA, RB, VA_stale, VB_start, VB_end, Q, P): + """Exact 2-step optimal midpoint (Section 5 of the note). + + Computes Z* = (Z_start + Z_end) / 2, solves quadratic for VB_mid. + """ + VA_start_cr = compute_VA_from_VB(RA, RB, VB_start, Q) + Z0 = compute_Z(VA_start_cr, VB_start, P) + VA_end_approx = compute_VA_from_VB(RA, RB, VB_end, Q) + Z2 = compute_Z(VA_end_approx, VB_end, P) + Z_star = (Z0 + Z2) / 2.0 + + # Step 1: jump to Z-midpoint + VB_mid = solve_VB_for_Z(RA, RB, Z_star, Q, P) + VA_mid = compute_VA_from_VB(RA, RB, VB_mid, Q) + RA1, RB1, loss1 = micro_step(RA, RB, VA_mid, VB_mid, P) + + # Step 2: jump to endpoint + VA_end = compute_VA_from_VB(RA1, RB1, VB_end, Q) + RA2, RB2, loss2 = micro_step(RA1, RB1, VA_end, VB_end, P) + + return loss1 + loss2, RA2, RB2 + + +# ── Scenario setup ───────────────────────────────────────────────────────── + + +def setup_centered_pool(P, price_ratio, R_scale=10000.0): + """Centered pool at price P with contract-rule-consistent virtuals. + + Returns (RA, RB, VA, VB, Q). + """ + Q = np.sqrt(price_ratio) + q4 = price_ratio ** 0.25 + + RA = R_scale + RB = P * R_scale + VA = RA / (q4 - 1) + VB = RB / (q4 - 1) + + return RA, RB, VA, VB, Q + + +def setup_decentered_pool(P_init, P_final, price_ratio, R_scale=10000.0): + """Centered pool at P_init, arb to P_final, then refresh virtuals. + + The refresh applies the contract rule to get consistent (VA, VB) at + the post-arb reserves, then arbs once more. This gives a decentered + but fully consistent state (equilibrium + contract rule). + + Returns (RA, RB, VA, VB, Q). + """ + Q = np.sqrt(price_ratio) + q4 = price_ratio ** 0.25 + + RA0 = R_scale + RB0 = P_init * R_scale + VA0 = RA0 / (q4 - 1) + VB0 = RB0 / (q4 - 1) + + # Arb to P_final (L preserved, virtuals stale) + X0 = RA0 + VA0 + Y0 = RB0 + VB0 + L = X0 * Y0 + X_new = np.sqrt(L / P_final) + Y_new = np.sqrt(L * P_final) + RA = X_new - VA0 + RB = Y_new - VB0 + + # Refresh: apply contract rule for current VB, then arb + VB = VB0 + VA = compute_VA_from_VB(RA, RB, VB, Q) + RA, RB, _ = micro_step(RA, RB, VA, VB, P_final) + + return RA, RB, VA, VB, Q + + +# ── JAX-differentiable versions for brute-force optimisation ────────────── + + +def _compute_VA_from_VB_jax(RA, RB, VB, Q): + return RA * (VB + RB) / ((Q - 1) * VB - RB) + + +def _compute_Z_jax(VA, VB, P): + sqP = jnp.sqrt(P) + return sqP * VA - VB / sqP + + +def _pool_value_jax(RA, RB, P): + return P * RA + RB + + +def _micro_step_jax(RA, RB, VA, VB, P): + val_before = _pool_value_jax(RA, RB, P) + X = RA + VA + Y = RB + VB + L = X * Y + X_eq = jnp.sqrt(L / P) + Y_eq = P * X_eq + RA_new = X_eq - VA + RB_new = Y_eq - VB + return RA_new, RB_new, val_before - _pool_value_jax(RA_new, RB_new, P) + + +def _solve_VB_for_Z_jax(RA, RB, Z_star, Q, P): + sqP = jnp.sqrt(P) + a = -(Q - 1) / sqP + b = sqP * RA + RB / sqP - (Q - 1) * Z_star + c = sqP * RA * RB + Z_star * RB + disc = jnp.maximum(b * b - 4 * a * c, 1e-30) + sd = jnp.sqrt(disc) + r1 = (-b + sd) / (2 * a) + r2 = (-b - sd) / (2 * a) + floor = RB / (Q - 1) + 1e-8 + return jnp.where(r2 > floor, r2, r1) + + +def _z_targets_from_raw(raw_params, Z_start, Z_end): + """Map unconstrained params -> sorted Z targets via softplus gaps.""" + gaps = jax.nn.softplus(raw_params) + gaps = gaps / jnp.sum(gaps) * (Z_end - Z_start) + return Z_start + jnp.cumsum(gaps) + + +def _make_loss_fn(N): + """Build a JIT-compiled loss function for a given N (unrolled loop).""" + + def total_loss(raw_params, RA, RB, Q, P, Z_start, Z_end): + Z_all = _z_targets_from_raw(raw_params, Z_start, Z_end) + RA_c, RB_c = RA, RB + total = 0.0 + for i in range(N): + VB_i = _solve_VB_for_Z_jax(RA_c, RB_c, Z_all[i], Q, P) + VA_i = _compute_VA_from_VB_jax(RA_c, RB_c, VB_i, Q) + RA_c, RB_c, loss = _micro_step_jax(RA_c, RB_c, VA_i, VB_i, P) + total = total + loss + return total + + return jax.jit(jax.value_and_grad(total_loss)) + + +def optimise_z_targets(RA, RB, Q, P, Z_start, Z_end, N, verbose=False): + """Find the Z-target sequence minimising total arb loss. + + Returns (optimal_loss, optimal_Z_targets_array_of_length_N). + """ + loss_and_grad_fn = _make_loss_fn(N) + RA_j = jnp.float64(RA) + RB_j = jnp.float64(RB) + Q_j = jnp.float64(Q) + P_j = jnp.float64(P) + Zs_j = jnp.float64(Z_start) + Ze_j = jnp.float64(Z_end) + + def objective(x): + val, grad = loss_and_grad_fn( + jnp.array(x, dtype=jnp.float64), RA_j, RB_j, Q_j, P_j, Zs_j, Ze_j + ) + return float(val), np.array(grad, dtype=np.float64) + + x0 = np.zeros(N) # softplus(0) = ln2, uniform gaps → linear Z init + result = scipy_minimize(objective, x0, jac=True, method="L-BFGS-B") + + optimal_Z = np.array( + _z_targets_from_raw(jnp.array(result.x), Zs_j, Ze_j) + ) + if verbose: + print(f" N={N}: loss={result.fun:.6f} " + f"nit={result.nit} success={result.success}") + return result.fun, optimal_Z + + +# ── Experiments ──────────────────────────────────────────────────────────── + + +def main(): + # --- Scenario: centered pool, moderate VB decay --- + P = 2.0 # token A costs 2 units of token B + price_ratio = 4.0 # rho, so Q = sqrt(4) = 2 + R_scale = 10000.0 + decay_fraction = 0.90 # VB_end = 0.90 * VB_start (10% decay) + + RA, RB, VA, VB, Q = setup_centered_pool(P, price_ratio, R_scale) + VB_start = VB + VB_end = VB * decay_fraction + + # Diagnostics + C = min(RA * VB, RB * VA) / max(RA * VB, RB * VA) + is_above = RA * VB > RB * VA + X = RA + VA + print("=" * 72) + print(f"Scenario: centered pool at P={P}, price_ratio={price_ratio}, Q={Q:.4f}") + print(f" RA={RA:.2f} RB={RB:.2f} VA={VA:.2f} VB={VB:.2f}") + print(f" Effective X={X:.2f} Pool value = {pool_value(RA, RB, P):.2f}") + print(f" Centeredness = {C:.4f} is_above = {is_above}") + print(f" VB shift: {VB_start:.2f} -> {VB_end:.2f} ({decay_fraction:.0%})") + VB_floor = RB / (Q - 1) + print(f" VB floor (denominator > 0): {VB_floor:.2f}") + Z_start = compute_Z(VA, VB, P) + VA_end_cr = compute_VA_from_VB(RA, RB, VB_end, Q) + Z_end = compute_Z(VA_end_cr, VB_end, P) + print(f" Z_start = {Z_start:.4f} Z_end = {Z_end:.4f}") + print(f" Approx 1-step loss ~ (DeltaZ)^2/(4X) = {(Z_end-Z_start)**2/(4*X):.2f}") + print("=" * 72) + + # ── Experiment 1: Loss vs N ──────────────────────────────────────── + + N_values = [1, 2, 3, 4, 6, 8, 12, 16, 24, 32, 48, 64, 96, 128] + schedules = ["geometric", "linear_VB", "linear_Z"] + results = {s: [] for s in schedules} + + for N in N_values: + for sched in schedules: + try: + loss, _, _ = run_shift( + RA, RB, VA, VB_start, VB_end, Q, P, N, sched + ) + except (ValueError, AssertionError) as e: + loss = np.nan + results[sched].append(loss) + + # Optimal 2-step (single point) + try: + loss_opt2, _, _ = run_shift_optimal_2step( + RA, RB, VA, VB_start, VB_end, Q, P + ) + except (ValueError, AssertionError): + loss_opt2 = np.nan + + # Table + loss_1 = results["geometric"][0] + print(f"\n{'N':>5s} {'Geo VB':>12s} {'Lin VB':>12s} {'Lin Z':>12s}" + f" {'Geo/1step':>9s} {'LinZ/1step':>10s} {'LinZ/Geo':>9s}") + print("-" * 80) + for j, N in enumerate(N_values): + g = results["geometric"][j] + lv = results["linear_VB"][j] + lz = results["linear_Z"][j] + print(f"{N:>5d} {g:>12.6f} {lv:>12.6f} {lz:>12.6f}" + f" {g / loss_1:>9.4f} {lz / loss_1:>10.4f} {lz / g:>9.4f}") + + print(f"\n Optimal 2-step loss: {loss_opt2:.6f}") + print(f" Geometric N=2 loss: {results['geometric'][1]:.6f}" + f" (opt/geo = {loss_opt2 / results['geometric'][1]:.4f})") + print(f" Linear Z N=2 loss: {results['linear_Z'][1]:.6f}" + f" (opt/linZ = {loss_opt2 / results['linear_Z'][1]:.4f})") + + # ── Experiment 2: Z and VB trajectories at N=8 ───────────────────── + + N_viz = 8 + traj_data = {} + for sched in schedules: + VB_traj, Z_traj, loss_traj = [VB_start], [], [] + VA_s = VA # stale + Z_traj.append(compute_Z(VA_s, VB_start, P)) + + RA_c, RB_c = RA, RB + if sched == "linear_Z": + Z0 = Z_traj[0] + VA_end_a = compute_VA_from_VB(RA, RB, VB_end, Q) + Z_end_val = compute_Z(VA_end_a, VB_end, P) + + for i in range(1, N_viz + 1): + frac = i / N_viz + if sched == "geometric": + VB_i = VB_start * (VB_end / VB_start) ** frac + elif sched == "linear_VB": + VB_i = VB_start + frac * (VB_end - VB_start) + else: + Z_i = Z0 + frac * (Z_end_val - Z0) + VB_i = solve_VB_for_Z(RA_c, RB_c, Z_i, Q, P) + + try: + VA_i = compute_VA_from_VB(RA_c, RB_c, VB_i, Q) + VB_traj.append(VB_i) + Z_traj.append(compute_Z(VA_i, VB_i, P)) + RA_c, RB_c, loss = micro_step(RA_c, RB_c, VA_i, VB_i, P) + loss_traj.append(loss) + except (ValueError, AssertionError): + break + + traj_data[sched] = { + "VB": np.array(VB_traj), + "Z": np.array(Z_traj), + "loss": np.array(loss_traj), + } + + # ── Experiment 3: sweep shift size at N=2 ────────────────────────── + + decay_sweep = np.linspace(0.80, 0.99, 30) + sweep = {s: [] for s in ["geometric", "linear_Z", "optimal_2step"]} + for df in decay_sweep: + VB_e = VB * df + try: + g, _, _ = run_shift(RA, RB, VA, VB_start, VB_e, Q, P, 2, "geometric") + lz, _, _ = run_shift(RA, RB, VA, VB_start, VB_e, Q, P, 2, "linear_Z") + o2, _, _ = run_shift_optimal_2step(RA, RB, VA, VB_start, VB_e, Q, P) + except (AssertionError, ValueError): + g = lz = o2 = np.nan + sweep["geometric"].append(g) + sweep["linear_Z"].append(lz) + sweep["optimal_2step"].append(o2) + + # ── Plots ────────────────────────────────────────────────────────── + + colours = {"geometric": "C0", "linear_VB": "C1", "linear_Z": "C2"} + labels = { + "geometric": "Geometric VB (contract)", + "linear_VB": "Linear VB", + "linear_Z": "Linear Z (optimal)", + } + + fig, axes = plt.subplots(2, 2, figsize=(13, 10)) + + # (0,0) Loss vs N + ax = axes[0, 0] + for s in schedules: + ax.plot(N_values, results[s], "o-", ms=4, color=colours[s], label=labels[s]) + ax.axhline(loss_opt2, color="C3", ls=":", label=f"Optimal 2-step = {loss_opt2:.4f}") + ax.set_xlabel("Steps N") + ax.set_ylabel("Total arb loss") + ax.set_title("Arb loss vs interpolation steps") + ax.set_xscale("log") + ax.set_yscale("log") + ax.legend(fontsize=7) + ax.grid(True, alpha=0.3) + + # (0,1) Ratio linear_Z / geometric + ax = axes[0, 1] + ratios = np.array(results["linear_Z"]) / np.array(results["geometric"]) + ax.plot(N_values, ratios, "o-", color="C2") + ax.axhline(1.0, color="gray", ls="--", alpha=0.5) + ax.set_xlabel("Steps N") + ax.set_ylabel("Loss(Linear Z) / Loss(Geometric VB)") + ax.set_title("Relative improvement of Z-optimal") + ax.grid(True, alpha=0.3) + + # (1,0) Z trajectories at N=8 + ax = axes[1, 0] + steps = np.arange(N_viz + 1) + for s in schedules: + ax.plot(steps, traj_data[s]["Z"], "o-", ms=4, color=colours[s], label=labels[s]) + ax.set_xlabel("Step") + ax.set_ylabel("Z = sqrt(P)*VA - VB/sqrt(P)") + ax.set_title(f"Z trajectory (N={N_viz})") + ax.legend(fontsize=7) + ax.grid(True, alpha=0.3) + + # (1,1) 2-step loss vs shift size + ax = axes[1, 1] + shift_pct = (1 - decay_sweep) * 100 + ax.plot(shift_pct, sweep["geometric"], color="C0", label="Geometric VB (N=2)") + ax.plot(shift_pct, sweep["linear_Z"], color="C2", label="Linear Z (N=2)") + ax.plot(shift_pct, sweep["optimal_2step"], ":", color="C3", label="Optimal 2-step") + ax.set_xlabel("Shift size (% VB decay)") + ax.set_ylabel("Arb loss") + ax.set_title("2-step loss vs shift magnitude") + ax.legend(fontsize=7) + ax.grid(True, alpha=0.3) + + plt.tight_layout() + plt.savefig("reclamm_interpolation_benchmark.png", dpi=150) + print("\nSaved reclamm_interpolation_benchmark.png") + + # ── Per-step loss bar chart for N=8 ──────────────────────────────── + + fig2, ax = plt.subplots(figsize=(10, 5)) + x = np.arange(1, N_viz + 1) + w = 0.25 + for i, s in enumerate(schedules): + ax.bar(x + i * w, traj_data[s]["loss"], w, color=colours[s], label=labels[s]) + ax.set_xlabel("Step") + ax.set_ylabel("Per-step arb loss") + ax.set_title(f"Per-step loss distribution (N={N_viz})") + ax.legend(fontsize=8) + ax.set_xticks(x + w) + plt.tight_layout() + plt.savefig("reclamm_interpolation_perstep.png", dpi=150) + print("Saved reclamm_interpolation_perstep.png") + + # ── Experiment 4: small-shift regime (paper's approximation valid) ─── + + print("\n" + "=" * 72) + print("Experiment 4: Optimal 2-step vs Geometric N=2 at small shifts") + print(" (reserves nearly constant → paper's analysis should hold)") + print("-" * 72) + print(f" {'Decay %':>8s} {'Geo N=2':>12s} {'LinZ N=2':>12s} " + f"{'Opt2':>12s} {'Opt2/Geo':>9s} {'Opt2/LinZ':>9s}") + print("-" * 72) + + small_decays = [0.999, 0.998, 0.995, 0.99, 0.98, 0.95, 0.90, 0.80] + for df in small_decays: + VB_e = VB * df + try: + g, _, _ = run_shift(RA, RB, VA, VB_start, VB_e, Q, P, 2, "geometric") + lz, _, _ = run_shift(RA, RB, VA, VB_start, VB_e, Q, P, 2, "linear_Z") + o2, _, _ = run_shift_optimal_2step( + RA, RB, VA, VB_start, VB_e, Q, P + ) + except (ValueError, AssertionError) as e: + print(f" {(1-df)*100:>7.1f}% FAILED: {e}") + continue + print(f" {(1-df)*100:>7.1f}% {g:>12.6f} {lz:>12.6f} " + f"{o2:>12.6f} {o2/g:>9.6f} {o2/lz:>9.6f}") + + print("=" * 72) + + # ── Experiment 5: brute-force JAX-optimised Z targets ──────────────── + + print("\n" + "=" * 72) + print("Experiment 5: Brute-force optimal Z targets (JAX + L-BFGS-B)") + print(" Parameterisation: softplus gaps → sorted Z targets") + print(" Initialised at linear Z (uniform gaps)") + print("-" * 72) + + opt_N_values = [2, 3, 4, 6, 8, 12, 16, 24, 32] + opt_losses = {} + opt_Z_trajs = {} + + for N in opt_N_values: + loss_bf, Z_bf = optimise_z_targets( + RA, RB, Q, P, Z_start, Z_end, N, verbose=True + ) + opt_losses[N] = loss_bf + opt_Z_trajs[N] = Z_bf + + # Comparison table + print(f"\n {'N':>5s} {'Geometric':>12s} {'Linear Z':>12s} " + f"{'BF Optimal':>12s} {'BF/LinZ':>9s} {'BF/Geo':>9s}") + print("-" * 72) + for N in opt_N_values: + idx = N_values.index(N) if N in N_values else None + g = results["geometric"][idx] if idx is not None else np.nan + lz = results["linear_Z"][idx] if idx is not None else np.nan + bf = opt_losses[N] + print(f" {N:>5d} {g:>12.6f} {lz:>12.6f} " + f"{bf:>12.6f} {bf/lz:>9.6f} {bf/g:>9.6f}") + + # ── Plot: overlay brute-force on the main loss-vs-N chart ──────────── + + fig3, axes3 = plt.subplots(1, 2, figsize=(14, 5)) + + # (left) Loss vs N with brute-force overlay + ax = axes3[0] + for s in schedules: + ax.plot(N_values, results[s], "o-", ms=4, color=colours[s], + label=labels[s]) + bf_Ns = sorted(opt_losses.keys()) + bf_vals = [opt_losses[n] for n in bf_Ns] + ax.plot(bf_Ns, bf_vals, "s--", ms=5, color="C3", label="BF Optimal (JAX)") + ax.set_xlabel("Steps N") + ax.set_ylabel("Total arb loss") + ax.set_title("Arb loss vs interpolation steps (with BF optimal)") + ax.set_xscale("log") + ax.set_yscale("log") + ax.legend(fontsize=7) + ax.grid(True, alpha=0.3) + + # (right) Z trajectory comparison at N=8 + ax = axes3[1] + N_cmp = 8 + steps_cmp = np.arange(N_cmp + 1) + + # Geometric: compute Z trajectory from VB + z_geo = [Z_start] + RA_t, RB_t = RA, RB + for i in range(1, N_cmp + 1): + frac = i / N_cmp + VB_i = VB_start * (VB_end / VB_start) ** frac + VA_i = compute_VA_from_VB(RA_t, RB_t, VB_i, Q) + z_geo.append(compute_Z(VA_i, VB_i, P)) + RA_t, RB_t, _ = micro_step(RA_t, RB_t, VA_i, VB_i, P) + + # Linear Z + z_linz = [Z_start] + RA_t, RB_t = RA, RB + for i in range(1, N_cmp + 1): + frac = i / N_cmp + Z_i = Z_start + frac * (Z_end - Z_start) + VB_i = solve_VB_for_Z(RA_t, RB_t, Z_i, Q, P) + VA_i = compute_VA_from_VB(RA_t, RB_t, VB_i, Q) + z_linz.append(compute_Z(VA_i, VB_i, P)) + RA_t, RB_t, _ = micro_step(RA_t, RB_t, VA_i, VB_i, P) + + # BF optimal + z_bf = [Z_start] + list(opt_Z_trajs[N_cmp]) + # Trace actual Z achieved after arb at each step + z_bf_actual = [Z_start] + RA_t, RB_t = RA, RB + for i in range(N_cmp): + VB_i = solve_VB_for_Z(RA_t, RB_t, opt_Z_trajs[N_cmp][i], Q, P) + VA_i = compute_VA_from_VB(RA_t, RB_t, VB_i, Q) + z_bf_actual.append(compute_Z(VA_i, VB_i, P)) + RA_t, RB_t, _ = micro_step(RA_t, RB_t, VA_i, VB_i, P) + + ax.plot(steps_cmp, z_geo, "o-", ms=4, color="C0", label="Geometric VB") + ax.plot(steps_cmp, z_linz, "o-", ms=4, color="C2", label="Linear Z") + ax.plot(steps_cmp, z_bf_actual, "s--", ms=5, color="C3", + label="BF Optimal") + ax.plot(steps_cmp, np.linspace(Z_start, Z_end, N_cmp + 1), + ":", color="gray", alpha=0.5, label="Ideal linear Z") + ax.set_xlabel("Step") + ax.set_ylabel("Z = sqrt(P)*VA - VB/sqrt(P)") + ax.set_title(f"Z trajectory comparison (N={N_cmp})") + ax.legend(fontsize=7) + ax.grid(True, alpha=0.3) + + plt.tight_layout() + plt.savefig("reclamm_interpolation_bruteforce.png", dpi=150) + print("\nSaved reclamm_interpolation_bruteforce.png") + + +if __name__ == "__main__": + main() diff --git a/scripts/build_pool_grids.py b/scripts/build_pool_grids.py new file mode 100644 index 0000000..d056908 --- /dev/null +++ b/scripts/build_pool_grids.py @@ -0,0 +1,750 @@ +"""Build 2D arb-volume grids (cadence x gas) for all Binance-matchable pools. + +v2: Per-day daily arb volumes at each grid point, correct pool weights, + pool type dispatch (WEIGHTED vs RECLAMM). + +For each real Balancer pool where both tokens have Binance minute data: + - Uses actual LP supply trajectory (BPT totalShares from panel) + - Uses actual initial TVL from panel + - Uses correct pool weights from pools.parquet + - Dispatches reCLAMM pools with on-chain params from pools_history.db + - Sweeps cadence x gas_cost as scalar grid + - Stores per-day V_arb at each grid point (not aggregated) + +Output: results/pool_grids_v2/{pool_id_prefix}_daily.parquet + summary CSV. + +Usage: + python scripts/build_pool_grids.py + python scripts/build_pool_grids.py --workers 6 --train-days 90 +""" + +import os +os.environ.setdefault("JAX_PLATFORMS", "cpu") + +import argparse +import ast +import sqlite3 +import time +from concurrent.futures import ProcessPoolExecutor, as_completed + +import jax.numpy as jnp +import matplotlib +matplotlib.use("Agg") +import matplotlib.pyplot as plt +import numpy as np +import pandas as pd + +from quantammsim.core_simulator.dynamic_inputs import DynamicInputFrames +from quantammsim.runners.jax_runners import do_run_on_historic_data +from quantammsim.utils.data_processing.historic_data_utils import get_historic_parquet_data + +# ── Output ──────────────────────────────────────────────────────────────── +OUTPUT_DIR = os.path.join( + os.path.dirname(os.path.dirname(__file__)), "results", "pool_grids_v2", +) + +# ── Grid ────────────────────────────────────────────────────────────────── +CADENCES = [1, 2, 3, 5, 8, 12, 20, 30, 45, 60] + +# Finer gas grids — concentrated in $0.1-$3.0 where most fitted values land. +# Mainnet: 19 points (was 10). Fills the $0.25→$1.50 gap that caused +# zero-arb artifacts on low-volatility days. +GAS_COSTS_MAINNET = [ + 0.0, 0.05, 0.1, 0.15, 0.25, 0.35, 0.5, 0.65, 0.8, + 1.0, 1.25, 1.5, 2.0, 3.0, 5.0, 8.0, 12.0, 20.0, 50.0, +] +# L2: 14 points (was 10). Fills $0.05→$0.50 range. +GAS_COSTS_L2 = [ + 0.0, 0.001, 0.003, 0.005, 0.01, 0.02, 0.05, + 0.1, 0.15, 0.25, 0.5, 0.75, 1.0, 2.0, +] + +# ── Token mapping ───────────────────────────────────────────────────────── +# Maps Balancer pool token symbols to Binance trading symbols. +# Validated via Balancer hourly vs Binance minute price comparison: +# all wrappers below have daily return correlation > 0.75 with their +# underlying, or are stablecoins (basis < 0.5%). +TOKEN_MAP = { + # Wrapped natives (corr > 0.96) + "WBTC": "BTC", "WETH": "ETH", "cbBTC": "BTC", + # ETH LSTs / Aave wrappers (corr 0.87-0.97) + "wstETH": "ETH", "stETH": "ETH", "rETH": "ETH", "cbETH": "ETH", + "waEthLidoWETH": "ETH", "waEthLidowstETH": "ETH", + "waBasWETH": "ETH", # Aave wrapped WETH on Base (corr 0.975) + "waGnowstETH": "ETH", # Aave wrapped wstETH on Gnosis (corr 0.976) + # GNO wrappers (corr 0.66-0.98) + "waGnoGNO": "GNO", # Aave wrapped GNO on Gnosis (corr 0.979) + "osGNO": "GNO", # StakeWise staked GNO (corr 0.755) + # S (Sonic) wrappers (corr 0.945) + "wS": "S", + "stS": "S", # Staked Sonic + # SOL LSTs (corr 0.922) + "JitoSOL": "SOL", # Jito staked SOL + # POL/MATIC variants (corr 0.96) + "wPOL": "POL", "WMATIC": "POL", "MATIC": "POL", + # Stablecoin equivalents (all ~$1.00, basis < 0.5%) + "USDC.e": "USDC", "USDbC": "USDC", "waBasUSDC": "USDC", + "DAI": "USDC", # Stale Binance data; basis < 10bps vs USDC + "WXDAI": "USDC", "sDAI": "USDC", # Gnosis DAI variants → USDC + "USDT": "USDC", + "DOLA": "USDC", + "scUSD": "USDC", +} + +# ── reCLAMM pool → DB table mapping ────────────────────────────────────── +RECLAMM_DB_PATH = "/Users/matthew/Projects/reclamm-simulations/data/pools_history.db" +RECLAMM_POOL_TABLE_MAP = { + "0x9d1fcf346ea1b0": "AAVE_WETH", +} + + +def _get_binance_tokens(): + """Get set of tokens with Binance minute parquets.""" + data_dir = os.path.join( + os.path.dirname(os.path.dirname(__file__)), + "quantammsim", "data", + ) + tokens = set() + for f in os.listdir(data_dir): + if f.endswith("_USD.parquet"): + tokens.add(f.replace("_USD.parquet", "")) + return tokens + + +def _map_token(tok, binance_tokens): + """Map a Balancer token symbol to Binance symbol, or None.""" + mapped = TOKEN_MAP.get(tok, tok) + return mapped if mapped in binance_tokens else None + + +def gas_costs_for_chain(chain): + return GAS_COSTS_L2 if chain != "MAINNET" else GAS_COSTS_MAINNET + + +def _parse_weights(weights_raw): + """Parse weights from pools.parquet (numpy array of strings or list).""" + if weights_raw is None: + return None + if isinstance(weights_raw, str): + weights_raw = ast.literal_eval(weights_raw) + if hasattr(weights_raw, 'tolist'): + weights_raw = weights_raw.tolist() + parsed = [] + for w in weights_raw: + if w is None or str(w).lower() == 'none': + return None + parsed.append(float(w)) + return parsed + + +def _parse_tokens(tokens_raw): + """Parse tokens from pools.parquet.""" + if isinstance(tokens_raw, str): + try: + return ast.literal_eval(tokens_raw) + except (ValueError, SyntaxError): + return [t.strip() for t in tokens_raw.split(",")] + if hasattr(tokens_raw, 'tolist'): + return tokens_raw.tolist() + return list(tokens_raw) + + +# ── Pool metadata loading ───────────────────────────────────────────────── + +def load_pools_metadata(): + """Load pools.parquet to get weights, pool_type, and true token count.""" + pools_path = os.path.join( + os.path.dirname(os.path.dirname(__file__)), + "local_data", "noise_calibration", "pools.parquet", + ) + pools = pd.read_parquet(pools_path) + meta = {} + for _, row in pools.iterrows(): + pool_id = row["pool_id"] + prefix = pool_id[:16] + tokens = _parse_tokens(row["tokens"]) + weights = _parse_weights(row["weights"]) + meta[prefix] = { + "pool_id": pool_id, + "pool_type": row["pool_type"], + "tokens_full": tokens, + "n_tokens": len(tokens), + "weights": weights, + } + return meta + + +def load_reclamm_params(pool_id_prefix): + """Load reCLAMM on-chain params from pools_history.db.""" + table_name = RECLAMM_POOL_TABLE_MAP.get(pool_id_prefix) + if table_name is None: + return None + if not os.path.exists(RECLAMM_DB_PATH): + return None + conn = sqlite3.connect(RECLAMM_DB_PATH) + try: + df = pd.read_sql(f"SELECT * FROM [{table_name}] ORDER BY timestamp DESC LIMIT 1", conn) + if len(df) == 0: + return None + row = df.iloc[0] + return { + "price_ratio": float(row["price_ratio"]), + "centeredness_margin": float(row["margin"]), + "shift_exponent": float(row["shift_rate"]), + "swap_fee": float(row["swap_fee"]), + } + except Exception: + return None + finally: + conn.close() + + +# ── Panel loading and pool matching ─────────────────────────────────────── + +def load_panel_and_match(train_days): + """Load panel, filter to last N days, find matchable 2-token pools.""" + panel_path = os.path.join( + os.path.dirname(os.path.dirname(__file__)), + "local_data", "noise_calibration", "panel.parquet", + ) + panel = pd.read_parquet(panel_path) + + if "obs_date" not in panel.columns and "date" in panel.columns: + panel = panel.rename(columns={"date": "obs_date"}) + panel["obs_date"] = pd.to_datetime(panel["obs_date"]) + + if "tvl" not in panel.columns and "log_tvl" in panel.columns: + panel["tvl"] = np.exp(panel["log_tvl"]) + + if train_days > 0: + cutoff = panel["obs_date"].max() - pd.Timedelta(days=train_days) + panel = panel[panel["obs_date"] >= cutoff].copy() + else: + panel = panel.copy() # no date filter — use all data + + binance_tokens = _get_binance_tokens() + pools_meta = load_pools_metadata() + + pools = [] + for pool_id, grp in panel.groupby("pool_id"): + prefix = pool_id[:16] + meta = pools_meta.get(prefix) + if meta is None: + continue + + # Skip multi-token pools + if meta["n_tokens"] > 2: + continue + + pool_type = meta["pool_type"] + + # Get tokens for Binance matching from panel + row = grp.iloc[0] + tokens_str = row["tokens"] + toks = [t.strip() for t in tokens_str.split(",")] + if len(toks) != 2: + continue + + mapped = [] + for t in toks: + m = _map_token(t, binance_tokens) + if m is None: + break + mapped.append(m) + else: + if mapped[0] == mapped[1]: + continue + + chain = row["chain"] + fee = row.get("swap_fee", np.exp(row["log_fee"])) + + # Get weights + weights = meta["weights"] + if pool_type == "WEIGHTED" and weights is None: + weights = [0.5, 0.5] + + # reCLAMM params + reclamm_params = None + if pool_type == "RECLAMM": + reclamm_params = load_reclamm_params(prefix) + if reclamm_params is None: + print(f" SKIP reCLAMM {'/'.join(mapped)} ({chain}): " + f"no DB params for {prefix}") + continue + + pools.append({ + "pool_id": pool_id, + "pool_id_prefix": prefix, + "tokens": mapped, + "chain": chain, + "fee": float(fee), + "panel_data": grp, + "pool_type": pool_type, + "weights": weights, + "reclamm_params": reclamm_params, + }) + + return pools + + +def build_lp_supply_df(panel_pool): + """Build lp_supply_df from daily BPT supply. Returns (df, initial_tvl).""" + panel_pool = panel_pool.sort_values("obs_date") + + has_bpt = ( + "total_shares" in panel_pool.columns + and not panel_pool["total_shares"].isna().all() + and (panel_pool["total_shares"] > 0).any() + ) + + initial_tvl = float(panel_pool["tvl"].iloc[0]) if len(panel_pool) > 0 else 0.0 + + if not has_bpt: + return None, initial_tvl + + bpt = panel_pool[["obs_date", "total_shares", "tvl"]].drop_duplicates("obs_date") + initial_bpt = bpt["total_shares"].iloc[0] + + if initial_bpt <= 0 or initial_tvl <= 0: + return None, initial_tvl + + unix_ms = bpt["obs_date"].apply( + lambda d: int(pd.Timestamp(d).timestamp() * 1000) + ).values + lp_supply = (bpt["total_shares"].values / initial_bpt).astype(float) + + return pd.DataFrame({"unix": unix_ms, "lp_supply": lp_supply}), initial_tvl + + +def get_date_range(panel_data): + """Get start/end date strings from panel data.""" + dates = panel_data["obs_date"].sort_values() + start = dates.iloc[0].strftime("%Y-%m-%d %H:%M:%S") + end = dates.iloc[-1].strftime("%Y-%m-%d %H:%M:%S") + return start, end + + +# ── Simulation ──────────────────────────────────────────────────────────── + +def run_arb_sim(tokens, fee, initial_tvl, start, end, cadence, gas_cost, + lp_supply_df=None, weights=None, pool_type="WEIGHTED", + reclamm_params=None, price_data=None): + """Run arb-only sim at one (cadence, gas_cost) point. + + Returns: pd.Series with date index → daily arb volume. + """ + fp = { + "tokens": tokens, + "startDateString": start, + "endDateString": end, + "initial_pool_value": initial_tvl, + "fees": fee, + "gas_cost": float(gas_cost), + "arb_fees": 0.0, + "do_arb": True, + "noise_trader_ratio": 0.0, + "arb_frequency": int(cadence), + "chunk_period": 1440, + "weight_interpolation_period": 1440, + "max_memory_days": 0, + } + + if pool_type == "RECLAMM" and reclamm_params is not None: + fp["rule"] = "reclamm" + fp["reclamm_use_shift_exponent"] = True + fp["fees"] = reclamm_params["swap_fee"] + params = { + "price_ratio": jnp.array(reclamm_params["price_ratio"]), + "centeredness_margin": jnp.array(reclamm_params["centeredness_margin"]), + "shift_exponent": jnp.array(reclamm_params["shift_exponent"]), + } + else: + fp["rule"] = "balancer" + if weights is not None and len(weights) == 2: + logits = np.log(np.array(weights, dtype=float)) + params = {"initial_weights_logits": jnp.array(logits)} + else: + params = {"initial_weights_logits": jnp.array([0.0, 0.0])} + + dynamic_input_frames = ( + DynamicInputFrames(lp_supply=lp_supply_df) + if lp_supply_df is not None else None + ) + result = do_run_on_historic_data( + fp, params, verbose=False, price_data=price_data, + dynamic_input_frames=dynamic_input_frames, preslice_burnin=False, + ) + + reserves = np.array(result["reserves"]) + prices = np.array(result["data_dict"]["prices"]) + unix_ms = np.array(result["data_dict"]["unix_values"]) + start_idx = int(result["data_dict"]["start_idx"]) + + T = reserves.shape[0] - 1 + prices_window = prices[start_idx:start_idx + T + 1] + delta_r = np.diff(reserves, axis=0) + step_vol = np.sum(np.abs(delta_r * prices_window[1:]), axis=1) / 2.0 + + dates = pd.to_datetime( + unix_ms[start_idx + 1:start_idx + T + 1], unit="ms", + ).normalize() + daily = pd.DataFrame( + {"date": dates, "volume": step_vol}, + ).groupby("date")["volume"].sum() + + return daily + + +def _run_cadence_sweep(pool_info, cadence, gas_costs): + """Worker: sweep all gas costs for one (pool, cadence). + + Returns list of dicts with per-day data: one dict per (gas, date) pair, + plus a summary list. + """ + os.environ.setdefault("JAX_PLATFORMS", "cpu") + + tokens = pool_info["tokens"] + fee = pool_info["fee"] + initial_tvl = pool_info["initial_tvl"] + start = pool_info["start"] + end = pool_info["end"] + lp_supply_df = pool_info["lp_supply_df"] + weights = pool_info.get("weights") + pool_type = pool_info.get("pool_type", "WEIGHTED") + reclamm_params = pool_info.get("reclamm_params") + + price_data = pool_info.get("price_data") + if price_data is None: + sorted_tokens = sorted(tokens) + price_data = get_historic_parquet_data(sorted_tokens, ["close"]) + + daily_rows = [] + summary_rows = [] + + for gas in gas_costs: + try: + daily = run_arb_sim( + tokens, fee, initial_tvl, start, end, cadence, gas, + lp_supply_df=lp_supply_df, + weights=weights, + pool_type=pool_type, + reclamm_params=reclamm_params, + price_data=price_data, + ) + # Store per-day data + for date_val, vol in daily.items(): + daily_rows.append({ + "cadence": cadence, + "gas_cost": gas, + "date": date_val, + "daily_arb_volume": vol, + }) + # Summary for diagnostics + summary_rows.append({ + "cadence": cadence, + "gas_cost": gas, + "total_arb_volume": daily.sum(), + "median_daily_arb_volume": daily.median(), + "mean_daily_arb_volume": daily.mean(), + "n_days": len(daily), + }) + except Exception as e: + summary_rows.append({ + "cadence": cadence, + "gas_cost": gas, + "total_arb_volume": np.nan, + "median_daily_arb_volume": np.nan, + "mean_daily_arb_volume": np.nan, + "n_days": 0, + "error": str(e), + }) + + return daily_rows, summary_rows + + +# ── Plotting ────────────────────────────────────────────────────────────── + +def plot_pool_grid(summary_df, pool_id, tokens, chain, fee, tvl, pool_type, + gas_costs, output_dir): + """Simple 2-panel diagnostic: V_arb vs cadence, gas attenuation.""" + fig, axes = plt.subplots(1, 3, figsize=(16, 4.5)) + df = summary_df + + ax = axes[0] + for gas in gas_costs: + sub = df[df["gas_cost"] == gas].sort_values("cadence") + if len(sub) > 0: + ax.plot(sub["cadence"], sub["median_daily_arb_volume"], + "o-", label=f"${gas}", markersize=3) + ax.set_xlabel("Cadence (min)") + ax.set_ylabel("Median daily V_arb ($)") + ax.set_title("V_arb vs cadence") + ax.legend(fontsize=5, ncol=2, title="gas") + + ax = axes[1] + for cadence in CADENCES: + sub = df[df["cadence"] == cadence].sort_values("gas_cost") + v0 = sub[sub["gas_cost"] == 0.0]["median_daily_arb_volume"].values + if len(v0) > 0 and v0[0] > 0: + ratio = sub["median_daily_arb_volume"].values / v0[0] + ax.plot(sub["gas_cost"].values, ratio, "o-", + label=f"{cadence}min", markersize=3) + ax.set_xlabel("Gas cost ($)") + ax.set_ylabel("V_arb / V_arb(gas=0)") + ax.set_title("Gas attenuation") + ax.set_ylim(-0.05, 1.05) + ax.legend(fontsize=5, ncol=2) + + ax = axes[2] + for gas in gas_costs: + sub = df[df["gas_cost"] == gas].sort_values("cadence") + vals = sub["median_daily_arb_volume"].values + if len(vals) > 0 and np.all(vals > 0): + ax.plot(sub["cadence"], vals, "o-", label=f"${gas}", markersize=3) + ax.set_xscale("log") + ax.set_yscale("log") + ax.set_xlabel("Cadence (min)") + ax.set_ylabel("Median daily V_arb ($)") + ax.set_title("Log-log") + ax.legend(fontsize=5, ncol=2, title="gas") + + tok_str = "/".join(tokens) + type_str = f" [{pool_type}]" if pool_type != "WEIGHTED" else "" + fig.suptitle( + f"{tok_str} ({chain}, fee={fee:.2%}, TVL=${tvl:,.0f}){type_str}\n" + f"{pool_id[:16]}", + fontsize=10, + ) + fig.tight_layout() + path = os.path.join(output_dir, f"{pool_id[:16]}_grid.png") + fig.savefig(path, dpi=120, bbox_inches="tight") + plt.close() + + +# ── Main ────────────────────────────────────────────────────────────────── + +def main(): + parser = argparse.ArgumentParser(description=__doc__, + formatter_class=argparse.RawDescriptionHelpFormatter) + parser.add_argument("--workers", type=int, default=6) + parser.add_argument("--train-days", type=int, default=90) + parser.add_argument("--pools", type=str, default=None, + help="Comma-separated pool_id prefixes to run (default: all)") + args = parser.parse_args() + + os.makedirs(OUTPUT_DIR, exist_ok=True) + + print("Loading panel and matching pools...") + pools = load_panel_and_match(args.train_days) + print(f"Found {len(pools)} matchable 2-token pools\n") + + for p in pools: + # Preload price data to determine actual date coverage + sorted_tokens = sorted(p["tokens"]) + price_data = get_historic_parquet_data(sorted_tokens, ["close"]) + p["price_data"] = price_data + + if len(price_data) == 0: + p["panel_data"] = p["panel_data"].iloc[:0] # empty + p["lp_supply_df"] = None + p["initial_tvl"] = 0.0 + p["start"], p["end"] = "2000-01-01", "2000-01-01" + continue + + # Clip panel to price data's actual date range + price_dates = pd.to_datetime(price_data.index, unit="ms") + price_start = price_dates.min().normalize() + price_end = price_dates.max().normalize() + panel_data = p["panel_data"] + panel_data = panel_data[ + (panel_data["obs_date"] >= price_start) + & (panel_data["obs_date"] <= price_end) + ].copy() + p["panel_data"] = panel_data + + lp_df, tvl = build_lp_supply_df(panel_data) + p["lp_supply_df"] = lp_df + p["initial_tvl"] = tvl + + # Start/end must be midnight timestamps that exist in the price data. + # start_and_end_calcs does an exact unix match and assumes alignment. + # If the price data starts mid-day (e.g. COW at noon), advance to + # the next midnight so the sim has a clean day boundary. + if len(panel_data) > 0: + first_midnight = (price_dates.min() + pd.Timedelta(days=1)).normalize() + last_midnight = price_dates.max().normalize() + first_midnight_ms = int(first_midnight.timestamp() * 1000) + last_midnight_ms = int(last_midnight.timestamp() * 1000) + # Verify these timestamps exist in the price data + if first_midnight_ms in price_data.index and last_midnight_ms in price_data.index: + p["start"] = first_midnight.strftime("%Y-%m-%d %H:%M:%S") + p["end"] = last_midnight.strftime("%Y-%m-%d %H:%M:%S") + else: + # Fallback: use panel dates (works when price data covers full range) + p["start"], p["end"] = get_date_range(panel_data) + else: + p["start"], p["end"] = "2000-01-01", "2000-01-01" + + pools = [p for p in pools if p["panel_data"]["obs_date"].nunique() >= 14] + pools.sort(key=lambda p: p["initial_tvl"], reverse=True) + + # Filter to specific pools if requested + if args.pools: + requested = set(args.pools.split(",")) + pools = [p for p in pools if p["pool_id_prefix"] in requested] + print(f"Filtered to {len(pools)} requested pools\n") + + # Print pool summary + print(f"{'#':>3} {'Tokens':<12} {'Chain':<10} {'Type':<9} {'Weights':<10} " + f"{'Fee':>6} {'TVL':>12} {'Days':>5} {'Pool ID':<18}") + print("-" * 95) + for i, p in enumerate(pools): + n_days = p["panel_data"]["obs_date"].nunique() + w_str = "/".join(f"{w:.0%}" for w in p["weights"]) if p["weights"] else "N/A" + print(f"{i+1:3d} {'/'.join(p['tokens']):<12} {p['chain']:<10} " + f"{p['pool_type']:<9} {w_str:<10} " + f"{p['fee']:5.2%} ${p['initial_tvl']:>10,.0f} " + f"{n_days:5d} {p['pool_id'][:16]}") + + all_summaries = [] + t_total = time.time() + + for pool_idx, pool in enumerate(pools): + pool_id = pool["pool_id"] + prefix = pool["pool_id_prefix"] + tokens = pool["tokens"] + chain = pool["chain"] + fee = pool["fee"] + tvl = pool["initial_tvl"] + pool_type = pool["pool_type"] + gas_costs = gas_costs_for_chain(chain) + n_runs = len(CADENCES) * len(gas_costs) + + if tvl <= 0: + print(f"\n SKIP {'/'.join(tokens)} ({chain}): TVL=0") + continue + + w_str = "/".join(f"{w:.0%}" for w in pool["weights"]) if pool["weights"] else "N/A" + print(f"\n{'='*60}") + print(f" [{pool_idx+1}/{len(pools)}] {'/'.join(tokens)} " + f"({chain}, {pool_type}, {w_str}, fee={fee:.2%}, TVL=${tvl:,.0f})") + print(f" Grid: {len(CADENCES)} cadences x {len(gas_costs)} gas = {n_runs} runs") + print(f"{'='*60}") + + t0 = time.time() + + # Price data was preloaded during panel clipping + price_data = pool["price_data"] + + pool_info = { + "tokens": tokens, + "fee": fee, + "initial_tvl": tvl, + "start": pool["start"], + "end": pool["end"], + "lp_supply_df": pool["lp_supply_df"], + "weights": pool["weights"], + "pool_type": pool_type, + "reclamm_params": pool.get("reclamm_params"), + # Only pass preloaded price_data in single-worker mode. + # For multi-worker, each subprocess loads its own to avoid + # pickling multi-million-row DataFrames across processes. + "price_data": price_data if args.workers <= 1 else None, + } + + all_daily_rows = [] + all_summary_rows = [] + + if args.workers <= 1: + for cadence in CADENCES: + daily_rows, summary_rows = _run_cadence_sweep( + pool_info, cadence, gas_costs, + ) + all_daily_rows.extend(daily_rows) + all_summary_rows.extend(summary_rows) + for r in summary_rows: + print(f" cad={r['cadence']:3d} gas=${r['gas_cost']:6.3f} -> " + f"median=${r['median_daily_arb_volume']:,.0f}/day") + else: + futures = {} + with ProcessPoolExecutor(max_workers=args.workers) as executor: + for cadence in CADENCES: + fut = executor.submit( + _run_cadence_sweep, pool_info, cadence, gas_costs, + ) + futures[fut] = cadence + + done = 0 + for fut in as_completed(futures): + cadence = futures[fut] + done += 1 + try: + daily_rows, summary_rows = fut.result() + all_daily_rows.extend(daily_rows) + all_summary_rows.extend(summary_rows) + medians = [r["median_daily_arb_volume"] for r in summary_rows + if not np.isnan(r.get("median_daily_arb_volume", np.nan))] + if medians: + print(f" [{done:2d}/{len(CADENCES)}] cad={cadence:3d} — " + f"V_arb: ${min(medians):,.0f} – ${max(medians):,.0f}/day") + else: + print(f" [{done:2d}/{len(CADENCES)}] cad={cadence:3d} — " + f"all failed") + except Exception as e: + print(f" [{done:2d}/{len(CADENCES)}] cad={cadence:3d} FAILED: {e}") + + elapsed = time.time() - t0 + print(f" {n_runs} runs in {elapsed:.1f}s ({elapsed/max(n_runs,1):.2f}s/run)") + + # Save per-day parquet + if all_daily_rows: + daily_df = pd.DataFrame(all_daily_rows) + daily_df["date"] = pd.to_datetime(daily_df["date"]) + parquet_path = os.path.join(OUTPUT_DIR, f"{prefix}_daily.parquet") + daily_df.to_parquet(parquet_path, index=False) + print(f" Saved {len(daily_df)} daily rows -> {parquet_path}") + + # Save summary CSV for diagnostics + summary_df = pd.DataFrame(all_summary_rows) + csv_path = os.path.join(OUTPUT_DIR, f"{prefix}_summary.csv") + summary_df.to_csv(csv_path, index=False) + + # Plot + if len(summary_df) > 0: + plot_pool_grid(summary_df, pool_id, tokens, chain, fee, tvl, + pool_type, gas_costs, OUTPUT_DIR) + + # Global summary + g0 = summary_df[summary_df["gas_cost"] == 0.0] if len(summary_df) > 0 else pd.DataFrame() + all_summaries.append({ + "pool_id": prefix, + "tokens": "/".join(tokens), + "chain": chain, + "pool_type": pool_type, + "weights": str(pool["weights"]), + "fee": fee, + "tvl": tvl, + "n_days": summary_df["n_days"].max() if len(summary_df) > 0 else 0, + "n_daily_rows": len(all_daily_rows), + "v_arb_cad1_gas0": g0[g0["cadence"] == 1]["median_daily_arb_volume"].values[0] + if len(g0[g0["cadence"] == 1]) > 0 else np.nan, + "v_arb_cad60_gas0": g0[g0["cadence"] == 60]["median_daily_arb_volume"].values[0] + if len(g0[g0["cadence"] == 60]) > 0 else np.nan, + "elapsed_s": elapsed, + }) + + total_elapsed = time.time() - t_total + + if all_summaries: + summary_df = pd.DataFrame(all_summaries) + summary_path = os.path.join(OUTPUT_DIR, "grid_summary.csv") + summary_df.to_csv(summary_path, index=False) + print(f"\n{'='*60}") + print(f" Summary saved: {summary_path}") + print(f" Total: {len(all_summaries)} pools, " + f"{total_elapsed:.0f}s ({total_elapsed/60:.1f} min)") + print(f"{'='*60}") + + +if __name__ == "__main__": + main() diff --git a/scripts/calibrate_noise_bayesian.py b/scripts/calibrate_noise_bayesian.py new file mode 100644 index 0000000..efdfb4b --- /dev/null +++ b/scripts/calibrate_noise_bayesian.py @@ -0,0 +1,853 @@ +"""Bayesian hierarchical noise volume model across Balancer pools. + +Full Bayesian version of the noise calibration: ALL K=4 per-pool +coefficients (intercept, TVL elasticity, volatility response, weekend +effect) vary per pool with pool-level covariates modulating their priors, +and an LKJ-decomposed covariance capturing correlations between +coefficients. + +Generative model: + For pool i with pool-level covariates z_i, day t: + + mu_i = B . z_i # K-vector population mean + eta_i ~ N(0, I_K) # non-centered offsets + theta_i = mu_i + diag(sigma) . L . eta_i # per-pool coefficients + + log(V_{i,t}) ~ N(theta_i . x_{i,t}, sigma_eps^2) + + theta_i = [intercept_i, b_tvl_i, b_sigma_i, b_weekend_i] + x_{i,t} = [1, log_tvl, volatility, weekend] + z_i = [1, chain_dummies(6), tier_A_dummies(2), tier_B_dummies(2), log_fee] + +Priors: + B_{k,d} ~ N(0, 5^2) + sigma_k ~ HalfNormal(2.0) + L ~ LKJCholesky(K=4, eta=2) + sigma_eps ~ HalfNormal(3.0) + +Usage: + # Full pipeline: fit + output + diagnostics + python scripts/calibrate_noise_bayesian.py \\ + --fit --output results/bayesian_noise_params.json --plot + + # Predict for an unseen pool + python scripts/calibrate_noise_bayesian.py \\ + --predict --chain BASE --tokens ETH USDC --fee 0.003 + + # Custom NUTS settings + python scripts/calibrate_noise_bayesian.py \\ + --fit --num-warmup 2000 --num-samples 4000 --num-chains 4 +""" + +import argparse +import json +import os +import sys + +import numpy as np +import pandas as pd + +# arviz 0.17.x imports scipy.signal.gaussian which was removed in scipy 1.13+. +# Patch it back from scipy.signal.windows before any arviz import. +try: + from scipy.signal import gaussian as _ # noqa: F401 +except ImportError: + from scipy.signal.windows import gaussian as _gauss + import scipy.signal + scipy.signal.gaussian = _gauss + +# --------------------------------------------------------------------------- +# Reuse constants and helpers from the frequentist script +# --------------------------------------------------------------------------- + +CACHE_DIR = os.path.join( + os.path.dirname(os.path.dirname(__file__)), "local_data", "noise_calibration" +) + +# Reference levels for dummy coding (dropped categories) +REF_CHAIN = "ARBITRUM" +REF_TIER = 0 + +# Ordered non-reference chains (alphabetical excluding REF_CHAIN) +CHAIN_ORDER = ["BASE", "GNOSIS", "MAINNET", "OPTIMISM", "POLYGON", "SONIC"] + +K = 4 # number of per-pool coefficients +D = 12 # pool-level covariate dimension: 1 + 6 chains + 2 tier_A + 2 tier_B + 1 log_fee + +COEFF_NAMES = ["intercept", "b_tvl", "b_sigma", "b_weekend"] + + +# --------------------------------------------------------------------------- +# Token tier helpers (duplicated to avoid import fragility) +# --------------------------------------------------------------------------- + +_TIER_0 = { + "ETH", "WETH", "BTC", "WBTC", "cbBTC", "USDC", "USDT", "DAI", + "wstETH", "stETH", "rETH", "cbETH", "WMATIC", "MATIC", "POL", + "WAVAX", "AVAX", "GNO", "WXDAI", "xDAI", + "S", "wS", +} + +_TIER_1 = { + "AAVE", "LINK", "UNI", "BAL", "MKR", "CRV", "COMP", "SNX", + "LDO", "RPL", "SUSHI", "YFI", "1INCH", "ENS", "DYDX", + "FXS", "FRAX", "LUSD", "sDAI", "GHO", "crvUSD", + "ARB", "OP", "PENDLE", "ENA", "EIGEN", + "SAFE", "COW", +} + + +def classify_token_tier(symbol: str) -> int: + s = symbol.strip() + if s in _TIER_0: + return 0 + if s in _TIER_1: + return 1 + return 2 + + +# --------------------------------------------------------------------------- +# Data preparation +# --------------------------------------------------------------------------- + +def load_panel(cache_dir: str = CACHE_DIR) -> pd.DataFrame: + """Load the cached panel parquet produced by calibrate_noise_hierarchical.py --fetch.""" + panel_path = os.path.join(cache_dir, "panel.parquet") + if not os.path.exists(panel_path): + print(f"ERROR: Panel cache not found at {panel_path}", file=sys.stderr) + print("Run: python scripts/calibrate_noise_hierarchical.py --fetch", file=sys.stderr) + sys.exit(1) + + panel = pd.read_parquet(panel_path) + + # Filter pools with < 10 observations + pool_counts = panel.groupby("pool_id").size() + valid_pools = pool_counts[pool_counts >= 10].index + panel = panel[panel["pool_id"].isin(valid_pools)].copy() + + print(f" Loaded panel: {len(panel)} obs, " + f"{panel['pool_id'].nunique()} pools, " + f"{panel['chain'].nunique()} chains") + return panel + + +def _build_z_pool(pool_meta: pd.DataFrame) -> np.ndarray: + """Build (N_pools, D) pool-level covariate matrix. + + Columns: [1, chain_BASE, ..., chain_SONIC (6), + tier_A_1, tier_A_2, tier_B_1, tier_B_2, log_fee] + """ + N = len(pool_meta) + z = np.zeros((N, D), dtype=np.float64) + + # Intercept + z[:, 0] = 1.0 + + # Chain dummies (columns 1-6) + for j, chain in enumerate(CHAIN_ORDER): + z[:, 1 + j] = (pool_meta["chain"].values == chain).astype(float) + + # tier_A dummies (columns 7-8): tiers 1 and 2, reference = 0 + tier_a = pool_meta["tier_A"].values.astype(int) + z[:, 7] = (tier_a == 1).astype(float) + z[:, 8] = (tier_a == 2).astype(float) + + # tier_B dummies (columns 9-10): tiers 1 and 2, reference = 0 + tier_b = pool_meta["tier_B"].values.astype(int) + z[:, 9] = (tier_b == 1).astype(float) + z[:, 10] = (tier_b == 2).astype(float) + + # log_fee (column 11) + z[:, 11] = np.log(np.maximum(pool_meta["swap_fee"].values.astype(float), 1e-6)) + + return z + + +def prepare_data(panel: pd.DataFrame) -> dict: + """Construct JAX-ready arrays from the panel DataFrame. + + Returns dict with: + pool_idx : (N_obs,) int32 — pool index per observation + z_pool : (N_pools, D) float64 — pool-level covariates + x_obs : (N_obs, K) float64 — within-day regressors + y_obs : (N_obs,) float64 — log_volume + pool_ids : list — ordered pool IDs + pool_meta : DataFrame — per-pool metadata (indexed same as z_pool rows) + """ + # Stable pool ordering + pool_ids = sorted(panel["pool_id"].unique()) + pool_id_to_idx = {pid: i for i, pid in enumerate(pool_ids)} + N_pools = len(pool_ids) + + # Pool-level metadata (one row per pool) + pool_meta = panel.drop_duplicates("pool_id").set_index("pool_id").loc[pool_ids].reset_index() + z_pool = _build_z_pool(pool_meta) + + # Observation-level arrays + pool_idx = panel["pool_id"].map(pool_id_to_idx).values.astype(np.int32) + x_obs = np.column_stack([ + np.ones(len(panel)), + panel["log_tvl"].values, + panel["volatility"].values, + panel["weekend"].values, + ]).astype(np.float64) + y_obs = panel["log_volume"].values.astype(np.float64) + + print(f" Prepared: N_obs={len(y_obs)}, N_pools={N_pools}, K={K}, D={D}") + print(f" z_pool range check — log_fee: [{z_pool[:, 11].min():.2f}, {z_pool[:, 11].max():.2f}]") + + return { + "pool_idx": pool_idx, + "z_pool": z_pool, + "x_obs": x_obs, + "y_obs": y_obs, + "pool_ids": pool_ids, + "pool_meta": pool_meta, + "N_pools": N_pools, + } + + +# --------------------------------------------------------------------------- +# NumPyro model +# --------------------------------------------------------------------------- + +def hierarchical_noise_model(pool_idx, z_pool, x_obs, y_obs=None, + N_pools=None, K=4, D=12): + """Bayesian hierarchical noise volume model. + + Non-centered parameterization with LKJ correlation prior. + """ + import jax.numpy as jnp + import numpyro + import numpyro.distributions as dist + + N_obs = pool_idx.shape[0] + + # --- Population coefficient matrix B: (K, D) --- + B = numpyro.sample("B", dist.Normal(0.0, 5.0).expand([K, D]).to_event(2)) + + # --- Per-pool scale and correlation --- + sigma = numpyro.sample("sigma", dist.HalfNormal(2.0).expand([K]).to_event(1)) + L_Omega = numpyro.sample("L_Omega", dist.LKJCholesky(K, concentration=2.0)) + + # Cholesky factor of covariance: diag(sigma) @ L_Omega + L_Sigma = jnp.diag(sigma) @ L_Omega # (K, K) + + # --- Non-centered pool effects --- + with numpyro.plate("pools", N_pools): + eta = numpyro.sample("eta", dist.Normal(0.0, 1.0).expand([K]).to_event(1)) + + # theta_i = B @ z_i + L_Sigma @ eta_i for each pool i + # mu: (N_pools, K) = z_pool @ B^T + mu = z_pool @ B.T # (N_pools, K) + theta = mu + eta @ L_Sigma.T # (N_pools, K) + + # --- Observation model --- + sigma_eps = numpyro.sample("sigma_eps", dist.HalfNormal(3.0)) + + # Predicted log-volume: theta[pool_idx] . x_obs (dot product per obs) + theta_obs = theta[pool_idx] # (N_obs, K) + mu_obs = jnp.sum(theta_obs * x_obs, axis=1) # (N_obs,) + + with numpyro.plate("obs", N_obs): + numpyro.sample("y", dist.Normal(mu_obs, sigma_eps), obs=y_obs) + + # Deterministic: store theta for extraction + numpyro.deterministic("theta", theta) + + +# --------------------------------------------------------------------------- +# Inference +# --------------------------------------------------------------------------- + +def run_inference(data, num_warmup=1000, num_samples=2000, num_chains=4, + target_accept=0.85, max_tree_depth=10, seed=42): + """Run NUTS on the hierarchical model. + + Returns the MCMC object with samples. + """ + import jax + import jax.numpy as jnp + import numpyro + from numpyro.infer import MCMC, NUTS + + # Use all available CPU cores for chains + numpyro.set_host_device_count(min(num_chains, len(jax.devices("cpu")))) + + kernel = NUTS( + hierarchical_noise_model, + target_accept_prob=target_accept, + max_tree_depth=max_tree_depth, + ) + mcmc = MCMC( + kernel, + num_warmup=num_warmup, + num_samples=num_samples, + num_chains=num_chains, + progress_bar=True, + ) + + rng_key = jax.random.PRNGKey(seed) + + print(f"\n Running NUTS: {num_chains} chains x " + f"({num_warmup} warmup + {num_samples} samples)") + print(f" target_accept={target_accept}, max_tree_depth={max_tree_depth}") + + mcmc.run( + rng_key, + pool_idx=jnp.array(data["pool_idx"]), + z_pool=jnp.array(data["z_pool"]), + x_obs=jnp.array(data["x_obs"]), + y_obs=jnp.array(data["y_obs"]), + N_pools=data["N_pools"], + K=K, + D=D, + ) + + mcmc.print_summary(exclude_deterministic=True) + return mcmc + + +# --------------------------------------------------------------------------- +# Post-processing +# --------------------------------------------------------------------------- + +def extract_noise_params(mcmc, data) -> list: + """Extract per-pool noise params from MCMC posterior. + + Reconstructs theta from the non-centered parameterization, + takes posterior medians, and applies weekend absorption: + b_0_effective = b_0_raw + b_weekend * (2/7) + + Returns list of dicts compatible with reclamm_loglinear_noise_volume. + """ + samples = mcmc.get_samples() + theta_samples = samples["theta"] # (n_samples, N_pools, K) + + # Posterior median per pool + theta_median = np.median(theta_samples, axis=0) # (N_pools, K) + theta_std = np.std(theta_samples, axis=0) # (N_pools, K) + + pool_ids = data["pool_ids"] + pool_meta = data["pool_meta"] + + results = [] + for i, pool_id in enumerate(pool_ids): + meta = pool_meta.iloc[i] + b_0_raw, b_tvl, b_sigma, b_weekend = theta_median[i] + std_vals = theta_std[i] + + # Weekend absorption: simulator has no weekend indicator, + # so fold the expected weekend effect into the intercept. + # Weekend days = 2/7 of all days. + b_0_effective = b_0_raw + b_weekend * (2.0 / 7.0) + + tokens = meta["tokens"] + if isinstance(tokens, str): + tokens = tokens.split(",") + + results.append({ + "pool_id": pool_id, + "chain": str(meta["chain"]), + "tokens": tokens, + "theta_median": [float(x) for x in theta_median[i]], + "theta_std": [float(x) for x in std_vals], + "noise_params": { + "b_0": float(b_0_effective), + "b_sigma": float(b_sigma), + "b_c": float(b_tvl), + "b_weekend": float(b_weekend), + "base_fee": float(meta["swap_fee"]), + }, + }) + + return results + + +def predict_new_pool(mcmc, data, chain: str, tokens: list, fee: float) -> dict: + """Predict noise params for an unseen pool using population effects. + + Constructs z_new, computes mu_new = B @ z_new across posterior samples, + and returns median + 90% credible intervals. + """ + # Build z_new + z_new = np.zeros(D, dtype=np.float64) + z_new[0] = 1.0 # intercept + + # Chain dummies + if chain in CHAIN_ORDER: + j = CHAIN_ORDER.index(chain) + z_new[1 + j] = 1.0 + + # Tier dummies + tiers = sorted([classify_token_tier(t) for t in tokens]) + tier_a = tiers[0] + tier_b = tiers[1] if len(tiers) > 1 else tiers[0] + if tier_a == 1: + z_new[7] = 1.0 + elif tier_a == 2: + z_new[8] = 1.0 + if tier_b == 1: + z_new[9] = 1.0 + elif tier_b == 2: + z_new[10] = 1.0 + + # log_fee + z_new[11] = np.log(max(fee, 1e-6)) + + # Compute mu_new = B @ z_new across all posterior samples + B_samples = np.array(mcmc.get_samples()["B"]) # (n_samples, K, D) + mu_samples = np.einsum("skd,d->sk", B_samples, z_new) # (n_samples, K) + + mu_median = np.median(mu_samples, axis=0) + mu_q05 = np.percentile(mu_samples, 5, axis=0) + mu_q95 = np.percentile(mu_samples, 95, axis=0) + + # Weekend absorption + b_0_raw, b_tvl, b_sigma, b_weekend = mu_median + b_0_effective = b_0_raw + b_weekend * (2.0 / 7.0) + + result = { + "chain": chain, + "tokens": tokens, + "fee": fee, + "prediction_source": "population_level", + "noise_params": { + "b_0": float(b_0_effective), + "b_sigma": float(b_sigma), + "b_c": float(b_tvl), + "b_weekend": float(b_weekend), + "base_fee": float(fee), + }, + "credible_intervals_90": { + name: { + "median": float(mu_median[k]), + "q05": float(mu_q05[k]), + "q95": float(mu_q95[k]), + } + for k, name in enumerate(COEFF_NAMES) + }, + } + + print(f"\n Predicted noise_params for {chain} {tokens} (fee={fee}):") + for name, ci in result["credible_intervals_90"].items(): + print(f" {name:12s}: {ci['median']:+.3f} " + f"[{ci['q05']:+.3f}, {ci['q95']:+.3f}]") + print(f"\n Effective b_0 (weekend-absorbed): {b_0_effective:.3f}") + + return result + + +# --------------------------------------------------------------------------- +# Diagnostics +# --------------------------------------------------------------------------- + +def check_convergence(mcmc) -> dict: + """Compute convergence diagnostics: R-hat, ESS, divergences.""" + import arviz as az + + idata = az.from_numpyro(mcmc) + + # R-hat and ESS for non-deterministic parameters + n_chains = idata.posterior.sizes.get("chain", 1) + + rhat_max = float("nan") + if n_chains >= 2: + rhat = az.rhat(idata) + rhat_vals = [] + for var in rhat.data_vars: + if var == "theta": + continue # deterministic + vals = rhat[var].values + rhat_vals.extend(vals.flatten()) + rhat_max = float(np.nanmax(rhat_vals)) if rhat_vals else float("nan") + + ess = az.ess(idata) + ess_vals = [] + for var in ess.data_vars: + if var == "theta": + continue + vals = ess[var].values + ess_vals.extend(vals.flatten()) + ess_min = float(np.nanmin(ess_vals)) if ess_vals else float("nan") + + # Divergences + divergences = int(idata.sample_stats["diverging"].sum().values) + + print(f"\n Convergence diagnostics:") + if n_chains >= 2: + print(f" R-hat max: {rhat_max:.4f} {'OK' if rhat_max < 1.05 else 'WARNING'}") + else: + print(f" R-hat max: N/A (need >= 2 chains)") + print(f" ESS min: {ess_min:.0f} {'OK' if ess_min > 400 else 'WARNING'}") + print(f" Divergences: {divergences} {'OK' if divergences == 0 else 'WARNING'}") + + return { + "r_hat_max": rhat_max, + "ess_min": ess_min, + "divergences": divergences, + } + + +def plot_bayesian_diagnostics(mcmc, data, output_dir="results"): + """Generate ArviZ diagnostic plots.""" + import matplotlib + matplotlib.use("Agg") + import matplotlib.pyplot as plt + import arviz as az + + os.makedirs(output_dir, exist_ok=True) + idata = az.from_numpyro(mcmc) + samples = mcmc.get_samples() + + # --- 1. Trace plots for sigma, sigma_eps --- + axes = az.plot_trace(idata, var_names=["sigma", "sigma_eps"], compact=True) + fig1 = axes.ravel()[0].figure + fig1.set_size_inches(14, 8) + path1 = os.path.join(output_dir, "bayesian_trace_sigma.png") + fig1.savefig(path1, dpi=150, bbox_inches="tight") + plt.close(fig1) + print(f" Saved: {path1}") + + # --- 2. Posterior predictive: predicted vs observed --- + theta_samples = samples["theta"] # (S, N_pools, K) + sigma_eps_samples = np.array(samples["sigma_eps"]) # (S,) + theta_median = np.median(theta_samples, axis=0) # (N_pools, K) + + pool_idx = data["pool_idx"] + x_obs = data["x_obs"] + y_obs = data["y_obs"] + + theta_obs = theta_median[pool_idx] + y_pred = np.sum(theta_obs * x_obs, axis=1) + + fig, axes = plt.subplots(1, 2, figsize=(14, 6)) + + ax = axes[0] + ax.scatter(y_obs, y_pred, alpha=0.1, s=4, color="steelblue") + lims = [min(y_obs.min(), y_pred.min()), max(y_obs.max(), y_pred.max())] + ax.plot(lims, lims, "r--", linewidth=1) + ax.set_xlabel("Observed log(volume)") + ax.set_ylabel("Predicted log(volume)") + ax.set_title("Posterior predictive check") + r2 = 1 - np.var(y_obs - y_pred) / np.var(y_obs) + ax.text(0.05, 0.95, f"R² = {r2:.3f}", transform=ax.transAxes, + fontsize=11, verticalalignment="top") + + ax = axes[1] + residuals = y_obs - y_pred + ax.hist(residuals, bins=60, color="steelblue", edgecolor="white", alpha=0.8) + ax.axvline(0, color="red", linestyle="--") + ax.set_xlabel("Residual") + ax.set_title(f"Residual distribution (σ_ε ≈ {np.median(sigma_eps_samples):.2f})") + + plt.tight_layout() + path2 = os.path.join(output_dir, "bayesian_posterior_predictive.png") + plt.savefig(path2, dpi=150, bbox_inches="tight") + plt.close() + print(f" Saved: {path2}") + + # --- 3. Per-pool b_c (TVL elasticity) by chain/tier --- + pool_meta = data["pool_meta"] + b_tvl_all = theta_median[:, 1] # index 1 = b_tvl + + fig, axes = plt.subplots(1, 2, figsize=(14, 6)) + + ax = axes[0] + chains_present = sorted(pool_meta["chain"].unique()) + chain_data = [] + chain_labels = [] + for c in chains_present: + mask = pool_meta["chain"].values == c + if mask.sum() > 0: + chain_data.append(b_tvl_all[mask]) + chain_labels.append(f"{c}\n(n={mask.sum()})") + ax.boxplot(chain_data, tick_labels=chain_labels, vert=True) + ax.axhline(1.0, color="red", linestyle="--", linewidth=0.8, alpha=0.6) + ax.set_ylabel("Per-pool b_c (TVL elasticity)") + ax.set_title("TVL elasticity by chain") + + ax = axes[1] + # By tier_A + tier_a_vals = pool_meta["tier_A"].values.astype(int) + tier_labels_map = {0: "Blue-chip", 1: "Mid-cap", 2: "Long-tail"} + tier_data = [] + tier_labels = [] + for t in [0, 1, 2]: + mask = tier_a_vals == t + if mask.sum() > 0: + tier_data.append(b_tvl_all[mask]) + tier_labels.append(f"{tier_labels_map[t]}\n(n={mask.sum()})") + ax.boxplot(tier_data, tick_labels=tier_labels, vert=True) + ax.axhline(1.0, color="red", linestyle="--", linewidth=0.8, alpha=0.6) + ax.set_ylabel("Per-pool b_c (TVL elasticity)") + ax.set_title("TVL elasticity by token tier (best token)") + + plt.tight_layout() + path3 = os.path.join(output_dir, "bayesian_per_pool_b_c.png") + plt.savefig(path3, dpi=150, bbox_inches="tight") + plt.close() + print(f" Saved: {path3}") + + # --- 4. Correlation matrix posterior --- + L_Omega_samples = np.array(samples["L_Omega"]) # (S, K, K) + # Correlation = L @ L^T + Omega_samples = np.einsum("sij,skj->sik", L_Omega_samples, L_Omega_samples) + Omega_median = np.median(Omega_samples, axis=0) + + fig, ax = plt.subplots(figsize=(7, 6)) + im = ax.imshow(Omega_median, vmin=-1, vmax=1, cmap="RdBu_r") + ax.set_xticks(range(K)) + ax.set_yticks(range(K)) + ax.set_xticklabels(COEFF_NAMES, rotation=45, ha="right") + ax.set_yticklabels(COEFF_NAMES) + for i in range(K): + for j in range(K): + ax.text(j, i, f"{Omega_median[i, j]:.2f}", ha="center", va="center", + fontsize=10, color="white" if abs(Omega_median[i, j]) > 0.5 else "black") + plt.colorbar(im, ax=ax, shrink=0.8) + ax.set_title("Posterior median correlation matrix (Ω)") + plt.tight_layout() + path4 = os.path.join(output_dir, "bayesian_correlation_matrix.png") + plt.savefig(path4, dpi=150, bbox_inches="tight") + plt.close() + print(f" Saved: {path4}") + + # --- 5. Shrinkage plot: OLS b_c vs hierarchical b_c --- + # Compute per-pool OLS b_c for comparison + panel_meta = data["pool_meta"] + pool_idx_arr = data["pool_idx"] + pool_ids = data["pool_ids"] + + ols_b_c = np.zeros(len(pool_ids)) + for i, pid in enumerate(pool_ids): + mask = pool_idx_arr == i + if mask.sum() < 5: + ols_b_c[i] = np.nan + continue + x_i = data["x_obs"][mask] + y_i = data["y_obs"][mask] + # Simple OLS: y = X @ beta + try: + beta, _, _, _ = np.linalg.lstsq(x_i, y_i, rcond=None) + ols_b_c[i] = beta[1] # TVL coefficient + except np.linalg.LinAlgError: + ols_b_c[i] = np.nan + + hier_b_c = theta_median[:, 1] + valid = np.isfinite(ols_b_c) + + fig, ax = plt.subplots(figsize=(8, 8)) + ax.scatter(ols_b_c[valid], hier_b_c[valid], alpha=0.6, s=20, color="steelblue") + + # Population mean line + pop_b_c = np.median(hier_b_c) + ax.axhline(pop_b_c, color="red", linestyle="--", linewidth=0.8, + label=f"Population median = {pop_b_c:.3f}") + + # 45-degree line + lims = [min(np.nanmin(ols_b_c[valid]), hier_b_c[valid].min()) - 0.2, + max(np.nanmax(ols_b_c[valid]), hier_b_c[valid].max()) + 0.2] + ax.plot(lims, lims, "k:", linewidth=0.8, alpha=0.5) + ax.set_xlabel("Per-pool OLS b_c") + ax.set_ylabel("Hierarchical posterior median b_c") + ax.set_title("Shrinkage: OLS vs hierarchical TVL elasticity") + ax.legend() + plt.tight_layout() + path5 = os.path.join(output_dir, "bayesian_shrinkage_b_c.png") + plt.savefig(path5, dpi=150, bbox_inches="tight") + plt.close() + print(f" Saved: {path5}") + + +# --------------------------------------------------------------------------- +# JSON output +# --------------------------------------------------------------------------- + +def generate_output_json(pool_params, mcmc, data, convergence, output_path, + num_warmup, num_samples, num_chains, target_accept): + """Write structured JSON output with population effects and per-pool params.""" + samples = mcmc.get_samples() + + B_median = np.median(np.array(samples["B"]), axis=0).tolist() + sigma_median = np.median(np.array(samples["sigma"]), axis=0).tolist() + sigma_eps_median = float(np.median(np.array(samples["sigma_eps"]))) + + output = { + "model": "bayesian_hierarchical_loglinear", + "inference": { + "method": "NUTS", + "num_warmup": num_warmup, + "num_samples": num_samples, + "num_chains": num_chains, + "target_accept_prob": target_accept, + }, + "population_effects": { + "B": B_median, + "sigma": sigma_median, + "sigma_eps": sigma_eps_median, + "coeff_names": COEFF_NAMES, + "covariate_names": ( + ["intercept"] + [f"chain_{c}" for c in CHAIN_ORDER] + + ["tier_A_1", "tier_A_2", "tier_B_1", "tier_B_2", "log_fee"] + ), + }, + "convergence": convergence, + "pools": { + p["pool_id"]: { + "chain": p["chain"], + "tokens": p["tokens"], + "theta_median": p["theta_median"], + "theta_std": p["theta_std"], + "noise_params": p["noise_params"], + } + for p in pool_params + }, + } + + os.makedirs(os.path.dirname(output_path) or ".", exist_ok=True) + with open(output_path, "w") as f: + json.dump(output, f, indent=2, default=str) + print(f" Wrote {len(pool_params)} pool params -> {output_path}") + + +# --------------------------------------------------------------------------- +# CLI +# --------------------------------------------------------------------------- + +def main(): + parser = argparse.ArgumentParser( + description="Bayesian hierarchical noise volume model for Balancer pools" + ) + parser.add_argument( + "--fetch", action="store_true", + help="Fetch pool data (delegates to calibrate_noise_hierarchical.py --fetch)", + ) + parser.add_argument("--fit", action="store_true", help="Run NUTS inference") + parser.add_argument("--plot", action="store_true", help="Generate diagnostic plots") + parser.add_argument("--output", default=None, help="Output JSON path") + parser.add_argument("--output-dir", default="results", help="Plot output directory") + parser.add_argument("--predict", action="store_true", help="Predict for a new pool") + parser.add_argument("--chain", default=None, help="Chain for --predict") + parser.add_argument("--tokens", nargs="+", default=None, help="Tokens for --predict") + parser.add_argument("--fee", type=float, default=0.003, help="Fee for --predict") + parser.add_argument("--cache-dir", default=None, help="Cache directory") + + # NUTS hyperparameters + parser.add_argument("--num-warmup", type=int, default=1000) + parser.add_argument("--num-samples", type=int, default=2000) + parser.add_argument("--num-chains", type=int, default=4) + parser.add_argument("--target-accept", type=float, default=0.85) + parser.add_argument("--max-tree-depth", type=int, default=10) + parser.add_argument("--seed", type=int, default=42) + + args = parser.parse_args() + + cache_dir = args.cache_dir or CACHE_DIR + + if not any([args.fetch, args.fit, args.predict]): + parser.error("At least one of --fetch, --fit, --predict is required") + + # --- Fetch (delegate to existing script) --- + if args.fetch: + import subprocess + cmd = [ + sys.executable, "scripts/calibrate_noise_hierarchical.py", + "--fetch", "--cache-dir", cache_dir, + ] + print("Delegating data fetch to calibrate_noise_hierarchical.py...") + subprocess.run(cmd, check=True) + + # --- Fit --- + if args.fit: + print("\nBayesian Hierarchical Noise Volume Model") + print("=" * 60) + + panel = load_panel(cache_dir) + data = prepare_data(panel) + + mcmc = run_inference( + data, + num_warmup=args.num_warmup, + num_samples=args.num_samples, + num_chains=args.num_chains, + target_accept=args.target_accept, + max_tree_depth=args.max_tree_depth, + seed=args.seed, + ) + + convergence = check_convergence(mcmc) + pool_params = extract_noise_params(mcmc, data) + + # Print summary statistics + b_c_vals = [p["noise_params"]["b_c"] for p in pool_params] + b_0_vals = [p["noise_params"]["b_0"] for p in pool_params] + print(f"\n Per-pool b_c: mean={np.mean(b_c_vals):.3f}, " + f"std={np.std(b_c_vals):.3f}, " + f"range=[{np.min(b_c_vals):.3f}, {np.max(b_c_vals):.3f}]") + print(f" Per-pool b_0: mean={np.mean(b_0_vals):.3f}, " + f"std={np.std(b_0_vals):.3f}") + + if args.output: + generate_output_json( + pool_params, mcmc, data, convergence, args.output, + args.num_warmup, args.num_samples, args.num_chains, + args.target_accept, + ) + + if args.plot: + print("\nGenerating diagnostic plots...") + plot_bayesian_diagnostics(mcmc, data, output_dir=args.output_dir) + + # Save MCMC samples for --predict reuse + mcmc_cache = os.path.join(cache_dir, "bayesian_mcmc_samples.npz") + samples = mcmc.get_samples() + np.savez_compressed( + mcmc_cache, + **{k: np.array(v) for k, v in samples.items()}, + ) + # Also save data arrays for predict + data_cache = os.path.join(cache_dir, "bayesian_data.npz") + np.savez_compressed( + data_cache, + pool_idx=data["pool_idx"], + z_pool=data["z_pool"], + x_obs=data["x_obs"], + y_obs=data["y_obs"], + ) + # Save pool_ids list + with open(os.path.join(cache_dir, "bayesian_pool_ids.json"), "w") as f: + json.dump(data["pool_ids"], f) + print(f" Saved MCMC samples -> {mcmc_cache}") + + # --- Predict --- + if args.predict: + if args.chain is None or args.tokens is None: + parser.error("--predict requires --chain and --tokens") + + # Load cached MCMC samples + mcmc_cache = os.path.join(cache_dir, "bayesian_mcmc_samples.npz") + if not os.path.exists(mcmc_cache): + print(f"ERROR: MCMC cache not found at {mcmc_cache}", file=sys.stderr) + print("Run with --fit first.", file=sys.stderr) + sys.exit(1) + + # For predict, we only need B samples — create a minimal mock + cached = np.load(mcmc_cache) + + class _MockMCMC: + """Minimal interface to reuse predict_new_pool with cached samples.""" + def __init__(self, samples_dict): + self._samples = samples_dict + def get_samples(self): + return self._samples + + samples_dict = {k: cached[k] for k in cached.files} + mock_mcmc = _MockMCMC(samples_dict) + + result = predict_new_pool(mock_mcmc, None, args.chain, args.tokens, args.fee) + print(json.dumps(result, indent=2)) + + +if __name__ == "__main__": + main() diff --git a/scripts/calibrate_noise_hierarchical.py b/scripts/calibrate_noise_hierarchical.py new file mode 100644 index 0000000..4fef642 --- /dev/null +++ b/scripts/calibrate_noise_hierarchical.py @@ -0,0 +1,1556 @@ +"""Bayesian hierarchical noise volume model across Balancer WEIGHTED + RECLAMM pools. + +Pools data cross-sectionally across all Balancer weighted/reCLAMM pools, +fits a Bayesian hierarchical model where pool covariates (chain, token tier, +fee) modulate all coefficients via group-level regression, with full +posterior inference via NumPyro. + +Model: + Hyperpriors: + Φ ~ Normal(0, 2) (K × 3) group-level regression + σ_θ ~ HalfNormal(2) (3,) per-coefficient scales + L_ω ~ LKJCholesky(3, η=2) correlation structure + β_weekend ~ Normal(0, 2) shared nuisance + σ_ε ~ HalfNormal(3) observation noise + + For each pool i: + x_i = [1, chain_dummies, tier_dummies, log_fee] (K,) covariates + z_i ~ N(0, I₃) non-centered + θ_i = Φᵀx_i + diag(σ_θ)·L_ω·z_i (α_i, β_tvl_i, β_vol_i) + + For each observation (i, t): + log(V) ~ N(α_i + β_tvl_i·log_tvl + β_vol_i·vol + β_weekend·weekend, σ²_ε) + +Usage: + # Full pipeline: fetch data + fit model + output + python scripts/calibrate_noise_hierarchical.py \\ + --fetch --fit --output results/hierarchical_noise_params.json --plot + + # Use cached data, re-fit only + python scripts/calibrate_noise_hierarchical.py \\ + --fit --output results/hierarchical_noise_params.json + + # Predict for a new pool + python scripts/calibrate_noise_hierarchical.py \\ + --predict --chain BASE --tokens ETH BTC --fee 0.003 + + # Use NUTS instead of SVI + python scripts/calibrate_noise_hierarchical.py \\ + --fit --nuts --output results/hierarchical_noise_params.json +""" + +import argparse +import json +import os +import sys +import time +import urllib.request +from datetime import datetime, timezone + +import numpy as np +import pandas as pd + +import jax +import jax.numpy as jnp +import numpyro +import numpyro.distributions as dist +from numpyro.infer import SVI, MCMC, NUTS, Trace_ELBO, Predictive +from numpyro.infer.autoguide import AutoMultivariateNormal + +numpyro.enable_x64() + + +# --------------------------------------------------------------------------- +# Constants +# --------------------------------------------------------------------------- + +BALANCER_API_URL = "https://api-v3.balancer.fi/" + +BALANCER_API_CHAINS = [ + "MAINNET", "POLYGON", "ARBITRUM", "GNOSIS", "BASE", "SONIC", "OPTIMISM", + "AVALANCHE", +] + +CACHE_DIR = os.path.join( + os.path.dirname(os.path.dirname(__file__)), "local_data", "noise_calibration" +) + +# --------------------------------------------------------------------------- +# Token tier classification +# --------------------------------------------------------------------------- + +# Tier 0: blue-chip — top by volume, wrapped native, major stables +_TIER_0 = { + "ETH", "WETH", "BTC", "WBTC", "cbBTC", "USDC", "USDT", "DAI", + "wstETH", "stETH", "rETH", "cbETH", "WMATIC", "MATIC", "POL", + "WAVAX", "AVAX", "GNO", "WXDAI", "xDAI", + "S", "wS", # Sonic native +} + +# Tier 1: mid-cap DeFi blue-chips (approx CoinGecko rank < 200) +_TIER_1 = { + "AAVE", "LINK", "UNI", "BAL", "MKR", "CRV", "COMP", "SNX", + "LDO", "RPL", "SUSHI", "YFI", "1INCH", "ENS", "DYDX", + "FXS", "FRAX", "LUSD", "sDAI", "GHO", "crvUSD", + "ARB", "OP", "PENDLE", "ENA", "EIGEN", + "SAFE", "COW", +} + + +def _normalise_symbol(symbol: str) -> str: + """Normalise wrapped/bridged variants to canonical form.""" + s = symbol.strip() + # Common wrapped → unwrapped + mapping = { + "WETH": "WETH", # keep WETH as-is (it's in tier 0) + "WBTC": "WBTC", + "cbBTC": "cbBTC", + "WMATIC": "WMATIC", + "WAVAX": "WAVAX", + "WXDAI": "WXDAI", + "wS": "wS", + } + return mapping.get(s, s) + + +def classify_token_tier(symbol: str) -> int: + """Classify a token symbol into tier 0/1/2. + + Returns + ------- + int + 0 = blue-chip, 1 = mid-cap, 2 = long-tail + """ + s = _normalise_symbol(symbol) + if s in _TIER_0: + return 0 + if s in _TIER_1: + return 1 + return 2 + + +# --------------------------------------------------------------------------- +# Phase 1: API data ingestion +# --------------------------------------------------------------------------- + +def _graphql_request(query: dict, base_url: str = BALANCER_API_URL, + timeout: int = 30) -> dict: + """Send a GraphQL request to the Balancer V3 API.""" + data = json.dumps(query).encode("utf-8") + req = urllib.request.Request( + base_url, + data=data, + headers={ + "Content-Type": "application/json", + "User-Agent": "quantammsim/1.0", + }, + ) + with urllib.request.urlopen(req, timeout=timeout) as resp: + return json.loads(resp.read().decode("utf-8")) + + +def enumerate_balancer_pools( + chains: list = None, + pool_types: list = None, + min_tvl: float = 10000.0, +) -> pd.DataFrame: + """Enumerate all WEIGHTED + RECLAMM pools across chains from Balancer API. + + Parameters + ---------- + chains : list of str + API chain identifiers (e.g. ["MAINNET", "BASE"]). + pool_types : list of str + Pool type filters (e.g. ["WEIGHTED", "STABLE"]). + min_tvl : float + Minimum TVL in USD to include. + + Returns + ------- + pd.DataFrame + Columns: pool_id, chain, pool_type, tokens (list of symbols), + swap_fee, create_time, dynamic_data_tvl. + """ + if chains is None: + chains = BALANCER_API_CHAINS + if pool_types is None: + pool_types = ["WEIGHTED", "RECLAMM"] + + all_pools = [] + for chain in chains: + print(f" Querying {chain}...", end=" ", flush=True) + query = { + "query": """ + query GetPools($chain: GqlChain!, $types: [GqlPoolType!], + $minTvl: Float) { + poolGetPools( + where: { + chainIn: [$chain] + poolTypeIn: $types + minTvl: $minTvl + } + ) { + id + chain + type + createTime + protocolVersion + poolTokens { + symbol + weight + address + } + dynamicData { + totalLiquidity + swapFee + } + } + } + """, + "variables": { + "chain": chain, + "types": pool_types, + "minTvl": min_tvl, + }, + } + + try: + body = _graphql_request(query) + pools = body.get("data", {}).get("poolGetPools", []) + except Exception as e: + print(f"FAILED ({e})") + continue + + for p in pools: + tokens = [t["symbol"] for t in p.get("poolTokens", [])] + weights = [t.get("weight") for t in p.get("poolTokens", [])] + token_addresses = [t.get("address", "") for t in p.get("poolTokens", [])] + tvl = float(p.get("dynamicData", {}).get("totalLiquidity", 0)) + fee = float(p.get("dynamicData", {}).get("swapFee", 0)) + + all_pools.append({ + "pool_id": p["id"], + "chain": p["chain"], + "pool_type": p["type"], + "protocol_version": p.get("protocolVersion", 0), + "tokens": tokens, + "token_addresses": token_addresses, + "weights": weights, + "swap_fee": fee, + "create_time": p.get("createTime", 0), + "current_tvl": tvl, + }) + + print(f"{len(pools)} pools") + time.sleep(0.3) + + df = pd.DataFrame(all_pools) + print(f"\n Total: {len(df)} pools across {len(chains)} chains") + return df + + +def fetch_pool_snapshots(pool_id: str, chain: str, + base_url: str = BALANCER_API_URL) -> pd.DataFrame: + """Fetch ALL_TIME daily snapshots for a single pool. + + Returns + ------- + pd.DataFrame + Columns: timestamp, volume_usd, total_liquidity_usd. + """ + query = { + "query": """ + query GetSnapshots($poolId: String!, $chain: GqlChain!, + $range: GqlPoolSnapshotDataRange!) { + poolGetSnapshots(id: $poolId, chain: $chain, range: $range) { + timestamp + volume24h + totalLiquidity + } + } + """, + "variables": { + "poolId": pool_id, + "chain": chain, + "range": "ALL_TIME", + }, + } + + body = _graphql_request(query) + snapshots = body.get("data", {}).get("poolGetSnapshots", []) + + if not snapshots: + return pd.DataFrame(columns=["timestamp", "volume_usd", "total_liquidity_usd"]) + + records = [] + for snap in snapshots: + records.append({ + "timestamp": int(snap["timestamp"]), + "volume_usd": float(snap["volume24h"]), + "total_liquidity_usd": float(snap["totalLiquidity"]), + }) + + df = pd.DataFrame(records) + df["date"] = pd.to_datetime(df["timestamp"], unit="s").dt.date + # Deduplicate by date (keep last snapshot per day) + df = df.sort_values("timestamp").drop_duplicates("date", keep="last") + return df + + +def fetch_all_snapshots(pools_df: pd.DataFrame, + cache_path: str = None) -> pd.DataFrame: + """Fetch daily snapshots for all pools, with caching. + + Parameters + ---------- + pools_df : pd.DataFrame + Pool enumeration from enumerate_balancer_pools. + cache_path : str, optional + Path to parquet cache. If it exists, only fetch missing pools. + + Returns + ------- + pd.DataFrame + Panel with columns: pool_id, chain, date, volume_usd, + total_liquidity_usd. + """ + # Load cache if exists + cached = pd.DataFrame() + cached_pool_ids = set() + if cache_path and os.path.exists(cache_path): + cached = pd.read_parquet(cache_path) + cached_pool_ids = set(cached["pool_id"].unique()) + print(f" Cache has {len(cached_pool_ids)} pools, " + f"{len(cached)} pool-days") + + # Determine which pools need fetching + if len(pools_df) == 0: + print(" No pools to fetch.") + return cached if len(cached) > 0 else pd.DataFrame( + columns=["pool_id", "chain", "date", "volume_usd", + "total_liquidity_usd"] + ) + to_fetch = pools_df[~pools_df["pool_id"].isin(cached_pool_ids)] + print(f" Need to fetch {len(to_fetch)} new pools") + + new_records = [] + for i, (_, pool) in enumerate(to_fetch.iterrows()): + if (i + 1) % 10 == 0 or i == 0: + print(f" Fetching {i+1}/{len(to_fetch)}: {pool['pool_id'][:10]}... " + f"({pool['chain']})", flush=True) + try: + snap_df = fetch_pool_snapshots(pool["pool_id"], pool["chain"]) + if len(snap_df) > 0: + snap_df["pool_id"] = pool["pool_id"] + snap_df["chain"] = pool["chain"] + new_records.append(snap_df[ + ["pool_id", "chain", "date", "volume_usd", + "total_liquidity_usd"] + ]) + except Exception as e: + print(f" FAILED {pool['pool_id'][:10]}: {e}") + time.sleep(0.5) # Rate limit + + if new_records: + new_df = pd.concat(new_records, ignore_index=True) + combined = pd.concat([cached, new_df], ignore_index=True) + else: + combined = cached + + # Save cache + if cache_path and len(combined) > 0: + os.makedirs(os.path.dirname(cache_path), exist_ok=True) + combined.to_parquet(cache_path, index=False) + print(f" Saved cache: {len(combined)} pool-days → {cache_path}") + + return combined + + +def fetch_token_prices(token_addresses_by_chain: dict, + cache_dir: str = None) -> dict: + """Fetch hourly token prices from Balancer API. + + Parameters + ---------- + token_addresses_by_chain : dict + {chain: {symbol: address, ...}, ...} + cache_dir : str, optional + Directory for per-token price caches. + + Returns + ------- + dict + {(chain, symbol): pd.DataFrame with columns [timestamp, price], ...} + """ + if cache_dir: + os.makedirs(cache_dir, exist_ok=True) + + prices = {} + + for chain, tokens in token_addresses_by_chain.items(): + # Check cache first, collect uncached addresses + uncached = {} + for symbol, address in tokens.items(): + cache_key = f"{chain}_{symbol}".replace("/", "_") + cp = os.path.join(cache_dir, f"{cache_key}.parquet") if cache_dir else None + + if cp and os.path.exists(cp): + prices[(chain, symbol)] = pd.read_parquet(cp) + else: + uncached[symbol] = address + + if not uncached: + continue + + # Batch fetch: API supports multiple addresses per request + addr_to_symbol = {addr: sym for sym, addr in uncached.items()} + addresses = list(uncached.values()) + + print(f" Fetching {len(addresses)} prices on {chain}...", + flush=True) + + # Batch in groups of 20 to avoid oversized requests + batch_size = 20 + for batch_start in range(0, len(addresses), batch_size): + batch_addrs = addresses[batch_start:batch_start + batch_size] + query = { + "query": """ + query GetPrices($chain: GqlChain!, $addresses: [String!]!, + $range: GqlTokenChartDataRange!) { + tokenGetHistoricalPrices( + addresses: $addresses, chain: $chain, range: $range + ) { + address + prices { + timestamp + price + } + } + } + """, + "variables": { + "chain": chain, + "addresses": batch_addrs, + "range": "ONE_YEAR", + }, + } + + try: + body = _graphql_request(query, timeout=60) + results = body.get("data", {}).get( + "tokenGetHistoricalPrices", []) + for result in results: + addr = result.get("address", "") + price_list = result.get("prices", []) + symbol = addr_to_symbol.get(addr) + if symbol and price_list: + pdf = pd.DataFrame(price_list) + pdf["timestamp"] = pdf["timestamp"].astype(int) + pdf["price"] = pdf["price"].astype(float) + prices[(chain, symbol)] = pdf + if cache_dir: + cache_key = f"{chain}_{symbol}".replace("/", "_") + cp = os.path.join(cache_dir, f"{cache_key}.parquet") + pdf.to_parquet(cp, index=False) + except Exception as e: + print(f" FAILED batch on {chain}: {e}") + + time.sleep(0.5) + + print(f" Got prices for {len(prices)} token-chain pairs") + return prices + + +def compute_pair_volatility( + snapshots_df: pd.DataFrame, + pool_row: pd.Series, + token_prices: dict, +) -> pd.Series: + """Compute daily annualised volatility for a pool's pair ratio. + + Uses hourly prices from the API to compute daily realised volatility. + Falls back to a default of 0.5 if price data is insufficient. + + Parameters + ---------- + snapshots_df : pd.DataFrame + Pool's daily snapshots (need dates). + pool_row : pd.Series + Pool metadata row (need tokens, chain). + token_prices : dict + {(chain, symbol): DataFrame, ...} + + Returns + ------- + pd.Series + Indexed by date, values are annualised daily volatility. + """ + tokens = pool_row["tokens"] + chain = pool_row["chain"] + + if len(tokens) < 2: + return pd.Series(dtype=float) + + # Get price series for token[0] and token[1] + # Try chain-specific first, then any chain + def _get_price_df(symbol): + # Exact match + key = (chain, symbol) + if key in token_prices: + return token_prices[key] + # Any chain + for k, v in token_prices.items(): + if k[1] == symbol: + return v + return None + + p0_df = _get_price_df(tokens[0]) + p1_df = _get_price_df(tokens[1]) + + # If either is a stablecoin, use $1 + stables = {"USDC", "USDT", "DAI", "LUSD", "GHO", "crvUSD", "sDAI", + "WXDAI", "xDAI", "USDC.e", "USDbC"} + + if tokens[0] in stables and tokens[1] in stables: + # Stable-stable pair: near-zero vol + dates = snapshots_df["date"].unique() + return pd.Series(0.01, index=dates) + + if p0_df is None and tokens[0] not in stables: + dates = snapshots_df["date"].unique() + return pd.Series(0.5, index=dates) # fallback + if p1_df is None and tokens[1] not in stables: + dates = snapshots_df["date"].unique() + return pd.Series(0.5, index=dates) # fallback + + # Build hourly price ratio + if tokens[0] in stables: + # ratio = 1 / p1 + if p1_df is None or len(p1_df) == 0: + dates = snapshots_df["date"].unique() + return pd.Series(0.5, index=dates) + ratio_df = p1_df.copy() + ratio_df["ratio"] = 1.0 / ratio_df["price"] + elif tokens[1] in stables: + # ratio = p0 + if p0_df is None or len(p0_df) == 0: + dates = snapshots_df["date"].unique() + return pd.Series(0.5, index=dates) + ratio_df = p0_df.copy() + ratio_df["ratio"] = ratio_df["price"] + else: + # Both non-stable: ratio = p0/p1 + if p0_df is None or p1_df is None or len(p0_df) == 0 or len(p1_df) == 0: + dates = snapshots_df["date"].unique() + return pd.Series(0.5, index=dates) + # Merge on nearest timestamp + merged = pd.merge_asof( + p0_df.sort_values("timestamp"), + p1_df.sort_values("timestamp"), + on="timestamp", + suffixes=("_0", "_1"), + tolerance=7200, # 2 hour tolerance + ).dropna() + if len(merged) == 0: + dates = snapshots_df["date"].unique() + return pd.Series(0.5, index=dates) + ratio_df = merged.copy() + ratio_df["ratio"] = merged["price_0"] / merged["price_1"] + + ratio_df["datetime"] = pd.to_datetime(ratio_df["timestamp"], unit="s") + ratio_df["date"] = ratio_df["datetime"].dt.date + ratio_df = ratio_df.sort_values("timestamp") + + # Log returns + ratio_df["log_return"] = np.log( + ratio_df["ratio"] / ratio_df["ratio"].shift(1) + ) + ratio_df = ratio_df.dropna(subset=["log_return"]) + + # Daily vol from hourly returns, annualised + daily_vol = ratio_df.groupby("date")["log_return"].std() + # Hourly data → ~24 returns/day. Annualise: σ_daily * sqrt(365) + # But std() already gives daily std from hourly returns, so: + # σ_annual = σ_hourly * sqrt(24 * 365) + daily_vol_ann = daily_vol * np.sqrt(24 * 365) + + return daily_vol_ann + + +def assemble_panel( + pools_df: pd.DataFrame, + snapshots_df: pd.DataFrame, + token_prices: dict, +) -> pd.DataFrame: + """Assemble the full panel DataFrame for hierarchical estimation. + + Parameters + ---------- + pools_df : pd.DataFrame + Pool enumeration from enumerate_balancer_pools. + snapshots_df : pd.DataFrame + Daily snapshots from fetch_all_snapshots. + token_prices : dict + Token prices from fetch_token_prices. + + Returns + ------- + pd.DataFrame + Panel with columns: pool_id, chain, date, log_volume, log_tvl, + volatility, weekend, log_fee, tier_A, tier_B, tokens. + """ + records = [] + pool_ids = snapshots_df["pool_id"].unique() + n_pools = len(pool_ids) + + for i, pool_id in enumerate(pool_ids): + if (i + 1) % 20 == 0 or i == 0: + print(f" Assembling {i+1}/{n_pools}...", flush=True) + + pool_snaps = snapshots_df[snapshots_df["pool_id"] == pool_id] + pool_meta = pools_df[pools_df["pool_id"] == pool_id] + if len(pool_meta) == 0: + continue + pool_row = pool_meta.iloc[0] + + tokens = pool_row["tokens"] + if len(tokens) < 2: + continue + + chain = pool_row["chain"] + swap_fee = pool_row["swap_fee"] + + # Token tiers: sort by tier (best tier first) + tiers = sorted([classify_token_tier(t) for t in tokens]) + tier_a = tiers[0] # best (lowest) tier + tier_b = tiers[1] if len(tiers) > 1 else tiers[0] + + # Compute volatility for this pool's pair + vol_series = compute_pair_volatility(pool_snaps, pool_row, token_prices) + + for _, snap in pool_snaps.iterrows(): + date = snap["date"] + volume = snap["volume_usd"] + tvl = snap["total_liquidity_usd"] + + # Skip zero/negative TVL or volume + if tvl <= 0 or volume <= 0: + continue + + # Volatility lookup + if isinstance(vol_series, pd.Series) and date in vol_series.index: + vol = vol_series[date] + else: + vol = 0.5 # fallback + + if not np.isfinite(vol) or vol <= 0: + vol = 0.5 + + # Weekend indicator + if isinstance(date, datetime): + is_weekend = date.weekday() >= 5 + else: + is_weekend = pd.Timestamp(date).weekday() >= 5 + + records.append({ + "pool_id": pool_id, + "chain": chain, + "date": date, + "log_volume": np.log(volume), + "log_tvl": np.log(tvl), + "volatility": vol, + "weekend": 1.0 if is_weekend else 0.0, + "log_fee": np.log(max(swap_fee, 1e-6)), + "tier_A": tier_a, + "tier_B": tier_b, + "tokens": ",".join(tokens[:2]), + "swap_fee": swap_fee, + }) + + panel = pd.DataFrame(records) + print(f"\n Panel: {len(panel)} observations, " + f"{panel['pool_id'].nunique()} pools, " + f"{panel['chain'].nunique()} chains") + + return panel + + +# --------------------------------------------------------------------------- +# Phase 2: Bayesian hierarchical model +# --------------------------------------------------------------------------- + +def _encode_covariates(panel: pd.DataFrame) -> dict: + """Build NumPyro-ready arrays from the panel DataFrame. + + Returns + ------- + dict with keys: + pool_idx : (N_obs,) int array mapping each observation to its pool + X_pool : (N_pools, K) covariate matrix (intercept + dummies + log_fee) + log_tvl, volatility, weekend, log_volume : (N_obs,) float arrays + pool_ids : (N_pools,) pool ID strings + covariate_names : list of str, column names for X_pool + ref_chain, ref_tier_a, ref_tier_b : reference categories + chains : sorted list of all chains + pool_meta : DataFrame of per-pool metadata + """ + pool_meta = panel.drop_duplicates("pool_id").reset_index(drop=True) + pool_ids = pool_meta["pool_id"].values + pool_id_to_idx = {pid: i for i, pid in enumerate(pool_ids)} + + pool_idx = panel["pool_id"].map(pool_id_to_idx).values + + # Build X_pool columns + chains = sorted(panel["chain"].unique()) + ref_chain = chains[0] + chain_cols = [] + chain_names = [] + for c in chains[1:]: + chain_cols.append((pool_meta["chain"] == c).astype(float).values) + chain_names.append(f"chain_{c}") + + tier_a_vals = sorted(pool_meta["tier_A"].astype(str).unique()) + ref_tier_a = tier_a_vals[0] + tier_a_cols = [] + tier_a_names = [] + for t in tier_a_vals[1:]: + tier_a_cols.append( + (pool_meta["tier_A"].astype(str) == t).astype(float).values + ) + tier_a_names.append(f"tier_A_{t}") + + tier_b_vals = sorted(pool_meta["tier_B"].astype(str).unique()) + ref_tier_b = tier_b_vals[0] + tier_b_cols = [] + tier_b_names = [] + for t in tier_b_vals[1:]: + tier_b_cols.append( + (pool_meta["tier_B"].astype(str) == t).astype(float).values + ) + tier_b_names.append(f"tier_B_{t}") + + N_pools = len(pool_ids) + columns = [np.ones((N_pools, 1))] + col_names = ["intercept"] + + for arr, name in zip(chain_cols, chain_names): + columns.append(arr.reshape(-1, 1)) + col_names.append(name) + for arr, name in zip(tier_a_cols, tier_a_names): + columns.append(arr.reshape(-1, 1)) + col_names.append(name) + for arr, name in zip(tier_b_cols, tier_b_names): + columns.append(arr.reshape(-1, 1)) + col_names.append(name) + columns.append(pool_meta["log_fee"].values.reshape(-1, 1)) + col_names.append("log_fee") + + X_pool = np.hstack(columns) + + return { + "pool_idx": pool_idx.astype(np.int32), + "X_pool": X_pool.astype(np.float64), + "log_tvl": panel["log_tvl"].values.astype(np.float64), + "volatility": panel["volatility"].values.astype(np.float64), + "weekend": panel["weekend"].values.astype(np.float64), + "log_volume": panel["log_volume"].values.astype(np.float64), + "pool_ids": pool_ids, + "covariate_names": col_names, + "ref_chain": ref_chain, + "ref_tier_a": ref_tier_a, + "ref_tier_b": ref_tier_b, + "chains": chains, + "pool_meta": pool_meta, + } + + +def _hierarchical_noise_model( + pool_idx, X_pool, log_tvl, volatility, weekend, log_volume=None, +): + """NumPyro model: Bayesian hierarchical loglinear noise volume. + + All pool covariates modulate all three coefficients (α, β_tvl, β_vol) + through the group-level regression matrix Φ, with correlated random + effects via LKJ-Cholesky. + """ + N_pools = X_pool.shape[0] + K = X_pool.shape[1] + + # Hyperpriors + Phi = numpyro.sample("Phi", dist.Normal(0, 2).expand([K, 3]).to_event(2)) + sigma_theta = numpyro.sample( + "sigma_theta", dist.HalfNormal(2).expand([3]).to_event(1) + ) + L_omega = numpyro.sample("L_omega", dist.LKJCholesky(3, concentration=2)) + beta_weekend = numpyro.sample("beta_weekend", dist.Normal(0, 2)) + sigma_eps = numpyro.sample("sigma_eps", dist.HalfNormal(3)) + + # Non-centered pool random effects + with numpyro.plate("pools", N_pools): + z = numpyro.sample("z", dist.Normal(jnp.zeros(3), 1).to_event(1)) + + # θ_i = Φᵀx_i + diag(σ_θ)·L_ω·z_i + mu = X_pool @ Phi # (N_pools, 3) + L_Sigma = sigma_theta[:, None] * L_omega # (3, 3) + theta = mu + z @ L_Sigma.T # (N_pools, 3) + + alpha = theta[:, 0] + beta_tvl = theta[:, 1] + beta_vol = theta[:, 2] + + # Observation model + loc = (alpha[pool_idx] + + beta_tvl[pool_idx] * log_tvl + + beta_vol[pool_idx] * volatility + + beta_weekend * weekend) + + with numpyro.plate("obs", pool_idx.shape[0]): + numpyro.sample("log_volume", dist.Normal(loc, sigma_eps), obs=log_volume) + + +def fit_bayesian_model( + panel: pd.DataFrame, use_nuts: bool = False, +) -> tuple: + """Fit the Bayesian hierarchical model via SVI or NUTS. + + Parameters + ---------- + panel : pd.DataFrame + Panel from assemble_panel. + use_nuts : bool + If True, use NUTS MCMC (slower, exact). Otherwise SVI with + AutoMultivariateNormal guide. + + Returns + ------- + samples : dict + Posterior samples keyed by parameter name. + encoding : dict + From _encode_covariates (needed downstream). + """ + encoding = _encode_covariates(panel) + + model_kwargs = dict( + pool_idx=jnp.array(encoding["pool_idx"]), + X_pool=jnp.array(encoding["X_pool"]), + log_tvl=jnp.array(encoding["log_tvl"]), + volatility=jnp.array(encoding["volatility"]), + weekend=jnp.array(encoding["weekend"]), + log_volume=jnp.array(encoding["log_volume"]), + ) + + N_pools = encoding["X_pool"].shape[0] + K = encoding["X_pool"].shape[1] + print(f" N obs = {len(encoding['pool_idx'])}, " + f"N pools = {N_pools}, K covariates = {K}") + print(f" Covariates: {encoding['covariate_names']}") + + rng_key = jax.random.PRNGKey(0) + + if use_nuts: + print(" Running NUTS (500 warmup + 1000 samples)...") + kernel = NUTS(_hierarchical_noise_model) + mcmc = MCMC(kernel, num_warmup=500, num_samples=1000, num_chains=1) + mcmc.run(rng_key, **model_kwargs) + samples = mcmc.get_samples() + print(" NUTS complete.") + else: + print(" Running SVI with AutoMultivariateNormal (20k steps)...") + guide = AutoMultivariateNormal(_hierarchical_noise_model) + optimizer = numpyro.optim.Adam(1e-3) + svi = SVI(_hierarchical_noise_model, guide, optimizer, + loss=Trace_ELBO()) + svi_result = svi.run(rng_key, 20_000, **model_kwargs) + print(f" SVI complete. Final ELBO loss: {svi_result.losses[-1]:.2f}") + + predictive = Predictive( + guide, params=svi_result.params, num_samples=1000, + ) + samples = predictive(jax.random.PRNGKey(1), **model_kwargs) + print(" Drew 1000 posterior samples.") + + return samples, encoding + + +def extract_posteriors(samples: dict, encoding: dict) -> dict: + """Reconstruct pool-specific coefficients from posterior samples. + + Computes θ_i = Φᵀx_i + diag(σ_θ)·L_ω·z_i for each posterior draw, + then returns posterior means and variance components. + + Returns + ------- + dict with keys: + pool_effects : {pool_id: {alpha, beta_tvl, beta_vol}} + Phi_mean : (K, 3) array + sigma_theta_mean : (3,) array + correlation_matrix : (3, 3) array + beta_weekend_mean : float + sigma_eps_mean : float + theta_samples : (S, N_pools, 3) array (for diagnostics) + """ + Phi = np.array(samples["Phi"]) # (S, K, 3) + sigma_theta = np.array(samples["sigma_theta"]) # (S, 3) + L_omega = np.array(samples["L_omega"]) # (S, 3, 3) + z = np.array(samples["z"]) # (S, N_pools, 3) + beta_weekend = np.array(samples["beta_weekend"]) # (S,) + sigma_eps = np.array(samples["sigma_eps"]) # (S,) + + X_pool = encoding["X_pool"] # (N_pools, K) + pool_ids = encoding["pool_ids"] + + # mu = X_pool @ Phi for each sample: (S, N_pools, 3) + mu = np.einsum("pk,skj->spj", X_pool, Phi) + + # L_Sigma = diag(sigma_theta) @ L_omega: (S, 3, 3) + L_Sigma = sigma_theta[:, :, None] * L_omega + + # offset = z @ L_Sigma^T: (S, N_pools, 3) + offset = np.einsum("spi,sji->spj", z, L_Sigma) + + theta = mu + offset # (S, N_pools, 3) + theta_mean = theta.mean(axis=0) # (N_pools, 3) + + pool_effects = {} + for i, pid in enumerate(pool_ids): + pool_effects[pid] = { + "alpha": float(theta_mean[i, 0]), + "beta_tvl": float(theta_mean[i, 1]), + "beta_vol": float(theta_mean[i, 2]), + } + + Phi_mean = Phi.mean(axis=0) # (K, 3) + + # Correlation matrix: R = L_omega @ L_omega^T, averaged over samples + R_samples = np.einsum("sij,skj->sik", L_omega, L_omega) + R_mean = R_samples.mean(axis=0) + + return { + "pool_effects": pool_effects, + "Phi_mean": Phi_mean, + "sigma_theta_mean": sigma_theta.mean(axis=0), + "correlation_matrix": R_mean, + "beta_weekend_mean": float(beta_weekend.mean()), + "sigma_eps_mean": float(sigma_eps.mean()), + "theta_samples": theta, + } + + +def compute_noise_params(posteriors: dict, panel: pd.DataFrame) -> list: + """Convert posterior pool effects to per-pool noise_params dicts. + + Each pool now has its own β_tvl and β_vol (from the hierarchical + posterior), rather than sharing global slopes. + + Parameters + ---------- + posteriors : dict + From extract_posteriors. + panel : pd.DataFrame + Panel data. + + Returns + ------- + list of dict + Each dict has: pool_id, chain, tokens, noise_params. + """ + pool_effects = posteriors["pool_effects"] + pool_meta = panel.drop_duplicates("pool_id").set_index("pool_id") + + results = [] + for pool_id, effects in pool_effects.items(): + if pool_id not in pool_meta.index: + continue + meta = pool_meta.loc[pool_id] + swap_fee = float(meta.get("swap_fee", 0.003)) + + results.append({ + "pool_id": pool_id, + "chain": meta["chain"], + "tokens": (meta["tokens"].split(",") + if isinstance(meta["tokens"], str) + else meta["tokens"]), + "noise_params": { + "b_0": effects["alpha"], + "b_sigma": effects["beta_vol"], + "b_c": effects["beta_tvl"], + "base_fee": swap_fee, + }, + }) + + return results + + +def _build_covariate_vector( + encoding: dict, chain: str, tokens: list, fee: float, +) -> np.ndarray: + """Construct a covariate vector x for a new pool. + + Matches the column order of X_pool from _encode_covariates so that + x @ Phi_mean gives population-level predictions for all 3 coefficients. + """ + col_names = encoding["covariate_names"] + x = np.zeros(len(col_names)) + + tiers = sorted([classify_token_tier(t) for t in tokens]) + tier_a = str(tiers[0]) + tier_b = str(tiers[1]) if len(tiers) > 1 else tier_a + + for i, name in enumerate(col_names): + if name == "intercept": + x[i] = 1.0 + elif name == "log_fee": + x[i] = np.log(max(fee, 1e-6)) + elif name == f"chain_{chain}": + x[i] = 1.0 + elif name == f"tier_A_{tier_a}": + x[i] = 1.0 + elif name == f"tier_B_{tier_b}": + x[i] = 1.0 + + return x + + +def predict_new_pool( + posteriors: dict, + encoding: dict, + chain: str, + tokens: list, + fee: float, +) -> dict: + """Predict noise params for an unseen pool. + + Uses population-level estimates only (x @ Φ, no pool random effect). + + Parameters + ---------- + posteriors : dict + From extract_posteriors. + encoding : dict + From _encode_covariates (or loaded from cache). + chain : str + Chain API identifier (e.g. "BASE"). + tokens : list + Token symbols (e.g. ["ETH", "BTC"]). + fee : float + Swap fee rate. + + Returns + ------- + dict + noise_params dict with pool-predicted coefficients. + """ + x = _build_covariate_vector(encoding, chain, tokens, fee) + Phi_mean = posteriors["Phi_mean"] + + theta_pred = x @ Phi_mean # (3,) — population-level prediction + + tiers = sorted([classify_token_tier(t) for t in tokens]) + tier_a = tiers[0] + tier_b = tiers[1] if len(tiers) > 1 else tiers[0] + + return { + "b_0": float(theta_pred[0]), + "b_sigma": float(theta_pred[2]), + "b_c": float(theta_pred[1]), + "base_fee": float(fee), + "_prediction_source": "population_level", + "_alpha": float(theta_pred[0]), + "_beta_tvl": float(theta_pred[1]), + "_beta_vol": float(theta_pred[2]), + "_tier_a": tier_a, + "_tier_b": tier_b, + } + + +# --------------------------------------------------------------------------- +# Phase 3: Diagnostics and output +# --------------------------------------------------------------------------- + +def plot_hierarchical_diagnostics( + panel: pd.DataFrame, + posteriors: dict, + encoding: dict, + output_dir: str = "results", +): + """Generate diagnostic plots for the Bayesian hierarchical model. + + Figure 1 (2x2): + (0,0) Pool-specific coefficient distributions (α, β_tvl, β_vol) + (0,1) Chain effects on all 3 coefficients + (1,0) Tier effects on all 3 coefficients + (1,1) Model summary (Φ, σ_θ, correlations, σ_ε) + + Figure 2: + β_tvl vs β_vol scatter colored by chain + """ + import matplotlib + matplotlib.use("Agg") + import matplotlib.pyplot as plt + + os.makedirs(output_dir, exist_ok=True) + + pool_effects = posteriors["pool_effects"] + Phi_mean = posteriors["Phi_mean"] + col_names = encoding["covariate_names"] + pool_meta = panel.drop_duplicates("pool_id") + + alphas = [e["alpha"] for e in pool_effects.values()] + beta_tvls = [e["beta_tvl"] for e in pool_effects.values()] + beta_vols = [e["beta_vol"] for e in pool_effects.values()] + + # --- Figure 1: Diagnostics 2x2 --- + fig, axes = plt.subplots(2, 2, figsize=(14, 10)) + + # (0,0) Pool-specific coefficient distributions + ax = axes[0, 0] + bins = 25 + ax.hist(alphas, bins=bins, alpha=0.6, label="α (intercept)", + color="steelblue", edgecolor="white") + ax.hist(beta_tvls, bins=bins, alpha=0.6, label="β_tvl", + color="coral", edgecolor="white") + ax.hist(beta_vols, bins=bins, alpha=0.6, label="β_vol", + color="seagreen", edgecolor="white") + ax.set_xlabel("Coefficient value") + ax.set_ylabel("Count") + ax.set_title(f"Pool-specific coefficients (n={len(alphas)})") + ax.legend() + + # (0,1) Chain effects on all 3 coefficients + ax = axes[0, 1] + chain_rows = {name: i for i, name in enumerate(col_names) + if name.startswith("chain_")} + chain_labels = [] + chain_alpha_effects = [] + chain_tvl_effects = [] + chain_vol_effects = [] + # Reference chain (effect = 0) + ref_chain = encoding["ref_chain"] + chain_counts = pool_meta["chain"].value_counts() + chain_labels.append(f"{ref_chain}\n(n={chain_counts.get(ref_chain, 0)})") + chain_alpha_effects.append(0.0) + chain_tvl_effects.append(0.0) + chain_vol_effects.append(0.0) + for name, row_idx in sorted(chain_rows.items()): + chain_name = name.replace("chain_", "") + chain_labels.append( + f"{chain_name}\n(n={chain_counts.get(chain_name, 0)})" + ) + chain_alpha_effects.append(Phi_mean[row_idx, 0]) + chain_tvl_effects.append(Phi_mean[row_idx, 1]) + chain_vol_effects.append(Phi_mean[row_idx, 2]) + + y_pos = np.arange(len(chain_labels)) + bar_h = 0.25 + ax.barh(y_pos - bar_h, chain_alpha_effects, bar_h, label="α", + color="steelblue", alpha=0.8) + ax.barh(y_pos, chain_tvl_effects, bar_h, label="β_tvl", + color="coral", alpha=0.8) + ax.barh(y_pos + bar_h, chain_vol_effects, bar_h, label="β_vol", + color="seagreen", alpha=0.8) + ax.set_yticks(y_pos) + ax.set_yticklabels(chain_labels) + ax.axvline(0, color="red", linestyle="--", linewidth=1) + ax.set_xlabel("Effect (relative to reference)") + ax.set_title("Chain effects on all coefficients") + ax.legend(fontsize=8) + + # (1,0) Tier effects on all 3 coefficients + ax = axes[1, 0] + tier_names = ["0 (blue-chip)", "1 (mid-cap)", "2 (long-tail)"] + coeff_labels = ["α", "β_tvl", "β_vol"] + tier_a_rows = {name: i for i, name in enumerate(col_names) + if name.startswith("tier_A_")} + tier_b_rows = {name: i for i, name in enumerate(col_names) + if name.startswith("tier_B_")} + + # Build effects matrix: (3 tiers) x (3 coefficients) x (A/B) + x_pos = np.arange(len(tier_names)) + width = 0.13 + for coeff_idx, (coeff_name, color) in enumerate( + zip(coeff_labels, ["steelblue", "coral", "seagreen"]) + ): + tier_a_vals = [0.0, 0.0, 0.0] # reference tier gets 0 + tier_b_vals = [0.0, 0.0, 0.0] + for name, row_idx in tier_a_rows.items(): + tier_val = name.replace("tier_A_", "") + if tier_val in ("0", "1", "2"): + tier_a_vals[int(tier_val)] = Phi_mean[row_idx, coeff_idx] + for name, row_idx in tier_b_rows.items(): + tier_val = name.replace("tier_B_", "") + if tier_val in ("0", "1", "2"): + tier_b_vals[int(tier_val)] = Phi_mean[row_idx, coeff_idx] + offset = (coeff_idx - 1) * width * 2 + ax.bar(x_pos + offset - width / 2, tier_a_vals, width, + label=f"{coeff_name} (A)" if coeff_idx == 0 else "", + color=color, alpha=0.7, edgecolor="white") + ax.bar(x_pos + offset + width / 2, tier_b_vals, width, + label=f"{coeff_name} (B)" if coeff_idx == 0 else "", + color=color, alpha=0.4, edgecolor="white", hatch="//") + + ax.set_xticks(x_pos) + ax.set_xticklabels(tier_names) + ax.set_ylabel("Effect on coefficient") + ax.set_title("Token tier effects (solid=A, hatched=B)") + ax.axhline(0, color="black", linewidth=0.5) + # Manual legend for coefficient colors + from matplotlib.patches import Patch + ax.legend(handles=[Patch(color=c, label=l) for c, l in + zip(["steelblue", "coral", "seagreen"], coeff_labels)], + fontsize=8) + + # (1,1) Model summary text + ax = axes[1, 1] + ax.axis("off") + sigma_theta = posteriors["sigma_theta_mean"] + R = posteriors["correlation_matrix"] + summary = "Group-level regression Φ (posterior mean):\n" + summary += f" {'covariate':<20s} {'α':>8s} {'β_tvl':>8s} {'β_vol':>8s}\n" + summary += " " + "-" * 46 + "\n" + for j, name in enumerate(col_names): + summary += (f" {name:<20s} {Phi_mean[j,0]:>8.3f} " + f"{Phi_mean[j,1]:>8.3f} {Phi_mean[j,2]:>8.3f}\n") + summary += f"\nσ_θ: [{sigma_theta[0]:.3f}, {sigma_theta[1]:.3f}, " + summary += f"{sigma_theta[2]:.3f}]\n" + summary += f"Correlation:\n" + for i in range(3): + summary += f" [{R[i,0]:>6.3f} {R[i,1]:>6.3f} {R[i,2]:>6.3f}]\n" + summary += f"β_weekend: {posteriors['beta_weekend_mean']:.4f}\n" + summary += f"σ_ε: {posteriors['sigma_eps_mean']:.4f}\n" + ax.text(0.02, 0.98, summary, transform=ax.transAxes, + fontsize=7, verticalalignment="top", fontfamily="monospace") + ax.set_title("Model Summary") + + fig.suptitle( + "Bayesian Hierarchical Noise Model — Diagnostics", fontsize=13, + ) + plt.tight_layout() + path = os.path.join(output_dir, "hierarchical_diagnostics.png") + plt.savefig(path, dpi=150, bbox_inches="tight") + plt.close() + print(f" Saved: {path}") + + # --- Figure 2: β_tvl vs β_vol scatter colored by chain --- + fig2, ax2 = plt.subplots(figsize=(10, 7)) + pool_id_to_chain = dict( + zip(pool_meta["pool_id"], pool_meta["chain"]) + ) + chain_colors = {} + cmap = plt.cm.tab10 + unique_chains = sorted(pool_meta["chain"].unique()) + for i, c in enumerate(unique_chains): + chain_colors[c] = cmap(i % 10) + + for pid, effects in pool_effects.items(): + c = pool_id_to_chain.get(pid, "?") + ax2.scatter(effects["beta_tvl"], effects["beta_vol"], + color=chain_colors.get(c, "gray"), alpha=0.6, s=20, + edgecolors="white", linewidths=0.3) + + # Legend + from matplotlib.lines import Line2D + handles = [Line2D([0], [0], marker="o", color="w", + markerfacecolor=chain_colors[c], markersize=8, + label=c) + for c in unique_chains if c in chain_colors] + ax2.legend(handles=handles, fontsize=8, loc="best") + ax2.set_xlabel("β_tvl (TVL elasticity)") + ax2.set_ylabel("β_vol (volatility sensitivity)") + ax2.set_title("Pool-specific coefficients by chain") + ax2.axhline(0, color="gray", linewidth=0.5, linestyle="--") + ax2.axvline(0, color="gray", linewidth=0.5, linestyle="--") + plt.tight_layout() + path2 = os.path.join(output_dir, "beta_tvl_vs_beta_vol.png") + plt.savefig(path2, dpi=150, bbox_inches="tight") + plt.close() + print(f" Saved: {path2}") + + return path, path2 + + +def generate_noise_params_json( + pool_params: list, + posteriors: dict, + encoding: dict, + output_path: str, + inference_method: str = "svi", +): + """Write per-pool noise params to JSON. + + Parameters + ---------- + pool_params : list of dict + From compute_noise_params. + posteriors : dict + From extract_posteriors. + encoding : dict + From _encode_covariates. + output_path : str + Output JSON path. + inference_method : str + "svi" or "nuts". + """ + output = { + "model": "bayesian_hierarchical_loglinear", + "inference_method": inference_method, + "Phi": posteriors["Phi_mean"].tolist(), + "covariate_names": encoding["covariate_names"], + "sigma_theta": posteriors["sigma_theta_mean"].tolist(), + "correlation_matrix": posteriors["correlation_matrix"].tolist(), + "beta_weekend": posteriors["beta_weekend_mean"], + "sigma_eps": posteriors["sigma_eps_mean"], + "pools": pool_params, + } + + os.makedirs(os.path.dirname(output_path) or ".", exist_ok=True) + with open(output_path, "w") as f: + json.dump(output, f, indent=2, default=str) + print(f" Wrote {len(pool_params)} pool params → {output_path}") + + +# --------------------------------------------------------------------------- +# CLI +# --------------------------------------------------------------------------- + +def main(): + parser = argparse.ArgumentParser( + description="Bayesian hierarchical noise volume model for Balancer pools" + ) + parser.add_argument( + "--fetch", action="store_true", + help="Fetch pool data from Balancer API (cached to local_data/)", + ) + parser.add_argument( + "--fit", action="store_true", + help="Fit Bayesian hierarchical model (requires fetched data)", + ) + parser.add_argument( + "--nuts", action="store_true", + help="Use NUTS MCMC instead of SVI (slower, exact posteriors)", + ) + parser.add_argument( + "--plot", action="store_true", + help="Generate diagnostic plots", + ) + parser.add_argument( + "--output", default=None, + help="Output JSON path for per-pool noise params", + ) + parser.add_argument( + "--output-dir", default="results", + help="Directory for diagnostic plots (default: results)", + ) + parser.add_argument( + "--predict", action="store_true", + help="Predict noise params for a new pool", + ) + parser.add_argument( + "--chain", default=None, + help="Chain for --predict (e.g. BASE, MAINNET)", + ) + parser.add_argument( + "--tokens", nargs="+", default=None, + help="Token symbols for --predict (e.g. ETH BTC)", + ) + parser.add_argument( + "--fee", type=float, default=0.003, + help="Swap fee for --predict", + ) + parser.add_argument( + "--min-tvl", type=float, default=10000.0, + help="Minimum TVL filter for pool enumeration", + ) + parser.add_argument( + "--cache-dir", default=None, + help="Cache directory (default: local_data/noise_calibration/)", + ) + args = parser.parse_args() + + cache_dir = args.cache_dir or CACHE_DIR + + if not any([args.fetch, args.fit, args.predict]): + parser.error("At least one of --fetch, --fit, --predict is required") + + # --- Fetch --- + pools_cache = os.path.join(cache_dir, "pools.parquet") + snaps_cache = os.path.join(cache_dir, "pool_snapshots.parquet") + prices_cache = os.path.join(cache_dir, "token_prices") + panel_cache = os.path.join(cache_dir, "panel.parquet") + + if args.fetch: + print("Phase 1: Fetching data from Balancer API") + print("=" * 60) + + # Step 1: Enumerate pools + print("\n1. Enumerating pools...") + pools_df = enumerate_balancer_pools(min_tvl=args.min_tvl) + os.makedirs(cache_dir, exist_ok=True) + pools_df.to_parquet(pools_cache, index=False) + print(f" Saved {len(pools_df)} pools → {pools_cache}") + + # Step 2: Fetch snapshots + print("\n2. Fetching daily snapshots...") + snapshots_df = fetch_all_snapshots(pools_df, cache_path=snaps_cache) + + # Step 3: Fetch token prices + print("\n3. Fetching token prices...") + token_addr_by_chain = {} + for _, pool in pools_df.iterrows(): + chain = pool["chain"] + tokens = pool["tokens"] + addresses = pool["token_addresses"] + if chain not in token_addr_by_chain: + token_addr_by_chain[chain] = {} + for sym, addr in zip(tokens, addresses): + if sym and addr: + token_addr_by_chain[chain][sym] = addr + + token_prices = fetch_token_prices( + token_addr_by_chain, cache_dir=prices_cache + ) + + # Step 4: Assemble panel + print("\n4. Assembling panel...") + panel = assemble_panel(pools_df, snapshots_df, token_prices) + panel.to_parquet(panel_cache, index=False) + print(f" Saved panel → {panel_cache}") + + print(f"\nFetch complete. Panel: {len(panel)} obs, " + f"{panel['pool_id'].nunique()} pools") + + # --- Fit --- + if args.fit: + inference_method = "nuts" if args.nuts else "svi" + print(f"\nPhase 2: Fitting Bayesian hierarchical model ({inference_method})") + print("=" * 60) + + # Load panel + if not os.path.exists(panel_cache): + print(f"ERROR: Panel cache not found at {panel_cache}", + file=sys.stderr) + print("Run with --fetch first.", file=sys.stderr) + sys.exit(1) + + panel = pd.read_parquet(panel_cache) + print(f" Loaded panel: {len(panel)} obs, " + f"{panel['pool_id'].nunique()} pools, " + f"{panel['chain'].nunique()} chains") + + # Filter: need at least 10 days per pool for stable estimates + pool_counts = panel.groupby("pool_id").size() + valid_pools = pool_counts[pool_counts >= 10].index + panel_filtered = panel[panel["pool_id"].isin(valid_pools)] + print(f" After filtering (≥10 days): {len(panel_filtered)} obs, " + f"{panel_filtered['pool_id'].nunique()} pools") + + samples, encoding = fit_bayesian_model( + panel_filtered, use_nuts=args.nuts, + ) + posteriors = extract_posteriors(samples, encoding) + pool_params = compute_noise_params(posteriors, panel_filtered) + + # Print key diagnostics + Phi_mean = posteriors["Phi_mean"] + col_names = encoding["covariate_names"] + intercept_idx = col_names.index("intercept") + log_fee_idx = col_names.index("log_fee") + + print(f"\n Key results:") + print(f" Population intercept (Φ[intercept]):") + print(f" α: {Phi_mean[intercept_idx, 0]:.4f}") + print(f" β_tvl: {Phi_mean[intercept_idx, 1]:.4f}") + print(f" β_vol: {Phi_mean[intercept_idx, 2]:.4f}") + print(f" Fee effect (Φ[log_fee]):") + print(f" α: {Phi_mean[log_fee_idx, 0]:.4f}") + print(f" β_tvl: {Phi_mean[log_fee_idx, 1]:.4f}") + print(f" β_vol: {Phi_mean[log_fee_idx, 2]:.4f}") + print(f" β_weekend: {posteriors['beta_weekend_mean']:.4f}") + print(f" σ_θ: {posteriors['sigma_theta_mean']}") + print(f" σ_ε: {posteriors['sigma_eps_mean']:.4f}") + + # Verify pool-specific variation + b_sigmas = [p["noise_params"]["b_sigma"] for p in pool_params] + b_cs = [p["noise_params"]["b_c"] for p in pool_params] + print(f" b_sigma range: [{min(b_sigmas):.4f}, {max(b_sigmas):.4f}]") + print(f" b_c range: [{min(b_cs):.4f}, {max(b_cs):.4f}]") + + # Cache posteriors + encoding for --predict and --plot + posteriors_cache = os.path.join(cache_dir, "posteriors.json") + cache_data = { + "Phi_mean": posteriors["Phi_mean"].tolist(), + "sigma_theta_mean": posteriors["sigma_theta_mean"].tolist(), + "correlation_matrix": posteriors["correlation_matrix"].tolist(), + "beta_weekend_mean": posteriors["beta_weekend_mean"], + "sigma_eps_mean": posteriors["sigma_eps_mean"], + "pool_effects": posteriors["pool_effects"], + "covariate_names": encoding["covariate_names"], + "ref_chain": encoding["ref_chain"], + "ref_tier_a": encoding["ref_tier_a"], + "ref_tier_b": encoding["ref_tier_b"], + "chains": encoding["chains"], + "inference_method": inference_method, + } + with open(posteriors_cache, "w") as f: + json.dump(cache_data, f, indent=2, default=str) + print(f" Cached posteriors → {posteriors_cache}") + + if args.output: + generate_noise_params_json( + pool_params, posteriors, encoding, + args.output, inference_method=inference_method, + ) + + if args.plot: + print("\nPhase 3: Generating diagnostics") + print("=" * 60) + plot_hierarchical_diagnostics( + panel_filtered, posteriors, encoding, + output_dir=args.output_dir, + ) + + # --- Predict --- + if args.predict: + if args.chain is None or args.tokens is None: + parser.error("--predict requires --chain and --tokens") + + print(f"\nPredicting noise params for new pool:") + print(f" Chain: {args.chain}") + print(f" Tokens: {args.tokens}") + print(f" Fee: {args.fee}") + + # Load cached posteriors + encoding metadata + posteriors_cache = os.path.join(cache_dir, "posteriors.json") + if not os.path.exists(posteriors_cache): + print(f"ERROR: Posteriors cache not found at {posteriors_cache}", + file=sys.stderr) + print("Run with --fit first.", file=sys.stderr) + sys.exit(1) + + with open(posteriors_cache) as f: + cache_data = json.load(f) + + posteriors = { + "Phi_mean": np.array(cache_data["Phi_mean"]), + "sigma_theta_mean": np.array(cache_data["sigma_theta_mean"]), + "correlation_matrix": np.array(cache_data["correlation_matrix"]), + "beta_weekend_mean": cache_data["beta_weekend_mean"], + "sigma_eps_mean": cache_data["sigma_eps_mean"], + "pool_effects": cache_data["pool_effects"], + } + encoding = { + "covariate_names": cache_data["covariate_names"], + "ref_chain": cache_data["ref_chain"], + "ref_tier_a": cache_data["ref_tier_a"], + "ref_tier_b": cache_data["ref_tier_b"], + "chains": cache_data["chains"], + } + + params = predict_new_pool( + posteriors, encoding, args.chain, args.tokens, args.fee, + ) + print(f"\n Predicted noise_params:") + print(json.dumps(params, indent=2)) + + +if __name__ == "__main__": + main() diff --git a/scripts/calibrate_noise_unified.py b/scripts/calibrate_noise_unified.py new file mode 100644 index 0000000..0e35527 --- /dev/null +++ b/scripts/calibrate_noise_unified.py @@ -0,0 +1,6 @@ +"""Thin wrapper — all logic lives in quantammsim.noise_calibration.""" +from quantammsim.noise_calibration import * # noqa: F401, F403 +from quantammsim.noise_calibration.cli import main + +if __name__ == "__main__": + main() diff --git a/scripts/calibrate_reclamm_noise.py b/scripts/calibrate_reclamm_noise.py new file mode 100644 index 0000000..8d09584 --- /dev/null +++ b/scripts/calibrate_reclamm_noise.py @@ -0,0 +1,842 @@ +"""OLS calibration of the Tsoukalas noise volume model for reClAMM pools. + +Fits the structural volume equation: + V_daily/1e6 = a_0 + a_sigma*sigma + a_c*sqrt(c_eff/1e6) + +where c_eff = (Ra+Va)*pA + (Rb+Vb)*pB is the effective TVL (real + virtual). + +From daily pool snapshots (volume, TVL, volatility). Outputs a noise_params dict +compatible with run_fingerprint["reclamm_noise_params"]. + +Usage: + # From a pre-assembled CSV + python scripts/calibrate_reclamm_noise.py --csv daily_data.csv --base-fee 0.003 + + # End-to-end from API + DB + parquets + python scripts/calibrate_reclamm_noise.py --pool cbBTC_WETH +""" + +import argparse +import json +import os +import sqlite3 +import sys +import urllib.request +from datetime import datetime, timezone + +import numpy as np +import pandas as pd + + +# --------------------------------------------------------------------------- +# Balancer V3 API +# --------------------------------------------------------------------------- + +BALANCER_API_URL = "https://api-v3.balancer.fi/" + +BALANCER_API_CHAIN = { + "base": "BASE", + "ethereum": "MAINNET", + "gnosis": "GNOSIS", + "avalanche": "AVALANCHE", + "arbitrum": "ARBITRUM", + "polygon": "POLYGON", + "optimism": "OPTIMISM", + "sonic": "SONIC", +} + + +def fetch_balancer_snapshots(chain, pool_address, start_ts, end_ts, + base_url=BALANCER_API_URL): + """Fetch daily pool snapshots from Balancer V3 GraphQL API. + + Parameters + ---------- + chain : str + Chain name (e.g. 'base', 'ethereum'). + pool_address : str + Pool contract address (hex, no 0x prefix). + start_ts : int + Start unix timestamp (seconds). + end_ts : int + End unix timestamp (seconds). + base_url : str + Balancer API base URL. + + Returns + ------- + pd.DataFrame + Columns: date, volume_usd, total_liquidity_usd. Indexed by date string. + """ + api_chain = BALANCER_API_CHAIN.get(chain) + if api_chain is None: + raise ValueError(f"Unknown chain for Balancer API: {chain!r}") + + pool_id = f"0x{pool_address}" if not pool_address.startswith("0x") else pool_address + + # Paginate: API may limit results. Fetch in 90-day windows. + all_snapshots = [] + window = 90 * 86400 + cursor = start_ts + + while cursor < end_ts: + window_end = min(cursor + window, end_ts) + query = { + "query": """ + query GetSnapshots($poolId: String!, $chain: GqlChain!, + $range: GqlPoolSnapshotDataRange!) { + poolGetSnapshots(id: $poolId, chain: $chain, range: $range) { + timestamp + volume24h + totalLiquidity + } + } + """, + "variables": { + "poolId": pool_id, + "chain": api_chain, + "range": "ALL_TIME", + }, + } + + data = json.dumps(query).encode("utf-8") + req = urllib.request.Request( + base_url, + data=data, + headers={ + "Content-Type": "application/json", + "User-Agent": "quantammsim/1.0", + }, + ) + + with urllib.request.urlopen(req, timeout=30) as resp: + body = json.loads(resp.read().decode("utf-8")) + + snapshots = body.get("data", {}).get("poolGetSnapshots", []) + if not snapshots: + break + + for snap in snapshots: + ts = int(snap["timestamp"]) + if start_ts <= ts <= end_ts: + all_snapshots.append({ + "timestamp": ts, + "volume_usd": float(snap["volume24h"]), + "total_liquidity_usd": float(snap["totalLiquidity"]), + }) + + # The API returns ALL_TIME, so no need to paginate further + break + + if not all_snapshots: + raise ValueError( + f"No Balancer snapshots for {pool_id} on {chain} " + f"between {start_ts} and {end_ts}" + ) + + df = pd.DataFrame(all_snapshots) + df["date"] = pd.to_datetime(df["timestamp"], unit="s").dt.date + # Deduplicate by date (keep last snapshot per day) + df = df.sort_values("timestamp").drop_duplicates("date", keep="last") + return df.set_index("date") + + +# --------------------------------------------------------------------------- +# DB-based daily pool state +# --------------------------------------------------------------------------- + +def load_daily_pool_state(pool, db_path, data_root): + """Load daily pool state from pools_history.db, compute effective TVL. + + Parameters + ---------- + pool : PoolConfig + Pool configuration (from pool_registry). + db_path : str + Path to pools_history.db. + data_root : str + Directory containing {TICKER}_USD.parquet files. + + Returns + ------- + pd.DataFrame + Indexed by date, columns: effective_tvl_usd, real_tvl_usd. + """ + conn = sqlite3.connect(db_path) + cur = conn.cursor() + cur.execute( + f"""SELECT timestamp, balance_0, balance_1, virtual_0, virtual_1 + FROM {pool.db_label} + ORDER BY timestamp""" + ) + rows = cur.fetchall() + conn.close() + + if not rows: + raise ValueError(f"No DB data for {pool.db_label}") + + df = pd.DataFrame(rows, columns=["timestamp", "bal_0", "bal_1", "virt_0", "virt_1"]) + df["date"] = pd.to_datetime(df["timestamp"], unit="s").dt.date + + # Keep last snapshot per day + daily = df.sort_values("timestamp").drop_duplicates("date", keep="last").set_index("date") + + # Load USD prices for each token + if pool.reverse: + tickers_in_db_order = [pool.tokens[1], pool.tokens[0]] + else: + tickers_in_db_order = [pool.tokens[0], pool.tokens[1]] + + price_dfs = {} + for ticker in tickers_in_db_order: + if ticker == "USDC": + price_dfs[ticker] = None # constant $1 + else: + path = os.path.join(data_root, f"{ticker}_USD.parquet") + pdf = pd.read_parquet(path) + pdf["date"] = pd.to_datetime(pdf["unix"], unit="ms").dt.date + # Daily close: last price per day + price_dfs[ticker] = ( + pdf.sort_values("unix") + .drop_duplicates("date", keep="last") + .set_index("date")["close"] + ) + + # Compute USD prices at each daily snapshot + records = [] + for date, row in daily.iterrows(): + b0, b1, v0, v1 = row["bal_0"], row["bal_1"], row["virt_0"], row["virt_1"] + + p0 = 1.0 if tickers_in_db_order[0] == "USDC" else price_dfs[tickers_in_db_order[0]].get(date, np.nan) + p1 = 1.0 if tickers_in_db_order[1] == "USDC" else price_dfs[tickers_in_db_order[1]].get(date, np.nan) + + if np.isnan(p0) or np.isnan(p1): + continue + + real_tvl = b0 * p0 + b1 * p1 + effective_tvl = (b0 + v0) * p0 + (b1 + v1) * p1 + + records.append({ + "date": date, + "real_tvl_usd": real_tvl, + "effective_tvl_usd": effective_tvl, + }) + + result = pd.DataFrame(records).set_index("date") + return result + + +# --------------------------------------------------------------------------- +# Daily volatility from price parquets +# --------------------------------------------------------------------------- + +def compute_daily_volatility(tokens, data_root, start_ts, end_ts): + """Compute daily annualised volatility of the price ratio. + + Uses 5-minute subsampled log returns within each day, then + annualises with sqrt(365). + + Parameters + ---------- + tokens : list + Token tickers in quantammsim sorted order (e.g. ['BTC', 'ETH']). + data_root : str + Directory containing {TICKER}_USD.parquet files. + start_ts : int + Start unix timestamp (seconds). + end_ts : int + End unix timestamp (seconds). + + Returns + ------- + pd.Series + Indexed by date, values are annualised daily volatility. + """ + # Load minute-level prices for both tokens + prices = {} + for ticker in tokens: + if ticker == "USDC": + prices[ticker] = None + else: + path = os.path.join(data_root, f"{ticker}_USD.parquet") + df = pd.read_parquet(path) + df = df[(df["unix"] >= start_ts * 1000) & (df["unix"] <= end_ts * 1000)] + df["datetime"] = pd.to_datetime(df["unix"], unit="ms") + df = df.set_index("datetime")["close"] + prices[ticker] = df + + # Compute price ratio (token[0] / token[1]) + t0, t1 = tokens[0], tokens[1] + if prices[t0] is not None and prices[t1] is not None: + # Align on common timestamps + combined = pd.DataFrame({"p0": prices[t0], "p1": prices[t1]}).dropna() + ratio = combined["p0"] / combined["p1"] + elif prices[t0] is not None: + ratio = prices[t0] # t1 is USDC ($1) + elif prices[t1] is not None: + ratio = 1.0 / prices[t1] # t0 is USDC + else: + raise ValueError("Both tokens are USDC — cannot compute ratio") + + # Subsample to 5-min intervals + ratio_5m = ratio.resample("5min").last().dropna() + log_returns = np.log(ratio_5m / ratio_5m.shift(1)).dropna() + + # Group by date, compute daily vol + log_returns_df = log_returns.to_frame("lr") + log_returns_df["date"] = log_returns_df.index.date + + daily_vol = log_returns_df.groupby("date")["lr"].std() + # Annualise: each day has ~288 5-min periods, scale by sqrt(288 * 365) + daily_vol_ann = daily_vol * np.sqrt(288 * 365) + + return daily_vol_ann + + +# --------------------------------------------------------------------------- +# Calibration DataFrame assembly +# --------------------------------------------------------------------------- + +def build_calibration_df(pool, data_root=None): + """Build daily calibration DataFrame from Balancer API + price parquets. + + All pool state (volume, effective TVL) comes from the Balancer V3 API. + The API's ``totalLiquidity`` is the effective TVL: for a reClAMM pool + on Balancer V3, the router sees real + virtual reserves, and + ``totalLiquidity`` reflects that full depth. Only the volatility + computation requires price parquets. + + Parameters + ---------- + pool : PoolConfig + Pool configuration (must have pool_address field). + data_root : str, optional + Directory containing {TICKER}_USD.parquet price files. + + Returns + ------- + pd.DataFrame + Columns: volume_usd, effective_tvl_usd, volatility. Indexed by date. + """ + from experiments.pool_registry import ( + get_data_end_date, + _date_to_unix, + ) + + if data_root is None: + data_root = os.path.join( + os.path.dirname(os.path.dirname(__file__)), + "quantammsim", "data", + ) + + start_ts = _date_to_unix(pool.plausible_start) + end_str = get_data_end_date(pool.tokens, data_root) + end_ts = _date_to_unix(end_str) + + print(f" Fetching Balancer snapshots for {pool.label} " + f"({pool.chain}, {pool.pool_address})...") + api_df = fetch_balancer_snapshots( + pool.chain, pool.pool_address, start_ts, end_ts, + ) + print(f" Got {len(api_df)} daily snapshots from API") + print(f" TVL range: ${api_df['total_liquidity_usd'].min():,.0f} — " + f"${api_df['total_liquidity_usd'].max():,.0f}") + + print(f" Computing daily volatility from price parquets...") + vol_series = compute_daily_volatility(pool.tokens, data_root, start_ts, end_ts) + print(f" Got {len(vol_series)} daily volatility values") + + # Assemble: volume + TVL from API, volatility from parquets + combined = api_df[["volume_usd", "total_liquidity_usd"]].copy() + combined = combined.rename(columns={"total_liquidity_usd": "effective_tvl_usd"}) + combined["volatility"] = vol_series + combined = combined.dropna() + + print(f" Combined: {len(combined)} days after join") + return combined + + +# --------------------------------------------------------------------------- +# OLS calibration +# --------------------------------------------------------------------------- + +def run_ols_calibration(daily_df, base_fee, model="sqrt"): + """OLS regression for Tsoukalas model params. + + Parameters + ---------- + daily_df : pd.DataFrame + Must contain columns: volume_usd, volatility, effective_tvl_usd. + base_fee : float + Static swap fee (e.g. 0.003). + model : str + 'sqrt' or 'log' — TVL regressor transformation. + + Returns + ------- + noise_params : dict + Coefficients for run_fingerprint["reclamm_noise_params"]. + diagnostics : dict + Standard errors, R², residual summary. + """ + if model == "loglinear": + # Multiplicative model: log(V) = b_0 + b_sigma·σ + b_c·log(TVL) + # Implies: V = exp(b_0) · TVL^b_c · exp(b_sigma·σ) + mask = daily_df["volume_usd"].values > 0 + n_dropped = int((~mask).sum()) + df_fit = daily_df[mask] + + y_log = np.log(df_fit["volume_usd"].values) + X = np.column_stack([ + np.ones(len(df_fit)), + df_fit["volatility"].values, + np.log(df_fit["effective_tvl_usd"].values), + ]) + + beta, _, _, _ = np.linalg.lstsq(X, y_log, rcond=None) + b_0, b_sigma, b_c = beta + + residuals = y_log - X @ beta + n, k = X.shape + bread = np.linalg.inv(X.T @ X) + hc1_scale = n / max(n - k, 1) + meat = X.T @ np.diag(residuals**2 * hc1_scale) @ X + robust_cov = bread @ meat @ bread + se = np.sqrt(np.diag(robust_cov)) + + ss_res = np.sum(residuals**2) + ss_tot = np.sum((y_log - y_log.mean())**2) + r_squared = 1.0 - ss_res / max(ss_tot, 1e-30) + + # Pseudo-R² in levels (median predictor) + y_pred_level = np.exp(X @ beta) + y_actual_level = df_fit["volume_usd"].values + res_level = y_actual_level - y_pred_level + r_sq_level = 1.0 - np.sum(res_level**2) / max( + np.sum((y_actual_level - y_actual_level.mean())**2), 1e-30) + + noise_params = { + "b_0": float(b_0), "b_sigma": float(b_sigma), + "b_c": float(b_c), "base_fee": float(base_fee), + } + diagnostics = { + "se": {"b_0": float(se[0]), "b_sigma": float(se[1]), + "b_c": float(se[2])}, + "r_squared": float(r_squared), + "r_squared_level": float(r_sq_level), + "n_obs": int(n), + "n_dropped_zero": n_dropped, + "residual_mean": float(np.mean(residuals)), + "residual_std": float(np.std(residuals)), + "smearing_factor": float(np.exp(np.var(residuals, ddof=1) / 2)), + "model": "loglinear", + } + return noise_params, diagnostics + + # --- Linear models (sqrt / log) --- + y = daily_df["volume_usd"].values / 1e6 + + if model == "sqrt": + tvl_eff = np.sqrt(daily_df["effective_tvl_usd"].values / 1e6) + elif model == "log": + tvl_eff = np.log(np.maximum(daily_df["effective_tvl_usd"].values / 1e6, 1e-30)) + else: + raise ValueError(f"Unknown model: {model!r}. Use 'sqrt', 'log', or 'loglinear'.") + + X = np.column_stack([ + np.ones(len(daily_df)), # a_0 + daily_df["volatility"].values, # a_sigma + tvl_eff, # a_c + ]) + + beta, residuals_ss, rank, sv = np.linalg.lstsq(X, y, rcond=None) + a_0, a_sigma, a_c = beta + + # Heteroskedasticity-robust standard errors (HC1) + residuals = y - X @ beta + n, k = X.shape + bread = np.linalg.inv(X.T @ X) + hc1_scale = n / max(n - k, 1) + meat = X.T @ np.diag(residuals**2 * hc1_scale) @ X + robust_cov = bread @ meat @ bread + se = np.sqrt(np.diag(robust_cov)) + + # R-squared + ss_res = np.sum(residuals**2) + ss_tot = np.sum((y - np.mean(y))**2) + r_squared = 1.0 - ss_res / max(ss_tot, 1e-30) + + noise_params = { + "a_0_base": float(a_0), + "a_f": 0.0, # not identified with static fees + "a_sigma": float(a_sigma), + "a_c": float(a_c), + "base_fee": float(base_fee), + } + + diagnostics = { + "se": dict(zip( + ["a_0", "a_sigma", "a_c"], + se.tolist(), + )), + "r_squared": float(r_squared), + "n_obs": int(n), + "residual_mean": float(np.mean(residuals)), + "residual_std": float(np.std(residuals)), + "model": model, + } + + return noise_params, diagnostics + + +# --------------------------------------------------------------------------- +# Plotting +# --------------------------------------------------------------------------- + +def plot_calibration_diagnostics(daily_df, noise_params, diagnostics, + pool_label="", model="sqrt", + output_dir="results"): + """Generate diagnostic plots for the noise volume calibration. + + Produces a 2×2 figure: + Top-left: Time series — real vs predicted daily volume + effective TVL + Top-right: Scatter — predicted vs actual with 45° line + Bot-left: Residuals vs time + residuals vs fitted + Bot-right: Component decomposition (stacked contributions) + + Parameters + ---------- + daily_df : pd.DataFrame + Calibration DataFrame (indexed by date). + noise_params : dict + Fitted coefficients from run_ols_calibration. + diagnostics : dict + Diagnostics dict from run_ols_calibration. + pool_label : str + Pool name for titles. + model : str + 'sqrt' or 'log'. + output_dir : str + Directory for output PNGs. + """ + import matplotlib + matplotlib.use("Agg") + import matplotlib.pyplot as plt + from matplotlib.dates import DateFormatter + import matplotlib.dates as mdates + + os.makedirs(output_dir, exist_ok=True) + + # Extract data + dates = pd.to_datetime(daily_df.index) + y_actual = daily_df["volume_usd"].values / 1e6 # in $M + eff_tvl = daily_df["effective_tvl_usd"].values + vol = daily_df["volatility"].values + + r2 = diagnostics["r_squared"] + n = diagnostics["n_obs"] + + if model == "loglinear": + b_0 = noise_params["b_0"] + b_sigma = noise_params["b_sigma"] + b_c = noise_params["b_c"] + log_tvl = np.log(np.maximum(eff_tvl, 1.0)) + y_pred_log = b_0 + b_sigma * vol + b_c * log_tvl + y_pred = np.exp(y_pred_log) / 1e6 # median prediction in $M + + # Log-space residuals + mask_pos = daily_df["volume_usd"].values > 0 + residuals = np.full(len(dates), np.nan) + residuals[mask_pos] = ( + np.log(daily_df["volume_usd"].values[mask_pos]) + - y_pred_log[mask_pos] + ) + resid_unit = "log scale" + r2_level = diagnostics.get("r_squared_level") + else: + a_0 = noise_params["a_0_base"] + a_sigma = noise_params["a_sigma"] + a_c = noise_params["a_c"] + + if model == "sqrt": + tvl_term = a_c * np.sqrt(eff_tvl / 1e6) + else: + tvl_term = a_c * np.log(np.maximum(eff_tvl / 1e6, 1e-30)) + + y_pred = a_0 + a_sigma * vol + tvl_term + residuals = y_actual - y_pred + resid_unit = "$M" + r2_level = None + + # --- Figure 1: Main diagnostics (2×2) --- + fig, axes = plt.subplots(2, 2, figsize=(16, 12)) + + # (0,0) Time series: real vs predicted + TVL on secondary axis + ax = axes[0, 0] + ax.plot(dates, y_actual, color="steelblue", alpha=0.7, linewidth=1, + label="Actual volume") + ax.plot(dates, y_pred, color="crimson", linewidth=1.5, + label="Predicted volume") + ax.set_ylabel("Daily volume ($M)", color="steelblue") + ax.tick_params(axis="y", labelcolor="steelblue") + ax.legend(loc="upper left", fontsize=8) + r2_str = (f"R²(log)={r2:.3f}, R²(level)={r2_level:.3f}" + if r2_level is not None else f"R²={r2:.3f}") + ax.set_title(f"Daily volume: actual vs predicted ({r2_str}, n={n})") + ax.xaxis.set_major_formatter(DateFormatter("%b %y")) + + ax2 = ax.twinx() + ax2.fill_between(dates, eff_tvl / 1e6, alpha=0.15, color="green", + label="Effective TVL ($M)") + ax2.set_ylabel("Effective TVL ($M)", color="green") + ax2.tick_params(axis="y", labelcolor="green") + ax2.legend(loc="upper right", fontsize=8) + + # (0,1) Scatter: predicted vs actual + ax = axes[0, 1] + ax.scatter(y_pred, y_actual, alpha=0.5, s=15, color="steelblue", + edgecolors="none") + lims = [min(y_pred.min(), y_actual.min()), max(y_pred.max(), y_actual.max())] + margin = (lims[1] - lims[0]) * 0.05 + lims = [lims[0] - margin, lims[1] + margin] + ax.plot(lims, lims, "k--", linewidth=0.8, alpha=0.5, label="45° line") + ax.set_xlabel("Predicted ($M)") + ax.set_ylabel("Actual ($M)") + ax.set_title("Predicted vs actual") + ax.legend(fontsize=8) + ax.set_aspect("equal", adjustable="box") + + # (1,0) Residuals: vs time (top) and vs fitted (bottom) + ax = axes[1, 0] + ax.scatter(dates, residuals, alpha=0.5, s=12, color="steelblue", + edgecolors="none") + ax.axhline(0, color="black", linewidth=0.8) + # 7-day rolling mean of residuals + res_series = pd.Series(residuals, index=dates) + rolling_mean = res_series.rolling(7, min_periods=1).mean() + ax.plot(dates, rolling_mean, color="crimson", linewidth=1.5, + label="7-day rolling mean") + ax.set_ylabel(f"Residual ({resid_unit})") + ax.set_title("Residuals vs time") + ax.legend(fontsize=8) + ax.xaxis.set_major_formatter(DateFormatter("%b %y")) + + # (1,1) Component decomposition + ax = axes[1, 1] + if model == "loglinear": + # Log-space additive decomposition as line plots + log_tvl_plot = np.log(np.maximum(eff_tvl, 1.0)) + comp_base = np.full(len(dates), b_0) + comp_tvl = b_c * log_tvl_plot + comp_vol = b_sigma * vol + total_log = comp_base + comp_tvl + comp_vol + vol_usd = daily_df["volume_usd"].values.copy() + vol_usd[vol_usd <= 0] = np.nan + actual_log = np.log(vol_usd) + ax.plot(dates, comp_base, color="grey", linestyle="--", linewidth=1, + label=f"b_0 = {b_0:.2f}") + ax.plot(dates, comp_base + comp_tvl, color="green", linewidth=1.5, + label=f"b_0 + b_c·log(TVL) (b_c={b_c:.4f})") + ax.plot(dates, total_log, color="crimson", linewidth=1.5, + label="Full prediction") + ax.scatter(dates, actual_log, color="steelblue", s=10, alpha=0.5, + label="Actual log(V)", zorder=5) + ax.set_ylabel("log(Volume, USD)") + ax.set_title("Component decomposition (log space)") + + fig.suptitle( + f"{pool_label} — noise calibration ({model})\n" + f"log(V) = {b_0:.2f} + {b_sigma:.4f}·σ + {b_c:.4f}·log(TVL)", + fontsize=11, + ) + else: + intercept_contrib = np.full(len(dates), a_0) + vol_contrib = a_sigma * vol + tvl_contrib = tvl_term + + ax.fill_between(dates, 0, intercept_contrib, alpha=0.3, color="grey", + label=f"a_0 = {a_0:.4f}") + ax.fill_between(dates, intercept_contrib, intercept_contrib + vol_contrib, + alpha=0.3, color="orange", + label=f"a_σ·σ (a_σ={a_sigma:.4f})") + ax.fill_between(dates, intercept_contrib + vol_contrib, + intercept_contrib + vol_contrib + tvl_contrib, + alpha=0.3, color="green", + label=f"a_c·{model}(TVL) (a_c={a_c:.4f})") + ax.plot(dates, y_actual, color="steelblue", linewidth=1, alpha=0.7, + label="Actual") + ax.set_ylabel("Volume ($M)") + ax.set_title("Component decomposition") + + fig.suptitle( + f"{pool_label} — Tsoukalas noise calibration ({model})\n" + f"V/1e6 = {a_0:.4f} + {a_sigma:.4f}·σ + {a_c:.4f}·{model}(TVL_eff/1e6)", + fontsize=11, + ) + ax.legend(fontsize=7, loc="upper left") + ax.xaxis.set_major_formatter(DateFormatter("%b %y")) + plt.tight_layout() + + fname = f"noise_calibration_{pool_label}_{model}.png" + path = os.path.join(output_dir, fname) + plt.savefig(path, dpi=150, bbox_inches="tight") + plt.close() + print(f" Saved: {path}") + + # --- Figure 2: Residuals vs each regressor --- + fig2, axes2 = plt.subplots(1, 3, figsize=(16, 5)) + + # Residuals vs volatility + ax = axes2[0] + ax.scatter(vol, residuals, alpha=0.5, s=12, color="orange", edgecolors="none") + ax.axhline(0, color="black", linewidth=0.8) + ax.set_xlabel("Volatility (annualised)") + ax.set_ylabel(f"Residual ({resid_unit})") + ax.set_title("Residuals vs volatility") + + # Residuals vs effective TVL + ax = axes2[1] + ax.scatter(eff_tvl / 1e6, residuals, alpha=0.5, s=12, color="green", + edgecolors="none") + ax.axhline(0, color="black", linewidth=0.8) + ax.set_xlabel("Effective TVL ($M)") + ax.set_ylabel(f"Residual ({resid_unit})") + ax.set_title("Residuals vs effective TVL") + + # Residuals vs fitted + ax = axes2[2] + ax.scatter(y_pred, residuals, alpha=0.5, s=12, color="steelblue", + edgecolors="none") + ax.axhline(0, color="black", linewidth=0.8) + ax.set_xlabel("Fitted ($M)") + ax.set_ylabel(f"Residual ({resid_unit})") + ax.set_title("Residuals vs fitted") + + fig2.suptitle(f"{pool_label} — Residual diagnostics ({model})", fontsize=11) + plt.tight_layout() + + fname2 = f"noise_residuals_{pool_label}_{model}.png" + path2 = os.path.join(output_dir, fname2) + plt.savefig(path2, dpi=150, bbox_inches="tight") + plt.close() + print(f" Saved: {path2}") + + return path, path2 + + +# --------------------------------------------------------------------------- +# CLI +# --------------------------------------------------------------------------- + +def main(): + parser = argparse.ArgumentParser( + description="Calibrate Tsoukalas noise volume model for reClAMM" + ) + parser.add_argument( + "--csv", default=None, + help="Path to CSV with columns: volume_usd, volatility, effective_tvl_usd", + ) + parser.add_argument( + "--pool", default=None, + help="Pool label from pool_registry (e.g. cbBTC_WETH) for end-to-end calibration", + ) + parser.add_argument("--base-fee", type=float, default=None, + help="Override base fee (default: use pool's swap_fee)") + parser.add_argument("--model", choices=["sqrt", "log", "loglinear"], + default="sqrt") + parser.add_argument( + "--output", default=None, + help="Output JSON file path. Defaults to stdout.", + ) + parser.add_argument( + "--plot", action="store_true", + help="Generate diagnostic plots (saved to --output-dir)", + ) + parser.add_argument( + "--output-dir", default="results", + help="Directory for diagnostic plots (default: results)", + ) + args = parser.parse_args() + + if args.csv is None and args.pool is None: + parser.error("One of --csv or --pool is required") + + if args.pool is not None: + # End-to-end mode: fetch data, assemble, calibrate + from experiments.pool_registry import POOL_REGISTRY + + if args.pool not in POOL_REGISTRY: + print(f"Unknown pool: {args.pool}", file=sys.stderr) + print(f"Available: {list(POOL_REGISTRY.keys())}", file=sys.stderr) + sys.exit(1) + + pool = POOL_REGISTRY[args.pool] + base_fee = args.base_fee if args.base_fee is not None else pool.swap_fee + + print(f"Calibrating noise model for {pool.label} ({pool.chain})") + print(f" Swap fee: {base_fee}") + print(f" Model: {args.model}") + + df = build_calibration_df(pool) + else: + # CSV mode + df = pd.read_csv(args.csv) + required_cols = {"volume_usd", "volatility", "effective_tvl_usd"} + missing = required_cols - set(df.columns) + if missing: + print(f"Error: missing columns: {missing}", file=sys.stderr) + sys.exit(1) + base_fee = args.base_fee if args.base_fee is not None else 0.003 + + noise_params, diagnostics = run_ols_calibration(df, base_fee, args.model) + + # Print diagnostics + print(f"\n OLS Results ({args.model} model):") + print(f" R² = {diagnostics['r_squared']:.4f}") + if "r_squared_level" in diagnostics: + print(f" R²(level) = {diagnostics['r_squared_level']:.4f}") + if "n_dropped_zero" in diagnostics and diagnostics["n_dropped_zero"] > 0: + print(f" Dropped {diagnostics['n_dropped_zero']} zero-volume days") + if "smearing_factor" in diagnostics: + print(f" Smearing factor = {diagnostics['smearing_factor']:.4f} " + f"(E[V]/median[V])") + print(f" n = {diagnostics['n_obs']}") + print(f" Coefficients:") + if args.model == "loglinear": + coef_keys = ["b_0", "b_sigma", "b_c"] + else: + coef_keys = ["a_0", "a_sigma", "a_c"] + for key in coef_keys: + param_key = "a_0_base" if key == "a_0" else key + val = noise_params[param_key] + se = diagnostics["se"][key] + t_stat = val / se if se > 0 else float("inf") + print(f" {key:>8} = {val:>10.4f} (SE={se:.4f}, t={t_stat:.2f})") + print(f" Residual: mean={diagnostics['residual_mean']:.6f}, " + f"std={diagnostics['residual_std']:.4f}") + + # Plot diagnostics + if args.plot: + label = args.pool if args.pool else "custom" + plot_calibration_diagnostics( + df, noise_params, diagnostics, + pool_label=label, model=args.model, + output_dir=args.output_dir, + ) + + result = { + "noise_params": noise_params, + "diagnostics": diagnostics, + } + + output_str = json.dumps(result, indent=2) + if args.output: + with open(args.output, "w") as f: + f.write(output_str + "\n") + print(f"\nWrote calibration to {args.output}", file=sys.stderr) + else: + print(f"\n{output_str}") + + +if __name__ == "__main__": + main() diff --git a/scripts/compare_modelled_vs_real.py b/scripts/compare_modelled_vs_real.py new file mode 100644 index 0000000..e23c7e4 --- /dev/null +++ b/scripts/compare_modelled_vs_real.py @@ -0,0 +1,243 @@ +"""Compare modelled reClAMM noise volume against a real pool's observed volume. + +Plots the modelled noise volume for one pool (at a specified counterfactual TVL) +against the actual observed volume of another pool (or the same pool), to +sanity-check the noise model's predictions. + +Usage: + # reClAMM AAVE/ETH modelled at $7M vs weighted wstETH/AAVE real + python scripts/compare_modelled_vs_real.py \ + --model-pool 0x9d1fcf346ea1b0 --model-tvl 7e6 \ + --real-pool 0x3de27efa2f1aa6 + + # Same but at $20M + python scripts/compare_modelled_vs_real.py \ + --model-pool 0x9d1fcf346ea1b0 --model-tvl 20e6 \ + --real-pool 0x3de27efa2f1aa6 + + # Multiple TVL levels + python scripts/compare_modelled_vs_real.py \ + --model-pool 0x9d1fcf346ea1b0 --model-tvl 1e6 7e6 20e6 50e6 \ + --real-pool 0x3de27efa2f1aa6 +""" + +import argparse +import os +import pickle + +import matplotlib +matplotlib.use("Agg") +import matplotlib.pyplot as plt +import numpy as np +import pandas as pd + +from quantammsim.calibration.noise_model_arrays import ( + build_simulator_arrays, load_artifact, _find_pool_index, +) + + +CACHE_DIR = os.path.join( + os.path.dirname(os.path.dirname(__file__)), + "results", "token_factored_calibration", "_cache", +) +ARTIFACT_DIR = os.path.join( + os.path.dirname(os.path.dirname(__file__)), + "results", "linear_market_noise", +) +OUTPUT_DIR = os.path.join( + os.path.dirname(os.path.dirname(__file__)), + "results", "noise_comparison", +) + +# Token mapping for pools +POOL_TOKENS = { + "0x9d1fcf346ea1b0": ("AAVE", "ETH"), + "0x3de27efa2f1aa6": ("AAVE", "ETH"), # wstETH/AAVE ≈ same pair + "0x0b09dea16768f0": ("DAI", "ETH"), + "0xa6f548df93de92": ("BTC", "ETH"), + "0x96646936b91d6b": ("USDC", "ETH"), +} + + +def load_real_pool(pid, mc): + """Load real observed volume + TVL for a pool.""" + entry = mc[pid] + panel = entry["panel"] + dates = pd.to_datetime(panel["date"]) + vol = np.exp(panel["log_volume"].values.astype(float)) + tvl = np.exp(panel["log_tvl_lag1"].values.astype(float)) + tokens = entry["tokens"] + chain = entry["chain"] + return dates, vol, tvl, tokens, chain + + +def compute_modelled_noise(pid, tvl_value, start_date, end_date, + artifact_dir, token_a, token_b): + """Compute modelled daily noise for a pool at a given TVL.""" + arrays = build_simulator_arrays( + token_a=token_a, token_b=token_b, + start_date=start_date, end_date=end_date, + artifact_dir=artifact_dir, pool_id=pid, + ) + + n_days = arrays["n_days"] + std_lt = (np.log(tvl_value) - arrays["tvl_mean"]) / arrays["tvl_std"] + + noise_base = arrays["noise_base"][::1440][:n_days] + tvl_coeff = arrays["noise_tvl_coeff"][::1440][:n_days] + noise_daily = np.exp(noise_base + tvl_coeff * std_lt) + + dates = pd.to_datetime(arrays["dates"][:n_days]) + return dates, noise_daily + + +def main(): + parser = argparse.ArgumentParser( + description=__doc__, + formatter_class=argparse.RawDescriptionHelpFormatter) + parser.add_argument("--model-pool", default="0x9d1fcf346ea1b0", + help="Pool ID for modelled noise") + parser.add_argument("--model-tvl", type=float, nargs="+", + default=[7_000_000], + help="Counterfactual TVL(s) for the modelled pool") + parser.add_argument("--model-tokens", nargs=2, default=None, + help="Token A and B for the modelled pool (auto-detected)") + parser.add_argument("--real-pool", default="0x3de27efa2f1aa6", + help="Pool ID for real observed data") + parser.add_argument("--artifact-dir", default=ARTIFACT_DIR) + parser.add_argument("--output-dir", default=OUTPUT_DIR) + args = parser.parse_args() + + os.environ.setdefault("JAX_PLATFORMS", "cpu") + os.makedirs(args.output_dir, exist_ok=True) + + # Load calibration data + with open(os.path.join(CACHE_DIR, "stage1.pkl"), "rb") as f: + data = pickle.load(f) + mc = data["matched_clean"] + + # Real pool data + real_dates, real_vol, real_tvl, real_tokens, real_chain = load_real_pool( + args.real_pool, mc) + print(f"Real pool: {args.real_pool} ({real_tokens}, {real_chain})") + print(f" {len(real_dates)} days: {real_dates.min().date()} → {real_dates.max().date()}") + print(f" TVL: ${real_tvl.min():,.0f} – ${real_tvl.max():,.0f}") + print(f" Volume: ${real_vol.min():,.0f} – ${real_vol.max():,.0f}") + + # Model tokens + if args.model_tokens: + tok_a, tok_b = args.model_tokens + elif args.model_pool[:16] in POOL_TOKENS: + tok_a, tok_b = POOL_TOKENS[args.model_pool[:16]] + else: + tok_a, tok_b = "ETH", "USDC" + print(f" Warning: unknown pool, using {tok_a}/{tok_b}") + + # Date range from real pool + start = str(real_dates.min().date()) + end = str(real_dates.max().date()) + + # Compute modelled noise at each TVL + model_results = [] + for tvl_val in args.model_tvl: + print(f"\nModelled: {args.model_pool} at ${tvl_val:,.0f} TVL") + m_dates, m_noise = compute_modelled_noise( + args.model_pool, tvl_val, start, end, + args.artifact_dir, tok_a, tok_b) + model_results.append((tvl_val, m_dates, m_noise)) + print(f" Median noise: ${np.median(m_noise):,.0f}/day" + f" ({np.median(m_noise)/tvl_val*100:.2f}% of TVL)") + + # Align dates + common_start = real_dates.min() + common_end = real_dates.max() + for _, md, _ in model_results: + common_start = max(common_start, md.min()) + common_end = min(common_end, md.max()) + + real_mask = (real_dates >= common_start) & (real_dates <= common_end) + + # Colors for different TVL levels + colors = ["#e74c3c", "#3498db", "#2ecc71", "#f39c12", "#9b59b6"] + + # Plot + fig, axes = plt.subplots(2, 1, figsize=(14, 9)) + + # 1. Volume comparison + ax = axes[0] + ax.plot(real_dates[real_mask], real_vol[real_mask] / 1e6, + "k-", linewidth=0.8, alpha=0.7, + label=f"{real_tokens} weighted (real," + f" TVL ${np.median(real_tvl[real_mask])/1e6:.0f}M)") + + for i, (tvl_val, m_dates, m_noise) in enumerate(model_results): + m_mask = (m_dates >= common_start) & (m_dates <= common_end) + c = colors[i % len(colors)] + ax.plot(m_dates[m_mask], m_noise[m_mask] / 1e6, + "-", color=c, linewidth=0.8, alpha=0.7, + label=f"reClAMM noise (modelled, TVL ${tvl_val/1e6:.0f}M)") + + ax.set_ylabel("Volume ($M/day)") + ax.set_yscale("log") + ax.set_title(f"Real weighted pool vs Modelled reClAMM noise") + ax.legend(fontsize=8) + ax.grid(True, alpha=0.3) + + # 2. Vol/TVL comparison + ax = axes[1] + real_vol_tvl = real_vol[real_mask] / real_tvl[real_mask] * 100 + ax.plot(real_dates[real_mask], real_vol_tvl, + "k-", linewidth=0.8, alpha=0.7, + label=f"{real_tokens} weighted real vol/TVL") + ax.axhline(np.median(real_vol_tvl), color="black", linestyle="--", + alpha=0.3, label=f"weighted median: {np.median(real_vol_tvl):.2f}%") + + for i, (tvl_val, m_dates, m_noise) in enumerate(model_results): + m_mask = (m_dates >= common_start) & (m_dates <= common_end) + noise_tvl = m_noise[m_mask] / tvl_val * 100 + c = colors[i % len(colors)] + ax.plot(m_dates[m_mask], noise_tvl, + "-", color=c, linewidth=0.8, alpha=0.7, + label=f"reClAMM noise/TVL (${tvl_val/1e6:.0f}M)") + ax.axhline(np.median(noise_tvl), color=c, linestyle="--", alpha=0.3, + label=f"median: {np.median(noise_tvl):.2f}%") + + ax.set_ylabel("Volume / TVL (%)") + ax.set_xlabel("Date") + ax.set_title("Volume as Fraction of TVL") + ax.legend(fontsize=7, loc="upper right") + ax.grid(True, alpha=0.3) + ymax = min( + max(np.percentile(real_vol_tvl, 95), + max(np.percentile(m_noise[m_mask] / tvl_val * 100, 95) + for tvl_val, m_dates, m_noise in model_results + for m_mask in [(m_dates >= common_start) & (m_dates <= common_end)])) * 1.5, + 50) + ax.set_ylim(0, ymax) + + fig.tight_layout() + tvl_str = "_".join(f"{t/1e6:.0f}M" for t in args.model_tvl) + out = os.path.join(args.output_dir, + f"{args.model_pool[:8]}_vs_{args.real_pool[:8]}_{tvl_str}.png") + fig.savefig(out, dpi=150, bbox_inches="tight") + plt.close(fig) + print(f"\nSaved: {out}") + + # Summary table + print(f"\n{'='*60}") + print(f"Summary") + print(f"{'='*60}") + print(f" Real {real_tokens} weighted:") + print(f" Median TVL: ${np.median(real_tvl[real_mask]):,.0f}") + print(f" Median vol: ${np.median(real_vol[real_mask]):,.0f}/day") + print(f" Median vol/TVL: {np.median(real_vol_tvl):.2f}%") + for tvl_val, m_dates, m_noise in model_results: + m_mask = (m_dates >= common_start) & (m_dates <= common_end) + med_noise = np.median(m_noise[m_mask]) + print(f" Modelled reClAMM at ${tvl_val/1e6:.0f}M:") + print(f" Median noise: ${med_noise:,.0f}/day") + print(f" Median noise/TVL: {med_noise/tvl_val*100:.2f}%") + + +if __name__ == "__main__": + main() diff --git a/scripts/compare_reclamm_geometric_noise_runs.py b/scripts/compare_reclamm_geometric_noise_runs.py new file mode 100644 index 0000000..96230ed --- /dev/null +++ b/scripts/compare_reclamm_geometric_noise_runs.py @@ -0,0 +1,13 @@ +"""Wrapper for the canonical reCLAMM geometric noise comparison script.""" + +from __future__ import annotations + +import runpy +from pathlib import Path + + +if __name__ == "__main__": + runpy.run_path( + str(Path(__file__).with_name("reclamm") / "compare_reclamm_geometric_noise_runs.py"), + run_name="__main__", + ) diff --git a/scripts/compare_reclamm_thermostats.py b/scripts/compare_reclamm_thermostats.py new file mode 100644 index 0000000..7fb1770 --- /dev/null +++ b/scripts/compare_reclamm_thermostats.py @@ -0,0 +1,2609 @@ +"""Compare reCLAMM interpolation modes on historic AAVE/ETH data. + +Runs the production geometric interpolation against the non-linear +constant-arc-length interpolation on: +1. The original launch-style range (price_ratio ~= 1.50) +2. A much tighter range (price_ratio = 1.10) + +The aggressive case is deliberate. A local AAVE/ETH sweep showed: +price_ratio 1.15, margin 0.5, shift 0.1 -> about +$10k vs geometric +price_ratio 1.10, margin 0.5, shift 0.1 -> about +$31k vs geometric +price_ratio 1.10, margin 0.6, shift 0.1 -> about +$73k vs geometric + +So the strongest clean demo setting came from tightening the band and +slightly raising the trigger margin, while keeping the launch-style shift +speed rather than pushing shift_exponent higher. +""" + +import gc +import hashlib +import os +from pathlib import Path + +import jax.numpy as jnp +import numpy as np +import pandas as pd +import matplotlib.pyplot as plt +from matplotlib.colors import Normalize, SymLogNorm, TwoSlopeNorm +from matplotlib.cm import ScalarMappable +from quantammsim.pools.reCLAMM.reclamm_reserves import ( + calibrate_arc_length_speed, + compute_price_ratio, + initialise_reclamm_reserves, +) +from quantammsim.runners.jax_runners import do_run_on_historic_data +from quantammsim.utils.data_processing.historic_data_utils import ( + get_historic_parquet_data, +) + + +def to_daily_price_shift_base(daily_price_shift_exponent): + """Convert shift rate to daily price shift base (matches Solidity).""" + return 1.0 - daily_price_shift_exponent / 124649.0 + + +def build_inclusive_sweep(start, stop, step): + """Build a sweep that keeps the requested step and explicitly includes the stop.""" + values = np.arange(start, stop + 1.0e-12, step, dtype=float) + if values.size == 0 or not np.isclose(values[-1], stop): + values = np.append(values, float(stop)) + return values + + +def _resolve_repo_root(script_path): + """Locate the repository root from either scripts/ or scripts/reclamm/.""" + script_path = Path(script_path).resolve() + for parent in script_path.parents: + if (parent / "quantammsim").exists() and (parent / "scripts").exists(): + return parent + return script_path.parents[1] + + +RUN_CONSTANT_ARC_LENGTH = True +INTERPOLATION_METHODS = ( + ("geometric", "constant_arc_length") + if RUN_CONSTANT_ARC_LENGTH + else ("geometric",) +) +HEATMAP_PRICE_RATIOS = build_inclusive_sweep(1.01, 3.00, 0.025) +HEATMAP_MARGINS = np.linspace(0.05, 0.90, 39) +HEATMAP_SHIFT_EXPONENTS = build_inclusive_sweep(0.01, 0.50, 0.0125) +HEATMAP_ARC_LENGTH_SPEEDS = np.geomspace(1.0e-6, 5.0e-4, 11) +PRICE_RATIO_TICKS = np.array([1.01, 1.25, 1.50, 2.00, 2.50, 3.00]) +MARGIN_TICKS = np.array([0.05, 0.15, 0.25, 0.35, 0.45, 0.55, 0.65, 0.75, 0.85, 0.90]) +SHIFT_EXPONENT_TICKS = np.array([0.01, 0.05, 0.10, 0.20, 0.30, 0.40, 0.50]) +ARC_LENGTH_SPEED_TICKS = np.array([ + 1.0e-6, + 2.0e-6, + 5.0e-6, + 1.0e-5, + 2.0e-5, + 5.0e-5, + 1.0e-4, + 2.0e-4, + 5.0e-4, +]) +SWEEP_LINE_WIDTH = 0.45 +REFERENCE_LINE_WIDTH = 0.9 +DEFAULT_INITIAL_POOL_VALUE = 1_000_000.0 +TVL_SWEEP_VALUES = ( + 1_000_000.0, + 5_000_000.0, + 20_000_000.0, +) +CENTER_ZERO_HEATMAP_COLOR_NORM = "symlog" +CENTER_ZERO_HEATMAP_COLOR_TAG = "symlog20" +CENTER_ZERO_HEATMAP_SYMLOG_LINTHRESH = 20.0 +FIXED_SLICE_FRACTIONS = (0.125, 0.375, 0.625, 0.875) +FIXED_SLICE_LABELS = ("Q1", "Q2", "Q3", "Q4") +THREE_D_VIEW_ELEVATION = 22.0 +THREE_D_VIEW_AZIMUTH = 140.0 +HEATMAP_FORWARD_CACHE_ENABLED = True +HEATMAP_FORWARD_CACHE_RUN_NAME = "aave_eth_thermostat_heatmaps_market_linear_v2" +HEATMAP_FORWARD_CACHE_ROOT = os.path.join( + "results", + "reclamm_heatmap_forward_cache", +) +HEATMAP_FORWARD_CACHE_FLUSH_EVERY = 360 + +REPO_ROOT = _resolve_repo_root(__file__) +AAVE_WETH_POOL_ID = "0x9d1fcf346ea1b0" +DEFAULT_MARKET_LINEAR_ARTIFACT_DIR = "results/linear_market_noise" +DEFAULT_MARKET_LINEAR_NOISE_START_DATE = "2024-06-01" +DEFAULT_MARKET_LINEAR_NOISE_END_DATE = "2026-03-01" +DEFAULT_MARKET_LINEAR_NOISE_ARRAYS_PATH = str( + REPO_ROOT + / "results" + / "linear_market_noise" + / "_sim_arrays" + / ( + f"{AAVE_WETH_POOL_ID}_{DEFAULT_MARKET_LINEAR_NOISE_START_DATE}_" + f"{DEFAULT_MARKET_LINEAR_NOISE_END_DATE}.npz" + ) +) +DEFAULT_NOISE_MODEL = "market_linear" +DEFAULT_GAS_COST = 1.0 +DEFAULT_PROTOCOL_FEE_SPLIT = 0.25 +FIXED_COMPARE_ARB_FREQUENCY = 15 +AAVE_ETH_NOISE_SETTINGS = { + "enable_noise_model": True, + "noise_model": DEFAULT_NOISE_MODEL, + "noise_reference_model": DEFAULT_NOISE_MODEL, + "noise_artifact_dir": DEFAULT_MARKET_LINEAR_ARTIFACT_DIR, + "noise_pool_id": AAVE_WETH_POOL_ID, + "noise_arrays_path": DEFAULT_MARKET_LINEAR_NOISE_ARRAYS_PATH, + "arb_frequency": FIXED_COMPARE_ARB_FREQUENCY, + "gas_cost": DEFAULT_GAS_COST, + "protocol_fee_split": DEFAULT_PROTOCOL_FEE_SPLIT, +} +PERSISTED_FORWARD_VALUE_COLUMNS = ( + "cache_key_hash", + "final_value", + "method", + "enable_noise_model", + "noise_model", + "price_ratio", + "centeredness_margin", + "daily_price_shift_exponent", + "initial_pool_value", + "arb_frequency", +) + +GEOMETRIC_ONLY_HEATMAP_METRIC_KEYS = ( + "geometric_vs_launch_geometric_pct", + "noise_geometric_final_value_musd", + "noise_vs_arb_geometric_improvement_pct", +) +CONSTANT_ARC_HEATMAP_METRIC_KEYS = ( + "efficiency_pct", + "launch_geometric_efficiency_pct", + "constant_arc_vs_launch_constant_arc_pct", + "noise_constant_arc_final_value_musd", + "noise_vs_arb_constant_arc_improvement_pct", +) +HEATMAP_METRIC_DEPENDENCIES = { + "efficiency_pct": ("noise_geometric", "noise_constant_arc"), + "launch_geometric_efficiency_pct": ("noise_constant_arc",), + "geometric_vs_launch_geometric_pct": ("noise_geometric",), + "constant_arc_vs_launch_constant_arc_pct": ("noise_constant_arc",), + "noise_geometric_final_value_musd": ("noise_geometric",), + "noise_constant_arc_final_value_musd": ("noise_constant_arc",), + "noise_vs_arb_geometric_improvement_pct": ("noise_geometric", "arb_geometric"), + "noise_vs_arb_constant_arc_improvement_pct": ( + "noise_constant_arc", + "arb_constant_arc", + ), +} + +_NOISE_SETTINGS_CACHE = {} +_MARKET_LINEAR_NOISE_DATA_CACHE = {} + + +def get_initial_pool_value(cfg): + """Return the configured base pool TVL in USD.""" + return float(cfg.get("initial_pool_value", DEFAULT_INITIAL_POOL_VALUE)) + + +def get_tvl_millions(cfg): + """Return the configured base pool TVL in millions of USD.""" + return get_initial_pool_value(cfg) / 1_000_000.0 + + +def format_tvl_millions_slug(cfg): + """Format the TVL in millions for stable filenames.""" + tvl_millions = get_tvl_millions(cfg) + rounded = round(float(tvl_millions), 6) + if np.isclose(rounded, round(rounded)): + return f"{int(round(rounded))}m" + return f"{rounded:.6f}".rstrip("0").rstrip(".").replace(".", "p") + "m" + + +def format_tvl_millions_label(cfg): + """Format the TVL in millions for plot titles and logs.""" + return f"{get_tvl_millions(cfg):.1f}M" + + +def tvl_artifact_filename(stem, cfg, suffix=None): + """Append a TVL-in-millions suffix to a PNG artifact name.""" + parts = [stem] + if suffix: + parts.append(suffix) + parts.append(f"tvl_{format_tvl_millions_slug(cfg)}") + return "_".join(parts) + ".png" + + +def heatmap_artifact_filename(spec, cfg, suffix=None): + """Build a heatmap filename, including any colour-style tag.""" + stem = f"reclamm_heatmap_{spec['slug']}" + artifact_tag = spec.get("artifact_tag") + if artifact_tag: + stem = f"{stem}_{artifact_tag}" + return tvl_artifact_filename(stem, cfg, suffix=suffix) + + +def three_d_heatmap_artifact_filename(spec, cfg, suffix=None): + """Build a 3D heatmap filename, including any colour-style tag.""" + stem = f"reclamm_heatmap_3d_{spec['slug']}" + artifact_tag = spec.get("artifact_tag") + if artifact_tag: + stem = f"{stem}_{artifact_tag}" + return tvl_artifact_filename(stem, cfg, suffix=suffix) + + +def format_heatmap_param_value(value): + """Format a sweep parameter compactly for titles and logs.""" + value = float(value) + if abs(value) >= 1.0: + return f"{value:.2f}".rstrip("0").rstrip(".") + return f"{value:.3f}".rstrip("0").rstrip(".") + + +def configs_for_tvl(base_configs, initial_pool_value): + """Attach a shared initial TVL to each compare configuration.""" + configs = [] + for cfg in base_configs: + updated = dict(cfg) + updated["initial_pool_value"] = float(initial_pool_value) + configs.append(updated) + return configs + + +def _normalize_arb_frequency(value, default=FIXED_COMPARE_ARB_FREQUENCY): + """Return a stable integer arb cadence for thermostat comparisons.""" + if value is None: + if default is None: + return None + value = default + return max(int(round(float(value))), 1) + + +def get_effective_arb_frequency(cfg, noise_cfg=None): + """Resolve the arb cadence used by a thermostat comparison run.""" + del noise_cfg + return _normalize_arb_frequency(FIXED_COMPARE_ARB_FREQUENCY) + + +def _canonical_noise_reference_model(cfg): + """Resolve the only supported thermostat noise parametrisation.""" + noise_model = cfg.get("noise_model", DEFAULT_NOISE_MODEL) or DEFAULT_NOISE_MODEL + reference_model = cfg.get("noise_reference_model") + if reference_model is None: + reference_model = DEFAULT_NOISE_MODEL if noise_model == "arb_only" else noise_model + noise_model = str(noise_model) + reference_model = str(reference_model) + if noise_model not in {DEFAULT_NOISE_MODEL, "arb_only"}: + raise ValueError( + "compare_reclamm_thermostats only supports " + "'market_linear' noise and 'arb_only' baselines." + ) + if reference_model != DEFAULT_NOISE_MODEL: + raise ValueError( + "compare_reclamm_thermostats only supports the " + "'market_linear' noise parametrisation." + ) + return reference_model + + +def normalize_compare_run_cfg(cfg, enable_noise_model=None): + """Canonicalize the compare-run config so non-axis inputs stay fixed.""" + updated = dict(cfg) + updated["price_ratio"] = float(cfg["price_ratio"]) + updated["centeredness_margin"] = float(cfg["centeredness_margin"]) + updated["daily_price_shift_exponent"] = float(cfg["daily_price_shift_exponent"]) + updated["initial_pool_value"] = float(get_initial_pool_value(cfg)) + updated["gas_cost"] = DEFAULT_GAS_COST + updated["protocol_fee_split"] = DEFAULT_PROTOCOL_FEE_SPLIT + updated["arb_fees"] = 0.0 + updated["arb_frequency"] = get_effective_arb_frequency(cfg) + updated["noise_trader_ratio"] = 0.0 + + arc_length_speed = cfg.get("arc_length_speed") + if arc_length_speed is None: + updated.pop("arc_length_speed", None) + else: + updated["arc_length_speed"] = float(arc_length_speed) + + use_noise = ( + bool(cfg.get("enable_noise_model", False)) + if enable_noise_model is None + else bool(enable_noise_model) + ) + updated["enable_noise_model"] = use_noise + + reference_mode = _canonical_noise_reference_model(cfg) + if use_noise: + updated["noise_model"] = reference_mode + updated["noise_reference_model"] = reference_mode + else: + updated["noise_model"] = "arb_only" + updated["noise_reference_model"] = reference_mode + + updated["noise_arrays_path"] = DEFAULT_MARKET_LINEAR_NOISE_ARRAYS_PATH + updated.pop("reclamm_noise_params", None) + updated["noise_artifact_dir"] = DEFAULT_MARKET_LINEAR_ARTIFACT_DIR + updated["noise_pool_id"] = AAVE_WETH_POOL_ID + + return updated + + +def make_noise_variant_cfg(cfg, enable_noise_model): + """Return a config with either noise modelling or pure arb-only enabled.""" + return normalize_compare_run_cfg(cfg, enable_noise_model=enable_noise_model) + + +def _hashable_noise_params(params): + """Convert a noise-params dict into a stable cache key fragment.""" + if params is None: + return None + return tuple(sorted((str(k), round(float(v), 12)) for k, v in params.items())) + + +def load_shared_market_linear_noise_data( + arrays_path=DEFAULT_MARKET_LINEAR_NOISE_ARRAYS_PATH, +): + """Load the market_linear arrays once so compare runs can reuse them.""" + arrays_path = os.path.abspath(os.fspath(arrays_path)) + cached = _MARKET_LINEAR_NOISE_DATA_CACHE.get(arrays_path) + if cached is not None: + return cached + + if not os.path.exists(arrays_path): + raise FileNotFoundError(f"market_linear arrays file not found: {arrays_path}") + + with np.load(arrays_path) as arrays: + required_keys = {"noise_base", "noise_tvl_coeff", "tvl_mean", "tvl_std"} + missing_keys = sorted(required_keys.difference(arrays.files)) + if missing_keys: + raise KeyError( + f"market_linear arrays file {arrays_path} is missing keys: {missing_keys}" + ) + shared = { + "arrays_path": arrays_path, + "noise_base_array": np.asarray(arrays["noise_base"]), + "noise_tvl_coeff_array": np.asarray(arrays["noise_tvl_coeff"]), + "tvl_mean": float(arrays["tvl_mean"]), + "tvl_std": float(arrays["tvl_std"]), + } + _MARKET_LINEAR_NOISE_DATA_CACHE[arrays_path] = shared + return shared + + +def _load_market_linear_noise_stats(arrays_path=DEFAULT_MARKET_LINEAR_NOISE_ARRAYS_PATH): + """Load the exact arrays file used by the market_linear run fingerprint. + + The simulator consumes ``noise_base`` and ``noise_tvl_coeff`` from + ``run_fingerprint["noise_arrays_path"]`` and uses ``tvl_mean``/``tvl_std`` + from the same file for TVL standardization. + """ + shared = load_shared_market_linear_noise_data(arrays_path=arrays_path) + return shared["arrays_path"], shared["tvl_mean"], shared["tvl_std"] + + +def _market_linear_noise_settings(noise_model="market_linear", arb_frequency=None): + """Build the tuned market_linear fingerprint block from the fixed arrays file.""" + arrays_path, tvl_mean, tvl_std = _load_market_linear_noise_stats() + arb_frequency = _normalize_arb_frequency(arb_frequency) + return { + "noise_model": noise_model, + "noise_trader_ratio": 0.0, + "reclamm_noise_params": { + "tvl_mean": tvl_mean, + "tvl_std": tvl_std, + }, + "noise_arrays_path": arrays_path, + "arb_frequency": arb_frequency, + "noise_summary": f"{noise_model} (arb_frequency={arb_frequency})", + "noise_cache_key": ( + noise_model, + arrays_path, + arb_frequency, + round(tvl_mean, 12), + round(tvl_std, 12), + ), + } + +def resolve_reclamm_noise_settings(cfg): + """Resolve the active reCLAMM noise-model fingerprint block for a config.""" + cfg = normalize_compare_run_cfg(cfg) + enable_noise_model = cfg.get("enable_noise_model", False) + requested_mode = cfg.get("noise_model", DEFAULT_NOISE_MODEL) + reference_mode = cfg.get("noise_reference_model", DEFAULT_NOISE_MODEL) + requested_arb_frequency = get_effective_arb_frequency(cfg) + cache_key = ( + tuple(cfg.get("tokens", [])), + cfg.get("start"), + cfg.get("end"), + enable_noise_model, + requested_mode, + reference_mode, + cfg.get("noise_artifact_dir", DEFAULT_MARKET_LINEAR_ARTIFACT_DIR), + cfg.get("noise_pool_id", AAVE_WETH_POOL_ID), + requested_arb_frequency, + round(float(cfg.get("noise_trader_ratio", 0.0)), 12), + _hashable_noise_params(cfg.get("reclamm_noise_params")), + cfg.get("noise_arrays_path"), + ) + if cache_key in _NOISE_SETTINGS_CACHE: + return _NOISE_SETTINGS_CACHE[cache_key] + + if requested_mode == "arb_only": + result = _market_linear_noise_settings( + noise_model="arb_only", + arb_frequency=requested_arb_frequency, + ) + elif requested_mode == DEFAULT_NOISE_MODEL: + result = _market_linear_noise_settings( + noise_model=DEFAULT_NOISE_MODEL, + arb_frequency=requested_arb_frequency, + ) + else: + raise ValueError( + "compare_reclamm_thermostats only supports " + "'market_linear' noise and 'arb_only' baselines." + ) + + _NOISE_SETTINGS_CACHE[cache_key] = result + return result + + +# Pool configurations to compare +CONFIGS = [ + { + "name": "AAVE/ETH launch-style range (25bps, reference)", + "tokens": ["AAVE", "ETH"], + "start": "2024-06-01 00:00:00", + "end": "2025-06-01 00:00:00", + "fees": 0.0025, + "price_ratio": 1.5014, + "centeredness_margin": 0.5, + "daily_price_shift_exponent": 0.1, + "reason": "Original launch-style parameters.", + **AAVE_ETH_NOISE_SETTINGS, + }, + { + "name": "AAVE/ETH aggressive tight range (25bps)", + "tokens": ["AAVE", "ETH"], + "start": "2024-06-01 00:00:00", + "end": "2025-06-01 00:00:00", + "fees": 0.0025, + "price_ratio": 1.10, + "centeredness_margin": 0.60, + "daily_price_shift_exponent": 0.1, + "reason": ( + "Aggressively tightened and moved to an earlier thermostat trigger. " + "At fixed price_ratio=1.10, the shift_exponent sweep still favored " + "0.1, while margin=0.60 widened the non-linear edge materially." + ), + **AAVE_ETH_NOISE_SETTINGS, + }, +] + + +def _attach_market_linear_noise_arrays( + fingerprint, + noise_cfg, + market_linear_noise_data, +): + """Attach preloaded market_linear arrays when the compare flow has them.""" + if market_linear_noise_data is None: + return + expected_path = noise_cfg.get("noise_arrays_path") + if expected_path is None: + return + shared_path = os.path.abspath(os.fspath(market_linear_noise_data["arrays_path"])) + expected_path = os.path.abspath(os.fspath(expected_path)) + if shared_path != expected_path: + raise ValueError( + "Shared market_linear noise arrays path does not match " + f"the resolved compare-run noise path: {shared_path} != {expected_path}" + ) + fingerprint["noise_base_array"] = market_linear_noise_data["noise_base_array"] + fingerprint["noise_tvl_coeff_array"] = market_linear_noise_data["noise_tvl_coeff_array"] + + +def make_fingerprint(cfg, interpolation_method, market_linear_noise_data=None): + """Build run fingerprint for a given config and interpolation method.""" + cfg = normalize_compare_run_cfg(cfg) + speed_override = ( + cfg.get("arc_length_speed") + if interpolation_method == "constant_arc_length" + else None + ) + noise_cfg = resolve_reclamm_noise_settings(cfg) + arb_frequency = get_effective_arb_frequency(cfg, noise_cfg) + fingerprint = { + "tokens": cfg["tokens"], + "rule": "reclamm", + "startDateString": cfg["start"], + "endDateString": cfg["end"], + "initial_pool_value": get_initial_pool_value(cfg), + "do_arb": True, + "fees": cfg["fees"], + "gas_cost": cfg.get( + "gas_cost", + DEFAULT_GAS_COST if cfg.get("enable_noise_model", False) else 0.0, + ), + "arb_fees": cfg.get("arb_fees", 0.0), + "protocol_fee_split": cfg.get( + "protocol_fee_split", + DEFAULT_PROTOCOL_FEE_SPLIT if cfg.get("enable_noise_model", False) else 0.0, + ), + "noise_trader_ratio": noise_cfg.get("noise_trader_ratio", 0.0), + "reclamm_interpolation_method": interpolation_method, + "reclamm_arc_length_speed": speed_override, + } + if noise_cfg.get("noise_model") is not None: + fingerprint["noise_model"] = noise_cfg["noise_model"] + if noise_cfg.get("reclamm_noise_params") is not None: + fingerprint["reclamm_noise_params"] = noise_cfg["reclamm_noise_params"] + if noise_cfg.get("noise_arrays_path") is not None: + fingerprint["noise_arrays_path"] = noise_cfg["noise_arrays_path"] + _attach_market_linear_noise_arrays( + fingerprint, + noise_cfg, + market_linear_noise_data, + ) + if arb_frequency is not None: + fingerprint["arb_frequency"] = arb_frequency + return fingerprint + + +def make_params(cfg): + """Build pool params from config.""" + cfg = normalize_compare_run_cfg(cfg) + return { + "price_ratio": jnp.array(cfg["price_ratio"]), + "centeredness_margin": jnp.array(cfg["centeredness_margin"]), + "daily_price_shift_base": jnp.array( + to_daily_price_shift_base(cfg["daily_price_shift_exponent"]) + ), + } + + +def load_shared_price_data(configs, root=None): + """Load the shared historic price panel once for all compare runs.""" + tokens = sorted({token for cfg in configs for token in cfg["tokens"]}) + return get_historic_parquet_data(tokens, cols=["close"], root=root) + + +def run_comparison( + cfg, + price_data=None, + low_data_mode=False, + market_linear_noise_data=None, +): + """Run both interpolation variants, return results dict.""" + params = make_params(cfg) + + results = {} + for method in INTERPOLATION_METHODS: + fp = make_fingerprint( + cfg, + method, + market_linear_noise_data=market_linear_noise_data, + ) + results[method] = do_run_on_historic_data( + run_fingerprint=fp, + params=params, + price_data=price_data, + low_data_mode=low_data_mode, + ) + + return results + + +def _set_padded_ylim(ax, series_list, pad_ratio=0.04): + """Fit the y-axis tightly around the plotted series.""" + flat = [ + np.asarray(series, dtype=float).ravel() + for series in series_list + if np.asarray(series).size > 0 + ] + if not flat: + return + + values = np.concatenate(flat) + values = values[np.isfinite(values)] + if values.size == 0: + return + + ymin = float(values.min()) + ymax = float(values.max()) + if np.isclose(ymin, ymax): + pad = max(abs(ymin) * pad_ratio, 1e-6) + else: + pad = (ymax - ymin) * pad_ratio + ax.set_ylim(ymin - pad, ymax + pad) + + +def _cache_size(cache): + """Count memoized final-value cache entries materialised in memory.""" + return len(cache.get("_final_value_cache", {})) + + +def _comparison_cache_size(cache): + """Count memoized scalar comparison bundles.""" + return len(cache.get("_comparison_cache", {})) + + +def _heatmap_forward_cache_scope_slug(cfg): + """Build a compact cache scope slug for a shared-TVL heatmap run.""" + if cfg is None: + return "unspecified_tvl" + return f"tvl_{format_tvl_millions_slug(cfg)}" + + +def _heatmap_forward_cache_path(cfg): + """Return the parquet path for persisted scalar forward values.""" + if not HEATMAP_FORWARD_CACHE_ENABLED: + return None + return os.path.join( + HEATMAP_FORWARD_CACHE_ROOT, + HEATMAP_FORWARD_CACHE_RUN_NAME, + f"forward_values_{_heatmap_forward_cache_scope_slug(cfg)}.parquet", + ) + + +def _make_method_cache_hash(key): + """Build a compact stable digest for a method cache key.""" + return hashlib.sha256(repr(key).encode("utf-8")).hexdigest() + + +def _build_persistent_final_value_record(cfg, method, cache_key_hash, final_value): + """Build one self-describing parquet row for a cached scalar run result.""" + cfg = normalize_compare_run_cfg(cfg) + noise_cfg = resolve_reclamm_noise_settings(cfg) + return { + "cache_key_hash": str(cache_key_hash), + "final_value": float(final_value), + "method": str(method), + "enable_noise_model": bool(cfg.get("enable_noise_model", False)), + "noise_model": noise_cfg.get("noise_model"), + "price_ratio": float(cfg["price_ratio"]), + "centeredness_margin": float(cfg["centeredness_margin"]), + "daily_price_shift_exponent": float(cfg["daily_price_shift_exponent"]), + "initial_pool_value": float(get_initial_pool_value(cfg)), + "arb_frequency": get_effective_arb_frequency(cfg, noise_cfg), + } + + +def _load_persistent_final_value_cache(cache): + """Load persisted scalar forward values from parquet once per sweep cache.""" + if cache.get("_persistent_final_value_cache_loaded"): + return + + disk_cache = {} + next_batch_id = 0 + cache_path = cache.get("_persistent_final_value_cache_path") + if cache_path and os.path.exists(cache_path): + parquet_files = [] + if os.path.isdir(cache_path): + parquet_files = [ + os.path.join(cache_path, filename) + for filename in sorted(os.listdir(cache_path)) + if filename.endswith(".parquet") + ] + batch_ids = [] + for filename in os.listdir(cache_path): + if not (filename.startswith("batch_") and filename.endswith(".parquet")): + continue + token = filename[len("batch_") : -len(".parquet")] + if token.isdigit(): + batch_ids.append(int(token)) + next_batch_id = (max(batch_ids) + 1) if batch_ids else 0 + else: + parquet_files = [cache_path] + + for parquet_file in parquet_files: + frame = pd.read_parquet( + parquet_file, + columns=["cache_key_hash", "final_value"], + ) + if frame.empty: + continue + for row in frame.itertuples(index=False): + cache_key_hash = str(row.cache_key_hash) + final_value = float(row.final_value) + disk_cache[cache_key_hash] = final_value + print( + f"Loaded {len(disk_cache)} persisted heatmap forward values from {cache_path}" + ) + + cache["_persistent_final_value_cache"] = disk_cache + cache["_persistent_final_value_next_batch_id"] = next_batch_id + cache["_persistent_final_value_cache_loaded"] = True + + +def flush_sweep_cache(cache, force=False): + """Persist newly computed scalar forward values to parquet.""" + if not HEATMAP_FORWARD_CACHE_ENABLED: + return + + pending = cache.get("_pending_persistent_final_values") + if not pending: + return + if not force and len(pending) < HEATMAP_FORWARD_CACHE_FLUSH_EVERY: + return + + _load_persistent_final_value_cache(cache) + disk_cache = cache.setdefault("_persistent_final_value_cache", {}) + batch_records = [] + for cache_key_hash, record in pending.items(): + normalized = dict(record) + normalized["cache_key_hash"] = str(cache_key_hash) + normalized["final_value"] = float(normalized["final_value"]) + disk_cache[cache_key_hash] = normalized["final_value"] + batch_records.append(normalized) + + cache_path = cache.get("_persistent_final_value_cache_path") + if cache_path is None: + pending.clear() + return + + if os.path.exists(cache_path) and not os.path.isdir(cache_path): + raise RuntimeError( + f"Persistent cache path {cache_path} already exists as a file. " + "Use a fresh cache namespace for append-only parquet shards." + ) + + os.makedirs(cache_path, exist_ok=True) + batch_records.sort(key=lambda record: record["cache_key_hash"]) + payload = { + column: [record.get(column) for record in batch_records] + for column in PERSISTED_FORWARD_VALUE_COLUMNS + } + payload["final_value"] = np.asarray(payload["final_value"], dtype=np.float64) + frame = pd.DataFrame(payload) + batch_id = int(cache.setdefault("_persistent_final_value_next_batch_id", 0)) + batch_path = os.path.join(cache_path, f"batch_{batch_id:08d}.parquet") + cache["_persistent_final_value_next_batch_id"] = batch_id + 1 + frame.to_parquet(batch_path, index=False, compression="zstd") + print( + f"Persisted {len(pending)} new heatmap forward values to {batch_path} " + f"({len(disk_cache)} total cached values)." + ) + pending.clear() + + +def make_sweep_cache( + price_data, + cache_scope_cfg=None, + market_linear_noise_data=None, +): + """Create a shared cache for heatmap and line sweeps.""" + cache = { + "_shared_price_data": price_data, + "_shared_market_linear_noise_data": market_linear_noise_data, + "_final_value_cache": {}, + "_comparison_cache": {}, + "_pending_persistent_final_values": {}, + "_persistent_final_value_cache": {}, + "_persistent_final_value_next_batch_id": 0, + "_persistent_final_value_cache_loaded": False, + "_persistent_final_value_cache_path": _heatmap_forward_cache_path( + cache_scope_cfg + ), + } + return cache + + +def _missing_artifacts(progress_label, filenames): + """Report which plot artifacts still need to be generated.""" + missing = [filename for filename in filenames if not os.path.exists(filename)] + if not missing: + print(f"[{progress_label}] skipping sweep: all artifacts already exist.") + return set() + + existing_count = len(filenames) - len(missing) + if existing_count: + print( + f"[{progress_label}] reusing {existing_count}/{len(filenames)} " + "existing artifacts; generating the missing outputs." + ) + return set(missing) + + +def _speed_cache_key(speed): + """Stable cache token for optional arc-length speed.""" + if speed is None: + return None + return round(float(speed), 12) + + +def _make_method_cache_key(cfg, method): + """Cache key for a single-method final-value run.""" + cfg = normalize_compare_run_cfg(cfg) + noise_cfg = resolve_reclamm_noise_settings(cfg) + arb_frequency = get_effective_arb_frequency(cfg, noise_cfg) + key = ( + method, + tuple(str(token) for token in cfg["tokens"]), + str(cfg["start"]), + str(cfg["end"]), + round(float(cfg["fees"]), 12), + bool(cfg.get("enable_noise_model", False)), + round(float(cfg["price_ratio"]), 6), + round(float(cfg["centeredness_margin"]), 6), + round(float(cfg["daily_price_shift_exponent"]), 6), + round(get_initial_pool_value(cfg), 2), + noise_cfg.get("noise_cache_key"), + None if arb_frequency is None else int(arb_frequency), + round( + float( + cfg.get( + "gas_cost", + DEFAULT_GAS_COST if cfg.get("enable_noise_model", False) else 0.0, + ) + ), + 6, + ), + round( + float( + cfg.get( + "protocol_fee_split", + DEFAULT_PROTOCOL_FEE_SPLIT if cfg.get("enable_noise_model", False) else 0.0, + ) + ), + 6, + ), + ) + if method == "constant_arc_length": + key += (_speed_cache_key(cfg.get("arc_length_speed")),) + return key + + +def _nearest_price_row(price_data, start_ts): + """Select the closest available price row to the requested start timestamp.""" + if len(price_data.index) == 0: + raise ValueError("price_data is empty") + + if isinstance(price_data.index, pd.DatetimeIndex): + target_ts = start_ts + index_tz = getattr(price_data.index, "tz", None) + if index_tz is not None and target_ts.tzinfo is None: + target_ts = target_ts.tz_localize(index_tz) + elif index_tz is None and target_ts.tzinfo is not None: + target_ts = target_ts.tz_convert(None) + target_value = int(target_ts.value) + index_values = price_data.index.asi8 + else: + target_value = int(start_ts.timestamp() * 1000.0) + index_values = price_data.index.to_numpy(dtype=np.int64) + + row_idx = int(np.searchsorted(index_values, target_value, side="left")) + if row_idx >= len(index_values): + row_idx = len(index_values) - 1 + elif row_idx > 0 and index_values[row_idx] != target_value: + prev_idx = row_idx - 1 + if abs(int(index_values[prev_idx]) - target_value) <= abs( + int(index_values[row_idx]) - target_value + ): + row_idx = prev_idx + + row = price_data.iloc[row_idx] + if isinstance(row, pd.DataFrame): + row = row.iloc[0] + return row + + +def _make_comparison_cache_key(cfg, launch_final_values): + """Cache key for scalar heatmap metrics at a single parameter point.""" + noise_cfg = make_noise_variant_cfg(cfg, True) + arb_only_cfg = make_noise_variant_cfg(cfg, False) + key = [ + _make_method_cache_key(noise_cfg, "geometric"), + _make_method_cache_key(arb_only_cfg, "geometric"), + round(float(launch_final_values["geometric"]), 6), + ] + if RUN_CONSTANT_ARC_LENGTH: + key.extend( + [ + _make_method_cache_key(noise_cfg, "constant_arc_length"), + _make_method_cache_key(arb_only_cfg, "constant_arc_length"), + round(float(launch_final_values["constant_arc_length"]), 6), + ] + ) + return tuple(key) + + +def _run_method_final_value_cached(cfg, method, cache): + """Memoize final value for a single interpolation method.""" + final_value_cache = cache.setdefault("_final_value_cache", {}) + key = _make_method_cache_key(cfg, method) + if key in final_value_cache: + return final_value_cache[key] + + _load_persistent_final_value_cache(cache) + key_hash = _make_method_cache_hash(key) + persisted_cache = cache.setdefault("_persistent_final_value_cache", {}) + if key_hash in persisted_cache: + final_value_cache[key] = persisted_cache[key_hash] + return final_value_cache[key] + + result = do_run_on_historic_data( + run_fingerprint=make_fingerprint( + cfg, + method, + market_linear_noise_data=cache.get("_shared_market_linear_noise_data"), + ), + params=make_params(cfg), + price_data=cache["_shared_price_data"], + low_data_mode=True, + ) + final_value_cache[key] = float(result["final_value"]) + cache.setdefault("_pending_persistent_final_values", {})[key_hash] = ( + _build_persistent_final_value_record( + cfg=cfg, + method=method, + cache_key_hash=key_hash, + final_value=final_value_cache[key], + ) + ) + flush_sweep_cache(cache, force=False) + del result + gc.collect() + return final_value_cache[key] + + +def extract_comparison_metrics_from_final_values( + geo_final, arc_final, launch_final_values +): + """Summarize scalar comparison metrics from final values only.""" + return { + "efficiency_pct": (arc_final / max(abs(geo_final), 1e-12) - 1.0) * 100.0, + "launch_geometric_efficiency_pct": ( + arc_final / max(abs(launch_final_values["geometric"]), 1e-12) - 1.0 + ) + * 100.0, + "geometric_vs_launch_geometric_pct": ( + geo_final / max(abs(launch_final_values["geometric"]), 1e-12) - 1.0 + ) + * 100.0, + "constant_arc_vs_launch_constant_arc_pct": ( + arc_final + / max(abs(launch_final_values["constant_arc_length"]), 1e-12) + - 1.0 + ) + * 100.0, + } + + +def _load_required_heatmap_final_values(cfg, cache, metric_keys): + """Load only the cached final values needed for the requested heatmap metrics.""" + required_sources = set() + for metric_key in metric_keys: + required_sources.update(HEATMAP_METRIC_DEPENDENCIES[metric_key]) + + if not RUN_CONSTANT_ARC_LENGTH and any( + source.endswith("constant_arc") for source in required_sources + ): + raise ValueError( + "Constant-arc heatmap metric requested while RUN_CONSTANT_ARC_LENGTH=False" + ) + + final_values = {} + noise_cfg = None + arb_only_cfg = None + + if any(source.startswith("noise_") for source in required_sources): + noise_cfg = make_noise_variant_cfg(cfg, True) + if any(source.startswith("arb_") for source in required_sources): + arb_only_cfg = make_noise_variant_cfg(cfg, False) + + if "noise_geometric" in required_sources: + final_values["noise_geometric"] = _run_method_final_value_cached( + noise_cfg, + "geometric", + cache, + ) + if "noise_constant_arc" in required_sources: + final_values["noise_constant_arc"] = _run_method_final_value_cached( + noise_cfg, + "constant_arc_length", + cache, + ) + if "arb_geometric" in required_sources: + final_values["arb_geometric"] = _run_method_final_value_cached( + arb_only_cfg, + "geometric", + cache, + ) + if "arb_constant_arc" in required_sources: + final_values["arb_constant_arc"] = _run_method_final_value_cached( + arb_only_cfg, + "constant_arc_length", + cache, + ) + return final_values + + +def extract_heatmap_metrics_from_mode_final_values( + metric_keys, + final_values, + launch_final_values, +): + """Collect the requested scalar heatmap metrics from cached final values.""" + metrics = {} + + if "efficiency_pct" in metric_keys: + metrics["efficiency_pct"] = ( + final_values["noise_constant_arc"] + / max(abs(final_values["noise_geometric"]), 1e-12) + - 1.0 + ) * 100.0 + + if "launch_geometric_efficiency_pct" in metric_keys: + metrics["launch_geometric_efficiency_pct"] = ( + final_values["noise_constant_arc"] + / max(abs(launch_final_values["geometric"]), 1e-12) + - 1.0 + ) * 100.0 + + if "geometric_vs_launch_geometric_pct" in metric_keys: + metrics["geometric_vs_launch_geometric_pct"] = ( + final_values["noise_geometric"] + / max(abs(launch_final_values["geometric"]), 1e-12) + - 1.0 + ) * 100.0 + + if "constant_arc_vs_launch_constant_arc_pct" in metric_keys: + metrics["constant_arc_vs_launch_constant_arc_pct"] = ( + final_values["noise_constant_arc"] + / max(abs(launch_final_values["constant_arc_length"]), 1e-12) + - 1.0 + ) * 100.0 + + if "noise_geometric_final_value_musd" in metric_keys: + metrics["noise_geometric_final_value_musd"] = ( + final_values["noise_geometric"] / 1e6 + ) + + if "noise_constant_arc_final_value_musd" in metric_keys: + metrics["noise_constant_arc_final_value_musd"] = ( + final_values["noise_constant_arc"] / 1e6 + ) + + if "noise_vs_arb_geometric_improvement_pct" in metric_keys: + metrics["noise_vs_arb_geometric_improvement_pct"] = ( + final_values["noise_geometric"] + / max(abs(final_values["arb_geometric"]), 1e-12) + - 1.0 + ) * 100.0 + + if "noise_vs_arb_constant_arc_improvement_pct" in metric_keys: + metrics["noise_vs_arb_constant_arc_improvement_pct"] = ( + final_values["noise_constant_arc"] + / max(abs(final_values["arb_constant_arc"]), 1e-12) + - 1.0 + ) * 100.0 + + return metrics + + +def extract_comparison_metrics(results, launch_final_values): + """Summarize scalar heatmap metrics for a pair of runs.""" + geo = results["geometric"] + arc = results["constant_arc_length"] + + geo_final = float(geo["final_value"]) + arc_final = float(arc["final_value"]) + + return extract_comparison_metrics_from_final_values( + geo_final, + arc_final, + launch_final_values=launch_final_values, + ) + + +def run_comparison_cached(cfg, cache, launch_final_values, metric_keys): + """Memoize scalar heatmap metrics across heatmap sweeps.""" + requested_metric_keys = tuple(dict.fromkeys(metric_keys)) + comparison_cache = cache.setdefault("_comparison_cache", {}) + cache_key = _make_comparison_cache_key(cfg, launch_final_values) + cached_metrics = comparison_cache.setdefault(cache_key, {}) + missing_metric_keys = [ + metric_key for metric_key in requested_metric_keys if metric_key not in cached_metrics + ] + if missing_metric_keys: + final_values = _load_required_heatmap_final_values( + cfg, + cache, + missing_metric_keys, + ) + cached_metrics.update( + extract_heatmap_metrics_from_mode_final_values( + missing_metric_keys, + final_values, + launch_final_values=launch_final_values, + ) + ) + return { + metric_key: cached_metrics[metric_key] for metric_key in requested_metric_keys + } + + +def build_heatmap_matrices( + x_values, + y_values, + x_key, + y_key, + base_cfg, + metric_keys, + cache, + progress_label, + launch_final_values, +): + """Evaluate multiple metrics over a 2D parameter grid in one pass.""" + data = { + metric_key: np.zeros((len(y_values), len(x_values)), dtype=float) + for metric_key in metric_keys + } + total_points = len(y_values) * len(x_values) + + print( + f"[{progress_label}] start: {len(y_values)} rows x {len(x_values)} cols " + f"= {total_points} parameter points" + ) + + for yi, y_value in enumerate(y_values): + final_cache_before_row = _cache_size(cache) + comparison_cache_before_row = _comparison_cache_size(cache) + for xi, x_value in enumerate(x_values): + cfg = dict(base_cfg) + cfg[x_key] = float(x_value) + cfg[y_key] = float(y_value) + metrics = run_comparison_cached( + cfg, + cache, + launch_final_values=launch_final_values, + metric_keys=metric_keys, + ) + for metric_key in metric_keys: + data[metric_key][yi, xi] = metrics[metric_key] + + completed_points = (yi + 1) * len(x_values) + row_new_final_entries = _cache_size(cache) - final_cache_before_row + row_new_comparisons = ( + _comparison_cache_size(cache) - comparison_cache_before_row + ) + row_pct = completed_points / total_points * 100.0 + flush_sweep_cache(cache, force=True) + print( + f"[{progress_label}] row {yi + 1}/{len(y_values)} complete " + f"({y_key}={float(y_value):.4f}, {completed_points}/{total_points} " + f"points, {row_pct:.1f}%, {row_new_final_entries} new final-value cache entries, " + f"{row_new_comparisons} new comparison bundles)" + ) + + print( + f"[{progress_label}] done: " + + ", ".join( + ( + f"{metric_key} min={float(np.nanmin(data[metric_key])):.4f}, " + f"max={float(np.nanmax(data[metric_key])):.4f}" + ) + for metric_key in metric_keys + ) + + ( + f", final_value_cache_size={_cache_size(cache)}, " + f"comparison_cache_size={_comparison_cache_size(cache)}" + ) + ) + + return data + + +def build_metric_curve( + x_values, + x_key, + base_cfg, + metric_key, + cache, + launch_final_values, +): + """Evaluate one metric over a 1D sweep.""" + data = np.zeros(len(x_values), dtype=float) + for xi, x_value in enumerate(x_values): + cfg = dict(base_cfg) + cfg[x_key] = float(x_value) + metrics = run_comparison_cached( + cfg, + cache, + launch_final_values=launch_final_values, + metric_keys=(metric_key,), + ) + data[xi] = metrics[metric_key] + flush_sweep_cache(cache, force=True) + return data + + +def _compute_axis_edges(values, scale="linear"): + """Convert axis centers to cell edges for pcolormesh.""" + values = np.asarray(values, dtype=float) + if values.size == 1: + if scale == "log": + return np.array([values[0] / np.sqrt(10.0), values[0] * np.sqrt(10.0)]) + pad = max(abs(values[0]) * 0.5, 1.0) + return np.array([values[0] - pad, values[0] + pad]) + + if scale == "log": + log_values = np.log10(values) + edges = np.empty(values.size + 1, dtype=float) + edges[1:-1] = 0.5 * (log_values[:-1] + log_values[1:]) + edges[0] = log_values[0] - 0.5 * (log_values[1] - log_values[0]) + edges[-1] = log_values[-1] + 0.5 * (log_values[-1] - log_values[-2]) + return 10.0 ** edges + + edges = np.empty(values.size + 1, dtype=float) + edges[1:-1] = 0.5 * (values[:-1] + values[1:]) + edges[0] = values[0] - 0.5 * (values[1] - values[0]) + edges[-1] = values[-1] + 0.5 * (values[-1] - values[-2]) + return edges + + +def build_fixed_slice_variants(values): + """Pick four representative quarter-range slices from a sweep grid.""" + values = np.asarray(values, dtype=float) + if values.size < len(FIXED_SLICE_FRACTIONS): + raise ValueError("Need at least four grid points to build fixed slices") + + variants = [] + used_indices = set() + for idx, fraction in enumerate(FIXED_SLICE_FRACTIONS): + target_index = int(round(fraction * (values.size - 1))) + while target_index in used_indices and target_index + 1 < values.size: + target_index += 1 + while target_index in used_indices and target_index - 1 >= 0: + target_index -= 1 + if target_index in used_indices: + raise ValueError("Could not build four unique fixed slices from sweep grid") + used_indices.add(target_index) + variants.append( + { + "index": target_index, + "fraction": fraction, + "label": FIXED_SLICE_LABELS[idx], + "slug": f"q{idx + 1}", + "value": float(values[target_index]), + } + ) + return variants + + +def _pair_slice_suffix(pair, slice_variant): + """Build a stable artifact suffix for a pairwise fixed-variable slice.""" + return f"{pair['slug']}_{pair['fixed_slug']}_{slice_variant['slug']}" + + +def _build_heatmap_norm( + data_arrays, + center_zero, + color_norm=None, + symlog_linthresh=None, +): + """Build a color normalizer shared by 2D and 3D heatmaps.""" + finite_parts = [] + for data in data_arrays: + finite = np.asarray(data, dtype=float) + finite = finite[np.isfinite(finite)] + if finite.size: + finite_parts.append(finite) + finite = np.concatenate(finite_parts) if finite_parts else np.array([], dtype=float) + + if center_zero: + if finite.size == 0: + vmax = 1.0 + else: + vmax = max(abs(float(finite.min())), abs(float(finite.max())), 1e-9) + if ( + color_norm == "symlog" + and symlog_linthresh is not None + and vmax > symlog_linthresh + ): + return SymLogNorm( + linthresh=symlog_linthresh, + linscale=1.0, + vmin=-vmax, + vmax=vmax, + base=10.0, + ) + return TwoSlopeNorm(vcenter=0.0, vmin=-vmax, vmax=vmax) + + if finite.size == 0: + vmin, vmax = 0.0, 1.0 + else: + vmin = float(finite.min()) + vmax = float(finite.max()) + if np.isclose(vmin, vmax): + pad = max(abs(vmin) * 0.01, 1e-9) + vmin -= pad + vmax += pad + return Normalize(vmin=vmin, vmax=vmax) + + +def get_pair_heatmap_metric_specs(): + """Return the standard thermostat pairwise heatmap metrics.""" + metric_specs = [ + { + "key": "efficiency_pct", + "title": "Efficiency vs heatmap geometric", + "colorbar_label": "Const Arc - heatmap Geo (% of heatmap geometric final value)", + "slug": "efficiency", + "center_zero": True, + "cmap": "RdYlGn", + }, + { + "key": "launch_geometric_efficiency_pct", + "title": "Efficiency vs launch-style geometric", + "colorbar_label": "Const Arc - launch Geo (% of launch geometric final value)", + "slug": "launch_geometric_efficiency", + "center_zero": True, + "cmap": "RdYlGn", + }, + { + "key": "geometric_vs_launch_geometric_pct", + "title": "Geometric tuning vs launch-style geometric", + "colorbar_label": "Candidate Geo - launch Geo (% of launch geometric final value)", + "slug": "geometric_vs_launch_geometric", + "center_zero": True, + "cmap": "RdYlGn", + }, + { + "key": "constant_arc_vs_launch_constant_arc_pct", + "title": "Const arc tuning vs launch-style const arc", + "colorbar_label": "Candidate Const Arc - launch Const Arc (% of launch const arc final value)", + "slug": "constant_arc_vs_launch_constant_arc", + "center_zero": True, + "cmap": "RdYlGn", + }, + { + "key": "noise_geometric_final_value_musd", + "title": "Geometric final value with noise model", + "colorbar_label": "Geometric final value with noise model ($M)", + "slug": "noise_geometric_final_value", + "center_zero": False, + "cmap": "viridis", + }, + { + "key": "noise_constant_arc_final_value_musd", + "title": "Const arc final value with noise model", + "colorbar_label": "Const Arc final value with noise model ($M)", + "slug": "noise_constant_arc_final_value", + "center_zero": False, + "cmap": "viridis", + }, + { + "key": "noise_vs_arb_geometric_improvement_pct", + "title": "Noise-model improvement over arb-only (geometric)", + "colorbar_label": "Noise-model Geo - arb-only Geo (% of arb-only final value)", + "slug": "noise_vs_arb_geometric_improvement", + "center_zero": True, + "cmap": "RdYlGn", + }, + { + "key": "noise_vs_arb_constant_arc_improvement_pct", + "title": "Noise-model improvement over arb-only (const arc)", + "colorbar_label": "Noise-model Const Arc - arb-only Const Arc (% of arb-only final value)", + "slug": "noise_vs_arb_constant_arc_improvement", + "center_zero": True, + "cmap": "RdYlGn", + }, + ] + for spec in metric_specs: + if spec["center_zero"]: + spec["color_norm"] = CENTER_ZERO_HEATMAP_COLOR_NORM + spec["symlog_linthresh"] = CENTER_ZERO_HEATMAP_SYMLOG_LINTHRESH + spec["artifact_tag"] = CENTER_ZERO_HEATMAP_COLOR_TAG + if not RUN_CONSTANT_ARC_LENGTH: + metric_specs = [ + spec + for spec in metric_specs + if spec["key"] in GEOMETRIC_ONLY_HEATMAP_METRIC_KEYS + ] + return metric_specs + + +def get_pair_heatmap_specs(base_cfg): + """Return the three pairwise thermostat heatmap families plus slice settings.""" + fixed_slice_variants = { + "price_ratio": build_fixed_slice_variants(HEATMAP_PRICE_RATIOS), + "centeredness_margin": build_fixed_slice_variants(HEATMAP_MARGINS), + "daily_price_shift_exponent": build_fixed_slice_variants( + HEATMAP_SHIFT_EXPONENTS + ), + } + return [ + { + "slug": "price_ratio_vs_margin", + "x_values": HEATMAP_PRICE_RATIOS, + "y_values": HEATMAP_MARGINS, + "x_key": "price_ratio", + "y_key": "centeredness_margin", + "x_label": "Price ratio", + "y_label": "Centeredness margin", + "xticks": PRICE_RATIO_TICKS, + "yticks": MARGIN_TICKS, + "fixed_key": "daily_price_shift_exponent", + "fixed_label": "Shift exponent", + "fixed_slug": "shift_exp", + "fixed_slices": fixed_slice_variants["daily_price_shift_exponent"], + }, + { + "slug": "shift_exp_vs_margin", + "x_values": HEATMAP_SHIFT_EXPONENTS, + "y_values": HEATMAP_MARGINS, + "x_key": "daily_price_shift_exponent", + "y_key": "centeredness_margin", + "x_label": "Shift exponent", + "y_label": "Centeredness margin", + "xticks": SHIFT_EXPONENT_TICKS, + "yticks": MARGIN_TICKS, + "fixed_key": "price_ratio", + "fixed_label": "Price ratio", + "fixed_slug": "price_ratio", + "fixed_slices": fixed_slice_variants["price_ratio"], + }, + { + "slug": "price_ratio_vs_shift_exp", + "x_values": HEATMAP_PRICE_RATIOS, + "y_values": HEATMAP_SHIFT_EXPONENTS, + "x_key": "price_ratio", + "y_key": "daily_price_shift_exponent", + "x_label": "Price ratio", + "y_label": "Shift exponent", + "xticks": PRICE_RATIO_TICKS, + "yticks": SHIFT_EXPONENT_TICKS, + "fixed_key": "centeredness_margin", + "fixed_label": "Centeredness margin", + "fixed_slug": "margin", + "fixed_slices": fixed_slice_variants["centeredness_margin"], + }, + ] + + +def plot_heatmap( + data, + x_values, + y_values, + x_label, + y_label, + title, + colorbar_label, + filename, + xticks=None, + yticks=None, + xscale="linear", + center_zero=True, + cmap=None, + color_norm=None, + symlog_linthresh=None, +): + """Render and save a single heatmap.""" + norm = _build_heatmap_norm( + [data], + center_zero=center_zero, + color_norm=color_norm, + symlog_linthresh=symlog_linthresh, + ) + cmap_name = cmap or ("RdYlGn" if center_zero else "viridis") + + x_edges = _compute_axis_edges(x_values, scale=xscale) + y_edges = _compute_axis_edges(y_values, scale="linear") + + fig, ax = plt.subplots(figsize=(8.5, 6.0)) + im = ax.pcolormesh( + x_edges, + y_edges, + data, + cmap=cmap_name, + norm=norm, + shading="auto", + ) + + ax.set_xlabel(x_label) + ax.set_ylabel(y_label) + ax.set_title(title) + if xscale == "log": + ax.set_xscale("log") + ax.set_xticks(np.asarray(xticks if xticks is not None else x_values, dtype=float)) + ax.set_yticks(np.asarray(yticks if yticks is not None else y_values, dtype=float)) + ax.grid(False) + + cbar = fig.colorbar(im, ax=ax) + cbar.set_label(colorbar_label) + + plt.tight_layout() + plt.savefig(filename, dpi=150) + print(f"Saved {filename}") + plt.close(fig) + + +def plot_three_variable_heatmap_3d( + price_margin_data, + shift_margin_data, + price_shift_data, + fixed_price_ratio, + fixed_margin, + fixed_shift_exponent, + title, + colorbar_label, + filename, + center_zero=True, + cmap=None, + color_norm=None, + symlog_linthresh=None, +): + """Render orthogonal 3D heatmap surfaces across the three thermostat variables.""" + norm = _build_heatmap_norm( + [price_margin_data, shift_margin_data, price_shift_data], + center_zero=center_zero, + color_norm=color_norm, + symlog_linthresh=symlog_linthresh, + ) + cmap_name = cmap or ("RdYlGn" if center_zero else "viridis") + cmap_obj = plt.get_cmap(cmap_name) + + price_margin_x, price_margin_y = np.meshgrid(HEATMAP_PRICE_RATIOS, HEATMAP_MARGINS) + price_margin_z = np.full_like(price_margin_x, fixed_shift_exponent, dtype=float) + + shift_margin_z, shift_margin_y = np.meshgrid( + HEATMAP_SHIFT_EXPONENTS, + HEATMAP_MARGINS, + ) + shift_margin_x = np.full_like(shift_margin_z, fixed_price_ratio, dtype=float) + + price_shift_x, price_shift_z = np.meshgrid( + HEATMAP_PRICE_RATIOS, + HEATMAP_SHIFT_EXPONENTS, + ) + price_shift_y = np.full_like(price_shift_x, fixed_margin, dtype=float) + + fig = plt.figure(figsize=(10.5, 7.2)) + ax = fig.add_subplot(111, projection="3d") + ax.set_facecolor("white") + fig.patch.set_facecolor("white") + + ax.plot_surface( + price_margin_x, + price_margin_y, + price_margin_z, + facecolors=cmap_obj(norm(np.asarray(price_margin_data, dtype=float))), + shade=False, + ) + ax.plot_surface( + shift_margin_x, + shift_margin_y, + shift_margin_z, + facecolors=cmap_obj(norm(np.asarray(shift_margin_data, dtype=float))), + shade=False, + ) + ax.plot_surface( + price_shift_x, + price_shift_y, + price_shift_z, + facecolors=cmap_obj(norm(np.asarray(price_shift_data, dtype=float))), + shade=False, + ) + + ax.set_xlim(float(HEATMAP_PRICE_RATIOS.min()), float(HEATMAP_PRICE_RATIOS.max())) + ax.set_ylim(float(HEATMAP_MARGINS.min()), float(HEATMAP_MARGINS.max())) + ax.set_zlim( + float(HEATMAP_SHIFT_EXPONENTS.min()), + float(HEATMAP_SHIFT_EXPONENTS.max()), + ) + ax.set_xlabel("Price ratio") + ax.set_ylabel("Centeredness margin") + ax.set_zlabel("Shift exponent") + ax.set_xticks(PRICE_RATIO_TICKS) + ax.set_yticks(MARGIN_TICKS[::2]) + ax.set_zticks(SHIFT_EXPONENT_TICKS) + ax.set_title(title) + ax.grid(False) + ax.view_init(elev=THREE_D_VIEW_ELEVATION, azim=THREE_D_VIEW_AZIMUTH) + try: + ax.set_box_aspect( + ( + float(HEATMAP_PRICE_RATIOS.max() - HEATMAP_PRICE_RATIOS.min()), + float(HEATMAP_MARGINS.max() - HEATMAP_MARGINS.min()), + float( + HEATMAP_SHIFT_EXPONENTS.max() - HEATMAP_SHIFT_EXPONENTS.min() + ), + ) + ) + except AttributeError: + pass + + sm = ScalarMappable(norm=norm, cmap=cmap_obj) + sm.set_array([]) + cbar = fig.colorbar(sm, ax=ax, fraction=0.03, pad=0.1, shrink=0.82) + cbar.set_label(colorbar_label) + + plt.tight_layout() + plt.savefig(filename, dpi=150) + print(f"Saved {filename}") + plt.close(fig) + + +def plot_arc_speed_line_chart( + data, + x_values, + y_values, + y_label, + title, + filename, + launch_curve, + launch_auto_speed=None, +): + """Plot thin multi-series efficiency lines over the arc-speed sweep.""" + fig, ax = plt.subplots(figsize=(10.5, 5.75)) + cmap = plt.cm.viridis + colors = cmap(np.linspace(0.0, 1.0, len(y_values))) + plotted_series = [] + + for yi, (y_value, color) in enumerate(zip(y_values, colors)): + series = np.asarray(data[yi], dtype=float) + plotted_series.append(series) + ax.plot( + x_values, + series, + color=color, + linewidth=SWEEP_LINE_WIDTH, + alpha=0.8, + ) + + launch_curve = np.asarray(launch_curve, dtype=float) + plotted_series.append(launch_curve) + ax.plot( + x_values, + launch_curve, + color="black", + linewidth=REFERENCE_LINE_WIDTH, + alpha=0.9, + label="Current launch config", + ) + if launch_auto_speed is not None: + ax.axvline( + float(launch_auto_speed), + color="black", + ls=":", + linewidth=0.8, + alpha=0.7, + label="Launch auto-cal speed", + ) + + ax.axhline(0.0, color="gray", ls="--", linewidth=0.8, alpha=0.5) + ax.set_xscale("log") + ax.set_xticks(ARC_LENGTH_SPEED_TICKS) + ax.set_xlabel("Arc-length speed") + ax.set_ylabel("Efficiency vs geometric (%)") + ax.set_title(title) + _set_padded_ylim(ax, plotted_series, pad_ratio=0.08) + ax.grid(True, alpha=0.25) + ax.legend(fontsize=8) + + sm = ScalarMappable( + norm=Normalize(vmin=float(np.min(y_values)), vmax=float(np.max(y_values))), + cmap=cmap, + ) + sm.set_array([]) + cbar = fig.colorbar(sm, ax=ax) + cbar.set_label(y_label) + + plt.tight_layout() + plt.savefig(filename, dpi=150) + print(f"Saved {filename}") + plt.close(fig) + + +def generate_heatmaps(base_cfg, price_data, launch_final_values, cache=None): + """Generate pairwise heatmaps for thermostat tuning and noise-vs-arb effects.""" + owns_cache = cache is None + if cache is None: + cache = make_sweep_cache(price_data, cache_scope_cfg=base_cfg) + metric_specs = get_pair_heatmap_metric_specs() + pair_specs = get_pair_heatmap_specs(base_cfg) + metric_spec_map = {spec["key"]: spec for spec in metric_specs} + slice_count = len(pair_specs[0]["fixed_slices"]) if pair_specs else 0 + + if RUN_CONSTANT_ARC_LENGTH: + print( + "Using launch-style benchmarks " + f"Geo=${launch_final_values['geometric']:,.0f}, " + f"Const Arc=${launch_final_values['constant_arc_length']:,.0f}, " + f"TVL={format_tvl_millions_label(base_cfg)}." + ) + print( + "Running {count} heatmap pair sweeps sequentially " + "(3 pair grids x {slice_count} fixed-variable quarter slices; " + "cached noise-model runs are reused across the absolute, launch, " + "and arb-only comparison outputs).".format( + count=len(pair_specs) * slice_count, + slice_count=slice_count, + ) + ) + else: + print( + "Using launch-style geometric benchmark " + f"Geo=${launch_final_values['geometric']:,.0f}, " + f"TVL={format_tvl_millions_label(base_cfg)}." + ) + print( + "RUN_CONSTANT_ARC_LENGTH=False, so only geometric heatmaps will be generated " + f"across {len(pair_specs) * slice_count} fixed-variable pair sweeps." + ) + + for pair in pair_specs: + for slice_variant in pair["fixed_slices"]: + pair_suffix = _pair_slice_suffix(pair, slice_variant) + slice_cfg = dict(base_cfg) + slice_cfg[pair["fixed_key"]] = float(slice_variant["value"]) + output_files = { + spec["key"]: heatmap_artifact_filename( + spec, + base_cfg, + suffix=pair_suffix, + ) + for spec in metric_specs + } + missing_files = _missing_artifacts( + pair_suffix, + list(output_files.values()), + ) + if not missing_files: + continue + + missing_metric_keys = [ + spec["key"] + for spec in metric_specs + if output_files[spec["key"]] in missing_files + ] + data_by_metric = build_heatmap_matrices( + x_values=pair["x_values"], + y_values=pair["y_values"], + x_key=pair["x_key"], + y_key=pair["y_key"], + base_cfg=slice_cfg, + metric_keys=missing_metric_keys, + cache=cache, + progress_label=pair_suffix, + launch_final_values=launch_final_values, + ) + print(f"[{pair_suffix}] plotting missing heatmaps...") + for metric_key in missing_metric_keys: + spec = metric_spec_map[metric_key] + plot_heatmap( + data=data_by_metric[metric_key], + x_values=pair["x_values"], + y_values=pair["y_values"], + x_label=pair["x_label"], + y_label=pair["y_label"], + title=( + f"{spec['title']}: {pair['fixed_label']} {slice_variant['label']} " + f"slice fixed at {format_heatmap_param_value(slice_variant['value'])} | " + f"TVL {format_tvl_millions_label(base_cfg)}" + ), + colorbar_label=spec["colorbar_label"], + filename=output_files[metric_key], + xticks=pair["xticks"], + yticks=pair["yticks"], + center_zero=spec["center_zero"], + cmap=spec["cmap"], + color_norm=spec.get("color_norm"), + symlog_linthresh=spec.get("symlog_linthresh"), + ) + del data_by_metric + gc.collect() + + if owns_cache: + flush_sweep_cache(cache, force=True) + cache.clear() + gc.collect() + print("Released heatmap metric cache.") + + +def generate_three_variable_3d_heatmaps( + base_cfg, + price_data, + launch_final_values, + cache=None, +): + """Render 3D thermostat heatmaps from the three pairwise quarter slices.""" + owns_cache = cache is None + if cache is None: + cache = make_sweep_cache(price_data, cache_scope_cfg=base_cfg) + + metric_specs = get_pair_heatmap_metric_specs() + metric_spec_map = {spec["key"]: spec for spec in metric_specs} + pair_specs = get_pair_heatmap_specs(base_cfg) + pair_by_fixed_key = {pair["fixed_key"]: pair for pair in pair_specs} + price_margin_pair = pair_by_fixed_key["daily_price_shift_exponent"] + shift_margin_pair = pair_by_fixed_key["price_ratio"] + price_shift_pair = pair_by_fixed_key["centeredness_margin"] + slice_count = len(price_margin_pair["fixed_slices"]) + + def build_pair_slice_data(pair, slice_variant, metric_keys): + pair_cfg = dict(base_cfg) + pair_cfg[pair["fixed_key"]] = float(slice_variant["value"]) + return build_heatmap_matrices( + x_values=pair["x_values"], + y_values=pair["y_values"], + x_key=pair["x_key"], + y_key=pair["y_key"], + base_cfg=pair_cfg, + metric_keys=metric_keys, + cache=cache, + progress_label=f"3d_{_pair_slice_suffix(pair, slice_variant)}", + launch_final_values=launch_final_values, + ) + + print( + "\nGenerating 3D thermostat heatmaps " + f"({slice_count} quarter-slice variants, TVL={format_tvl_millions_label(base_cfg)})..." + ) + + for slice_idx in range(slice_count): + shift_slice = price_margin_pair["fixed_slices"][slice_idx] + price_slice = shift_margin_pair["fixed_slices"][slice_idx] + margin_slice = price_shift_pair["fixed_slices"][slice_idx] + slice_slug = shift_slice["slug"] + slice_label = shift_slice["label"] + + output_files = { + spec["key"]: three_d_heatmap_artifact_filename( + spec, + base_cfg, + suffix=f"slice_{slice_slug}", + ) + for spec in metric_specs + } + missing_files = _missing_artifacts( + f"3d_slice_{slice_slug}", + list(output_files.values()), + ) + if not missing_files: + continue + + missing_metric_keys = [ + spec["key"] + for spec in metric_specs + if output_files[spec["key"]] in missing_files + ] + price_margin_data = build_pair_slice_data( + price_margin_pair, + shift_slice, + missing_metric_keys, + ) + shift_margin_data = build_pair_slice_data( + shift_margin_pair, + price_slice, + missing_metric_keys, + ) + price_shift_data = build_pair_slice_data( + price_shift_pair, + margin_slice, + missing_metric_keys, + ) + + for metric_key in missing_metric_keys: + spec = metric_spec_map[metric_key] + plot_three_variable_heatmap_3d( + price_margin_data=price_margin_data[metric_key], + shift_margin_data=shift_margin_data[metric_key], + price_shift_data=price_shift_data[metric_key], + fixed_price_ratio=float(price_slice["value"]), + fixed_margin=float(margin_slice["value"]), + fixed_shift_exponent=float(shift_slice["value"]), + title=( + f"{spec['title']} 3D {slice_label} slice | TVL {format_tvl_millions_label(base_cfg)}\n" + f"price_ratio={format_heatmap_param_value(price_slice['value'])}, " + f"margin={format_heatmap_param_value(margin_slice['value'])}, " + f"shift_exp={format_heatmap_param_value(shift_slice['value'])}" + ), + colorbar_label=spec["colorbar_label"], + filename=output_files[metric_key], + center_zero=spec["center_zero"], + cmap=spec["cmap"], + color_norm=spec.get("color_norm"), + symlog_linthresh=spec.get("symlog_linthresh"), + ) + + del price_margin_data, shift_margin_data, price_shift_data + gc.collect() + + if owns_cache: + flush_sweep_cache(cache, force=True) + cache.clear() + gc.collect() + print("Released 3D heatmap cache.") + + +def compute_auto_calibrated_arc_length_speed(cfg, price_data): + """Compute the launch/reference auto-calibrated speed for a config.""" + start_ts = pd.Timestamp(cfg["start"]) + row = _nearest_price_row(price_data, start_ts) + + if isinstance(price_data.columns, pd.MultiIndex): + initial_price_values = [ + float(row[(token, "close")]) + for token in cfg["tokens"] + ] + else: + initial_price_values = [ + float(row[f"close_{token}"]) + for token in cfg["tokens"] + ] + + initial_prices = jnp.array(initial_price_values, dtype=jnp.float64) + initial_reserves, Va, Vb = initialise_reclamm_reserves( + get_initial_pool_value(cfg), + initial_prices, + float(cfg["price_ratio"]), + ) + market_price_0 = float(initial_prices[0] / initial_prices[1]) + sqrt_Q = jnp.sqrt( + compute_price_ratio( + initial_reserves[0], + initial_reserves[1], + Va, + Vb, + ) + ) + return float( + calibrate_arc_length_speed( + initial_reserves[0], + initial_reserves[1], + Va, + Vb, + to_daily_price_shift_base(float(cfg["daily_price_shift_exponent"])), + 60.0, + sqrt_Q, + market_price_0, + centeredness_margin=float(cfg["centeredness_margin"]), + ) + ) + + +def generate_arc_speed_efficiency_artifacts( + base_cfg, + launch_cfg, + price_data, + launch_final_values, + cache=None, +): + """Generate arc-speed heatmaps plus the existing efficiency line charts.""" + if not RUN_CONSTANT_ARC_LENGTH: + print("\nSkipping arc-speed heatmaps because RUN_CONSTANT_ARC_LENGTH=False.") + return + owns_cache = cache is None + if cache is None: + cache = make_sweep_cache(price_data, cache_scope_cfg=base_cfg) + launch_auto_speed = compute_auto_calibrated_arc_length_speed(launch_cfg, price_data) + heatmap_metric_specs = [ + { + "key": "efficiency_pct", + "title": "Efficiency vs geometric", + "colorbar_label": "Const Arc - heatmap Geo (% of heatmap geometric final value)", + "slug": "efficiency", + "center_zero": True, + "cmap": "RdYlGn", + }, + { + "key": "noise_constant_arc_final_value_musd", + "title": "Const arc final value with noise model", + "colorbar_label": "Const Arc final value with noise model ($M)", + "slug": "noise_constant_arc_final_value", + "center_zero": False, + "cmap": "viridis", + }, + { + "key": "noise_vs_arb_constant_arc_improvement_pct", + "title": "Noise-model improvement over arb-only (const arc)", + "colorbar_label": "Noise-model Const Arc - arb-only Const Arc (% of arb-only final value)", + "slug": "noise_vs_arb_constant_arc_improvement", + "center_zero": True, + "cmap": "RdYlGn", + }, + ] + for spec in heatmap_metric_specs: + if spec["center_zero"]: + spec["color_norm"] = CENTER_ZERO_HEATMAP_COLOR_NORM + spec["symlog_linthresh"] = CENTER_ZERO_HEATMAP_SYMLOG_LINTHRESH + spec["artifact_tag"] = CENTER_ZERO_HEATMAP_COLOR_TAG + pair_specs = [ + { + "slug": "arc_speed_vs_price_ratio", + "x_values": HEATMAP_ARC_LENGTH_SPEEDS, + "y_values": HEATMAP_PRICE_RATIOS, + "x_key": "arc_length_speed", + "y_key": "price_ratio", + "x_label": "Arc-length speed", + "y_label": "Price ratio", + "title_suffix": ( + f"margin fixed at {base_cfg['centeredness_margin']:.2f}, " + f"shift_exp fixed at {base_cfg['daily_price_shift_exponent']:.2f}" + ), + "xticks": ARC_LENGTH_SPEED_TICKS, + "yticks": PRICE_RATIO_TICKS, + }, + { + "slug": "arc_speed_vs_margin", + "x_values": HEATMAP_ARC_LENGTH_SPEEDS, + "y_values": HEATMAP_MARGINS, + "x_key": "arc_length_speed", + "y_key": "centeredness_margin", + "x_label": "Arc-length speed", + "y_label": "Centeredness margin", + "title_suffix": ( + f"price_ratio fixed at {base_cfg['price_ratio']:.2f}, " + f"shift_exp fixed at {base_cfg['daily_price_shift_exponent']:.2f}" + ), + "xticks": ARC_LENGTH_SPEED_TICKS, + "yticks": MARGIN_TICKS + }, + { + "slug": "arc_speed_vs_shift_exp", + "x_values": HEATMAP_ARC_LENGTH_SPEEDS, + "y_values": HEATMAP_SHIFT_EXPONENTS, + "x_key": "arc_length_speed", + "y_key": "daily_price_shift_exponent", + "x_label": "Arc-length speed", + "y_label": "Shift exponent", + "title_suffix": ( + f"price_ratio fixed at {base_cfg['price_ratio']:.2f}, " + f"margin fixed at {base_cfg['centeredness_margin']:.2f}" + ), + "xticks": ARC_LENGTH_SPEED_TICKS, + "yticks": SHIFT_EXPONENT_TICKS, + }, + ] + metric_spec_map = {spec["key"]: spec for spec in heatmap_metric_specs} + + print( + "\nGenerating arc-speed heatmaps and line charts " + f"(launch auto-cal speed={launch_auto_speed:.3e}, TVL={format_tvl_millions_label(base_cfg)})..." + ) + + for pair in pair_specs: + heatmap_files = { + spec["key"]: heatmap_artifact_filename( + spec, + base_cfg, + suffix=pair["slug"], + ) + for spec in heatmap_metric_specs + } + line_filename = tvl_artifact_filename( + "reclamm_line_efficiency", + base_cfg, + suffix=pair["slug"], + ) + missing_files = _missing_artifacts( + pair["slug"], + list(heatmap_files.values()) + [line_filename], + ) + if not missing_files: + continue + + missing_metric_keys = [ + spec["key"] + for spec in heatmap_metric_specs + if heatmap_files[spec["key"]] in missing_files + ] + if line_filename in missing_files and "efficiency_pct" not in missing_metric_keys: + missing_metric_keys.append("efficiency_pct") + + data_by_metric = build_heatmap_matrices( + x_values=pair["x_values"], + y_values=pair["y_values"], + x_key=pair["x_key"], + y_key=pair["y_key"], + base_cfg=base_cfg, + metric_keys=missing_metric_keys, + cache=cache, + progress_label=pair["slug"], + launch_final_values=launch_final_values, + ) + for metric_key in missing_metric_keys: + if metric_key not in heatmap_files: + continue + if heatmap_files[metric_key] not in missing_files: + continue + spec = metric_spec_map[metric_key] + plot_heatmap( + data=data_by_metric[metric_key], + x_values=pair["x_values"], + y_values=pair["y_values"], + x_label=pair["x_label"], + y_label=pair["y_label"], + title=( + f"{spec['title']}: {pair['title_suffix']} | " + f"TVL {format_tvl_millions_label(base_cfg)}" + ), + colorbar_label=spec["colorbar_label"], + filename=heatmap_files[metric_key], + xticks=pair["xticks"], + yticks=pair["yticks"], + xscale="log", + center_zero=spec["center_zero"], + cmap=spec["cmap"], + color_norm=spec.get("color_norm"), + symlog_linthresh=spec.get("symlog_linthresh"), + ) + + if line_filename in missing_files: + efficiency_data = data_by_metric["efficiency_pct"] + launch_curve = build_metric_curve( + x_values=pair["x_values"], + x_key=pair["x_key"], + base_cfg=launch_cfg, + metric_key="efficiency_pct", + cache=cache, + launch_final_values=launch_final_values, + ) + plot_arc_speed_line_chart( + data=efficiency_data, + x_values=pair["x_values"], + y_values=pair["y_values"], + y_label=pair["y_label"], + title=( + "Arc-speed efficiency sweep: " + f"{pair['title_suffix']} | TVL {format_tvl_millions_label(base_cfg)}" + ), + filename=line_filename, + launch_curve=launch_curve, + launch_auto_speed=launch_auto_speed, + ) + del data_by_metric + gc.collect() + + if owns_cache: + flush_sweep_cache(cache, force=True) + cache.clear() + gc.collect() + print("Released arc-speed sweep cache.") + + +def get_launch_final_values( + all_results, + launch_cfg, + price_data, + market_linear_noise_data=None, +): + """Reuse launch-style runs when available; otherwise run them once.""" + for cfg, results in all_results: + if cfg["name"] == launch_cfg["name"]: + launch_final_values = { + "geometric": float(results["geometric"]["final_value"]), + } + if "constant_arc_length" in results: + launch_final_values["constant_arc_length"] = float( + results["constant_arc_length"]["final_value"] + ) + return launch_final_values + + print("\nRunning launch-style benchmarks for heatmaps...") + launch_results = run_comparison( + launch_cfg, + price_data=price_data, + low_data_mode=True, + market_linear_noise_data=market_linear_noise_data, + ) + launch_final_values = { + "geometric": float(launch_results["geometric"]["final_value"]), + } + if "constant_arc_length" in launch_results: + launch_final_values["constant_arc_length"] = float( + launch_results["constant_arc_length"]["final_value"] + ) + del launch_results + gc.collect() + return launch_final_values + + + +def print_comparison(cfg, results): + """Print text summary table.""" + methods = [("Geometric", results["geometric"])] + has_constant_arc = "constant_arc_length" in results + if has_constant_arc: + methods.append(("Const Arc", results["constant_arc_length"])) + noise_cfg = resolve_reclamm_noise_settings(cfg) + + hodl_value = float((methods[0][1]["reserves"][0] * methods[0][1]["prices"][-1]).sum()) + + print("=" * 105) + print(f" {cfg['name']}") + print(f" price_ratio={cfg['price_ratio']}, " + f"margin={cfg['centeredness_margin']}, " + f"shift_exp={cfg['daily_price_shift_exponent']}, " + f"fees={cfg['fees']}") + print( + f" base_tvl=${get_initial_pool_value(cfg):,.0f} " + f"(TVL {format_tvl_millions_label(cfg)})" + ) + print(f" note={cfg['reason']}") + print( + f" noise={noise_cfg['noise_summary']}, " + f"gas={cfg.get('gas_cost', 0.0)}, " + f"protocol_fee_split={cfg.get('protocol_fee_split', 0.0)}" + ) + if not has_constant_arc: + print(" constant_arc=disabled") + print("-" * 105) + header = " {:20s}".format("") + for name, _ in methods: + header += f" {name:>14s}" + print(header) + + row = " {:20s}".format("Final value") + for _, r in methods: + row += f" ${float(r['final_value']):>13,.0f}" + print(row) + + print(f" {'HODL value':20s} ${hodl_value:>13,.0f}") + + row = " {:20s}".format("LVR (HODL - final)") + for _, r in methods: + lvr = hodl_value - float(r["final_value"]) + row += f" ${lvr:>13,.0f}" + print(row) + + row = " {:20s}".format("Return") + for _, r in methods: + ret = (float(r["final_value"]) / float(r["value"][0]) - 1) * 100 + row += f" {ret:>13.2f}%" + print(row) + + row = " {:20s}".format("vs HODL") + for _, r in methods: + vs = (float(r["final_value"]) / hodl_value - 1) * 100 + row += f" {vs:>13.2f}%" + print(row) + + if has_constant_arc: + geo_final = float(results["geometric"]["final_value"]) + arc_final = float(results["constant_arc_length"]["final_value"]) + geo_lvr = hodl_value - geo_final + arc_lvr = hodl_value - arc_final + print(f" {'Const Arc - Geo':20s} ${arc_final - geo_final:>13,.0f}") + print(f" {'LVR saved vs Geo':20s} ${geo_lvr - arc_lvr:>13,.0f}") + print("=" * 105) + + + +def plot_comparison(cfg, results, fig_idx): + """Plot comparison diagnostics for one config.""" + tvl_label = format_tvl_millions_label(cfg) + variants = { + "Geometric": (results["geometric"], "C0", "-"), + } + has_constant_arc = "constant_arc_length" in results + if has_constant_arc: + variants["Const arc-len"] = (results["constant_arc_length"], "C2", "--") + + geo = results["geometric"] + geo_prices = np.array(geo["prices"]) + geo_reserves = np.array(geo["reserves"]) + n_steps = len(np.array(geo["value"])) + t_days = np.arange(n_steps) / (60 * 24) + + hodl_traj = (geo_reserves[0] * geo_prices[:n_steps]).sum(axis=-1) + price_ratio_traj = geo_prices[:n_steps, 0] / geo_prices[:n_steps, 1] + + fig, axes = plt.subplots(2, 2, figsize=(14, 10)) + fig.suptitle(f"{cfg['name']} — TVL {tvl_label}", fontsize=13, fontweight="bold") + + ax = axes[0, 0] + plotted_values = [] + for name, (r, color, ls) in variants.items(): + vals = np.array(r["value"]) + plotted_values.append(vals / 1e6) + ax.plot(t_days, vals / 1e6, color=color, ls=ls, label=name, alpha=0.9) + _set_padded_ylim(ax, plotted_values, pad_ratio=0.03) + ax.set_xlabel("Days") + ax.set_ylabel("Pool value ($M)") + ax.set_title("Pool value") + ax.legend(fontsize=8) + ax.grid(True, alpha=0.3) + + ax = axes[0, 1] + for name, (r, color, ls) in variants.items(): + vals = np.array(r["value"]) + lvr = np.array(hodl_traj) - vals + ax.plot(t_days, lvr / 1e3, color=color, ls=ls, label=name, alpha=0.9) + ax.set_xlabel("Days") + ax.set_ylabel("Cumulative LVR ($K)") + ax.set_title("Cumulative LVR (HODL - pool value)") + ax.legend(fontsize=8) + ax.grid(True, alpha=0.3) + + ax = axes[1, 0] + ax.plot(t_days, price_ratio_traj, color="C4", alpha=0.7) + ax.set_xlabel("Days") + ax.set_ylabel(f"{cfg['tokens'][0]}/{cfg['tokens'][1]} price ratio") + ax.set_title("Price path") + ax.grid(True, alpha=0.3) + + ax = axes[1, 1] + for name, (r, color, ls) in variants.items(): + w = np.array(r["weights"]) + n_w = min(len(w), n_steps) + t_w = np.arange(n_w) / (60 * 24) + ax.plot(t_w, w[:n_w, 0], color=color, ls=ls, label=name, alpha=0.9) + ax.set_xlabel("Days") + ax.set_ylabel(f"Weight ({cfg['tokens'][0]})") + ax.set_title("Empirical weight (token 0)") + ax.legend(fontsize=8) + ax.grid(True, alpha=0.3) + + plt.tight_layout() + fname = tvl_artifact_filename("reclamm_thermostat_comparison", cfg, suffix=str(fig_idx)) + plt.savefig(fname, dpi=150) + print(f"Saved {fname}") + plt.close(fig) + + if not has_constant_arc: + print("Skipping constant-arc comparison diagnostics because RUN_CONSTANT_ARC_LENGTH=False.") + return + + geo_values = np.array(geo["value"]) + geo_lvr = np.array(hodl_traj) - geo_values + + fig2, axes2 = plt.subplots(1, 3, figsize=(18, 5)) + fig2.suptitle(f"{cfg['name']} — diagnostics — TVL {tvl_label}", fontsize=13, fontweight="bold") + + ax = axes2[0] + for name, (r, color, ls) in variants.items(): + if name == "Geometric": + continue + vals = np.array(r["value"]) + ax.plot(t_days, (vals - geo_values) / 1e3, color=color, ls=ls, + label=name, alpha=0.9) + ax.axhline(0, color="gray", ls="--", alpha=0.5) + ax.set_xlabel("Days") + ax.set_ylabel("Value difference ($K)") + ax.set_title("Minus Geometric") + ax.legend(fontsize=8) + ax.grid(True, alpha=0.3) + + ax = axes2[1] + mask = np.abs(geo_lvr) > 100 + if mask.any(): + for name, (r, color, ls) in variants.items(): + if name == "Geometric": + continue + vals = np.array(r["value"]) + method_lvr = np.array(hodl_traj) - vals + ratio = np.full_like(geo_lvr, np.nan) + ratio[mask] = method_lvr[mask] / geo_lvr[mask] + ax.plot(t_days, ratio, color=color, ls=ls, alpha=0.7, label=name) + ax.axhline(1.0, color="gray", ls="--", alpha=0.5) + ax.set_ylabel("LVR ratio (method / geometric)") + ax.legend(fontsize=8) + else: + ax.text(0.5, 0.5, "LVR too small to compare", + transform=ax.transAxes, ha="center", va="center") + ax.set_xlabel("Days") + ax.set_title("Relative LVR") + ax.grid(True, alpha=0.3) + + ax = axes2[2] + all_pos = [] + for name, (r, color, ls) in variants.items(): + vals = np.array(r["value"]) + method_lvr = np.array(hodl_traj) - vals + step_lvr = np.diff(method_lvr) + pos = step_lvr[step_lvr > 0] + all_pos.append((name, pos, color)) + has_data = [len(p) > 10 for _, p, _ in all_pos] + if any(has_data): + max_val = max(np.percentile(p, 99) for _, p, _ in all_pos if len(p) > 10) + bins = np.linspace(0, max_val, 50) + for name, pos, color in all_pos: + if len(pos) > 10: + ax.hist(pos, bins=bins, color=color, alpha=0.3, label=name, + density=True) + ax.set_xlabel("Per-step LVR ($)") + ax.set_ylabel("Density") + ax.legend(fontsize=8) + else: + ax.text(0.5, 0.5, "Too few thermostat steps", + transform=ax.transAxes, ha="center", va="center") + ax.set_title("Per-step LVR distribution") + ax.grid(True, alpha=0.3) + + plt.tight_layout() + fname2 = tvl_artifact_filename("reclamm_thermostat_diff", cfg, suffix=str(fig_idx)) + plt.savefig(fname2, dpi=150) + print(f"Saved {fname2}") + plt.close(fig2) + + arc_values = np.array(results["constant_arc_length"]["value"]) + n_eff = min(len(geo_values), len(arc_values)) + t_eff = np.arange(n_eff) / (60 * 24) + efficiency_pct = ( + (arc_values[:n_eff] - geo_values[:n_eff]) + / np.maximum(np.abs(geo_values[:n_eff]), 1e-12) + * 100.0 + ) + + fig3, ax3 = plt.subplots(1, 1, figsize=(10, 4.5)) + fig3.suptitle(f"{cfg['name']} — efficiency — TVL {tvl_label}", fontsize=13, fontweight="bold") + ax3.plot( + t_eff, + efficiency_pct, + color="C2", + linewidth=1.8, + label="(Const Arc - Geo) / Geo", + ) + ax3.axhline(0.0, color="gray", ls="--", alpha=0.6) + _set_padded_ylim(ax3, [efficiency_pct], pad_ratio=0.08) + ax3.set_xlabel("Days") + ax3.set_ylabel("Efficiency vs geometric (%)") + ax3.set_title("Efficiency") + ax3.legend(fontsize=8) + ax3.grid(True, alpha=0.3) + + plt.tight_layout() + fname3 = tvl_artifact_filename("reclamm_thermostat_efficiency", cfg, suffix=str(fig_idx)) + plt.savefig(fname3, dpi=150) + print(f"Saved {fname3}") + plt.close(fig3) + + + +if __name__ == "__main__": + shared_price_data = load_shared_price_data(CONFIGS) + shared_market_linear_noise_data = load_shared_market_linear_noise_data() + + for initial_pool_value in TVL_SWEEP_VALUES: + tvl_configs = configs_for_tvl(CONFIGS, initial_pool_value) + tvl_label = format_tvl_millions_label(tvl_configs[0]) + print(f"\n=== TVL sweep: {tvl_label} ===") + + all_results = [] + for i, cfg in enumerate(tvl_configs): + print(f"\n>>> Running {cfg['name']} at TVL {tvl_label}...") + try: + results = run_comparison( + cfg, + price_data=shared_price_data, + market_linear_noise_data=shared_market_linear_noise_data, + ) + print_comparison(cfg, results) + plot_comparison(cfg, results, i) + all_results.append((cfg, results)) + except Exception as e: + print(f" FAILED: {e}") + import traceback + + traceback.print_exc() + + if len(all_results) > 1: + if RUN_CONSTANT_ARC_LENGTH: + fig, axes = plt.subplots(1, 2, figsize=(16, 5)) + fig.suptitle( + f"Cross-config comparison (normalised) — TVL {tvl_label}", + fontsize=13, + fontweight="bold", + ) + + method_keys = [ + ("geometric", "geo", "-"), + ("constant_arc_length", "arc", "--"), + ] + + for i, (cfg, results) in enumerate(all_results): + geo_v = np.array(results["geometric"]["value"]) + t = np.arange(len(geo_v)) / (60 * 24) + short_name = cfg["name"].split("(")[0].strip() + + for j, (key, suffix, ls) in enumerate(method_keys): + v = np.array(results[key]["value"]) + color_idx = i * len(method_keys) + j + + axes[0].plot( + t, + v / v[0], + ls=ls, + alpha=0.8, + label=f"{short_name} {suffix}", + color=f"C{color_idx % 10}", + ) + + if key != "geometric": + pct_diff = (v - geo_v) / geo_v * 100 + axes[1].plot( + t, + pct_diff, + ls=ls, + alpha=0.8, + label=f"{short_name} {suffix}", + color=f"C{color_idx % 10}", + ) + + axes[0].set_xlabel("Days") + axes[0].set_ylabel("Normalised pool value") + axes[0].set_title("Pool value (V/V0)") + axes[0].legend(fontsize=6, ncol=2) + axes[0].grid(True, alpha=0.3) + + axes[1].set_xlabel("Days") + axes[1].set_ylabel("Efficiency vs geometric (%)") + axes[1].set_title("Efficiency vs Geometric") + axes[1].axhline(0, color="gray", ls="--", alpha=0.5) + axes[1].legend(fontsize=6, ncol=2) + axes[1].grid(True, alpha=0.3) + else: + fig, ax = plt.subplots(1, 1, figsize=(9, 5)) + fig.suptitle( + f"Cross-config comparison (normalised geometric) — TVL {tvl_label}", + fontsize=13, + fontweight="bold", + ) + + for i, (cfg, results) in enumerate(all_results): + geo_v = np.array(results["geometric"]["value"]) + t = np.arange(len(geo_v)) / (60 * 24) + short_name = cfg["name"].split("(")[0].strip() + ax.plot( + t, + geo_v / geo_v[0], + ls="-", + alpha=0.8, + label=f"{short_name} geo", + color=f"C{i % 10}", + ) + + ax.set_xlabel("Days") + ax.set_ylabel("Normalised pool value") + ax.set_title("Geometric pool value (V/V0)") + ax.legend(fontsize=6, ncol=2) + ax.grid(True, alpha=0.3) + + plt.tight_layout() + summary_name = tvl_artifact_filename( + "reclamm_thermostat_summary", + tvl_configs[0], + ) + plt.savefig(summary_name, dpi=150) + print(f"\nSaved {summary_name}") + plt.close(fig) + + launch_final_values = get_launch_final_values( + all_results, + launch_cfg=tvl_configs[0], + price_data=shared_price_data, + market_linear_noise_data=shared_market_linear_noise_data, + ) + shared_sweep_cache = make_sweep_cache( + shared_price_data, + cache_scope_cfg=tvl_configs[1], + market_linear_noise_data=shared_market_linear_noise_data, + ) + + print(f"\nGenerating thermostat heatmaps for TVL {tvl_label}...") + generate_heatmaps( + dict(tvl_configs[1]), + shared_price_data, + launch_final_values=launch_final_values, + cache=shared_sweep_cache, + ) + + generate_arc_speed_efficiency_artifacts( + dict(tvl_configs[1]), + launch_cfg=dict(tvl_configs[0]), + price_data=shared_price_data, + launch_final_values=launch_final_values, + cache=shared_sweep_cache, + ) + generate_three_variable_3d_heatmaps( + dict(tvl_configs[1]), + price_data=shared_price_data, + launch_final_values=launch_final_values, + cache=shared_sweep_cache, + ) + flush_sweep_cache(shared_sweep_cache, force=True) + shared_sweep_cache.clear() + gc.collect() + print(f"Released shared sweep cache for TVL {tvl_label}.") diff --git a/scripts/demo_run_reclamm.py b/scripts/demo_run_reclamm.py new file mode 100644 index 0000000..132f512 --- /dev/null +++ b/scripts/demo_run_reclamm.py @@ -0,0 +1,236 @@ +"""Demo runs for reClAMM pools vs Balancer 50/50 baseline. + +Runs reClAMM pool simulations with parameters pulled from on-chain pools +(AAVE/ETH) and hypothetical configurations, each paired with a Balancer +50/50 constant-weight pool at the same fee level for comparison. + +Usage: + cd + source ~/miniconda3/etc/profile.d/conda.sh && conda activate qsim-reclamm + python scripts/demo_run_reclamm.py +""" + +import jax.numpy as jnp +from quantammsim.runners.jax_runners import do_run_on_historic_data + + +def to_daily_price_shift_base(daily_price_shift_exponent): + """Convert shift rate to daily price shift base (matches Solidity).""" + return 1.0 - daily_price_shift_exponent / 124649.0 + + +def balancer_fingerprint(tokens, start, end, fees): + """Build a Balancer 50/50 fingerprint matching the given reclamm config.""" + return { + "tokens": tokens, + "rule": "balancer", + "startDateString": start, + "endDateString": end, + "initial_pool_value": 1000000.0, + "do_arb": True, + "fees": fees, + "gas_cost": 0.0, + "arb_fees": 0.0, + "chunk_period": 60, + "weight_interpolation_period": 60, + } + + +def reclamm_fingerprint(tokens, start, end, fees, interpolation_method="geometric"): + """Build a reCLAMM fingerprint for a demo scenario.""" + return { + "tokens": tokens, + "rule": "reclamm", + "startDateString": start, + "endDateString": end, + "initial_pool_value": 1000000.0, + "do_arb": True, + "fees": fees, + "gas_cost": 0.0, + "arb_fees": 0.0, + "chunk_period": 60, + "weight_interpolation_period": 60, + "reclamm_interpolation_method": interpolation_method, + "reclamm_arc_length_speed": None, + } + + +def reclamm_params(price_ratio, centeredness_margin, daily_price_shift_exponent): + """Build reCLAMM params from a concise config.""" + return { + "price_ratio": jnp.array(price_ratio), + "centeredness_margin": jnp.array(centeredness_margin), + "daily_price_shift_base": jnp.array( + to_daily_price_shift_base(daily_price_shift_exponent) + ), + } + + +def _apply_active_noise_settings(fp): + """Enable the active AAVE/ETH reCLAMM noise model for demo runs.""" + if fp.get("rule") != "reclamm" or list(fp.get("tokens", [])) != ["AAVE", "ETH"]: + return fp, "disabled" + + from compare_reclamm_thermostats import ( + AAVE_ETH_NOISE_SETTINGS, + resolve_reclamm_noise_settings, + ) + + cfg = { + "tokens": fp["tokens"], + "start": fp["startDateString"], + "end": fp["endDateString"], + "enable_noise_model": True, + "noise_model": AAVE_ETH_NOISE_SETTINGS["noise_model"], + "noise_artifact_dir": AAVE_ETH_NOISE_SETTINGS["noise_artifact_dir"], + "noise_pool_id": AAVE_ETH_NOISE_SETTINGS["noise_pool_id"], + "gas_cost": fp.get("gas_cost", AAVE_ETH_NOISE_SETTINGS["gas_cost"]), + "protocol_fee_split": fp.get( + "protocol_fee_split", + AAVE_ETH_NOISE_SETTINGS["protocol_fee_split"], + ), + "arb_frequency": fp.get("arb_frequency"), + "noise_trader_ratio": fp.get("noise_trader_ratio", 0.0), + "reclamm_noise_params": fp.get("reclamm_noise_params"), + "noise_arrays_path": fp.get("noise_arrays_path"), + } + noise_cfg = resolve_reclamm_noise_settings(cfg) + + updated = dict(fp) + updated["gas_cost"] = cfg["gas_cost"] + updated["protocol_fee_split"] = cfg["protocol_fee_split"] + updated["noise_trader_ratio"] = noise_cfg.get("noise_trader_ratio", 0.0) + for key in ("noise_model", "reclamm_noise_params", "noise_arrays_path", "arb_frequency"): + if noise_cfg.get(key) is not None: + updated[key] = noise_cfg[key] + return updated, noise_cfg["noise_summary"] + + +SCENARIOS = [ + { + "name": "AAVE/ETH launch-style range (25bps, geometric)", + "reclamm": { + "fingerprint": reclamm_fingerprint( + ["AAVE", "ETH"], + "2024-06-01 00:00:00", + "2025-06-01 00:00:00", + 0.0025, + interpolation_method="geometric", + ), + "params": reclamm_params(1.5014, 0.5, 0.1), + }, + }, + { + "name": "AAVE/ETH tighter launch-style range (25bps, geometric)", + "reclamm": { + "fingerprint": reclamm_fingerprint( + ["AAVE", "ETH"], + "2024-06-01 00:00:00", + "2025-06-01 00:00:00", + 0.0025, + interpolation_method="geometric", + ), + "params": reclamm_params(1.15, 0.5, 0.1), + }, + }, + { + "name": "AAVE/ETH tighter launch-style range (25bps, constant arc)", + "reclamm": { + "fingerprint": reclamm_fingerprint( + ["AAVE", "ETH"], + "2024-06-01 00:00:00", + "2025-06-01 00:00:00", + 0.0025, + interpolation_method="constant_arc_length", + ), + "params": reclamm_params(1.15, 0.5, 0.1), + }, + }, + { + "name": "BTC/ETH (10bps)", + "reclamm": { + "fingerprint": reclamm_fingerprint( + ["BTC", "ETH"], + "2024-01-01 00:00:00", + "2025-06-01 00:00:00", + 0.001, + interpolation_method="geometric", + ), + "params": reclamm_params(2.0, 0.3, 0.5), + }, + }, +] + + +def run_scenario(scenario): + """Run a reClAMM config and its Balancer 50/50 baseline, print comparison.""" + rc = scenario["reclamm"] + fp, noise_summary = _apply_active_noise_settings(dict(rc["fingerprint"])) + + # Run reClAMM + reclamm_result = do_run_on_historic_data( + run_fingerprint=fp, params=rc["params"] + ) + + # Run Balancer 50/50 with same tokens, dates, fees + bal_fp = balancer_fingerprint( + fp["tokens"], fp["startDateString"], fp["endDateString"], fp["fees"] + ) + bal_params = { + "initial_weights_logits": jnp.zeros(len(fp["tokens"])), + } + balancer_result = do_run_on_historic_data( + run_fingerprint=bal_fp, params=bal_params + ) + + # HODL value (from reClAMM initial reserves at final prices) + hodl_value = float( + (reclamm_result["reserves"][0] * reclamm_result["prices"][-1]).sum() + ) + + rc_final = float(reclamm_result["final_value"]) + bal_final = float(balancer_result["final_value"]) + rc_init = float(reclamm_result["value"][0]) + bal_init = float(balancer_result["value"][0]) + + print("=" * 80) + print(f" {scenario['name']}") + print( + f" Tokens: {', '.join(fp['tokens'])} | Fees: {fp['fees']} | " + f"Interpolation: {fp.get('reclamm_interpolation_method', 'geometric')}" + ) + print( + f" Noise: {noise_summary} | Gas: {fp.get('gas_cost', 0.0)} | " + f"Protocol fee split: {fp.get('protocol_fee_split', 0.0)}" + ) + print("-" * 80) + print(f" {'':30s} {'reClAMM':>14s} {'Balancer 50/50':>14s}") + print(f" {'Initial value':30s} ${rc_init:>13,.0f} ${bal_init:>13,.0f}") + print(f" {'Final value':30s} ${rc_final:>13,.0f} ${bal_final:>13,.0f}") + print( + f" {'Return':30s} " + f"{(rc_final / rc_init - 1) * 100:>13.2f}% " + f"{(bal_final / bal_init - 1) * 100:>13.2f}%" + ) + print( + f" {'vs HODL':30s} " + f"{(rc_final / hodl_value - 1) * 100:>13.2f}% " + f"{(bal_final / hodl_value - 1) * 100:>13.2f}%" + ) + print( + f" {'reClAMM vs Balancer':30s} " + f"{(rc_final / bal_final - 1) * 100:>13.2f}%" + ) + print("=" * 80) + + +if __name__ == "__main__": + for scenario in SCENARIOS: + print(f"\n>>> {scenario['name']}...") + try: + run_scenario(scenario) + except Exception as e: + print(f" FAILED: {e}") + import traceback + + traceback.print_exc() diff --git a/scripts/evaluate_trials.py b/scripts/evaluate_trials.py new file mode 100644 index 0000000..96bb63e --- /dev/null +++ b/scripts/evaluate_trials.py @@ -0,0 +1,2054 @@ +import os +import argparse +import json +from pathlib import Path +import pandas as pd +import numpy as np +import itertools +import matplotlib.pyplot as plt +import matplotlib as mpl +import seaborn as sns +import seaborn.objects as so +from jax import clear_caches + +import gc +import ast +from datetime import datetime + +from quantammsim.runners.jax_runners import do_run_on_historic_data +from quantammsim.pools.G3M.balancer.balancer import BalancerPool +from quantammsim.runners.jax_runner_utils import NestedHashabledict +from quantammsim.utils.data_processing.historic_data_utils import get_historic_parquet_data + +from quantammsim.core_simulator.param_utils import retrieve_best, calc_lamb, lamb_to_memory_days_clipped, memory_days_to_logit_lamb +import jax.numpy as jnp +from jax.tree_util import tree_map +from jax import config + +config.update("jax_compilation_cache_dir", "/tmp/jax_cache") +config.update("jax_persistent_cache_min_entry_size_bytes", -1) +config.update("jax_persistent_cache_min_compile_time_secs", 0) + +import warnings +warnings.filterwarnings('ignore') + + +"""Configuration for SGD analysis.""" + + +from quantammsim.utils.post_train_analysis import ( + calculate_period_metrics, + calculate_continuous_test_metrics, +) + + +def _json_default(obj): + """Serialise JAX/NumPy arrays and scalars for json.dump.""" + if hasattr(obj, "tolist"): + return obj.tolist() + if isinstance(obj, (np.integer, np.floating, np.bool_)): + return obj.item() + raise TypeError(f"Object of type {type(obj).__name__} is not JSON serialisable") + + +# Keys at the top of optimisation_settings that only matter when +# method='gradient_descent'. For other methods (optuna/cma_es/bfgs) these +# are inherited defaults that were never actually used during training; +# surfacing them in output columns is misleading, so they get NaN'd. +_GRADIENT_DESCENT_ONLY_KEYS = ( + "sample_method", "use_gradient_clipping", "clip_norm", + "optimiser", "base_lr", "batch_size", "use_plateau_decay", + "lr_schedule_type", "warmup_steps", "weight_decay", "min_lr", + "decay_lr_ratio", "decay_lr_plateau", "n_iterations", + "early_stopping", "early_stopping_patience", "use_swa", + "swa_start_frac", "swa_freq", +) + +# For non-gradient-descent methods, the real hyperparams live in a nested +# sub-dict. Mapping method → sub-dict key. +_METHOD_SUBDICT = { + "optuna": "optuna_settings", + "cma_es": "cma_es_settings", + "bfgs": "bfgs_settings", +} + + +def _method_hyperparams(run_fingerprint): + """Return the set of hyperparams that were actually relevant to this run. + + Emitted as a single ``hyperparams`` column (JSON-encoded) so a reader can + see at a glance what each run's optimiser was actually configured with, + regardless of method. For gradient_descent: a curated flat slice of the + top-level optimisation_settings keys. For optuna/cma_es/bfgs: the + matching ``*_settings`` sub-dict (with noisy fields like ``parameter_config`` + and ``storage`` dropped). + """ + opt = run_fingerprint.get("optimisation_settings", {}) + method = opt.get("method") + if method == "gradient_descent": + return {k: opt.get(k) for k in _GRADIENT_DESCENT_ONLY_KEYS if k in opt} + sub_key = _METHOD_SUBDICT.get(method) + if not sub_key: + return {} + sub = dict(opt.get(sub_key, {})) + sub.pop("parameter_config", None) # verbose; not useful as a column + sub.pop("storage", None) + return sub + + +def _gd_only_columns(run_fingerprint): + """Adam-family-only columns for the result dict. Returns every relevant + column populated for gradient_descent runs, or all-None for any other + method (stops inherited defaults from masquerading as actual config). + """ + opt = run_fingerprint["optimisation_settings"] + is_gd = opt.get("method") == "gradient_descent" + fetch = (lambda k: opt.get(k)) if is_gd else (lambda k: None) + return { + "sample_method": fetch("sample_method"), + "use_gradient_clipping": fetch("use_gradient_clipping"), + "clip_norm": fetch("clip_norm"), + "optimiser": fetch("optimiser"), + "learning_rate": fetch("base_lr"), + "batch_size": fetch("batch_size"), + "use_plateau_decay": fetch("use_plateau_decay"), + "lr_schedule_type": fetch("lr_schedule_type"), + "warmup_steps": fetch("warmup_steps"), + } +# from quantammsim.utils.plot_utils import name_to_latex_name, plot_weights + +# Environment setup +ENV_VARS = { + "XLA_FLAGS": "--xla_cpu_multi_thread_eigen=false intra_op_parallelism_threads=1", + "OPENBLAS_NUM_THREADS": "1", + "MKL_NUM_THREADS": "1", + "OMP_NUM_THREAD": "1", +} + +# Directory configuration +BASE_DIR = Path("./") +PLOT_DIR = BASE_DIR / "product_training_runs/plots" +RESULTS_DIR = BASE_DIR / "analysis_results" + +# Plot styling +PLOT_STYLE = { + "COLOR": "#E6CE97", + "BACKGROUND": "#162536", + "SNS_CONFIG": { + "text.color": "#E6CE97", + "axes.labelcolor": "#E6CE97", + "xtick.color": "#E6CE97", + "ytick.color": "#E6CE97", + "figure.facecolor": "#162536", + "axes.facecolor": "#162536", + "text.usetex": True, + "axes.grid": False, + }, +} + + +# Create required directories +for directory in [PLOT_DIR, RESULTS_DIR]: + directory.mkdir(parents=True, exist_ok=True) + +# # Set environment variables +# for key, value in ENV_VARS.items(): +# os.environ[key] = value + + +def make_run_period(name, start_date, end_date, end_test_date): + """Construct a run-period dict used throughout the analysis. + + The ``name`` is used both as a filename-safe identifier AND as the + display label in simplified-CSV column headers (e.g. + ``Returns test (baseline)``). Pick something short and readable. + """ + return { + "name": name, + "start_date": start_date, + "end_date": end_date, + "end_test_date": end_test_date, + } + + +def default_run_period_for_trial(trial): + """Synthesise a run period from a trial's own fingerprint dates. + + Used when the caller passes no explicit run periods — equivalent to the + legacy ``period_from_rf`` behaviour but named ``trained`` for clearer + column headers. + """ + return make_run_period( + name="trained", + start_date=trial.get("start_date") or trial.get("run_fingerprint", {}).get("startDateString"), + end_date=trial.get("end_date") or trial.get("run_fingerprint", {}).get("endDateString"), + end_test_date=trial.get("end_test_date") or trial.get("run_fingerprint", {}).get("endTestDateString"), + ) + + +def parse_run_period_cli(values): + """argparse helper: turn a 4-token ``--run-period NAME START END TEST_END`` + invocation into a run-period dict.""" + if len(values) != 4: + raise argparse.ArgumentTypeError( + "--run-period expects 4 values: NAME START END TEST_END" + ) + name, start, end, test_end = values + return make_run_period(name, start, end, test_end) + +def name_to_latex_name_OG(name): + """Convert run name to LaTeX formatted name. + + Parameters + ---------- + name : str + Name of the run (e.g. 'index_market_cap', 'momentum') + + Returns + ------- + str + LaTeX formatted name + """ + # Special case for index_market_cap since we want to shorten it + if name == "index_market_cap": + return "\\mathrm{Index}" + + # Split name into words + words = name.split("_") + + # Capitalize first letter of each word + words = [word.capitalize() for word in words] + + # Join with escaped spaces and wrap in \mathrm{} + latex_name = "\\ ".join(words) + return f"$\\mathrm{{{latex_name}}}$" + + +def name_to_latex_name(name): + """Convert run name to clean LaTeX formatted name. + + Parameters + ---------- + name : str + Name of the run (e.g. 'Current_index_BTC-ETH_min_0.1_index_memory_day_30.0') + + Returns + ------- + str + LaTeX formatted name + """ + # Handle different strategy types + if name.startswith("Current_index"): + return "$\\mathrm{Current\\ Index\\ Product}$" + elif name.startswith("HODL"): + return "$\\mathrm{HODL}$" + elif name.startswith("QuantAMM_index"): + return "$\\mathrm{QuantAMM\\ Index}$" + elif name.startswith("Optimized_QuantAMM"): + # Extract rule name from pattern "..._rule_RULENAME" + rule = name.split("rule_")[-1] + # Clean up rule name + rule = rule.replace("_", " ").title() + # Special case for specific rules + if rule == "Mean Reversion Channel": + rule = "Mean-Reversion\\ Channel" + elif rule == "Anti Momentum": + rule = "Anti-Momentum" + elif rule == "Power Channel": + rule = "Power-Channel" + elif rule == "Difference Momentum": + rule = "Difference\\ Momentum" + elif rule == "Triple Threat Mean Reversion Channel": + rule = "Triple\\ Threat\\ Mean\\ Reversion\\ Channel" + elif rule == "Bounded Mean Reversion Channel": + rule = "Bounded\\ Mean\\ Reversion\\ Channel" + else: + rule = rule.replace(" ", "\\ ") + return f"$\\mathrm{{QuantAMM\\ {rule}}}$" + + return name_to_latex_name_OG(name) + +def plot_weights(output_dict, run_fingerprint, plot_prefix="weights", verbose=True): + plot_path = Path("./plots/") + plot_path.mkdir(parents=True, exist_ok=True) + plot_prefix = "./plots/" + plot_prefix + + # Calculate weights from reserves and prices + total_value = np.sum(output_dict["reserves"] * output_dict["prices"], axis=1, keepdims=True) + weights = np.array(output_dict["reserves"] * output_dict["prices"] / total_value) + + weights = weights[::1440] + # Create DataFrame for plotting + df_list = [] + tokens = sorted(run_fingerprint["tokens"]) + for i, token in enumerate(tokens): + df_list.extend([ + { + "Time": t, + "Weight": w, + "Token": token + } + for t, w in enumerate(weights[:, i]) + ]) + + df = pd.DataFrame(df_list) + start_date = datetime.strptime(run_fingerprint["startDateString"], "%Y-%m-%d %H:%M:%S") + end_date = datetime.strptime(run_fingerprint["endDateString"], "%Y-%m-%d %H:%M:%S") + + # Create date range for x-axis + date_range = pd.date_range(start=start_date, end=end_date, periods=len(df["Time"].unique())) + df["Time"] = np.tile(date_range, weights.shape[1]) + + # fig, ax = plt.subplots(figsize=(10, 6)) + f = mpl.figure.Figure() + + # Create stacked area plot + pl = ( + so.Plot(df, "Time", "Weight", color="Token") + .add(so.Area(alpha=0.7), so.Stack()) + .limit(y=(0, 1)) + .scale(color=sns.color_palette()) + .label(y="$\\mathrm{Weight}$", x="$\\mathrm{Date}$") + ) + + # Render the plot on our axis + res = pl.on(f).plot() + ax = f.axes[0] + # Select sparse dates for x-axis (4 evenly spaced dates) + unique_dates = df["Time"].unique() + date_indices = np.linspace(0, len(unique_dates)-1, 4, dtype=int) + selected_dates = unique_dates[date_indices] + + # Format dates as LaTeX strings + date_labels = [f"$$\\mathrm{{{pd.Timestamp(date).strftime('%Y-%m-%d')}}}$$" for date in selected_dates] + # Set the ticks and labels + ax.set_xticks(date_indices,date_labels, rotation=45) + # plt.xticks(date_indices, date_labels, rotation=45) + # Adjust layout to prevent label cutoff + plt.tight_layout() + # Save plot + pl.save( + plot_prefix + "_weights_over_time.png", + dpi=700, + bbox_inches="tight" + ) + plt.close() + +def load_reclamm_results(base_dir, load_method="best_train_objective", metric_key="returns_over_hodl"): + """Load reClAMM training results from hash-named JSON files. + + Uses load_manually to read the training trajectory and pick the best + checkpoint. Handles scalar params (n_parameter_sets=1) correctly. + + Returns list of trial dicts compatible with analyze_best_trials. + """ + from quantammsim.core_simulator.param_utils import load_manually + trials_info = [] + base_path = Path(base_dir) + + for file_path in sorted(base_path.glob("run_*.json")): + try: + param_data, context = load_manually( + str(file_path), load_method=load_method, recalc_hess=False, + min_test=-1.0, return_as_iterables=False, + metric_key=metric_key, + ) + except Exception as e: + print(f" Skipping {file_path.name}: {e}") + continue + + # Load run fingerprint from first entry + with open(file_path, encoding="utf-8") as f: + data = json.load(f) + data = json.loads(data) + run_fingerprint = data[0] + + # Skip non-reClAMM runs + if run_fingerprint.get("rule") != "reclamm": + continue + + # Extract train/test metrics + train_obj = param_data.get("train_objective", {}) + test_obj = param_data.get("test_objective", {}) + cont_test = param_data.get("continuous_test_metrics", {}) + + def _extract_metric(obj, key): + """Extract a scalar metric from train/test objective (handles dict, list, scalar).""" + if obj is None: + return float("-inf") + if isinstance(obj, dict): + return float(obj.get(key, obj.get("returns_over_hodl", float("-inf")))) + if isinstance(obj, (list, np.ndarray)): + # Multi-param-set: index by context, or take max + if isinstance(context, (int, np.integer)) and len(obj) > context: + entry = obj[context] + else: + entry = obj[0] if len(obj) > 0 else float("-inf") + if isinstance(entry, dict): + return float(entry.get(key, float("-inf"))) + return float(entry) + return float(obj) + + train_val = _extract_metric(train_obj, metric_key) + test_val = _extract_metric(test_obj, metric_key) + + # Build param dict — handle scalar vs array params + param_fields = {} + for k, v in param_data.items(): + if k in ("train_objective", "test_objective", "objective", + "subsidary_params", "continuous_test_metrics", + "step", "hessian_trace", "local_learning_rate", + "iterations_since_improvement"): + continue + if isinstance(v, dict): + continue + try: + arr = jnp.array(v) + # For scalar reClAMM params with context index + if arr.ndim >= 1 and isinstance(context, (int, np.integer)): + arr = arr[context] + param_fields[k] = arr + except Exception: + continue + + # Zero out initial_weights_logits + n_assets = len(run_fingerprint.get("tokens", [])) + if "initial_weights_logits" in param_fields: + param_fields["initial_weights_logits"] = jnp.zeros_like(param_fields["initial_weights_logits"]) + else: + param_fields["initial_weights_logits"] = jnp.zeros(n_assets) + + # Overall objective (matches load_sgd_results format) + obj_raw = param_data.get("objective", train_val) + if isinstance(obj_raw, (list, np.ndarray)): + obj_val = float(obj_raw[context]) if isinstance(context, (int, np.integer)) else float(obj_raw[0]) + elif isinstance(obj_raw, dict): + obj_val = float(obj_raw.get(metric_key, train_val)) + else: + obj_val = float(obj_raw) if obj_raw is not None else float(train_val) + + trial_info = { + "study_id": file_path.stem, + "trial_number": param_data.get("step", 0), + "train_value": float(train_val), + "test_value": float(test_val), + "objective": obj_val, + "train_objective": train_obj, + "test_objective": test_obj, + "continuous_test_metrics": cont_test, + "rule": run_fingerprint.get("rule"), + "tokens": tuple(sorted(run_fingerprint.get("tokens", []))), + "start_date": run_fingerprint.get("startDateString"), + "end_date": run_fingerprint.get("endDateString"), + "end_test_date": run_fingerprint.get("endTestDateString"), + "return_val": run_fingerprint.get("return_val", "sharpe"), + "chunk_period": run_fingerprint.get("chunk_period"), + "weight_interpolation_period": run_fingerprint.get("weight_interpolation_period"), + "minimum_weight": run_fingerprint.get("minimum_weight"), + "bout_offset": int(run_fingerprint.get("bout_offset", 0)), + "initial_pool_value": float(run_fingerprint.get("initial_pool_value", 0.0)), + "datetime": datetime.now().strftime("%Y-%m-%d %H:%M:%S"), + "params": param_fields, + "run_fingerprint": run_fingerprint, + } + trials_info.append(trial_info) + + return trials_info + + +def load_sgd_results(base_dir, load_method="best_objective", min_test=None): + """Load and parse SGD results files. + + Parameters + ---------- + base_dir : str or Path + Directory containing SGD result JSON files + load_method : str, optional + Method for selecting parameter sets. One of: + 'last', 'best_objective', 'best_train_objective', 'best_test_objective', + 'best_train_min_test_objective' + min_test : float, optional + Minimum test objective threshold for methods that use it + + Returns + ------- + list + List of trial info dictionaries matching Optuna format + """ + trials_info = [] + base_path = Path(base_dir) + + for file_path in base_path.glob("run_*"): + # Use retrieve_best to get cleaned parameters + # try: + params, steps = retrieve_best( + str(file_path), load_method, re_calc_hess=False, min_alt_obj=min_test, return_as_iterables=True + ) + # Load run fingerprint from first entry + with open(file_path, encoding="utf-8") as f: + data = json.load(f) + data = json.loads(data) + run_fingerprint = data[0] + # Format trial info to match Optuna structure + for param, step in zip(params, steps): + trial_info = { + "study_id": file_path.stem, + "trial_number": step, + # Parameters are already indexed by retrieve_best + "train_value": float(param["train_objective"]["returns_over_uniform_hodl"]) if isinstance(param["train_objective"], dict) else float(param["train_objective"]), + "test_value": float(param["test_objective"]["returns_over_uniform_hodl"]) if isinstance(param["test_objective"], dict) else float(param["test_objective"]), + "objective": float(param["objective"]), + # Configuration info from run fingerprint + "rule": run_fingerprint.get("rule"), + "tokens": tuple(sorted(run_fingerprint.get("tokens", []))), + "start_date": run_fingerprint.get("startDateString"), + "end_date": run_fingerprint.get("endDateString"), + "end_test_date": run_fingerprint.get("endTestDateString"), + "return_val": run_fingerprint.get("return_val", "sharpe"), + "chunk_period": run_fingerprint.get("chunk_period"), + "weight_interpolation_period": run_fingerprint.get("weight_interpolation_period"), + "minimum_weight": run_fingerprint.get("minimum_weight"), + "bout_offset": run_fingerprint.get("bout_offset"), + "datetime": datetime.now().strftime("%Y-%m-%d %H:%M:%S"), + "initial_pool_value": float( + run_fingerprint.get("initial_pool_value", 0.0) + ), + "bout_offset": int(run_fingerprint.get("bout_offset", 0)), + } + + # Add parameters with param_ prefix + # retrieve_best has already cleaned metadata and indexed parameters + param_fields = { + k: jnp.array(v) + for k, v in param.items() + if k + not in [ + "train_objective", + "test_objective", + "objective", + "subsidary_params", + "continuous_test_metrics", + ] + and not isinstance(v, dict) + } + # Set logit_delta_lamb to zeros if present + if "logit_delta_lamb" in param_fields: + param_fields["logit_delta_lamb"] = jnp.zeros_like(param_fields["logit_delta_lamb"]) + if "initial_weights_logits" in param_fields: + param_fields["initial_weights_logits"] = jnp.zeros_like(param_fields["initial_weights_logits"]) + trial_info.update({"params": param_fields}) + trial_info.update({"run_fingerprint": run_fingerprint}) + # Validate required fields + required_fields = ["rule", "tokens", "train_value", "test_value"] + if all(trial_info.get(f) is not None for f in required_fields): + trials_info.append(trial_info) + else: + print(f"Skipping {file_path}: missing required fields") + # except Exception as e: + # print(f"Skipping {file_path}: {e}") + # continue + + return trials_info + + +def convert_daily_to_hourly_params(params, run_fingerprint, scale_k_by_frequency=False): + """Convert parameters from daily (1440min) to hourly (60min) updates. + + Parameters + ---------- + params : dict + Original parameter dictionary + run_fingerprint : dict + Run fingerprint containing chunk_period info + scale_k_by_frequency : bool + If True, scale k down by 24 to maintain similar daily aggressiveness. + If False, keep k unchanged (resulting in 24x more aggressive daily behavior). + + Returns + ------- + tuple + (converted_params, converted_fingerprint) + """ + + # Only convert if original chunk_period is 1440 + if run_fingerprint.get("chunk_period", 60) != 1440: + return params, run_fingerprint + + converted_params = tree_map(lambda x: x.copy() if hasattr(x, 'copy') else x, params) + converted_fingerprint = run_fingerprint.copy() + + original_chunk_period = 1440 + new_chunk_period = 60 + + print(f"Converting daily to hourly parameters:") + print(f" Original chunk_period: {original_chunk_period}") + print(f" New chunk_period: {new_chunk_period}") + + # Adjust logit_lamb to preserve memory days + if "logit_lamb" in params: + current_lamb = calc_lamb(params) + current_memory_days = lamb_to_memory_days_clipped( + current_lamb, + original_chunk_period, + max_memory_days=365 + ) + + # Calculate new logit_lamb to achieve same memory days with new chunk_period + new_logit_lamb = memory_days_to_logit_lamb(current_memory_days, new_chunk_period) + converted_params["logit_lamb"] = new_logit_lamb + + print(f" Memory days preserved: {current_memory_days}") + print(f" Original logit_lamb: {params['logit_lamb']}") + print(f" New logit_lamb: {new_logit_lamb}") + else: + print(" No logit_lamb found - memory days adjustment skipped") + + # Handle k parameter scaling + if scale_k_by_frequency: + frequency_ratio = original_chunk_period / new_chunk_period # 24 + + if "log_k" in params: + converted_params["log_k"] = params["log_k"] - jnp.log2(frequency_ratio) + print(f" Scaling log_k: {params['log_k']} -> {converted_params['log_k']} (reduction by log2({frequency_ratio}))") + elif "k" in params: + converted_params["k"] = params["k"] / frequency_ratio + print(f" Scaling k: {params['k']} -> {converted_params['k']} (divided by {frequency_ratio})") + else: + print(" No k or log_k found - k scaling skipped") + + print(" K scaling: ON (maintaining daily aggressiveness)") + else: + print(" K scaling: OFF (24x more aggressive daily behavior)") + + # Update chunk_period in fingerprint + converted_fingerprint["chunk_period"] = new_chunk_period + + # Scale other timing-related parameters + frequency_ratio = original_chunk_period / new_chunk_period # 24 + + # Scale weight_interpolation_period if present + if "weight_interpolation_period" in converted_fingerprint: + original_wip = converted_fingerprint["weight_interpolation_period"] + converted_fingerprint["weight_interpolation_period"] = int(original_wip / frequency_ratio) + print(f" Scaling weight_interpolation_period: {original_wip} -> {converted_fingerprint['weight_interpolation_period']}") + + # Scale bout_length if present (this controls the simulation length) + if "bout_length" in converted_fingerprint: + original_bout_length = converted_fingerprint["bout_length"] + converted_fingerprint["bout_length"] = int(original_bout_length / frequency_ratio) + print(f" Scaling bout_length: {original_bout_length} -> {converted_fingerprint['bout_length']}") + + print(f" Conversion complete!") + + return converted_params, converted_fingerprint + +def analyze_specific_trials( + base_dir, + trials_to_analyze, + return_val="sharpe", + load_method="best_objective", + use_pareto_frontier=True, + do_plots=False, + keep_top=1.0, + tokens='', + convert_daily_to_hourly=False, + scale_k_by_frequency=False, + force_reload=False, + run_periods=None, +): + """Analyze specific trials from run files. + + Parameters + ---------- + base_dir : str + Directory containing the run files + trials_to_analyze : list[dict] + List of dicts containing study_id and trial_number to analyze + Example: [{"study_id": "run_XXXXX", "trial_number": 42}, ...] + return_val : str, optional + Metric to optimize for, by default "sharpe" + load_method : str, optional + Method for selecting parameter sets, by default "best_objective" + use_pareto_frontier : bool, optional + Whether to use pareto frontier analysis, by default True + do_plots : bool, optional + Whether to generate plots, by default False + keep_top : float, optional + Fraction of top trials to keep, by default 1.0 + tokens : str, optional + Token filter string, by default '' + convert_daily_to_hourly : bool, optional + Whether to convert daily (1440min) runs to hourly (60min), by default False + scale_k_by_frequency : bool, optional + If convert_daily_to_hourly=True, whether to scale k by frequency ratio, by default True + run_periods : list of dict, optional + Run periods to evaluate on. Each dict: ``{name, start_date, end_date, + end_test_date}`` (see :func:`make_run_period`). If None/empty, each + trial is evaluated over its own trained window as a period named + ``"trained"``. + """ + base_path = Path(base_dir) + all_trials = [] + + filename = "sgd_analysis_result_" + load_method + "_keeptop_" + str(keep_top) + "_" + "_".join(tokens) + ".csv" + + if (Path(base_dir) / filename).exists() and not force_reload: + df = pd.read_csv(Path(base_dir) / filename) + # Convert string columns to dicts + if "params" in df.columns: + df["params"] = df["params"].str.replace(", dtype=float64", "") + df["params"] = df["params"].str.replace(", dtype=float64", "") + df["params"] = df["params"].str.replace(",\s*dtype=float64", "") + df["params"] = df["params"].str.replace("Array(", "") + df["params"] = df["params"].str.replace(")", "") + # Drop rows where params contains 'nan' + df = df[~df["params"].astype(str).str.contains('nan')] + df["params"] = df["params"].apply(lambda x: ast.literal_eval(x) if isinstance(x, str) else x) + df["params"] = df["params"].apply(lambda x: {k: jnp.array(v) for k, v in x.items()}) + if "tokens" in df.columns: + df["tokens"] = df["tokens"].apply(lambda x: ast.literal_eval(x) if isinstance(x, str) else x) + if "run_fingerprint" in df.columns: + df["run_fingerprint"] = df["run_fingerprint"].apply(lambda x: ast.literal_eval(x) if isinstance(x, str) else x) + else: + # Load all study results — try standard loader first, fall back to reClAMM loader + try: + trials_data = load_sgd_results(base_path, load_method=load_method, min_test=0.0) + except (TypeError, IndexError): + print(" Standard loader failed, trying reClAMM loader...") + trials_data = load_reclamm_results(base_path, load_method=load_method) + if tokens != '': + trials_data = [trial for trial in trials_data if set(trial["tokens"]) == set(tokens)] + # Group by tokens and keep only top fraction within each group + if keep_top != 1.0: + # Convert trials_data to DataFrame for easier filtering + df_temp = pd.DataFrame(trials_data) + + # Determine which column to sort by based on load_method + if load_method == "best_objective": + sort_col = "objective" + elif load_method == "best_train_objective": + sort_col = "train_value" + elif load_method == "best_test_objective": + sort_col = "test_value" + else: + sort_col = "objective" # Default to objective + + # Group by tokens and keep top fraction within each group + filtered_trials = [] + groupby_cols = ["tokens", "return_val"] if load_method == "best_objective" else ["tokens"] + for _, group in df_temp.groupby(groupby_cols): + # Sort descending since we want highest values + sorted_group = group.sort_values(sort_col, ascending=False) + # Calculate number of rows to keep + # Determine number of rows to keep based on keep_top value + if keep_top > 1: + # If keep_top is > 1, use it as absolute number of rows + n_keep = int(keep_top) + else: + # If keep_top <= 1, interpret as fraction + n_keep = max(1, int(len(sorted_group) * keep_top)) + # Keep top rows + filtered_trials.extend(sorted_group.head(n_keep).to_dict('records')) + + trials_data = filtered_trials + if trials_data is not None: + all_trials.extend(trials_data) + # Convert to DataFrame + df = pd.DataFrame(all_trials) + # Sort DataFrame by tokens column + df = df.sort_values( + by=["bout_offset", "chunk_period", "tokens", "return_val", "start_date", "end_date", "minimum_weight", "rule"], + ascending=[False, True, True, True, True, False, True, True], + na_position="last", + ) + # Reorder columns to put specified columns at the front + column_order = [ + "chunk_period", + "bout_offset", + "tokens", + "rule", + "return_val", + "minimum_weight", + "start_date", + "end_date", + "study_id", + "trial_number", + "objective", + "test_value", + "train_value" + ] + # Add remaining columns after the specified ones + column_order.extend([col for col in df.columns if col not in column_order]) + df = df[column_order] + # Save DataFrame to disk + + df.to_csv(Path(base_dir) / filename, index=False) + + if len(trials_to_analyze) > 0: + # Filter DataFrame to only include specified trials + mask = pd.DataFrame(False, index=df.index, columns=["match"]) + for trial_info in trials_to_analyze: + mask["match"] |= (df["study_id"] == trial_info["study_id"]) & ( + df["trial_number"] == trial_info["trial_number"] + ) + filtered_df = df[mask["match"]] + if filtered_df.empty: + raise ValueError("No matching trials found") + else: + filtered_df = df + print(filtered_df) + # Create visualizations and analyze results + simplified_df = analyze_best_trials( + filtered_df, + base_dir=base_dir, + output_string=load_method + "__keeptop_" + str(keep_top) + "_" + str(len(trials_to_analyze)) + "_" + "_".join(tokens), + do_plots=do_plots, + convert_daily_to_hourly=convert_daily_to_hourly, + scale_k_by_frequency=scale_k_by_frequency, + run_periods=run_periods, + ) + + df = df.merge( + simplified_df, + left_on='study_id', + right_on='Run ID', + how='left' + ) + + # Then apply the same sorting + # Convert return_val to categorical with custom order before sorting + df['return_val'] = pd.Categorical( + df['return_val'], + categories=['sharpe', 'calmar', 'ulcer', 'sterling', 'daily_log_sharpe'], + ordered=True + ) + + + # Effective period names for column-building: explicit run_periods if + # provided, otherwise the auto-generated default used inside + # analyze_best_trials (a single "trained" period derived from each + # trial's own fingerprint dates). + effective_period_names = [rp["name"] for rp in (run_periods or [])] or ["trained"] + primary_period = effective_period_names[0] + + df = df.sort_values( + by=[ + "tokens", + f"Returns over HODL train ({primary_period})", + "start_date", + "end_date", + "rule", + ], + ascending=[True, False, True, False, True], + na_position="last", + ) + + def _per_period_cols(template, periods): + """Expand a one-line template ``'Metric {side} ({period})'`` into a + flat list over the cartesian product of sides × periods. + """ + return [template.format(period=p) for p in periods] + + # Per-period metric column names — train and test, for each period. + train_metric_templates = [ + "Returns over HODL train ({period})", + "Returns train ({period})", + "Sharpe train ({period})", + "Annualized Ulcer Index [M] train ({period})", + "Annualized Calmer Ratio [M] train ({period})", + ] + test_metric_templates = [ + "Returns over HODL test ({period})", + "Returns test ({period})", + "Sharpe test ({period})", + "Annualized Ulcer Index [M] test ({period})", + "Annualized Calmer Ratio [M] test ({period})", + ] + + per_period_train_cols = [ + c for t in train_metric_templates for c in _per_period_cols(t, effective_period_names) + ] + per_period_test_cols = [ + c for t in test_metric_templates for c in _per_period_cols(t, effective_period_names) + ] + + CONFIG_TAIL = [ + "bout_offset", "chunk_period", + "noise_trader_ratio", "analysis_pool_value", "analysis_fees", "analysis_gas_cost", + "optimisation_method", "hyperparams", + "sample_method", "use_gradient_clipping", "clip_norm", + "optimiser", "learning_rate", "batch_size", "use_plateau_decay", + "lr_schedule_type", "warmup_steps", "ste_max_change", "ste_min_max_weight", + ] + + columns_to_keep = ( + [ + "tokens", "rule", "return_val", "minimum_weight", "start_date", "end_date", + "study_id", "trial_number", "objective", "test_value", "train_value", + "Comments", "Params", + ] + + per_period_train_cols + + per_period_test_cols + + CONFIG_TAIL + ) + + df_filtered = df[columns_to_keep] + + # Reorder: headline columns first (study ID + tokens + rule), then the + # most-useful per-period headline metrics grouped across all periods + # (Returns-over-HODL train/test, Sharpe train/test), then objective + # context, then detailed train + detailed test, then params/metadata. + headline_train = [f"Returns over HODL train ({p})" for p in effective_period_names] + headline_test = [f"Returns over HODL test ({p})" for p in effective_period_names] + sharpe_train = [f"Sharpe train ({p})" for p in effective_period_names] + sharpe_test = [f"Sharpe test ({p})" for p in effective_period_names] + + detailed_train = [ + c for t in ( + "Returns train ({period})", + "Annualized Ulcer Index [M] train ({period})", + "Annualized Calmer Ratio [M] train ({period})", + ) for c in _per_period_cols(t, effective_period_names) + ] + detailed_test = [ + c for t in ( + "Returns test ({period})", + "Annualized Ulcer Index [M] test ({period})", + "Annualized Calmer Ratio [M] test ({period})", + ) for c in _per_period_cols(t, effective_period_names) + ] + + column_order = ( + ["study_id", "trial_number", "tokens", "rule"] + + headline_train + headline_test + sharpe_train + sharpe_test + + ["return_val", "objective", "train_value"] + + detailed_train + + ["test_value"] + + detailed_test + + ["bout_offset", "chunk_period", "Params", "minimum_weight", + "start_date", "end_date", "Comments"] + + CONFIG_TAIL + ) + column_order = list(dict.fromkeys(column_order)) + df_filtered = df_filtered[column_order] + df_filtered = df_filtered.loc[:, ~df_filtered.columns.duplicated(keep='first')] + # Add empty rows after each change in minimum_weight + empty_row = pd.Series([None] * len(df_filtered.columns), index=df_filtered.columns) + + # Create new dataframe with empty rows inserted + df_with_breaks = pd.DataFrame() + + # Iterate through rows and add empty row after token changes + for i in range(len(df_filtered)): + df_with_breaks = pd.concat([df_with_breaks, df_filtered.iloc[[i]]]) + if i < len(df_filtered)-1 and df_filtered.iloc[i]['tokens'] != df_filtered.iloc[i+1]['tokens']: + df_with_breaks = pd.concat([df_with_breaks, empty_row.to_frame().T]) + + filename = f"filled_analysis_{load_method}_keeptop_{keep_top}_{len(trials_to_analyze)}_{'_'.join(tokens)}.csv" + df_with_breaks.to_csv(Path(base_dir) / filename, index=False) + + +def plot_values(results, tokens, suffix="", plot_start_end=None, plot_white_line=False, white_line_date=None, plot_dir=None, initial_hodl_weights="same"): + """Plot value over time for all runs on the same graph.""" + suffix = suffix + "_balancer" + if plot_dir is None: + plot_dir = "./plots/" + plot_path = Path(plot_dir) + plot_path.mkdir(parents=True, exist_ok=True) + + plt.figure(figsize=(12, 6)) + + # Create DataFrame for plotting + df_list = [] + start_date = datetime.strptime( + next(iter(results.values()))["fingerprint"]["startDateString"], + "%Y-%m-%d %H:%M:%S", + ) + if len(results) == 1: + # For single run case, add a HODL baseline + if initial_hodl_weights == "same": + hodl_reserves = next(iter(results.values()))["reserves"][0] + elif initial_hodl_weights == "uniform": + initial_hodl_value = next(iter(results.values()))["value"][0].sum() + initial_hodl_prices = next(iter(results.values()))["prices"][0] + n_assets = len(initial_hodl_prices) + hodl_reserves = (1.0/n_assets) * initial_hodl_value / initial_hodl_prices + prices = next(iter(results.values()))["prices"] + # hodl_values = np.sum(hodl_reserves * prices, axis=1) + # results["HODL"] = { + # "value": hodl_values, + # "fingerprint": next(iter(results.values()))["fingerprint"].copy() + # } + # results["HODL"]["fingerprint"]["rule"] = "HODL" + hodl_fingerprint = next(iter(results.values()))["fingerprint"].copy() + hodl_fingerprint["rule"] = "HODL" + hodl_fingerprint["bout_length"] = len(prices) + 1 + hodl_fingerprint["n_assets"] = len(tokens) + hodl_fingerprint["initial_pool_value"] = float(next( + iter(results.values()) + )["value"][0].sum()) + # print("initial_balancer_pool_value", hodl_fingerprint["initial_pool_value"]) + # hodl_params = {"initial_weights": jnp.ones(len(tokens)) / len(tokens)} + # balancer_pool = BalancerPool() + # hodl_reserves = balancer_pool.calculate_reserves_zero_fees( + # params=hodl_params, + # run_fingerprint=NestedHashabledict(hodl_fingerprint), + # prices=prices, + # start_index=jnp.array([0,0]), + # ) + hodl_values = (hodl_reserves * prices).sum(axis=1) + results["HODL"] = { + "value": hodl_values, + "fingerprint": hodl_fingerprint.copy() + } + for run_name, result in results.items(): + values = result["value"] + dates = pd.date_range( + start=start_date, + end=datetime.strptime( + result["fingerprint"]["endDateString"], "%Y-%m-%d %H:%M:%S" + ), + freq="1min", + )[:-1] + + df_list.extend( + [ + { + "Date": date, + "Value": float(value), # Ensure values are float + "Strategy": str(run_name), # Ensure strategy names are strings + } + for date, value in zip( + dates[::1440], values[::1440] + ) # Take every 1440th value (daily) + ] + ) + + # Create DataFrame with explicit types + df = pd.DataFrame(df_list) + df["Date"] = pd.to_datetime(df["Date"]) + df["Value"] = df["Value"].astype(float) / 1e6 + df["Strategy"] = df["Strategy"].astype("category") + df["Strategy"] = df["Strategy"].apply(name_to_latex_name) + # Create plot + + if plot_start_end is not None: + # Filter df to plot_start_end range if provided + if isinstance(plot_start_end, tuple) and len(plot_start_end) == 2: + start_str, end_str = plot_start_end + start_date = pd.to_datetime(start_str) + end_date = pd.to_datetime(end_str) + df = df[(df['Date'] >= start_date) & (df['Date'] <= end_date)] + + # sns.set_style("darkgrid") + # Get default color palette + default_palette = sns.color_palette() + + # Modify palette to use COLOR for 4th item if needed + strategies = df["Strategy"].unique() + n_strategies = len(strategies) + palette = list(default_palette[:3]) + if n_strategies > 3: + palette = [COLOR] + palette + if n_strategies > 4: + palette.extend(default_palette[4:n_strategies]) + print("n_strategies", n_strategies) + # Sort strategies to put QuantAMM rules (except QuantAMM Index) first + # df['Strategy_order'] = df['Strategy'].apply(lambda x: + # 0 if (x.startswith('QuantAMM') and x != 'QuantAMM Index') + # else 1) + # df = df.sort_values('Strategy_order') + # Define explicit order for strategies + strategy_order = [ + "$\\mathrm{QuantAMM\\ Mean-Reversion\\ Channel}$", + "$\\mathrm{QuantAMM\\ Triple\\ Threat\\ Mean\\ Reversion\\ Channel}$", + "$\\mathrm{QuantAMM\\ Momentum}$", + "$\\mathrm{QuantAMM\\ Anti-Momentum}$", + "$\\mathrm{QuantAMM\\ Power-Channel}$", + "$\\mathrm{QuantAMM\\ Difference\\ Momentum}$", + "$\\mathrm{QuantAMM\\ Index}$", + "$\\mathrm{HODL}$", + "$\\mathrm{Balancer}$", + "$\\mathrm{Traditional\\ DEX}$", + "$\\mathrm{Current\\ Index\\ Product}$", + ] + # Filter strategy_order to only include strategies that exist in the data + strategy_order = [s for s in strategy_order if s in df["Strategy"].unique()] + sns.lineplot(data=df, x="Date", y="Value", hue="Strategy", linewidth=2, palette=palette, hue_order=strategy_order) + + # plt.title("$\\mathrm{Value\\ Over\\ Time}$", pad=20) + plt.xlabel("$\\mathrm{Date}$") + plt.ylabel("$\\mathrm{Value\\ (\\$M\\ USD)}$") + + # Format x-axis + ax = plt.gca() + ax.spines["top"].set_visible(False) + ax.spines["right"].set_visible(False) + ax.spines["left"].set_color(PLOT_STYLE["COLOR"]) + ax.spines["bottom"].set_color(PLOT_STYLE["COLOR"]) + ax.yaxis.set_ticks_position("left") + ax.xaxis.set_ticks_position("bottom") + + if plot_white_line: + + # Add vertical line and shading for train/test split + plt.axvline(x=pd.Timestamp(white_line_date), color='white', linestyle='--', alpha=0.5) + + # Add "Train" and "Test" labels in LaTeX near the red line + plt.text(pd.Timestamp(white_line_date) - pd.Timedelta(days=15), plt.ylim()[1]*0.95, + "$\\mathrm{Train}$", + horizontalalignment='right', + verticalalignment='top', + fontsize=10, + color='white', + alpha=0.6) + plt.text(pd.Timestamp(white_line_date) + pd.Timedelta(days=15), plt.ylim()[1]*0.95, + "$\\mathrm{Test}$", + horizontalalignment='left', + verticalalignment='top', + fontsize=10, + color='white', + alpha=0.6) + + # Remove legend title + # Reorder legend to put QuantAMM rules (except QuantAMM Index) last + handles, labels = ax.get_legend_handles_labels() + # Find indices of QuantAMM rules that aren't QuantAMM Index + quantamm_indices = [i for i, label in enumerate(labels) + if label.startswith("QuantAMM") and label != "QuantAMM Index"] + if quantamm_indices: + # Move each QuantAMM rule to the end, preserving their relative order + for idx in sorted(quantamm_indices, reverse=True): + handles.append(handles.pop(idx)) + labels.append(labels.pop(idx)) + # Replace legend + ax.legend(handles, labels) + + ax.get_legend().set_title(None) + # Save plot + plt.tight_layout() + plt.savefig( + plot_path / (f"pool_values_comparison_{len(results)}_{'-'.join(tokens)}_{suffix}.png"), + dpi=700, + bbox_inches="tight", + ) + return_over_hodl = df[df["Strategy"]!= name_to_latex_name("HODL")]["Value"].iloc[-1] * 1e6 / hodl_values[-1] - 1.0 + plt.close('all') + del df + del hodl_values + del hodl_reserves + del df_list + gc.collect() + gc.collect() + gc.collect() + if initial_hodl_weights == "uniform": + return return_over_hodl + +def analyze_best_trials( + df, + base_dir="./sgd_studies", + output_string = None, + do_plots=False, + convert_daily_to_hourly=False, + scale_k_by_frequency=False, + run_periods=None, +): + """Analyze best trials with different parameters and generate detailed performance metrics. + + Parameters + ---------- + df : pd.DataFrame + DataFrame containing all trial results + base_dir : str, default="./sgd_studies" + Directory containing Optuna study results + output_string : str, default=None + String to append to output file name + do_plots : bool, default=False + If True, generate plots for each trial + run_periods : list of dict, optional + Run periods to evaluate each trial over (format: see ``make_run_period``). + If None/empty, a single "trained" period is auto-generated per trial from + that trial's own fingerprint dates — matches the old period_from_rf + behaviour but without the duplication that happened when user periods + had the same dates. + """ + output_path = Path(base_dir) + output_path.mkdir(parents=True, exist_ok=True) + + # Parameters to test + initial_pool_values = [20000.0, 50000.0,100000.0, 1000000.0, 10000000.0] + fee_values = [0.0, 0.001, 0.003, 0.01] # 0 to 1% + gas_costs = [0.0,0.005,0.5] # USD per trade + noise_trader_ratios = [0.0, 0.5, 1.0] # 0 to 1 + # initial_pool_values = [1000000.0] + fee_values = [0.0, 0.003] + gas_costs = [0.0, 1.0, 2.0] + noise_trader_ratios = [0.0, 0.5] + + initial_pool_values = [100000.0] + noise_trader_ratios = [0.0] # 0 to 1 + # initial_pool_values = [1000000.0] + fee_values = [0.0] + gas_costs = [0.0] + noise_trader_ratios = [0.0] + + # Run periods: use what the caller supplied, or fall back to a single + # per-trial "trained" period synthesised below from each trial's own dates. + run_periods = list(run_periods) if run_periods else [] + + + # Print number of rows for each rule + rule_counts = df['rule'].value_counts() + print("\nNumber of rows per rule:") + for rule, count in rule_counts.items(): + print(f"{rule}: {count}") + + grouped = df.groupby(["tokens", "initial_pool_value", "end_date", "bout_offset", "chunk_period", "rule"]) + + results = [] + + base_path = Path(base_dir) + + # Create results directory within base_dir + results_dir = base_path / "analysis_results" + results_dir.mkdir(parents=True, exist_ok=True) + + plot_dir = base_path / "plots" + plot_dir.mkdir(parents=True, exist_ok=True) + # Extract study name from base_dir + study_name = base_path.name + + # Construct results filename + results_filename = ( + f"analysis_results" + f"_{study_name}" + f"_{output_string}" + f".json" + ) + + # results_filename = "analysis_unified_results_all_runs_uptodate_pareto_20250405_033520_best.json" + + results_path = results_dir / results_filename + # Check if analysis file already exists + if do_plots or os.path.exists(results_path)==False: + price_data = get_historic_parquet_data(df.iloc[0]["run_fingerprint"]["tokens"], cols=["close"]) + for _, trial in df.iterrows(): + params = trial["params"] + run_fingerprint = trial["run_fingerprint"] + run_fingerprint["startTestDateString"] = run_fingerprint["endDateString"] + # params["raw_exponents"] = jnp.array( + # [-0.48537339, 1.44890609, -0.03770628, 0.28857155] + # ) + # params["raw_exponents"] = jnp.array( + # [-0.5935307, 1.49911745, -0.03770628, 0.28857155] + # ) + for pool_val, fee, gas, noise_trader_ratio in itertools.product(initial_pool_values, fee_values, gas_costs, noise_trader_ratios): + run_period_results = [] + if run_periods: + local_run_periods = [rp.copy() for rp in run_periods] + else: + # No explicit periods: just evaluate on the trial's own training window. + local_run_periods = [default_run_period_for_trial(trial)] + if 'ARB' in run_fingerprint["tokens"]: + for i in range(len(local_run_periods)): + local_run_periods[i]["start_date"] = local_run_periods[0]["start_date"] + for run_period in local_run_periods: + # run_period["end_test_date"] = "2025-07-12 00:00:00" + run_period = run_period.copy() + prices = {"train_prices": None, "test_prices": None, "continuous_test_prices": None} + print("tokens", run_fingerprint["tokens"]) + print("study_id", trial["study_id"]) + print("trial_number", trial["trial_number"]) + print("rule", trial["rule"]) + print("pool_val", pool_val) + print("fee", fee) + print("gas", gas) + print("noise_trader_ratio", noise_trader_ratio) + + # Update run_fingerprint with new values + local_fingerprint = run_fingerprint.copy() + local_fingerprint["startDateString"] = run_period["start_date"] + if "OM" in local_fingerprint["tokens"]: + local_fingerprint["startDateString"] = trial["start_date"] + run_period["start_date"] = trial["start_date"] + if run_period["name"].startswith("March"): + run_period["name"] = "May" + run_period["name"][5:] + if "PEPE" in local_fingerprint["tokens"]: + local_fingerprint["startDateString"] = trial["start_date"] + run_period["start_date"] = trial["start_date"] + if run_period["name"].startswith("March"): + run_period["name"] = "May2023" + run_period["name"][9:] + run_period["end_test_date"] = "2025-03-16 00:00:00" + print("run_period", run_period["name"]) + local_fingerprint["endDateString"] = run_period["end_date"] + local_fingerprint["startTestDateString"] = run_period["end_date"] + local_fingerprint["endTestDateString"] = run_period["end_test_date"] + local_fingerprint["initial_pool_value"] = pool_val + local_fingerprint["fees"] = fee + local_fingerprint["gas_cost"] = gas + local_fingerprint["noise_trader_ratio"] = noise_trader_ratio + + end_date = run_period["end_date"] + tokens = trial["tokens"] + bout_offset = trial["bout_offset"] + chunk_period = trial["chunk_period"] + init_pool_val = trial["initial_pool_value"] + rule = trial["rule"] + # Broadcast scalar params to n_assets shape. Optuna runs + # don't save initial_weights_logits, so synthesise a zero + # template from tokens. + n_assets = len(tokens) + template = params.get( + "initial_weights_logits", jnp.zeros(n_assets) + ) + if "log_k" in params: + params["log_k"] = params["log_k"] * jnp.ones_like(template) + if "k" in params: + params["k"] = params["k"] * jnp.ones_like(template) + if "logit_lamb" in params: + params["logit_lamb"] = params["logit_lamb"] * jnp.ones_like(template) + if "logit_delta_lamb" in params: + params["logit_delta_lamb"] = params["logit_delta_lamb"] * jnp.ones_like(template) + if "initial_weights_logits" not in params: + params["initial_weights_logits"] = jnp.zeros(n_assets) + + # Apply daily-to-hourly conversion if requested + if convert_daily_to_hourly: + params, local_fingerprint = convert_daily_to_hourly_params( + params, local_fingerprint, scale_k_by_frequency + ) + # Update chunk_period for plot prefix after conversion + chunk_period = local_fingerprint["chunk_period"] + + base_plot_prefix = f"{tokens}_{rule}_{pool_val}_{fee}_{gas}_{noise_trader_ratio}_chunk_{chunk_period}_param_{trial['study_id'][4:][:5]}_trial_{trial['trial_number']}_runperiod_{run_period['name']}" + + # Add suffix to indicate conversion type if applied + if convert_daily_to_hourly: + conversion_suffix = "_hourly_k_scaled" if scale_k_by_frequency else "_hourly_k_unscaled" + base_plot_prefix += conversion_suffix + + run_filename = f"analysis_{base_plot_prefix}.json" + if os.path.exists(output_path / run_filename) and do_plots==False: + print(f"Skipping {run_filename} because it already exists") + with open(output_path / run_filename, "r") as f: + result = json.load(f) + results.append(result) + continue + # else: + # print(f"Skipping {run_filename} because we dont have time") + # continue + + train_dict, test_dict = do_run_on_historic_data( + local_fingerprint, + params=params, + do_test_period=True, + verbose=False, + price_data=price_data, + ) + + # Run continuous test simulation + continuous_fingerprint = local_fingerprint.copy() + continuous_fingerprint["endDateString"] = local_fingerprint["endTestDateString"] + shifted_end = pd.to_datetime(continuous_fingerprint["endDateString"]) - pd.Timedelta(minutes=1) + continuous_fingerprint["endDateString"] = str(shifted_end.strftime("%Y-%m-%d %H:%M:%S")) + continuous_dict = do_run_on_historic_data( + continuous_fingerprint, + params=params, + do_test_period=False, + verbose=False, + price_data=price_data, + ) + # Store prices and remove from result dicts + if prices["train_prices"] is None: + prices["train_prices"] = train_dict.pop("prices") + prices["test_prices"] = test_dict.pop("prices") + prices["continuous_test_prices"] = continuous_dict.pop("prices") + else: + train_dict.pop("prices", None) + test_dict.pop("prices", None) + continuous_dict.pop("prices", None) + + # (train_dict["reserves"][0]*train_dict["prices"][-1]).sum() + if do_plots: + print("top of plots") + train_dict["prices"] = prices["train_prices"] + plot_weights( + train_dict, + local_fingerprint, + plot_prefix=f"train_{base_plot_prefix}", + # plot_dir=plot_dir, + ) + # do_weight_change_as_rebalances_plots( + # train_dict, + # run_fingerprint, + # plot_prefix=f"train_{tokens}_{rule}_{pool_val}_{fee}_{gas}_end_{end_date}_param_{i}", + # ) + train_dict["fingerprint"] = local_fingerprint + plot_values( + {"Optimized_QuantAMM_pool_"+'-'.join(tokens)+"_rule_"+rule: train_dict}, + tokens, + suffix=f"train_{base_plot_prefix}", + plot_dir=plot_dir, + ) + del train_dict["prices"] + test_dict["prices"] = prices["test_prices"] + test_dict["fingerprint"] = local_fingerprint.copy() + test_dict["fingerprint"]["startDateString"] = local_fingerprint["startTestDateString"] + test_dict["fingerprint"]["endDateString"] = local_fingerprint["endTestDateString"] + plot_values( + { + "Optimized_QuantAMM_pool_" + + "-".join(tokens) + + "_rule_" + + rule: test_dict + }, + tokens, + suffix=f"test_{base_plot_prefix}", + plot_dir=plot_dir, + ) + del test_dict["prices"] + continuous_dict["prices"] = prices["continuous_test_prices"] + continuous_dict["fingerprint"] = continuous_fingerprint + plot_values( + { + "Optimized_QuantAMM_pool_" + + "-".join(tokens) + + "_rule_" + + rule: continuous_dict + }, + tokens, + suffix=f"continuous_run_{base_plot_prefix}", + plot_white_line=True, + white_line_date=test_dict["fingerprint"]["startDateString"], + plot_dir=plot_dir, + ) + print("run_period: ", run_period) + print("white line date", test_dict["fingerprint"]["startDateString"]) + del continuous_dict["prices"] + continuous_test_results = { + "value": continuous_dict["value"][ + len(train_dict["value"]) : len(train_dict["value"]) + + len(test_dict["value"]) + ], + "reserves": continuous_dict["reserves"][ + len(train_dict["reserves"]) : len( + train_dict["reserves"] + ) + + len(test_dict["reserves"]) + ], + "prices": prices["continuous_test_prices"][ + len(train_dict["value"]) : len( + train_dict["value"] + ) + + len(test_dict["value"]) + ], + "fingerprint": test_dict["fingerprint"], + } + def calculate_period_returns(results_dict, period_length=30 * 24 * 60): + """Calculate returns over different periods and strategies. + + Args: + results_dict: Dictionary containing 'value', 'prices', 'reserves' arrays + period_length: Period length in minutes (default 30 days) + + Returns: + List of dictionaries containing return metrics for each period + """ + # Convert inputs to numpy arrays + test_values = np.array(results_dict["value"]) + test_prices = np.array(results_dict["prices"]) + test_reserves = np.array(results_dict["reserves"]) + + period_returns = [] + n_tokens = len(test_prices[0]) + uniform_weights = np.ones(n_tokens) / n_tokens + + # Iterate over each period + for period_start in range(0, len(test_values), period_length): + period_end = min(period_start + period_length, len(test_values)) + + # print("period_start", period_start) + # print("period_end", period_end) + # print("period length", period_end - period_start) + + # Get strategy values for this period + start_value = test_values[period_start] + end_value = test_values[period_end - 1] + strategy_return = end_value / start_value - 1 + + # Calculate initial reserves and prices + initial_period_reserves = test_reserves[period_start] + start_prices = test_prices[period_start] + end_prices = test_prices[period_end - 1] + + # Calculate weights and price ratios + initial_period_weights = (initial_period_reserves * start_prices) / np.sum((initial_period_reserves * start_prices)) + price_ratios = end_prices / start_prices + + # Calculate end values for different strategies + end_balance_pool_value = start_value * np.prod((price_ratios) ** initial_period_weights) + end_uniform_balance_pool_value = start_value * np.prod((price_ratios) ** uniform_weights) + hodl_end_value = np.sum(initial_period_reserves * end_prices) + + # Calculate returns + hodl_return = hodl_end_value / start_value - 1 + returns_over_hodl = (end_value) / (hodl_end_value) - 1 + returns_over_balancer = (end_value) / (end_balance_pool_value) - 1 + returns_over_uniform_balancer = (end_value) / (end_uniform_balance_pool_value) - 1 + + # Set returns_over_balancer to 0 if within 10^-12 of 0 + if abs(returns_over_balancer) < 1e-12: + returns_over_balancer = 0.0 + period_returns.append({ + "period": period_start // period_length, + "strategy_return": strategy_return, + "returns_over_hodl": returns_over_hodl, + "hodl_return": hodl_return, + "returns_over_balancer": returns_over_balancer, + "returns_over_uniform_balancer": returns_over_uniform_balancer, + "annualised_strategy_return": (1 + strategy_return) ** (365 * 24 * 60 / period_length) - 1, + "annualised_returns_over_hodl": (1 + returns_over_hodl) ** (365 * 24 * 60 / period_length) - 1, + "annualised_returns_over_balancer": (1 + returns_over_balancer) ** (365 * 24 * 60 / period_length) - 1, + "annualised_returns_over_uniform_balancer": (1 + returns_over_uniform_balancer) ** (365 * 24 * 60 / period_length) - 1, + }) + + print("\nPeriod Annualised Returns Over HODL:") + for period_data in period_returns: + print(f"Period {period_data['period']}: {100.0 * period_data['annualised_returns_over_hodl']}") + print("--------------------------------") + print("\nPeriod Annualised Returns:") + for period_data in period_returns: + print( + f"Period {period_data['period']}: {100.0 * period_data['annualised_strategy_return']}" + ) + print("--------------------------------") + print("\nPeriod Annualised Returns Over Balancer:") + for period_data in period_returns: + print(f"Period {period_data['period']}: {100.0 * period_data['annualised_returns_over_balancer']}") + print("\nPeriod Annualised Returns Over Uniform Balancer:") + for period_data in period_returns: + print(f"Period {period_data['period']}: {100.0 * period_data['annualised_returns_over_uniform_balancer']}") + + return period_returns + + # Calculate monthly returns over HODL (using fixed 30-day periods) + print("="*100) + print("calculating monthly returns test") + calculate_period_returns(continuous_test_results.copy(), period_length=30 * 24 * 60) + # print("calculating weekly returns test") + # calculate_period_returns(continuous_test_results.copy(), period_length=7 * 24 * 60) + print("calculating monthly returns train") + train_dict["prices"] = prices["train_prices"] + calculate_period_returns(train_dict.copy(), period_length=30 * 24 * 60) + # print("calculating weekly returns train") + # calculate_period_returns(train_dict.copy(), period_length=7 * 24 * 60) + print("="*100) + # if "2024" in run_period["name"]: + # raise Exception("Stop here") + # Print period returns + + plot_values( + { + "Optimized_QuantAMM_pool_" + + "-".join(tokens) + + "_rule_" + + rule: continuous_test_results + }, + tokens, + suffix=f"continuous_test_{base_plot_prefix}", + plot_dir=plot_dir, + ) + plot_weights( + continuous_test_results, + continuous_test_results["fingerprint"], + plot_prefix=f"continuous_test_{base_plot_prefix}", + # plot_dir=plot_dir, + ) + uniform_hodl_return = plot_values( + { + "Optimized_QuantAMM_pool_" + + "-".join(tokens) + + "_rule_" + + rule: continuous_test_results + }, + tokens, + suffix=f"continuous_test_uniformhodl_{base_plot_prefix}", + initial_hodl_weights="uniform", + plot_dir=plot_dir, + ) + del continuous_test_results + else: + # Calculate uniform HODL return for continuous test period + initial_value = continuous_dict["value"][ + len(train_dict["value"]) : len(train_dict["value"]) + + len(test_dict["value"]) + ][0] + initial_prices = prices["continuous_test_prices"][ + len(train_dict["value"]) : len( + train_dict["value"] + ) + + len(test_dict["value"]) + ][0] + final_prices = prices["continuous_test_prices"][::1440][-1] + # Calculate uniform weights + n_tokens = len(tokens) + uniform_weights = np.ones(n_tokens) / n_tokens + uniform_reserves = initial_value * uniform_weights / initial_prices + # Calculate return from uniform HODL strategy + price_ratios = final_prices / initial_prices + uniform_hodl_return = continuous_dict["value"][::1440][-1] / np.sum(uniform_reserves * final_prices) - 1 + # Helper: calculate_period_metrics returns JAX scalars (0-d + # ArrayImpl) for most keys plus a 1-d daily_returns. Cast + # scalars to Python floats here so downstream pandas sorts + # / categoricals don't choke on unhashable arrays. + def _py_scalarise(d): + out = {} + for k, v in d.items(): + if hasattr(v, "ndim") and v.ndim == 0: + out[k] = float(v) + else: + out[k] = v + return out + + # Calculate metrics for training period + train_metrics = _py_scalarise(calculate_period_metrics(train_dict, prices["train_prices"])) + # Prefix each key with "train_" + train_metrics = {f"train_{k}": v for k, v in train_metrics.items()} + # Calculate metrics for test period + test_metrics = _py_scalarise(calculate_period_metrics(test_dict, prices["test_prices"])) + # Prefix each key with "test_" + test_metrics = {f"test_{k}": v for k, v in test_metrics.items()} + + # Calculate metrics for continuous test period + continuous_test_metrics = _py_scalarise(calculate_continuous_test_metrics( + continuous_dict, + len(train_dict["value"]), + len(test_dict["value"]), + prices["continuous_test_prices"] + )) + continuous_test_metrics = {f"continuous_test_{k}": v for k, v in continuous_test_metrics.items()} + continuous_metrics = _py_scalarise(calculate_period_metrics(continuous_dict, prices["continuous_test_prices"])) + + continuous_metrics = {f"continuous_{k}": v for k, v in continuous_metrics.items()} + # Store results + result = { + "study_id": trial["study_id"], + "trial_number": trial["trial_number"], + "tokens": tokens, + "rule": rule, + "run_period": run_period["name"], + "original_pool_value": init_pool_val, + "original_fees": run_fingerprint["fees"], + "original_gas_cost": run_fingerprint["gas_cost"], + "original_objective": trial["objective"], + "original_return_val": trial["return_val"], + "original_train_value": trial["train_value"], + "original_test_value": trial["test_value"], + "noise_trader_ratio": noise_trader_ratio, + "analysis_pool_value": pool_val, + "analysis_fees": fee, + "analysis_gas_cost": gas, + "bout_offset": int(trial["bout_offset"]), + "chunk_period": int(trial["chunk_period"]), + "run_period_start_date": run_period["start_date"], + "start_date": trial.get("start_date"), + "end_date": trial.get("end_date"), + "end_test_date": trial.get("end_test_date"), + "run_period_start_date": run_period["start_date"], + "run_period_end_date": run_period["end_date"], + "run_period_end_test_date": run_period["end_test_date"], + "weight_interpolation_period": trial.get( + "weight_interpolation_period" + ), + "minimum_weight": trial.get("minimum_weight"), + **train_metrics, + **test_metrics, + **continuous_test_metrics, + **continuous_metrics, + "uniform_continuous_test_hodl_return": float( + uniform_hodl_return + ), + "params": tree_map( + lambda x: (x.tolist() if isinstance(x, jnp.ndarray) else x), + params, + ), + "optimisation_method": run_fingerprint["optimisation_settings"]["method"], + # One column with ALL relevant hyperparams for this + # run's method (adam-flat-keys OR optuna/cma_es/bfgs + # sub-dict). JSON-encoded so it round-trips through CSV. + "hyperparams": json.dumps( + _method_hyperparams(run_fingerprint), + default=_json_default, + ), + # Adam-family-only columns. NaN for non-gradient_descent + # runs so we don't show inherited defaults the run never + # actually used. + **_gd_only_columns(run_fingerprint), + "ste_max_change": run_fingerprint.get( + "ste_max_change" + ), + "ste_min_max_weight": run_fingerprint.get( + "ste_min_max_weight" + ), + } + print(result) + results.append(result) + # Save individual result files + for filename in [ + f"analysis_{base_plot_prefix}.json", + # f"analysis_{base_plot_prefix}_studyid_{trial['study_id']}_trialno_{trial['trial_number']}_poolval_{pool_val:.0f}_fees_{fee:.4f}_gas_{gas:.1f}_noise_{noise_trader_ratio:.2f}_bout_{trial['bout_offset']}_chunk_{trial['chunk_period']}_end_{trial['end_date']}_param_{0}_usePF_{use_pareto_frontier}.json", + ]: + with open(output_path / filename, "w") as f: + json.dump(result, f, indent=2, default=_json_default) + gc.collect() + + del result + del train_dict + del test_dict + del continuous_dict + del prices + clear_caches() + gc.collect() + gc.collect() + # Save all results to a single file + with open(results_path, "w") as f: + json.dump(results, f, indent=2, default=_json_default) + else: + with open(results_path, "r") as f: + results = json.load(f) + print(f"Loading existing results from {results_path}") + # After collecting all results, but before saving: + results_df = pd.DataFrame(results) + + # Identify non-varying columns (keys that uniquely identify a trial) + id_columns = [ + "study_id", + "trial_number", + "tokens", + "rule", + "start_date", + "end_date", + "original_pool_value", + "original_fees", + "original_gas_cost", + "original_objective", + "original_return_val", + "original_train_value", + "original_test_value", + "noise_trader_ratio", + "analysis_pool_value", + "analysis_fees", + "analysis_gas_cost", + "bout_offset", + "chunk_period", + "weight_interpolation_period", + "minimum_weight", + "optimisation_method", + "hyperparams", + "sample_method", + "use_gradient_clipping", + "clip_norm", + "optimiser", + "learning_rate", + "batch_size", + "use_plateau_decay", + "lr_schedule_type", + "warmup_steps", + "ste_max_change", + "ste_min_max_weight", + ] + + # Create DataFrame with explicit types + df = pd.DataFrame(results) + df["study_id"] = df["study_id"].astype(str) + df["trial_number"] = df["trial_number"].astype(float) + df["Strategy"] = ( + df["Strategy"].astype("category") if "Strategy" in df.columns else None + ) + # Get all columns that need period prefix + metric_columns = [ + col + for col in results_df.columns + if col not in id_columns and col != "run_period" and col != "params" and col != "noise_trader_ratio" and col != "analysis_pool_value" and col != "analysis_fees" and col != "analysis_gas_cost" + ] + + # Create a mapping of (study_id, trial_number) to params + params_dict = ( + results_df.groupby(["study_id", "trial_number"])["params"].first().to_dict() + ) + + if 'tokens' in results_df.columns: + results_df['tokens'] = results_df['tokens'].apply(tuple) + # Set index to id columns for proper pivoting + results_df = results_df.set_index(id_columns) + + # Pivot all period-specific columns at once + period_data = [] + for col in metric_columns: + pivoted = results_df.pivot(columns="run_period", values=col) + cols = pivoted.columns + cols = [col[:-4] if col.endswith("test") else col for col in cols] + pivoted.columns = [f"{period}_{col}" for period in cols] + period_data.append(pivoted) + + # Combine all pivoted data + transformed_df = pd.concat(period_data, axis=1) + + # Reset index to get id columns back + transformed_df = transformed_df.reset_index() + + # Add params column + transformed_df["params"] = transformed_df.apply( + lambda row: params_dict[(row["study_id"], row["trial_number"])], axis=1 + ) + + # Convert dates to datetime for sorting + transformed_df["start_date"] = pd.to_datetime(transformed_df["start_date"]) + transformed_df["end_date"] = pd.to_datetime(transformed_df["end_date"]) + + # Sort the DataFrame + transformed_df = transformed_df.sort_values( + by=[ + "bout_offset", + "chunk_period", + "tokens", + "original_return_val", + "start_date", + "end_date", + "minimum_weight", + "rule" + ], + ascending=[ + False, + True, + True, # tokens ascending + True, # return_val ascending + True, # start date ascending + False, # end date descending + True, # minimum weight ascending + True # rule alphabetically + ] + ) + + # Reset index after sorting + transformed_df = transformed_df.reset_index(drop=True) + + # Add conversion suffix to output filenames if conversion was applied + conversion_suffix = "" + if convert_daily_to_hourly: + conversion_suffix = "_hourly_k_scaled" if scale_k_by_frequency else "_hourly_k_unscaled" + + # Save transformed DataFrame + filename = f"analysis_results_{Path(base_dir).name}_{output_string}{conversion_suffix}_{datetime.now().strftime('%Y%m%d_%H%M%S')}.csv" + transformed_df.to_csv(Path(base_dir) / filename, index=False) + + # Create simplified DataFrame matching BTF format + simplified_df = pd.DataFrame() + + # Map basic columns + simplified_df["Tokens"] = transformed_df["tokens"] + simplified_df["Rule"] = transformed_df["rule"].str.replace("_", " ").str.title() + simplified_df["Objective"] = transformed_df["original_return_val"] + simplified_df["Min weight"] = transformed_df["minimum_weight"].apply(lambda x: f"{float(x)*100:.0f}%") + simplified_df["Start date"] = pd.to_datetime(transformed_df["start_date"]).dt.strftime("%Y") + simplified_df["start test"] = pd.to_datetime(transformed_df["end_date"]).dt.strftime("%Y-%m-%d %H:%M:%S") + simplified_df["Run ID"] = transformed_df["study_id"] + simplified_df["Iteration no"] = transformed_df["trial_number"] + simplified_df["Best obj"] = transformed_df["original_objective"] + simplified_df["Test Returns Over HODL"] = transformed_df["original_test_value"] + simplified_df["Train Returns Over HODL"] = transformed_df["original_train_value"] + + # Add conversion metadata to Comments + conversion_info = "" + if convert_daily_to_hourly: + k_scaling = "with k scaling" if scale_k_by_frequency else "without k scaling" + conversion_info = f"Converted daily->hourly ({k_scaling})" + simplified_df["Comments"] = conversion_info + + simplified_df["Params"] = transformed_df["params"] + + # Add metrics for each period + periods = np.unique(results_df["run_period"]).tolist() + for period in periods: + if period.endswith("test"): + period = period[:-4] + period_prefix = f"{period}_" + # simplified_df[f"Returns over HODL test ({period})"] = transformed_df[f"{period_prefix}continuous_test_returns_over_hodl"] + simplified_df[f"Returns test ({period})"] = transformed_df[f"{period_prefix}continuous_test_return"] + simplified_df[f"Sharpe test ({period})"] = transformed_df[f"{period_prefix}continuous_test_sharpe"] + simplified_df[f"Annualized Ulcer Index [M] test ({period})"] = transformed_df[f"{period_prefix}continuous_test_ulcer"] + simplified_df[f"Annualized Calmer Ratio [M] test ({period})"] = transformed_df[f"{period_prefix}continuous_test_calmar"] + simplified_df[f"Returns over HODL train ({period})"] = transformed_df[ + f"{period_prefix}train_returns_over_hodl" + ] + simplified_df[f"Returns train ({period})"] = transformed_df[ + f"{period_prefix}train_return" + ] + simplified_df[f"Sharpe train ({period})"] = transformed_df[ + f"{period_prefix}train_sharpe" + ] + simplified_df[f"Annualized Ulcer Index [M] train ({period})"] = transformed_df[ + f"{period_prefix}continuous_test_ulcer" + ] + simplified_df[f"Annualized Calmer Ratio [M] train ({period})"] = transformed_df[ + f"{period_prefix}train_calmar" + ] + simplified_df[f"Returns over HODL test ({period})"] = transformed_df[ + f"{period_prefix}uniform_continuous_test_hodl_return" + ] + simplified_df["noise_trader_ratio"] = transformed_df["noise_trader_ratio"] + simplified_df["analysis_pool_value"] = transformed_df["analysis_pool_value"] + simplified_df["analysis_fees"] = transformed_df["analysis_fees"] + simplified_df["analysis_gas_cost"] = transformed_df["analysis_gas_cost"] + simplified_df["optimisation_method"] = transformed_df["optimisation_method"] + simplified_df["hyperparams"] = transformed_df["hyperparams"] + simplified_df["sample_method"] = transformed_df["sample_method"] + simplified_df["use_gradient_clipping"] = transformed_df["use_gradient_clipping"] + simplified_df["clip_norm"] = transformed_df["clip_norm"] + simplified_df["optimiser"] = transformed_df["optimiser"] + simplified_df["learning_rate"] = transformed_df["learning_rate"] + simplified_df["batch_size"] = transformed_df["batch_size"] + simplified_df["use_plateau_decay"] = transformed_df["use_plateau_decay"] + simplified_df["lr_schedule_type"] = transformed_df["lr_schedule_type"] + simplified_df["warmup_steps"] = transformed_df["warmup_steps"] + simplified_df["ste_max_change"] = transformed_df["ste_max_change"] + simplified_df["ste_min_max_weight"] = transformed_df["ste_min_max_weight"] + + # Save simplified CSV + simplified_filename = f"simplified_analysis_{Path(base_dir).name}_{output_string}{conversion_suffix}_{datetime.now().strftime('%Y%m%d_%H%M%S')}.csv" + simplified_df.to_csv(Path(base_dir) / simplified_filename, index=False, float_format='%.10f') + + # Continue with existing JSON save for compatibility + results_path = ( + Path(base_dir) + / "analysis_results" + / f"analysis_unified_results_{Path(base_dir).name}_{output_string}{conversion_suffix}_{datetime.now().strftime('%Y%m%d_%H%M%S')}.json" + ) + with open(results_path, "w") as f: + json.dump(results, f, indent=2, default=_json_default) + + return simplified_df + + +def load_run_fingerprints( + base_dir, + trials_to_analyze, + load_method="best_objective", + tokens='' +): + """Analyze specific trials from run files. + + Parameters + ---------- + base_dir : str + Directory containing the run files + trials_to_analyze : list[dict] + List of dicts containing study_id and trial_number to analyze + Example: [{"study_id": "run_XXXXX", "trial_number": 42}, ...] + return_val : str, optional + Metric to optimize for, by default "sharpe" + load_method : str, optional + Method for selecting parameter sets, by default "best_objective" + use_pareto_frontier : bool, optional + Whether to use pareto frontier analysis, by default True + """ + base_path = Path(base_dir) + all_trials = [] + + keep_top = 1.0 + + filename = "sgd_analysis_result_" + load_method + "_keeptop_" + str(keep_top) + "_" + "_".join(tokens) + ".csv" + + if (Path(base_dir) / filename).exists(): + df = pd.read_csv(Path(base_dir) / filename) + # Convert string columns to dicts + if "params" in df.columns: + df["params"] = df["params"].str.replace(", dtype=float64", "") + df["params"] = df["params"].str.replace(", dtype=float64", "") + df["params"] = df["params"].str.replace(",\s*dtype=float64", "") + df["params"] = df["params"].str.replace("Array(", "") + df["params"] = df["params"].str.replace(")", "") + # Drop rows where params contains 'nan' + df = df[~df["params"].astype(str).str.contains('nan')] + df["params"] = df["params"].apply(lambda x: ast.literal_eval(x) if isinstance(x, str) else x) + df["params"] = df["params"].apply(lambda x: {k: jnp.array(v) for k, v in x.items()}) + if "tokens" in df.columns: + df["tokens"] = df["tokens"].apply(lambda x: ast.literal_eval(x) if isinstance(x, str) else x) + if "run_fingerprint" in df.columns: + df["run_fingerprint"] = df["run_fingerprint"].apply(lambda x: ast.literal_eval(x) if isinstance(x, str) else x) + + if len(trials_to_analyze) > 0: + # Filter DataFrame to only include specified trials + mask = pd.DataFrame(False, index=df.index, columns=["match"]) + for trial_info in trials_to_analyze: + mask["match"] |= (df["study_id"] == trial_info["study_id"]) & ( + df["trial_number"] == trial_info["trial_number"] + ) + filtered_df = df[mask["match"]] + if filtered_df.empty: + raise ValueError("No matching trials found") + else: + filtered_df = df + # Write run fingerprints as JSONL file + output_path = Path(base_dir) / f"run_fingerprints_{datetime.now().strftime('%Y%m%d_%H%M%S')}.jsonl" + with open(output_path, "w", encoding="utf-8") as f: + for _, row in filtered_df.iterrows(): + json.dump(row["run_fingerprint"], f) + f.write("\n") + + return all_trials + +def build_cli_parser() -> argparse.ArgumentParser: + p = argparse.ArgumentParser( + description="Post-train analysis: walk a directory of run_*.json training " + "results, run the trained params on one or more evaluation " + "windows, and emit comparison CSVs + plots.", + formatter_class=argparse.ArgumentDefaultsHelpFormatter, + ) + p.add_argument("--base-dir", required=True, + help="Directory containing run_*.json training result files.") + p.add_argument("--tokens", nargs="+", default=["ETH", "USDC"], + help="Token filter — only trials matching this exact token set are kept.") + p.add_argument("--load-method", default="best_train_min_test_objective", + choices=["last", "best_objective", "best_train_objective", + "best_test_objective", "best_train_min_test_objective"]) + p.add_argument("--force-reload", action="store_true", + help="Ignore any cached sgd_analysis_result_*.csv and re-scan all run files.") + p.add_argument("--no-plots", action="store_true", + help="Skip plot generation (analysis CSVs still written).") + p.add_argument("--daily-to-hourly", action="store_true", + help="Convert daily (chunk_period=1440) runs to hourly (60min) for analysis.") + p.add_argument("--scale-k", action="store_true", + help="If --daily-to-hourly, also scale k by the frequency ratio.") + p.add_argument("--run-period", action="append", nargs=4, + metavar=("NAME", "START", "END", "TEST_END"), + default=[], + help="Evaluation window. NAME is the short label that appears " + "in CSV column headers (e.g. 'baseline'). Dates as " + "'YYYY-MM-DD HH:MM:SS'. Repeatable.") + return p + + +if __name__ == "__main__": + args = build_cli_parser().parse_args() + + run_periods = [parse_run_period_cli(tokens) for tokens in args.run_period] or None + + print("Starting analysis...") + print("=" * 100) + print(f"base_dir: {args.base_dir}") + print(f"tokens: {args.tokens}") + print(f"periods: {[rp['name'] for rp in run_periods] if run_periods else ['trained (from each trial)']}") + print("=" * 100) + + analyze_specific_trials( + base_dir=args.base_dir, + trials_to_analyze=[], + load_method=args.load_method, + tokens=args.tokens, + force_reload=args.force_reload, + do_plots=not args.no_plots, + convert_daily_to_hourly=args.daily_to_hourly, + scale_k_by_frequency=args.scale_k, + run_periods=run_periods, + ) diff --git a/scripts/fetch_competitor_tvl.py b/scripts/fetch_competitor_tvl.py new file mode 100644 index 0000000..5f00393 --- /dev/null +++ b/scripts/fetch_competitor_tvl.py @@ -0,0 +1,513 @@ +"""Fetch competitor TVL for each token pair from DeFi Llama. + +For each of our 36 calibration pools, finds all other DEX pools trading +the same token pair, sums their daily TVL, and saves as a time series. + +K_i(t) = sum_{j != i} TVL_j(t) for all pools trading pool i's pair + +Output: results/competitor_tvl/competitor_tvl.npz + - pool_ids: list of our pool IDs + - dates: array of dates (days since epoch or ISO strings) + - competitor_tvl: (n_dates, n_pools) array of daily competitor TVL in USD + +Usage: + python scripts/fetch_competitor_tvl.py + python scripts/fetch_competitor_tvl.py --cache-dir results/competitor_tvl +""" + +import argparse +import json +import os +import pickle +import sys +import time +from collections import defaultdict + +import numpy as np +import pandas as pd + +CACHE_DIR = os.path.join( + os.path.dirname(os.path.dirname(__file__)), + "results", "token_factored_calibration", "_cache", +) + +# Map Balancer token names to DeFi Llama symbol conventions +# DeFi Llama symbols are typically uppercase, no dots, no "W" prefix inconsistencies +SYMBOL_MAP = { + "WETH": "WETH", + "WBTC": "WBTC", + "wstETH": "WSTETH", + "waEthLidowstETH": "WSTETH", + "waEthLidoWETH": "WETH", + "waGnowstETH": "WSTETH", + "waGnoGNO": "GNO", + "waBasUSDC": "USDC", + "waBasWETH": "WETH", + "sDAI": "DAI", + "scUSD": "USDC", + "stS": "S", + "JitoSOL": "JITOSOL", + # Common DeFi Llama variants + "USDC.e": "USDC", + "USDT.e": "USDT", + "WETH.e": "WETH", + "WBTC.e": "WBTC", +} + + +def _normalize_symbol(token): + """Normalize Balancer token name to DeFi Llama symbol.""" + return SYMBOL_MAP.get(token, token.upper()) + + +def _fetch_json(url, retries=5, delay=3.0): + """Fetch JSON from URL with exponential backoff.""" + import urllib.request + for attempt in range(retries): + try: + req = urllib.request.Request(url) + req.add_header("User-Agent", "quantammsim/1.0") + with urllib.request.urlopen(req, timeout=30) as resp: + return json.loads(resp.read().decode()) + except Exception as e: + wait = delay * (2 ** attempt) # 3, 6, 12, 24, 48s + if attempt < retries - 1: + print(f" Retry {attempt+1} (wait {wait:.0f}s): {e}") + time.sleep(wait) + else: + raise + + +def fetch_all_pools(local_path=None): + """Load DeFi Llama yield pools from local file or API.""" + if local_path and os.path.exists(local_path): + print(f"Loading DeFi Llama pools from {local_path}...") + with open(local_path) as f: + data = json.load(f) + else: + print("Fetching DeFi Llama pool list from API...") + data = _fetch_json("https://yields.llama.fi/pools") + pools = data.get("data", []) if isinstance(data, dict) else data + print(f" {len(pools)} pools") + return pools + + +# Map Balancer chain names to DeFi Llama chain names +CHAIN_MAP = { + "mainnet": "Ethereum", + "ethereum": "Ethereum", + "arbitrum": "Arbitrum", + "polygon": "Polygon", + "gnosis": "Gnosis", + "base": "Base", + "optimism": "Optimism", + "avalanche": "Avalanche", + "sonic": "Sonic", +} + + +def match_pools(our_pools, llama_pools): + """Match our token pairs to DeFi Llama pools. + + Returns dict: pool_id -> {pair_key, chain, llama_pools_same_chain, + llama_pools_all_chains, tokens} + """ + # Normalize DeFi Llama token symbols to match our convention + LLAMA_NORMALIZE = { + "ETH": "WETH", + "BTC": "WBTC", + "STETH": "WSTETH", + } + + # Index llama pools by (pair, chain) + pair_chain_to_llama = defaultdict(list) + pair_to_llama = defaultdict(list) + for p in llama_pools: + symbol = p.get("symbol", "") + if not symbol or "-" not in symbol: + continue + tokens = symbol.split("-") + if len(tokens) != 2: + continue + normed = [LLAMA_NORMALIZE.get(t.upper(), t.upper()) for t in tokens] + pair_key = tuple(sorted(normed)) + chain = p.get("chain", "") + pair_to_llama[pair_key].append(p) + pair_chain_to_llama[(pair_key, chain)].append(p) + + from quantammsim.calibration.pool_data import _parse_tokens + + matches = {} + for pid, entry in our_pools.items(): + toks = _parse_tokens(entry["tokens"]) + tok_a = _normalize_symbol(toks[0]) + tok_b = _normalize_symbol(toks[1]) if len(toks) > 1 else tok_a + pair_key = tuple(sorted([tok_a, tok_b])) + + our_chain = entry.get("chain", "mainnet") + llama_chain = CHAIN_MAP.get(our_chain.lower(), our_chain) + + matches[pid] = { + "pair_key": pair_key, + "chain": llama_chain, + "llama_pools_same_chain": pair_chain_to_llama.get( + (pair_key, llama_chain), []), + "llama_pools_all_chains": pair_to_llama.get(pair_key, []), + "tokens": (tok_a, tok_b), + } + + return matches, pair_chain_to_llama + + +def fetch_pool_history(pool_id): + """Fetch daily TVL history for a DeFi Llama pool.""" + url = f"https://yields.llama.fi/chart/{pool_id}" + data = _fetch_json(url) + points = data.get("data", []) + if not points: + return None + + dates = [] + tvls = [] + for p in points: + ts = p.get("timestamp", "")[:10] + tvl = p.get("tvlUsd", 0) + if ts and tvl is not None: + dates.append(pd.Timestamp(ts)) + tvls.append(float(tvl)) + + return pd.Series(tvls, index=pd.DatetimeIndex(dates), name="tvl") + + +def main(): + parser = argparse.ArgumentParser(description=__doc__, + formatter_class=argparse.RawDescriptionHelpFormatter) + parser.add_argument("--cache-dir", default="results/competitor_tvl") + parser.add_argument("--max-pools-per-pair", type=int, default=30, + help="Max DeFi Llama pools to fetch per pair") + parser.add_argument("--min-tvl", type=float, default=10000, + help="Skip pools with current TVL below this") + parser.add_argument("--pools-json", default=None, + help="Local DeFi Llama pools.json (skip API fetch)") + args = parser.parse_args() + + os.makedirs(args.cache_dir, exist_ok=True) + + # Load our pools + with open(os.path.join(CACHE_DIR, "stage1.pkl"), "rb") as f: + stage1 = pickle.load(f) + matched_clean = stage1["matched_clean"] + pool_ids = sorted(matched_clean.keys()) + print(f"Our pools: {len(pool_ids)}") + + # Fetch DeFi Llama pools + llama_pools = fetch_all_pools(args.pools_json) + + # Match + matches, pair_chain_to_llama = match_pools(matched_clean, llama_pools) + + # Summary + print(f"\nPair matching (same-chain / all-chains):") + seen = set() + for pid in pool_ids: + m = matches[pid] + pair = m["pair_key"] + chain = m["chain"] + key = (pair, chain) + if key in seen: + continue + seen.add(key) + toks = matched_clean[pid].get("tokens", "?") + n_same = len(m["llama_pools_same_chain"]) + n_all = len(m["llama_pools_all_chains"]) + tvl_same = sum(p.get("tvlUsd", 0) for p in m["llama_pools_same_chain"]) + tvl_all = sum(p.get("tvlUsd", 0) for p in m["llama_pools_all_chains"]) + print(f" {'/'.join(pair):>20s} {chain:>10s}" + f" same={n_same:>3d} (${tvl_same/1e6:>7.1f}M)" + f" all={n_all:>3d} (${tvl_all/1e6:>7.1f}M)" + f" [{toks}]") + + # Flag zero-match pairs — likely symbol mapping issues + zero_matches = [] + for pid in pool_ids: + m = matches[pid] + if len(m["llama_pools_same_chain"]) == 0 and len(m["llama_pools_all_chains"]) == 0: + toks = matched_clean[pid].get("tokens", "?") + zero_matches.append((pid[:16], toks, m["pair_key"], m["chain"])) + if zero_matches: + print(f"\n WARNING: {len(zero_matches)} pools with zero DeFi Llama matches" + f" (check SYMBOL_MAP):") + for pid, toks, pair, chain in zero_matches: + print(f" {pid} {toks:>20s} → {'/'.join(pair)} ({chain})") + + # Fetch historical TVL for each (pair, chain) combination + print(f"\nFetching historical TVL (same-chain)...") + # Key: (pair, chain) -> pd.Series of daily total TVL + pair_chain_histories = {} + + fetched = set() + for pid in pool_ids: + m = matches[pid] + pair = m["pair_key"] + chain = m["chain"] + key = (pair, chain) + if key in fetched: + continue + fetched.add(key) + + # Same-chain pools, sorted by TVL + llama = sorted(m["llama_pools_same_chain"], + key=lambda p: p.get("tvlUsd", 0), reverse=True) + llama = [p for p in llama if p.get("tvlUsd", 0) >= args.min_tvl] + llama = llama[:args.max_pools_per_pair] + + cache_name = f"{'_'.join(pair)}_{chain}_history.pkl" + pair_cache = os.path.join(args.cache_dir, cache_name) + + if not llama: + print(f" {'/'.join(pair)} ({chain}): no qualifying pools") + continue + + if os.path.exists(pair_cache): + with open(pair_cache, "rb") as f: + pair_chain_histories[key] = pickle.load(f) + print(f" {'/'.join(pair)} ({chain}): loaded from cache" + f" ({len(pair_chain_histories[key])} days)") + continue + + print(f" {'/'.join(pair)} ({chain}): fetching {len(llama)} pools...", + end="", flush=True) + pool_series = [] + for lp in llama: + lid = lp["pool"] + try: + hist = fetch_pool_history(lid) + if hist is not None and len(hist) > 10: + pool_series.append(hist) + except Exception as e: + print(f"\n Skip {lid}: {e}", end="") + time.sleep(3.0) # rate limit — DeFi Llama allows ~1 req/3s + print(f" got {len(pool_series)} histories") + + if pool_series: + df = pd.concat(pool_series, axis=1).sort_index() + pair_chain_histories[key] = df.sum(axis=1) # skipna=True: pre-launch = 0 + + with open(pair_cache, "wb") as f: + pickle.dump(pair_chain_histories[key], f) + + # --- Network conductance: fetch hub-pair TVL for multi-hop K --- + HUB_TOKENS = ["WETH", "WSTETH", "USDC", "USDT", "DAI", "WBTC"] + + # Identify all (token, hub) pairs we need across all pools + hub_pairs_needed = set() + for pid in pool_ids: + m = matches[pid] + tok_a, tok_b = m["tokens"] + chain = m["chain"] + for hub in HUB_TOKENS: + if hub in (tok_a, tok_b): + continue + # Need L(tok_a, hub) and L(hub, tok_b) on same chain + pair_ah = tuple(sorted([tok_a, hub])) + pair_hb = tuple(sorted([hub, tok_b])) + hub_pairs_needed.add((pair_ah, chain)) + hub_pairs_needed.add((pair_hb, chain)) + + # Remove pairs we already have + hub_pairs_to_fetch = hub_pairs_needed - fetched + print(f"\nFetching hub-pair TVL for network conductance...") + print(f" {len(hub_pairs_needed)} hub pairs needed," + f" {len(hub_pairs_to_fetch)} to fetch") + + for pair, chain in sorted(hub_pairs_to_fetch): + key = (pair, chain) + cache_name = f"{'_'.join(pair)}_{chain}_history.pkl" + pair_cache = os.path.join(args.cache_dir, cache_name) + + if os.path.exists(pair_cache): + with open(pair_cache, "rb") as f: + pair_chain_histories[key] = pickle.load(f) + continue + + # Find matching DeFi Llama pools + llama = pair_chain_to_llama.get((pair, chain), []) + llama = sorted(llama, key=lambda p: p.get("tvlUsd", 0), reverse=True) + llama = [p for p in llama if p.get("tvlUsd", 0) >= args.min_tvl] + llama = llama[:args.max_pools_per_pair] + + if not llama: + continue + + print(f" {'/'.join(pair)} ({chain}): fetching {len(llama)} pools...", + end="", flush=True) + pool_series = [] + for lp in llama: + lid = lp["pool"] + try: + hist = fetch_pool_history(lid) + if hist is not None and len(hist) > 10: + pool_series.append(hist) + except Exception as e: + print(f"\n Skip {lid}: {e}", end="") + time.sleep(3.0) + print(f" got {len(pool_series)} histories") + + if pool_series: + df = pd.concat(pool_series, axis=1).sort_index() + pair_chain_histories[key] = df.sum(axis=1) # skipna=True: pre-launch = 0 + with open(pair_cache, "wb") as f: + pickle.dump(pair_chain_histories[key], f) + + # Build per-pool competitor TVL arrays aligned to our panel dates + print(f"\nBuilding competitor TVL arrays...") + + # Common date grid + all_dates = set() + for pid in pool_ids: + all_dates.update(matched_clean[pid]["panel"]["date"].values) + date_list = sorted(all_dates) + n_dates = len(date_list) + date_to_idx = {d: i for i, d in enumerate(date_list)} + n_pools = len(pool_ids) + + competitor_tvl = np.full((n_dates, n_pools), np.nan) + + for j, pid in enumerate(pool_ids): + m = matches[pid] + pair = m["pair_key"] + chain = m["chain"] + key = (pair, chain) + if key not in pair_chain_histories: + continue + + hist = pair_chain_histories[key] + panel = matched_clean[pid]["panel"] + panel_dates = panel["date"].values + # Own TVL for self-exclusion: K_i = total_pair_tvl - own_tvl + own_tvl = np.exp(panel["log_tvl_lag1"].values.astype(float)) + + for k, date in enumerate(panel_dates): + t = date_to_idx[date] + day = pd.Timestamp(date).normalize() + if day in hist.index: + total = hist.loc[day] + own = own_tvl[k] if k < len(own_tvl) else 0 + # Competitor TVL = total pair TVL - own TVL (floor at 0) + competitor_tvl[t, j] = max(total - own, 0) + + # Forward-fill then back-fill gaps + for j in range(n_pools): + col = competitor_tvl[:, j] + mask = np.isfinite(col) + if mask.any() and not mask.all(): + s = pd.Series(col, index=date_list).ffill().bfill() + competitor_tvl[:, j] = s.values + + valid = np.isfinite(competitor_tvl) + n_valid = valid.sum() + n_total = n_dates * n_pools + print(f" Coverage: {n_valid}/{n_total} ({100*n_valid/n_total:.0f}%)") + + # Warn about pools with surprisingly low coverage despite having pair data + for j, pid in enumerate(pool_ids): + m = matches[pid] + key = (m["pair_key"], m["chain"]) + if key in pair_chain_histories and len(pair_chain_histories[key]) > 100: + col = competitor_tvl[:, j] + cov = np.isfinite(col).sum() / n_dates + if cov < 0.5: + print(f" WARNING: {pid[:16]} has pair data but only" + f" {cov*100:.0f}% coverage — possible date mismatch") + + # --- Compute K_eff = K_direct + multi-hop contributions --- + print(f"\nComputing network K_eff (direct + multi-hop)...") + # Compute multi-hop contribution over ALL dates in the grid + # (not just panel dates — so forward-fill works correctly) + k_eff = np.full((n_dates, n_pools), np.nan) + + def _get_pair_tvl_on_date(pair_key, chain, day): + """Get total TVL for a pair on a given date.""" + key = (pair_key, chain) + if key not in pair_chain_histories: + return 0.0 + hist = pair_chain_histories[key] + if day in hist.index: + return float(hist.loc[day]) + return 0.0 + + for j, pid in enumerate(pool_ids): + m = matches[pid] + tok_a, tok_b = m["tokens"] + chain = m["chain"] + + for t, date in enumerate(date_list): + day = pd.Timestamp(date).normalize() + + # Direct (from already-computed competitor_tvl) + direct = competitor_tvl[t, j] if np.isfinite(competitor_tvl[t, j]) else 0.0 + + # Multi-hop through hub tokens + multihop = 0.0 + for hub in HUB_TOKENS: + if hub in (tok_a, tok_b): + continue + pair_ah = tuple(sorted([tok_a, hub])) + pair_hb = tuple(sorted([hub, tok_b])) + L_ah = _get_pair_tvl_on_date(pair_ah, chain, day) + L_hb = _get_pair_tvl_on_date(pair_hb, chain, day) + if L_ah > 0 and L_hb > 0: + multihop += L_ah * L_hb / (L_ah + L_hb) + + total = direct + multihop + if total > 0: + k_eff[t, j] = total + + # Forward-fill / back-fill K_eff gaps + for j in range(n_pools): + col = k_eff[:, j] + mask = np.isfinite(col) & (col > 0) + if mask.any() and not mask.all(): + s = pd.Series(col, index=date_list).ffill().bfill() + k_eff[:, j] = s.values + + # Per-pool stats + print(f"\n {'Pool':>16s} {'Tokens':>20s} {'Pair':>20s}" + f" {'K_direct med':>14s} {'K_eff med':>14s} {'Multi/Dir':>10s}") + for j, pid in enumerate(pool_ids): + toks = matched_clean[pid].get("tokens", "?") + pair = matches[pid]["pair_key"] + # Use only panel dates for display (not forward-filled grid dates) + panel_dates = matched_clean[pid]["panel"]["date"].values + panel_t = [date_to_idx[d] for d in panel_dates if d in date_to_idx] + if panel_t: + d_vals = competitor_tvl[panel_t, j] + e_vals = k_eff[panel_t, j] + valid_d = d_vals[np.isfinite(d_vals)] + valid_e = e_vals[np.isfinite(e_vals)] + else: + valid_d = valid_e = np.array([]) + med_d = np.median(valid_d) if len(valid_d) > 0 else 0 + med_e = np.median(valid_e) if len(valid_e) > 0 else 0 + ratio = med_e / med_d if med_d > 0 else float("inf") + print(f" {pid[:16]} {toks:>20s} {'/'.join(pair):>20s}" + f" ${med_d:>13,.0f} ${med_e:>13,.0f} {ratio:>9.1f}x") + + # Save + out_path = os.path.join(args.cache_dir, "competitor_tvl.npz") + np.savez(out_path, + pool_ids=pool_ids, + date_list=np.array([str(d) for d in date_list]), + competitor_tvl=competitor_tvl, + k_eff=k_eff) + print(f"\nSaved: {out_path}") + + # Also save raw pair-chain histories for inspection + pair_path = os.path.join(args.cache_dir, "pair_chain_histories.pkl") + with open(pair_path, "wb") as f: + pickle.dump(pair_chain_histories, f) + print(f"Saved: {pair_path}") + + +if __name__ == "__main__": + main() diff --git a/scripts/fetch_token_mcaps.py b/scripts/fetch_token_mcaps.py new file mode 100644 index 0000000..8e71d6f --- /dev/null +++ b/scripts/fetch_token_mcaps.py @@ -0,0 +1,196 @@ +"""Fetch token market caps from CoinGecko and cache locally. + +Usage: + python scripts/fetch_token_mcaps.py + +Output: + local_data/noise_calibration/token_mcaps.json +""" + +import json +import os +import sys +import time + +import requests + +OUTPUT_PATH = os.path.join( + os.path.dirname(os.path.dirname(__file__)), + "local_data", "noise_calibration", "token_mcaps.json", +) + +# Token symbol -> CoinGecko ID mapping +# Covers all tokens appearing in our 26 matched pools + common Balancer tokens +COINGECKO_IDS = { + # Blue-chip / wrapped natives + "WETH": "ethereum", + "ETH": "ethereum", + "WBTC": "wrapped-bitcoin", + "BTC": "bitcoin", + "cbBTC": "bitcoin", # Coinbase wrapped BTC — use BTC mcap + "USDC": "usd-coin", + "USDT": "tether", + "DAI": "dai", + "wstETH": "wrapped-steth", + "stETH": "staked-ether", + "rETH": "rocket-pool-eth", + "cbETH": "coinbase-wrapped-staked-eth", + "WMATIC": "polygon-ecosystem-token", + "MATIC": "polygon-ecosystem-token", + "POL": "polygon-ecosystem-token", + "WAVAX": "avalanche-2", + "AVAX": "avalanche-2", + "GNO": "gnosis", + "WXDAI": "dai", # Wrapped xDAI ≈ DAI + "xDAI": "dai", + "S": "sonic-3", + "wS": "sonic-3", + # Mid-cap DeFi + "AAVE": "aave", + "LINK": "chainlink", + "UNI": "uniswap", + "BAL": "balancer", + "MKR": "maker", + "CRV": "curve-dao-token", + "COMP": "compound-governance-token", + "SNX": "havven", + "LDO": "lido-dao", + "RPL": "rocket-pool", + "SUSHI": "sushi", + "YFI": "yearn-finance", + "1INCH": "1inch", + "ENS": "ethereum-name-service", + "ARB": "arbitrum", + "OP": "optimism", + "PENDLE": "pendle", + "ENA": "ethena", + "EIGEN": "eigenlayer", + "COW": "cow-protocol", + "SAFE": "safe", + # Smaller / specific tokens in our pools + "ACX": "across-protocol", + "ALCX": "alchemix", + "QI": "benqi", + "QNT": "quant-network", + "RDNT": "radiant-capital", + # TREE not on CoinGecko — handled as fallback below + "XAI": "xai-blockchain", + # Wrapped aTokens — use underlying + "waEthLidoWETH": "ethereum", + "waEthLidowstETH": "wrapped-steth", + "waBasWETH": "ethereum", + "waBasUSDC": "usd-coin", + "waEthUSDC": "usd-coin", + "waGnoGNO": "gnosis", + "waGnowstETH": "wrapped-steth", + # Additional tokens from expanded pool set + "wPOL": "polygon-ecosystem-token", + "stS": "sonic-3", # Staked Sonic — use S mcap + "JitoSOL": "jito-governance-token", + "scUSD": "usd-coin", # Rings scUSD stablecoin — use USDC mcap as proxy + "DOLA": "dola-usd", +} + +# Asset type classification +STABLECOINS = { + "USDC", "USDT", "DAI", "WXDAI", "xDAI", "GHO", "LUSD", "crvUSD", + "FRAX", "sDAI", "scUSD", "DOLA", + "waBasUSDC", "waEthUSDC", +} +NATIVE_LST = { + "WETH", "ETH", "wstETH", "stETH", "rETH", "cbETH", + "WBTC", "BTC", "cbBTC", + "WMATIC", "MATIC", "POL", "wPOL", + "WAVAX", "AVAX", + "GNO", "S", "wS", "stS", + "JitoSOL", + "waEthLidoWETH", "waEthLidowstETH", + "waBasWETH", "waGnoGNO", "waGnowstETH", +} +# Everything else is VOLATILE (asset_type=2) + + +def fetch_mcaps(): + """Fetch market caps from CoinGecko in batches.""" + unique_ids = sorted(set(COINGECKO_IDS.values())) + print(f"Fetching market caps for {len(unique_ids)} unique CoinGecko IDs...") + + # CoinGecko allows up to 250 IDs per request + batch_size = 100 + all_data = {} + + for i in range(0, len(unique_ids), batch_size): + batch = unique_ids[i:i + batch_size] + ids_str = ",".join(batch) + url = ( + f"https://api.coingecko.com/api/v3/simple/price" + f"?ids={ids_str}&vs_currencies=usd&include_market_cap=true" + ) + resp = requests.get(url, timeout=30) + resp.raise_for_status() + data = resp.json() + all_data.update(data) + print(f" Batch {i // batch_size + 1}: {len(data)} tokens") + if i + batch_size < len(unique_ids): + time.sleep(1) # rate limit + + # Build symbol -> mcap mapping + mcaps = {} + missing = [] + for symbol, gecko_id in COINGECKO_IDS.items(): + if gecko_id in all_data and "usd_market_cap" in all_data[gecko_id]: + mcaps[symbol] = { + "mcap_usd": all_data[gecko_id]["usd_market_cap"], + "price_usd": all_data[gecko_id]["usd"], + "coingecko_id": gecko_id, + } + else: + missing.append((symbol, gecko_id)) + + if missing: + print(f"\n Missing from CoinGecko: {missing}") + + # Fallback for tokens not on CoinGecko (very small tokens) + FALLBACK_MCAPS = { + "TREE": 1_000_000, # ~$1M estimate for small governance token + } + for symbol, mcap_est in FALLBACK_MCAPS.items(): + if symbol not in mcaps: + mcaps[symbol] = { + "mcap_usd": mcap_est, + "price_usd": 0.0, + "coingecko_id": "fallback", + } + print(f" Fallback: {symbol} -> ${mcap_est:,.0f}") + + # Add asset type classification + for symbol in mcaps: + if symbol in STABLECOINS: + mcaps[symbol]["asset_type"] = "stable" + elif symbol in NATIVE_LST: + mcaps[symbol]["asset_type"] = "native_lst" + else: + mcaps[symbol]["asset_type"] = "volatile" + + return mcaps + + +def main(): + mcaps = fetch_mcaps() + + os.makedirs(os.path.dirname(OUTPUT_PATH), exist_ok=True) + with open(OUTPUT_PATH, "w") as f: + json.dump(mcaps, f, indent=2) + + print(f"\nSaved {len(mcaps)} tokens to {OUTPUT_PATH}") + + # Summary + print("\nSample entries:") + for sym in ["WETH", "AAVE", "USDC", "QI", "TREE"]: + if sym in mcaps: + m = mcaps[sym] + print(f" {sym}: ${m['mcap_usd']:,.0f} ({m['asset_type']})") + + +if __name__ == "__main__": + main() diff --git a/scripts/plot_calibrated_vs_real.py b/scripts/plot_calibrated_vs_real.py new file mode 100644 index 0000000..475422b --- /dev/null +++ b/scripts/plot_calibrated_vs_real.py @@ -0,0 +1,241 @@ +"""Plot predicted vs real volume using saved per-pool calibrated noise model. + +Loads the artifact from experiments/run_linear_market_noise.py (--per-pool), +rebuilds the features, evaluates V_arb(learned cadence) + V_noise(x @ coeffs_i), +and generates stacked area plots showing the arb/noise decomposition per pool. + +Usage: + # First train and save: + python experiments/run_linear_market_noise.py --per-pool --no-split --epochs 2000 + + # Then plot: + python scripts/plot_calibrated_vs_real.py + python scripts/plot_calibrated_vs_real.py --artifact results/linear_market_noise/model.npz +""" + +import argparse +import json +import os +import sys +import time + +import matplotlib +matplotlib.use("Agg") +import matplotlib.pyplot as plt +import numpy as np +import pandas as pd + +sys.path.insert(0, os.path.dirname(os.path.dirname(__file__))) + +ARTIFACT_DIR = os.path.join( + os.path.dirname(os.path.dirname(__file__)), + "results", "linear_market_noise", +) +OUTPUT_DIR = os.path.join( + os.path.dirname(os.path.dirname(__file__)), + "results", "calibrated_vs_real", +) + + +def main(): + parser = argparse.ArgumentParser(description=__doc__, + formatter_class=argparse.RawDescriptionHelpFormatter) + parser.add_argument("--artifact", default=os.path.join(ARTIFACT_DIR, "model.npz")) + parser.add_argument("--meta", default=os.path.join(ARTIFACT_DIR, "meta.json")) + parser.add_argument("--output-dir", default=OUTPUT_DIR) + args = parser.parse_args() + + os.environ.setdefault("JAX_PLATFORMS", "cpu") + os.makedirs(args.output_dir, exist_ok=True) + + # ---- Load artifact ---- + print(f"Loading artifact: {args.artifact}") + art = np.load(args.artifact, allow_pickle=True) + noise_coeffs = art["noise_coeffs"] + log_cadence = art["log_cadence"] + init_log_cadences = art["init_log_cadences"] + + with open(args.meta) as f: + meta = json.load(f) + feat_names = meta["feat_names"] + pool_ids = meta["pool_ids"] + n_pools = meta["n_pools"] + hparams = meta["hparams"] + per_pool = noise_coeffs.ndim == 2 + + print(f" {n_pools} pools, {len(feat_names)} features, per_pool={per_pool}") + print(f" hparams: {hparams}") + + # ---- Rebuild data (features only, no training) ---- + import jax.numpy as jnp + from quantammsim.calibration.grid_interpolation import interpolate_pool_daily + from experiments.run_linear_market_noise import load_stage1, build_data + + matched_clean, option_c_clean = load_stage1() + + print("\nRebuilding features...") + t0 = time.time() + data = build_data( + matched_clean, option_c_clean, + trend_windows=tuple(hparams["trend_windows"]), + include_market=True, include_cross_pool=True, + ) + print(f" {len(data['pool_idx'])} samples, {time.time() - t0:.1f}s") + + x = data["x"] + y_total = data["y_total"] + pool_idx = data["pool_idx"] + day_idx = data["day_idx"] + sgd = data["sample_grid_days"] + + # ---- Compute predictions ---- + if per_pool: + per_sample_coeffs = noise_coeffs[pool_idx] + log_v_noise = np.sum(x * per_sample_coeffs, axis=1) + else: + log_v_noise = x @ noise_coeffs + + if "pool_intercepts" in art: + log_v_noise = log_v_noise + art["pool_intercepts"][pool_idx] + + v_noise = np.exp(log_v_noise) + + v_arb = np.zeros(len(y_total)) + for i in range(n_pools): + mask = pool_idx == i + if not mask.any(): + continue + v_arb_all = np.array(interpolate_pool_daily( + data["pool_coeffs"][i], jnp.float64(log_cadence[i]), + data["pool_gas"][i])) + v_arb[mask] = v_arb_all[sgd[mask]] + + v_obs = np.exp(y_total) + log_v_arb = np.log(np.maximum(v_arb, 1e-10)) + pred_total_log = np.logaddexp(log_v_arb, log_v_noise) + + # ---- Reconstruct dates ---- + all_dates = set() + for pid in pool_ids: + all_dates.update(matched_clean[pid]["panel"]["date"].values) + date_list = sorted(all_dates) + + # ---- Plot: stacked area per pool ---- + per_page = 9 + n_pages = (n_pools + per_page - 1) // per_page + + for page in range(n_pages): + start = page * per_page + end = min(start + per_page, n_pools) + n_this = end - start + + ncols = 3 + nrows = (n_this + ncols - 1) // ncols + fig, axes = plt.subplots(nrows, ncols, figsize=(16, 4 * nrows)) + if nrows == 1: + axes = axes.reshape(1, -1) + + for idx, i in enumerate(range(start, end)): + ax = axes[idx // ncols][idx % ncols] + mask = pool_idx == i + if mask.sum() < 5: + ax.set_visible(False) + continue + + days = day_idx[mask] + dates = [pd.Timestamp(date_list[d]) for d in days] + vo = v_obs[mask] + va = v_arb[mask] + vn = v_noise[mask] + + ax.fill_between(dates, 0, va, alpha=0.3, color="steelblue", + label="V_arb") + ax.fill_between(dates, va, va + vn, alpha=0.3, color="coral", + label="V_noise") + ax.plot(dates, vo, "k-", linewidth=0.8, alpha=0.7, label="V_obs") + ax.plot(dates, va + vn, "--", color="darkred", linewidth=0.8, + alpha=0.7, label="V_pred") + + ax.set_yscale("log") + ax.set_ylabel("USD/day", fontsize=7) + ax.tick_params(labelsize=6) + ax.tick_params(axis="x", rotation=30) + + yt = y_total[mask] + pt = pred_total_log[mask] + ss_res = np.sum((yt - pt) ** 2) + ss_tot = np.sum((yt - yt.mean()) ** 2) + r2 = 1 - ss_res / max(ss_tot, 1e-10) + + pid = pool_ids[i] + tokens = matched_clean[pid]["tokens"] + chain = matched_clean[pid]["chain"] + ci = np.exp(init_log_cadences[i]) + cl = np.exp(log_cadence[i]) + arb_share = np.median(va / vo) * 100 + noise_share = np.median(vn / vo) * 100 + b_tvl = noise_coeffs[i, 1] if per_pool else noise_coeffs[1] + + ax.set_title( + f"{tokens} ({chain})\n" + f"R\u00b2={r2:.3f} cad={ci:.0f}\u2192{cl:.0f}min " + f"arb={arb_share:.0f}% noise={noise_share:.0f}% " + f"b_tvl={b_tvl:.2f}", + fontsize=7) + ax.legend(fontsize=6, loc="upper right") + + for idx in range(n_this, nrows * ncols): + axes[idx // ncols][idx % ncols].set_visible(False) + + fig.suptitle( + f"Per-pool calibrated noise model \u2014 V_arb + V_noise " + f"(page {page+1}/{n_pages})", fontsize=10) + fig.tight_layout() + out = os.path.join(args.output_dir, f"calibrated_page{page+1}.png") + fig.savefig(out, dpi=150, bbox_inches="tight") + plt.close(fig) + print(f" Saved: {out}") + + # ---- Summary ---- + summary = [] + for i in range(n_pools): + mask = pool_idx == i + if mask.sum() < 2: + continue + yt = y_total[mask] + pt = pred_total_log[mask] + ss_res = np.sum((yt - pt) ** 2) + ss_tot = np.sum((yt - yt.mean()) ** 2) + r2 = 1 - ss_res / max(ss_tot, 1e-10) + + pid = pool_ids[i] + va = v_arb[mask] + vn = v_noise[mask] + vo = v_obs[mask] + + summary.append({ + "pool_id": pid, + "tokens": matched_clean[pid]["tokens"], + "chain": matched_clean[pid]["chain"], + "n_obs": int(mask.sum()), + "R2": r2, + "cadence_init": float(np.exp(init_log_cadences[i])), + "cadence_learned": float(np.exp(log_cadence[i])), + "median_arb_pct": float(np.median(va / vo) * 100), + "median_noise_pct": float(np.median(vn / vo) * 100), + "b_tvl": float(noise_coeffs[i, 1] if per_pool else noise_coeffs[1]), + }) + + summary_df = pd.DataFrame(summary) + csv_path = os.path.join(args.output_dir, "summary.csv") + summary_df.to_csv(csv_path, index=False) + print(f"\n Summary: {csv_path}") + print(f" Median R\u00b2: {summary_df['R2'].median():.4f}") + print(f" Median arb: {summary_df['median_arb_pct'].median():.0f}%") + print(f" b_tvl: [{summary_df['b_tvl'].min():.2f}," + f" {summary_df['b_tvl'].max():.2f}]," + f" median={summary_df['b_tvl'].median():.2f}") + + +if __name__ == "__main__": + main() diff --git a/scripts/plot_mm_noise_fit.py b/scripts/plot_mm_noise_fit.py new file mode 100644 index 0000000..6d1b331 --- /dev/null +++ b/scripts/plot_mm_noise_fit.py @@ -0,0 +1,494 @@ +"""Plot MM noise model fit: per-pool time series + TVL response curves. + +Produces two types of plots: +1. Per-pool 6-panel time series (like plot_model_vs_real_reclamm.py): + TVL, volume decomposition, V_noise, fee revenue, vol/TVL, pred/obs +2. Cross-pool TVL response curves showing MM saturation + +Usage: + python scripts/plot_mm_noise_fit.py + python scripts/plot_mm_noise_fit.py --pool 0x9d1fcf346ea1b0 + python scripts/plot_mm_noise_fit.py --all-pools +""" + +import argparse +import json +import os +import pickle +import sys + +import matplotlib +matplotlib.use("Agg") +import matplotlib.pyplot as plt +import numpy as np +import pandas as pd + +import jax.numpy as jnp + + +CACHE_DIR = os.path.join( + os.path.dirname(os.path.dirname(__file__)), + "results", "token_factored_calibration", "_cache", +) + + +def load_model(artifact_dir): + """Load MM model artifact.""" + art = dict(np.load(os.path.join(artifact_dir, "model.npz"), + allow_pickle=True)) + with open(os.path.join(artifact_dir, "meta.json")) as f: + meta = json.load(f) + params = {} + for k in art: + params[k] = jnp.array(art[k]) + return params, meta + + +def get_pool_K(params, decomp, pool_i): + """Get median K for a pool, handling all K modes.""" + mask = decomp["pool_idx"] == pool_i + if "k_scale" in params: + ks = np.array(params["k_scale"]) + if not mask.any(): + return float(np.exp(ks[0])) + lc = decomp.get("log_comp_tvl", np.zeros(mask.sum()))[mask] + log_K = ks[0] + ks[1] * lc + return float(np.exp(np.median(log_K))) + elif "log_K" in params: + return float(np.exp(params["log_K"][pool_i])) + elif "k_params" in params: + k_p = np.array(params["k_params"]) + return float(np.exp(k_p[0])) + elif "log_comp_tvl" in decomp and mask.any(): + # Observed K directly from competitor TVL + return float(np.exp(np.median(decomp["log_comp_tvl"][mask]))) + return np.exp(14.5) + + +def compute_decomposition(params, meta, matched_clean, option_c_clean): + """Compute V_arb, V_noise, V_total for all pools.""" + from experiments.run_mm_noise import build_mm_data, forward_mm + from quantammsim.calibration.grid_interpolation import interpolate_pool_daily + + data = build_mm_data(matched_clean, option_c_clean, + trend_windows=(7,), + include_cross_pool=False) + + pool_ids = data["pool_ids"] + n_pools = data["n_pools"] + pool_idx = np.array(data["pool_idx"]) + sgd = np.array(data["sample_grid_days"]) + day_idx = np.array(data["day_idx"]) + y = np.array(data["y_total"]) + log_tvl = np.array(data["log_tvl"]) + + log_cadence = np.array(params["log_cadence"]) + + # V_arb + v_arb = np.zeros(len(y)) + for i in range(n_pools): + mask = pool_idx == i + if not mask.any(): + continue + v_arb_all = np.array(interpolate_pool_daily( + data["pool_coeffs"][i], jnp.float64(log_cadence[i]), + data["pool_gas"][i])) + safe = np.clip(sgd[mask], 0, len(v_arb_all) - 1) + v_arb[mask] = v_arb_all[safe] + + # V_noise + log_v_noise = np.array(forward_mm( + params, jnp.array(data["x_market"]), + jnp.array(data["log_tvl"]), + jnp.array(data["pool_idx"]), + log_comp_tvl=jnp.array(data["log_comp_tvl"]))) + v_noise = np.exp(log_v_noise) + v_total = v_arb + v_noise + v_obs = np.exp(y) + + # Reconstruct dates + all_dates = set() + for pid in pool_ids: + all_dates.update(matched_clean[pid]["panel"]["date"].values) + date_list = sorted(all_dates) + + dates = np.array([pd.Timestamp(date_list[d]) for d in day_idx]) + + return { + "pool_ids": pool_ids, + "pool_tokens": data["pool_tokens"], + "pool_idx": pool_idx, + "dates": dates, + "v_arb": v_arb, + "v_noise": v_noise, + "v_total": v_total, + "v_obs": v_obs, + "log_tvl": log_tvl, + "log_comp_tvl": data["log_comp_tvl"], + "tvl": np.exp(log_tvl), + } + + +def plot_pool_timeseries(decomp, params, pool_i, output_dir): + """6-panel time series for a single pool.""" + pid = decomp["pool_ids"][pool_i] + toks = decomp["pool_tokens"][pool_i] + label = f"{toks[0]}/{toks[1]}" + + mask = decomp["pool_idx"] == pool_i + if mask.sum() < 10: + print(f" Skipping {pid[:16]} ({label}): {mask.sum()} samples") + return + + dates = decomp["dates"][mask] + v_arb = decomp["v_arb"][mask] + v_noise = decomp["v_noise"][mask] + v_total = decomp["v_total"][mask] + v_obs = decomp["v_obs"][mask] + tvl = decomp["tvl"][mask] + + K_i = get_pool_K(params, decomp, pool_i) + + # R² + log_pred = np.log(np.maximum(v_total, 1e-10)) + log_obs = np.log(np.maximum(v_obs, 1e-10)) + ss_res = np.sum((log_pred - log_obs) ** 2) + ss_tot = np.sum((log_obs - log_obs.mean()) ** 2) + r2 = 1 - ss_res / max(ss_tot, 1e-10) + + fig, axes = plt.subplots(6, 1, figsize=(14, 18), sharex=True) + + # 1. TVL + ax = axes[0] + ax.plot(dates, tvl / 1e6, "k-", linewidth=0.7) + ax.axhline(K_i / 1e6, color="red", linestyle="--", alpha=0.5, + label=f"K = ${K_i/1e6:.1f}M") + ax.set_ylabel("TVL ($M)") + ax.set_yscale("log") + ax.legend(fontsize=8) + ax.set_title(f"{label} — TVL (K={K_i/1e6:.1f}M)") + ax.grid(True, alpha=0.3) + + # 2. Volume decomposition + ax = axes[1] + ax.fill_between(dates, 0, v_arb / 1e6, alpha=0.4, color="steelblue", + label="V_arb") + ax.fill_between(dates, v_arb / 1e6, v_total / 1e6, alpha=0.4, + color="coral", label="V_noise (MM)") + ax.plot(dates, v_obs / 1e6, "k-", linewidth=0.5, alpha=0.7, + label="V_obs") + ax.plot(dates, v_total / 1e6, "r--", linewidth=0.5, alpha=0.7, + label="V_pred") + ax.set_ylabel("Volume ($M/day)") + ax.set_yscale("log") + ax.legend(fontsize=7) + ax.set_title(f"Volume Decomposition (R²={r2:.3f})") + ax.grid(True, alpha=0.3) + + # 3. V_noise only + ax = axes[2] + ax.fill_between(dates, 0, v_noise / 1e6, alpha=0.4, color="coral") + ax.plot(dates, v_noise / 1e6, "r-", linewidth=0.5) + noise_med = np.median(v_noise) + ax.axhline(noise_med / 1e6, color="red", linestyle=":", alpha=0.5, + label=f"median=${noise_med:,.0f}") + ax.set_ylabel("V_noise ($M/day)") + ax.set_yscale("log") + ax.legend(fontsize=8) + ax.set_title("Noise Volume (MM model)") + ax.grid(True, alpha=0.3) + + # 4. Fee revenue (assuming 0.25% fee, 25% protocol take) + fee_rate = 0.0025 + protocol_take = 0.25 + fee_arb = v_arb * fee_rate * (1 - protocol_take) + fee_noise = v_noise * fee_rate * (1 - protocol_take) + fee_obs = v_obs * fee_rate * (1 - protocol_take) + ax = axes[3] + ax.fill_between(dates, 0, fee_arb, alpha=0.4, color="steelblue", + label="Arb fees") + ax.fill_between(dates, fee_arb, fee_arb + fee_noise, alpha=0.4, + color="coral", label="Noise fees") + ax.plot(dates, fee_obs, "k-", linewidth=0.5, alpha=0.7, + label="Obs fees (approx)") + ax.set_ylabel("Fee revenue ($/day)") + ax.legend(fontsize=7) + ax.set_title("Fee Revenue (0.25% fee, 75% LP)") + ax.grid(True, alpha=0.3) + + # 5. Vol/TVL + ax = axes[4] + vol_tvl_obs = v_obs / tvl + vol_tvl_pred = v_total / tvl + ax.plot(dates, vol_tvl_obs * 100, "k-", linewidth=0.5, alpha=0.5, + label="Observed") + ax.plot(dates, vol_tvl_pred * 100, "r-", linewidth=0.5, alpha=0.5, + label="Predicted") + ax.axhline(np.median(vol_tvl_obs) * 100, color="black", linestyle=":", + alpha=0.3) + ax.axhline(np.median(vol_tvl_pred) * 100, color="red", linestyle=":", + alpha=0.3) + ax.set_ylabel("Vol/TVL (%)") + ax.legend(fontsize=8) + ax.set_title("Volume as % of TVL") + ax.grid(True, alpha=0.3) + + # 6. Pred/Obs ratio + ax = axes[5] + ratio = v_total / np.maximum(v_obs, 1) + ax.plot(dates, ratio, "b-", linewidth=0.5, alpha=0.5) + ax.axhline(1.0, color="black", linestyle="-", alpha=0.3) + med_ratio = np.median(ratio) + ax.axhline(med_ratio, color="blue", linestyle=":", alpha=0.5, + label=f"median={med_ratio:.2f}") + ax.set_ylabel("Pred / Obs") + ax.set_yscale("log") + ax.set_ylim(0.05, 20) + ax.legend(fontsize=8) + ax.set_title("Prediction Ratio") + ax.grid(True, alpha=0.3) + ax.set_xlabel("Date") + + fig.suptitle(f"MM Noise Model — {label} ({pid[:16]})", fontsize=13) + fig.tight_layout() + out = os.path.join(output_dir, f"{pid[:16]}_mm_fit.png") + fig.savefig(out, dpi=150, bbox_inches="tight") + plt.close(fig) + print(f" Saved: {out}") + + +def plot_tvl_response(params, meta, decomp, output_dir): + """Cross-pool TVL response curves showing MM saturation.""" + pool_ids = meta["pool_ids"] + pool_tokens = meta["pool_tokens"] + n_pools = len(pool_ids) + + tvl_range = np.logspace(4, 10, 200) # $10K to $10B + + fig, axes = plt.subplots(1, 3, figsize=(18, 6)) + + pool_idx_arr = np.array(decomp["pool_idx"]) + interesting = [] + for i in range(n_pools): + n = (pool_idx_arr == i).sum() + if n > 0: + interesting.append(i) + + colors = plt.cm.tab20(np.linspace(0, 1, len(interesting))) + + # Panel 1: Noise volume vs TVL (absolute) + ax = axes[0] + for ci, i in enumerate(interesting): + K_i = get_pool_K(params, decomp, i) + + mask = pool_idx_arr == i + actual_noise = np.median(decomp["v_noise"][mask]) + actual_tvl = np.median(decomp["tvl"][mask]) + # Scale: noise(TVL) = actual_noise * [TVL/(K+TVL)] / [actual_TVL/(K+actual_TVL)] + mm_actual = actual_tvl / (K_i + actual_tvl) + mm_curve = tvl_range / (K_i + tvl_range) + noise_curve = actual_noise * mm_curve / mm_actual + + label = f"{pool_tokens[i][0]}/{pool_tokens[i][1]}" + ax.plot(tvl_range / 1e6, noise_curve / 1e6, color=colors[ci], + linewidth=1.0, alpha=0.7, label=label) + # Mark actual TVL + ax.scatter([actual_tvl / 1e6], [actual_noise / 1e6], + color=colors[ci], s=20, zorder=5) + + ax.set_xlabel("TVL ($M)") + ax.set_ylabel("Daily Noise Volume ($M)") + ax.set_xscale("log") + ax.set_yscale("log") + ax.set_title("Noise Volume vs TVL (MM saturation)") + ax.legend(fontsize=5, ncol=3, loc="best") + ax.grid(True, alpha=0.3) + + # Panel 2: Noise/TVL ratio vs TVL + ax = axes[1] + for ci, i in enumerate(interesting): + K_i = get_pool_K(params, decomp, i) + mask = pool_idx_arr == i + actual_noise = np.median(decomp["v_noise"][mask]) + actual_tvl = np.median(decomp["tvl"][mask]) + mm_actual = actual_tvl / (K_i + actual_tvl) + mm_curve = tvl_range / (K_i + tvl_range) + noise_curve = actual_noise * mm_curve / mm_actual + ratio_curve = noise_curve / tvl_range * 100 + + label = f"{pool_tokens[i][0]}/{pool_tokens[i][1]}" + ax.plot(tvl_range / 1e6, ratio_curve, color=colors[ci], + linewidth=1.0, alpha=0.7, label=label) + + ax.set_xlabel("TVL ($M)") + ax.set_ylabel("Noise / TVL (%)") + ax.set_xscale("log") + ax.set_title("Noise as Fraction of TVL") + ax.legend(fontsize=5, ncol=3, loc="best") + ax.grid(True, alpha=0.3) + + # Panel 3: Elasticity vs TVL + ax = axes[2] + for ci, i in enumerate(interesting): + K_i = get_pool_K(params, decomp, i) + eps_curve = K_i / (K_i + tvl_range) + + label = f"{pool_tokens[i][0]}/{pool_tokens[i][1]}" + ax.plot(tvl_range / 1e6, eps_curve, color=colors[ci], + linewidth=1.0, alpha=0.7, label=label) + # Mark actual TVL + actual_tvl = np.median(decomp["tvl"][pool_idx_arr == i]) + eps_actual = K_i / (K_i + actual_tvl) + ax.scatter([actual_tvl / 1e6], [eps_actual], + color=colors[ci], s=20, zorder=5) + + ax.axhline(0.5, color="gray", linestyle="--", alpha=0.3, label="ε=0.5") + ax.set_xlabel("TVL ($M)") + ax.set_ylabel("Elasticity ε(TVL)") + ax.set_xscale("log") + ax.set_ylim(0, 1.05) + ax.set_title("TVL Elasticity (K/(K+TVL))") + ax.legend(fontsize=5, ncol=3, loc="best") + ax.grid(True, alpha=0.3) + + fig.suptitle("Michaelis-Menten Noise Model — TVL Response", fontsize=13) + fig.tight_layout() + out = os.path.join(output_dir, "mm_tvl_response.png") + fig.savefig(out, dpi=150, bbox_inches="tight") + plt.close(fig) + print(f" Saved: {out}") + + +def plot_K_distribution(params, meta, decomp, output_dir): + """Plot K values across pools with data quality indicator.""" + pool_ids = meta["pool_ids"] + pool_tokens = meta["pool_tokens"] + n_pools = len(pool_ids) + pool_idx_arr = np.array(decomp["pool_idx"]) + + K_vals = [] + labels = [] + n_days = [] + tvl_ranges = [] + for i in range(n_pools): + mask = pool_idx_arr == i + n = mask.sum() + K_i = get_pool_K(params, decomp, i) + K_vals.append(K_i) + tok = pool_tokens[i] + labels.append(f"{tok[0]}/{tok[1]}") + n_days.append(n) + if n > 0: + tvl = decomp["tvl"][mask] + tvl_ranges.append(np.log10(tvl.max() / max(tvl.min(), 1))) + else: + tvl_ranges.append(0) + + K_vals = np.array(K_vals) + n_days = np.array(n_days) + tvl_ranges = np.array(tvl_ranges) + + # Sort by K + order = np.argsort(K_vals) + + fig, axes = plt.subplots(1, 2, figsize=(16, 8)) + + # Panel 1: K values as horizontal bar + ax = axes[0] + y_pos = np.arange(n_pools) + colors = plt.cm.viridis(tvl_ranges[order] / max(tvl_ranges.max(), 1)) + ax.barh(y_pos, K_vals[order] / 1e6, color=colors, height=0.7) + ax.set_yticks(y_pos) + ax.set_yticklabels([labels[i] for i in order], fontsize=7) + ax.set_xlabel("K ($M)") + ax.set_title("Half-Saturation TVL by Pool\n(color = log10 TVL range)") + ax.axvline(np.median(K_vals) / 1e6, color="red", linestyle="--", + alpha=0.5, label=f"median=${np.median(K_vals)/1e6:.1f}M") + ax.legend() + ax.grid(True, alpha=0.3, axis="x") + + # Panel 2: K vs data quality (n_days and TVL range) + ax = axes[1] + valid = n_days > 0 + sc = ax.scatter(n_days[valid], K_vals[valid] / 1e6, + c=tvl_ranges[valid], cmap="viridis", + s=50, alpha=0.7) + for i in range(n_pools): + if n_days[i] > 0: + ax.annotate(labels[i], (n_days[i], K_vals[i] / 1e6), + fontsize=5, alpha=0.7) + ax.set_xlabel("Number of training days") + ax.set_ylabel("K ($M)") + ax.set_title("K vs Data Quantity\n(color = log10 TVL range)") + plt.colorbar(sc, ax=ax, label="log10(TVL_max/TVL_min)") + ax.grid(True, alpha=0.3) + + fig.suptitle("Michaelis-Menten K Distribution", fontsize=13) + fig.tight_layout() + out = os.path.join(output_dir, "mm_K_distribution.png") + fig.savefig(out, dpi=150, bbox_inches="tight") + plt.close(fig) + print(f" Saved: {out}") + + +def main(): + parser = argparse.ArgumentParser(description=__doc__, + formatter_class=argparse.RawDescriptionHelpFormatter) + parser.add_argument("--pool", default=None, + help="Plot single pool (prefix match)") + parser.add_argument("--all-pools", action="store_true") + parser.add_argument("--artifact-dir", default="results/mm_noise") + parser.add_argument("--output-dir", default="results/mm_noise/plots") + parser.add_argument("--top-n", type=int, default=None, + help="Plot top N pools by sample count (default: all)") + args = parser.parse_args() + + os.environ.setdefault("JAX_PLATFORMS", "cpu") + os.makedirs(args.output_dir, exist_ok=True) + + # Load + print("Loading model...") + params, meta = load_model(args.artifact_dir) + + print("Loading data...") + with open(os.path.join(CACHE_DIR, "stage1.pkl"), "rb") as f: + stage1 = pickle.load(f) + matched_clean = stage1["matched_clean"] + option_c_clean = stage1["option_c_clean"] + + print("Computing decomposition...") + decomp = compute_decomposition(params, meta, matched_clean, option_c_clean) + + pool_ids = decomp["pool_ids"] + pool_idx = decomp["pool_idx"] + + # Which pools to plot + if args.pool: + targets = [i for i, pid in enumerate(pool_ids) + if pid.startswith(args.pool)] + elif args.top_n is not None: + counts = [(pool_idx == i).sum() for i in range(len(pool_ids))] + targets = sorted(range(len(pool_ids)), key=lambda i: -counts[i]) + targets = targets[:args.top_n] + else: + # Default: all pools + targets = list(range(len(pool_ids))) + + # Per-pool time series + print(f"\nPlotting {len(targets)} pools...") + for i in targets: + plot_pool_timeseries(decomp, params, i, args.output_dir) + + # TVL response curves + print("\nPlotting TVL response...") + plot_tvl_response(params, meta, decomp, args.output_dir) + + # K distribution + print("\nPlotting K distribution...") + plot_K_distribution(params, meta, decomp, args.output_dir) + + print(f"\nDone. Plots in {args.output_dir}/") + + +if __name__ == "__main__": + main() diff --git a/scripts/plot_model_vs_real_reclamm.py b/scripts/plot_model_vs_real_reclamm.py new file mode 100644 index 0000000..c0c3da3 --- /dev/null +++ b/scripts/plot_model_vs_real_reclamm.py @@ -0,0 +1,262 @@ +"""Plot full model (V_arb + V_noise) vs real observed volume for a pool. + +Uses the pool's actual historical TVL path, evaluates V_arb from the PCHIP +grid at the learned cadence, and V_noise from the per-pool linear model. +Compares against observed total volume. + +Usage: + python scripts/plot_model_vs_real_reclamm.py + python scripts/plot_model_vs_real_reclamm.py --pool 0x3de27efa2f1aa6 + python scripts/plot_model_vs_real_reclamm.py --pool 0x9d1fcf346ea1b0 +""" + +import argparse +import os +import pickle +import sys + +import matplotlib +matplotlib.use("Agg") +import matplotlib.pyplot as plt +import numpy as np +import pandas as pd + +sys.path.insert(0, os.path.dirname(os.path.dirname(__file__))) + +import jax.numpy as jnp +from quantammsim.calibration.grid_interpolation import interpolate_pool_daily +from quantammsim.calibration.noise_model_arrays import load_artifact + + +CACHE_DIR = os.path.join( + os.path.dirname(os.path.dirname(__file__)), + "results", "token_factored_calibration", "_cache", +) +ARTIFACT_DIR = os.path.join( + os.path.dirname(os.path.dirname(__file__)), + "results", "linear_market_noise", +) +OUTPUT_DIR = os.path.join( + os.path.dirname(os.path.dirname(__file__)), + "results", "model_vs_real", +) + + +def main(): + parser = argparse.ArgumentParser(description=__doc__, + formatter_class=argparse.RawDescriptionHelpFormatter) + parser.add_argument("--pool", default="0x9d1fcf346ea1b0", + help="Pool ID prefix") + parser.add_argument("--artifact-dir", default=ARTIFACT_DIR) + parser.add_argument("--output-dir", default=OUTPUT_DIR) + args = parser.parse_args() + + os.environ.setdefault("JAX_PLATFORMS", "cpu") + os.makedirs(args.output_dir, exist_ok=True) + + # Load data + with open(os.path.join(CACHE_DIR, "stage1.pkl"), "rb") as f: + data = pickle.load(f) + mc = data["matched_clean"] + oc = data["option_c_clean"] + + pid = args.pool + entry = mc[pid] + panel = entry["panel"] + dates = pd.to_datetime(panel["date"]) + vol_obs = np.exp(panel["log_volume"].values.astype(float)) + tvl = np.exp(panel["log_tvl_lag1"].values.astype(float)) + + # Load noise model + art, meta = load_artifact(args.artifact_dir) + pool_ids = meta["pool_ids"] + idx = pool_ids.index(pid) + coeffs = art["noise_coeffs"][idx] + cadence = float(np.exp(art["log_cadence"][idx])) + gas = float(np.exp(oc[pid]["log_gas"])) + + print(f"Pool: {pid} ({entry['tokens']}, {entry['chain']})") + print(f"Cadence: {cadence:.1f} min, Gas: ${gas}") + print(f"{len(dates)} days: {dates.min().date()} → {dates.max().date()}") + print(f"TVL: ${tvl.min():,.0f} – ${tvl.max():,.0f}") + + # V_arb from PCHIP + v_arb_all = np.array(interpolate_pool_daily( + entry["coeffs"], jnp.float64(np.log(cadence)), jnp.float64(gas))) + + # V_noise from model at actual TVL + from experiments.run_linear_market_noise import build_data + data_full = build_data(mc, oc, trend_windows=(7,), + include_market=True, include_cross_pool=False) + x_full = data_full["x"] + pool_idx_full = data_full["pool_idx"] + pool_mask = pool_idx_full == idx + sample_x = x_full[pool_mask] + sgd = data_full["sample_grid_days"][pool_mask] + day_idx = data_full["day_idx"][pool_mask] + + log_v_noise = sample_x @ coeffs + v_noise = np.exp(log_v_noise) + v_arb_samples = v_arb_all[sgd] + v_total_pred = v_arb_samples + v_noise + + # Align dates + all_dates = set() + for p in pool_ids: + all_dates.update(mc[p]["panel"]["date"].values) + date_list = sorted(all_dates) + sample_dates = np.array([pd.Timestamp(date_list[d]) for d in day_idx]) + + # Match TVL and obs volume + tvl_samples = np.zeros(len(sample_dates)) + vol_obs_samples = np.zeros(len(sample_dates)) + for i, sd in enumerate(sample_dates): + matches = np.where(dates == sd)[0] + if len(matches) > 0: + tvl_samples[i] = tvl[matches[0]] + vol_obs_samples[i] = vol_obs[matches[0]] + + valid = tvl_samples > 100 + sd = sample_dates[valid] + vo = vol_obs_samples[valid] + va = v_arb_samples[valid] + vn = v_noise[valid] + vt = v_total_pred[valid] + tv = tvl_samples[valid] + + # R² + log_obs = np.log(np.maximum(vo, 1)) + log_pred = np.log(np.maximum(vt, 1)) + ss_res = np.sum((log_obs - log_pred) ** 2) + ss_tot = np.sum((log_obs - log_obs.mean()) ** 2) + r2 = 1 - ss_res / max(ss_tot, 1e-10) + + print(f"\nR² (log): {r2:.3f}") + print(f"Median obs: ${np.median(vo):,.0f}, pred: ${np.median(vt):,.0f}") + print(f"Median V_arb: ${np.median(va):,.0f}, V_noise: ${np.median(vn):,.0f}") + + # Fee rate + fee_rate = float(panel["swap_fee"].iloc[0]) if "swap_fee" in panel.columns else 0.003 + + # Plot + fig, axes = plt.subplots(6, 1, figsize=(14, 20), sharex=True) + + # 1. TVL + ax = axes[0] + ax.plot(sd, tv, "b-", linewidth=1) + ax.set_ylabel("TVL (USD)") + ax.set_yscale("log") + ax.set_title(f"{entry['tokens']} ({entry['chain']}) — " + f"Model (V_arb + V_noise) vs Observed " + f"[R\u00b2={r2:.3f}, cadence={cadence:.0f}min, fee={fee_rate:.4f}]") + ax.grid(True, alpha=0.3) + + # 2. Volume: stacked arb + noise vs observed + ax = axes[1] + ax.fill_between(sd, 0, va, alpha=0.3, color="steelblue", label="V_arb (PCHIP)") + ax.fill_between(sd, va, va + vn, alpha=0.3, color="coral", label="V_noise (model)") + ax.plot(sd, vo, "k-", linewidth=0.8, alpha=0.7, label="V_obs (actual)") + ax.plot(sd, vt, "r--", linewidth=0.8, alpha=0.5, label="V_pred = V_arb + V_noise") + ax.set_ylabel("Volume (USD/day)") + ax.set_yscale("log") + ax.legend(fontsize=8) + ax.grid(True, alpha=0.3) + + # 3. V_noise only + ax = axes[2] + ax.fill_between(sd, 0, vn, alpha=0.4, color="coral") + ax.plot(sd, vn, "r-", linewidth=0.8, alpha=0.7, label="V_noise (model)") + ax.axhline(np.median(vn), color="red", linestyle="--", alpha=0.5, + label=f"median: ${np.median(vn):,.0f}") + ax.set_ylabel("Noise volume (USD/day)") + ax.set_yscale("log") + ax.legend(fontsize=8) + ax.grid(True, alpha=0.3) + + # 4. Fee revenue: observed vs predicted + ax = axes[3] + fee_obs = vo * fee_rate + fee_pred = vt * fee_rate + fee_noise_only = vn * fee_rate + fee_arb = va * fee_rate + ax.fill_between(sd, 0, fee_arb, alpha=0.3, color="steelblue", label="Arb fees") + ax.fill_between(sd, fee_arb, fee_pred, alpha=0.3, color="coral", label="Noise fees") + ax.plot(sd, fee_obs, "k-", linewidth=0.8, alpha=0.7, label="Observed fees") + ax.plot(sd, fee_pred, "r--", linewidth=0.8, alpha=0.5, label="Predicted total fees") + ax.plot(sd, fee_noise_only, "m-", linewidth=0.8, alpha=0.6, + label=f"Noise fees only (med=${np.median(fee_noise_only):,.0f})") + ax.set_ylabel("Fee revenue (USD/day)") + ax.legend(fontsize=8) + ax.grid(True, alpha=0.3) + + # 5. Vol/TVL + ax = axes[4] + vol_tvl_obs = vo / tv * 100 + vol_tvl_pred = vt / tv * 100 + ax.plot(sd, vol_tvl_obs, "k-", linewidth=0.8, alpha=0.7, label="Observed") + ax.plot(sd, vol_tvl_pred, "r--", linewidth=0.8, alpha=0.5, label="Predicted") + ax.axhline(np.median(vol_tvl_obs), color="black", linestyle=":", + alpha=0.3, label=f"obs median: {np.median(vol_tvl_obs):.1f}%") + ax.axhline(np.median(vol_tvl_pred), color="red", linestyle=":", + alpha=0.3, label=f"pred median: {np.median(vol_tvl_pred):.1f}%") + ax.set_ylabel("Vol / TVL (%)") + ax.legend(fontsize=8) + ax.grid(True, alpha=0.3) + ax.set_ylim(0, min(np.percentile(vol_tvl_obs, 95) * 2, 200)) + + # 6. Pred/Obs ratio + ax = axes[5] + ratio = vt / np.maximum(vo, 1) + ax.plot(sd, ratio, "g-", linewidth=0.8, alpha=0.7) + ax.axhline(1.0, color="black", linestyle="--", alpha=0.5, label="perfect") + ax.axhline(np.median(ratio), color="red", linestyle="--", alpha=0.5, + label=f"median: {np.median(ratio):.2f}") + ax.set_ylabel("Pred / Obs") + ax.set_xlabel("Date") + ax.set_yscale("log") + ax.legend(fontsize=8) + ax.grid(True, alpha=0.3) + ax.set_ylim(0.01, 100) + + fig.tight_layout() + out = os.path.join(args.output_dir, f"{pid[:16]}_model_vs_real.png") + fig.savefig(out, dpi=150, bbox_inches="tight") + plt.close(fig) + print(f"\nSaved: {out}") + + # Fee summary + fee_obs_total = np.sum(vo * fee_rate) + fee_pred_total = np.sum(vt * fee_rate) + fee_noise_total = np.sum(vn * fee_rate) + fee_arb_total = np.sum(va * fee_rate) + print(f"\nFee revenue (cumulative, fee={fee_rate:.4f}):") + print(f" Observed: ${fee_obs_total:,.0f}") + print(f" Predicted: ${fee_pred_total:,.0f}" + f" (arb: ${fee_arb_total:,.0f}, noise: ${fee_noise_total:,.0f})") + + # Pre/post deposit stats (for reClAMM AAVE/ETH) + pre = sd < pd.Timestamp("2026-01-10") + post = sd >= pd.Timestamp("2026-01-20") + if pre.sum() > 5 and post.sum() > 5: + print(f"\nPre-deposit (before Jan 10):") + print(f" TVL: ${np.median(tv[pre]):,.0f}") + print(f" V_obs: ${np.median(vo[pre]):,.0f}," + f" V_pred: ${np.median(vt[pre]):,.0f}") + print(f" V_arb: ${np.median(va[pre]):,.0f}," + f" V_noise: ${np.median(vn[pre]):,.0f}") + print(f" Fees obs: ${np.median(vo[pre])*fee_rate:,.0f}/day," + f" pred: ${np.median(vt[pre])*fee_rate:,.0f}/day") + print(f" Pred/Obs: {np.median(vt[pre] / vo[pre]):.2f}") + print(f"Post-deposit (after Jan 20):") + print(f" TVL: ${np.median(tv[post]):,.0f}") + print(f" V_obs: ${np.median(vo[post]):,.0f}," + f" V_pred: ${np.median(vt[post]):,.0f}") + print(f" V_arb: ${np.median(va[post]):,.0f}," + f" V_noise: ${np.median(vn[post]):,.0f}") + print(f" Fees obs: ${np.median(vo[post])*fee_rate:,.0f}/day," + f" pred: ${np.median(vt[post])*fee_rate:,.0f}/day") + print(f" Pred/Obs: {np.median(vt[post] / vo[post]):.2f}") + + +if __name__ == "__main__": + main() diff --git a/scripts/plot_predicted_vs_real_volume.py b/scripts/plot_predicted_vs_real_volume.py new file mode 100644 index 0000000..83232ab --- /dev/null +++ b/scripts/plot_predicted_vs_real_volume.py @@ -0,0 +1,151 @@ +"""Plot predicted vs real daily volume for pool registry pools. + +Uses the fitted noise model (from calibrate_noise_unified.py) to compute +predicted daily log-volume for each pool in the registry, and overlays +the actual observed volume from the Balancer API panel data. +""" + +import json +import os +import sys + +import matplotlib.pyplot as plt +import numpy as np +import pandas as pd + +sys.path.insert(0, os.path.join(os.path.dirname(__file__), "..", "experiments")) +from pool_registry import POOL_REGISTRY, BALANCER_API_CHAIN + + +def main(): + fitted_path = "results/unified_full_90d.json" + panel_path = "local_data/noise_calibration/panel.parquet" + output_dir = "results/unified_full_90d" + os.makedirs(output_dir, exist_ok=True) + + with open(fitted_path) as f: + fitted = json.load(f) + + panel = pd.read_parquet(panel_path) + + # Deduplicate registry: multiple entries can share the same pool address + # (e.g. cbBTC_WETH and cbBTC_WETH_post_oct). Group by address. + unique_pools = {} + for label, pool in POOL_REGISTRY.items(): + addr = pool.pool_address.lower() + if addr not in unique_pools: + unique_pools[addr] = (label, pool) + + # Match to panel + matched = [] + for addr, (label, pool) in unique_pools.items(): + pid_matches = [ + pid for pid in fitted["pools"] + if addr in pid.lower() + ] + if pid_matches: + pid = pid_matches[0] + matched.append((label, pool, pid)) + else: + print(f" {label}: not in fitted model (skipping)") + + if not matched: + print("No registry pools found in the fitted model.") + return + + print(f"Plotting {len(matched)} pools: {[m[0] for m in matched]}") + + # Determine grid layout + n = len(matched) + ncols = min(n, 2) + nrows = (n + ncols - 1) // ncols + fig, axes = plt.subplots(nrows, ncols, figsize=(7 * ncols, 5 * nrows), + squeeze=False) + + for idx, (label, pool, pid) in enumerate(matched): + ax = axes[idx // ncols][idx % ncols] + pool_data = fitted["pools"][pid] + theta = np.array(pool_data["theta_median"]) + # theta = [intercept, b_tvl, b_sigma, b_weekend] + + # Get panel data for this pool + pool_panel = panel[panel["pool_id"] == pid].copy() + pool_panel = pool_panel.sort_values("date") + + if len(pool_panel) == 0: + ax.set_title(f"{label}: no panel data") + continue + + # Filter to last 90 days (matching training window) + max_date = panel["date"].max() + if hasattr(max_date, "date"): + max_date = max_date + from datetime import date, timedelta + if isinstance(max_date, date): + cutoff = max_date - timedelta(days=90) + else: + cutoff = pd.Timestamp(max_date) - pd.Timedelta(days=90) + pool_panel = pool_panel[ + pool_panel["date"].apply( + lambda d: d >= cutoff if isinstance(d, date) + else pd.Timestamp(d).date() >= cutoff + ) + ].copy() + + if len(pool_panel) < 5: + ax.set_title(f"{label}: <5 obs in 90d window") + continue + + # Build x_obs: [1, log_tvl_lag1, volatility, weekend] + x_obs = np.column_stack([ + np.ones(len(pool_panel)), + pool_panel["log_tvl_lag1"].values, + pool_panel["volatility"].values, + pool_panel["weekend"].values, + ]) + + predicted_log_vol = x_obs @ theta + actual_log_vol = pool_panel["log_volume"].values + + # Convert to USD volume for interpretability + predicted_vol = np.exp(predicted_log_vol) + actual_vol = np.exp(actual_log_vol) + + dates = pd.to_datetime(pool_panel["date"].values) + + # Plot + ax.plot(dates, actual_vol, "o-", color="steelblue", markersize=3, + linewidth=1, alpha=0.7, label="Actual") + ax.plot(dates, predicted_vol, "s--", color="orangered", markersize=3, + linewidth=1, alpha=0.7, label="Predicted") + ax.set_yscale("log") + ax.set_ylabel("Daily volume (USD)") + ax.set_title(f"{label} ({pool_data['chain']})\n" + f"b_c={theta[1]:.2f} b_σ={theta[2]:.2f} " + f"b_wknd={theta[3]:.2f}") + ax.legend(fontsize=8) + ax.tick_params(axis="x", rotation=30) + + # Annotate R² for this pool + ss_res = np.sum((actual_log_vol - predicted_log_vol) ** 2) + ss_tot = np.sum((actual_log_vol - actual_log_vol.mean()) ** 2) + r2 = 1 - ss_res / ss_tot if ss_tot > 0 else float("nan") + ax.text(0.02, 0.95, f"R²={r2:.3f}\nn={len(pool_panel)}", + transform=ax.transAxes, fontsize=8, va="top", + bbox=dict(boxstyle="round,pad=0.3", fc="white", alpha=0.8)) + + # Hide unused axes + for idx in range(n, nrows * ncols): + axes[idx // ncols][idx % ncols].set_visible(False) + + fig.suptitle("Noise model: predicted vs actual daily volume\n" + "(registry pools, 90-day training window)", fontsize=13) + fig.tight_layout() + out_path = os.path.join(output_dir, "registry_predicted_vs_real.png") + fig.savefig(out_path, dpi=150, bbox_inches="tight") + print(f"Saved: {out_path}") + plt.close(fig) + + +if __name__ == "__main__": + main() diff --git a/scripts/plot_reclamm_optuna_result.py b/scripts/plot_reclamm_optuna_result.py new file mode 100644 index 0000000..9a0862c --- /dev/null +++ b/scripts/plot_reclamm_optuna_result.py @@ -0,0 +1,625 @@ +#!/usr/bin/env python3 +"""Plot reClAMM pool performance from Optuna tuning results. + +Reads SGD-compatible JSON output(s) of tune_reclamm_params.py, extracts the +best trial's pool params, re-runs a forward pass over the full train+test +window, and produces a value-over-time plot with on-chain baselines and +cumulative fee revenue. + +Usage: + # Single result + python scripts/plot_reclamm_optuna_result.py results/run_.json + + # Multiple results (comparison across objectives / noise models) + python scripts/plot_reclamm_optuna_result.py results/run_*.json + + # Top-3 trials from each result + python scripts/plot_reclamm_optuna_result.py results/run_*.json --top-k 3 +""" + +import argparse +import json +import os +import sys + +import jax.numpy as jnp +import matplotlib +matplotlib.use("Agg") +import matplotlib.pyplot as plt +import numpy as np +import pandas as pd +from datetime import datetime + +from quantammsim.runners.jax_runners import do_run_on_historic_data + +# ── On-chain baselines ──────────────────────────────────────────────────── +ONCHAIN_LAUNCH_PARAMS = { + "price_ratio": 1.5, "centeredness_margin": 0.5, "shift_exponent": 0.1, +} +ONCHAIN_CURRENT_PARAMS = { + "price_ratio": 4.0, "centeredness_margin": 0.1, "shift_exponent": 0.001, +} + +THEME_ORDER = ("light", "dark") +THEMES = { + "light": { + "bg": "#F7FAFC", + "text": "#171923", + "subtle_text": "#4A5568", + "reference": "#112055", + "grid": "#4F5764", + "colors": [ + "#2048e9", "#008361", "#b43821", "#c2410c", "#5c38c9", + "#6D4F2C", "#457dff", "#00a474", "#d7462b", "#ea580c", + "#7f6ae8", "#92693A", "#183bbb", "#00674e", "#718096", + ], + }, + "dark": { + "bg": "#171923", + "text": "#EDF2F7", + "subtle_text": "#CBD5E0", + "reference": "#F7FAFC", + "grid": "#4F5764", + "colors": [ + "#457dff", "#00d395", "#ea6249", "#f97316", "#7f6ae8", + "#B68449", "#2554ff", "#00a474", "#d7462b", "#fb923c", + "#6c4add", "#92693A", "#2048e9", "#008361", "#A0AEC0", + ], + }, +} +BG = THEMES["dark"]["bg"] +TEXT_COLOR = THEMES["dark"]["text"] +SUBTLE_TEXT_COLOR = THEMES["dark"]["subtle_text"] +REFERENCE_COLOR = THEMES["dark"]["reference"] +GRID_COLOR = THEMES["dark"]["grid"] +COLORS = THEMES["dark"]["colors"] + + +def _apply_theme(theme_name): + """Apply one of the chart themes derived from colors.ts.""" + global BG, TEXT_COLOR, SUBTLE_TEXT_COLOR, REFERENCE_COLOR, GRID_COLOR, COLORS + theme = THEMES[theme_name] + BG = theme["bg"] + TEXT_COLOR = theme["text"] + SUBTLE_TEXT_COLOR = theme["subtle_text"] + REFERENCE_COLOR = theme["reference"] + GRID_COLOR = theme["grid"] + COLORS = theme["colors"] + + +def _themed_output_path(output, theme_name): + root, ext = os.path.splitext(output) + return f"{root}_{theme_name}{ext or '.png'}" + +# Short labels for objectives +_OBJ_SHORT = { + "daily_log_sharpe": "sharpe", + "returns_over_hodl": "ret/hodl", + "fee_revenue_over_value": "fee_rev", +} + + +def _plot_order(configs): + """Yield (name, meta, color_idx) with baselines first, optimized trials last.""" + optimized = [] + baselines = [] + for i, (name, meta) in enumerate(configs.items()): + if "On-Chain" in name: + baselines.append((name, meta, i)) + else: + optimized.append((name, meta, i)) + return baselines + optimized + + +def parse_args(): + p = argparse.ArgumentParser( + description=__doc__, + formatter_class=argparse.RawDescriptionHelpFormatter, + ) + p.add_argument("results_json", nargs="+", + help="Path(s) to run_.json from Optuna") + p.add_argument("--top-k", type=int, default=1, + help="Plot top K trials per result file (default 1)") + p.add_argument("--output", default=None, + help="Output PNG path (default: auto-generated)") + p.add_argument("--no-onchain", action="store_true", + help="Skip on-chain baseline runs") + p.add_argument("--end-test-date", default=None, + help="Override endTestDateString (e.g. '2026-02-15 00:00:00')") + p.add_argument("--noise-trader-ratio", type=float, default=None, + help="Override noise_trader_ratio from results config") + return p.parse_args() + + +def load_results(path): + """Load the double-encoded JSONL from Optuna results.""" + with open(path) as f: + raw = f.read() + data = json.loads(raw) + if isinstance(data, str): + data = json.loads(data) + if not isinstance(data, list) or len(data) < 2: + print(f"ERROR: Expected [config, trial1, trial2, ...], got {type(data)}") + sys.exit(1) + config = data[0] + trials = data[1:] + return config, trials + + +def extract_pool_params(trial, config): + """Extract reClAMM pool params from a trial entry.""" + param_keys = ["price_ratio", "centeredness_margin", "shift_exponent", + "arc_length_speed", "fees"] + params = {} + for k in param_keys: + if k in trial: + params[k] = trial[k] + return params + + +def _noise_model_label(config): + """Short label describing the noise model in the config.""" + nm = config.get("noise_model", "ratio") + if nm != "calibrated": + ntr = config.get("noise_trader_ratio", 0.0) + return f"{nm}(ntr={ntr})" + nc = config.get("reclamm_noise_params", {}) + n_coeffs = len(nc) + arb_freq = config.get("arb_frequency", 1) + return f"cal-{n_coeffs}cov(af={arb_freq})" + + +def run_full_period(params, config, fees_override=None): + """Run forward pass over the full train+test window.""" + fees = fees_override if fees_override is not None else config["fees"] + fp = { + "rule": "reclamm", + "tokens": config["tokens"], + "startDateString": config["startDateString"], + "endDateString": config["endTestDateString"], # full period + "initial_pool_value": config["initial_pool_value"], + "do_arb": config["do_arb"], + "fees": fees, + "gas_cost": config.get("gas_cost", 1.0), + "arb_fees": config.get("arb_fees", 0.0), + "protocol_fee_split": config.get("protocol_fee_split", 0.0), + "noise_trader_ratio": config.get("noise_trader_ratio", 0.0), + "reclamm_use_shift_exponent": config.get("reclamm_use_shift_exponent", True), + "reclamm_interpolation_method": config.get("reclamm_interpolation_method", "geometric"), + "reclamm_centeredness_scaling": config.get("reclamm_centeredness_scaling", False), + "reclamm_learn_arc_length_speed": config.get("reclamm_learn_arc_length_speed", False), + } + # Forward noise model settings + if "noise_model" in config: + fp["noise_model"] = config["noise_model"] + if "reclamm_noise_params" in config: + fp["reclamm_noise_params"] = config["reclamm_noise_params"] + if "noise_arrays_path" in config: + fp["noise_arrays_path"] = config["noise_arrays_path"] + if "arb_frequency" in config: + fp["arb_frequency"] = config["arb_frequency"] + jax_params = {k: jnp.array(v) for k, v in params.items()} + return do_run_on_historic_data(run_fingerprint=fp, params=jax_params) + + +def _plot_results_once(configs, time_series, hodl_values, ref_config, args, theme_name): + """Two-panel plot: value-over-time + cumulative fee revenue.""" + train_end_str = ref_config["endDateString"] + train_end_dt = datetime.strptime(train_end_str, "%Y-%m-%d %H:%M:%S") + start_dt = datetime.strptime(ref_config["startDateString"], "%Y-%m-%d %H:%M:%S") + + first_out = next(iter(time_series.values())) + n_minutes = len(first_out["value"]) + dates = pd.date_range(start=start_dt, periods=n_minutes, freq="1min") + step = 1440 + dates_daily = dates[::step] + + # Detect normalised data (values near 1.0 vs millions) + normalised = ref_config.get("normalised", False) + if not normalised: + first_val = np.array(first_out["value"][0]) + normalised = first_val < 100 # heuristic: normalised data starts near 1.0 + + val_scale = 1.0 if normalised else 1e-6 + val_ylabel = "Normalised Value" if normalised else "Pool Value ($M USD)" + fee_scale = 1.0 if normalised else 1e-3 + fee_ylabel = ("Cum. Fee Revenue (fraction of TVL)" if normalised + else "Cumulative Fee Revenue ($K)") + + has_fee_revenue = any( + "fee_revenue" in time_series[n] and time_series[n]["fee_revenue"] is not None + for n in time_series + ) + has_reserves = any( + "reserves" in time_series[n] and "prices" in time_series[n] + for n in time_series + ) + n_panels = 1 + int(has_fee_revenue) + int(has_reserves) + ratios = [3] + [1.5] * (n_panels - 1) + fig, axes = plt.subplots( + n_panels, 1, figsize=(14, 3.5 + 3 * n_panels), + sharex=True, gridspec_kw={"height_ratios": ratios, "hspace": 0.3}, + ) + if n_panels == 1: + axes = [axes] + ax_val = axes[0] + + # ── Panel 1: Value over time ────────────────────────────────────── + for name, meta, ci in _plot_order(configs): + out = time_series[name] + vals = np.array(out["value"][::step]) * val_scale + label = f"{name}" + if "test_objective" in meta: + obj_name = meta.get("obj_name", "objective") + label += f" (OOS {obj_name}={meta['test_objective']:.4f})" + is_optimized = "On-Chain" not in name + ax_val.plot(dates_daily[:len(vals)], vals, + linewidth=2.5 if is_optimized else 1.8, + color=COLORS[ci % len(COLORS)], label=label, + zorder=3 if is_optimized else 2) + + hodl_daily = hodl_values[::step] * val_scale + ax_val.plot(dates_daily[:len(hodl_daily)], hodl_daily, linewidth=2, + color=REFERENCE_COLOR, alpha=0.7, linestyle="--", label="HODL") + + if train_end_dt > start_dt and train_end_dt < dates[-1]: + ax_val.axvline(x=train_end_dt, color=REFERENCE_COLOR, linestyle=":", alpha=0.5, linewidth=1.5) + ylims = ax_val.get_ylim() + ax_val.text(train_end_dt - pd.Timedelta(days=5), ylims[1] * 0.97, "Train", + color=SUBTLE_TEXT_COLOR, alpha=0.8, fontsize=11, ha="right", va="top") + ax_val.text(train_end_dt + pd.Timedelta(days=5), ylims[1] * 0.97, "Test", + color=SUBTLE_TEXT_COLOR, alpha=0.8, fontsize=11, ha="left", va="top") + + _style_axis(ax_val) + ax_val.set_ylabel(val_ylabel, color=TEXT_COLOR, fontsize=12) + tokens_str = "/".join(ref_config["tokens"]) + date_range_str = f"{start_dt.strftime('%b %Y')} — {dates[-1].strftime('%b %Y')}" + ax_val.set_title( + f"Auto-range {tokens_str} — {date_range_str}", + color=TEXT_COLOR, fontsize=13, pad=15, + ) + ax_val.legend(loc="upper left", fontsize=8, facecolor=BG, + edgecolor=TEXT_COLOR, labelcolor=TEXT_COLOR) + + # ── Panel 2: Cumulative fee revenue ─────────────────────────────── + if has_fee_revenue: + ax_fee = axes[1] + for name, _meta, ci in _plot_order(configs): + out = time_series[name] + fr = out.get("fee_revenue") + if fr is None: + continue + fr = np.array(fr) + cumfee = np.cumsum(fr)[::step] * fee_scale + is_optimized = "On-Chain" not in name + ax_fee.plot(dates_daily[:len(cumfee)], cumfee, + linewidth=2.5 if is_optimized else 1.8, + color=COLORS[ci % len(COLORS)], label=name, + zorder=3 if is_optimized else 2) + + if train_end_dt > start_dt and train_end_dt < dates[-1]: + ax_fee.axvline(x=train_end_dt, color=REFERENCE_COLOR, linestyle=":", alpha=0.5, linewidth=1.5) + _style_axis(ax_fee) + ax_fee.set_ylabel(fee_ylabel, color=TEXT_COLOR, fontsize=12) + ax_fee.legend(loc="upper left", fontsize=8, facecolor=BG, + edgecolor=TEXT_COLOR, labelcolor=TEXT_COLOR) + + # ── Panel 3: Cumulative volume ─────────────────────────────────── + if has_reserves: + ax_idx = 1 + int(has_fee_revenue) + ax_vol = axes[ax_idx] + vol_ylabel = ("Cum. Volume (multiple of TVL)" if normalised + else "Cum. Volume ($M)") + for name, _meta, ci in _plot_order(configs): + out = time_series[name] + res = np.array(out.get("reserves")) + pri = np.array(out.get("prices")) + if res is None or pri is None: + continue + # Volume ≈ 0.5 * sum(|Δreserves| * prices) per step + delta_r = np.diff(res, axis=0) + step_vol = 0.5 * np.sum(np.abs(delta_r) * pri[1:], axis=1) + cum_vol = np.cumsum(step_vol) + # Normalise: by initial TVL if normalised mode, else to $M + if normalised: + init_tvl = out.get("initial_tvl", 1.0) + vol_scale = 1.0 / max(init_tvl, 1.0) + else: + vol_scale = 1e-6 + # Pad to match dates_daily length + cum_vol_full = np.zeros(len(res)) + cum_vol_full[1:] = cum_vol + cum_vol_daily = cum_vol_full[::step] * vol_scale + is_optimized = "On-Chain" not in name + ax_vol.plot(dates_daily[:len(cum_vol_daily)], cum_vol_daily, + linewidth=2.5 if is_optimized else 1.8, + color=COLORS[ci % len(COLORS)], label=name, + zorder=3 if is_optimized else 2) + + if train_end_dt > start_dt and train_end_dt < dates[-1]: + ax_vol.axvline(x=train_end_dt, color=REFERENCE_COLOR, linestyle=":", alpha=0.5, linewidth=1.5) + _style_axis(ax_vol) + ax_vol.set_ylabel(vol_ylabel, color=TEXT_COLOR, fontsize=12) + ax_vol.set_xlabel("Date", color=TEXT_COLOR, fontsize=12) + ax_vol.legend(loc="upper left", fontsize=8, facecolor=BG, + edgecolor=TEXT_COLOR, labelcolor=TEXT_COLOR) + elif not has_fee_revenue: + ax_val.set_xlabel("Date", color=TEXT_COLOR, fontsize=12) + + fig.patch.set_facecolor(BG) + plt.tight_layout() + + output = _themed_output_path( + args.output or f"reclamm_optuna_{tokens_str.replace('/', '_')}.png", + theme_name, + ) + plt.savefig(output, dpi=200, bbox_inches="tight", facecolor=BG) + print(f"\nSaved plot to {output}") + plt.close() + + +def plot_results(configs, time_series, hodl_values, ref_config, args): + for theme_name in THEME_ORDER: + _apply_theme(theme_name) + _plot_results_once(configs, time_series, hodl_values, ref_config, args, theme_name) + + +def _plot_test_only_once(configs, time_series, hodl_values, ref_config, args, theme_name): + """Test-period plot with all curves normalised to start at 1.0.""" + train_end_str = ref_config["endDateString"] + train_end_dt = datetime.strptime(train_end_str, "%Y-%m-%d %H:%M:%S") + start_dt = datetime.strptime(ref_config["startDateString"], "%Y-%m-%d %H:%M:%S") + + first_out = next(iter(time_series.values())) + n_minutes = len(first_out["value"]) + dates = pd.date_range(start=start_dt, periods=n_minutes, freq="1min") + + # Find the index of the train/test boundary + train_minutes = int((train_end_dt - start_dt).total_seconds() / 60) + test_start_idx = min(train_minutes, n_minutes - 1) + + step = 1440 + test_dates = dates[test_start_idx::step] + + fig, ax = plt.subplots(1, 1, figsize=(14, 6)) + + for name, _meta, ci in _plot_order(configs): + out = time_series[name] + vals = np.array(out["value"]) + test_vals = vals[test_start_idx::step] + if len(test_vals) == 0: + continue + normalised = test_vals / test_vals[0] + is_optimized = "On-Chain" not in name + ax.plot(test_dates[:len(normalised)], normalised, + linewidth=2.5 if is_optimized else 1.8, + color=COLORS[ci % len(COLORS)], label=name, + zorder=3 if is_optimized else 2) + + hodl_test = hodl_values[test_start_idx::step] + if len(hodl_test) > 0: + hodl_norm = hodl_test / hodl_test[0] + ax.plot(test_dates[:len(hodl_norm)], hodl_norm, linewidth=2, + color=REFERENCE_COLOR, alpha=0.7, linestyle="--", label="HODL") + + ax.axhline(1.0, color=REFERENCE_COLOR, linestyle=":", alpha=0.3, linewidth=1) + _style_axis(ax) + tokens_str = "/".join(ref_config["tokens"]) + ax.set_title(f"Test Period Only (normalised) — {tokens_str}", + color=TEXT_COLOR, fontsize=13, pad=15) + ax.set_ylabel("Normalised Value", color=TEXT_COLOR, fontsize=12) + ax.set_xlabel("Date", color=TEXT_COLOR, fontsize=12) + ax.legend(loc="best", fontsize=8, facecolor=BG, + edgecolor=TEXT_COLOR, labelcolor=TEXT_COLOR) + + fig.patch.set_facecolor(BG) + plt.tight_layout() + base = (args.output or f"reclamm_optuna_{tokens_str.replace('/', '_')}.png") + output = _themed_output_path(base.replace(".png", "_test_only.png"), theme_name) + plt.savefig(output, dpi=200, bbox_inches="tight", facecolor=BG) + print(f"Saved plot to {output}") + plt.close() + + +def plot_test_only(configs, time_series, hodl_values, ref_config, args): + for theme_name in THEME_ORDER: + _apply_theme(theme_name) + _plot_test_only_once(configs, time_series, hodl_values, ref_config, args, theme_name) + + +def _plot_weights_once(configs, time_series, ref_config, args, theme_name): + """Effective weight (value fraction) of token 0 over time.""" + start_dt = datetime.strptime(ref_config["startDateString"], "%Y-%m-%d %H:%M:%S") + train_end_dt = datetime.strptime(ref_config["endDateString"], "%Y-%m-%d %H:%M:%S") + + first_out = next(iter(time_series.values())) + n_minutes = len(first_out["value"]) + dates = pd.date_range(start=start_dt, periods=n_minutes, freq="1min") + step = 1440 + dates_daily = dates[::step] + + token_name = ref_config["tokens"][0] + + fig, ax = plt.subplots(1, 1, figsize=(14, 5)) + + for name, _meta, ci in _plot_order(configs): + out = time_series[name] + weights = np.array(out["weights"]) # (T, 2) + w0 = weights[::step, 0] + is_optimized = "On-Chain" not in name + ax.plot(dates_daily[:len(w0)], w0, + linewidth=2.0 if is_optimized else 1.5, + color=COLORS[ci % len(COLORS)], label=name, + alpha=0.9 if is_optimized else 0.7, + zorder=3 if is_optimized else 2) + + ax.axhline(0.5, color=REFERENCE_COLOR, linestyle="--", alpha=0.3, linewidth=1) + if train_end_dt > start_dt and train_end_dt < dates[-1]: + ax.axvline(x=train_end_dt, color=REFERENCE_COLOR, linestyle=":", alpha=0.5, linewidth=1.5) + ylims = ax.get_ylim() + ax.text(train_end_dt - pd.Timedelta(days=5), ylims[1] * 0.97, "Train", + color=SUBTLE_TEXT_COLOR, alpha=0.8, fontsize=11, ha="right", va="top") + ax.text(train_end_dt + pd.Timedelta(days=5), ylims[1] * 0.97, "Test", + color=SUBTLE_TEXT_COLOR, alpha=0.8, fontsize=11, ha="left", va="top") + + _style_axis(ax) + tokens_str = "/".join(ref_config["tokens"]) + date_range_str = f"{start_dt.strftime('%b %Y')} — {dates[-1].strftime('%b %Y')}" + ax.set_title(f"Effective {token_name} Weight — Auto-range {tokens_str} — {date_range_str}", + color=TEXT_COLOR, fontsize=13, pad=15) + ax.set_ylabel(f"{token_name} weight (value fraction)", color=TEXT_COLOR, fontsize=12) + ax.set_xlabel("Date", color=TEXT_COLOR, fontsize=12) + ax.legend(loc="best", fontsize=8, facecolor=BG, + edgecolor=TEXT_COLOR, labelcolor=TEXT_COLOR) + + fig.patch.set_facecolor(BG) + plt.tight_layout() + base = (args.output or f"reclamm_optuna_{tokens_str.replace('/', '_')}.png") + output = _themed_output_path(base.replace(".png", "_weights.png"), theme_name) + plt.savefig(output, dpi=200, bbox_inches="tight", facecolor=BG) + print(f"Saved plot to {output}") + plt.close() + + +def plot_weights(configs, time_series, ref_config, args): + for theme_name in THEME_ORDER: + _apply_theme(theme_name) + _plot_weights_once(configs, time_series, ref_config, args, theme_name) + + +def _style_axis(ax): + ax.set_facecolor(BG) + ax.tick_params(colors=TEXT_COLOR) + for spine in ax.spines.values(): + spine.set_color(GRID_COLOR) + spine.set_alpha(0.8) + ax.spines["top"].set_visible(False) + ax.spines["right"].set_visible(False) + ax.grid(True, alpha=0.35, color=GRID_COLOR) + + +def main(): + args = parse_args() + + # ── Load all result files ───────────────────────────────────────── + all_loaded = [] + for path in args.results_json: + config, trials = load_results(path) + if args.end_test_date: + config["endTestDateString"] = args.end_test_date + if args.noise_trader_ratio is not None: + config["noise_trader_ratio"] = args.noise_trader_ratio + all_loaded.append((path, config, trials)) + + # Use first file's config as reference for dates/tokens + ref_config = all_loaded[0][1] + tokens = ref_config["tokens"] + + print("=" * 100) + print(f"reClAMM Optuna Result Plotter — {len(all_loaded)} result file(s)") + print("=" * 100) + print(f" Tokens: {'/'.join(tokens)}") + print(f" Train: {ref_config['startDateString']} → {ref_config['endDateString']}") + print(f" Test: {ref_config['endDateString']} → {ref_config['endTestDateString']}") + + # ── Build configs dict from all files ───────────────────────────── + configs = {} + for path, config, trials in all_loaded: + obj_name = config.get("return_val", "objective") + obj_short = _OBJ_SHORT.get(obj_name, obj_name) + noise_label = _noise_model_label(config) + + trials_sorted = sorted(trials, key=lambda t: t.get("objective", 0), reverse=True) + top_trials = trials_sorted[:args.top_k] + + for i, trial in enumerate(top_trials): + params = extract_pool_params(trial, config) + rank_suffix = f" r{i+1}" if args.top_k > 1 else "" + name = f"{obj_short} {noise_label}{rank_suffix}" + configs[name] = { + "params": params, + "config": config, # per-file config for noise model + "objective": trial.get("objective", 0), + "train_objective": trial.get("train_objective", 0), + "test_objective": trial.get("test_objective", 0), + "train_sharpe": trial.get("train_sharpe", 0), + "validation_sharpe": trial.get("validation_sharpe", 0), + "obj_name": obj_name, + } + print(f"\n {name}:") + print(f" {obj_name}: train={trial.get('train_objective', 0):.4f} " + f"test={trial.get('test_objective', 0):.4f} " + f"penalised={trial.get('objective', 0):.4f}") + print(f" sharpe: train={trial.get('train_sharpe', 0):+.4f} " + f"val={trial.get('validation_sharpe', 0):+.4f}") + for k, v in params.items(): + print(f" {k}: {v:.6g}") + + if not args.no_onchain: + configs["On-Chain (launch)"] = { + "params": dict(ONCHAIN_LAUNCH_PARAMS), + "config": ref_config, + } + configs["On-Chain (current)"] = { + "params": dict(ONCHAIN_CURRENT_PARAMS), + "config": ref_config, + } + + # ── Full-period runs ────────────────────────────────────────────── + print(f"\n--- Running full-period simulations ({ref_config['startDateString']} → " + f"{ref_config['endTestDateString']}) ---") + time_series = {} + for name, cfg in configs.items(): + print(f" {name}...", end=" ", flush=True) + run_config = cfg.get("config", ref_config) + out = run_full_period(cfg["params"], run_config) + time_series[name] = out + fv = float(out["final_value"]) + fr = out.get("fee_revenue") + fr_total = float(np.array(fr).sum()) if fr is not None else 0 + hodl = float((out["reserves"][0] * out["prices"][-1]).sum()) + print(f"final=${fv:,.0f} hodl=${hodl:,.0f} RoH={fv/hodl - 1:+.2%} " + f"fee_rev=${fr_total:,.0f}") + + first_out = next(iter(time_series.values())) + hodl_reserves = first_out["reserves"][0] + hodl_values = np.sum( + np.array(hodl_reserves) * np.array(first_out["prices"]), axis=1, + ) + + # ── Plots ───────────────────────────────────────────────────────── + plot_results(configs, time_series, hodl_values, ref_config, args) + plot_test_only(configs, time_series, hodl_values, ref_config, args) + plot_weights(configs, time_series, ref_config, args) + + # ── Summary table ───────────────────────────────────────────────── + print(f"\n{'=' * 130}") + print(f"SUMMARY — {'/'.join(tokens)}") + print(f"{'=' * 130}") + hdr = (f"{'Config':<35s} {'Objective':>12s} {'Train':>10s} {'Test':>10s} " + f"{'Train SR':>10s} {'Val SR':>10s} " + f"{'PR':>7s} {'Margin':>7s} {'ShiftExp':>10s} {'Full RoH':>10s}") + print(hdr) + print("-" * 130) + + for name, cfg in configs.items(): + cp = cfg["params"] + fv = float(time_series[name]["final_value"]) + full_roh = fv / float(hodl_values[-1]) - 1 + print( + f"{name:<35s} " + f"{cfg.get('obj_name', ''):>12s} " + f"{cfg.get('train_objective', float('nan')):>10.4f} " + f"{cfg.get('test_objective', float('nan')):>10.4f} " + f"{cfg.get('train_sharpe', float('nan')):>+10.4f} " + f"{cfg.get('validation_sharpe', float('nan')):>+10.4f} " + f"{cp.get('price_ratio', float('nan')):>7.3f} " + f"{cp.get('centeredness_margin', float('nan')):>7.4f} " + f"{cp.get('shift_exponent', float('nan')):>10.4g} " + f"{full_roh * 100:>+9.2f}%" + ) + print("=" * 130) + + +if __name__ == "__main__": + main() diff --git a/scripts/plot_sweep_results.py b/scripts/plot_sweep_results.py new file mode 100644 index 0000000..53d50ae --- /dev/null +++ b/scripts/plot_sweep_results.py @@ -0,0 +1,545 @@ +"""Plot all sweep results: rerun each (objective, period) combo and compare. + +Reads results/sweep/*.json, groups by period, reruns forward passes over +the full date range (train + test to end_test_date), and produces per-period +comparison plots. + +Usage: + python scripts/plot_sweep_results.py + python scripts/plot_sweep_results.py --end-test-date "2026-03-01 00:00:00" +""" + +import argparse +import glob +import json +import os +import re +import sys + +import matplotlib +matplotlib.use("Agg") +import matplotlib.pyplot as plt +import numpy as np +import pandas as pd +from datetime import datetime + +import jax.numpy as jnp +from quantammsim.runners.jax_runners import do_run_on_historic_data + + +_RESULTS_ROOT = os.path.dirname(os.path.dirname(__file__)) + +# ── Pool configurations ───────────────────────────────────────────────── +POOL_CONFIGS = { + "aave_eth": { + "sweep_dir": os.path.join(_RESULTS_ROOT, "results", "sweep"), + "output_dir": os.path.join(_RESULTS_ROOT, "results", "sweep", "plots"), + "periods": { + "bull_2023": ("2023-06-01", "2024-06-01", "2026-03-01"), + "default_2024": ("2024-06-01", "2025-06-01", "2026-03-01"), + "recent_2025": ("2025-01-01", "2025-09-01", "2026-03-01"), + "long_2021": ("2021-06-01", "2025-01-01", "2026-03-01"), + }, + "base_fp": { + "rule": "reclamm", + "tokens": ["AAVE", "ETH"], + "do_arb": True, + "arb_frequency": 4, + "fees": 0.0025, + "gas_cost": 1.0, + "arb_fees": 0.0, + "protocol_fee_split": 0.25, + "noise_trader_ratio": 0.0, + "noise_model": "mm_observed", + "noise_arrays_path": "results/mm_noise/_sim_arrays/" + "0x9d1fcf346ea1b0_2024-06-01_2026-03-01_mm.npz", + "reclamm_interpolation_method": "geometric", + "reclamm_centeredness_scaling": False, + "reclamm_learn_arc_length_speed": False, + "reclamm_use_shift_exponent": True, + "initial_pool_value": 20_000_000.0, + }, + "onchain_configs": { + "OnChain-launch": { + "price_ratio": 1.5, "centeredness_margin": 0.5, + "shift_exponent": 0.1, + }, + "OnChain-current": { + "price_ratio": 4.0, "centeredness_margin": 0.1, + "shift_exponent": 0.001, + }, + }, + "noise_builder": { + "token_a": "AAVE", "token_b": "ETH", + "pool_id": "0x9d1fcf346ea1b0", + }, + }, + "cow_eth_mainnet": { + "sweep_dir": os.path.join(_RESULTS_ROOT, "results", "cow_sweep"), + "output_dir": os.path.join(_RESULTS_ROOT, "results", "cow_sweep", "plots"), + "periods": { + "default_mainnet": ("2025-01-01", "2025-10-01", "2026-04-01"), + "recent_mainnet": ("2025-04-01", "2025-12-01", "2026-04-01"), + }, + "base_fp": { + "rule": "reclamm", + "tokens": ["COW", "ETH"], + "do_arb": True, + "arb_frequency": 3, + "fees": 0.003, + "gas_cost": 3.0, + "arb_fees": 0.0, + "protocol_fee_split": 0.25, + "noise_trader_ratio": 0.0, + "noise_model": "mm_observed", + "noise_arrays_path": "", # built dynamically + "reclamm_interpolation_method": "geometric", + "reclamm_centeredness_scaling": False, + "reclamm_learn_arc_length_speed": False, + "reclamm_use_shift_exponent": True, + "initial_pool_value": 600_000.0, + }, + "onchain_configs": { + "OnChain-mainnet": { + "price_ratio": 2.02, "centeredness_margin": 0.5, + "shift_exponent": 0.1, + }, + }, + "noise_builder": { + "token_a": "COW", "token_b": "ETH", + "pool_id": "0xd321300ef77067", + }, + "fname_suffix": "_mainnet", + }, + "cow_eth_base": { + "sweep_dir": os.path.join(_RESULTS_ROOT, "results", "cow_sweep"), + "output_dir": os.path.join(_RESULTS_ROOT, "results", "cow_sweep", "plots"), + "periods": { + "default_base": ("2025-01-01", "2025-10-01", "2026-04-01"), + "recent_base": ("2025-04-01", "2025-12-01", "2026-04-01"), + }, + "base_fp": { + "rule": "reclamm", + "tokens": ["COW", "ETH"], + "do_arb": True, + "arb_frequency": 3, + "fees": 0.003, + "gas_cost": 0.01, + "arb_fees": 0.0, + "protocol_fee_split": 0.25, + "noise_trader_ratio": 0.0, + "noise_model": "mm_observed", + "noise_arrays_path": "", + "reclamm_interpolation_method": "geometric", + "reclamm_centeredness_scaling": False, + "reclamm_learn_arc_length_speed": False, + "reclamm_use_shift_exponent": True, + "initial_pool_value": 500_000.0, + }, + "onchain_configs": { + "OnChain-base": { + "price_ratio": 3.30, "centeredness_margin": 0.5, + "shift_exponent": 0.1, + }, + }, + "noise_builder": { + "token_a": "COW", "token_b": "ETH", + "pool_id": "0xff028c1ec4559d", + }, + "fname_suffix": "_base", + }, +} + +# Active config — set by --pool arg in main() +SWEEP_DIR = POOL_CONFIGS["aave_eth"]["sweep_dir"] +OUTPUT_DIR = POOL_CONFIGS["aave_eth"]["output_dir"] +PERIODS = POOL_CONFIGS["aave_eth"]["periods"] +BASE_FP = POOL_CONFIGS["aave_eth"]["base_fp"] + +OBJ_SHORT = { + "daily_log_sharpe": "sharpe", + "daily_log_sharpe_excess": "excess_sharpe", + "fee_revenue_over_value": "fee_rev", + "returns_over_hodl": "ret_hodl", + "calmar": "calmar", + "sterling": "sterling", + "weekly_rovar": "rovar", +} + + +def _short_label(obj_name): + """Convert obj_name (possibly with robust/penalty suffix) to short label.""" + for full, short in OBJ_SHORT.items(): + if obj_name.startswith(full): + suffix = obj_name[len(full):] + if suffix: + suffix = suffix.replace("_robust", " r") + suffix = suffix.replace("_penalty", " p") + return f"{short}{suffix}" + return obj_name + +BG = "#162536" +TEXT_COLOR = "#E6CE97" +COLORS = [ + "#3498db", "#2ecc71", "#e74c3c", "#f39c12", "#9b59b6", + "#1abc9c", "#e67e22", "#2980b9", "#c0392b", "#8e44ad", + "#27ae60", "#d35400", "#16a085", "#f1c40f", "#7f8c8d", + "#e74c3c", "#3498db", "#2ecc71", "#f39c12", "#9b59b6", + "#1abc9c", "#e67e22", "#2980b9", "#c0392b", "#8e44ad", +] + + +ALLOWED_ROBUST = {""} +ALLOWED_PENALTY = {"", "5.0"} + + +def _parse_variant(obj_name): + """Extract (base_obj, robust, penalty) from an obj_name like 'calmar_robust0.5_penalty5.0'.""" + robust = "" + penalty = "" + rest = obj_name + m = re.search(r"_robust([\d.]+)", rest) + if m: + robust = m.group(1) + rest = rest[:m.start()] + rest[m.end():] + m = re.search(r"_penalty([\d.]+)", rest) + if m: + penalty = m.group(1) + rest = rest[:m.start()] + rest[m.end():] + return rest, robust, penalty + + +def load_sweep_results(): + """Load sweep result JSONs matching current sweep config, grouped by period.""" + results = {} + for path in sorted(glob.glob(os.path.join(SWEEP_DIR, "*.json"))): + fname = os.path.basename(path).replace(".json", "") + for period_name in PERIODS: + if fname.endswith(f"_{period_name}"): + obj_name = fname[: -(len(period_name) + 1)] + _, robust, penalty = _parse_variant(obj_name) + if robust not in ALLOWED_ROBUST or penalty not in ALLOWED_PENALTY: + break + with open(path) as f: + data = json.load(f) + if isinstance(data, dict): + params_raw = list(data.values())[0] + if isinstance(params_raw, dict): + params = { + k: v for k, v in params_raw.items() + if k in ("price_ratio", "centeredness_margin", + "shift_exponent", "arc_length_speed") + } + if period_name not in results: + results[period_name] = [] + results[period_name].append((obj_name, params)) + break + return results + + +_arrays_cache = {} + + +_active_noise_builder = POOL_CONFIGS["aave_eth"]["noise_builder"] +_active_onchain_configs = POOL_CONFIGS["aave_eth"]["onchain_configs"] + + +def _get_noise_arrays_path(start_date, end_date): + """Build or retrieve MM noise arrays for this date range.""" + key = (start_date, end_date) + if key in _arrays_cache: + return _arrays_cache[key] + + from quantammsim.calibration.noise_model_arrays import build_mm_simulator_arrays + + nb = _active_noise_builder + cache_dir = os.path.join( + os.path.dirname(os.path.dirname(__file__)), + "results", "mm_noise", "_sim_arrays") + os.makedirs(cache_dir, exist_ok=True) + arrays_path = os.path.join( + cache_dir, f"{nb['pool_id']}_{start_date}_{end_date}_mm.npz") + + if not os.path.exists(arrays_path): + print(f"\n Building noise arrays for {nb['pool_id']} {start_date}→{end_date}...", + end=" ", flush=True) + arrays = build_mm_simulator_arrays( + token_a=nb["token_a"], token_b=nb["token_b"], + start_date=start_date, end_date=end_date, + mm_artifact_dir="results/mm_noise", + competitor_tvl_path="results/competitor_tvl/competitor_tvl.npz", + pool_id=nb["pool_id"], + ) + np.savez(arrays_path, + noise_base=arrays["noise_base"], + competitor_tvl=arrays["competitor_tvl"]) + print("done") + + _arrays_cache[key] = arrays_path + return arrays_path + + +def run_config(params, start_date, end_date, end_test_date=None): + """Run a forward pass over start→end_test (or end if no test).""" + actual_end = end_test_date or end_date + arrays_path = _get_noise_arrays_path(start_date, actual_end) + + fp = dict(BASE_FP) + fp["startDateString"] = f"{start_date} 00:00:00" + fp["endDateString"] = f"{actual_end} 00:00:00" + fp["noise_arrays_path"] = arrays_path + jax_params = {k: jnp.array(v) for k, v in params.items()} + return do_run_on_historic_data(run_fingerprint=fp, params=jax_params) + + +def _style_axis(ax): + ax.set_facecolor(BG) + ax.tick_params(colors=TEXT_COLOR) + for spine in ax.spines.values(): + spine.set_color(TEXT_COLOR) + spine.set_alpha(0.3) + ax.spines["top"].set_visible(False) + ax.spines["right"].set_visible(False) + ax.grid(True, alpha=0.15, color=TEXT_COLOR) + + +def plot_period(period_name, obj_results, end_test_date, output_dir, top_n=None): + """Plot all objectives for one period.""" + start, train_end, default_test_end = PERIODS[period_name] + test_end = end_test_date or default_test_end + + print(f"\n{'='*80}") + print(f"Period: {period_name} ({start} → {train_end} → {test_end})") + print(f"{'='*80}") + + # On-chain baselines (from active pool config) + ONCHAIN_CONFIGS = _active_onchain_configs + + # Run all configs + baselines + runs = {} # label -> forward pass output + run_params = {} # label -> pool params dict + all_configs = ( + [(name, params) for name, params in ONCHAIN_CONFIGS.items()] + + [(f"{_short_label(obj)} (pr={p.get('price_ratio', 0):.2f})", p) + for obj, p in obj_results] + ) + for label, params in all_configs: + print(f" Running {label}...", end=" ", flush=True) + try: + out = run_config(params, start, train_end, test_end) + runs[label] = out + run_params[label] = params + fv = float(out["final_value"]) + hodl = float((out["reserves"][0] * out["prices"][-1]).sum()) + print(f"final=${fv:,.0f} RoH={fv/hodl - 1:+.2%}") + except Exception as e: + print(f"FAILED: {e}") + + if not runs: + print(" No successful runs!") + return + + # HODL baseline (compute before filtering so test-period RoH is available) + first_out = next(iter(runs.values())) + hodl_reserves = first_out["reserves"][0] + hodl_values = np.sum( + np.array(hodl_reserves) * np.array(first_out["prices"]), axis=1) + + n_minutes = len(first_out["value"]) + start_dt = datetime.strptime(f"{start} 00:00:00", "%Y-%m-%d %H:%M:%S") + train_end_dt = datetime.strptime(f"{train_end} 00:00:00", "%Y-%m-%d %H:%M:%S") + train_minutes = int((train_end_dt - start_dt).total_seconds() / 60) + test_start_idx = min(train_minutes, n_minutes - 1) + + # Filter to top N by test-period normalised return + if top_n is not None and top_n < len(runs): + def _test_return(label): + vals = np.array(runs[label]["value"]) + if test_start_idx >= len(vals) - 1: + return float("-inf") + return vals[-1] / vals[test_start_idx] - 1.0 + + ranked = sorted(runs.keys(), key=_test_return, reverse=True) + keep = set(ranked[:top_n]) + keep.update(l for l in runs if l.startswith("OnChain")) + runs = {l: runs[l] for l in runs if l in keep} + run_params = {l: run_params[l] for l in run_params if l in keep} + print(f" Filtered to top {top_n} (test-period return) + baselines ({len(runs)} configs)") + dates = pd.date_range(start=start_dt, periods=n_minutes, freq="1min") + step = 1440 + dates_daily = dates[::step] + + # ── Plot 1: Full period value ── + fig, axes = plt.subplots(2, 1, figsize=(16, 10), sharex=True, + gridspec_kw={"height_ratios": [3, 1]}) + + ax = axes[0] + for ci, (label, out) in enumerate(runs.items()): + vals = np.array(out["value"][::step]) / 1e6 + is_baseline = label.startswith("OnChain") + ax.plot(dates_daily[:len(vals)], vals, + linewidth=1.5 if is_baseline else 1.8, + linestyle="--" if is_baseline else "-", + alpha=0.7 if is_baseline else 1.0, + color=COLORS[ci % len(COLORS)], label=label) + + hodl_daily = hodl_values[::step] / 1e6 + ax.plot(dates_daily[:len(hodl_daily)], hodl_daily, linewidth=2, + color="white", alpha=0.7, linestyle="--", label="HODL") + + ax.axvline(x=train_end_dt, color="white", linestyle=":", alpha=0.5) + _style_axis(ax) + ax.set_ylabel("Pool Value ($M)", color=TEXT_COLOR) + tokens_str = "/".join(BASE_FP["tokens"]) + ax.set_title(f"reClAMM {tokens_str} — {period_name}", + color=TEXT_COLOR, fontsize=14, pad=10) + ax.legend(loc="upper left", fontsize=7, facecolor=BG, + edgecolor=TEXT_COLOR, labelcolor=TEXT_COLOR, ncol=2) + + # Fee revenue + ax = axes[1] + for ci, (label, out) in enumerate(runs.items()): + fr = out.get("fee_revenue") + if fr is None: + continue + cumfee = np.cumsum(np.array(fr))[::step] / 1e3 + is_baseline = label.startswith("OnChain") + ax.plot(dates_daily[:len(cumfee)], cumfee, + linewidth=1.2 if is_baseline else 1.5, + linestyle="--" if is_baseline else "-", + alpha=0.7 if is_baseline else 1.0, + color=COLORS[ci % len(COLORS)], label=label) + + ax.axvline(x=train_end_dt, color="white", linestyle=":", alpha=0.5) + _style_axis(ax) + ax.set_ylabel("Cum. Fee Revenue ($K)", color=TEXT_COLOR) + ax.set_xlabel("Date", color=TEXT_COLOR) + + fig.patch.set_facecolor(BG) + plt.tight_layout() + top_suffix = f"_top{top_n}" if top_n else "" + out_path = os.path.join(output_dir, f"sweep_{period_name}_value{top_suffix}.png") + fig.savefig(out_path, dpi=200, bbox_inches="tight", facecolor=BG) + plt.close(fig) + print(f" Saved: {out_path}") + + # ── Plot 2: Test-only normalised value + cumulative fee revenue ── + test_start = test_start_idx + + fig, axes = plt.subplots(2, 1, figsize=(16, 10), sharex=True, + gridspec_kw={"height_ratios": [3, 1]}) + ax = axes[0] + test_dates = dates[test_start::step] + + for ci, (label, out) in enumerate(runs.items()): + vals = np.array(out["value"]) + test_vals = vals[test_start::step] + if len(test_vals) < 2: + continue + normed = test_vals / test_vals[0] + is_baseline = label.startswith("OnChain") + ax.plot(test_dates[:len(normed)], normed, + linewidth=1.5 if is_baseline else 1.8, + linestyle="--" if is_baseline else "-", + alpha=0.7 if is_baseline else 1.0, + color=COLORS[ci % len(COLORS)], label=label) + + hodl_test = hodl_values[test_start::step] + if len(hodl_test) > 1: + ax.plot(test_dates[:len(hodl_test)], hodl_test / hodl_test[0], + linewidth=2, color="white", alpha=0.7, linestyle="--", + label="HODL") + + ax.axhline(1.0, color="white", linestyle=":", alpha=0.3) + _style_axis(ax) + ax.set_title(f"Test Period (normalised) — {period_name}", + color=TEXT_COLOR, fontsize=14, pad=10) + ax.set_ylabel("Normalised Value", color=TEXT_COLOR) + ax.legend(loc="best", fontsize=7, facecolor=BG, + edgecolor=TEXT_COLOR, labelcolor=TEXT_COLOR, ncol=2) + + # Cumulative fee revenue (test period only) + ax = axes[1] + for ci, (label, out) in enumerate(runs.items()): + fr = out.get("fee_revenue") + if fr is None: + continue + fr = np.array(fr) + test_fr = fr[test_start:] + cumfee = np.cumsum(test_fr)[::step] / 1e3 + is_baseline = label.startswith("OnChain") + ax.plot(test_dates[:len(cumfee)], cumfee, + linewidth=1.2 if is_baseline else 1.5, + linestyle="--" if is_baseline else "-", + alpha=0.7 if is_baseline else 1.0, + color=COLORS[ci % len(COLORS)], label=label) + + _style_axis(ax) + ax.set_ylabel("Cum. Fee Revenue ($K)", color=TEXT_COLOR) + ax.set_xlabel("Date", color=TEXT_COLOR) + + fig.patch.set_facecolor(BG) + plt.tight_layout() + out_path = os.path.join(output_dir, f"sweep_{period_name}_test{top_suffix}.png") + fig.savefig(out_path, dpi=200, bbox_inches="tight", facecolor=BG) + plt.close(fig) + print(f" Saved: {out_path}") + + # ── Summary table ── + print(f"\n {'Objective':<25s} {'PR':>8s} {'Margin':>7s} {'ShiftExp':>9s}" + f" {'Final $M':>10s} {'HODL $M':>10s} {'RoH':>8s} {'Fee $K':>8s}") + for label, out in runs.items(): + fv = float(out["final_value"]) / 1e6 + hodl_end = float(hodl_values[-1]) / 1e6 + roh = float(out["final_value"]) / float(hodl_values[-1]) - 1 + fr = float(np.array(out.get("fee_revenue", [0])).sum()) / 1e3 + p = run_params.get(label, {}) + pr = p.get("price_ratio", float("nan")) + margin = p.get("centeredness_margin", float("nan")) + se = p.get("shift_exponent", float("nan")) + print(f" {label:<25s} {pr:>8.2f} {margin:>7.3f} {se:>9.4g}" + f" ${fv:>9.2f} ${hodl_end:>9.2f} {roh:>+7.1%} ${fr:>7.0f}") + + +def main(): + parser = argparse.ArgumentParser(description=__doc__, + formatter_class=argparse.RawDescriptionHelpFormatter) + parser.add_argument("--pool", default="aave_eth", + choices=list(POOL_CONFIGS.keys()), + help="Pool config to use (default: aave_eth)") + parser.add_argument("--end-test-date", default=None, + help="Override test end date for all periods") + parser.add_argument("--output-dir", default=None) + parser.add_argument("--periods", nargs="+", default=None, + help="Only plot these periods (default: all)") + parser.add_argument("--top", type=int, default=None, + help="Only plot top N configs by test-period RoH") + args = parser.parse_args() + + # Set active pool config + global SWEEP_DIR, OUTPUT_DIR, PERIODS, BASE_FP + global _active_noise_builder, _active_onchain_configs + pc = POOL_CONFIGS[args.pool] + SWEEP_DIR = pc["sweep_dir"] + OUTPUT_DIR = pc["output_dir"] + PERIODS = pc["periods"] + BASE_FP = pc["base_fp"] + _active_noise_builder = pc["noise_builder"] + _active_onchain_configs = pc["onchain_configs"] + + output_dir = args.output_dir or OUTPUT_DIR + os.makedirs(output_dir, exist_ok=True) + + results = load_sweep_results() + print(f"Loaded {sum(len(v) for v in results.values())} results" + f" across {len(results)} periods (pool={args.pool})") + + for period_name in sorted(results.keys()): + if args.periods and period_name not in args.periods: + continue + plot_period(period_name, results[period_name], + args.end_test_date, output_dir, top_n=args.top) + + +if __name__ == "__main__": + main() diff --git a/scripts/plot_top50_predicted_vs_real.py b/scripts/plot_top50_predicted_vs_real.py new file mode 100644 index 0000000..0951a20 --- /dev/null +++ b/scripts/plot_top50_predicted_vs_real.py @@ -0,0 +1,521 @@ +"""Plot predicted vs real volume for top 50 pools by TVL on Feb 1st 2026. + +Enumerates WEIGHTED (min_tvl=1000) and RECLAMM (min_tvl=0) pools, +fetches their snapshots, filters to those with TVL >= $10k on Feb 1st 2026, +takes the top 50 by TVL, and plots predicted vs actual daily volume using +the inference artifact from calibrate_noise_unified.py. + +For pools that were in the model's training set, uses their per-pool theta. +For pools not in the training set, uses population-level prediction from B. +""" + +import ast +import json +import os +import sys +import time +from datetime import date, timedelta + +import matplotlib +matplotlib.use("Agg") +import matplotlib.pyplot as plt +import numpy as np +import pandas as pd + +# Reuse functions from the noise calibration package +from quantammsim.noise_calibration import ( + BALANCER_API_CHAINS, + _graphql_request, + assemble_panel, + classify_token_tier, + encode_covariates, + fetch_pool_snapshots, + fetch_token_prices, +) + + +CACHE_DIR = os.path.join( + os.path.dirname(os.path.dirname(__file__)), + "local_data", "noise_top50" +) +TVL_DATE = date(2026, 2, 1) +OUTPUT_DIR = os.path.join( + os.path.dirname(os.path.dirname(__file__)), + "results", "top50_feb1" +) +# Inference artifact from the main unified model run +FITTED_JSON = os.path.join( + os.path.dirname(os.path.dirname(__file__)), + "results", "unified_full_90d.json" +) + + +def enumerate_all_pools(): + """Enumerate WEIGHTED (min_tvl=1000) and RECLAMM (min_tvl=0) pools.""" + all_pools = [] + + for chain in BALANCER_API_CHAINS: + for pool_type, min_tvl in [("WEIGHTED", 1000), ("RECLAMM", 0)]: + query = { + "query": """ + query GetPools($chain: GqlChain!, $types: [GqlPoolType!], + $minTvl: Float) { + poolGetPools( + where: { chainIn: [$chain], poolTypeIn: $types, minTvl: $minTvl } + ) { + id chain type protocolVersion + poolTokens { symbol weight address } + dynamicData { totalLiquidity swapFee } + } + } + """, + "variables": { + "chain": chain, + "types": [pool_type], + "minTvl": min_tvl, + }, + } + + try: + body = _graphql_request(query) + pools = body.get("data", {}).get("poolGetPools", []) + except Exception as e: + print(f" FAILED {chain} {pool_type}: {e}") + continue + + for p in pools: + tokens = [t["symbol"] for t in p.get("poolTokens", [])] + addresses = [t.get("address", "") for t in p.get("poolTokens", [])] + tvl = float(p.get("dynamicData", {}).get("totalLiquidity", 0)) + fee = float(p.get("dynamicData", {}).get("swapFee", 0)) + all_pools.append({ + "pool_id": p["id"], + "chain": p["chain"], + "pool_type": p["type"], + "tokens": tokens, + "token_addresses": addresses, + "swap_fee": fee, + "current_tvl": tvl, + }) + + if pools: + print(f" {chain:>10} {pool_type:>10}: {len(pools)}") + time.sleep(0.3) + + df = pd.DataFrame(all_pools) + print(f"\n Total: {len(df)} pools") + return df + + +def fetch_all_snapshots_cached(pools_df, cache_dir): + """Fetch snapshots for all pools, caching per-pool.""" + snap_dir = os.path.join(cache_dir, "snapshots") + os.makedirs(snap_dir, exist_ok=True) + + all_snaps = [] + n = len(pools_df) + + for i, (_, pool) in enumerate(pools_df.iterrows()): + pid = pool["pool_id"] + chain = pool["chain"] + cache_file = os.path.join(snap_dir, f"{pid}.parquet") + + if os.path.exists(cache_file): + df = pd.read_parquet(cache_file) + else: + if (i + 1) % 20 == 0 or i == 0: + print(f" Fetching snapshots {i+1}/{n}...", flush=True) + try: + df = fetch_pool_snapshots(pid, chain) + if len(df) > 0: + df.to_parquet(cache_file, index=False) + time.sleep(0.3) + except Exception as e: + print(f" FAILED {pid[:20]}: {e}") + continue + + if len(df) > 0: + df["pool_id"] = pid + df["chain"] = chain + all_snaps.append(df) + + if all_snaps: + return pd.concat(all_snaps, ignore_index=True) + return pd.DataFrame() + + +def get_tvl_on_date(snapshots_df, target_date, window_days=3): + """Get TVL for each pool on/near target_date.""" + results = [] + for pid in snapshots_df["pool_id"].unique(): + pool_snaps = snapshots_df[snapshots_df["pool_id"] == pid] + + best_row = None + best_dist = float("inf") + for _, row in pool_snaps.iterrows(): + d = row["date"] + if isinstance(d, date): + dist = abs((d - target_date).days) + else: + dist = abs((pd.Timestamp(d).date() - target_date).days) + if dist < best_dist: + best_dist = dist + best_row = row + + if best_row is not None and best_dist <= window_days: + results.append({ + "pool_id": pid, + "tvl_feb1": float(best_row["total_liquidity_usd"]), + "date_used": best_row["date"], + }) + + return pd.DataFrame(results) + + +def _get_theta_for_pool(pid, fitted, panel_90d, pop_B, pop_cov_names): + """Get theta for a pool: from fitted artifact if available, else population. + + Returns (theta, source) where source is 'fitted' or 'population'. + For IBP models, population fallback adds marginal feature effect (pi @ W). + """ + if pid in fitted["pools"]: + return np.array(fitted["pools"][pid]["theta_median"]), "fitted" + + # Population-level prediction: theta = B @ z_pool + # Build z_pool from the pool's covariates + pp = panel_90d[panel_90d["pool_id"] == pid] + if len(pp) == 0: + return None, "no_data" + + chain = pp["chain"].iloc[0] + tokens = pp["tokens"].iloc[0] + if isinstance(tokens, str): + tokens = tokens.split(",") + fee = pp["swap_fee"].iloc[0] if "swap_fee" in pp.columns else 0.003 + tiers = sorted([classify_token_tier(t) for t in tokens]) + tier_a = tiers[0] + + # Build covariate vector matching the model's encoding + z = np.zeros(len(pop_cov_names)) + for i, name in enumerate(pop_cov_names): + if name == "intercept": + z[i] = 1.0 + elif name == f"chain_{chain}": + z[i] = 1.0 + elif name == f"tier_A_{tier_a}": + z[i] = 1.0 + elif name == "log_fee": + z[i] = np.log(max(fee, 1e-6)) + + # B is (K_coeff, K_cov), theta = B @ z + B = np.array(pop_B) # (K_coeff, K_cov) + theta = B @ z + + # IBP: add marginal feature effect (pi @ W) + pop = fitted["population_effects"] + if "W" in pop and "feature_prevalences" in pop: + W = np.array(pop["W"]) # (K_features, K_coeff) + pi = np.array(pop["feature_prevalences"]) # (K_features,) + theta = theta + pi @ W + + return theta, "population" + + +def plot_pages(plot_pools, pool_idx_map_fitted, fitted, panel_90d, + pools_df, tvl_lookup, pop_B, pop_cov_names, + output_dir=OUTPUT_DIR): + """Generate paginated plots, 10 pools per page.""" + n_pools = len(plot_pools) + per_page = 10 + n_pages = (n_pools + per_page - 1) // per_page + + for page in range(n_pages): + start = page * per_page + end = min(start + per_page, n_pools) + page_pools = plot_pools[start:end] + n_this = len(page_pools) + + ncols = 2 + nrows = (n_this + ncols - 1) // ncols + fig, axes = plt.subplots(nrows, ncols, figsize=(14, 4 * nrows)) + if nrows == 1 and ncols == 1: + axes = np.array([[axes]]) + elif nrows == 1: + axes = axes.reshape(1, -1) + + for idx, (pid, feb_tvl) in enumerate(page_pools): + ax = axes[idx // ncols][idx % ncols] + + theta, source = _get_theta_for_pool( + pid, fitted, panel_90d, pop_B, pop_cov_names + ) + if theta is None: + ax.set_visible(False) + continue + + pp = panel_90d[panel_90d["pool_id"] == pid].sort_values("date") + if len(pp) < 5: + ax.set_visible(False) + continue + + x_obs = np.column_stack([ + np.ones(len(pp)), + pp["log_tvl_lag1"].values, + pp["volatility"].values, + pp["weekend"].values, + ]) + + pred_log = x_obs @ theta + actual_log = pp["log_volume"].values + pred_vol = np.exp(pred_log) + actual_vol = np.exp(actual_log) + dates = pd.to_datetime(pp["date"].values) + + ax.plot(dates, actual_vol, "o-", color="steelblue", markersize=2.5, + linewidth=0.9, alpha=0.7, label="Actual") + ax.plot(dates, pred_vol, "s--", color="orangered", markersize=2.5, + linewidth=0.9, alpha=0.7, label="Predicted") + ax.set_yscale("log") + ax.set_ylabel("Daily volume (USD)", fontsize=8) + + ss_res = np.sum((actual_log - pred_log) ** 2) + ss_tot = np.sum((actual_log - actual_log.mean()) ** 2) + r2 = 1 - ss_res / ss_tot if ss_tot > 0 else float("nan") + + meta = pools_df[pools_df["pool_id"] == pid] + if len(meta) > 0: + m = meta.iloc[0] + tokens = m["tokens"] + tok_str = "/".join(str(t)[:8] for t in tokens[:2]) + chain = str(m["chain"]) + ptype = str(m["pool_type"]) + else: + tok_str = pid[:16] + chain = "?" + ptype = "?" + + type_tag = "R" if ptype == "RECLAMM" else "W" + src_tag = "*" if source == "population" else "" + ax.set_title( + "{} ({}, {}){}\n" + "TVL ${:,.0f} on Feb 1 | " + "R\u00b2={:.3f} b_c={:.2f} b_\u03c3={:.2f} " + "b_wknd={:.2f} n={}".format( + tok_str, chain, type_tag, src_tag, feb_tvl, + r2, theta[1], theta[2], theta[3], len(pp)), + fontsize=8) + ax.legend(fontsize=7) + ax.tick_params(labelsize=7) + ax.tick_params(axis="x", rotation=30) + + for idx in range(n_this, nrows * ncols): + axes[idx // ncols][idx % ncols].set_visible(False) + + fig.suptitle( + "Predicted vs actual daily volume \u2014 page {}/{} " + "(sorted by TVL on {}) [* = population prediction]".format( + page + 1, n_pages, TVL_DATE), + fontsize=11) + fig.tight_layout() + out = os.path.join(output_dir, "pred_vs_real_page{}.png".format(page + 1)) + fig.savefig(out, dpi=150, bbox_inches="tight") + plt.close(fig) + print(" Saved: {}".format(out)) + + +def main(): + import argparse + parser = argparse.ArgumentParser(description=__doc__) + parser.add_argument("--artifact", default=FITTED_JSON, + help="Path to inference artifact JSON") + parser.add_argument("--output-dir", default=None, + help="Output directory (default: auto from model name)") + args = parser.parse_args() + + artifact_path = args.artifact + os.makedirs(CACHE_DIR, exist_ok=True) + + # ---- Load inference artifact ---- + print(f"Loading inference artifact: {artifact_path}") + with open(artifact_path) as f: + fitted = json.load(f) + n_fitted = len(fitted["pools"]) + model_name = fitted.get("model", "unknown") + print(f" Model: {model_name}") + print(f" {n_fitted} pools with fitted theta") + + # Output dir: use CLI override or auto from model name + output_dir = args.output_dir or os.path.join( + os.path.dirname(os.path.dirname(__file__)), + "results", f"top50_feb1_{model_name}", + ) + os.makedirs(output_dir, exist_ok=True) + + # Extract population-level B matrix for pools not in the model + pop_cov_names = fitted["model_spec"]["covariate_names"] + pop_B = np.array(fitted["population_effects"]["B"]) # (K_coeff, K_cov) + print(f" Population B: {pop_B.shape}, covariates: {pop_cov_names}") + + # ---- Step 1: Enumerate pools ---- + pools_cache = os.path.join(CACHE_DIR, "pools.parquet") + if os.path.exists(pools_cache): + pools_df = pd.read_parquet(pools_cache) + if isinstance(pools_df["tokens"].iloc[0], str): + pools_df["tokens"] = pools_df["tokens"].apply(ast.literal_eval) + pools_df["token_addresses"] = pools_df["token_addresses"].apply( + ast.literal_eval + ) + print(f"\nLoaded {len(pools_df)} pools from cache") + else: + print("\n1. Enumerating pools...") + pools_df = enumerate_all_pools() + pools_df.to_parquet(pools_cache, index=False) + + # ---- Step 2: Fetch snapshots ---- + print("\n2. Fetching snapshots...") + snapshots_df = fetch_all_snapshots_cached(pools_df, CACHE_DIR) + print(f" {len(snapshots_df)} pool-days") + + # ---- Step 3: TVL on Feb 1st ---- + print(f"\n3. Finding TVL on {TVL_DATE}...") + tvl_df = get_tvl_on_date(snapshots_df, TVL_DATE) + tvl_df = tvl_df[tvl_df["tvl_feb1"] >= 10_000].copy() + tvl_df = tvl_df.sort_values("tvl_feb1", ascending=False).head(50) + top50_ids = set(tvl_df["pool_id"]) + tvl_lookup = dict(zip(tvl_df["pool_id"], tvl_df["tvl_feb1"])) + print(f" {len(tvl_df)} pools with TVL >= $10k") + + # How many are in the fitted model? + n_in_model = sum(1 for pid in top50_ids if pid in fitted["pools"]) + print(f" {n_in_model} in fitted model, " + f"{len(top50_ids) - n_in_model} will use population prediction") + + # ---- Step 4: Fetch token prices & assemble panel ---- + panel_cache = os.path.join(CACHE_DIR, "panel.parquet") + if os.path.exists(panel_cache): + panel = pd.read_parquet(panel_cache) + print(f"\n4. Loaded panel from cache: {len(panel)} obs") + else: + top50_pools = pools_df[pools_df["pool_id"].isin(top50_ids)].copy() + top50_snaps = snapshots_df[snapshots_df["pool_id"].isin(top50_ids)].copy() + + print("\n4. Fetching token prices...") + prices_cache = os.path.join(CACHE_DIR, "token_prices") + token_addr_by_chain = {} + for _, pool in top50_pools.iterrows(): + chain = pool["chain"] + tokens = pool["tokens"] + addresses = pool["token_addresses"] + if chain not in token_addr_by_chain: + token_addr_by_chain[chain] = {} + for sym, addr in zip(tokens, addresses): + if sym and addr: + token_addr_by_chain[chain][sym] = addr + + token_prices = fetch_token_prices( + token_addr_by_chain, cache_dir=prices_cache + ) + + print("\n Assembling panel...") + panel = assemble_panel(top50_pools, top50_snaps, token_prices) + panel.to_parquet(panel_cache, index=False) + + # ---- Step 5: Filter to 90 days ---- + max_date = panel["date"].max() + if not isinstance(max_date, date): + max_date = pd.Timestamp(max_date).date() + cutoff = max_date - timedelta(days=90) + panel_90d = panel[ + panel["date"].apply( + lambda d: d >= cutoff if isinstance(d, date) + else pd.Timestamp(d).date() >= cutoff + ) + ].copy() + + if "log_tvl_lag1" not in panel_90d.columns: + panel_90d = panel_90d.sort_values(["pool_id", "date"]).reset_index(drop=True) + panel_90d["log_tvl_lag1"] = panel_90d.groupby("pool_id")["log_tvl"].shift(1) + panel_90d = panel_90d.dropna(subset=["log_tvl_lag1"]).reset_index(drop=True) + + pool_counts = panel_90d.groupby("pool_id").size() + valid_pools = pool_counts[pool_counts >= 10].index + panel_90d = panel_90d[panel_90d["pool_id"].isin(valid_pools)].copy() + print(f"\n5. 90-day panel: {len(panel_90d)} obs, " + f"{panel_90d['pool_id'].nunique()} pools") + + # ---- Step 6: Plot ---- + # Sort by Feb 1 TVL, only include pools with panel data + plot_pools = [] + for pid in panel_90d["pool_id"].unique(): + if pid in tvl_lookup: + plot_pools.append((pid, tvl_lookup[pid])) + plot_pools.sort(key=lambda x: -x[1]) + print(f"\n6. Plotting {len(plot_pools)} pools...") + + plot_pages(plot_pools, fitted, fitted, panel_90d, pools_df, + tvl_lookup, pop_B, pop_cov_names, output_dir=output_dir) + + # ---- Summary table ---- + summary = [] + for pid, feb_tvl in plot_pools: + theta, source = _get_theta_for_pool( + pid, fitted, panel_90d, pop_B, pop_cov_names + ) + if theta is None: + continue + pp = panel_90d[panel_90d["pool_id"] == pid] + x = np.column_stack([ + np.ones(len(pp)), + pp["log_tvl_lag1"].values, + pp["volatility"].values, + pp["weekend"].values, + ]) + pred = x @ theta + actual = pp["log_volume"].values + ss_res = np.sum((actual - pred) ** 2) + ss_tot = np.sum((actual - actual.mean()) ** 2) + r2 = 1 - ss_res / ss_tot if ss_tot > 0 else float("nan") + + meta = pools_df[pools_df["pool_id"] == pid] + if len(meta) > 0: + m = meta.iloc[0] + tok_str = "/".join(str(t) for t in m["tokens"][:2]) + chain = str(m["chain"]) + ptype = str(m["pool_type"]) + else: + tok_str = pid[:16] + chain = "?" + ptype = "?" + + summary.append({ + "pool_id": pid[:20], + "tokens": tok_str, + "chain": chain, + "type": ptype, + "tvl_feb1": feb_tvl, + "n_obs": len(pp), + "R2": r2, + "b_c": theta[1], + "b_sigma": theta[2], + "b_weekend": theta[3], + "source": source, + }) + + summary_df = pd.DataFrame(summary) + summary_path = os.path.join(output_dir, "top50_summary.csv") + summary_df.to_csv(summary_path, index=False) + print(f"\n Saved: {summary_path}") + + n_pools = len(summary_df) + n_fitted_used = (summary_df["source"] == "fitted").sum() + n_pop = (summary_df["source"] == "population").sum() + n_reclamm = (summary_df["type"] == "RECLAMM").sum() + print(f"\n{'='*70}") + print(f"Summary: {n_pools} pools ({n_fitted_used} fitted, {n_pop} population)") + print(f" RECLAMM: {n_reclamm} WEIGHTED: {n_pools - n_reclamm}") + print(f" Median R\u00b2: {summary_df['R2'].median():.3f}") + print(f" Mean b_c: {summary_df['b_c'].mean():.3f}") + + +if __name__ == "__main__": + main() diff --git a/scripts/prepare_bold_usdc_data.py b/scripts/prepare_bold_usdc_data.py new file mode 100644 index 0000000..bb547dc --- /dev/null +++ b/scripts/prepare_bold_usdc_data.py @@ -0,0 +1,154 @@ +"""Prepare price data for BOLD/USDC reCLAMM simulations. + +1. Fetches BOLD/USD daily price + traded volume from CoinGecko (free tier = + last 365 days), expands to a minute grid by forward-fill, and writes + quantammsim/data/BOLD_USD.parquet. The real daily traded volume is spread + evenly across the day so that market_features' daily resample recovers the + true daily USD volume (used by the organic-volume noise model). + +2. Rebuilds quantammsim/data/USDC_USD.parquet pegged at $1.00 over the union + of its existing coverage and the BOLD grid, so both the BOLD/USDC window + and the earlier Monad window stay covered. + +BOLD (Liquity V2, CoinGecko id "liquity-bold-2") is not on Binance/Coinbase, +so CoinGecko is the only source. Free-tier history is capped at 365 days — +this bounds the earliest possible simulation start date. + +Usage (from repo root): + python scripts/prepare_bold_usdc_data.py +""" + +from __future__ import annotations + +import time +from pathlib import Path + +import numpy as np +import pandas as pd +import requests + +REPO_ROOT = Path(__file__).resolve().parent.parent +DATA_DIR = REPO_ROOT / "quantammsim" / "data" + +CG_ID = "liquity-bold-2" +_CG_URL = f"https://api.coingecko.com/api/v3/coins/{CG_ID}/market_chart" + + +def _fetch_coingecko_daily() -> tuple[list, list]: + """Return (prices, total_volumes) as [[unix_ms, value], ...] for last 365d.""" + params = {"vs_currency": "usd", "days": "365", "interval": "daily"} + for attempt in range(4): + try: + resp = requests.get(_CG_URL, params=params, timeout=40) + if resp.status_code == 429: + print(" rate-limited, sleeping 60s …") + time.sleep(60) + continue + resp.raise_for_status() + j = resp.json() + return j.get("prices", []), j.get("total_volumes", []) + except Exception as exc: + print(f" CoinGecko attempt {attempt+1} failed: {exc}") + time.sleep(10) + raise RuntimeError("CoinGecko fetch failed for BOLD") + + +def build_bold_parquet() -> pd.DataFrame: + prices, volumes = _fetch_coingecko_daily() + if len(prices) < 100: + raise RuntimeError(f"BOLD price series too short: {len(prices)}") + + # Daily frame: price (close) + daily USD volume, indexed by midnight-UTC day. + pdf = pd.DataFrame(prices, columns=["unix", "close"]) + vdf = pd.DataFrame(volumes, columns=["unix", "vol_usd"]) + pdf["day"] = pd.to_datetime(pdf["unix"], unit="ms", utc=True).dt.normalize() + vdf["day"] = pd.to_datetime(vdf["unix"], unit="ms", utc=True).dt.normalize() + daily = (pdf.groupby("day")["close"].last() + .to_frame() + .join(vdf.groupby("day")["vol_usd"].last())) + daily["vol_usd"] = daily["vol_usd"].fillna(0.0) + daily = daily.dropna(subset=["close"]).sort_index() + + start = daily.index.min() + end = daily.index.max() + print(f" BOLD daily: {start.date()} → {end.date()} ({len(daily)} days)") + print(f" price ${daily['close'].min():.4f}–${daily['close'].max():.4f}, " + f"last ${daily['close'].iloc[-1]:.4f}") + print(f" daily vol (USD): median ${daily['vol_usd'].median():,.0f}, " + f"mean ${daily['vol_usd'].mean():,.0f}") + + # Expand to a complete minute grid covering full days [start, end+1day). + epoch = pd.Timestamp("1970-01-01", tz="UTC") + grid_end = end + pd.Timedelta(days=1) - pd.Timedelta(minutes=1) + min_range = pd.date_range(start=start, end=grid_end, freq="1min") + min_idx = ((min_range - epoch).total_seconds() * 1000).astype(np.int64) + + out = pd.DataFrame(index=min_idx) + out.index.name = "unix" + out["date"] = min_range + out["day"] = min_range.normalize() + + # Map close + per-minute volume (daily vol spread across 1440 minutes so the + # daily resample-sum in market_features recovers the real daily volume). + close_map = daily["close"].to_dict() + vol_map = (daily["vol_usd"] / 1440.0).to_dict() + out["close"] = out["day"].map(close_map).astype(float) + out["Volume USD"] = out["day"].map(vol_map).astype(float) + out["close"] = out["close"].ffill().bfill() + out["Volume USD"] = out["Volume USD"].fillna(0.0) + out["open"] = out["high"] = out["low"] = out["close"] + out["Volume BOLD"] = out["Volume USD"] / out["close"].clip(lower=1e-9) + out["symbol"] = "BOLD" + out = out.drop(columns=["day"]) + out = out[["date", "symbol", "open", "high", "low", "close", + "Volume USD", "Volume BOLD"]] + + path = DATA_DIR / "BOLD_USD.parquet" + out.to_parquet(path, engine="pyarrow") + print(f" Saved {len(out):,} rows → {path}") + return out + + +def rebuild_usdc_parquet(bold_df: pd.DataFrame) -> None: + """USDC pegged at $1.00 over the union of its current grid and BOLD's.""" + path = DATA_DIR / "USDC_USD.parquet" + start_ms = int(bold_df.index.min()) + end_ms = int(bold_df.index.max()) + if path.exists(): + existing = pd.read_parquet(path) + start_ms = min(start_ms, int(existing.index.min())) + end_ms = max(end_ms, int(existing.index.max())) + + idx = np.arange(start_ms, end_ms + 60_000, 60_000, dtype=np.int64) + n = len(idx) + out = pd.DataFrame({ + "date": pd.to_datetime(idx, unit="ms", utc=True), + "symbol": "USDC", + "open": np.ones(n), "high": np.ones(n), + "low": np.ones(n), "close": np.ones(n), + "Volume USD": np.zeros(n), "Volume USDC": np.zeros(n), + }, index=idx) + out.index.name = "unix" + out.to_parquet(path, engine="pyarrow") + print(f" Saved {n:,} rows → {path} (peg $1.00, " + f"{out['date'].iloc[0].date()} → {out['date'].iloc[-1].date()})") + + +def main() -> None: + DATA_DIR.mkdir(parents=True, exist_ok=True) + print("Fetching BOLD/USD from CoinGecko …") + bold = build_bold_parquet() + print("Rebuilding USDC peg parquet …") + rebuild_usdc_parquet(bold) + + print("\n--- Data summary ---") + for token in ["BOLD", "USDC"]: + df = pd.read_parquet(DATA_DIR / f"{token}_USD.parquet") + dates = pd.to_datetime(df.index, unit="ms", utc=True) + print(f" {token:5s}: {dates.min().date()} → {dates.max().date()} " + f"({len(df):,} rows) close=[{df['close'].min():.4f}, " + f"{df['close'].max():.4f}]") + + +if __name__ == "__main__": + main() diff --git a/scripts/reclamm/compare_reclamm_geometric_noise_runs.py b/scripts/reclamm/compare_reclamm_geometric_noise_runs.py new file mode 100644 index 0000000..240482b --- /dev/null +++ b/scripts/reclamm/compare_reclamm_geometric_noise_runs.py @@ -0,0 +1,679 @@ +"""Compare two geometric reCLAMM runs against matched arb-only baselines. + +This script reuses the same AAVE/ETH reCLAMM fingerprint and parameter wiring as +``compare_reclamm_thermostats.py``, but runs only the geometric interpolation +mode and plots: +1. Share price / TVL over time in absolute USD terms +2. Pool weights over time +3. Estimated gross swap volume over time +4. Noise-model improvement over arb-only over time + +Because these runs use no LP supply changes, share price and TVL are the same +series here. + +Usage: + python scripts/reclamm/compare_reclamm_geometric_noise_runs.py + python scripts/reclamm/compare_reclamm_geometric_noise_runs.py \ + --adjacent-csv scripts/results/...csv --adjacent-row-index 0 +""" + +from __future__ import annotations + +import argparse +import importlib.util +import json +from pathlib import Path +from typing import Mapping, Optional, Sequence + +import numpy as np +import pandas as pd + + +DEFAULT_SOURCE_HEATMAP_DESCRIPTION = ( + "market_linear price_ratio_vs_margin heatmap with shift_exp fixed at 0.10" +) +DEFAULT_RUN_SPECS = [ + { + "name": "Green cell near price_ratio 1.31", + "price_ratio": 1.31, + "centeredness_margin": 0.6763157894736842, + "daily_price_shift_exponent": 0.10, + "tvl_usd": 1_000_000.0, + "color": "C0", + "reason": ( + "Geometric noise-model run taken from the positive cell in the " + "market_linear price_ratio-vs-margin heatmap at price_ratio=1.31 and the " + "lower adjacent centeredness row." + ), + }, + { + "name": "Red cell near price_ratio 1.31", + "price_ratio": 1.31, + "centeredness_margin": 0.7210526315789474, + "daily_price_shift_exponent": 0.10, + "tvl_usd": 1_000_000.0, + "color": "C1", + "reason": ( + "Geometric noise-model run taken from the negative cell directly " + "above the green cell in the market_linear price_ratio-vs-margin heatmap " + "at price_ratio=1.31." + ), + }, +] +DEFAULT_OUTPUT_FILE = "reclamm_geometric_noise_pair_compare.png" +VARIANT_STYLES = { + "noise": {"linestyle": "-", "alpha": 0.95, "linewidth": 2.2}, + "arb": {"linestyle": "--", "alpha": 0.85, "linewidth": 2.0}, +} + + +def parse_args() -> argparse.Namespace: + parser = argparse.ArgumentParser( + description=( + "Compare two geometric reCLAMM runs plus matched arb-only baselines. " + "Optionally source the run pair from an adjacent-heatmap CSV row." + ) + ) + parser.add_argument( + "--adjacent-csv", + default=None, + help="Optional adjacent-pairs CSV generated by find_adjacent_heatmap_pairs.py.", + ) + parser.add_argument( + "--adjacent-row-index", + type=int, + default=0, + help="Which row from --adjacent-csv to use. Defaults to the largest-diff row.", + ) + parser.add_argument( + "--output-file", + default=None, + help="Optional PNG output path override.", + ) + return parser.parse_args() + + +def load_runtime_dependencies(): + """Load heavy runtime dependencies only when an actual simulation is needed.""" + thermostat_path = Path(__file__).with_name("compare_reclamm_thermostats.py") + spec = importlib.util.spec_from_file_location( + "reclamm_compare_reclamm_thermostats_runtime", + thermostat_path, + ) + if spec is None or spec.loader is None: + raise RuntimeError(f"Could not load compare module from {thermostat_path}") + thermostat_compare = importlib.util.module_from_spec(spec) + try: + spec.loader.exec_module(thermostat_compare) + except ModuleNotFoundError as exc: # pragma: no cover - depends on local runtime deps + if exc.name == "jax": + raise RuntimeError( + "compare_reclamm_geometric_noise_runs.py requires JAX to execute " + "the simulator runs, but JAX is not available in this environment." + ) from exc + raise + + from quantammsim.runners.jax_runners import do_run_on_historic_data + + return thermostat_compare, do_run_on_historic_data + + +def build_default_base_config(thermostat_compare): + """Return the aggressive AAVE/ETH geometric base config used by the compare script.""" + return dict(thermostat_compare.CONFIGS[1]) + + +def load_adjacent_csv_row(csv_path: Path, row_index: int = 0) -> Mapping[str, object]: + """Load one row from an adjacent-pairs CSV.""" + frame = pd.read_csv(csv_path) + if frame.empty: + raise ValueError(f"Adjacent CSV is empty: {csv_path}") + if not 0 <= row_index < len(frame): + raise IndexError( + f"adjacent-row-index {row_index} is out of range for {len(frame)} rows" + ) + return frame.iloc[row_index].to_dict() + + +def build_run_specs_from_adjacent_row( + row: Mapping[str, object], + csv_path: Optional[Path] = None, + row_index: int = 0, +): + """Convert one adjacent-pairs CSV row into the two run specs this script compares.""" + metric_key = str(row.get("metric_key", "unknown_metric")) + metric_unit = str(row.get("metric_unit", "value")) + pair_slug = str(row.get("pair_slug", "unknown_pair")) + slice_slug = str(row.get("slice_slug", "unknown_slice")) + adjacency_axis = str(row.get("adjacency_axis", "unknown_axis")) + diff_abs = float(row["heatmap_value_diff_abs"]) + source_noise_profile = str(row.get("source_noise_profile", "unknown")) + + source_description = ( + f"{pair_slug} {slice_slug} adjacent pair from {metric_key} " + f"({adjacency_axis}, abs diff={diff_abs:.6f} {metric_unit}, " + f"noise_profile={source_noise_profile})" + ) + if csv_path is not None: + source_description = ( + f"{csv_path.name} row {row_index} | {source_description}" + ) + + run_specs = [] + for prefix, color in (("1", "C0"), ("2", "C1")): + heatmap_value = float(row[f"{prefix}_heatmap_value"]) + spec = { + "name": f"Top diff row cell {prefix}", + "price_ratio": float(row[f"{prefix}_price_ratio"]), + "centeredness_margin": float(row[f"{prefix}_centeredness_margin"]), + "daily_price_shift_exponent": float(row[f"{prefix}_daily_price_shift_exponent"]), + "tvl_usd": float(row[f"{prefix}_tvl_usd"]), + "color": color, + "source_noise_profile": source_noise_profile, + "reason": ( + f"Run derived from adjacent heatmap CSV row {row_index}, cell {prefix}. " + f"Source={pair_slug}/{slice_slug}, adjacency={adjacency_axis}, " + f"heatmap_value={heatmap_value:.6f} {metric_unit}, " + f"noise_profile={source_noise_profile}." + ), + } + run_specs.append(spec) + return source_description, run_specs + + +def default_output_file_for_adjacent_csv(csv_path: Path, row_index: int = 0) -> Path: + """Build a deterministic output PNG path for an adjacent-pairs CSV selection.""" + stem = f"{csv_path.stem}_row_{row_index}_geometric_noise_compare" + return csv_path.with_name(stem + ".png") + + +def build_run_config(spec, base_config): + """Build the base geometric reCLAMM config for one highlighted heatmap cell.""" + cfg = dict(base_config) + cfg.update( + { + "name": spec["name"], + "price_ratio": float(spec["price_ratio"]), + "centeredness_margin": float(spec["centeredness_margin"]), + "daily_price_shift_exponent": float(spec["daily_price_shift_exponent"]), + "initial_pool_value": float(spec["tvl_usd"]), + "reason": spec.get( + "reason", + "Standalone geometric noise-model comparison run.", + ), + "enable_noise_model": True, + } + ) + source_noise_profile = spec.get("source_noise_profile") + if source_noise_profile == "market_linear": + cfg["noise_model"] = "market_linear" + elif source_noise_profile not in (None, "", "unknown"): + raise ValueError( + "compare_reclamm_geometric_noise_runs.py only supports the current " + f"market_linear source profile, got {source_noise_profile!r}. " + "Regenerate the adjacent-pairs CSV with the current heatmap cache." + ) + return cfg + + +def build_run_variants(spec, base_config, thermostat_compare): + """Build matched noise-model and arb-only variants for one highlighted cell.""" + noise_cfg = thermostat_compare.make_noise_variant_cfg( + build_run_config(spec, base_config=base_config), + True, + ) + noise_cfg["variant_key"] = "noise" + noise_cfg["variant_label"] = "noise-model" + + arb_cfg = thermostat_compare.make_noise_variant_cfg(noise_cfg, False) + arb_cfg["variant_key"] = "arb" + arb_cfg["variant_label"] = "arb-only" + arb_cfg["name"] = noise_cfg["name"] + arb_cfg["reason"] = ( + f"{noise_cfg['reason']} Matched arb-only baseline with noise disabled." + ) + return {"spec": spec, "noise": noise_cfg, "arb": arb_cfg} + + +def build_run_label(cfg, thermostat_compare): + """Build a compact legend label for a run.""" + return ( + f"{cfg['name']} ({cfg.get('variant_label', 'run')}) | PR {cfg['price_ratio']:.4g}, " + f"M {cfg['centeredness_margin']:.3g}, " + f"Shift {cfg['daily_price_shift_exponent']:.3g}, " + f"TVL {thermostat_compare.format_tvl_millions_label(cfg)}" + ) + + +def build_time_index(run_fingerprint, periods, step_minutes=1): + """Return a DatetimeIndex for a result series with the given cadence.""" + return pd.date_range( + start=pd.Timestamp(run_fingerprint["startDateString"]), + periods=int(periods), + freq=f"{max(int(step_minutes), 1)}min", + ) + + +def infer_series_step_minutes(run_fingerprint, series_length, minute_length): + """Infer the cadence of a result series from its length.""" + if minute_length and series_length and series_length != minute_length: + ratio = float(minute_length) / float(series_length) + rounded_ratio = max(int(round(ratio)), 1) + if abs(ratio - rounded_ratio) < 1.0e-9: + return rounded_ratio + return max(int(run_fingerprint.get("arb_frequency", 1)), 1) + + +def build_daily_weight_series( + weights, + tokens, + run_fingerprint, + minute_length, +): + """Build a daily weight frame from a weight array at inferred cadence.""" + weights = np.asarray(weights, dtype=float) + weight_step_minutes = infer_series_step_minutes( + run_fingerprint, + len(weights), + minute_length, + ) + weight_dates = build_time_index( + run_fingerprint, + len(weights), + step_minutes=weight_step_minutes, + ) + weight_frame = pd.DataFrame(weights, index=weight_dates, columns=tokens) + return weight_frame.resample("1D").last() + + +def estimate_gross_volume_usd(cfg, result, default_protocol_fee_split=0.25): + """Recover gross traded USD volume from LP fee revenue.""" + fee_revenue = result.get("fee_revenue") + if fee_revenue is None: + return np.zeros(len(np.asarray(result["value"])), dtype=float) + + fee_revenue = np.asarray(fee_revenue, dtype=float) + lp_fee_rate_share = float(cfg["fees"]) * ( + 1.0 - float(cfg.get("protocol_fee_split", default_protocol_fee_split)) + ) + if lp_fee_rate_share <= 0.0: + return np.zeros_like(fee_revenue) + return fee_revenue / lp_fee_rate_share + + +def build_daily_run_series(cfg, run_fingerprint, result, default_protocol_fee_split=0.25): + """Build daily TVL/share-price, weight, and volume series for plotting.""" + value = np.asarray(result["value"], dtype=float) + value_dates = build_time_index(run_fingerprint, len(value), step_minutes=1) + value_series = pd.Series(value, index=value_dates) + daily_value = value_series.resample("1D").last() + + zero_fee_weights = np.asarray(result["weights"], dtype=float) + daily_zero_fee_weights = build_daily_weight_series( + zero_fee_weights, + cfg["tokens"], + run_fingerprint, + len(value), + ) + + reserves = np.asarray(result["reserves"], dtype=float) + prices = np.asarray(result["prices"], dtype=float) + reserve_value = reserves * prices + reserve_value_totals = np.maximum( + reserve_value.sum(axis=1, keepdims=True), + 1.0e-12, + ) + actual_reserve_value_weights = reserve_value / reserve_value_totals + daily_actual_reserve_value_weights = build_daily_weight_series( + actual_reserve_value_weights, + cfg["tokens"], + run_fingerprint, + len(value), + ) + + gross_volume_usd = estimate_gross_volume_usd( + cfg, + result, + default_protocol_fee_split=default_protocol_fee_split, + ) + volume_step_minutes = ( + 1 + if len(gross_volume_usd) == len(value) + else infer_series_step_minutes(run_fingerprint, len(gross_volume_usd), len(value)) + ) + volume_dates = build_time_index( + run_fingerprint, + len(gross_volume_usd), + step_minutes=volume_step_minutes, + ) + daily_volume = pd.Series(gross_volume_usd, index=volume_dates).resample("1D").sum() + + return { + "daily_value": daily_value, + "daily_zero_fee_weights": daily_zero_fee_weights, + "daily_actual_reserve_value_weights": daily_actual_reserve_value_weights, + "daily_volume": daily_volume, + } + + +def _terminal_json_default(value): + """Serialize NumPy/JAX-backed values for readable terminal logging.""" + if isinstance(value, Path): + return str(value) + if hasattr(value, "tolist"): + return value.tolist() + return str(value) + + +def print_run_inputs_to_terminal(cfg, run_fingerprint, update_params): + """Print the full run fingerprint and update params for a triggered run.""" + print( + f"Run inputs for {cfg['name']} ({cfg.get('variant_label', 'run')}):" + ) + print( + json.dumps( + { + "run_fingerprint": run_fingerprint, + "update_params": update_params, + }, + indent=2, + sort_keys=True, + default=_terminal_json_default, + ) + ) + + +def run_single_config(cfg, price_data, thermostat_compare, do_run_on_historic_data): + """Run one geometric noise-model configuration.""" + run_fingerprint = thermostat_compare.make_fingerprint(cfg, "geometric") + update_params = thermostat_compare.make_params(cfg) + print_run_inputs_to_terminal(cfg, run_fingerprint, update_params) + result = do_run_on_historic_data( + run_fingerprint=run_fingerprint, + params=update_params, + price_data=price_data, + ) + return { + "config": cfg, + "fingerprint": run_fingerprint, + "noise_summary": thermostat_compare.resolve_reclamm_noise_settings(cfg)[ + "noise_summary" + ], + "result": result, + "series": build_daily_run_series( + cfg, + run_fingerprint, + result, + default_protocol_fee_split=thermostat_compare.DEFAULT_PROTOCOL_FEE_SPLIT, + ), + } + + +def plot_pair_results( + run_pairs, + thermostat_compare, + source_heatmap_description, + output_file, +): + """Plot paired noise-model/arb-only outputs for the two highlighted cells.""" + import matplotlib.pyplot as plt + + fig, axes = plt.subplots( + 5, + 1, + figsize=(14, 16), + sharex=True, + gridspec_kw={"height_ratios": [2.2, 1.5, 1.5, 1.4, 1.6]}, + ) + ( + ax_value, + ax_zero_fee_weights, + ax_actual_reserve_weights, + ax_volume, + ax_improvement, + ) = axes + + for pair in run_pairs: + spec = pair["spec"] + color = spec["color"] + noise_output = pair["noise"] + arb_output = pair["arb"] + + for variant_key, output in (("noise", noise_output), ("arb", arb_output)): + cfg = output["config"] + label = build_run_label(cfg, thermostat_compare=thermostat_compare) + series = output["series"] + style = VARIANT_STYLES[variant_key] + + ax_value.plot( + series["daily_value"].index, + series["daily_value"].to_numpy(dtype=float) / 1e6, + color=color, + linestyle=style["linestyle"], + linewidth=style["linewidth"], + alpha=style["alpha"], + label=label, + ) + + for token_idx, token in enumerate(cfg["tokens"]): + token_linestyle = style["linestyle"] if token_idx == 0 else ":" + ax_zero_fee_weights.plot( + series["daily_zero_fee_weights"].index, + series["daily_zero_fee_weights"][token].to_numpy(dtype=float), + color=color, + linestyle=token_linestyle, + linewidth=1.8 if token_idx == 0 else 1.6, + alpha=style["alpha"], + label=f"{cfg['name']} {cfg['variant_label']} {token}", + ) + ax_actual_reserve_weights.plot( + series["daily_actual_reserve_value_weights"].index, + series["daily_actual_reserve_value_weights"][token].to_numpy( + dtype=float + ), + color=color, + linestyle=token_linestyle, + linewidth=1.8 if token_idx == 0 else 1.6, + alpha=style["alpha"], + label=f"{cfg['name']} {cfg['variant_label']} {token}", + ) + + ax_volume.plot( + series["daily_volume"].index, + series["daily_volume"].to_numpy(dtype=float) / 1e6, + color=color, + linestyle=style["linestyle"], + linewidth=style["linewidth"], + alpha=style["alpha"], + label=f"{cfg['name']} {cfg['variant_label']}", + ) + + noise_value, arb_value = noise_output["series"]["daily_value"].align( + arb_output["series"]["daily_value"], + join="inner", + ) + improvement_pct = (noise_value - arb_value) / arb_value * 100.0 + ax_improvement.plot( + improvement_pct.index, + improvement_pct.to_numpy(dtype=float), + color=color, + linewidth=2.2, + label=noise_output["config"]["name"], + ) + + shared_noise_summary = run_pairs[0]["noise"]["noise_summary"] + fig.suptitle( + "reCLAMM geometric noise-model vs arb-only comparison", + fontsize=14, + fontweight="bold", + ) + fig.text( + 0.5, + 0.965, + ( + "Compare-script AAVE/ETH fingerprint | " + f"Cell source: {source_heatmap_description} | " + f"Noise: {shared_noise_summary} | " + "Share price equals TVL here because LP supply is fixed at 1.0" + ), + ha="center", + va="top", + fontsize=10, + ) + + ax_value.set_ylabel("Share price / TVL ($M)") + ax_value.set_title("Absolute share price / TVL") + ax_value.grid(True, alpha=0.3) + ax_value.legend(fontsize=8) + + ax_zero_fee_weights.set_ylabel("Weight") + ax_zero_fee_weights.set_title("Reported zero-fee empirical weights") + ax_zero_fee_weights.set_ylim(-0.02, 1.02) + ax_zero_fee_weights.grid(True, alpha=0.3) + ax_zero_fee_weights.legend(fontsize=8, ncol=2) + + ax_actual_reserve_weights.set_ylabel("Weight") + ax_actual_reserve_weights.set_title("Actual reserve value weights") + ax_actual_reserve_weights.set_ylim(-0.02, 1.02) + ax_actual_reserve_weights.grid(True, alpha=0.3) + ax_actual_reserve_weights.legend(fontsize=8, ncol=2) + + ax_volume.set_ylabel("Daily volume ($M)") + ax_volume.set_title("Estimated gross swap volume") + ax_volume.grid(True, alpha=0.3) + ax_volume.legend(fontsize=8) + + ax_improvement.axhline(0.0, color="black", linewidth=0.9, alpha=0.55) + ax_improvement.set_ylabel("Noise vs arb (%)") + ax_improvement.set_title("Daily TVL improvement: (noise - arb) / arb") + ax_improvement.set_xlabel("Date") + ax_improvement.grid(True, alpha=0.3) + ax_improvement.legend(fontsize=8) + + output_file = Path(output_file) + output_file.parent.mkdir(parents=True, exist_ok=True) + plt.tight_layout(rect=(0.0, 0.0, 1.0, 0.945)) + plt.savefig(output_file, dpi=180) + print(f"Saved {output_file}") + plt.close(fig) + + +def print_final_heatmap_summary(run_pairs, source_heatmap_description): + """Print the final values and heatmap-equivalent improvement for each pair.""" + print(f"\nSource heatmap selection: {source_heatmap_description}") + print("Final values represented by the heatmap metric:") + for pair in run_pairs: + spec = pair["spec"] + noise_final = float(pair["noise"]["series"]["daily_value"].iloc[-1]) + arb_final = float(pair["arb"]["series"]["daily_value"].iloc[-1]) + improvement_pct = (noise_final - arb_final) / arb_final * 100.0 + print( + f" {spec['name']}: " + f"noise=${noise_final:,.2f}, " + f"arb=${arb_final:,.2f}, " + f"heatmap_improvement={improvement_pct:.6f}%" + ) + + +def run_pair_comparison( + run_specs: Sequence[Mapping[str, object]], + source_heatmap_description: str, + output_file, +): + """Run both configs and render the comparison figure.""" + thermostat_compare, do_run_on_historic_data = load_runtime_dependencies() + base_config = build_default_base_config(thermostat_compare) + run_pairs = [ + build_run_variants( + spec, + base_config=base_config, + thermostat_compare=thermostat_compare, + ) + for spec in run_specs + ] + run_configs = [ + cfg + for pair in run_pairs + for variant_key, cfg in pair.items() + if variant_key in ("noise", "arb") + ] + price_data = thermostat_compare.load_shared_price_data(run_configs) + + completed_pairs = [] + for pair in run_pairs: + completed_pair = {"spec": pair["spec"]} + for variant_key in ("noise", "arb"): + cfg = pair[variant_key] + print( + f"Running {cfg['name']} ({cfg['variant_label']}) | " + f"price_ratio={cfg['price_ratio']}, " + f"margin={cfg['centeredness_margin']}, " + f"shift_exp={cfg['daily_price_shift_exponent']}, " + f"TVL={thermostat_compare.format_tvl_millions_label(cfg)}" + ) + completed_pair[variant_key] = run_single_config( + cfg, + price_data, + thermostat_compare=thermostat_compare, + do_run_on_historic_data=do_run_on_historic_data, + ) + completed_pairs.append(completed_pair) + + plot_pair_results( + completed_pairs, + thermostat_compare=thermostat_compare, + source_heatmap_description=source_heatmap_description, + output_file=output_file, + ) + print_final_heatmap_summary( + completed_pairs, + source_heatmap_description=source_heatmap_description, + ) + return output_file + + +def run_adjacent_csv_row_comparison( + csv_path, + row_index: int = 0, + output_file=None, +): + """Run the standard geometric comparison using one adjacent-pairs CSV row.""" + csv_path = Path(csv_path) + row = load_adjacent_csv_row(csv_path, row_index=row_index) + source_heatmap_description, run_specs = build_run_specs_from_adjacent_row( + row, + csv_path=csv_path, + row_index=row_index, + ) + resolved_output_file = ( + Path(output_file) + if output_file is not None + else default_output_file_for_adjacent_csv(csv_path, row_index=row_index) + ) + return run_pair_comparison( + run_specs=run_specs, + source_heatmap_description=source_heatmap_description, + output_file=resolved_output_file, + ) + + +def main(cli_args: Optional[argparse.Namespace] = None): + """Entry point for CLI execution.""" + args = cli_args or parse_args() + if args.adjacent_csv: + return run_adjacent_csv_row_comparison( + args.adjacent_csv, + row_index=args.adjacent_row_index, + output_file=args.output_file, + ) + + output_file = Path(args.output_file) if args.output_file else Path(DEFAULT_OUTPUT_FILE) + return run_pair_comparison( + run_specs=DEFAULT_RUN_SPECS, + source_heatmap_description=DEFAULT_SOURCE_HEATMAP_DESCRIPTION, + output_file=output_file, + ) + + +if __name__ == "__main__": + main() diff --git a/scripts/reclamm/compare_reclamm_thermostats.py b/scripts/reclamm/compare_reclamm_thermostats.py index 8a2c374..f705c66 100644 --- a/scripts/reclamm/compare_reclamm_thermostats.py +++ b/scripts/reclamm/compare_reclamm_thermostats.py @@ -1,19 +1,40 @@ -"""Compare geometric vs constant-arc-length thermostats on historic data. +"""Compare reCLAMM interpolation modes on historic AAVE/ETH data. -Runs AAVE/ETH reClAMM pool simulations with both interpolation methods. -Plots: pool value, cumulative LVR, price path, empirical weights, -value difference, LVR ratio, and per-step LVR distribution (∝ Δs²). +Runs the production geometric interpolation against the non-linear +constant-arc-length interpolation on: +1. The original launch-style range (price_ratio ~= 1.50) +2. A much tighter range (price_ratio = 1.10) -Usage: - cd - source ~/miniconda3/etc/profile.d/conda.sh && conda activate qsim-reclamm - python scripts/compare_reclamm_thermostats.py +The aggressive case is deliberate. A local AAVE/ETH sweep showed: +price_ratio 1.15, margin 0.5, shift 0.1 -> about +$10k vs geometric +price_ratio 1.10, margin 0.5, shift 0.1 -> about +$31k vs geometric +price_ratio 1.10, margin 0.6, shift 0.1 -> about +$73k vs geometric + +So the strongest clean demo setting came from tightening the band and +slightly raising the trigger margin, while keeping the launch-style shift +speed rather than pushing shift_exponent higher. """ +import gc +import hashlib +import os +from pathlib import Path + import jax.numpy as jnp import numpy as np +import pandas as pd import matplotlib.pyplot as plt +from matplotlib.colors import Normalize, SymLogNorm, TwoSlopeNorm +from matplotlib.cm import ScalarMappable +from quantammsim.pools.reCLAMM.reclamm_reserves import ( + calibrate_arc_length_speed, + compute_price_ratio, + initialise_reclamm_reserves, +) from quantammsim.runners.jax_runners import do_run_on_historic_data +from quantammsim.utils.data_processing.historic_data_utils import ( + get_historic_parquet_data, +) def to_daily_price_shift_base(daily_price_shift_exponent): @@ -21,61 +42,515 @@ def to_daily_price_shift_base(daily_price_shift_exponent): return 1.0 - daily_price_shift_exponent / 124649.0 +def build_inclusive_sweep(start, stop, step): + """Build a sweep that keeps the requested step and explicitly includes the stop.""" + values = np.arange(start, stop + 1.0e-12, step, dtype=float) + if values.size == 0 or not np.isclose(values[-1], stop): + values = np.append(values, float(stop)) + return values + + +def _resolve_repo_root(script_path): + """Locate the repository root from either scripts/ or scripts/reclamm/.""" + script_path = Path(script_path).resolve() + for parent in script_path.parents: + if (parent / "quantammsim").exists() and (parent / "scripts").exists(): + return parent + return script_path.parents[1] + + +RUN_CONSTANT_ARC_LENGTH = True +INTERPOLATION_METHODS = ( + ("geometric", "constant_arc_length") + if RUN_CONSTANT_ARC_LENGTH + else ("geometric",) +) +HEATMAP_PRICE_RATIOS = build_inclusive_sweep(1.01, 3.00, 0.025) +HEATMAP_MARGINS = np.linspace(0.05, 0.90, 39) +HEATMAP_SHIFT_EXPONENTS = build_inclusive_sweep(0.01, 0.50, 0.0125) +HEATMAP_ARC_LENGTH_SPEEDS = np.geomspace(1.0e-6, 5.0e-4, 11) +PRICE_RATIO_TICKS = np.array([1.01, 1.25, 1.50, 2.00, 2.50, 3.00]) +MARGIN_TICKS = np.array([0.05, 0.15, 0.25, 0.35, 0.45, 0.55, 0.65, 0.75, 0.85, 0.90]) +SHIFT_EXPONENT_TICKS = np.array([0.01, 0.05, 0.10, 0.20, 0.30, 0.40, 0.50]) +ARC_LENGTH_SPEED_TICKS = np.array([ + 1.0e-6, + 2.0e-6, + 5.0e-6, + 1.0e-5, + 2.0e-5, + 5.0e-5, + 1.0e-4, + 2.0e-4, + 5.0e-4, +]) +SWEEP_LINE_WIDTH = 0.45 +REFERENCE_LINE_WIDTH = 0.9 +DEFAULT_INITIAL_POOL_VALUE = 1_000_000.0 +TVL_SWEEP_VALUES = ( + 1_000_000.0, + 5_000_000.0, + 20_000_000.0, +) +CENTER_ZERO_HEATMAP_COLOR_NORM = "symlog" +CENTER_ZERO_HEATMAP_COLOR_TAG = "symlog20" +CENTER_ZERO_HEATMAP_SYMLOG_LINTHRESH = 20.0 +FIXED_SLICE_FRACTIONS = (0.125, 0.375, 0.625, 0.875) +FIXED_SLICE_LABELS = ("Q1", "Q2", "Q3", "Q4") +THREE_D_VIEW_ELEVATION = 22.0 +THREE_D_VIEW_AZIMUTH = 140.0 +HEATMAP_FORWARD_CACHE_ENABLED = True +HEATMAP_FORWARD_CACHE_RUN_NAME = "aave_eth_thermostat_heatmaps_market_linear_v2" +HEATMAP_FORWARD_CACHE_ROOT = os.path.join( + "results", + "reclamm_heatmap_forward_cache", +) +HEATMAP_FORWARD_CACHE_FLUSH_EVERY = 32 + +REPO_ROOT = _resolve_repo_root(__file__) +AAVE_WETH_POOL_ID = "0x9d1fcf346ea1b0" +DEFAULT_MARKET_LINEAR_ARTIFACT_DIR = "results/linear_market_noise" +DEFAULT_MARKET_LINEAR_NOISE_START_DATE = "2024-06-01" +DEFAULT_MARKET_LINEAR_NOISE_END_DATE = "2026-03-01" +DEFAULT_MARKET_LINEAR_NOISE_ARRAYS_PATH = str( + REPO_ROOT + / "results" + / "linear_market_noise" + / "_sim_arrays" + / ( + f"{AAVE_WETH_POOL_ID}_{DEFAULT_MARKET_LINEAR_NOISE_START_DATE}_" + f"{DEFAULT_MARKET_LINEAR_NOISE_END_DATE}.npz" + ) +) +DEFAULT_NOISE_MODEL = "market_linear" +DEFAULT_GAS_COST = 1.0 +DEFAULT_PROTOCOL_FEE_SPLIT = 0.25 +FIXED_COMPARE_ARB_FREQUENCY = 15 +AAVE_ETH_NOISE_SETTINGS = { + "enable_noise_model": True, + "noise_model": DEFAULT_NOISE_MODEL, + "noise_reference_model": DEFAULT_NOISE_MODEL, + "noise_artifact_dir": DEFAULT_MARKET_LINEAR_ARTIFACT_DIR, + "noise_pool_id": AAVE_WETH_POOL_ID, + "noise_arrays_path": DEFAULT_MARKET_LINEAR_NOISE_ARRAYS_PATH, + "arb_frequency": FIXED_COMPARE_ARB_FREQUENCY, + "gas_cost": DEFAULT_GAS_COST, + "protocol_fee_split": DEFAULT_PROTOCOL_FEE_SPLIT, +} +PERSISTED_FORWARD_VALUE_COLUMNS = ( + "cache_key_hash", + "final_value", + "method", + "enable_noise_model", + "noise_model", + "price_ratio", + "centeredness_margin", + "daily_price_shift_exponent", + "initial_pool_value", + "arb_frequency", +) + +GEOMETRIC_ONLY_HEATMAP_METRIC_KEYS = ( + "geometric_vs_launch_geometric_pct", + "noise_geometric_final_value_musd", + "noise_vs_arb_geometric_improvement_pct", +) +CONSTANT_ARC_HEATMAP_METRIC_KEYS = ( + "efficiency_pct", + "launch_geometric_efficiency_pct", + "constant_arc_vs_launch_constant_arc_pct", + "noise_constant_arc_final_value_musd", + "noise_vs_arb_constant_arc_improvement_pct", +) +HEATMAP_METRIC_DEPENDENCIES = { + "efficiency_pct": ("noise_geometric", "noise_constant_arc"), + "launch_geometric_efficiency_pct": ("noise_constant_arc",), + "geometric_vs_launch_geometric_pct": ("noise_geometric",), + "constant_arc_vs_launch_constant_arc_pct": ("noise_constant_arc",), + "noise_geometric_final_value_musd": ("noise_geometric",), + "noise_constant_arc_final_value_musd": ("noise_constant_arc",), + "noise_vs_arb_geometric_improvement_pct": ("noise_geometric", "arb_geometric"), + "noise_vs_arb_constant_arc_improvement_pct": ( + "noise_constant_arc", + "arb_constant_arc", + ), +} + +_NOISE_SETTINGS_CACHE = {} +_MARKET_LINEAR_NOISE_DATA_CACHE = {} + + +def get_initial_pool_value(cfg): + """Return the configured base pool TVL in USD.""" + return float(cfg.get("initial_pool_value", DEFAULT_INITIAL_POOL_VALUE)) + + +def get_tvl_millions(cfg): + """Return the configured base pool TVL in millions of USD.""" + return get_initial_pool_value(cfg) / 1_000_000.0 + + +def format_tvl_millions_slug(cfg): + """Format the TVL in millions for stable filenames.""" + tvl_millions = get_tvl_millions(cfg) + rounded = round(float(tvl_millions), 6) + if np.isclose(rounded, round(rounded)): + return f"{int(round(rounded))}m" + return f"{rounded:.6f}".rstrip("0").rstrip(".").replace(".", "p") + "m" + + +def format_tvl_millions_label(cfg): + """Format the TVL in millions for plot titles and logs.""" + return f"{get_tvl_millions(cfg):.1f}M" + + +def tvl_artifact_filename(stem, cfg, suffix=None): + """Append a TVL-in-millions suffix to a PNG artifact name.""" + parts = [stem] + if suffix: + parts.append(suffix) + parts.append(f"tvl_{format_tvl_millions_slug(cfg)}") + return "_".join(parts) + ".png" + + +def heatmap_artifact_filename(spec, cfg, suffix=None): + """Build a heatmap filename, including any colour-style tag.""" + stem = f"reclamm_heatmap_{spec['slug']}" + artifact_tag = spec.get("artifact_tag") + if artifact_tag: + stem = f"{stem}_{artifact_tag}" + return tvl_artifact_filename(stem, cfg, suffix=suffix) + + +def three_d_heatmap_artifact_filename(spec, cfg, suffix=None): + """Build a 3D heatmap filename, including any colour-style tag.""" + stem = f"reclamm_heatmap_3d_{spec['slug']}" + artifact_tag = spec.get("artifact_tag") + if artifact_tag: + stem = f"{stem}_{artifact_tag}" + return tvl_artifact_filename(stem, cfg, suffix=suffix) + + +def format_heatmap_param_value(value): + """Format a sweep parameter compactly for titles and logs.""" + value = float(value) + if abs(value) >= 1.0: + return f"{value:.2f}".rstrip("0").rstrip(".") + return f"{value:.3f}".rstrip("0").rstrip(".") + + +def configs_for_tvl(base_configs, initial_pool_value): + """Attach a shared initial TVL to each compare configuration.""" + configs = [] + for cfg in base_configs: + updated = dict(cfg) + updated["initial_pool_value"] = float(initial_pool_value) + configs.append(updated) + return configs + + +def _normalize_arb_frequency(value, default=FIXED_COMPARE_ARB_FREQUENCY): + """Return a stable integer arb cadence for thermostat comparisons.""" + if value is None: + if default is None: + return None + value = default + return max(int(round(float(value))), 1) + + +def get_effective_arb_frequency(cfg, noise_cfg=None): + """Resolve the arb cadence used by a thermostat comparison run.""" + del noise_cfg + return _normalize_arb_frequency(FIXED_COMPARE_ARB_FREQUENCY) + + +def _canonical_noise_reference_model(cfg): + """Resolve the only supported thermostat noise parametrisation.""" + noise_model = cfg.get("noise_model", DEFAULT_NOISE_MODEL) or DEFAULT_NOISE_MODEL + reference_model = cfg.get("noise_reference_model") + if reference_model is None: + reference_model = DEFAULT_NOISE_MODEL if noise_model == "arb_only" else noise_model + noise_model = str(noise_model) + reference_model = str(reference_model) + if noise_model not in {DEFAULT_NOISE_MODEL, "arb_only"}: + raise ValueError( + "compare_reclamm_thermostats only supports " + "'market_linear' noise and 'arb_only' baselines." + ) + if reference_model != DEFAULT_NOISE_MODEL: + raise ValueError( + "compare_reclamm_thermostats only supports the " + "'market_linear' noise parametrisation." + ) + return reference_model + + +def normalize_compare_run_cfg(cfg, enable_noise_model=None): + """Canonicalize the compare-run config so non-axis inputs stay fixed.""" + updated = dict(cfg) + updated["price_ratio"] = float(cfg["price_ratio"]) + updated["centeredness_margin"] = float(cfg["centeredness_margin"]) + updated["daily_price_shift_exponent"] = float(cfg["daily_price_shift_exponent"]) + updated["initial_pool_value"] = float(get_initial_pool_value(cfg)) + updated["gas_cost"] = DEFAULT_GAS_COST + updated["protocol_fee_split"] = DEFAULT_PROTOCOL_FEE_SPLIT + updated["arb_fees"] = 0.0 + updated["arb_frequency"] = get_effective_arb_frequency(cfg) + updated["noise_trader_ratio"] = 0.0 + + arc_length_speed = cfg.get("arc_length_speed") + if arc_length_speed is None: + updated.pop("arc_length_speed", None) + else: + updated["arc_length_speed"] = float(arc_length_speed) + + use_noise = ( + bool(cfg.get("enable_noise_model", False)) + if enable_noise_model is None + else bool(enable_noise_model) + ) + updated["enable_noise_model"] = use_noise + + reference_mode = _canonical_noise_reference_model(cfg) + if use_noise: + updated["noise_model"] = reference_mode + updated["noise_reference_model"] = reference_mode + else: + updated["noise_model"] = "arb_only" + updated["noise_reference_model"] = reference_mode + + updated["noise_arrays_path"] = DEFAULT_MARKET_LINEAR_NOISE_ARRAYS_PATH + updated.pop("reclamm_noise_params", None) + updated["noise_artifact_dir"] = DEFAULT_MARKET_LINEAR_ARTIFACT_DIR + updated["noise_pool_id"] = AAVE_WETH_POOL_ID + + return updated + + +def make_noise_variant_cfg(cfg, enable_noise_model): + """Return a config with either noise modelling or pure arb-only enabled.""" + return normalize_compare_run_cfg(cfg, enable_noise_model=enable_noise_model) + + +def _hashable_noise_params(params): + """Convert a noise-params dict into a stable cache key fragment.""" + if params is None: + return None + return tuple(sorted((str(k), round(float(v), 12)) for k, v in params.items())) + + +def load_shared_market_linear_noise_data( + arrays_path=DEFAULT_MARKET_LINEAR_NOISE_ARRAYS_PATH, +): + """Load the market_linear arrays once so compare runs can reuse them.""" + arrays_path = os.path.abspath(os.fspath(arrays_path)) + cached = _MARKET_LINEAR_NOISE_DATA_CACHE.get(arrays_path) + if cached is not None: + return cached + + if not os.path.exists(arrays_path): + raise FileNotFoundError(f"market_linear arrays file not found: {arrays_path}") + + with np.load(arrays_path) as arrays: + required_keys = {"noise_base", "noise_tvl_coeff", "tvl_mean", "tvl_std"} + missing_keys = sorted(required_keys.difference(arrays.files)) + if missing_keys: + raise KeyError( + f"market_linear arrays file {arrays_path} is missing keys: {missing_keys}" + ) + shared = { + "arrays_path": arrays_path, + "noise_base_array": np.asarray(arrays["noise_base"]), + "noise_tvl_coeff_array": np.asarray(arrays["noise_tvl_coeff"]), + "tvl_mean": float(arrays["tvl_mean"]), + "tvl_std": float(arrays["tvl_std"]), + } + _MARKET_LINEAR_NOISE_DATA_CACHE[arrays_path] = shared + return shared + + +def _load_market_linear_noise_stats(arrays_path=DEFAULT_MARKET_LINEAR_NOISE_ARRAYS_PATH): + """Load the exact arrays file used by the market_linear run fingerprint. + + The simulator consumes ``noise_base`` and ``noise_tvl_coeff`` from + ``run_fingerprint["noise_arrays_path"]`` and uses ``tvl_mean``/``tvl_std`` + from the same file for TVL standardization. + """ + shared = load_shared_market_linear_noise_data(arrays_path=arrays_path) + return shared["arrays_path"], shared["tvl_mean"], shared["tvl_std"] + + +def _market_linear_noise_settings(noise_model="market_linear", arb_frequency=None): + """Build the tuned market_linear fingerprint block from the fixed arrays file.""" + arrays_path, tvl_mean, tvl_std = _load_market_linear_noise_stats() + arb_frequency = _normalize_arb_frequency(arb_frequency) + return { + "noise_model": noise_model, + "noise_trader_ratio": 0.0, + "reclamm_noise_params": { + "tvl_mean": tvl_mean, + "tvl_std": tvl_std, + }, + "noise_arrays_path": arrays_path, + "arb_frequency": arb_frequency, + "noise_summary": f"{noise_model} (arb_frequency={arb_frequency})", + "noise_cache_key": ( + noise_model, + arrays_path, + arb_frequency, + round(tvl_mean, 12), + round(tvl_std, 12), + ), + } + +def resolve_reclamm_noise_settings(cfg): + """Resolve the active reCLAMM noise-model fingerprint block for a config.""" + cfg = normalize_compare_run_cfg(cfg) + enable_noise_model = cfg.get("enable_noise_model", False) + requested_mode = cfg.get("noise_model", DEFAULT_NOISE_MODEL) + reference_mode = cfg.get("noise_reference_model", DEFAULT_NOISE_MODEL) + requested_arb_frequency = get_effective_arb_frequency(cfg) + cache_key = ( + tuple(cfg.get("tokens", [])), + cfg.get("start"), + cfg.get("end"), + enable_noise_model, + requested_mode, + reference_mode, + cfg.get("noise_artifact_dir", DEFAULT_MARKET_LINEAR_ARTIFACT_DIR), + cfg.get("noise_pool_id", AAVE_WETH_POOL_ID), + requested_arb_frequency, + round(float(cfg.get("noise_trader_ratio", 0.0)), 12), + _hashable_noise_params(cfg.get("reclamm_noise_params")), + cfg.get("noise_arrays_path"), + ) + if cache_key in _NOISE_SETTINGS_CACHE: + return _NOISE_SETTINGS_CACHE[cache_key] + + if requested_mode == "arb_only": + result = _market_linear_noise_settings( + noise_model="arb_only", + arb_frequency=requested_arb_frequency, + ) + elif requested_mode == DEFAULT_NOISE_MODEL: + result = _market_linear_noise_settings( + noise_model=DEFAULT_NOISE_MODEL, + arb_frequency=requested_arb_frequency, + ) + else: + raise ValueError( + "compare_reclamm_thermostats only supports " + "'market_linear' noise and 'arb_only' baselines." + ) + + _NOISE_SETTINGS_CACHE[cache_key] = result + return result + + # Pool configurations to compare CONFIGS = [ { - "name": "AAVE/ETH on-chain (25bps, narrow range)", + "name": "AAVE/ETH launch-style range (25bps, reference)", "tokens": ["AAVE", "ETH"], "start": "2024-06-01 00:00:00", "end": "2025-06-01 00:00:00", "fees": 0.0025, - "price_ratio": 1.5, + "price_ratio": 1.5014, "centeredness_margin": 0.5, "daily_price_shift_exponent": 0.1, + "reason": "Original launch-style parameters.", + **AAVE_ETH_NOISE_SETTINGS, }, { - "name": "AAVE/ETH wide range (25bps)", + "name": "AAVE/ETH aggressive tight range (25bps)", "tokens": ["AAVE", "ETH"], "start": "2024-06-01 00:00:00", "end": "2025-06-01 00:00:00", "fees": 0.0025, - "price_ratio": 4.0, - "centeredness_margin": 0.2, - "daily_price_shift_exponent": 1.0, - }, - { - "name": "AAVE/ETH zero fees (narrow)", - "tokens": ["AAVE", "ETH"], - "start": "2024-06-01 00:00:00", - "end": "2025-06-01 00:00:00", - "fees": 0.0, - "price_ratio": 1.5, - "centeredness_margin": 0.5, + "price_ratio": 1.10, + "centeredness_margin": 0.60, "daily_price_shift_exponent": 0.1, + "reason": ( + "Aggressively tightened and moved to an earlier thermostat trigger. " + "At fixed price_ratio=1.10, the shift_exponent sweep still favored " + "0.1, while margin=0.60 widened the non-linear edge materially." + ), + **AAVE_ETH_NOISE_SETTINGS, }, ] -def make_fingerprint(cfg, interpolation_method, centeredness_scaling=False): +def _attach_market_linear_noise_arrays( + fingerprint, + noise_cfg, + market_linear_noise_data, +): + """Attach preloaded market_linear arrays when the compare flow has them.""" + if market_linear_noise_data is None: + return + expected_path = noise_cfg.get("noise_arrays_path") + if expected_path is None: + return + shared_path = os.path.abspath(os.fspath(market_linear_noise_data["arrays_path"])) + expected_path = os.path.abspath(os.fspath(expected_path)) + if shared_path != expected_path: + raise ValueError( + "Shared market_linear noise arrays path does not match " + f"the resolved compare-run noise path: {shared_path} != {expected_path}" + ) + fingerprint["noise_base_array"] = market_linear_noise_data["noise_base_array"] + fingerprint["noise_tvl_coeff_array"] = market_linear_noise_data["noise_tvl_coeff_array"] + + +def make_fingerprint(cfg, interpolation_method, market_linear_noise_data=None): """Build run fingerprint for a given config and interpolation method.""" - return { + cfg = normalize_compare_run_cfg(cfg) + speed_override = ( + cfg.get("arc_length_speed") + if interpolation_method == "constant_arc_length" + else None + ) + noise_cfg = resolve_reclamm_noise_settings(cfg) + arb_frequency = get_effective_arb_frequency(cfg, noise_cfg) + fingerprint = { "tokens": cfg["tokens"], "rule": "reclamm", "startDateString": cfg["start"], "endDateString": cfg["end"], - "initial_pool_value": 1000000.0, + "initial_pool_value": get_initial_pool_value(cfg), "do_arb": True, "fees": cfg["fees"], - "gas_cost": 0.0, - "arb_fees": 0.0, + "gas_cost": cfg.get( + "gas_cost", + DEFAULT_GAS_COST if cfg.get("enable_noise_model", False) else 0.0, + ), + "arb_fees": cfg.get("arb_fees", 0.0), + "protocol_fee_split": cfg.get( + "protocol_fee_split", + DEFAULT_PROTOCOL_FEE_SPLIT if cfg.get("enable_noise_model", False) else 0.0, + ), + "noise_trader_ratio": noise_cfg.get("noise_trader_ratio", 0.0), "reclamm_interpolation_method": interpolation_method, - "reclamm_arc_length_speed": None, # auto-calibrate - "reclamm_centeredness_scaling": centeredness_scaling, + "reclamm_arc_length_speed": speed_override, } + if noise_cfg.get("noise_model") is not None: + fingerprint["noise_model"] = noise_cfg["noise_model"] + if noise_cfg.get("reclamm_noise_params") is not None: + fingerprint["reclamm_noise_params"] = noise_cfg["reclamm_noise_params"] + if noise_cfg.get("noise_arrays_path") is not None: + fingerprint["noise_arrays_path"] = noise_cfg["noise_arrays_path"] + _attach_market_linear_noise_arrays( + fingerprint, + noise_cfg, + market_linear_noise_data, + ) + if arb_frequency is not None: + fingerprint["arb_frequency"] = arb_frequency + return fingerprint def make_params(cfg): """Build pool params from config.""" + cfg = normalize_compare_run_cfg(cfg) return { "price_ratio": jnp.array(cfg["price_ratio"]), "centeredness_margin": jnp.array(cfg["centeredness_margin"]), @@ -85,40 +560,1654 @@ def make_params(cfg): } -def run_comparison(cfg): - """Run all thermostat variants, return results dict.""" +def load_shared_price_data(configs, root=None): + """Load the shared historic price panel once for all compare runs.""" + tokens = sorted({token for cfg in configs for token in cfg["tokens"]}) + return get_historic_parquet_data(tokens, cols=["close"], root=root) + + +def run_comparison( + cfg, + price_data=None, + low_data_mode=False, + market_linear_noise_data=None, +): + """Run both interpolation variants, return results dict.""" params = make_params(cfg) results = {} - for method in ["geometric", "constant_arc_length"]: - fp = make_fingerprint(cfg, method) + for method in INTERPOLATION_METHODS: + fp = make_fingerprint( + cfg, + method, + market_linear_noise_data=market_linear_noise_data, + ) results[method] = do_run_on_historic_data( - run_fingerprint=fp, params=params + run_fingerprint=fp, + params=params, + price_data=price_data, + low_data_mode=low_data_mode, + ) + + return results + + +def _set_padded_ylim(ax, series_list, pad_ratio=0.04): + """Fit the y-axis tightly around the plotted series.""" + flat = [ + np.asarray(series, dtype=float).ravel() + for series in series_list + if np.asarray(series).size > 0 + ] + if not flat: + return + + values = np.concatenate(flat) + values = values[np.isfinite(values)] + if values.size == 0: + return + + ymin = float(values.min()) + ymax = float(values.max()) + if np.isclose(ymin, ymax): + pad = max(abs(ymin) * pad_ratio, 1e-6) + else: + pad = (ymax - ymin) * pad_ratio + ax.set_ylim(ymin - pad, ymax + pad) + + +def _cache_size(cache): + """Count memoized final-value cache entries materialised in memory.""" + return len(cache.get("_final_value_cache", {})) + + +def _comparison_cache_size(cache): + """Count memoized scalar comparison bundles.""" + return len(cache.get("_comparison_cache", {})) + + +def _heatmap_forward_cache_scope_slug(cfg): + """Build a compact cache scope slug for a shared-TVL heatmap run.""" + if cfg is None: + return "unspecified_tvl" + return f"tvl_{format_tvl_millions_slug(cfg)}" + + +def _heatmap_forward_cache_path(cfg): + """Return the parquet path for persisted scalar forward values.""" + if not HEATMAP_FORWARD_CACHE_ENABLED: + return None + return os.path.join( + HEATMAP_FORWARD_CACHE_ROOT, + HEATMAP_FORWARD_CACHE_RUN_NAME, + f"forward_values_{_heatmap_forward_cache_scope_slug(cfg)}.parquet", + ) + + +def _make_method_cache_hash(key): + """Build a compact stable digest for a method cache key.""" + return hashlib.sha256(repr(key).encode("utf-8")).hexdigest() + + +def _build_persistent_final_value_record(cfg, method, cache_key_hash, final_value): + """Build one self-describing parquet row for a cached scalar run result.""" + cfg = normalize_compare_run_cfg(cfg) + noise_cfg = resolve_reclamm_noise_settings(cfg) + return { + "cache_key_hash": str(cache_key_hash), + "final_value": float(final_value), + "method": str(method), + "enable_noise_model": bool(cfg.get("enable_noise_model", False)), + "noise_model": noise_cfg.get("noise_model"), + "price_ratio": float(cfg["price_ratio"]), + "centeredness_margin": float(cfg["centeredness_margin"]), + "daily_price_shift_exponent": float(cfg["daily_price_shift_exponent"]), + "initial_pool_value": float(get_initial_pool_value(cfg)), + "arb_frequency": get_effective_arb_frequency(cfg, noise_cfg), + } + + +def _load_persistent_final_value_cache(cache): + """Load persisted scalar forward values from parquet once per sweep cache.""" + if cache.get("_persistent_final_value_cache_loaded"): + return + + disk_cache = {} + next_batch_id = 0 + cache_path = cache.get("_persistent_final_value_cache_path") + if cache_path and os.path.exists(cache_path): + parquet_files = [] + if os.path.isdir(cache_path): + parquet_files = [ + os.path.join(cache_path, filename) + for filename in sorted(os.listdir(cache_path)) + if filename.endswith(".parquet") + ] + batch_ids = [] + for filename in os.listdir(cache_path): + if not (filename.startswith("batch_") and filename.endswith(".parquet")): + continue + token = filename[len("batch_") : -len(".parquet")] + if token.isdigit(): + batch_ids.append(int(token)) + next_batch_id = (max(batch_ids) + 1) if batch_ids else 0 + else: + parquet_files = [cache_path] + + for parquet_file in parquet_files: + frame = pd.read_parquet( + parquet_file, + columns=["cache_key_hash", "final_value"], + ) + if frame.empty: + continue + for row in frame.itertuples(index=False): + cache_key_hash = str(row.cache_key_hash) + final_value = float(row.final_value) + disk_cache[cache_key_hash] = final_value + print( + f"Loaded {len(disk_cache)} persisted heatmap forward values from {cache_path}" ) - # Geometric + centeredness-proportional scaling (scales decay duration) - fp_geo_scaled = make_fingerprint(cfg, "geometric", centeredness_scaling=True) - results["geometric_scaled"] = do_run_on_historic_data( - run_fingerprint=fp_geo_scaled, params=params + cache["_persistent_final_value_cache"] = disk_cache + cache["_persistent_final_value_next_batch_id"] = next_batch_id + cache["_persistent_final_value_cache_loaded"] = True + + +def flush_sweep_cache(cache, force=False): + """Persist newly computed scalar forward values to parquet.""" + if not HEATMAP_FORWARD_CACHE_ENABLED: + return + + pending = cache.get("_pending_persistent_final_values") + if not pending: + return + if not force and len(pending) < HEATMAP_FORWARD_CACHE_FLUSH_EVERY: + return + + _load_persistent_final_value_cache(cache) + disk_cache = cache.setdefault("_persistent_final_value_cache", {}) + batch_records = [] + for cache_key_hash, record in pending.items(): + normalized = dict(record) + normalized["cache_key_hash"] = str(cache_key_hash) + normalized["final_value"] = float(normalized["final_value"]) + disk_cache[cache_key_hash] = normalized["final_value"] + batch_records.append(normalized) + + cache_path = cache.get("_persistent_final_value_cache_path") + if cache_path is None: + pending.clear() + return + + if os.path.exists(cache_path) and not os.path.isdir(cache_path): + raise RuntimeError( + f"Persistent cache path {cache_path} already exists as a file. " + "Use a fresh cache namespace for append-only parquet shards." + ) + + os.makedirs(cache_path, exist_ok=True) + batch_records.sort(key=lambda record: record["cache_key_hash"]) + payload = { + column: [record.get(column) for record in batch_records] + for column in PERSISTED_FORWARD_VALUE_COLUMNS + } + payload["final_value"] = np.asarray(payload["final_value"], dtype=np.float64) + frame = pd.DataFrame(payload) + batch_id = int(cache.setdefault("_persistent_final_value_next_batch_id", 0)) + batch_path = os.path.join(cache_path, f"batch_{batch_id:08d}.parquet") + cache["_persistent_final_value_next_batch_id"] = batch_id + 1 + frame.to_parquet(batch_path, index=False, compression="zstd") + print( + f"Persisted {len(pending)} new heatmap forward values to {batch_path} " + f"({len(disk_cache)} total cached values)." ) + pending.clear() + + +def make_sweep_cache( + price_data, + cache_scope_cfg=None, + market_linear_noise_data=None, +): + """Create a shared cache for heatmap and line sweeps.""" + cache = { + "_shared_price_data": price_data, + "_shared_market_linear_noise_data": market_linear_noise_data, + "_final_value_cache": {}, + "_comparison_cache": {}, + "_pending_persistent_final_values": {}, + "_persistent_final_value_cache": {}, + "_persistent_final_value_next_batch_id": 0, + "_persistent_final_value_cache_loaded": False, + "_persistent_final_value_cache_path": _heatmap_forward_cache_path( + cache_scope_cfg + ), + } + return cache + + +def _missing_artifacts(progress_label, filenames): + """Report which plot artifacts still need to be generated.""" + missing = [filename for filename in filenames if not os.path.exists(filename)] + if not missing: + print(f"[{progress_label}] skipping sweep: all artifacts already exist.") + return set() + + existing_count = len(filenames) - len(missing) + if existing_count: + print( + f"[{progress_label}] reusing {existing_count}/{len(filenames)} " + "existing artifacts; generating the missing outputs." + ) + return set(missing) + + +def _speed_cache_key(speed): + """Stable cache token for optional arc-length speed.""" + if speed is None: + return None + return round(float(speed), 12) - # Arc-length + centeredness-proportional scaling (scales speed) - fp_cal_scaled = make_fingerprint(cfg, "constant_arc_length", centeredness_scaling=True) - results["cal_scaled"] = do_run_on_historic_data( - run_fingerprint=fp_cal_scaled, params=params + +def _make_method_cache_key(cfg, method): + """Cache key for a single-method final-value run.""" + cfg = normalize_compare_run_cfg(cfg) + noise_cfg = resolve_reclamm_noise_settings(cfg) + arb_frequency = get_effective_arb_frequency(cfg, noise_cfg) + key = ( + method, + tuple(str(token) for token in cfg["tokens"]), + str(cfg["start"]), + str(cfg["end"]), + round(float(cfg["fees"]), 12), + bool(cfg.get("enable_noise_model", False)), + round(float(cfg["price_ratio"]), 6), + round(float(cfg["centeredness_margin"]), 6), + round(float(cfg["daily_price_shift_exponent"]), 6), + round(get_initial_pool_value(cfg), 2), + noise_cfg.get("noise_cache_key"), + None if arb_frequency is None else int(arb_frequency), + round( + float( + cfg.get( + "gas_cost", + DEFAULT_GAS_COST if cfg.get("enable_noise_model", False) else 0.0, + ) + ), + 6, + ), + round( + float( + cfg.get( + "protocol_fee_split", + DEFAULT_PROTOCOL_FEE_SPLIT if cfg.get("enable_noise_model", False) else 0.0, + ) + ), + 6, + ), ) + if method == "constant_arc_length": + key += (_speed_cache_key(cfg.get("arc_length_speed")),) + return key + + +def _nearest_price_row(price_data, start_ts): + """Select the closest available price row to the requested start timestamp.""" + if len(price_data.index) == 0: + raise ValueError("price_data is empty") + + if isinstance(price_data.index, pd.DatetimeIndex): + target_ts = start_ts + index_tz = getattr(price_data.index, "tz", None) + if index_tz is not None and target_ts.tzinfo is None: + target_ts = target_ts.tz_localize(index_tz) + elif index_tz is None and target_ts.tzinfo is not None: + target_ts = target_ts.tz_convert(None) + target_value = int(target_ts.value) + index_values = price_data.index.asi8 + else: + target_value = int(start_ts.timestamp() * 1000.0) + index_values = price_data.index.to_numpy(dtype=np.int64) + + row_idx = int(np.searchsorted(index_values, target_value, side="left")) + if row_idx >= len(index_values): + row_idx = len(index_values) - 1 + elif row_idx > 0 and index_values[row_idx] != target_value: + prev_idx = row_idx - 1 + if abs(int(index_values[prev_idx]) - target_value) <= abs( + int(index_values[row_idx]) - target_value + ): + row_idx = prev_idx + + row = price_data.iloc[row_idx] + if isinstance(row, pd.DataFrame): + row = row.iloc[0] + return row + + +def _make_comparison_cache_key(cfg, launch_final_values): + """Cache key for scalar heatmap metrics at a single parameter point.""" + noise_cfg = make_noise_variant_cfg(cfg, True) + arb_only_cfg = make_noise_variant_cfg(cfg, False) + key = [ + _make_method_cache_key(noise_cfg, "geometric"), + _make_method_cache_key(arb_only_cfg, "geometric"), + round(float(launch_final_values["geometric"]), 6), + ] + if RUN_CONSTANT_ARC_LENGTH: + key.extend( + [ + _make_method_cache_key(noise_cfg, "constant_arc_length"), + _make_method_cache_key(arb_only_cfg, "constant_arc_length"), + round(float(launch_final_values["constant_arc_length"]), 6), + ] + ) + return tuple(key) + + +def _run_method_final_value_cached(cfg, method, cache): + """Memoize final value for a single interpolation method.""" + final_value_cache = cache.setdefault("_final_value_cache", {}) + key = _make_method_cache_key(cfg, method) + if key in final_value_cache: + return final_value_cache[key] + + _load_persistent_final_value_cache(cache) + key_hash = _make_method_cache_hash(key) + persisted_cache = cache.setdefault("_persistent_final_value_cache", {}) + if key_hash in persisted_cache: + final_value_cache[key] = persisted_cache[key_hash] + return final_value_cache[key] + + result = do_run_on_historic_data( + run_fingerprint=make_fingerprint( + cfg, + method, + market_linear_noise_data=cache.get("_shared_market_linear_noise_data"), + ), + params=make_params(cfg), + price_data=cache["_shared_price_data"], + low_data_mode=True, + ) + final_value_cache[key] = float(result["final_value"]) + cache.setdefault("_pending_persistent_final_values", {})[key_hash] = ( + _build_persistent_final_value_record( + cfg=cfg, + method=method, + cache_key_hash=key_hash, + final_value=final_value_cache[key], + ) + ) + flush_sweep_cache(cache, force=False) + del result + gc.collect() + return final_value_cache[key] + + +def extract_comparison_metrics_from_final_values( + geo_final, arc_final, launch_final_values +): + """Summarize scalar comparison metrics from final values only.""" + return { + "efficiency_pct": (arc_final / max(abs(geo_final), 1e-12) - 1.0) * 100.0, + "launch_geometric_efficiency_pct": ( + arc_final / max(abs(launch_final_values["geometric"]), 1e-12) - 1.0 + ) + * 100.0, + "geometric_vs_launch_geometric_pct": ( + geo_final / max(abs(launch_final_values["geometric"]), 1e-12) - 1.0 + ) + * 100.0, + "constant_arc_vs_launch_constant_arc_pct": ( + arc_final + / max(abs(launch_final_values["constant_arc_length"]), 1e-12) + - 1.0 + ) + * 100.0, + } + + +def _load_required_heatmap_final_values(cfg, cache, metric_keys): + """Load only the cached final values needed for the requested heatmap metrics.""" + required_sources = set() + for metric_key in metric_keys: + required_sources.update(HEATMAP_METRIC_DEPENDENCIES[metric_key]) + + if not RUN_CONSTANT_ARC_LENGTH and any( + source.endswith("constant_arc") for source in required_sources + ): + raise ValueError( + "Constant-arc heatmap metric requested while RUN_CONSTANT_ARC_LENGTH=False" + ) + + final_values = {} + noise_cfg = None + arb_only_cfg = None + + if any(source.startswith("noise_") for source in required_sources): + noise_cfg = make_noise_variant_cfg(cfg, True) + if any(source.startswith("arb_") for source in required_sources): + arb_only_cfg = make_noise_variant_cfg(cfg, False) + + if "noise_geometric" in required_sources: + final_values["noise_geometric"] = _run_method_final_value_cached( + noise_cfg, + "geometric", + cache, + ) + if "noise_constant_arc" in required_sources: + final_values["noise_constant_arc"] = _run_method_final_value_cached( + noise_cfg, + "constant_arc_length", + cache, + ) + if "arb_geometric" in required_sources: + final_values["arb_geometric"] = _run_method_final_value_cached( + arb_only_cfg, + "geometric", + cache, + ) + if "arb_constant_arc" in required_sources: + final_values["arb_constant_arc"] = _run_method_final_value_cached( + arb_only_cfg, + "constant_arc_length", + cache, + ) + return final_values + + +def extract_heatmap_metrics_from_mode_final_values( + metric_keys, + final_values, + launch_final_values, +): + """Collect the requested scalar heatmap metrics from cached final values.""" + metrics = {} + + if "efficiency_pct" in metric_keys: + metrics["efficiency_pct"] = ( + final_values["noise_constant_arc"] + / max(abs(final_values["noise_geometric"]), 1e-12) + - 1.0 + ) * 100.0 + + if "launch_geometric_efficiency_pct" in metric_keys: + metrics["launch_geometric_efficiency_pct"] = ( + final_values["noise_constant_arc"] + / max(abs(launch_final_values["geometric"]), 1e-12) + - 1.0 + ) * 100.0 + + if "geometric_vs_launch_geometric_pct" in metric_keys: + metrics["geometric_vs_launch_geometric_pct"] = ( + final_values["noise_geometric"] + / max(abs(launch_final_values["geometric"]), 1e-12) + - 1.0 + ) * 100.0 + + if "constant_arc_vs_launch_constant_arc_pct" in metric_keys: + metrics["constant_arc_vs_launch_constant_arc_pct"] = ( + final_values["noise_constant_arc"] + / max(abs(launch_final_values["constant_arc_length"]), 1e-12) + - 1.0 + ) * 100.0 + + if "noise_geometric_final_value_musd" in metric_keys: + metrics["noise_geometric_final_value_musd"] = ( + final_values["noise_geometric"] / 1e6 + ) + + if "noise_constant_arc_final_value_musd" in metric_keys: + metrics["noise_constant_arc_final_value_musd"] = ( + final_values["noise_constant_arc"] / 1e6 + ) + + if "noise_vs_arb_geometric_improvement_pct" in metric_keys: + metrics["noise_vs_arb_geometric_improvement_pct"] = ( + final_values["noise_geometric"] + / max(abs(final_values["arb_geometric"]), 1e-12) + - 1.0 + ) * 100.0 + + if "noise_vs_arb_constant_arc_improvement_pct" in metric_keys: + metrics["noise_vs_arb_constant_arc_improvement_pct"] = ( + final_values["noise_constant_arc"] + / max(abs(final_values["arb_constant_arc"]), 1e-12) + - 1.0 + ) * 100.0 + + return metrics + + +def extract_comparison_metrics(results, launch_final_values): + """Summarize scalar heatmap metrics for a pair of runs.""" + geo = results["geometric"] + arc = results["constant_arc_length"] + + geo_final = float(geo["final_value"]) + arc_final = float(arc["final_value"]) + + return extract_comparison_metrics_from_final_values( + geo_final, + arc_final, + launch_final_values=launch_final_values, + ) + + +def run_comparison_cached(cfg, cache, launch_final_values, metric_keys): + """Memoize scalar heatmap metrics across heatmap sweeps.""" + requested_metric_keys = tuple(dict.fromkeys(metric_keys)) + comparison_cache = cache.setdefault("_comparison_cache", {}) + cache_key = _make_comparison_cache_key(cfg, launch_final_values) + cached_metrics = comparison_cache.setdefault(cache_key, {}) + missing_metric_keys = [ + metric_key for metric_key in requested_metric_keys if metric_key not in cached_metrics + ] + if missing_metric_keys: + final_values = _load_required_heatmap_final_values( + cfg, + cache, + missing_metric_keys, + ) + cached_metrics.update( + extract_heatmap_metrics_from_mode_final_values( + missing_metric_keys, + final_values, + launch_final_values=launch_final_values, + ) + ) + return { + metric_key: cached_metrics[metric_key] for metric_key in requested_metric_keys + } + + +def build_heatmap_matrices( + x_values, + y_values, + x_key, + y_key, + base_cfg, + metric_keys, + cache, + progress_label, + launch_final_values, +): + """Evaluate multiple metrics over a 2D parameter grid in one pass.""" + data = { + metric_key: np.zeros((len(y_values), len(x_values)), dtype=float) + for metric_key in metric_keys + } + total_points = len(y_values) * len(x_values) + + print( + f"[{progress_label}] start: {len(y_values)} rows x {len(x_values)} cols " + f"= {total_points} parameter points" + ) + + for yi, y_value in enumerate(y_values): + final_cache_before_row = _cache_size(cache) + comparison_cache_before_row = _comparison_cache_size(cache) + for xi, x_value in enumerate(x_values): + cfg = dict(base_cfg) + cfg[x_key] = float(x_value) + cfg[y_key] = float(y_value) + metrics = run_comparison_cached( + cfg, + cache, + launch_final_values=launch_final_values, + metric_keys=metric_keys, + ) + for metric_key in metric_keys: + data[metric_key][yi, xi] = metrics[metric_key] + + completed_points = (yi + 1) * len(x_values) + row_new_final_entries = _cache_size(cache) - final_cache_before_row + row_new_comparisons = ( + _comparison_cache_size(cache) - comparison_cache_before_row + ) + row_pct = completed_points / total_points * 100.0 + flush_sweep_cache(cache, force=True) + print( + f"[{progress_label}] row {yi + 1}/{len(y_values)} complete " + f"({y_key}={float(y_value):.4f}, {completed_points}/{total_points} " + f"points, {row_pct:.1f}%, {row_new_final_entries} new final-value cache entries, " + f"{row_new_comparisons} new comparison bundles)" + ) + + print( + f"[{progress_label}] done: " + + ", ".join( + ( + f"{metric_key} min={float(np.nanmin(data[metric_key])):.4f}, " + f"max={float(np.nanmax(data[metric_key])):.4f}" + ) + for metric_key in metric_keys + ) + + ( + f", final_value_cache_size={_cache_size(cache)}, " + f"comparison_cache_size={_comparison_cache_size(cache)}" + ) + ) + + return data + + +def build_metric_curve( + x_values, + x_key, + base_cfg, + metric_key, + cache, + launch_final_values, +): + """Evaluate one metric over a 1D sweep.""" + data = np.zeros(len(x_values), dtype=float) + for xi, x_value in enumerate(x_values): + cfg = dict(base_cfg) + cfg[x_key] = float(x_value) + metrics = run_comparison_cached( + cfg, + cache, + launch_final_values=launch_final_values, + metric_keys=(metric_key,), + ) + data[xi] = metrics[metric_key] + flush_sweep_cache(cache, force=True) + return data + + +def _compute_axis_edges(values, scale="linear"): + """Convert axis centers to cell edges for pcolormesh.""" + values = np.asarray(values, dtype=float) + if values.size == 1: + if scale == "log": + return np.array([values[0] / np.sqrt(10.0), values[0] * np.sqrt(10.0)]) + pad = max(abs(values[0]) * 0.5, 1.0) + return np.array([values[0] - pad, values[0] + pad]) + + if scale == "log": + log_values = np.log10(values) + edges = np.empty(values.size + 1, dtype=float) + edges[1:-1] = 0.5 * (log_values[:-1] + log_values[1:]) + edges[0] = log_values[0] - 0.5 * (log_values[1] - log_values[0]) + edges[-1] = log_values[-1] + 0.5 * (log_values[-1] - log_values[-2]) + return 10.0 ** edges + + edges = np.empty(values.size + 1, dtype=float) + edges[1:-1] = 0.5 * (values[:-1] + values[1:]) + edges[0] = values[0] - 0.5 * (values[1] - values[0]) + edges[-1] = values[-1] + 0.5 * (values[-1] - values[-2]) + return edges + + +def build_fixed_slice_variants(values): + """Pick four representative quarter-range slices from a sweep grid.""" + values = np.asarray(values, dtype=float) + if values.size < len(FIXED_SLICE_FRACTIONS): + raise ValueError("Need at least four grid points to build fixed slices") + + variants = [] + used_indices = set() + for idx, fraction in enumerate(FIXED_SLICE_FRACTIONS): + target_index = int(round(fraction * (values.size - 1))) + while target_index in used_indices and target_index + 1 < values.size: + target_index += 1 + while target_index in used_indices and target_index - 1 >= 0: + target_index -= 1 + if target_index in used_indices: + raise ValueError("Could not build four unique fixed slices from sweep grid") + used_indices.add(target_index) + variants.append( + { + "index": target_index, + "fraction": fraction, + "label": FIXED_SLICE_LABELS[idx], + "slug": f"q{idx + 1}", + "value": float(values[target_index]), + } + ) + return variants + + +def _pair_slice_suffix(pair, slice_variant): + """Build a stable artifact suffix for a pairwise fixed-variable slice.""" + return f"{pair['slug']}_{pair['fixed_slug']}_{slice_variant['slug']}" + + +def _build_heatmap_norm( + data_arrays, + center_zero, + color_norm=None, + symlog_linthresh=None, +): + """Build a color normalizer shared by 2D and 3D heatmaps.""" + finite_parts = [] + for data in data_arrays: + finite = np.asarray(data, dtype=float) + finite = finite[np.isfinite(finite)] + if finite.size: + finite_parts.append(finite) + finite = np.concatenate(finite_parts) if finite_parts else np.array([], dtype=float) + + if center_zero: + if finite.size == 0: + vmax = 1.0 + else: + vmax = max(abs(float(finite.min())), abs(float(finite.max())), 1e-9) + if ( + color_norm == "symlog" + and symlog_linthresh is not None + and vmax > symlog_linthresh + ): + return SymLogNorm( + linthresh=symlog_linthresh, + linscale=1.0, + vmin=-vmax, + vmax=vmax, + base=10.0, + ) + return TwoSlopeNorm(vcenter=0.0, vmin=-vmax, vmax=vmax) + + if finite.size == 0: + vmin, vmax = 0.0, 1.0 + else: + vmin = float(finite.min()) + vmax = float(finite.max()) + if np.isclose(vmin, vmax): + pad = max(abs(vmin) * 0.01, 1e-9) + vmin -= pad + vmax += pad + return Normalize(vmin=vmin, vmax=vmax) + + +def get_pair_heatmap_metric_specs(): + """Return the standard thermostat pairwise heatmap metrics.""" + metric_specs = [ + { + "key": "efficiency_pct", + "title": "Efficiency vs heatmap geometric", + "colorbar_label": "Const Arc - heatmap Geo (% of heatmap geometric final value)", + "slug": "efficiency", + "center_zero": True, + "cmap": "RdYlGn", + }, + { + "key": "launch_geometric_efficiency_pct", + "title": "Efficiency vs launch-style geometric", + "colorbar_label": "Const Arc - launch Geo (% of launch geometric final value)", + "slug": "launch_geometric_efficiency", + "center_zero": True, + "cmap": "RdYlGn", + }, + { + "key": "geometric_vs_launch_geometric_pct", + "title": "Geometric tuning vs launch-style geometric", + "colorbar_label": "Candidate Geo - launch Geo (% of launch geometric final value)", + "slug": "geometric_vs_launch_geometric", + "center_zero": True, + "cmap": "RdYlGn", + }, + { + "key": "constant_arc_vs_launch_constant_arc_pct", + "title": "Const arc tuning vs launch-style const arc", + "colorbar_label": "Candidate Const Arc - launch Const Arc (% of launch const arc final value)", + "slug": "constant_arc_vs_launch_constant_arc", + "center_zero": True, + "cmap": "RdYlGn", + }, + { + "key": "noise_geometric_final_value_musd", + "title": "Geometric final value with noise model", + "colorbar_label": "Geometric final value with noise model ($M)", + "slug": "noise_geometric_final_value", + "center_zero": False, + "cmap": "viridis", + }, + { + "key": "noise_constant_arc_final_value_musd", + "title": "Const arc final value with noise model", + "colorbar_label": "Const Arc final value with noise model ($M)", + "slug": "noise_constant_arc_final_value", + "center_zero": False, + "cmap": "viridis", + }, + { + "key": "noise_vs_arb_geometric_improvement_pct", + "title": "Noise-model improvement over arb-only (geometric)", + "colorbar_label": "Noise-model Geo - arb-only Geo (% of arb-only final value)", + "slug": "noise_vs_arb_geometric_improvement", + "center_zero": True, + "cmap": "RdYlGn", + }, + { + "key": "noise_vs_arb_constant_arc_improvement_pct", + "title": "Noise-model improvement over arb-only (const arc)", + "colorbar_label": "Noise-model Const Arc - arb-only Const Arc (% of arb-only final value)", + "slug": "noise_vs_arb_constant_arc_improvement", + "center_zero": True, + "cmap": "RdYlGn", + }, + ] + for spec in metric_specs: + if spec["center_zero"]: + spec["color_norm"] = CENTER_ZERO_HEATMAP_COLOR_NORM + spec["symlog_linthresh"] = CENTER_ZERO_HEATMAP_SYMLOG_LINTHRESH + spec["artifact_tag"] = CENTER_ZERO_HEATMAP_COLOR_TAG + if not RUN_CONSTANT_ARC_LENGTH: + metric_specs = [ + spec + for spec in metric_specs + if spec["key"] in GEOMETRIC_ONLY_HEATMAP_METRIC_KEYS + ] + return metric_specs + + +def get_pair_heatmap_specs(base_cfg): + """Return the three pairwise thermostat heatmap families plus slice settings.""" + fixed_slice_variants = { + "price_ratio": build_fixed_slice_variants(HEATMAP_PRICE_RATIOS), + "centeredness_margin": build_fixed_slice_variants(HEATMAP_MARGINS), + "daily_price_shift_exponent": build_fixed_slice_variants( + HEATMAP_SHIFT_EXPONENTS + ), + } + return [ + { + "slug": "price_ratio_vs_margin", + "x_values": HEATMAP_PRICE_RATIOS, + "y_values": HEATMAP_MARGINS, + "x_key": "price_ratio", + "y_key": "centeredness_margin", + "x_label": "Price ratio", + "y_label": "Centeredness margin", + "xticks": PRICE_RATIO_TICKS, + "yticks": MARGIN_TICKS, + "fixed_key": "daily_price_shift_exponent", + "fixed_label": "Shift exponent", + "fixed_slug": "shift_exp", + "fixed_slices": fixed_slice_variants["daily_price_shift_exponent"], + }, + { + "slug": "shift_exp_vs_margin", + "x_values": HEATMAP_SHIFT_EXPONENTS, + "y_values": HEATMAP_MARGINS, + "x_key": "daily_price_shift_exponent", + "y_key": "centeredness_margin", + "x_label": "Shift exponent", + "y_label": "Centeredness margin", + "xticks": SHIFT_EXPONENT_TICKS, + "yticks": MARGIN_TICKS, + "fixed_key": "price_ratio", + "fixed_label": "Price ratio", + "fixed_slug": "price_ratio", + "fixed_slices": fixed_slice_variants["price_ratio"], + }, + { + "slug": "price_ratio_vs_shift_exp", + "x_values": HEATMAP_PRICE_RATIOS, + "y_values": HEATMAP_SHIFT_EXPONENTS, + "x_key": "price_ratio", + "y_key": "daily_price_shift_exponent", + "x_label": "Price ratio", + "y_label": "Shift exponent", + "xticks": PRICE_RATIO_TICKS, + "yticks": SHIFT_EXPONENT_TICKS, + "fixed_key": "centeredness_margin", + "fixed_label": "Centeredness margin", + "fixed_slug": "margin", + "fixed_slices": fixed_slice_variants["centeredness_margin"], + }, + ] + + +def plot_heatmap( + data, + x_values, + y_values, + x_label, + y_label, + title, + colorbar_label, + filename, + xticks=None, + yticks=None, + xscale="linear", + center_zero=True, + cmap=None, + color_norm=None, + symlog_linthresh=None, +): + """Render and save a single heatmap.""" + norm = _build_heatmap_norm( + [data], + center_zero=center_zero, + color_norm=color_norm, + symlog_linthresh=symlog_linthresh, + ) + cmap_name = cmap or ("RdYlGn" if center_zero else "viridis") + + x_edges = _compute_axis_edges(x_values, scale=xscale) + y_edges = _compute_axis_edges(y_values, scale="linear") + + fig, ax = plt.subplots(figsize=(8.5, 6.0)) + im = ax.pcolormesh( + x_edges, + y_edges, + data, + cmap=cmap_name, + norm=norm, + shading="auto", + ) + + ax.set_xlabel(x_label) + ax.set_ylabel(y_label) + ax.set_title(title) + if xscale == "log": + ax.set_xscale("log") + ax.set_xticks(np.asarray(xticks if xticks is not None else x_values, dtype=float)) + ax.set_yticks(np.asarray(yticks if yticks is not None else y_values, dtype=float)) + ax.grid(False) + + cbar = fig.colorbar(im, ax=ax) + cbar.set_label(colorbar_label) + + plt.tight_layout() + plt.savefig(filename, dpi=150) + print(f"Saved {filename}") + plt.close(fig) + + +def plot_three_variable_heatmap_3d( + price_margin_data, + shift_margin_data, + price_shift_data, + fixed_price_ratio, + fixed_margin, + fixed_shift_exponent, + title, + colorbar_label, + filename, + center_zero=True, + cmap=None, + color_norm=None, + symlog_linthresh=None, +): + """Render orthogonal 3D heatmap surfaces across the three thermostat variables.""" + norm = _build_heatmap_norm( + [price_margin_data, shift_margin_data, price_shift_data], + center_zero=center_zero, + color_norm=color_norm, + symlog_linthresh=symlog_linthresh, + ) + cmap_name = cmap or ("RdYlGn" if center_zero else "viridis") + cmap_obj = plt.get_cmap(cmap_name) + + price_margin_x, price_margin_y = np.meshgrid(HEATMAP_PRICE_RATIOS, HEATMAP_MARGINS) + price_margin_z = np.full_like(price_margin_x, fixed_shift_exponent, dtype=float) + + shift_margin_z, shift_margin_y = np.meshgrid( + HEATMAP_SHIFT_EXPONENTS, + HEATMAP_MARGINS, + ) + shift_margin_x = np.full_like(shift_margin_z, fixed_price_ratio, dtype=float) + + price_shift_x, price_shift_z = np.meshgrid( + HEATMAP_PRICE_RATIOS, + HEATMAP_SHIFT_EXPONENTS, + ) + price_shift_y = np.full_like(price_shift_x, fixed_margin, dtype=float) + + fig = plt.figure(figsize=(10.5, 7.2)) + ax = fig.add_subplot(111, projection="3d") + ax.set_facecolor("white") + fig.patch.set_facecolor("white") + + ax.plot_surface( + price_margin_x, + price_margin_y, + price_margin_z, + facecolors=cmap_obj(norm(np.asarray(price_margin_data, dtype=float))), + shade=False, + ) + ax.plot_surface( + shift_margin_x, + shift_margin_y, + shift_margin_z, + facecolors=cmap_obj(norm(np.asarray(shift_margin_data, dtype=float))), + shade=False, + ) + ax.plot_surface( + price_shift_x, + price_shift_y, + price_shift_z, + facecolors=cmap_obj(norm(np.asarray(price_shift_data, dtype=float))), + shade=False, + ) + + ax.set_xlim(float(HEATMAP_PRICE_RATIOS.min()), float(HEATMAP_PRICE_RATIOS.max())) + ax.set_ylim(float(HEATMAP_MARGINS.min()), float(HEATMAP_MARGINS.max())) + ax.set_zlim( + float(HEATMAP_SHIFT_EXPONENTS.min()), + float(HEATMAP_SHIFT_EXPONENTS.max()), + ) + ax.set_xlabel("Price ratio") + ax.set_ylabel("Centeredness margin") + ax.set_zlabel("Shift exponent") + ax.set_xticks(PRICE_RATIO_TICKS) + ax.set_yticks(MARGIN_TICKS[::2]) + ax.set_zticks(SHIFT_EXPONENT_TICKS) + ax.set_title(title) + ax.grid(False) + ax.view_init(elev=THREE_D_VIEW_ELEVATION, azim=THREE_D_VIEW_AZIMUTH) + try: + ax.set_box_aspect( + ( + float(HEATMAP_PRICE_RATIOS.max() - HEATMAP_PRICE_RATIOS.min()), + float(HEATMAP_MARGINS.max() - HEATMAP_MARGINS.min()), + float( + HEATMAP_SHIFT_EXPONENTS.max() - HEATMAP_SHIFT_EXPONENTS.min() + ), + ) + ) + except AttributeError: + pass + + sm = ScalarMappable(norm=norm, cmap=cmap_obj) + sm.set_array([]) + cbar = fig.colorbar(sm, ax=ax, fraction=0.03, pad=0.1, shrink=0.82) + cbar.set_label(colorbar_label) + + plt.tight_layout() + plt.savefig(filename, dpi=150) + print(f"Saved {filename}") + plt.close(fig) + + +def plot_arc_speed_line_chart( + data, + x_values, + y_values, + y_label, + title, + filename, + launch_curve, + launch_auto_speed=None, +): + """Plot thin multi-series efficiency lines over the arc-speed sweep.""" + fig, ax = plt.subplots(figsize=(10.5, 5.75)) + cmap = plt.cm.viridis + colors = cmap(np.linspace(0.0, 1.0, len(y_values))) + plotted_series = [] + + for yi, (y_value, color) in enumerate(zip(y_values, colors)): + series = np.asarray(data[yi], dtype=float) + plotted_series.append(series) + ax.plot( + x_values, + series, + color=color, + linewidth=SWEEP_LINE_WIDTH, + alpha=0.8, + ) + + launch_curve = np.asarray(launch_curve, dtype=float) + plotted_series.append(launch_curve) + ax.plot( + x_values, + launch_curve, + color="black", + linewidth=REFERENCE_LINE_WIDTH, + alpha=0.9, + label="Current launch config", + ) + if launch_auto_speed is not None: + ax.axvline( + float(launch_auto_speed), + color="black", + ls=":", + linewidth=0.8, + alpha=0.7, + label="Launch auto-cal speed", + ) + + ax.axhline(0.0, color="gray", ls="--", linewidth=0.8, alpha=0.5) + ax.set_xscale("log") + ax.set_xticks(ARC_LENGTH_SPEED_TICKS) + ax.set_xlabel("Arc-length speed") + ax.set_ylabel("Efficiency vs geometric (%)") + ax.set_title(title) + _set_padded_ylim(ax, plotted_series, pad_ratio=0.08) + ax.grid(True, alpha=0.25) + ax.legend(fontsize=8) + + sm = ScalarMappable( + norm=Normalize(vmin=float(np.min(y_values)), vmax=float(np.max(y_values))), + cmap=cmap, + ) + sm.set_array([]) + cbar = fig.colorbar(sm, ax=ax) + cbar.set_label(y_label) + + plt.tight_layout() + plt.savefig(filename, dpi=150) + print(f"Saved {filename}") + plt.close(fig) + + +def generate_heatmaps(base_cfg, price_data, launch_final_values, cache=None): + """Generate pairwise heatmaps for thermostat tuning and noise-vs-arb effects.""" + owns_cache = cache is None + if cache is None: + cache = make_sweep_cache(price_data, cache_scope_cfg=base_cfg) + metric_specs = get_pair_heatmap_metric_specs() + pair_specs = get_pair_heatmap_specs(base_cfg) + metric_spec_map = {spec["key"]: spec for spec in metric_specs} + slice_count = len(pair_specs[0]["fixed_slices"]) if pair_specs else 0 + + if RUN_CONSTANT_ARC_LENGTH: + print( + "Using launch-style benchmarks " + f"Geo=${launch_final_values['geometric']:,.0f}, " + f"Const Arc=${launch_final_values['constant_arc_length']:,.0f}, " + f"TVL={format_tvl_millions_label(base_cfg)}." + ) + print( + "Running {count} heatmap pair sweeps sequentially " + "(3 pair grids x {slice_count} fixed-variable quarter slices; " + "cached noise-model runs are reused across the absolute, launch, " + "and arb-only comparison outputs).".format( + count=len(pair_specs) * slice_count, + slice_count=slice_count, + ) + ) + else: + print( + "Using launch-style geometric benchmark " + f"Geo=${launch_final_values['geometric']:,.0f}, " + f"TVL={format_tvl_millions_label(base_cfg)}." + ) + print( + "RUN_CONSTANT_ARC_LENGTH=False, so only geometric heatmaps will be generated " + f"across {len(pair_specs) * slice_count} fixed-variable pair sweeps." + ) + + for pair in pair_specs: + for slice_variant in pair["fixed_slices"]: + pair_suffix = _pair_slice_suffix(pair, slice_variant) + slice_cfg = dict(base_cfg) + slice_cfg[pair["fixed_key"]] = float(slice_variant["value"]) + output_files = { + spec["key"]: heatmap_artifact_filename( + spec, + base_cfg, + suffix=pair_suffix, + ) + for spec in metric_specs + } + missing_files = _missing_artifacts( + pair_suffix, + list(output_files.values()), + ) + if not missing_files: + continue + + missing_metric_keys = [ + spec["key"] + for spec in metric_specs + if output_files[spec["key"]] in missing_files + ] + data_by_metric = build_heatmap_matrices( + x_values=pair["x_values"], + y_values=pair["y_values"], + x_key=pair["x_key"], + y_key=pair["y_key"], + base_cfg=slice_cfg, + metric_keys=missing_metric_keys, + cache=cache, + progress_label=pair_suffix, + launch_final_values=launch_final_values, + ) + print(f"[{pair_suffix}] plotting missing heatmaps...") + for metric_key in missing_metric_keys: + spec = metric_spec_map[metric_key] + plot_heatmap( + data=data_by_metric[metric_key], + x_values=pair["x_values"], + y_values=pair["y_values"], + x_label=pair["x_label"], + y_label=pair["y_label"], + title=( + f"{spec['title']}: {pair['fixed_label']} {slice_variant['label']} " + f"slice fixed at {format_heatmap_param_value(slice_variant['value'])} | " + f"TVL {format_tvl_millions_label(base_cfg)}" + ), + colorbar_label=spec["colorbar_label"], + filename=output_files[metric_key], + xticks=pair["xticks"], + yticks=pair["yticks"], + center_zero=spec["center_zero"], + cmap=spec["cmap"], + color_norm=spec.get("color_norm"), + symlog_linthresh=spec.get("symlog_linthresh"), + ) + del data_by_metric + gc.collect() + + if owns_cache: + flush_sweep_cache(cache, force=True) + cache.clear() + gc.collect() + print("Released heatmap metric cache.") + + +def generate_three_variable_3d_heatmaps( + base_cfg, + price_data, + launch_final_values, + cache=None, +): + """Render 3D thermostat heatmaps from the three pairwise quarter slices.""" + owns_cache = cache is None + if cache is None: + cache = make_sweep_cache(price_data, cache_scope_cfg=base_cfg) + + metric_specs = get_pair_heatmap_metric_specs() + metric_spec_map = {spec["key"]: spec for spec in metric_specs} + pair_specs = get_pair_heatmap_specs(base_cfg) + pair_by_fixed_key = {pair["fixed_key"]: pair for pair in pair_specs} + price_margin_pair = pair_by_fixed_key["daily_price_shift_exponent"] + shift_margin_pair = pair_by_fixed_key["price_ratio"] + price_shift_pair = pair_by_fixed_key["centeredness_margin"] + slice_count = len(price_margin_pair["fixed_slices"]) + + def build_pair_slice_data(pair, slice_variant, metric_keys): + pair_cfg = dict(base_cfg) + pair_cfg[pair["fixed_key"]] = float(slice_variant["value"]) + return build_heatmap_matrices( + x_values=pair["x_values"], + y_values=pair["y_values"], + x_key=pair["x_key"], + y_key=pair["y_key"], + base_cfg=pair_cfg, + metric_keys=metric_keys, + cache=cache, + progress_label=f"3d_{_pair_slice_suffix(pair, slice_variant)}", + launch_final_values=launch_final_values, + ) + + print( + "\nGenerating 3D thermostat heatmaps " + f"({slice_count} quarter-slice variants, TVL={format_tvl_millions_label(base_cfg)})..." + ) + + for slice_idx in range(slice_count): + shift_slice = price_margin_pair["fixed_slices"][slice_idx] + price_slice = shift_margin_pair["fixed_slices"][slice_idx] + margin_slice = price_shift_pair["fixed_slices"][slice_idx] + slice_slug = shift_slice["slug"] + slice_label = shift_slice["label"] + + output_files = { + spec["key"]: three_d_heatmap_artifact_filename( + spec, + base_cfg, + suffix=f"slice_{slice_slug}", + ) + for spec in metric_specs + } + missing_files = _missing_artifacts( + f"3d_slice_{slice_slug}", + list(output_files.values()), + ) + if not missing_files: + continue + + missing_metric_keys = [ + spec["key"] + for spec in metric_specs + if output_files[spec["key"]] in missing_files + ] + price_margin_data = build_pair_slice_data( + price_margin_pair, + shift_slice, + missing_metric_keys, + ) + shift_margin_data = build_pair_slice_data( + shift_margin_pair, + price_slice, + missing_metric_keys, + ) + price_shift_data = build_pair_slice_data( + price_shift_pair, + margin_slice, + missing_metric_keys, + ) + + for metric_key in missing_metric_keys: + spec = metric_spec_map[metric_key] + plot_three_variable_heatmap_3d( + price_margin_data=price_margin_data[metric_key], + shift_margin_data=shift_margin_data[metric_key], + price_shift_data=price_shift_data[metric_key], + fixed_price_ratio=float(price_slice["value"]), + fixed_margin=float(margin_slice["value"]), + fixed_shift_exponent=float(shift_slice["value"]), + title=( + f"{spec['title']} 3D {slice_label} slice | TVL {format_tvl_millions_label(base_cfg)}\n" + f"price_ratio={format_heatmap_param_value(price_slice['value'])}, " + f"margin={format_heatmap_param_value(margin_slice['value'])}, " + f"shift_exp={format_heatmap_param_value(shift_slice['value'])}" + ), + colorbar_label=spec["colorbar_label"], + filename=output_files[metric_key], + center_zero=spec["center_zero"], + cmap=spec["cmap"], + color_norm=spec.get("color_norm"), + symlog_linthresh=spec.get("symlog_linthresh"), + ) + + del price_margin_data, shift_margin_data, price_shift_data + gc.collect() + + if owns_cache: + flush_sweep_cache(cache, force=True) + cache.clear() + gc.collect() + print("Released 3D heatmap cache.") + + +def compute_auto_calibrated_arc_length_speed(cfg, price_data): + """Compute the launch/reference auto-calibrated speed for a config.""" + start_ts = pd.Timestamp(cfg["start"]) + row = _nearest_price_row(price_data, start_ts) + + if isinstance(price_data.columns, pd.MultiIndex): + initial_price_values = [ + float(row[(token, "close")]) + for token in cfg["tokens"] + ] + else: + initial_price_values = [ + float(row[f"close_{token}"]) + for token in cfg["tokens"] + ] + + initial_prices = jnp.array(initial_price_values, dtype=jnp.float64) + initial_reserves, Va, Vb = initialise_reclamm_reserves( + get_initial_pool_value(cfg), + initial_prices, + float(cfg["price_ratio"]), + ) + market_price_0 = float(initial_prices[0] / initial_prices[1]) + sqrt_Q = jnp.sqrt( + compute_price_ratio( + initial_reserves[0], + initial_reserves[1], + Va, + Vb, + ) + ) + return float( + calibrate_arc_length_speed( + initial_reserves[0], + initial_reserves[1], + Va, + Vb, + to_daily_price_shift_base(float(cfg["daily_price_shift_exponent"])), + 60.0, + sqrt_Q, + market_price_0, + centeredness_margin=float(cfg["centeredness_margin"]), + ) + ) + + +def generate_arc_speed_efficiency_artifacts( + base_cfg, + launch_cfg, + price_data, + launch_final_values, + cache=None, +): + """Generate arc-speed heatmaps plus the existing efficiency line charts.""" + if not RUN_CONSTANT_ARC_LENGTH: + print("\nSkipping arc-speed heatmaps because RUN_CONSTANT_ARC_LENGTH=False.") + return + owns_cache = cache is None + if cache is None: + cache = make_sweep_cache(price_data, cache_scope_cfg=base_cfg) + launch_auto_speed = compute_auto_calibrated_arc_length_speed(launch_cfg, price_data) + heatmap_metric_specs = [ + { + "key": "efficiency_pct", + "title": "Efficiency vs geometric", + "colorbar_label": "Const Arc - heatmap Geo (% of heatmap geometric final value)", + "slug": "efficiency", + "center_zero": True, + "cmap": "RdYlGn", + }, + { + "key": "noise_constant_arc_final_value_musd", + "title": "Const arc final value with noise model", + "colorbar_label": "Const Arc final value with noise model ($M)", + "slug": "noise_constant_arc_final_value", + "center_zero": False, + "cmap": "viridis", + }, + { + "key": "noise_vs_arb_constant_arc_improvement_pct", + "title": "Noise-model improvement over arb-only (const arc)", + "colorbar_label": "Noise-model Const Arc - arb-only Const Arc (% of arb-only final value)", + "slug": "noise_vs_arb_constant_arc_improvement", + "center_zero": True, + "cmap": "RdYlGn", + }, + ] + for spec in heatmap_metric_specs: + if spec["center_zero"]: + spec["color_norm"] = CENTER_ZERO_HEATMAP_COLOR_NORM + spec["symlog_linthresh"] = CENTER_ZERO_HEATMAP_SYMLOG_LINTHRESH + spec["artifact_tag"] = CENTER_ZERO_HEATMAP_COLOR_TAG + pair_specs = [ + { + "slug": "arc_speed_vs_price_ratio", + "x_values": HEATMAP_ARC_LENGTH_SPEEDS, + "y_values": HEATMAP_PRICE_RATIOS, + "x_key": "arc_length_speed", + "y_key": "price_ratio", + "x_label": "Arc-length speed", + "y_label": "Price ratio", + "title_suffix": ( + f"margin fixed at {base_cfg['centeredness_margin']:.2f}, " + f"shift_exp fixed at {base_cfg['daily_price_shift_exponent']:.2f}" + ), + "xticks": ARC_LENGTH_SPEED_TICKS, + "yticks": PRICE_RATIO_TICKS, + }, + { + "slug": "arc_speed_vs_margin", + "x_values": HEATMAP_ARC_LENGTH_SPEEDS, + "y_values": HEATMAP_MARGINS, + "x_key": "arc_length_speed", + "y_key": "centeredness_margin", + "x_label": "Arc-length speed", + "y_label": "Centeredness margin", + "title_suffix": ( + f"price_ratio fixed at {base_cfg['price_ratio']:.2f}, " + f"shift_exp fixed at {base_cfg['daily_price_shift_exponent']:.2f}" + ), + "xticks": ARC_LENGTH_SPEED_TICKS, + "yticks": MARGIN_TICKS + }, + { + "slug": "arc_speed_vs_shift_exp", + "x_values": HEATMAP_ARC_LENGTH_SPEEDS, + "y_values": HEATMAP_SHIFT_EXPONENTS, + "x_key": "arc_length_speed", + "y_key": "daily_price_shift_exponent", + "x_label": "Arc-length speed", + "y_label": "Shift exponent", + "title_suffix": ( + f"price_ratio fixed at {base_cfg['price_ratio']:.2f}, " + f"margin fixed at {base_cfg['centeredness_margin']:.2f}" + ), + "xticks": ARC_LENGTH_SPEED_TICKS, + "yticks": SHIFT_EXPONENT_TICKS, + }, + ] + metric_spec_map = {spec["key"]: spec for spec in heatmap_metric_specs} + + print( + "\nGenerating arc-speed heatmaps and line charts " + f"(launch auto-cal speed={launch_auto_speed:.3e}, TVL={format_tvl_millions_label(base_cfg)})..." + ) + + for pair in pair_specs: + heatmap_files = { + spec["key"]: heatmap_artifact_filename( + spec, + base_cfg, + suffix=pair["slug"], + ) + for spec in heatmap_metric_specs + } + line_filename = tvl_artifact_filename( + "reclamm_line_efficiency", + base_cfg, + suffix=pair["slug"], + ) + missing_files = _missing_artifacts( + pair["slug"], + list(heatmap_files.values()) + [line_filename], + ) + if not missing_files: + continue + + missing_metric_keys = [ + spec["key"] + for spec in heatmap_metric_specs + if heatmap_files[spec["key"]] in missing_files + ] + if line_filename in missing_files and "efficiency_pct" not in missing_metric_keys: + missing_metric_keys.append("efficiency_pct") + + data_by_metric = build_heatmap_matrices( + x_values=pair["x_values"], + y_values=pair["y_values"], + x_key=pair["x_key"], + y_key=pair["y_key"], + base_cfg=base_cfg, + metric_keys=missing_metric_keys, + cache=cache, + progress_label=pair["slug"], + launch_final_values=launch_final_values, + ) + for metric_key in missing_metric_keys: + if metric_key not in heatmap_files: + continue + if heatmap_files[metric_key] not in missing_files: + continue + spec = metric_spec_map[metric_key] + plot_heatmap( + data=data_by_metric[metric_key], + x_values=pair["x_values"], + y_values=pair["y_values"], + x_label=pair["x_label"], + y_label=pair["y_label"], + title=( + f"{spec['title']}: {pair['title_suffix']} | " + f"TVL {format_tvl_millions_label(base_cfg)}" + ), + colorbar_label=spec["colorbar_label"], + filename=heatmap_files[metric_key], + xticks=pair["xticks"], + yticks=pair["yticks"], + xscale="log", + center_zero=spec["center_zero"], + cmap=spec["cmap"], + color_norm=spec.get("color_norm"), + symlog_linthresh=spec.get("symlog_linthresh"), + ) + + if line_filename in missing_files: + efficiency_data = data_by_metric["efficiency_pct"] + launch_curve = build_metric_curve( + x_values=pair["x_values"], + x_key=pair["x_key"], + base_cfg=launch_cfg, + metric_key="efficiency_pct", + cache=cache, + launch_final_values=launch_final_values, + ) + plot_arc_speed_line_chart( + data=efficiency_data, + x_values=pair["x_values"], + y_values=pair["y_values"], + y_label=pair["y_label"], + title=( + "Arc-speed efficiency sweep: " + f"{pair['title_suffix']} | TVL {format_tvl_millions_label(base_cfg)}" + ), + filename=line_filename, + launch_curve=launch_curve, + launch_auto_speed=launch_auto_speed, + ) + del data_by_metric + gc.collect() + + if owns_cache: + flush_sweep_cache(cache, force=True) + cache.clear() + gc.collect() + print("Released arc-speed sweep cache.") + + +def get_launch_final_values( + all_results, + launch_cfg, + price_data, + market_linear_noise_data=None, +): + """Reuse launch-style runs when available; otherwise run them once.""" + for cfg, results in all_results: + if cfg["name"] == launch_cfg["name"]: + launch_final_values = { + "geometric": float(results["geometric"]["final_value"]), + } + if "constant_arc_length" in results: + launch_final_values["constant_arc_length"] = float( + results["constant_arc_length"]["final_value"] + ) + return launch_final_values + + print("\nRunning launch-style benchmarks for heatmaps...") + launch_results = run_comparison( + launch_cfg, + price_data=price_data, + low_data_mode=True, + market_linear_noise_data=market_linear_noise_data, + ) + launch_final_values = { + "geometric": float(launch_results["geometric"]["final_value"]), + } + if "constant_arc_length" in launch_results: + launch_final_values["constant_arc_length"] = float( + launch_results["constant_arc_length"]["final_value"] + ) + del launch_results + gc.collect() + return launch_final_values - return results def print_comparison(cfg, results): """Print text summary table.""" - methods = [ - ("Geometric", results["geometric"]), - ("Geo+Scaled", results["geometric_scaled"]), - ("Const Arc", results["constant_arc_length"]), - ("Arc+Scaled", results["cal_scaled"]), - ] + methods = [("Geometric", results["geometric"])] + has_constant_arc = "constant_arc_length" in results + if has_constant_arc: + methods.append(("Const Arc", results["constant_arc_length"])) + noise_cfg = resolve_reclamm_noise_settings(cfg) hodl_value = float((methods[0][1]["reserves"][0] * methods[0][1]["prices"][-1]).sum()) @@ -128,6 +2217,18 @@ def print_comparison(cfg, results): f"margin={cfg['centeredness_margin']}, " f"shift_exp={cfg['daily_price_shift_exponent']}, " f"fees={cfg['fees']}") + print( + f" base_tvl=${get_initial_pool_value(cfg):,.0f} " + f"(TVL {format_tvl_millions_label(cfg)})" + ) + print(f" note={cfg['reason']}") + print( + f" noise={noise_cfg['noise_summary']}, " + f"gas={cfg.get('gas_cost', 0.0)}, " + f"protocol_fee_split={cfg.get('protocol_fee_split', 0.0)}" + ) + if not has_constant_arc: + print(" constant_arc=disabled") print("-" * 105) header = " {:20s}".format("") for name, _ in methods: @@ -158,18 +2259,27 @@ def print_comparison(cfg, results): vs = (float(r["final_value"]) / hodl_value - 1) * 100 row += f" {vs:>13.2f}%" print(row) + + if has_constant_arc: + geo_final = float(results["geometric"]["final_value"]) + arc_final = float(results["constant_arc_length"]["final_value"]) + geo_lvr = hodl_value - geo_final + arc_lvr = hodl_value - arc_final + print(f" {'Const Arc - Geo':20s} ${arc_final - geo_final:>13,.0f}") + print(f" {'LVR saved vs Geo':20s} ${geo_lvr - arc_lvr:>13,.0f}") print("=" * 105) + def plot_comparison(cfg, results, fig_idx): - """Plot 4-panel comparison for one config.""" - # Method name → (result dict, color, linestyle) + """Plot comparison diagnostics for one config.""" + tvl_label = format_tvl_millions_label(cfg) variants = { "Geometric": (results["geometric"], "C0", "-"), - "Geo+Scaled": (results["geometric_scaled"], "C1", "-"), - "Const arc-len": (results["constant_arc_length"], "C2", "--"), - "Arc+Scaled": (results["cal_scaled"], "C3", "--"), } + has_constant_arc = "constant_arc_length" in results + if has_constant_arc: + variants["Const arc-len"] = (results["constant_arc_length"], "C2", "--") geo = results["geometric"] geo_prices = np.array(geo["prices"]) @@ -181,22 +2291,21 @@ def plot_comparison(cfg, results, fig_idx): price_ratio_traj = geo_prices[:n_steps, 0] / geo_prices[:n_steps, 1] fig, axes = plt.subplots(2, 2, figsize=(14, 10)) - fig.suptitle(cfg["name"], fontsize=13, fontweight="bold") + fig.suptitle(f"{cfg['name']} — TVL {tvl_label}", fontsize=13, fontweight="bold") - # (0,0) Pool value over time ax = axes[0, 0] + plotted_values = [] for name, (r, color, ls) in variants.items(): vals = np.array(r["value"]) + plotted_values.append(vals / 1e6) ax.plot(t_days, vals / 1e6, color=color, ls=ls, label=name, alpha=0.9) - ax.plot(t_days, np.array(hodl_traj) / 1e6, color="gray", ls=":", - alpha=0.5, label="HODL") + _set_padded_ylim(ax, plotted_values, pad_ratio=0.03) ax.set_xlabel("Days") ax.set_ylabel("Pool value ($M)") ax.set_title("Pool value") ax.legend(fontsize=8) ax.grid(True, alpha=0.3) - # (0,1) Cumulative LVR ax = axes[0, 1] for name, (r, color, ls) in variants.items(): vals = np.array(r["value"]) @@ -208,7 +2317,6 @@ def plot_comparison(cfg, results, fig_idx): ax.legend(fontsize=8) ax.grid(True, alpha=0.3) - # (1,0) Price ratio ax = axes[1, 0] ax.plot(t_days, price_ratio_traj, color="C4", alpha=0.7) ax.set_xlabel("Days") @@ -216,7 +2324,6 @@ def plot_comparison(cfg, results, fig_idx): ax.set_title("Price path") ax.grid(True, alpha=0.3) - # (1,1) Empirical weights ax = axes[1, 1] for name, (r, color, ls) in variants.items(): w = np.array(r["weights"]) @@ -230,19 +2337,21 @@ def plot_comparison(cfg, results, fig_idx): ax.grid(True, alpha=0.3) plt.tight_layout() - fname = f"reclamm_thermostat_comparison_{fig_idx}.png" + fname = tvl_artifact_filename("reclamm_thermostat_comparison", cfg, suffix=str(fig_idx)) plt.savefig(fname, dpi=150) print(f"Saved {fname}") plt.close(fig) - # Second figure: diagnostics + if not has_constant_arc: + print("Skipping constant-arc comparison diagnostics because RUN_CONSTANT_ARC_LENGTH=False.") + return + geo_values = np.array(geo["value"]) geo_lvr = np.array(hodl_traj) - geo_values fig2, axes2 = plt.subplots(1, 3, figsize=(18, 5)) - fig2.suptitle(f"{cfg['name']} — diagnostics", fontsize=13, fontweight="bold") + fig2.suptitle(f"{cfg['name']} — diagnostics — TVL {tvl_label}", fontsize=13, fontweight="bold") - # (left) Value difference vs geometric ax = axes2[0] for name, (r, color, ls) in variants.items(): if name == "Geometric": @@ -257,7 +2366,6 @@ def plot_comparison(cfg, results, fig_idx): ax.legend(fontsize=8) ax.grid(True, alpha=0.3) - # (middle) LVR ratio over time ax = axes2[1] mask = np.abs(geo_lvr) > 100 if mask.any(): @@ -279,7 +2387,6 @@ def plot_comparison(cfg, results, fig_idx): ax.set_title("Relative LVR") ax.grid(True, alpha=0.3) - # (right) Per-step LVR histogram ax = axes2[2] all_pos = [] for name, (r, color, ls) in variants.items(): @@ -306,74 +2413,197 @@ def plot_comparison(cfg, results, fig_idx): ax.grid(True, alpha=0.3) plt.tight_layout() - fname2 = f"reclamm_thermostat_diff_{fig_idx}.png" + fname2 = tvl_artifact_filename("reclamm_thermostat_diff", cfg, suffix=str(fig_idx)) plt.savefig(fname2, dpi=150) print(f"Saved {fname2}") plt.close(fig2) + arc_values = np.array(results["constant_arc_length"]["value"]) + n_eff = min(len(geo_values), len(arc_values)) + t_eff = np.arange(n_eff) / (60 * 24) + efficiency_pct = ( + (arc_values[:n_eff] - geo_values[:n_eff]) + / np.maximum(np.abs(geo_values[:n_eff]), 1e-12) + * 100.0 + ) + + fig3, ax3 = plt.subplots(1, 1, figsize=(10, 4.5)) + fig3.suptitle(f"{cfg['name']} — efficiency — TVL {tvl_label}", fontsize=13, fontweight="bold") + ax3.plot( + t_eff, + efficiency_pct, + color="C2", + linewidth=1.8, + label="(Const Arc - Geo) / Geo", + ) + ax3.axhline(0.0, color="gray", ls="--", alpha=0.6) + _set_padded_ylim(ax3, [efficiency_pct], pad_ratio=0.08) + ax3.set_xlabel("Days") + ax3.set_ylabel("Efficiency vs geometric (%)") + ax3.set_title("Efficiency") + ax3.legend(fontsize=8) + ax3.grid(True, alpha=0.3) + + plt.tight_layout() + fname3 = tvl_artifact_filename("reclamm_thermostat_efficiency", cfg, suffix=str(fig_idx)) + plt.savefig(fname3, dpi=150) + print(f"Saved {fname3}") + plt.close(fig3) + + if __name__ == "__main__": - all_results = [] - for i, cfg in enumerate(CONFIGS): - print(f"\n>>> Running {cfg['name']}...") - try: - results = run_comparison(cfg) - print_comparison(cfg, results) - plot_comparison(cfg, results, i) - all_results.append((cfg, results)) - except Exception as e: - print(f" FAILED: {e}") - import traceback - traceback.print_exc() - - # Summary overlay: all configs on one figure (pool value normalised) - if len(all_results) > 1: - fig, axes = plt.subplots(1, 2, figsize=(16, 5)) - fig.suptitle("Cross-config comparison (normalised)", fontsize=13, - fontweight="bold") - - method_keys = [ - ("geometric", "geo", "-"), - ("geometric_scaled", "geo+s", "-."), - ("constant_arc_length", "arc", "--"), - ("cal_scaled", "arc+s", ":"), - ] + shared_price_data = load_shared_price_data(CONFIGS) + shared_market_linear_noise_data = load_shared_market_linear_noise_data() + + for initial_pool_value in TVL_SWEEP_VALUES: + tvl_configs = configs_for_tvl(CONFIGS, initial_pool_value) + tvl_label = format_tvl_millions_label(tvl_configs[0]) + print(f"\n=== TVL sweep: {tvl_label} ===") + + all_results = [] + for i, cfg in enumerate(tvl_configs): + print(f"\n>>> Running {cfg['name']} at TVL {tvl_label}...") + try: + results = run_comparison( + cfg, + price_data=shared_price_data, + market_linear_noise_data=shared_market_linear_noise_data, + ) + print_comparison(cfg, results) + plot_comparison(cfg, results, i) + all_results.append((cfg, results)) + except Exception as e: + print(f" FAILED: {e}") + import traceback + + traceback.print_exc() + + if len(all_results) > 1: + if RUN_CONSTANT_ARC_LENGTH: + fig, axes = plt.subplots(1, 2, figsize=(16, 5)) + fig.suptitle( + f"Cross-config comparison (normalised) — TVL {tvl_label}", + fontsize=13, + fontweight="bold", + ) + + method_keys = [ + ("geometric", "geo", "-"), + ("constant_arc_length", "arc", "--"), + ] + + for i, (cfg, results) in enumerate(all_results): + geo_v = np.array(results["geometric"]["value"]) + t = np.arange(len(geo_v)) / (60 * 24) + short_name = cfg["name"].split("(")[0].strip() + + for j, (key, suffix, ls) in enumerate(method_keys): + v = np.array(results[key]["value"]) + color_idx = i * len(method_keys) + j + + axes[0].plot( + t, + v / v[0], + ls=ls, + alpha=0.8, + label=f"{short_name} {suffix}", + color=f"C{color_idx % 10}", + ) + + if key != "geometric": + pct_diff = (v - geo_v) / geo_v * 100 + axes[1].plot( + t, + pct_diff, + ls=ls, + alpha=0.8, + label=f"{short_name} {suffix}", + color=f"C{color_idx % 10}", + ) - for i, (cfg, results) in enumerate(all_results): - geo_v = np.array(results["geometric"]["value"]) - t = np.arange(len(geo_v)) / (60 * 24) - short_name = cfg["name"].split("(")[0].strip() - - for j, (key, suffix, ls) in enumerate(method_keys): - v = np.array(results[key]["value"]) - color_idx = i * len(method_keys) + j - - # (left) Normalised pool value - axes[0].plot(t, v / v[0], ls=ls, alpha=0.8, - label=f"{short_name} {suffix}", - color=f"C{color_idx % 10}") - - # (right) Value difference vs geometric (skip geo itself) - if key != "geometric": - pct_diff = (v - geo_v) / geo_v * 100 - axes[1].plot(t, pct_diff, ls=ls, alpha=0.8, - label=f"{short_name} {suffix}", - color=f"C{color_idx % 10}") - - axes[0].set_xlabel("Days") - axes[0].set_ylabel("Normalised pool value") - axes[0].set_title("Pool value (V/V0)") - axes[0].legend(fontsize=6, ncol=2) - axes[0].grid(True, alpha=0.3) - - axes[1].set_xlabel("Days") - axes[1].set_ylabel("(Method - Geo) / Geo (%)") - axes[1].set_title("Relative value difference vs Geometric") - axes[1].axhline(0, color="gray", ls="--", alpha=0.5) - axes[1].legend(fontsize=6, ncol=2) - axes[1].grid(True, alpha=0.3) - - plt.tight_layout() - plt.savefig("reclamm_thermostat_summary.png", dpi=150) - print("\nSaved reclamm_thermostat_summary.png") - plt.close(fig) + axes[0].set_xlabel("Days") + axes[0].set_ylabel("Normalised pool value") + axes[0].set_title("Pool value (V/V0)") + axes[0].legend(fontsize=6, ncol=2) + axes[0].grid(True, alpha=0.3) + + axes[1].set_xlabel("Days") + axes[1].set_ylabel("Efficiency vs geometric (%)") + axes[1].set_title("Efficiency vs Geometric") + axes[1].axhline(0, color="gray", ls="--", alpha=0.5) + axes[1].legend(fontsize=6, ncol=2) + axes[1].grid(True, alpha=0.3) + else: + fig, ax = plt.subplots(1, 1, figsize=(9, 5)) + fig.suptitle( + f"Cross-config comparison (normalised geometric) — TVL {tvl_label}", + fontsize=13, + fontweight="bold", + ) + + for i, (cfg, results) in enumerate(all_results): + geo_v = np.array(results["geometric"]["value"]) + t = np.arange(len(geo_v)) / (60 * 24) + short_name = cfg["name"].split("(")[0].strip() + ax.plot( + t, + geo_v / geo_v[0], + ls="-", + alpha=0.8, + label=f"{short_name} geo", + color=f"C{i % 10}", + ) + + ax.set_xlabel("Days") + ax.set_ylabel("Normalised pool value") + ax.set_title("Geometric pool value (V/V0)") + ax.legend(fontsize=6, ncol=2) + ax.grid(True, alpha=0.3) + + plt.tight_layout() + summary_name = tvl_artifact_filename( + "reclamm_thermostat_summary", + tvl_configs[0], + ) + plt.savefig(summary_name, dpi=150) + print(f"\nSaved {summary_name}") + plt.close(fig) + + launch_final_values = get_launch_final_values( + all_results, + launch_cfg=tvl_configs[0], + price_data=shared_price_data, + market_linear_noise_data=shared_market_linear_noise_data, + ) + shared_sweep_cache = make_sweep_cache( + shared_price_data, + cache_scope_cfg=tvl_configs[1], + market_linear_noise_data=shared_market_linear_noise_data, + ) + + print(f"\nGenerating thermostat heatmaps for TVL {tvl_label}...") + generate_heatmaps( + dict(tvl_configs[1]), + shared_price_data, + launch_final_values=launch_final_values, + cache=shared_sweep_cache, + ) + + generate_arc_speed_efficiency_artifacts( + dict(tvl_configs[1]), + launch_cfg=dict(tvl_configs[0]), + price_data=shared_price_data, + launch_final_values=launch_final_values, + cache=shared_sweep_cache, + ) + generate_three_variable_3d_heatmaps( + dict(tvl_configs[1]), + price_data=shared_price_data, + launch_final_values=launch_final_values, + cache=shared_sweep_cache, + ) + flush_sweep_cache(shared_sweep_cache, force=True) + shared_sweep_cache.clear() + gc.collect() + print(f"Released shared sweep cache for TVL {tvl_label}.") diff --git a/scripts/reclamm/demo_run_reclamm.py b/scripts/reclamm/demo_run_reclamm.py index 3ea21ec..132f512 100644 --- a/scripts/reclamm/demo_run_reclamm.py +++ b/scripts/reclamm/demo_run_reclamm.py @@ -36,105 +36,127 @@ def balancer_fingerprint(tokens, start, end, fees): } +def reclamm_fingerprint(tokens, start, end, fees, interpolation_method="geometric"): + """Build a reCLAMM fingerprint for a demo scenario.""" + return { + "tokens": tokens, + "rule": "reclamm", + "startDateString": start, + "endDateString": end, + "initial_pool_value": 1000000.0, + "do_arb": True, + "fees": fees, + "gas_cost": 0.0, + "arb_fees": 0.0, + "chunk_period": 60, + "weight_interpolation_period": 60, + "reclamm_interpolation_method": interpolation_method, + "reclamm_arc_length_speed": None, + } + + +def reclamm_params(price_ratio, centeredness_margin, daily_price_shift_exponent): + """Build reCLAMM params from a concise config.""" + return { + "price_ratio": jnp.array(price_ratio), + "centeredness_margin": jnp.array(centeredness_margin), + "daily_price_shift_base": jnp.array( + to_daily_price_shift_base(daily_price_shift_exponent) + ), + } + + +def _apply_active_noise_settings(fp): + """Enable the active AAVE/ETH reCLAMM noise model for demo runs.""" + if fp.get("rule") != "reclamm" or list(fp.get("tokens", [])) != ["AAVE", "ETH"]: + return fp, "disabled" + + from compare_reclamm_thermostats import ( + AAVE_ETH_NOISE_SETTINGS, + resolve_reclamm_noise_settings, + ) + + cfg = { + "tokens": fp["tokens"], + "start": fp["startDateString"], + "end": fp["endDateString"], + "enable_noise_model": True, + "noise_model": AAVE_ETH_NOISE_SETTINGS["noise_model"], + "noise_artifact_dir": AAVE_ETH_NOISE_SETTINGS["noise_artifact_dir"], + "noise_pool_id": AAVE_ETH_NOISE_SETTINGS["noise_pool_id"], + "gas_cost": fp.get("gas_cost", AAVE_ETH_NOISE_SETTINGS["gas_cost"]), + "protocol_fee_split": fp.get( + "protocol_fee_split", + AAVE_ETH_NOISE_SETTINGS["protocol_fee_split"], + ), + "arb_frequency": fp.get("arb_frequency"), + "noise_trader_ratio": fp.get("noise_trader_ratio", 0.0), + "reclamm_noise_params": fp.get("reclamm_noise_params"), + "noise_arrays_path": fp.get("noise_arrays_path"), + } + noise_cfg = resolve_reclamm_noise_settings(cfg) + + updated = dict(fp) + updated["gas_cost"] = cfg["gas_cost"] + updated["protocol_fee_split"] = cfg["protocol_fee_split"] + updated["noise_trader_ratio"] = noise_cfg.get("noise_trader_ratio", 0.0) + for key in ("noise_model", "reclamm_noise_params", "noise_arrays_path", "arb_frequency"): + if noise_cfg.get(key) is not None: + updated[key] = noise_cfg[key] + return updated, noise_cfg["noise_summary"] + + SCENARIOS = [ { - "name": "AAVE/ETH on-chain (25bps)", + "name": "AAVE/ETH launch-style range (25bps, geometric)", "reclamm": { - "fingerprint": { - "tokens": ["AAVE", "ETH"], - "rule": "reclamm", - "startDateString": "2024-06-01 00:00:00", - "endDateString": "2025-06-01 00:00:00", - "initial_pool_value": 1000000.0, - "do_arb": True, - "fees": 0.0025, - "gas_cost": 0.0, - "arb_fees": 0.0, - "chunk_period": 60, - "weight_interpolation_period": 60, - }, - "params": { - "price_ratio": jnp.array(1.5), - "centeredness_margin": jnp.array(0.5), - "daily_price_shift_base": jnp.array( - to_daily_price_shift_base(0.1) - ), - }, + "fingerprint": reclamm_fingerprint( + ["AAVE", "ETH"], + "2024-06-01 00:00:00", + "2025-06-01 00:00:00", + 0.0025, + interpolation_method="geometric", + ), + "params": reclamm_params(1.5014, 0.5, 0.1), }, }, { - "name": "AAVE/ETH zero fees", + "name": "AAVE/ETH tighter launch-style range (25bps, geometric)", "reclamm": { - "fingerprint": { - "tokens": ["AAVE", "ETH"], - "rule": "reclamm", - "startDateString": "2024-06-01 00:00:00", - "endDateString": "2025-06-01 00:00:00", - "initial_pool_value": 1000000.0, - "do_arb": True, - "fees": 0.0, - "gas_cost": 0.0, - "arb_fees": 0.0, - "chunk_period": 60, - "weight_interpolation_period": 60, - }, - "params": { - "price_ratio": jnp.array(1.5), - "centeredness_margin": jnp.array(0.5), - "daily_price_shift_base": jnp.array( - to_daily_price_shift_base(0.1) - ), - }, + "fingerprint": reclamm_fingerprint( + ["AAVE", "ETH"], + "2024-06-01 00:00:00", + "2025-06-01 00:00:00", + 0.0025, + interpolation_method="geometric", + ), + "params": reclamm_params(1.15, 0.5, 0.1), }, }, { - "name": "AAVE/ETH wide range (25bps)", + "name": "AAVE/ETH tighter launch-style range (25bps, constant arc)", "reclamm": { - "fingerprint": { - "tokens": ["AAVE", "ETH"], - "rule": "reclamm", - "startDateString": "2024-06-01 00:00:00", - "endDateString": "2025-06-01 00:00:00", - "initial_pool_value": 1000000.0, - "do_arb": True, - "fees": 0.0025, - "gas_cost": 0.0, - "arb_fees": 0.0, - "chunk_period": 60, - "weight_interpolation_period": 60, - }, - "params": { - "price_ratio": jnp.array(4.0), - "centeredness_margin": jnp.array(0.2), - "daily_price_shift_base": jnp.array( - to_daily_price_shift_base(1.0) - ), - }, + "fingerprint": reclamm_fingerprint( + ["AAVE", "ETH"], + "2024-06-01 00:00:00", + "2025-06-01 00:00:00", + 0.0025, + interpolation_method="constant_arc_length", + ), + "params": reclamm_params(1.15, 0.5, 0.1), }, }, { "name": "BTC/ETH (10bps)", "reclamm": { - "fingerprint": { - "tokens": ["BTC", "ETH"], - "rule": "reclamm", - "startDateString": "2024-01-01 00:00:00", - "endDateString": "2025-06-01 00:00:00", - "initial_pool_value": 1000000.0, - "do_arb": True, - "fees": 0.001, - "gas_cost": 0.0, - "arb_fees": 0.0, - "chunk_period": 60, - "weight_interpolation_period": 60, - }, - "params": { - "price_ratio": jnp.array(2.0), - "centeredness_margin": jnp.array(0.3), - "daily_price_shift_base": jnp.array( - to_daily_price_shift_base(0.5) - ), - }, + "fingerprint": reclamm_fingerprint( + ["BTC", "ETH"], + "2024-01-01 00:00:00", + "2025-06-01 00:00:00", + 0.001, + interpolation_method="geometric", + ), + "params": reclamm_params(2.0, 0.3, 0.5), }, }, ] @@ -143,7 +165,7 @@ def balancer_fingerprint(tokens, start, end, fees): def run_scenario(scenario): """Run a reClAMM config and its Balancer 50/50 baseline, print comparison.""" rc = scenario["reclamm"] - fp = rc["fingerprint"] + fp, noise_summary = _apply_active_noise_settings(dict(rc["fingerprint"])) # Run reClAMM reclamm_result = do_run_on_historic_data( @@ -173,7 +195,14 @@ def run_scenario(scenario): print("=" * 80) print(f" {scenario['name']}") - print(f" Tokens: {', '.join(fp['tokens'])} | Fees: {fp['fees']}") + print( + f" Tokens: {', '.join(fp['tokens'])} | Fees: {fp['fees']} | " + f"Interpolation: {fp.get('reclamm_interpolation_method', 'geometric')}" + ) + print( + f" Noise: {noise_summary} | Gas: {fp.get('gas_cost', 0.0)} | " + f"Protocol fee split: {fp.get('protocol_fee_split', 0.0)}" + ) print("-" * 80) print(f" {'':30s} {'reClAMM':>14s} {'Balancer 50/50':>14s}") print(f" {'Initial value':30s} ${rc_init:>13,.0f} ${bal_init:>13,.0f}") diff --git a/scripts/reclamm/find_adjacent_heatmap_pairs.py b/scripts/reclamm/find_adjacent_heatmap_pairs.py new file mode 100644 index 0000000..51a3a8a --- /dev/null +++ b/scripts/reclamm/find_adjacent_heatmap_pairs.py @@ -0,0 +1,1257 @@ +"""Scan cached reCLAMM heatmaps for adjacent cells with large value gaps. + +This script reconstructs heatmap cells from the persisted scalar forward-value +cache written by ``compare_reclamm_thermostats.py``. It does not inspect PNG +pixels or rerun the simulator for cache-backed metrics. +""" + +from __future__ import annotations + +import argparse +import hashlib +import importlib.util +import math +import os +from pathlib import Path +from typing import Dict, Iterable, List, Mapping, MutableMapping, Optional, Sequence, Tuple + +import numpy as np +import pandas as pd + + +CACHE_ONLY_METRIC_SPECS = { + "efficiency_pct": { + "sources": ("noise_constant_arc", "noise_geometric"), + "unit": "pct", + "compute": lambda values: ( + values["noise_constant_arc"] / max(abs(values["noise_geometric"]), 1.0e-12) + - 1.0 + ) + * 100.0, + }, + "noise_geometric_final_value_musd": { + "sources": ("noise_geometric",), + "unit": "musd", + "compute": lambda values: values["noise_geometric"] / 1.0e6, + }, + "noise_constant_arc_final_value_musd": { + "sources": ("noise_constant_arc",), + "unit": "musd", + "compute": lambda values: values["noise_constant_arc"] / 1.0e6, + }, + "noise_vs_arb_geometric_improvement_pct": { + "sources": ("noise_geometric", "arb_geometric"), + "unit": "pct", + "compute": lambda values: ( + values["noise_geometric"] / max(abs(values["arb_geometric"]), 1.0e-12) - 1.0 + ) + * 100.0, + }, + "noise_vs_arb_constant_arc_improvement_pct": { + "sources": ("noise_constant_arc", "arb_constant_arc"), + "unit": "pct", + "compute": lambda values: ( + values["noise_constant_arc"] + / max(abs(values["arb_constant_arc"]), 1.0e-12) + - 1.0 + ) + * 100.0, + }, +} + +OUTPUT_COLUMNS = [ + "metric_key", + "metric_unit", + "source_noise_profile", + "pair_slug", + "slice_slug", + "slice_label", + "fixed_key", + "fixed_value", + "adjacency_axis", + "heatmap_value_diff_abs", + "heatmap_value_diff_signed_2_minus_1", + "1_price_ratio", + "1_centeredness_margin", + "1_daily_price_shift_exponent", + "1_tvl_usd", + "1_heatmap_value", + "1_x_index", + "1_y_index", + "2_price_ratio", + "2_centeredness_margin", + "2_daily_price_shift_exponent", + "2_tvl_usd", + "2_heatmap_value", + "2_x_index", + "2_y_index", +] + + +def build_inclusive_sweep(start: float, stop: float, step: float) -> np.ndarray: + """Build a sweep that keeps the requested step and explicitly includes the stop.""" + values = np.arange(start, stop + 1.0e-12, step, dtype=float) + if values.size == 0 or not np.isclose(values[-1], stop): + values = np.append(values, float(stop)) + return values + + +def _resolve_repo_root(script_path): + """Locate the repository root from either scripts/ or scripts/reclamm/.""" + script_path = Path(script_path).resolve() + for parent in script_path.parents: + if (parent / "quantammsim").exists() and (parent / "scripts").exists(): + return parent + return script_path.parents[1] + + +REPO_ROOT = _resolve_repo_root(__file__) +DEFAULT_MARKET_LINEAR_NOISE_START_DATE = "2024-06-01" +DEFAULT_MARKET_LINEAR_NOISE_END_DATE = "2026-03-01" +DEFAULT_MARKET_LINEAR_NOISE_ARRAYS_PATH = str( + REPO_ROOT + / "results" + / "linear_market_noise" + / "_sim_arrays" + / ( + "0x9d1fcf346ea1b0_" + f"{DEFAULT_MARKET_LINEAR_NOISE_START_DATE}_{DEFAULT_MARKET_LINEAR_NOISE_END_DATE}.npz" + ) +) + + +class _LightweightCompareContext: + """Small subset of compare_reclamm_thermostats usable without JAX.""" + + RUN_CONSTANT_ARC_LENGTH = True + DEFAULT_INITIAL_POOL_VALUE = 1_000_000.0 + TVL_SWEEP_VALUES = ( + 1_000_000.0, + 5_000_000.0, + 20_000_000.0, + ) + HEATMAP_PRICE_RATIOS = build_inclusive_sweep(1.01, 3.00, 0.025) + HEATMAP_MARGINS = np.linspace(0.05, 0.90, 39) + HEATMAP_SHIFT_EXPONENTS = build_inclusive_sweep(0.01, 0.50, 0.0125) + FIXED_SLICE_FRACTIONS = (0.125, 0.375, 0.625, 0.875) + FIXED_SLICE_LABELS = ("Q1", "Q2", "Q3", "Q4") + HEATMAP_FORWARD_CACHE_ENABLED = True + HEATMAP_FORWARD_CACHE_RUN_NAME = "aave_eth_thermostat_heatmaps_market_linear_v2" + HEATMAP_FORWARD_CACHE_ROOT = os.path.join( + "results", + "reclamm_heatmap_forward_cache", + ) + AAVE_WETH_POOL_ID = "0x9d1fcf346ea1b0" + DEFAULT_MARKET_LINEAR_ARTIFACT_DIR = "results/linear_market_noise" + DEFAULT_MARKET_LINEAR_NOISE_START_DATE = DEFAULT_MARKET_LINEAR_NOISE_START_DATE + DEFAULT_MARKET_LINEAR_NOISE_END_DATE = DEFAULT_MARKET_LINEAR_NOISE_END_DATE + DEFAULT_MARKET_LINEAR_NOISE_ARRAYS_PATH = DEFAULT_MARKET_LINEAR_NOISE_ARRAYS_PATH + DEFAULT_NOISE_MODEL = "market_linear" + DEFAULT_GAS_COST = 1.0 + DEFAULT_PROTOCOL_FEE_SPLIT = 0.25 + LEGACY_NOISE_COEFFS = [ + -0.453, + 0.025, + -0.060, + 0.310, + -0.149, + 0.359, + 0.061, + 0.060, + ] + LEGACY_LOG_CADENCE = 2.68 + LEGACY_ARB_FREQUENCY = max(1, round(math.exp(LEGACY_LOG_CADENCE))) + FIXED_COMPARE_ARB_FREQUENCY = LEGACY_ARB_FREQUENCY + AAVE_ETH_NOISE_SETTINGS = { + "enable_noise_model": True, + "noise_model": DEFAULT_NOISE_MODEL, + "noise_reference_model": DEFAULT_NOISE_MODEL, + "noise_artifact_dir": DEFAULT_MARKET_LINEAR_ARTIFACT_DIR, + "noise_pool_id": AAVE_WETH_POOL_ID, + "noise_arrays_path": DEFAULT_MARKET_LINEAR_NOISE_ARRAYS_PATH, + "arb_frequency": FIXED_COMPARE_ARB_FREQUENCY, + "gas_cost": DEFAULT_GAS_COST, + "protocol_fee_split": DEFAULT_PROTOCOL_FEE_SPLIT, + } + CONFIGS = [ + { + "name": "AAVE/ETH launch-style range (25bps, reference)", + "tokens": ["AAVE", "ETH"], + "start": "2024-06-01 00:00:00", + "end": "2025-06-01 00:00:00", + "fees": 0.0025, + "price_ratio": 1.5014, + "centeredness_margin": 0.5, + "daily_price_shift_exponent": 0.1, + "reason": "Original launch-style parameters.", + **AAVE_ETH_NOISE_SETTINGS, + }, + { + "name": "AAVE/ETH aggressive tight range (25bps)", + "tokens": ["AAVE", "ETH"], + "start": "2024-06-01 00:00:00", + "end": "2025-06-01 00:00:00", + "fees": 0.0025, + "price_ratio": 1.10, + "centeredness_margin": 0.60, + "daily_price_shift_exponent": 0.1, + "reason": ( + "Aggressively tightened and moved to an earlier thermostat trigger. " + "At fixed price_ratio=1.10, the shift_exponent sweep still favored " + "0.1, while margin=0.60 widened the non-linear edge materially." + ), + **AAVE_ETH_NOISE_SETTINGS, + }, + ] + + def __init__(self): + self._noise_settings_cache = {} + self.noise_profile = "market_linear" + + @classmethod + def from_compare_module(cls, compare_module): + """Build an analyzer-friendly context from an imported thermostat module.""" + context = cls() + copied_attrs = ( + "RUN_CONSTANT_ARC_LENGTH", + "DEFAULT_INITIAL_POOL_VALUE", + "TVL_SWEEP_VALUES", + "HEATMAP_PRICE_RATIOS", + "HEATMAP_MARGINS", + "HEATMAP_SHIFT_EXPONENTS", + "FIXED_SLICE_FRACTIONS", + "FIXED_SLICE_LABELS", + "HEATMAP_FORWARD_CACHE_ENABLED", + "HEATMAP_FORWARD_CACHE_RUN_NAME", + "HEATMAP_FORWARD_CACHE_ROOT", + "AAVE_WETH_POOL_ID", + "DEFAULT_MARKET_LINEAR_ARTIFACT_DIR", + "DEFAULT_MARKET_LINEAR_NOISE_START_DATE", + "DEFAULT_MARKET_LINEAR_NOISE_END_DATE", + "DEFAULT_MARKET_LINEAR_NOISE_ARRAYS_PATH", + "DEFAULT_NOISE_MODEL", + "DEFAULT_GAS_COST", + "DEFAULT_PROTOCOL_FEE_SPLIT", + "LEGACY_NOISE_COEFFS", + "LEGACY_LOG_CADENCE", + "LEGACY_ARB_FREQUENCY", + "FIXED_COMPARE_ARB_FREQUENCY", + "AAVE_ETH_NOISE_SETTINGS", + "CONFIGS", + ) + for attr_name in copied_attrs: + if hasattr(compare_module, attr_name): + value = getattr(compare_module, attr_name) + if attr_name == "CONFIGS": + value = [dict(cfg) for cfg in value] + elif isinstance(value, dict): + value = dict(value) + elif isinstance(value, np.ndarray): + value = np.asarray(value, dtype=float).copy() + elif isinstance(value, tuple): + value = tuple(value) + elif isinstance(value, list): + value = list(value) + setattr(context, attr_name, value) + context._noise_settings_cache.clear() + return context + + def set_noise_profile(self, profile): + if profile != "market_linear": + raise ValueError(f"Unsupported lightweight noise profile: {profile}") + if profile != self.noise_profile: + self.noise_profile = profile + self._noise_settings_cache.clear() + + def get_initial_pool_value(self, cfg): + return float(cfg.get("initial_pool_value", self.DEFAULT_INITIAL_POOL_VALUE)) + + def get_tvl_millions(self, cfg): + return self.get_initial_pool_value(cfg) / 1_000_000.0 + + def format_tvl_millions_slug(self, cfg): + tvl_millions = self.get_tvl_millions(cfg) + rounded = round(float(tvl_millions), 6) + if np.isclose(rounded, round(rounded)): + return f"{int(round(rounded))}m" + return f"{rounded:.6f}".rstrip("0").rstrip(".").replace(".", "p") + "m" + + def format_tvl_millions_label(self, cfg): + return f"{self.get_tvl_millions(cfg):.1f}M" + + def configs_for_tvl(self, base_configs, initial_pool_value): + configs = [] + for cfg in base_configs: + updated = dict(cfg) + updated["initial_pool_value"] = float(initial_pool_value) + configs.append(updated) + return configs + + def _heatmap_forward_cache_scope_slug(self, cfg): + if cfg is None: + return "unspecified_tvl" + return f"tvl_{self.format_tvl_millions_slug(cfg)}" + + def _heatmap_forward_cache_path(self, cfg): + if not self.HEATMAP_FORWARD_CACHE_ENABLED: + return None + return os.path.join( + self.HEATMAP_FORWARD_CACHE_ROOT, + self.HEATMAP_FORWARD_CACHE_RUN_NAME, + f"forward_values_{self._heatmap_forward_cache_scope_slug(cfg)}.parquet", + ) + + def build_fixed_slice_variants(self, values): + values = np.asarray(values, dtype=float) + if values.size < len(self.FIXED_SLICE_FRACTIONS): + raise ValueError("Need at least four grid points to build fixed slices") + + variants = [] + used_indices = set() + for idx, fraction in enumerate(self.FIXED_SLICE_FRACTIONS): + target_index = int(round(fraction * (values.size - 1))) + while target_index in used_indices and target_index + 1 < values.size: + target_index += 1 + while target_index in used_indices and target_index - 1 >= 0: + target_index -= 1 + if target_index in used_indices: + raise ValueError( + "Could not build four unique fixed slices from sweep grid" + ) + used_indices.add(target_index) + variants.append( + { + "index": target_index, + "fraction": fraction, + "label": self.FIXED_SLICE_LABELS[idx], + "slug": f"q{idx + 1}", + "value": float(values[target_index]), + } + ) + return variants + + def get_pair_heatmap_specs(self, _base_cfg): + fixed_slice_variants = { + "price_ratio": self.build_fixed_slice_variants(self.HEATMAP_PRICE_RATIOS), + "centeredness_margin": self.build_fixed_slice_variants(self.HEATMAP_MARGINS), + "daily_price_shift_exponent": self.build_fixed_slice_variants( + self.HEATMAP_SHIFT_EXPONENTS + ), + } + return [ + { + "slug": "price_ratio_vs_margin", + "x_values": self.HEATMAP_PRICE_RATIOS, + "y_values": self.HEATMAP_MARGINS, + "x_key": "price_ratio", + "y_key": "centeredness_margin", + "fixed_key": "daily_price_shift_exponent", + "fixed_slices": fixed_slice_variants["daily_price_shift_exponent"], + }, + { + "slug": "shift_exp_vs_margin", + "x_values": self.HEATMAP_SHIFT_EXPONENTS, + "y_values": self.HEATMAP_MARGINS, + "x_key": "daily_price_shift_exponent", + "y_key": "centeredness_margin", + "fixed_key": "price_ratio", + "fixed_slices": fixed_slice_variants["price_ratio"], + }, + { + "slug": "price_ratio_vs_shift_exp", + "x_values": self.HEATMAP_PRICE_RATIOS, + "y_values": self.HEATMAP_SHIFT_EXPONENTS, + "x_key": "price_ratio", + "y_key": "daily_price_shift_exponent", + "fixed_key": "centeredness_margin", + "fixed_slices": fixed_slice_variants["centeredness_margin"], + }, + ] + + @staticmethod + def _hashable_noise_params(params): + if params is None: + return None + return tuple(sorted((str(k), round(float(v), 12)) for k, v in params.items())) + + def _normalize_arb_frequency(self, value, default=None): + if value is None: + if default is None: + default = self.FIXED_COMPARE_ARB_FREQUENCY + value = default + return max(int(round(float(value))), 1) + + def get_effective_arb_frequency(self, cfg, noise_cfg=None): + del noise_cfg + return self._normalize_arb_frequency(self.FIXED_COMPARE_ARB_FREQUENCY) + + def _canonical_noise_reference_model(self, cfg): + noise_model = cfg.get("noise_model", self.DEFAULT_NOISE_MODEL) or self.DEFAULT_NOISE_MODEL + reference_model = cfg.get("noise_reference_model") + if reference_model is None: + reference_model = self.DEFAULT_NOISE_MODEL if noise_model == "arb_only" else noise_model + return str(reference_model) + + def normalize_compare_run_cfg(self, cfg, enable_noise_model=None): + updated = dict(cfg) + updated["price_ratio"] = float(cfg["price_ratio"]) + updated["centeredness_margin"] = float(cfg["centeredness_margin"]) + updated["daily_price_shift_exponent"] = float( + cfg["daily_price_shift_exponent"] + ) + updated["initial_pool_value"] = float(self.get_initial_pool_value(cfg)) + updated["gas_cost"] = self.DEFAULT_GAS_COST + updated["protocol_fee_split"] = self.DEFAULT_PROTOCOL_FEE_SPLIT + updated["arb_fees"] = 0.0 + updated["arb_frequency"] = self.get_effective_arb_frequency(cfg) + updated["noise_trader_ratio"] = 0.0 + + arc_length_speed = cfg.get("arc_length_speed") + if arc_length_speed is None: + updated.pop("arc_length_speed", None) + else: + updated["arc_length_speed"] = float(arc_length_speed) + + use_noise = ( + bool(cfg.get("enable_noise_model", False)) + if enable_noise_model is None + else bool(enable_noise_model) + ) + updated["enable_noise_model"] = use_noise + + reference_mode = self._canonical_noise_reference_model(cfg) + if use_noise: + updated["noise_model"] = reference_mode + updated["noise_reference_model"] = reference_mode + else: + updated["noise_model"] = "arb_only" + updated["noise_reference_model"] = reference_mode + + if reference_mode == "market_linear": + updated["noise_arrays_path"] = self.DEFAULT_MARKET_LINEAR_NOISE_ARRAYS_PATH + updated.pop("reclamm_noise_params", None) + if use_noise or updated["noise_model"] == "arb_only": + updated["noise_artifact_dir"] = self.DEFAULT_MARKET_LINEAR_ARTIFACT_DIR + updated["noise_pool_id"] = self.AAVE_WETH_POOL_ID + else: + updated.pop("reclamm_noise_params", None) + updated.pop("noise_arrays_path", None) + updated.pop("noise_artifact_dir", None) + updated.pop("noise_pool_id", None) + + return updated + + def _load_market_linear_noise_stats(self, arrays_path=None): + arrays_path = os.path.abspath( + os.fspath(arrays_path or self.DEFAULT_MARKET_LINEAR_NOISE_ARRAYS_PATH) + ) + if not os.path.exists(arrays_path): + raise FileNotFoundError(f"market_linear arrays file not found: {arrays_path}") + + with np.load(arrays_path) as arrays: + required_keys = {"noise_base", "noise_tvl_coeff", "tvl_mean", "tvl_std"} + missing_keys = sorted(required_keys.difference(arrays.files)) + if missing_keys: + raise KeyError( + f"market_linear arrays file {arrays_path} is missing keys: {missing_keys}" + ) + return arrays_path, float(arrays["tvl_mean"]), float(arrays["tvl_std"]) + + def _market_linear_noise_settings(self, noise_model="market_linear", arb_frequency=None): + arrays_path, tvl_mean, tvl_std = self._load_market_linear_noise_stats() + arb_frequency = self._normalize_arb_frequency(arb_frequency) + return { + "noise_model": noise_model, + "noise_trader_ratio": 0.0, + "reclamm_noise_params": { + "tvl_mean": tvl_mean, + "tvl_std": tvl_std, + }, + "noise_arrays_path": arrays_path, + "arb_frequency": arb_frequency, + "noise_summary": f"{noise_model} (arb_frequency={arb_frequency})", + "noise_cache_key": ( + noise_model, + arrays_path, + arb_frequency, + round(tvl_mean, 12), + round(tvl_std, 12), + ), + } + + def _legacy_calibrated_noise_settings(self, arb_frequency=None, noise_model="calibrated"): + arb_frequency = self._normalize_arb_frequency(arb_frequency) + return { + "noise_model": noise_model, + "noise_trader_ratio": 0.0, + "reclamm_noise_params": { + f"c_{i}": self.LEGACY_NOISE_COEFFS[i] + for i in range(len(self.LEGACY_NOISE_COEFFS)) + }, + "arb_frequency": arb_frequency, + "noise_summary": f"{noise_model} (arb_frequency={arb_frequency})", + "noise_cache_key": ( + noise_model, + tuple(round(float(c), 12) for c in self.LEGACY_NOISE_COEFFS), + arb_frequency, + ), + } + + def resolve_reclamm_noise_settings(self, cfg): + cfg = self.normalize_compare_run_cfg(cfg) + enable_noise_model = cfg.get("enable_noise_model", False) + requested_mode = cfg.get("noise_model", self.DEFAULT_NOISE_MODEL) + reference_mode = cfg.get("noise_reference_model", self.DEFAULT_NOISE_MODEL) + requested_arb_frequency = self.get_effective_arb_frequency(cfg) + cache_key = ( + tuple(cfg.get("tokens", [])), + cfg.get("start"), + cfg.get("end"), + enable_noise_model, + requested_mode, + reference_mode, + cfg.get("noise_artifact_dir", self.DEFAULT_MARKET_LINEAR_ARTIFACT_DIR), + cfg.get("noise_pool_id", self.AAVE_WETH_POOL_ID), + requested_arb_frequency, + round(float(cfg.get("noise_trader_ratio", 0.0)), 12), + self._hashable_noise_params(cfg.get("reclamm_noise_params")), + cfg.get("noise_arrays_path"), + ) + if cache_key in self._noise_settings_cache: + return self._noise_settings_cache[cache_key] + + if requested_mode == "arb_only": + if reference_mode == "market_linear": + result = self._market_linear_noise_settings( + noise_model="arb_only", + arb_frequency=requested_arb_frequency + ) + elif reference_mode == "calibrated": + result = self._legacy_calibrated_noise_settings( + noise_model="arb_only", + arb_frequency=requested_arb_frequency + ) + else: + arb_frequency = requested_arb_frequency + result = { + "noise_model": "arb_only", + "noise_trader_ratio": cfg.get("noise_trader_ratio", 0.0), + "reclamm_noise_params": cfg.get("reclamm_noise_params"), + "noise_arrays_path": cfg.get("noise_arrays_path"), + "arb_frequency": arb_frequency, + "noise_summary": f"arb_only (arb_frequency={arb_frequency})", + "noise_cache_key": ( + "arb_only", + round(float(cfg.get("noise_trader_ratio", 0.0)), 12), + self._hashable_noise_params(cfg.get("reclamm_noise_params")), + cfg.get("noise_arrays_path"), + arb_frequency, + ), + } + elif requested_mode == "market_linear": + result = self._market_linear_noise_settings( + noise_model="market_linear", + arb_frequency=requested_arb_frequency, + ) + elif requested_mode == "calibrated": + result = self._legacy_calibrated_noise_settings( + arb_frequency=requested_arb_frequency + ) + else: + arb_frequency = requested_arb_frequency + result = { + "noise_model": requested_mode, + "noise_trader_ratio": cfg.get("noise_trader_ratio", 0.0), + "reclamm_noise_params": cfg.get("reclamm_noise_params"), + "noise_arrays_path": cfg.get("noise_arrays_path"), + "arb_frequency": arb_frequency, + "noise_summary": f"{requested_mode} (arb_frequency={arb_frequency})", + "noise_cache_key": ( + requested_mode, + round(float(cfg.get("noise_trader_ratio", 0.0)), 12), + self._hashable_noise_params(cfg.get("reclamm_noise_params")), + cfg.get("noise_arrays_path"), + arb_frequency, + ), + } + + self._noise_settings_cache[cache_key] = result + return result + + def make_noise_variant_cfg(self, cfg, enable_noise_model): + return self.normalize_compare_run_cfg( + cfg, + enable_noise_model=enable_noise_model, + ) + + @staticmethod + def _make_method_cache_hash(key): + return hashlib.sha256(repr(key).encode("utf-8")).hexdigest() + + def _make_method_cache_key(self, cfg, method): + cfg = self.normalize_compare_run_cfg(cfg) + noise_cfg = self.resolve_reclamm_noise_settings(cfg) + arb_frequency = self.get_effective_arb_frequency(cfg, noise_cfg) + key = ( + method, + bool(cfg.get("enable_noise_model", False)), + round(float(cfg["price_ratio"]), 6), + round(float(cfg["centeredness_margin"]), 6), + round(float(cfg["daily_price_shift_exponent"]), 6), + round(self.get_initial_pool_value(cfg), 2), + noise_cfg.get("noise_cache_key"), + None if arb_frequency is None else int(arb_frequency), + round( + float( + cfg.get( + "gas_cost", + self.DEFAULT_GAS_COST + if cfg.get("enable_noise_model", False) + else 0.0, + ) + ), + 6, + ), + round( + float( + cfg.get( + "protocol_fee_split", + self.DEFAULT_PROTOCOL_FEE_SPLIT + if cfg.get("enable_noise_model", False) + else 0.0, + ) + ), + 6, + ), + ) + if method == "constant_arc_length": + speed = cfg.get("arc_length_speed") + key += (None if speed is None else round(float(speed), 12),) + return key + + +def parse_args() -> argparse.Namespace: + parser = argparse.ArgumentParser( + description=( + "Identify horizontally and vertically adjacent reCLAMM heatmap cells " + "whose derived metric values differ by at least the requested threshold." + ) + ) + parser.add_argument( + "--metric-key", + default="noise_vs_arb_geometric_improvement_pct", + choices=sorted(CACHE_ONLY_METRIC_SPECS), + help="Heatmap metric to reconstruct from the persisted forward-value cache.", + ) + parser.add_argument( + "--pair-slug", + default="price_ratio_vs_margin", + help="Pair heatmap family slug, or 'all' to scan every pair family.", + ) + parser.add_argument( + "--slice-slug", + default="all", + help="Quarter-slice slug (q1/q2/q3/q4), or 'all' to scan every slice.", + ) + parser.add_argument( + "--min-diff", + type=float, + default=30.0, + help=( + "Minimum absolute difference between adjacent heatmap values. " + "For the default metric this is in percentage points." + ), + ) + parser.add_argument( + "--adjacency-axis", + default="both", + choices=("both", "horizontal", "vertical"), + help=( + "Which adjacency direction to scan. " + "'both' includes horizontal and vertical neighbors." + ), + ) + parser.add_argument( + "--initial-pool-value", + type=float, + default=1_000_000.0, + help="TVL in USD used for the cached heatmap sweep.", + ) + parser.add_argument( + "--config-index", + type=int, + default=1, + help="Which compare_reclamm_thermostats.py base config to use.", + ) + parser.add_argument( + "--cache-path", + default=None, + help="Optional parquet cache override. Defaults to the compare script's TVL cache.", + ) + parser.add_argument( + "--output-csv", + default=None, + help="Optional CSV output path. Defaults under scripts/results/.", + ) + parser.add_argument( + "--skip-top-row-geometric-comparison", + action="store_true", + help=( + "Skip the follow-up geometric noise comparison for the top CSV row. " + "By default the script attempts that comparison after writing the CSV." + ), + ) + parser.add_argument( + "--top-row-geometric-comparison-output-file", + default=None, + help="Optional PNG output path override for the top-row geometric comparison.", + ) + parser.add_argument( + "--allow-partial-cache", + action="store_true", + help="Write output even if some heatmap cells are missing from the cache.", + ) + return parser.parse_args() + + +def load_compare_module(module_path: Optional[Path] = None): + compare_path = module_path or Path(__file__).with_name("compare_reclamm_thermostats.py") + spec = importlib.util.spec_from_file_location( + "reclamm_compare_reclamm_thermostats", + compare_path, + ) + if spec is not None and spec.loader is not None: + module = importlib.util.module_from_spec(spec) + try: + spec.loader.exec_module(module) + return _LightweightCompareContext.from_compare_module(module) + except ModuleNotFoundError as exc: + if exc.name != "jax": + raise + print( + "compare_reclamm_thermostats.py depends on jax in this environment; " + "using the lightweight cache-key context instead." + ) + return _LightweightCompareContext() + + +def get_metric_spec(metric_key: str) -> Mapping[str, object]: + try: + return CACHE_ONLY_METRIC_SPECS[metric_key] + except KeyError as exc: + raise ValueError( + f"Unsupported metric_key={metric_key!r}. " + f"Supported cache-only metrics: {sorted(CACHE_ONLY_METRIC_SPECS)}" + ) from exc + + +def load_cache_lookup(cache_path: Path) -> Dict[str, float]: + frame = pd.read_parquet(cache_path, columns=["cache_key_hash", "final_value"]) + return { + str(row.cache_key_hash): float(row.final_value) + for row in frame.itertuples(index=False) + } + + +def resolve_existing_cache_path(cache_path: Path) -> Path: + candidates = [Path(cache_path)] + if not cache_path.is_absolute(): + candidates.append(Path("scripts") / cache_path) + for candidate in candidates: + if candidate.exists(): + return candidate + return Path(cache_path) + + +def load_geometric_compare_module(module_path: Optional[Path] = None): + compare_path = module_path or Path(__file__).with_name( + "compare_reclamm_geometric_noise_runs.py" + ) + spec = importlib.util.spec_from_file_location( + "reclamm_compare_reclamm_geometric_noise_runs", + compare_path, + ) + if spec is None or spec.loader is None: + raise RuntimeError(f"Could not load geometric compare module from {compare_path}") + module = importlib.util.module_from_spec(spec) + spec.loader.exec_module(module) + return module + + +def run_top_row_geometric_comparison( + csv_path: Path, + output_file: Optional[str] = None, + row_index: int = 0, +): + """Run the paired geometric-vs-arb comparison using the top adjacent CSV row.""" + module = load_geometric_compare_module() + if not hasattr(module, "run_adjacent_csv_row_comparison"): + raise RuntimeError( + "compare_reclamm_geometric_noise_runs.py does not expose " + "run_adjacent_csv_row_comparison" + ) + return module.run_adjacent_csv_row_comparison( + csv_path=csv_path, + row_index=row_index, + output_file=output_file, + ) + + +def resolve_pair_specs(compare_module, base_cfg: Mapping[str, object], pair_slug: str): + pair_specs = compare_module.get_pair_heatmap_specs(base_cfg) + if pair_slug == "all": + return pair_specs + + matched = [pair for pair in pair_specs if pair["slug"] == pair_slug] + if not matched: + available = [pair["slug"] for pair in pair_specs] + raise ValueError( + f"Unknown pair slug {pair_slug!r}. Available pair slugs: {available}" + ) + return matched + + +def resolve_slice_variants(pair_spec: Mapping[str, object], slice_slug: str): + slice_variants = pair_spec["fixed_slices"] + if slice_slug == "all": + return list(slice_variants) + + matched = [variant for variant in slice_variants if variant["slug"] == slice_slug] + if not matched: + available = [variant["slug"] for variant in slice_variants] + raise ValueError( + f"Unknown slice slug {slice_slug!r}. Available slice slugs: {available}" + ) + return matched + + +def build_default_output_path( + compare_module, + base_cfg: Mapping[str, object], + metric_key: str, + pair_slug: str, + slice_slug: str, + min_diff: float, +) -> Path: + output_dir = Path("scripts/results/reclamm_heatmap_adjacency") + output_dir.mkdir(parents=True, exist_ok=True) + diff_token = str(float(min_diff)).rstrip("0").rstrip(".").replace(".", "p") + filename = ( + f"reclamm_adjacent_pairs_{metric_key}_{pair_slug}_{slice_slug}" + f"_mindiff_{diff_token}_tvl_{compare_module.format_tvl_millions_slug(base_cfg)}.csv" + ) + return output_dir / filename + + +def autodetect_lightweight_noise_profile( + compare_module, + base_cfg: Mapping[str, object], + pair_specs: Sequence[Mapping[str, object]], + metric_key: str, + slice_slug: str, + cache_lookup: Mapping[str, float], +): + if not hasattr(compare_module, "set_noise_profile"): + return + del base_cfg, pair_specs, metric_key, slice_slug, cache_lookup + compare_module.set_noise_profile("market_linear") + print("Lightweight noise profile fixed to market_linear.") + + +def _source_variant(compare_module, cfg: Mapping[str, object], source_name: str): + enable_noise_model = source_name.startswith("noise_") + method = "geometric" if source_name.endswith("geometric") else "constant_arc_length" + return compare_module.make_noise_variant_cfg(cfg, enable_noise_model), method + + +def _compute_metric_value(metric_key: str, final_values: Mapping[str, float]) -> float: + metric_spec = get_metric_spec(metric_key) + return float(metric_spec["compute"](final_values)) + + +def build_cell_record( + compare_module, + cfg: Mapping[str, object], + pair_spec: Mapping[str, object], + slice_variant: Mapping[str, object], + metric_key: str, + x_index: int, + y_index: int, + cache_lookup: Mapping[str, float], +): + metric_spec = get_metric_spec(metric_key) + final_values = {} + missing_hashes = [] + for source_name in metric_spec["sources"]: + source_cfg, method = _source_variant(compare_module, cfg, source_name) + cache_key = compare_module._make_method_cache_key(source_cfg, method) + cache_key_hash = compare_module._make_method_cache_hash(cache_key) + cached_value = cache_lookup.get(cache_key_hash) + if cached_value is None: + missing_hashes.append(cache_key_hash) + continue + final_values[source_name] = float(cached_value) + + if missing_hashes: + return None, missing_hashes + + return ( + { + "metric_key": metric_key, + "metric_unit": metric_spec["unit"], + "source_noise_profile": str( + getattr(compare_module, "noise_profile", "unknown") + ), + "pair_slug": pair_spec["slug"], + "slice_slug": slice_variant["slug"], + "slice_label": slice_variant["label"], + "fixed_key": pair_spec["fixed_key"], + "fixed_value": float(slice_variant["value"]), + "price_ratio": float(cfg["price_ratio"]), + "centeredness_margin": float(cfg["centeredness_margin"]), + "daily_price_shift_exponent": float(cfg["daily_price_shift_exponent"]), + "tvl_usd": float(compare_module.get_initial_pool_value(cfg)), + "heatmap_value": _compute_metric_value(metric_key, final_values), + "x_index": int(x_index), + "y_index": int(y_index), + }, + [], + ) + + +def build_slice_cell_grid( + compare_module, + base_cfg: Mapping[str, object], + pair_spec: Mapping[str, object], + slice_variant: Mapping[str, object], + metric_key: str, + cache_lookup: Mapping[str, float], +): + records_by_coord: Dict[Tuple[int, int], MutableMapping[str, object]] = {} + missing_hashes: List[str] = [] + x_values = pair_spec["x_values"] + y_values = pair_spec["y_values"] + slice_cfg = dict(base_cfg) + slice_cfg[pair_spec["fixed_key"]] = float(slice_variant["value"]) + + for y_index, y_value in enumerate(y_values): + for x_index, x_value in enumerate(x_values): + cfg = dict(slice_cfg) + cfg[pair_spec["x_key"]] = float(x_value) + cfg[pair_spec["y_key"]] = float(y_value) + record, missing_for_cell = build_cell_record( + compare_module=compare_module, + cfg=cfg, + pair_spec=pair_spec, + slice_variant=slice_variant, + metric_key=metric_key, + x_index=x_index, + y_index=y_index, + cache_lookup=cache_lookup, + ) + if record is not None: + records_by_coord[(y_index, x_index)] = record + missing_hashes.extend(missing_for_cell) + + expected_cell_count = len(x_values) * len(y_values) + return { + "records_by_coord": records_by_coord, + "expected_cell_count": expected_cell_count, + "resolved_cell_count": len(records_by_coord), + "missing_hash_count": len(missing_hashes), + "missing_hashes": missing_hashes, + } + + +def build_adjacent_row( + metric_key: str, + metric_unit: str, + axis: str, + first_cell: Mapping[str, object], + second_cell: Mapping[str, object], +) -> Dict[str, object]: + signed_diff = float(second_cell["heatmap_value"]) - float(first_cell["heatmap_value"]) + abs_diff = abs(signed_diff) + return { + "metric_key": metric_key, + "metric_unit": metric_unit, + "source_noise_profile": first_cell.get("source_noise_profile", "unknown"), + "pair_slug": first_cell["pair_slug"], + "slice_slug": first_cell["slice_slug"], + "slice_label": first_cell["slice_label"], + "fixed_key": first_cell["fixed_key"], + "fixed_value": float(first_cell["fixed_value"]), + "adjacency_axis": axis, + "heatmap_value_diff_abs": abs_diff, + "heatmap_value_diff_signed_2_minus_1": signed_diff, + "1_price_ratio": float(first_cell["price_ratio"]), + "1_centeredness_margin": float(first_cell["centeredness_margin"]), + "1_daily_price_shift_exponent": float(first_cell["daily_price_shift_exponent"]), + "1_tvl_usd": float(first_cell["tvl_usd"]), + "1_heatmap_value": float(first_cell["heatmap_value"]), + "1_x_index": int(first_cell["x_index"]), + "1_y_index": int(first_cell["y_index"]), + "2_price_ratio": float(second_cell["price_ratio"]), + "2_centeredness_margin": float(second_cell["centeredness_margin"]), + "2_daily_price_shift_exponent": float(second_cell["daily_price_shift_exponent"]), + "2_tvl_usd": float(second_cell["tvl_usd"]), + "2_heatmap_value": float(second_cell["heatmap_value"]), + "2_x_index": int(second_cell["x_index"]), + "2_y_index": int(second_cell["y_index"]), + } + + +def find_adjacent_rows_for_slice( + metric_key: str, + metric_unit: str, + records_by_coord: Mapping[Tuple[int, int], Mapping[str, object]], + x_count: int, + y_count: int, + min_diff: float, + adjacency_axis: str = "both", +) -> List[Dict[str, object]]: + rows = [] + + if adjacency_axis not in {"both", "horizontal", "vertical"}: + raise ValueError( + f"Unsupported adjacency_axis={adjacency_axis!r}; expected both, horizontal, or vertical" + ) + + if adjacency_axis in {"both", "horizontal"}: + for y_index in range(y_count): + for x_index in range(x_count - 1): + first_cell = records_by_coord.get((y_index, x_index)) + second_cell = records_by_coord.get((y_index, x_index + 1)) + if first_cell is None or second_cell is None: + continue + row = build_adjacent_row( + metric_key=metric_key, + metric_unit=metric_unit, + axis="horizontal", + first_cell=first_cell, + second_cell=second_cell, + ) + if row["heatmap_value_diff_abs"] >= min_diff: + rows.append(row) + + if adjacency_axis in {"both", "vertical"}: + for y_index in range(y_count - 1): + for x_index in range(x_count): + first_cell = records_by_coord.get((y_index, x_index)) + second_cell = records_by_coord.get((y_index + 1, x_index)) + if first_cell is None or second_cell is None: + continue + row = build_adjacent_row( + metric_key=metric_key, + metric_unit=metric_unit, + axis="vertical", + first_cell=first_cell, + second_cell=second_cell, + ) + if row["heatmap_value_diff_abs"] >= min_diff: + rows.append(row) + + rows.sort( + key=lambda row: ( + -float(row["heatmap_value_diff_abs"]), + str(row["pair_slug"]), + str(row["slice_slug"]), + str(row["adjacency_axis"]), + int(row["1_y_index"]), + int(row["1_x_index"]), + ) + ) + return rows + + +def scan_heatmap_pairs( + compare_module, + base_cfg: Mapping[str, object], + metric_key: str, + pair_specs: Sequence[Mapping[str, object]], + slice_slug: str, + min_diff: float, + adjacency_axis: str, + cache_lookup: Mapping[str, float], +): + metric_spec = get_metric_spec(metric_key) + all_rows: List[Dict[str, object]] = [] + diagnostics = [] + + for pair_spec in pair_specs: + slice_variants = resolve_slice_variants(pair_spec, slice_slug) + for slice_variant in slice_variants: + slice_scan = build_slice_cell_grid( + compare_module=compare_module, + base_cfg=base_cfg, + pair_spec=pair_spec, + slice_variant=slice_variant, + metric_key=metric_key, + cache_lookup=cache_lookup, + ) + diagnostics.append( + { + "pair_slug": pair_spec["slug"], + "slice_slug": slice_variant["slug"], + "resolved_cell_count": slice_scan["resolved_cell_count"], + "expected_cell_count": slice_scan["expected_cell_count"], + "missing_hash_count": slice_scan["missing_hash_count"], + } + ) + all_rows.extend( + find_adjacent_rows_for_slice( + metric_key=metric_key, + metric_unit=metric_spec["unit"], + records_by_coord=slice_scan["records_by_coord"], + x_count=len(pair_spec["x_values"]), + y_count=len(pair_spec["y_values"]), + min_diff=min_diff, + adjacency_axis=adjacency_axis, + ) + ) + + return all_rows, diagnostics + + +def rows_to_frame(rows: Iterable[Mapping[str, object]]) -> pd.DataFrame: + frame = pd.DataFrame(list(rows)) + if frame.empty: + return pd.DataFrame(columns=OUTPUT_COLUMNS) + frame = frame.loc[:, OUTPUT_COLUMNS] + frame.sort_values( + by=[ + "heatmap_value_diff_abs", + "pair_slug", + "slice_slug", + "adjacency_axis", + "1_y_index", + "1_x_index", + ], + ascending=[False, True, True, True, True, True], + inplace=True, + ignore_index=True, + ) + return frame + + +def main() -> int: + args = parse_args() + compare_module = load_compare_module() + + if not 0 <= args.config_index < len(compare_module.CONFIGS): + raise ValueError( + f"config-index {args.config_index} is out of range for " + f"{len(compare_module.CONFIGS)} available configs" + ) + + base_cfg = compare_module.configs_for_tvl( + compare_module.CONFIGS, + initial_pool_value=args.initial_pool_value, + )[args.config_index] + pair_specs = resolve_pair_specs(compare_module, base_cfg, args.pair_slug) + + cache_path = ( + Path(args.cache_path) + if args.cache_path is not None + else Path(compare_module._heatmap_forward_cache_path(base_cfg)) + ) + cache_path = resolve_existing_cache_path(cache_path) + if not cache_path.exists(): + raise FileNotFoundError( + f"Cache parquet not found: {cache_path}. " + "Generate the current market_linear/arb_only heatmap cache first." + ) + + cache_lookup = load_cache_lookup(cache_path) + autodetect_lightweight_noise_profile( + compare_module=compare_module, + base_cfg=base_cfg, + pair_specs=pair_specs, + metric_key=args.metric_key, + slice_slug=args.slice_slug, + cache_lookup=cache_lookup, + ) + output_csv = ( + Path(args.output_csv) + if args.output_csv is not None + else build_default_output_path( + compare_module=compare_module, + base_cfg=base_cfg, + metric_key=args.metric_key, + pair_slug=args.pair_slug, + slice_slug=args.slice_slug, + min_diff=args.min_diff, + ) + ) + + print( + f"Loaded {len(cache_lookup):,} cached final values from {cache_path} " + f"for {base_cfg['name']} at TVL {compare_module.format_tvl_millions_label(base_cfg)}." + ) + print( + f"Scanning metric={args.metric_key}, pair_slug={args.pair_slug}, " + f"slice_slug={args.slice_slug}, adjacency_axis={args.adjacency_axis}, " + f"min_diff={args.min_diff} " + f"({get_metric_spec(args.metric_key)['unit']})." + ) + + rows, diagnostics = scan_heatmap_pairs( + compare_module=compare_module, + base_cfg=base_cfg, + metric_key=args.metric_key, + pair_specs=pair_specs, + slice_slug=args.slice_slug, + min_diff=args.min_diff, + adjacency_axis=args.adjacency_axis, + cache_lookup=cache_lookup, + ) + + for diagnostic in diagnostics: + print( + f"[{diagnostic['pair_slug']}:{diagnostic['slice_slug']}] " + f"resolved {diagnostic['resolved_cell_count']}/" + f"{diagnostic['expected_cell_count']} cells " + f"({diagnostic['missing_hash_count']} missing cache hashes)" + ) + + missing_any = any(diagnostic["missing_hash_count"] > 0 for diagnostic in diagnostics) + if missing_any and not args.allow_partial_cache: + raise RuntimeError( + "Cache was incomplete for at least one requested heatmap slice. " + "This cache does not match the current market_linear/arb_only " + "parameterization. Regenerate the heatmap cache, or re-run with " + "--allow-partial-cache to write only the rows that were resolvable." + ) + + frame = rows_to_frame(rows) + output_csv.parent.mkdir(parents=True, exist_ok=True) + frame.to_csv(output_csv, index=False) + print(f"Wrote {len(frame):,} adjacent pairs to {output_csv}") + + if not args.skip_top_row_geometric_comparison: + if frame.empty: + print("Skipping top-row geometric comparison because the CSV is empty.") + else: + print( + "Running geometric noise comparison for the top adjacent-pairs CSV row..." + ) + try: + comparison_output = run_top_row_geometric_comparison( + csv_path=output_csv, + output_file=args.top_row_geometric_comparison_output_file, + row_index=0, + ) + print( + f"Completed top-row geometric comparison using {output_csv} row 0. " + f"Output: {comparison_output}" + ) + except Exception as exc: # pragma: no cover - depends on local runtime deps + print( + "Top-row geometric comparison did not run successfully: " + f"{exc}" + ) + return 0 + + +if __name__ == "__main__": + raise SystemExit(main()) diff --git a/scripts/reclamm/sim_vs_world_comparison.py b/scripts/reclamm/sim_vs_world_comparison.py index 0c754ea..bf7d19d 100644 --- a/scripts/reclamm/sim_vs_world_comparison.py +++ b/scripts/reclamm/sim_vs_world_comparison.py @@ -34,6 +34,7 @@ from pathlib import Path from datetime import datetime, timezone +from quantammsim.core_simulator.dynamic_inputs import DynamicInputFrames from quantammsim.runners.jax_runners import do_run_on_historic_data # ── On-chain reClAMM params ─────────────────────────────────────────────────── @@ -187,9 +188,18 @@ def sample_at_timestamps(minute_vals, start_unix_sec, timestamps_sec): return minute_vals[indices] -def run_pool(tokens, start, end, rule, fees, params, gas_cost=0.0, - protocol_fee_split=0.0, gas_cost_df=None, - onchain_initial_state=None): +def run_pool( + tokens, + start, + end, + rule, + fees, + params, + gas_cost=0.0, + protocol_fee_split=0.0, + dynamic_input_frames=None, + onchain_initial_state=None, +): """Run a quantammsim pool and return minute-level results. Returns (val_eth, price_ratio, start_unix_sec) where val_eth and @@ -220,7 +230,9 @@ def run_pool(tokens, start, end, rule, fees, params, gas_cost=0.0, fp["reclamm_initial_state"] = onchain_initial_state result = do_run_on_historic_data( - run_fingerprint=fp, params=params, gas_cost_df=gas_cost_df, + run_fingerprint=fp, + params=params, + dynamic_input_frames=dynamic_input_frames, ) # Prices: sorted tokens → [AAVE, ETH] in USD @@ -291,7 +303,8 @@ def run_gas_experiment(args): gas_df = load_gas_csv(pct) val_eth_min, _, _ = run_pool( tokens, start, end, "reclamm", ONCHAIN_FEES, pool_params, - protocol_fee_split=PROTOCOL_FEE_SPLIT, gas_cost_df=gas_df, + protocol_fee_split=PROTOCOL_FEE_SPLIT, + dynamic_input_frames=DynamicInputFrames(gas_cost=gas_df), ) gas_results_min[pct] = val_eth_min @@ -433,7 +446,8 @@ def run_gas_scale_experiment(args): gas_df["trade_gas_cost_usd"] = gas_df_raw["trade_gas_cost_usd"] * scale val_eth_min, pr_min, start_sec = run_pool( tokens, start, end, "reclamm", ONCHAIN_FEES, pool_params, - protocol_fee_split=PROTOCOL_FEE_SPLIT, gas_cost_df=gas_df, + protocol_fee_split=PROTOCOL_FEE_SPLIT, + dynamic_input_frames=DynamicInputFrames(gas_cost=gas_df), onchain_initial_state=onchain_state, ) results_min[(pct, scale)] = (val_eth_min, start_sec) @@ -662,7 +676,8 @@ def run_best_gas_experiment(args): gas_df_50p = load_gas_csv("50p") g50_min, _, _ = run_pool( tokens, start, end, "reclamm", ONCHAIN_FEES, pool_params, - protocol_fee_split=PROTOCOL_FEE_SPLIT, gas_cost_df=gas_df_50p, + protocol_fee_split=PROTOCOL_FEE_SPLIT, + dynamic_input_frames=DynamicInputFrames(gas_cost=gas_df_50p), onchain_initial_state=onchain_state, ) @@ -674,7 +689,8 @@ def run_best_gas_experiment(args): gas_df_75p_scaled["trade_gas_cost_usd"] *= 0.75 g75_min, _, _ = run_pool( tokens, start, end, "reclamm", ONCHAIN_FEES, pool_params, - protocol_fee_split=PROTOCOL_FEE_SPLIT, gas_cost_df=gas_df_75p_scaled, + protocol_fee_split=PROTOCOL_FEE_SPLIT, + dynamic_input_frames=DynamicInputFrames(gas_cost=gas_df_75p_scaled), onchain_initial_state=onchain_state, ) @@ -686,7 +702,8 @@ def run_best_gas_experiment(args): gas_df_90p_scaled["trade_gas_cost_usd"] *= 0.25 g90_min, _, _ = run_pool( tokens, start, end, "reclamm", ONCHAIN_FEES, pool_params, - protocol_fee_split=PROTOCOL_FEE_SPLIT, gas_cost_df=gas_df_90p_scaled, + protocol_fee_split=PROTOCOL_FEE_SPLIT, + dynamic_input_frames=DynamicInputFrames(gas_cost=gas_df_90p_scaled), onchain_initial_state=onchain_state, ) diff --git a/scripts/replace_usdt_with_usdc_data.py b/scripts/replace_usdt_with_usdc_data.py new file mode 100644 index 0000000..2eaa185 --- /dev/null +++ b/scripts/replace_usdt_with_usdc_data.py @@ -0,0 +1,74 @@ +import argparse +from pathlib import Path + +import pandas as pd + + +FILE_PAIRS = [ + ("USDC_USD.parquet", "USDT_USD.parquet"), + ("USDC_USD_daily.csv", "USDT_USD_daily.csv"), + ("combined_data/USDC_USD_hourly.csv", "combined_data/USDT_USD_hourly.csv"), +] + + +def rewrite_usdc_frame_as_usdt(df: pd.DataFrame) -> pd.DataFrame: + rewritten = df.copy() + + if "Volume USDC" in rewritten.columns: + rewritten = rewritten.rename(columns={"Volume USDC": "Volume USDT"}) + + if "symbol" in rewritten.columns: + rewritten["symbol"] = "USDT/USD" + + return rewritten + + +def process_file(data_dir: Path, source_rel: str, target_rel: str, dry_run: bool) -> None: + source_path = data_dir / source_rel + target_path = data_dir / target_rel + + if not source_path.exists(): + raise FileNotFoundError(f"Missing source file: {source_path}") + + if source_path.suffix == ".parquet": + df = pd.read_parquet(source_path) + else: + df = pd.read_csv(source_path) + + rewritten = rewrite_usdc_frame_as_usdt(df) + + print(f"{source_path} -> {target_path} ({len(rewritten)} rows)") + + if dry_run: + return + + target_path.parent.mkdir(parents=True, exist_ok=True) + if target_path.suffix == ".parquet": + rewritten.to_parquet(target_path, index=False) + else: + rewritten.to_csv(target_path, index=False) + + +def main() -> None: + parser = argparse.ArgumentParser( + description="Overwrite USDT data files with USDC data rewritten as USDT/USD." + ) + parser.add_argument( + "--data-dir", + default=Path(__file__).resolve().parent.parent / "quantammsim" / "data", + type=Path, + help="Base data directory containing the USDC/USDT files.", + ) + parser.add_argument( + "--dry-run", + action="store_true", + help="Show the files that would be rewritten without modifying them.", + ) + args = parser.parse_args() + + for source_rel, target_rel in FILE_PAIRS: + process_file(args.data_dir, source_rel, target_rel, args.dry_run) + + +if __name__ == "__main__": + main() diff --git a/scripts/run_direct_calibration_top50.py b/scripts/run_direct_calibration_top50.py new file mode 100644 index 0000000..d323c0b --- /dev/null +++ b/scripts/run_direct_calibration_top50.py @@ -0,0 +1,862 @@ +"""Run direct calibration pipeline and plot top-50 style decomposition. + +Steps: + 1. Load panel, match to per-day grids in results/pool_grids_v2/ + 2. Option C: per-pool L-BFGS-B fits + 3. Option A: joint end-to-end optimization (warm-started from C) + 4. Paginated plots: V_arb + V_noise decomposition per pool + 5. Summary plots: cadence, gas, R², arb fraction distributions +""" + +import json +import os +import sys +from datetime import date, timedelta + +import matplotlib +matplotlib.use("Agg") +import matplotlib.pyplot as plt +import numpy as np +import pandas as pd + +# ---- Config ---- +PANEL_CACHE = os.path.join( + os.path.dirname(os.path.dirname(__file__)), + "local_data", "noise_calibration", "panel.parquet", +) +GRID_DIR = os.path.join( + os.path.dirname(os.path.dirname(__file__)), + "results", "pool_grids_v2", +) +OUTPUT_DIR = os.path.join( + os.path.dirname(os.path.dirname(__file__)), + "results", "direct_calibration_top50", +) +TRAIN_DAYS = 0 # 0 = no filter, use all available data per pool +TOP_N = 50 +OPTION_C_MAXITER = 500 +JOINT_MAXITER = 500 +OPTION_C_LOSS_CUTOFF = 5.0 # Drop pools with Option C loss above this from joint fit + + +def load_and_match(): + """Load panel, match to grids. No date filter — each pool uses all data.""" + from quantammsim.calibration.pool_data import ( + match_grids_to_panel, + replace_panel_volatility_with_binance, + ) + + panel = pd.read_parquet(PANEL_CACHE) + + # Optional date filter (TRAIN_DAYS=0 means no filter) + if TRAIN_DAYS > 0: + max_date = panel["date"].max() + if not isinstance(max_date, date): + max_date = pd.Timestamp(max_date).date() + cutoff = max_date - timedelta(days=TRAIN_DAYS) + panel = panel[ + panel["date"].apply( + lambda d: d >= cutoff if isinstance(d, date) + else pd.Timestamp(d).date() >= cutoff + ) + ].copy() + else: + panel = panel.copy() + + if "log_tvl_lag1" not in panel.columns: + panel = panel.sort_values(["pool_id", "date"]).reset_index(drop=True) + panel["log_tvl_lag1"] = panel.groupby("pool_id")["log_tvl"].shift(1) + panel = panel.dropna(subset=["log_tvl_lag1"]).reset_index(drop=True) + + pool_counts = panel.groupby("pool_id").size() + valid = pool_counts[pool_counts >= 10].index + panel = panel[panel["pool_id"].isin(valid)].copy() + + # Replace Balancer-hourly volatility with Binance-minute volatility + print("Replacing volatility with Binance minute data...") + panel = replace_panel_volatility_with_binance(panel) + + min_date = panel["date"].min() + max_date = panel["date"].max() + print(f"Panel: {len(panel)} obs, {panel['pool_id'].nunique()} pools, " + f"{min_date} to {max_date}") + + matched = match_grids_to_panel(GRID_DIR, panel) + print(f"Matched: {len(matched)} pools with grids") + + return panel, matched + + +def run_option_c(matched, fix_gas_to_chain=False): + """Per-pool L-BFGS-B fits.""" + from quantammsim.calibration.per_pool_fit import fit_all_pools + gas_label = " (gas fixed to chain)" if fix_gas_to_chain else "" + print(f"\n--- Option C: per-pool fits ({len(matched)} pools){gas_label} ---") + results = fit_all_pools(matched, fix_gas_to_chain=fix_gas_to_chain) + n_converged = sum(1 for r in results.values() if r["converged"]) + losses = [r["loss"] for r in results.values()] + print(f" Converged: {n_converged}/{len(results)}") + print(f" Loss: median={np.median(losses):.4f}, " + f"mean={np.mean(losses):.4f}, " + f"range=[{np.min(losses):.4f}, {np.max(losses):.4f}]") + return results + + +def run_option_a(matched, option_c_results, fix_gas_to_chain=False): + """Joint end-to-end optimization, warm-started from Option C. + + Drops pathological pools (Option C loss > OPTION_C_LOSS_CUTOFF) from the + joint fit to prevent them from dominating the shared mapping. + """ + from quantammsim.calibration.joint_fit import fit_joint + + # Filter out pathological pools + good_pools = {p: r for p, r in option_c_results.items() + if r["loss"] <= OPTION_C_LOSS_CUTOFF} + dropped = set(option_c_results) - set(good_pools) + matched_clean = {p: matched[p] for p in good_pools if p in matched} + + if dropped: + print(f"\n Dropping {len(dropped)} pathological pools (Option C loss > {OPTION_C_LOSS_CUTOFF}):") + for p in sorted(dropped): + r = option_c_results[p] + print(f" {p} {r['tokens']:<16} loss={r['loss']:.1f}") + + gas_label = ", gas fixed" if fix_gas_to_chain else "" + print(f"\n--- Option A: joint fit (per_pool_noise, {len(matched_clean)} pools, " + f"warm-start from C, no chain dummies{gas_label}) ---") + result_ppn = fit_joint( + matched_clean, + mode="per_pool_noise", + init_from_option_c=good_pools, + maxiter=JOINT_MAXITER, + drop_chain_dummies=True, + fix_gas_to_chain=fix_gas_to_chain, + ) + print(f" Loss: {result_ppn['init_loss']:.4f} -> {result_ppn['loss']:.4f}") + print(f" Converged: {result_ppn['converged']}") + + print(f"\n--- Option A: joint fit (shared_noise, {len(matched_clean)} pools, " + f"warm-start from C, no chain dummies{gas_label}) ---") + result_sn = fit_joint( + matched_clean, + mode="shared_noise", + init_from_option_c=good_pools, + maxiter=JOINT_MAXITER, + drop_chain_dummies=True, + fix_gas_to_chain=fix_gas_to_chain, + ) + print(f" Loss: {result_sn['init_loss']:.4f} -> {result_sn['loss']:.4f}") + print(f" Converged: {result_sn['converged']}") + + return result_ppn, result_sn + + +def run_option_rf(matched, option_c_results): + """2-stage approach: Option C per-pool fits → Ridge/RF on pool attributes. + + Drops chain dummies (too sparse for n~30), keeps 6 continuous/binary features. + Trains both Ridge regression and RF, reports both. LOO-CV for generalization. + + Noise coefficients are taken directly from Option C (per-pool). + Drops pathological pools (Option C loss > OPTION_C_LOSS_CUTOFF). + """ + from sklearn.ensemble import RandomForestRegressor + from sklearn.linear_model import RidgeCV + from sklearn.model_selection import LeaveOneOut + from quantammsim.calibration.pool_data import build_pool_attributes + + # Filter pathological pools + good_pools = {p: r for p, r in option_c_results.items() + if r["loss"] <= OPTION_C_LOSS_CUTOFF} + dropped = set(option_c_results) - set(good_pools) + matched_clean = {p: matched[p] for p in good_pools if p in matched} + + if dropped: + print(f"\n Dropping {len(dropped)} pathological pools (Option C loss > {OPTION_C_LOSS_CUTOFF}):") + for p in sorted(dropped): + r = option_c_results[p] + print(f" {p} {r['tokens']:<16} loss={r['loss']:.1f}") + + # Build attributes and targets + X_attr_full, attr_names_full, pool_ids = build_pool_attributes(matched_clean) + n_pools = len(pool_ids) + + # Drop chain dummies — too sparse for n~30. Keep only continuous/binary features. + non_chain_mask = [i for i, name in enumerate(attr_names_full) + if not name.startswith("chain_")] + X_attr = X_attr_full[:, non_chain_mask] + attr_names = [attr_names_full[i] for i in non_chain_mask] + k_attr = len(attr_names) + + # Detect if gas was fixed in Option C + gas_fixed = any(good_pools[p].get("gas_fixed", False) for p in pool_ids) + + if gas_fixed: + print(f"\n--- Option RF: 2-stage mapping ({n_pools} pools, {k_attr} features, " + f"cadence only — gas fixed) ---") + else: + print(f"\n--- Option RF: 2-stage mapping ({n_pools} pools, {k_attr} features) ---") + print(f" Features: {', '.join(attr_names)}") + + Y_cad = np.array([good_pools[p]["log_cadence"] for p in pool_ids]) + Y_gas = np.array([good_pools[p]["log_gas"] for p in pool_ids]) + + ss_tot_cad = np.sum((Y_cad - Y_cad.mean()) ** 2) + + def compute_r2(y_true, y_pred, ss_tot): + return 1 - np.sum((y_true - y_pred) ** 2) / max(ss_tot, 1e-10) + + # ---- Ridge regression (cadence only when gas is fixed) ---- + alphas = np.logspace(-2, 4, 50) + ridge_cad = RidgeCV(alphas=alphas, cv=None) # GCV/LOO built-in + ridge_cad.fit(X_attr, Y_cad) + + Y_ridge_cad_train = ridge_cad.predict(X_attr) + r2_ridge_cad = compute_r2(Y_cad, Y_ridge_cad_train, ss_tot_cad) + + print(f"\n Ridge (alpha_cad={ridge_cad.alpha_:.1f}):") + print(f" In-sample R² cadence: {r2_ridge_cad:.3f}") + + # Ridge LOO-CV (cadence only) + loo = LeaveOneOut() + Y_ridge_cad_loo = np.zeros_like(Y_cad) + for train_idx, test_idx in loo.split(X_attr): + rc = RidgeCV(alphas=alphas, cv=None).fit(X_attr[train_idx], Y_cad[train_idx]) + Y_ridge_cad_loo[test_idx] = rc.predict(X_attr[test_idx]) + + r2_ridge_loo_cad = compute_r2(Y_cad, Y_ridge_cad_loo, ss_tot_cad) + print(f" LOO-CV R² cadence: {r2_ridge_loo_cad:.3f}") + print(f" LOO-CV MAE cadence: {np.mean(np.abs(np.exp(Y_cad) - np.exp(Y_ridge_cad_loo))):.1f} min") + + # Ridge coefficients + print(f" Coefficients (cadence):") + print(f" {'intercept':<20} {ridge_cad.intercept_:>7.3f}") + for j, name in enumerate(attr_names): + print(f" {name:<20} {ridge_cad.coef_[j]:>7.3f}") + + # ---- Random Forest (cadence only) ---- + rf = RandomForestRegressor( + n_estimators=200, + max_depth=None, + min_samples_leaf=3, + max_features=min(4, k_attr), + random_state=42, + n_jobs=-1, + ) + rf.fit(X_attr, Y_cad) + Y_rf_cad_train = rf.predict(X_attr) + + r2_rf_cad = compute_r2(Y_cad, Y_rf_cad_train, ss_tot_cad) + + print(f"\n Random Forest (min_leaf=3, max_feat=4):") + print(f" In-sample R² cadence: {r2_rf_cad:.3f}") + + # RF LOO-CV + Y_rf_cad_loo = np.zeros_like(Y_cad) + for train_idx, test_idx in loo.split(X_attr): + rf_loo = RandomForestRegressor( + n_estimators=200, max_depth=None, min_samples_leaf=3, + max_features=min(4, k_attr), random_state=42, n_jobs=-1, + ) + rf_loo.fit(X_attr[train_idx], Y_cad[train_idx]) + Y_rf_cad_loo[test_idx] = rf_loo.predict(X_attr[test_idx]) + + r2_rf_loo_cad = compute_r2(Y_cad, Y_rf_cad_loo, ss_tot_cad) + print(f" LOO-CV R² cadence: {r2_rf_loo_cad:.3f}") + print(f" LOO-CV MAE cadence: {np.mean(np.abs(np.exp(Y_cad) - np.exp(Y_rf_cad_loo))):.1f} min") + + print(f"\n Feature importances:") + for j, name in enumerate(attr_names): + print(f" {name:<20} {rf.feature_importances_[j]:.3f}") + + # ---- Pick best LOO model ---- + best = "ridge" if r2_ridge_loo_cad >= r2_rf_loo_cad else "rf" + print(f"\n Best LOO model: {best} (ridge={r2_ridge_loo_cad:.3f} vs rf={r2_rf_loo_cad:.3f})") + + if best == "ridge": + Y_best_cad_train = Y_ridge_cad_train + Y_best_cad_loo = Y_ridge_cad_loo + r2_best_cad = r2_ridge_cad + r2_best_loo_cad = r2_ridge_loo_cad + else: + Y_best_cad_train = Y_rf_cad_train + Y_best_cad_loo = Y_rf_cad_loo + r2_best_cad = r2_rf_cad + r2_best_loo_cad = r2_rf_loo_cad + + # Build result dict — gas comes from Option C (which used chain-level values) + noise_all = np.array([good_pools[p]["noise_coeffs"] for p in pool_ids]) + + result = { + "pool_ids": pool_ids, + "attr_names": attr_names, + "X_attr": X_attr, + "best_model": best, + "predictions": {}, + "loo_predictions": {}, + "noise_coeffs": noise_all, + "r2_train_cad": r2_best_cad, + "r2_loo_cad": r2_best_loo_cad, + } + + for i, pid in enumerate(pool_ids): + log_gas_fixed = good_pools[pid]["log_gas"] + result["predictions"][pid] = { + "log_cadence": float(Y_best_cad_train[i]), + "log_gas": float(log_gas_fixed), + } + result["loo_predictions"][pid] = { + "log_cadence": float(Y_best_cad_loo[i]), + "log_gas": float(log_gas_fixed), + } + + return result + + +def compute_per_pool_predictions(matched, option_c_results, joint_result, rf_result=None): + """Compute per-observation V_arb, V_noise, V_total for each pool. + + Pools not in the joint result (dropped as pathological) get NaN for + Option A predictions. Same for RF. + """ + from quantammsim.calibration.pool_data import build_x_obs, build_pool_attributes + from quantammsim.calibration.grid_interpolation import interpolate_pool_daily + from quantammsim.calibration.loss import K_OBS + import jax.numpy as jnp + + pool_ids = sorted(matched.keys()) + + # Build attributes for the joint-fitted pool subset + joint_pool_ids = joint_result["pool_ids"] + joint_matched = {p: matched[p] for p in joint_pool_ids if p in matched} + X_attr_joint_full, attr_names_full, _ = build_pool_attributes(joint_matched) + # Filter to the features actually used by the joint model + joint_attr_names = joint_result["attr_names"] + joint_feat_idx = [attr_names_full.index(n) for n in joint_attr_names + if n in attr_names_full] + X_attr_joint = X_attr_joint_full[:, joint_feat_idx] + joint_pid_to_idx = {p: i for i, p in enumerate(joint_pool_ids)} + + # RF predictions lookup + rf_pool_ids = rf_result["pool_ids"] if rf_result else [] + rf_pid_to_idx = {p: i for i, p in enumerate(rf_pool_ids)} + + predictions = {} + for pid in pool_ids: + entry = matched[pid] + panel = entry["panel"] + coeffs = entry["coeffs"] + day_indices = entry["day_indices"] + + x_obs = build_x_obs(panel) + y_obs = panel["log_volume"].values.astype(float) + + def r2(v_arb, v_noise, y): + log_pred = np.log(np.maximum(v_arb + v_noise, 1e-6)) + ss_res = np.sum((log_pred - y) ** 2) + ss_tot = np.sum((y - y.mean()) ** 2) + return 1 - ss_res / max(ss_tot, 1e-10) + + # --- Option C predictions --- + r = option_c_results[pid] + log_cad_c = r["log_cadence"] + log_gas_c = r["log_gas"] + noise_c_c = r["noise_coeffs"] + + v_arb_all_c = np.array(interpolate_pool_daily( + coeffs, jnp.float64(log_cad_c), jnp.float64(np.exp(log_gas_c)), + )) + v_arb_c = v_arb_all_c[day_indices] + v_noise_c = np.exp(x_obs @ noise_c_c) + + # --- Option A (per_pool_noise) predictions --- + if pid in joint_pid_to_idx: + ji = joint_pid_to_idx[pid] + x_attr = X_attr_joint[ji] + log_cad_a = float(joint_result["bias_cad"]) + float(x_attr @ joint_result["W_cad"]) + if joint_result.get("fix_gas"): + gas_a = float(joint_result["gas_per_pool"][ji]) + log_gas_a = np.log(max(gas_a, 1e-6)) + else: + log_gas_a = float(joint_result["bias_gas"]) + float(x_attr @ joint_result["W_gas"]) + gas_a = np.exp(log_gas_a) + noise_c_a = joint_result["noise_coeffs"][ji] + + v_arb_all_a = np.array(interpolate_pool_daily( + coeffs, jnp.float64(log_cad_a), jnp.float64(np.exp(log_gas_a)), + )) + v_arb_a = v_arb_all_a[day_indices] + v_noise_a = np.exp(x_obs @ noise_c_a) + r2_a = r2(v_arb_a, v_noise_a, y_obs) + cad_a = np.exp(log_cad_a) + else: + v_arb_a = np.full(len(y_obs), np.nan) + v_noise_a = np.full(len(y_obs), np.nan) + r2_a = np.nan + cad_a = np.nan + gas_a = np.nan + + # --- Option RF predictions --- + if rf_result and pid in rf_pid_to_idx: + ri = rf_pid_to_idx[pid] + rf_pred = rf_result["predictions"][pid] + log_cad_rf = rf_pred["log_cadence"] + log_gas_rf = rf_pred["log_gas"] + noise_c_rf = rf_result["noise_coeffs"][ri] # from Option C + + v_arb_all_rf = np.array(interpolate_pool_daily( + coeffs, jnp.float64(log_cad_rf), jnp.float64(np.exp(log_gas_rf)), + )) + v_arb_rf = v_arb_all_rf[day_indices] + v_noise_rf = np.exp(x_obs @ noise_c_rf) + r2_rf = r2(v_arb_rf, v_noise_rf, y_obs) + cad_rf = np.exp(log_cad_rf) + gas_rf = np.exp(log_gas_rf) + + # LOO predictions (out-of-sample) + loo_pred = rf_result["loo_predictions"][pid] + log_cad_loo = loo_pred["log_cadence"] + log_gas_loo = loo_pred["log_gas"] + v_arb_all_loo = np.array(interpolate_pool_daily( + coeffs, jnp.float64(log_cad_loo), jnp.float64(np.exp(log_gas_loo)), + )) + v_arb_loo = v_arb_all_loo[day_indices] + v_noise_loo = np.exp(x_obs @ noise_c_rf) # same noise coeffs + r2_loo = r2(v_arb_loo, v_noise_loo, y_obs) + cad_loo = np.exp(log_cad_loo) + gas_loo = np.exp(log_gas_loo) + else: + v_arb_rf = np.full(len(y_obs), np.nan) + v_noise_rf = np.full(len(y_obs), np.nan) + r2_rf = np.nan + cad_rf = np.nan + gas_rf = np.nan + r2_loo = np.nan + cad_loo = np.nan + gas_loo = np.nan + + predictions[pid] = { + "dates": pd.to_datetime(panel["date"].values), + "y_obs": y_obs, + "actual_vol": np.exp(y_obs), + # Option C + "v_arb_c": v_arb_c, + "v_noise_c": v_noise_c, + "r2_c": r2(v_arb_c, v_noise_c, y_obs), + "cadence_c": np.exp(log_cad_c), + "gas_c": np.exp(log_gas_c), + "converged_c": r["converged"], + # Option A + "v_arb_a": v_arb_a, + "v_noise_a": v_noise_a, + "r2_a": r2_a, + "cadence_a": cad_a, + "gas_a": gas_a, + # Option RF (in-sample) + "v_arb_rf": v_arb_rf, + "v_noise_rf": v_noise_rf, + "r2_rf": r2_rf, + "cadence_rf": cad_rf, + "gas_rf": gas_rf, + # Option RF LOO (out-of-sample) + "r2_rf_loo": r2_loo, + "cadence_rf_loo": cad_loo, + "gas_rf_loo": gas_loo, + # Metadata + "chain": entry["chain"], + "tokens": entry["tokens"], + "fee": entry["fee"], + "median_tvl": float(np.exp(panel["log_tvl_lag1"].median())), + "n_obs": len(y_obs), + } + + return predictions + + +def plot_top50_pages(predictions, method="c"): + """Paginated plots: V_arb + V_noise decomposition.""" + # Rank by median TVL + ranked = sorted( + predictions.items(), + key=lambda x: -x[1]["median_tvl"], + )[:TOP_N] + + suffix = {"c": "option_c", "a": "option_a", "rf": "option_rf"}[method] + per_page = 10 + n_pages = (len(ranked) + per_page - 1) // per_page + + for page in range(n_pages): + start = page * per_page + end = min(start + per_page, len(ranked)) + page_pools = ranked[start:end] + n_this = len(page_pools) + + ncols = 2 + nrows = (n_this + ncols - 1) // ncols + fig, axes = plt.subplots(nrows, ncols, figsize=(16, 4.5 * nrows)) + if nrows == 1 and ncols == 1: + axes = np.array([[axes]]) + elif nrows == 1: + axes = axes.reshape(1, -1) + elif ncols == 1: + axes = axes.reshape(-1, 1) + + for idx, (pid, p) in enumerate(page_pools): + ax = axes[idx // ncols][idx % ncols] + dates = p["dates"] + + if method == "c": + v_arb = p["v_arb_c"] + v_noise = p["v_noise_c"] + r2_val = p["r2_c"] + cad = p["cadence_c"] + gas = p["gas_c"] + elif method == "rf": + v_arb = p["v_arb_rf"] + v_noise = p["v_noise_rf"] + r2_val = p["r2_rf"] + cad = p["cadence_rf"] + gas = p["gas_rf"] + else: + v_arb = p["v_arb_a"] + v_noise = p["v_noise_a"] + r2_val = p["r2_a"] + cad = p["cadence_a"] + gas = p["gas_a"] + + # Skip pools with NaN predictions (dropped from this method) + if np.any(np.isnan(v_arb)): + ax.text(0.5, 0.5, f"Dropped from {method.upper()}", fontsize=12, + ha="center", va="center", transform=ax.transAxes, color="gray") + ax.set_title(f"{pid[:16]} — dropped", fontsize=8) + continue + + v_total = v_arb + v_noise + arb_frac = np.median(v_arb / np.maximum(v_total, 1.0)) + actual = p["actual_vol"] + + # Stacked area: V_arb bottom, V_noise on top + ax.fill_between(dates, 0, np.maximum(v_arb, 0), + alpha=0.3, color="orangered", label="V_arb (grid)") + ax.fill_between(dates, np.maximum(v_arb, 0), np.maximum(v_total, 0), + alpha=0.3, color="steelblue", label="V_noise (covariates)") + ax.plot(dates, actual, "k-", linewidth=0.8, alpha=0.7, label="Actual") + ax.plot(dates, np.maximum(v_total, 0), "--", color="purple", + linewidth=0.8, alpha=0.7, label="Predicted total") + + ax.set_yscale("log") + ax.set_ylabel("Daily volume (USD)", fontsize=8) + + tokens = p["tokens"] + if isinstance(tokens, (list, tuple)): + tok_str = "/".join(str(t)[:8] for t in tokens[:2]) + elif isinstance(tokens, str): + tok_str = "/".join(t.strip()[:8] for t in tokens.split(",")[:2]) + else: + tok_str = pid[:16] + + ax.set_title( + f"{tok_str} ({p['chain']})\n" + f"TVL ${p['median_tvl']:,.0f} | R²={r2_val:.3f} " + f"cad={cad:.1f}min gas=${gas:.2f} " + f"arb_frac={arb_frac:.1%} n={p['n_obs']}", + fontsize=8, + ) + ax.legend(fontsize=6, loc="upper right") + ax.tick_params(labelsize=7) + ax.tick_params(axis="x", rotation=30) + + for idx in range(n_this, nrows * ncols): + axes[idx // ncols][idx % ncols].set_visible(False) + + method_label = {"c": "Option C (per-pool)", "a": "Option A (linear)", + "rf": "Option RF (random forest)"}[method] + fig.suptitle( + f"Direct calibration: V_arb + V_noise — {method_label}\n" + f"page {page + 1}/{n_pages} " + f"(top {min(TOP_N, len(ranked))} by median TVL, {TRAIN_DAYS}d window)", + fontsize=11, + ) + fig.tight_layout() + out = os.path.join(OUTPUT_DIR, f"{suffix}_page{page + 1}.png") + fig.savefig(out, dpi=150, bbox_inches="tight") + plt.close(fig) + print(f" Saved: {out}") + + +def plot_summary(predictions, option_c_results, joint_result): + """Summary: distributions of cadence, gas, R², arb fraction for both methods.""" + pool_ids = sorted(predictions.keys()) + n = len(pool_ids) + + fig, axes = plt.subplots(2, 4, figsize=(20, 10)) + + for row, (method, label) in enumerate([("c", "Option C (per-pool)"), + ("a", "Option A (joint)")]): + cads = [predictions[p][f"cadence_{method}"] for p in pool_ids] + gases = [predictions[p][f"gas_{method}"] for p in pool_ids] + r2s = [predictions[p][f"r2_{method}"] for p in pool_ids] + arb_fracs = [] + for p in pool_ids: + v_arb = predictions[p][f"v_arb_{method}"] + v_noise = predictions[p][f"v_noise_{method}"] + total = v_arb + v_noise + arb_fracs.append(np.median(v_arb / np.maximum(total, 1.0))) + + # Cadence + ax = axes[row, 0] + ax.hist(cads, bins=20, color="orangered", alpha=0.7, edgecolor="white") + ax.axvline(np.median(cads), color="black", linestyle="--", + label=f"Median={np.median(cads):.1f}min") + ax.set_xlabel("Cadence (minutes)") + ax.set_title(f"{label}: Cadence") + ax.legend(fontsize=8) + + # Gas + ax = axes[row, 1] + ax.hist(gases, bins=20, color="goldenrod", alpha=0.7, edgecolor="white") + ax.axvline(np.median(gases), color="black", linestyle="--", + label=f"Median=${np.median(gases):.2f}") + ax.set_xlabel("Gas (USD)") + ax.set_title(f"{label}: Gas cost") + ax.legend(fontsize=8) + + # R² + ax = axes[row, 2] + r2arr = np.array(r2s) + ax.hist(r2arr[np.isfinite(r2arr)], bins=20, color="green", alpha=0.7, + edgecolor="white") + ax.axvline(np.nanmedian(r2arr), color="black", linestyle="--", + label=f"Median={np.nanmedian(r2arr):.3f}") + ax.set_xlabel("R²") + ax.set_title(f"{label}: R²") + ax.legend(fontsize=8) + + # Arb fraction + ax = axes[row, 3] + ax.hist(arb_fracs, bins=20, color="steelblue", alpha=0.7, edgecolor="white") + ax.axvline(np.median(arb_fracs), color="black", linestyle="--", + label=f"Median={np.median(arb_fracs):.2f}") + ax.set_xlabel("Arb fraction") + ax.set_title(f"{label}: Arb fraction") + ax.legend(fontsize=8) + + fig.tight_layout() + out = os.path.join(OUTPUT_DIR, "summary_distributions.png") + fig.savefig(out, dpi=150, bbox_inches="tight") + plt.close(fig) + print(f" Saved: {out}") + + +def plot_c_vs_a_scatter(predictions): + """Scatter: Option C vs Option A parameters.""" + pool_ids = sorted(predictions.keys()) + fig, axes = plt.subplots(1, 3, figsize=(15, 5)) + + for ax, metric, label in [ + (axes[0], "cadence", "Cadence (min)"), + (axes[1], "gas", "Gas (USD)"), + (axes[2], "r2", "R²"), + ]: + c_vals = [predictions[p][f"{metric}_c"] for p in pool_ids] + a_vals = [predictions[p][f"{metric}_a"] for p in pool_ids] + ax.scatter(c_vals, a_vals, alpha=0.7, s=30, edgecolors="k", linewidth=0.5) + lo = min(min(c_vals), min(a_vals)) + hi = max(max(c_vals), max(a_vals)) + margin = (hi - lo) * 0.05 + ax.plot([lo - margin, hi + margin], [lo - margin, hi + margin], + "k--", alpha=0.3, linewidth=1) + ax.set_xlabel(f"Option C: {label}") + ax.set_ylabel(f"Option A: {label}") + ax.set_title(f"{label}: C vs A") + + fig.tight_layout() + out = os.path.join(OUTPUT_DIR, "c_vs_a_scatter.png") + fig.savefig(out, dpi=150, bbox_inches="tight") + plt.close(fig) + print(f" Saved: {out}") + + +def plot_cadence_gas_by_chain(predictions): + """Scatter: cadence vs gas, colored by chain.""" + pool_ids = sorted(predictions.keys()) + chains = [predictions[p]["chain"] for p in pool_ids] + unique_chains = sorted(set(chains)) + colors = plt.cm.tab10(np.linspace(0, 1, max(len(unique_chains), 1))) + chain_color = {c: colors[i] for i, c in enumerate(unique_chains)} + + fig, axes = plt.subplots(1, 2, figsize=(14, 6)) + for ax, method, label in [(axes[0], "c", "Option C"), (axes[1], "a", "Option A")]: + for c in unique_chains: + mask = [i for i, p in enumerate(pool_ids) if predictions[p]["chain"] == c] + cads = [predictions[pool_ids[i]][f"cadence_{method}"] for i in mask] + gases = [predictions[pool_ids[i]][f"gas_{method}"] for i in mask] + ax.scatter(cads, gases, label=c, color=chain_color[c], + alpha=0.7, s=50, edgecolors="k", linewidth=0.5) + ax.set_xlabel("Cadence (minutes)") + ax.set_ylabel("Gas cost (USD)") + ax.set_title(f"{label}: Cadence vs Gas by chain") + ax.legend(fontsize=8) + ax.set_xscale("log") + ax.set_yscale("log") + + fig.tight_layout() + out = os.path.join(OUTPUT_DIR, "cadence_gas_by_chain.png") + fig.savefig(out, dpi=150, bbox_inches="tight") + plt.close(fig) + print(f" Saved: {out}") + + +def save_results_json(predictions, option_c_results, joint_ppn, joint_sn): + """Save fitted params as JSON for later use.""" + out = { + "option_c": {}, + "option_a_ppn": { + "bias_cad": joint_ppn["bias_cad"], + "bias_gas": joint_ppn["bias_gas"], + "W_cad": joint_ppn["W_cad"].tolist(), + "W_gas": joint_ppn["W_gas"].tolist(), + "noise_coeffs": joint_ppn["noise_coeffs"].tolist(), + "loss": joint_ppn["loss"], + "init_loss": joint_ppn["init_loss"], + "converged": bool(joint_ppn["converged"]), + "attr_names": joint_ppn["attr_names"], + "pool_ids": joint_ppn["pool_ids"], + }, + "option_a_shared": { + "bias_cad": joint_sn["bias_cad"], + "bias_gas": joint_sn["bias_gas"], + "W_cad": joint_sn["W_cad"].tolist(), + "W_gas": joint_sn["W_gas"].tolist(), + "bias_noise": joint_sn["bias_noise"].tolist(), + "W_noise": joint_sn["W_noise"].tolist(), + "loss": joint_sn["loss"], + "init_loss": joint_sn["init_loss"], + "converged": bool(joint_sn["converged"]), + "attr_names": joint_sn["attr_names"], + "pool_ids": joint_sn["pool_ids"], + }, + } + for pid, r in option_c_results.items(): + out["option_c"][pid] = { + "log_cadence": r["log_cadence"], + "log_gas": r["log_gas"], + "noise_coeffs": r["noise_coeffs"].tolist(), + "loss": r["loss"], + "converged": bool(r["converged"]), + "cadence_minutes": r["cadence_minutes"], + "gas_usd": r["gas_usd"], + "chain": r["chain"], + "fee": r["fee"], + "tokens": r["tokens"], + } + + path = os.path.join(OUTPUT_DIR, "direct_calibration_results.json") + with open(path, "w") as f: + json.dump(out, f, indent=2) + print(f" Saved: {path}") + + +def print_pool_table(predictions, option_c_results): + """Print a summary table of per-pool results.""" + ranked = sorted( + predictions.items(), + key=lambda x: -x[1]["median_tvl"], + ) + + has_rf = any(not np.isnan(p["cadence_rf"]) for _, p in ranked) + + print(f"\n{'='*150}") + header = (f"{'Pool':<24} {'Chain':<10} {'TVL':>12} {'N':>4} " + f"{'Cad_C':>6} {'Gas_C':>7} {'R2_C':>6} " + f"{'Cad_A':>6} {'Gas_A':>7} {'R2_A':>6}") + if has_rf: + header += f" {'Cad_RF':>6} {'Gas_RF':>7} {'R2_RF':>6} {'R2_LOO':>6}" + header += f" {'Arb%_C':>6}" + print(header) + print(f"{'-'*150}") + for pid, p in ranked: + tokens = p["tokens"] + if isinstance(tokens, str): + tok_str = "/".join(t.strip()[:6] for t in tokens.split(",")[:2]) + else: + tok_str = pid[:16] + arb_total_c = p["v_arb_c"] + p["v_noise_c"] + arb_frac = np.median(p["v_arb_c"] / np.maximum(arb_total_c, 1.0)) + if np.isnan(p["cadence_a"]): + a_str = " --- dropped --- " + else: + a_str = f"{p['cadence_a']:>5.1f}m ${p['gas_a']:>5.2f} {p['r2_a']:>6.3f}" + line = (f"{tok_str:<24} {p['chain']:<10} ${p['median_tvl']:>10,.0f} {p['n_obs']:>4} " + f"{p['cadence_c']:>5.1f}m ${p['gas_c']:>5.2f} {p['r2_c']:>6.3f} " + f"{a_str}") + if has_rf: + if np.isnan(p["cadence_rf"]): + line += " --- dropped --- " + else: + line += (f" {p['cadence_rf']:>5.1f}m ${p['gas_rf']:>5.2f} " + f"{p['r2_rf']:>6.3f} {p['r2_rf_loo']:>6.3f}") + line += f" {arb_frac:>5.1%}" + print(line) + + +def main(): + os.environ.setdefault("JAX_PLATFORMS", "cpu") + + print("=" * 70) + print("Direct Calibration Pipeline: Training + Top 50 Plots") + print("=" * 70) + + panel, matched = load_and_match() + + # Step 1: Option C (gas fixed to chain-level costs) + option_c = run_option_c(matched, fix_gas_to_chain=True) + + # Step 2: Option A (linear mapping, gas fixed) + joint_ppn, joint_sn = run_option_a(matched, option_c, fix_gas_to_chain=True) + + # Step 3: Option RF (random forest 2-stage) + rf_result = run_option_rf(matched, option_c) + + # Step 4: Compute predictions + print("\nComputing per-pool predictions...") + predictions = compute_per_pool_predictions(matched, option_c, joint_ppn, rf_result) + + # Step 5: Print table + print_pool_table(predictions, option_c) + + # Print RF vs A comparison summary + rf_pools = [p for p in predictions if not np.isnan(predictions[p]["r2_rf"])] + if rf_pools: + r2_c = [predictions[p]["r2_c"] for p in rf_pools] + r2_a = [predictions[p]["r2_a"] for p in rf_pools + if not np.isnan(predictions[p]["r2_a"])] + r2_rf = [predictions[p]["r2_rf"] for p in rf_pools] + r2_loo = [predictions[p]["r2_rf_loo"] for p in rf_pools] + print(f"\n--- R² comparison (non-dropped pools) ---") + print(f" Option C: median={np.median(r2_c):.4f} mean={np.mean(r2_c):.4f}") + if r2_a: + print(f" Option A (linear): median={np.median(r2_a):.4f} mean={np.mean(r2_a):.4f}") + print(f" Option RF (train): median={np.median(r2_rf):.4f} mean={np.mean(r2_rf):.4f}") + print(f" Option RF (LOO): median={np.median(r2_loo):.4f} mean={np.mean(r2_loo):.4f}") + + # Step 6: Plots + print("\nGenerating plots...") + os.makedirs(OUTPUT_DIR, exist_ok=True) + plot_top50_pages(predictions, method="c") + plot_top50_pages(predictions, method="a") + plot_top50_pages(predictions, method="rf") + plot_summary(predictions, option_c, joint_ppn) + plot_c_vs_a_scatter(predictions) + plot_cadence_gas_by_chain(predictions) + + # Step 7: Save results + save_results_json(predictions, option_c, joint_ppn, joint_sn) + + print(f"\n{'='*70}") + print(f"Done. Output in: {OUTPUT_DIR}") + + +if __name__ == "__main__": + main() diff --git a/scripts/run_final_sims.py b/scripts/run_final_sims.py new file mode 100644 index 0000000..31162f3 --- /dev/null +++ b/scripts/run_final_sims.py @@ -0,0 +1,480 @@ +#!/usr/bin/env python3 +"""Run final forward simulations for best sweep params at multiple TVLs. + +For each token pair, loads the best params from the sweep, runs train and +test period forward passes at each TVL level, and generates comparison plots. + +Usage: + python scripts/run_final_sims.py --pair aave + python scripts/run_final_sims.py --pair cow + python scripts/run_final_sims.py --all + python scripts/run_final_sims.py --all --method cma_es +""" + +import argparse +import json +import os +import pickle +from collections import defaultdict +from pathlib import Path + +import jax +import jax.numpy as jnp +import matplotlib +matplotlib.use("Agg") +import matplotlib.pyplot as plt +import numpy as np +import pandas as pd +from datetime import datetime + +from quantammsim.runners.jax_runners import do_run_on_historic_data +from scripts.evaluate_trials import load_reclamm_results +from scripts.plot_reclamm_optuna_result import ( + plot_results as plot_optuna_results, + plot_test_only, + plot_weights as plot_optuna_weights, +) + +BG = "#162536" +TC = "#E6CE97" + +# Train/test split around the Oct 10 flash crash. Pairs whose price data +# starts later (e.g. CoinGecko free tier = last 365 days) can override these +# with per-pair "train"/"test" (start, end) tuples in PAIR_CONFIGS. +TRAIN_START = "2025-01-01 00:00:00" +TRAIN_END = "2025-10-05 00:00:00" +TEST_START = "2025-10-25 00:00:00" +TEST_END = "2026-05-01 00:00:00" + +PAIR_CONFIGS = { + "aave": { + "tokens": ["AAVE", "ETH"], + "pool_id": "0x9d1fcf346ea1b0", + "gas_cost": 1.0, + "fees": 0.0025, + "tvls": { + "1m": 1_000_000, + "5m": 5_000_000, + "20m": 20_000_000, + }, + }, + "cow": { + "tokens": ["COW", "ETH"], + "pool_id": "0xd321300ef77067", + "gas_cost": 3.0, + "fees": 0.003, + "tvls": { + "500k": 500_000, + "2m": 2_000_000, + "20m": 20_000_000, + }, + }, + "btceth": { + # WBTC/WETH pool from the MM artifact (results/mm_noise/meta.json) + "tokens": ["BTC", "ETH"], + "pool_id": "0xa6f548df93de92", + "gas_cost": 1.0, + "fees": 0.0025, + "tvls": { + "5m": 5_000_000, + }, + }, + "boldusdc": { + # Not in the MM artifact — noise model uses the median-pool fallback. + # BOLD price data (CoinGecko) only starts 2025-07-09, hence the + # shortened train window. + "tokens": ["BOLD", "USDC"], + "pool_id": "boldusdc", + "gas_cost": 1.0, + "fees": 0.0005, + "tvls": { + "1m": 1_000_000, + }, + "train": ("2025-07-15 00:00:00", "2025-10-05 00:00:00"), + }, +} + + +def select_best_params(trials, tokens_set, tvl, metric_key="returns_over_hodl"): + """From loaded trials, pick the best for a given token pair and TVL. + + Ranks by val (OOS) returns_over_hodl regardless of what objective the + trial was trained on — this is the consistent comparison metric. + """ + matching = [ + t for t in trials + if tuple(sorted(t["tokens"])) == tokens_set + and abs(t["initial_pool_value"] - tvl) < 1.0 + ] + if not matching: + return None + + def _get_val_roh(t): + """Extract OOS returns_over_hodl from test_objective or continuous_test_metrics.""" + # Try continuous_test_metrics first (more reliable) + ct = t.get("continuous_test_metrics", {}) + if isinstance(ct, dict) and "returns_over_hodl" in ct: + return float(ct["returns_over_hodl"]) + # Fall back to test_objective + to = t.get("test_objective", {}) + if isinstance(to, dict) and "returns_over_hodl" in to: + return float(to["returns_over_hodl"]) + if isinstance(to, list): + for entry in to: + if isinstance(entry, dict) and "returns_over_hodl" in entry: + return float(entry["returns_over_hodl"]) + # If the trial's own objective matches the requested metric, use test_value + if t.get("return_val") == metric_key: + return float(t.get("test_value", float("-inf"))) + return float("-inf") + + matching.sort(key=_get_val_roh, reverse=True) + best = matching[0] + best_roh = _get_val_roh(best) + print(f" Selection: {len(matching)} candidates, best val_roh={best_roh:+.4f} " + f"(trained on {best['return_val']})") + return best + + +def build_fingerprint(pair_cfg, tvl, start, end, noise_path=None): + """Build a run_fingerprint for a forward pass.""" + fp = { + "rule": "reclamm", + "tokens": pair_cfg["tokens"], + "startDateString": start, + "endDateString": end, + "initial_pool_value": float(tvl), + "do_arb": True, + "arb_frequency": 4, + "fees": pair_cfg["fees"], + "gas_cost": pair_cfg["gas_cost"], + "arb_fees": 0.0, + "protocol_fee_split": 0.25, + "noise_trader_ratio": 0.0, + "noise_model": "mm_observed", + "noise_arrays_path": noise_path, + } + return fp + + +def build_noise_arrays(pair_cfg, start, end): + """Build or load cached noise arrays for the period.""" + from quantammsim.calibration.noise_model_arrays import build_mm_simulator_arrays + tok_a, tok_b = pair_cfg["tokens"] + pool_id = pair_cfg["pool_id"] + tag = f"{pool_id}_{start.split()[0]}_{end.split()[0]}_mm" + cache_path = f"results/mm_noise/_sim_arrays/{tag}.npz" + + if not os.path.exists(cache_path): + print(f" Building noise arrays: {tok_a}/{tok_b} {start} → {end}") + arrays = build_mm_simulator_arrays( + token_a=tok_a, token_b=tok_b, + start_date=start.split()[0], end_date=end.split()[0], + mm_artifact_dir="results/mm_noise", + competitor_tvl_path="results/competitor_tvl/competitor_tvl.npz", + pool_id=pool_id, + ) + os.makedirs(os.path.dirname(cache_path), exist_ok=True) + np.savez(cache_path, + noise_base=arrays["noise_base"], + competitor_tvl=arrays["competitor_tvl"]) + return cache_path + + +def run_forward(pair_cfg, tvl, params, start, end, noise_path): + """Run a single forward pass and return results dict.""" + fp = build_fingerprint(pair_cfg, tvl, start, end, noise_path) + result = do_run_on_historic_data( + run_fingerprint=fp, params=params, verbose=False, + ) + return result + + +def _trial_hash(best): + """Extract the originating run hash from a selected trial.""" + study_id = str(best.get("study_id", "unknown")) + return study_id.removeprefix("run_") + + +def _unix_values_for_result(result): + """Return the unix timestamp vector aligned to result['value'].""" + value_len = len(np.asarray(result["value"])) + if "unix_values" in result: + unix_values = np.asarray(result["unix_values"]) + else: + data_dict = result.get("data_dict") + if data_dict is None or "unix_values" not in data_dict: + raise KeyError("Forward result has no unix_values or data_dict['unix_values']") + start_idx = int(data_dict.get("start_idx", 0)) + unix_values = np.asarray(data_dict["unix_values"])[start_idx:start_idx + value_len] + + if len(unix_values) != value_len: + raise ValueError( + f"Timestamp/value length mismatch: unix={len(unix_values)} value={value_len}" + ) + return unix_values.astype(np.int64) + + +def export_forward_csvs(result, run_fingerprint, output_dir, identifier, source_hash): + """Write value, reserves, and per-token value CSVs for one forward pass.""" + value = np.asarray(result["value"], dtype=np.float64) + reserves = np.asarray(result["reserves"], dtype=np.float64) + prices = np.asarray(result["prices"], dtype=np.float64) + unix_values = _unix_values_for_result(result) + + tokens = list(run_fingerprint["tokens"]) + if tokens != sorted(tokens): + raise ValueError( + "Final-sim CSV export assumes run_fingerprint['tokens'] is alphabetically " + f"ordered to match runner price/reserve arrays; got {tokens}" + ) + + if reserves.shape != prices.shape: + raise ValueError(f"Reserves/prices shape mismatch: {reserves.shape} vs {prices.shape}") + if reserves.shape[0] != value.shape[0]: + raise ValueError(f"Reserves/value length mismatch: {reserves.shape[0]} vs {value.shape[0]}") + + token_values = reserves * prices + value_from_tokens = token_values.sum(axis=1) + if not np.allclose(value_from_tokens, value, rtol=1e-8, atol=1e-6): + max_diff = float(np.max(np.abs(value_from_tokens - value))) + raise ValueError( + f"Token value sanity check failed for {identifier}: max_diff={max_diff:.6g}" + ) + + output_path = Path(output_dir) + output_path.mkdir(parents=True, exist_ok=True) + file_stem = f"{identifier}_{source_hash}" + + pd.DataFrame({"unix": unix_values, "value": value}).to_csv( + output_path / f"run_Value_{file_stem}.csv", index=False, + ) + + reserve_df = pd.DataFrame({"unix": unix_values}) + for idx, token in enumerate(tokens): + reserve_df[f"reserve_{token}"] = reserves[:, idx] + reserve_df.to_csv(output_path / f"run_Reserves_{file_stem}.csv", index=False) + + token_value_df = pd.DataFrame({"unix": unix_values}) + for idx, token in enumerate(tokens): + token_value_df[f"{token}_value"] = token_values[:, idx] + token_value_df.to_csv(output_path / f"run_TokenValues_{file_stem}.csv", index=False) + print(f" Saved CSVs: run_{{Value,Reserves,TokenValues}}_{file_stem}.csv") + + +def make_plot_data(all_results, pair_cfg, start, end): + """Convert our results into the format expected by plot_reclamm_optuna_result functions. + + All values are normalised to start at 1.0 for cross-TVL comparability. + Fee revenue is expressed as fraction of initial pool value. + Weights are computed from reserves × prices (effective weight of token 0). + + Returns (configs, time_series, hodl_values, ref_config). + """ + from collections import OrderedDict + configs = OrderedDict() + time_series = {} + + start_str = start.split()[0] + end_str = end.split()[0] + ref_config = { + "tokens": pair_cfg["tokens"], + "startDateString": start, + "endDateString": end, + } + + # Normalised HODL: use the first result's initial reserves + first_result = next(iter(all_results.values()))["result"] + prices = np.array(first_result["prices"]) + reserves_0 = np.array(first_result["reserves"][0]) + hodl_raw = np.sum(reserves_0 * prices, axis=1) + hodl_values = hodl_raw / hodl_raw[0] # normalise to 1.0 + + for tvl_label, data in all_results.items(): + result = data["result"] + val = np.array(result["value"]) + initial_val = val[0] + reserves = np.array(result["reserves"]) + res_prices = np.array(result["prices"]) + + # Compute effective weights from reserves × prices + token_values = reserves * res_prices # (T, 2) + total_value = token_values.sum(axis=1, keepdims=True) + weights = token_values / np.maximum(total_value, 1e-30) + + # Normalise fee revenue as fraction of initial TVL + fee_rev = np.array(result.get("fee_revenue", np.zeros(len(val)))) + fee_rev_normalised = fee_rev / initial_val + + p = data["params"] + pr = float(jnp.asarray(p.get("price_ratio", 0)).flatten()[0]) + margin = float(jnp.asarray(p.get("centeredness_margin", 0)).flatten()[0]) + shift = float(jnp.asarray(p.get("shift_exponent", 0)).flatten()[0]) + name = f"Auto-range ${tvl_label} (PR={pr:.2f}, m={margin:.2f}, s={shift:.3f})" + configs[name] = { + "tvl_label": tvl_label, + "params": p, + } + time_series[name] = { + "value": val / initial_val, # normalise to 1.0 + "fee_revenue": fee_rev_normalised, + "reserves": reserves, + "prices": res_prices, + "initial_tvl": initial_val, # for volume normalisation + "weights": weights, + } + + return configs, time_series, hodl_values, ref_config + + +def filter_trials_to_window(trials, train_start, train_end): + """Keep only trials swept over the given train window.""" + start_day = train_start.split(" ")[0] + end_day = train_end.split(" ")[0] + return [ + t for t in trials + if t.get("start_date", "").startswith(start_day) + and end_day in t.get("end_date", "") + ] + + +def run_pair(pair_name, pair_cfg, trials, output_dir, args=None): + """Run all TVL variants for a pair, for train and test periods.""" + tokens = pair_cfg["tokens"] + tokens_set = tuple(sorted(tokens)) + print(f"\n{'='*60}") + print(f" {'/'.join(tokens)} — {pair_name}") + print(f"{'='*60}") + + # Per-pair window overrides (defaults: the global flash-crash split) + train_start, train_end = pair_cfg.get("train", (TRAIN_START, TRAIN_END)) + test_start, test_end = pair_cfg.get("test", (TEST_START, TEST_END)) + trials = filter_trials_to_window(trials, train_start, train_end) + print(f" {len(trials)} trials swept over {train_start[:10]} → {train_end[:10]}") + + # Build noise arrays for both periods + train_noise = build_noise_arrays(pair_cfg, train_start, train_end) + test_noise = build_noise_arrays(pair_cfg, test_start, test_end) + + train_results = {} + test_results = {} + + for tvl_label, tvl in pair_cfg["tvls"].items(): + full_label = f"{pair_name}_{tvl_label}" + print(f"\n --- {full_label} (TVL=${tvl:,.0f}) ---") + + # Select best params for this TVL + metric = args.metric if args is not None else "returns_over_hodl" + best = select_best_params(trials, tokens_set, tvl, metric_key=metric) + if best is None: + print(f" No results found for {tokens_set} TVL={tvl}") + continue + + params = dict(best["params"]) + pr = float(jnp.asarray(params.get("price_ratio", 0)).flatten()[0]) + margin = float(jnp.asarray(params.get("centeredness_margin", 0)).flatten()[0]) + shift = float(jnp.asarray(params.get("shift_exponent", 0)).flatten()[0]) + print(f" Best: obj={best['return_val']} train={best['train_value']:+.4f} test={best['test_value']:+.4f}") + print(f" Params: PR={pr:.3f} margin={margin:.4f} shift={shift:.6f}") + print(f" Source: {best['study_id']}") + + # Ensure initial_weights_logits exists + if "initial_weights_logits" not in params: + params["initial_weights_logits"] = jnp.zeros(len(tokens)) + + # Train period forward pass + print(f" Running train period...") + train_result = run_forward(pair_cfg, tvl, params, train_start, train_end, train_noise) + source_hash = _trial_hash(best) + train_fp = build_fingerprint(pair_cfg, tvl, train_start, train_end, train_noise) + export_forward_csvs( + train_result, train_fp, output_dir, + identifier=f"{pair_name}_{tvl_label}_train", + source_hash=source_hash, + ) + train_results[tvl_label] = {"result": train_result, "params": params, "best": best} + + # Clear JIT caches between runs to manage memory + jax.clear_caches() + + # Test period forward pass + print(f" Running test period...") + test_result = run_forward(pair_cfg, tvl, params, test_start, test_end, test_noise) + test_fp = build_fingerprint(pair_cfg, tvl, test_start, test_end, test_noise) + export_forward_csvs( + test_result, test_fp, output_dir, + identifier=f"{pair_name}_{tvl_label}_test", + source_hash=source_hash, + ) + test_results[tvl_label] = {"result": test_result, "params": params, "best": best} + + jax.clear_caches() + + # Print summary + train_val = np.array(train_result["value"]) + test_val = np.array(test_result["value"]) + train_hodl = np.sum(np.array(train_result["reserves"][0]) * np.array(train_result["prices"]), axis=1) + test_hodl = np.sum(np.array(test_result["reserves"][0]) * np.array(test_result["prices"]), axis=1) + train_roh = train_val[-1] / train_hodl[-1] - 1 + test_roh = test_val[-1] / test_hodl[-1] - 1 + train_fee = float(np.array(train_result.get("fee_revenue", np.zeros(1))).sum()) + test_fee = float(np.array(test_result.get("fee_revenue", np.zeros(1))).sum()) + print(f" Train RoH: {train_roh:+.2%} Fee: ${train_fee:,.0f}") + print(f" Test RoH: {test_roh:+.2%} Fee: ${test_fee:,.0f}") + + # Generate plots using existing plotting functions + for period_name, results_dict, start, end in [ + ("train", train_results, train_start, train_end), + ("test", test_results, test_start, test_end), + ]: + if not results_dict: + continue + configs, ts, hodl, ref_cfg = make_plot_data(results_dict, pair_cfg, start, end) + # Create a simple args object for the plot functions + class PlotArgs: + output = os.path.join(output_dir, f"{pair_name}_{period_name}.png") + plot_args = PlotArgs() + plot_optuna_results(configs, ts, hodl, ref_cfg, plot_args) + plot_optuna_weights(configs, ts, ref_cfg, plot_args) + + # Save results for later use + cache_path = os.path.join(output_dir, f"{pair_name}_sim_results.pkl") + with open(cache_path, "wb") as f: + pickle.dump({"train": train_results, "test": test_results}, f) + print(f" Saved cache: {cache_path}") + + return train_results, test_results + + +def main(): + p = argparse.ArgumentParser(description=__doc__, + formatter_class=argparse.RawDescriptionHelpFormatter) + p.add_argument("--pair", choices=sorted(PAIR_CONFIGS), default=None) + p.add_argument("--all", action="store_true") + p.add_argument("--method", default="optuna", choices=["optuna", "cma_es"], + help="Which sweep method results to use") + p.add_argument("--output-dir", default="results/final_sims") + p.add_argument("--metric", default="returns_over_hodl", + help="Metric to rank trials by for param selection") + args = p.parse_args() + + os.makedirs(args.output_dir, exist_ok=True) + + pairs = list(PAIR_CONFIGS.keys()) if args.all else [args.pair] + if not args.all and not args.pair: + p.error("Specify --pair or --all") + + # Load all trials; each pair filters to its own train window in run_pair + print("Loading sweep results...") + trials = load_reclamm_results("./results/", metric_key=args.metric) + print(f" {len(trials)} reCLAMM trials loaded") + + for pair_name in pairs: + pair_cfg = PAIR_CONFIGS[pair_name] + run_pair(pair_name, pair_cfg, trials, args.output_dir, args=args) + + print("\nDone.") + + +if __name__ == "__main__": + main() diff --git a/scripts/run_full_sweep.sh b/scripts/run_full_sweep.sh new file mode 100755 index 0000000..ab4000b --- /dev/null +++ b/scripts/run_full_sweep.sh @@ -0,0 +1,143 @@ +#!/bin/bash +# Full reClAMM parameter sweep: AAVE/ETH + COW/ETH +# Train: 2025-01-01 → 2025-10-05 (pre flash crash) +# Test: 2025-10-25 → 2026-05-01 (post flash crash, separate run) +# +# Usage: bash scripts/run_full_sweep.sh [--method optuna|cma_es] +# Monitor: tail -5 /tmp/tune_full_*.log +# Results: results/full_sweep/ + +source ~/miniconda3/etc/profile.d/conda.sh && conda activate qsim_reclamm_public + +# Parse arguments +METHOD="optuna" +PAIR_FILTER="" # empty = all pairs +while [ $# -gt 0 ]; do + case $1 in + --method=*) METHOD="${1#*=}" ;; + --method) shift; METHOD="$1" ;; + --pair=*) PAIR_FILTER="${1#*=}" ;; + --pair) shift; PAIR_FILTER="$1" ;; + esac + shift +done + +TRIALS=300 +MAX_PARALLEL="${MAX_WORKERS:-8}" +if [ "$METHOD" = "cma_es" ] && [ "$MAX_PARALLEL" = "8" ]; then + MAX_PARALLEL=4 +fi + +OBJECTIVES=( + returns_over_hodl + fee_revenue_over_value + calmar + daily_log_sharpe_excess +) + +OVERFITTING_PENALTIES=("" "1.0" "5.0") + +# Train period: pre flash crash +TRAIN_START="2025-01-01 00:00:00" +TRAIN_END="2025-10-05 00:00:00" + +# token_a token_b pool_id gas_cost fees tvl_label initial_tvl +CONFIGS=( + "AAVE ETH 0x9d1fcf346ea1b0 1.0 0.0025 aave_1m 1000000" + "AAVE ETH 0x9d1fcf346ea1b0 1.0 0.0025 aave_5m 5000000" + "AAVE ETH 0x9d1fcf346ea1b0 1.0 0.0025 aave_20m 20000000" + "COW ETH 0xd321300ef77067 3.0 0.003 cow_500k 500000" + "COW ETH 0xd321300ef77067 3.0 0.003 cow_2m 2000000" + "COW ETH 0xd321300ef77067 3.0 0.003 cow_20m 20000000" + "BTC ETH 0xa6f548df93de92 1.0 0.0025 btceth_5m 5000000" +) + +OUTDIR="results/full_sweep" +mkdir -p "$OUTDIR" + +# Build COMMON command based on method +if [ "$METHOD" = "cma_es" ]; then + CMA_GENS="${CMA_GENERATIONS:-500}" + COMMON="python scripts/tune_reclamm_calibrated_noise.py --noise-model mm_observed --artifact-dir results/mm_noise --method cma_es --cma-generations $CMA_GENS" + METHOD_TAG="_cmaes" + echo "=== CMA-ES mode ($CMA_GENS generations) ===" +else + PR_MAX_FLAG="" + if [ -n "${PR_MAX:-}" ]; then + PR_MAX_FLAG="--pr-max $PR_MAX" + METHOD_TAG="_prmax${PR_MAX}" + else + METHOD_TAG="" + fi + COMMON="python scripts/tune_reclamm_calibrated_noise.py --noise-model mm_observed --artifact-dir results/mm_noise --n-trials $TRIALS $PR_MAX_FLAG" + echo "=== Optuna mode ($TRIALS trials${PR_MAX:+, PR max=$PR_MAX}) ===" +fi + +wait_for_slot() { + while [ "$(jobs -rp | wc -l)" -ge "$MAX_PARALLEL" ]; do + sleep 10 + done +} + +N=0 +SKIPPED=0 +for config_line in "${CONFIGS[@]}"; do + read -r tok_a tok_b pool_id gas_cost fees tvl_label initial_tvl <<< "$config_line" + + # Filter by pair if specified (e.g. --pair cow matches cow_500k, cow_2m, cow_20m) + if [ -n "$PAIR_FILTER" ] && [[ "$tvl_label" != ${PAIR_FILTER}* ]]; then + continue + fi + + for obj in "${OBJECTIVES[@]}"; do + for penalty in "${OVERFITTING_PENALTIES[@]}"; do + tag="${obj}" + penalty_flag="" + if [ -n "$penalty" ]; then + tag="${tag}_penalty${penalty}" + penalty_flag="--overfitting-penalty $penalty" + fi + tag="${tag}${METHOD_TAG}_${tvl_label}" + + logfile="/tmp/tune_full_${tag}.log" + outfile="${OUTDIR}/${tag}.json" + + # Skip if result already exists + if [ -f "$outfile" ]; then + echo "[$N] Skipping (exists): ${tag}" + N=$((N + 1)) + SKIPPED=$((SKIPPED + 1)) + continue + fi + + wait_for_slot + + echo "[$N] Launching: ${tag}" + $COMMON --tokens "$tok_a" "$tok_b" \ + --pool-id "$pool_id" \ + --gas-cost "$gas_cost" \ + --fees "$fees" \ + --initial-pool-value "$initial_tvl" \ + --objective "$obj" \ + --start-date "$TRAIN_START" \ + --end-date "$TRAIN_END" \ + $penalty_flag \ + --output "$outfile" \ + > "$logfile" 2>&1 & + + N=$((N + 1)) + done + done +done + +echo "" +echo "$N jobs total ($SKIPPED skipped, $((N - SKIPPED)) launched)" +echo "Max parallel: $MAX_PARALLEL" +echo "" +echo "Monitor: tail -5 /tmp/tune_full_*.log" +echo "Results: ls $OUTDIR/" +echo "Summary: grep -A3 'Best trial' /tmp/tune_full_*.log" +echo "" +echo "Waiting for all jobs to finish..." +wait +echo "Done." diff --git a/scripts/run_mlp_calibration.py b/scripts/run_mlp_calibration.py new file mode 100644 index 0000000..e9b377b --- /dev/null +++ b/scripts/run_mlp_calibration.py @@ -0,0 +1,1012 @@ +"""Run calibration with MLPNoiseHead and compare against linear baselines. + +Steps: + 1. Load panel, match to per-day grids + 2. Option C: per-pool L-BFGS-B fits (baseline, gas fixed to chain) + 3. Linear joint: SharedLinearNoiseHead baseline + 4. MLP noise joint: MLPNoiseHead (new) + 5. Full MLP joint: MLPHead cadence + MLPNoiseHead (new) + 6. Per-pool prediction, R², decomposition for each method + 7. Paginated plots, summary distributions, comparison scatter + 8. JSON export +""" + +import json +import os + +import matplotlib +matplotlib.use("Agg") +import matplotlib.pyplot as plt +import numpy as np +import pandas as pd + +# ---- Config ---- +PANEL_CACHE = os.path.join( + os.path.dirname(os.path.dirname(__file__)), + "local_data", "noise_calibration", "panel.parquet", +) +GRID_DIR = os.path.join( + os.path.dirname(os.path.dirname(__file__)), + "results", "pool_grids_v2", +) +OUTPUT_DIR = os.path.join( + os.path.dirname(os.path.dirname(__file__)), + "results", "mlp_calibration", +) +OPTION_C_LOSS_CUTOFF = 5.0 +OPTION_C_MAXITER = 500 +JOINT_MAXITER = 5000 +MLP_HIDDEN = 16 +TOP_N = 50 +# Best alpha settings from sweep (phase 2) +ALPHA_CAD = 0.001 +ALPHA_NOISE = 0.1 + + +# ---- Data loading ---- + + +def load_and_match(): + """Load panel, match to grids.""" + from quantammsim.calibration.pool_data import ( + match_grids_to_panel, + replace_panel_volatility_with_binance, + ) + + panel = pd.read_parquet(PANEL_CACHE) + + if "log_tvl_lag1" not in panel.columns: + panel = panel.sort_values(["pool_id", "date"]).reset_index(drop=True) + panel["log_tvl_lag1"] = panel.groupby("pool_id")["log_tvl"].shift(1) + panel = panel.dropna(subset=["log_tvl_lag1"]).reset_index(drop=True) + + pool_counts = panel.groupby("pool_id").size() + valid = pool_counts[pool_counts >= 10].index + panel = panel[panel["pool_id"].isin(valid)].copy() + + print("Replacing volatility with Binance minute data...") + panel = replace_panel_volatility_with_binance(panel) + + print(f"Panel: {len(panel)} obs, {panel['pool_id'].nunique()} pools, " + f"{panel['date'].min()} to {panel['date'].max()}") + + matched = match_grids_to_panel(GRID_DIR, panel) + print(f"Matched: {len(matched)} pools with grids") + return panel, matched + + +def filter_pathological(matched, option_c): + """Drop pools with high Option C loss.""" + good = {p: r for p, r in option_c.items() if r["loss"] <= OPTION_C_LOSS_CUTOFF} + dropped = set(option_c) - set(good) + matched_clean = {p: matched[p] for p in good if p in matched} + if dropped: + print(f" Dropping {len(dropped)} pools (loss > {OPTION_C_LOSS_CUTOFF}):") + for p in sorted(dropped): + print(f" {p} loss={option_c[p]['loss']:.1f}") + return matched_clean, good + + +# ---- Fitting ---- + + +def run_option_c(matched): + """Per-pool fits with gas fixed to chain costs.""" + from quantammsim.calibration.per_pool_fit import fit_all_pools + + print(f"\n--- Option C: per-pool fits ({len(matched)} pools, gas fixed) ---") + results = fit_all_pools(matched, fix_gas_to_chain=True) + + losses = [r["loss"] for r in results.values()] + n_conv = sum(1 for r in results.values() if r["converged"]) + print(f" Converged: {n_conv}/{len(results)}") + print(f" Loss: median={np.median(losses):.4f}, mean={np.mean(losses):.4f}") + return results + + +def run_option_c_reduced(matched): + """Per-pool fits with reduced x_obs (4 covariates) and gas fixed.""" + from quantammsim.calibration.per_pool_fit import fit_all_pools + + print(f"\n--- Option C Reduced: per-pool fits ({len(matched)} pools, " + f"4-covariate x_obs, gas fixed) ---") + results = fit_all_pools(matched, fix_gas_to_chain=True, reduced=True) + + losses = [r["loss"] for r in results.values()] + n_conv = sum(1 for r in results.values() if r["converged"]) + print(f" Converged: {n_conv}/{len(results)}") + print(f" Loss: median={np.median(losses):.4f}, mean={np.mean(losses):.4f}") + return results + + +def _build_gas_values(jdata, matched_clean): + """Build fixed gas values (log-space) from chain data.""" + from quantammsim.calibration.loss import CHAIN_GAS_USD + gas_values = [] + for pid in jdata.pool_ids: + chain = matched_clean[pid]["chain"] + gas_usd = CHAIN_GAS_USD.get(chain, 1.0) + gas_values.append(np.log(max(gas_usd, 1e-6))) + return np.array(gas_values) + + +def run_linear_joint(matched_clean, option_c_clean): + """Joint fit with LinearHead + SharedLinearNoiseHead (baseline).""" + from quantammsim.calibration.calibration_model import CalibrationModel + from quantammsim.calibration.heads import FixedHead, LinearHead, SharedLinearNoiseHead + from quantammsim.calibration.joint_fit import prepare_joint_data + + jdata = prepare_joint_data( + matched_clean, drop_chain_dummies=True, fix_gas_to_chain=True) + gas_values = _build_gas_values(jdata, matched_clean) + + model = CalibrationModel( + cadence_head=LinearHead("cad", alpha=ALPHA_CAD), + gas_head=FixedHead("gas", gas_values), + noise_head=SharedLinearNoiseHead(alpha=ALPHA_NOISE), + ) + + n_pools = len(jdata.pool_data) + k_attr = jdata.x_attr.shape[1] + n_p = model.n_params(n_pools, k_attr) + print(f"\n--- Linear baseline: SharedLinearNoiseHead ({n_pools} pools, {n_p} params) ---") + + result = model.fit(jdata, maxiter=JOINT_MAXITER, warm_start=option_c_clean) + print(f" Loss: {result['init_loss']:.4f} -> {result['loss']:.4f}") + print(f" Converged: {result['converged']}") + return result, model, jdata + + +def run_mlp_noise_joint(matched_clean, option_c_clean, hidden=MLP_HIDDEN): + """Joint fit with LinearHead cadence + FixedHead gas + MLPNoiseHead.""" + from quantammsim.calibration.calibration_model import CalibrationModel + from quantammsim.calibration.heads import FixedHead, LinearHead, MLPNoiseHead + from quantammsim.calibration.joint_fit import prepare_joint_data + + jdata = prepare_joint_data( + matched_clean, drop_chain_dummies=True, fix_gas_to_chain=True) + gas_values = _build_gas_values(jdata, matched_clean) + + model = CalibrationModel( + cadence_head=LinearHead("cad", alpha=ALPHA_CAD), + gas_head=FixedHead("gas", gas_values), + noise_head=MLPNoiseHead(hidden=hidden, alpha=ALPHA_NOISE), + ) + + n_pools = len(jdata.pool_data) + k_attr = jdata.x_attr.shape[1] + n_p = model.n_params(n_pools, k_attr) + print(f"\n--- MLP noise: MLPNoiseHead(hidden={hidden}) ({n_pools} pools, {n_p} params) ---") + + result = model.fit(jdata, maxiter=JOINT_MAXITER, warm_start=option_c_clean) + print(f" Loss: {result['init_loss']:.4f} -> {result['loss']:.4f}") + print(f" Converged: {result['converged']}") + return result, model, jdata + + +def run_mlp_full_joint(matched_clean, option_c_clean, hidden=MLP_HIDDEN): + """Joint fit with MLPHead cadence + FixedHead gas + MLPNoiseHead.""" + from quantammsim.calibration.calibration_model import CalibrationModel + from quantammsim.calibration.heads import FixedHead, MLPHead, MLPNoiseHead + from quantammsim.calibration.joint_fit import prepare_joint_data + + jdata = prepare_joint_data( + matched_clean, drop_chain_dummies=True, fix_gas_to_chain=True) + gas_values = _build_gas_values(jdata, matched_clean) + + model = CalibrationModel( + cadence_head=MLPHead("cad", hidden=hidden, alpha=ALPHA_CAD), + gas_head=FixedHead("gas", gas_values), + noise_head=MLPNoiseHead(hidden=hidden, alpha=ALPHA_NOISE), + ) + + n_pools = len(jdata.pool_data) + k_attr = jdata.x_attr.shape[1] + n_p = model.n_params(n_pools, k_attr) + print(f"\n--- Full MLP: MLPHead(cad) + MLPNoiseHead ({n_pools} pools, {n_p} params) ---") + + result = model.fit(jdata, maxiter=JOINT_MAXITER, warm_start=option_c_clean) + print(f" Loss: {result['init_loss']:.4f} -> {result['loss']:.4f}") + print(f" Converged: {result['converged']}") + return result, model, jdata + + +def run_two_stage_joint(matched_clean, option_c_clean, hidden=MLP_HIDDEN): + """Two-stage joint fit to identify cadence separately from noise. + + Stage 1: LinearHead(cad) + FixedHead(gas) + PerPoolNoiseHead + Per-pool noise (8 coeffs/pool) can't fully absorb arb's daily + volatility pattern, so cadence is identified. + + Stage 2: FixedHead(cad, stage1_values) + FixedHead(gas) + MLPNoiseHead + Cadence frozen from stage 1, MLP learns shared noise mapping. + + For new-pool prediction: stage 1 linear coefficients give cadence, + stage 2 MLP gives noise. + """ + import jax.numpy as jnp + from quantammsim.calibration.calibration_model import CalibrationModel + from quantammsim.calibration.heads import ( + FixedHead, LinearHead, MLPNoiseHead, PerPoolNoiseHead, + ) + from quantammsim.calibration.joint_fit import prepare_joint_data + + jdata = prepare_joint_data( + matched_clean, drop_chain_dummies=True, fix_gas_to_chain=True) + gas_values = _build_gas_values(jdata, matched_clean) + n_pools = len(jdata.pool_data) + k_attr = jdata.x_attr.shape[1] + + # ---- Stage 1: fit cadence with per-pool noise ---- + stage1_model = CalibrationModel( + cadence_head=LinearHead("cad", alpha=ALPHA_CAD), + gas_head=FixedHead("gas", gas_values), + noise_head=PerPoolNoiseHead(), + ) + n_p1 = stage1_model.n_params(n_pools, k_attr) + print(f"\n--- Two-stage S1: LinearHead(cad) + PerPoolNoiseHead " + f"({n_pools} pools, {n_p1} params) ---") + + stage1_result = stage1_model.fit( + jdata, maxiter=JOINT_MAXITER, warm_start=option_c_clean) + print(f" Loss: {stage1_result['init_loss']:.4f} -> {stage1_result['loss']:.4f}") + print(f" Converged: {stage1_result['converged']}") + + # Extract per-pool cadences from stage 1 + params1 = jnp.array(stage1_result["params_flat"]) + (cs, ce), _, _ = stage1_model._head_slices(n_pools, k_attr) + cad_slice = params1[cs:ce] + stage1_cadences = np.array([ + float(stage1_model.cadence_head.predict(cad_slice, i, jdata.x_attr[i])) + for i in range(n_pools) + ]) + print(f" Cadence range: {np.exp(stage1_cadences.min()):.1f} - " + f"{np.exp(stage1_cadences.max()):.1f} min") + + # ---- Stage 2: fit MLP noise with frozen cadence ---- + stage2_model = CalibrationModel( + cadence_head=FixedHead("cad", stage1_cadences), + gas_head=FixedHead("gas", gas_values), + noise_head=MLPNoiseHead(hidden=hidden, alpha=ALPHA_NOISE), + ) + n_p2 = stage2_model.n_params(n_pools, k_attr) + print(f"\n--- Two-stage S2: FixedHead(cad) + MLPNoiseHead(hidden={hidden}) " + f"({n_pools} pools, {n_p2} params) ---") + + # Build warm-start for stage 2 noise from stage 1 per-pool noise + (_, _), (_, _), (ns, ne) = stage1_model._head_slices(n_pools, k_attr) + noise_params1 = np.array(params1[ns:ne]) + stage2_warm = {} + for i, pid in enumerate(jdata.pool_ids): + noise_c = np.array(stage1_model.noise_head.predict( + jnp.array(noise_params1), i, jdata.x_attr[i])) + stage2_warm[pid] = {"noise_coeffs": noise_c} + + stage2_result = stage2_model.fit( + jdata, maxiter=JOINT_MAXITER, warm_start=stage2_warm) + print(f" Loss: {stage2_result['init_loss']:.4f} -> {stage2_result['loss']:.4f}") + print(f" Converged: {stage2_result['converged']}") + + # Build a composite result dict for downstream use + # Cadence comes from stage 1 linear head, noise from stage 2 MLP + result = { + "stage1_result": stage1_result, + "stage2_result": stage2_result, + "loss": stage2_result["loss"], + "init_loss": stage1_result["init_loss"], + "converged": stage1_result["converged"] and stage2_result["converged"], + "n_pools": n_pools, + "k_attr": k_attr, + "pool_ids": jdata.pool_ids, + "attr_names": jdata.attr_names, + } + + return result, stage1_model, stage2_model, jdata + + +def run_reduced_joint(matched_clean, option_c_clean, hidden=MLP_HIDDEN): + """Joint fit with reduced x_obs (4 cols) to avoid noise-cadence confounding. + + Removes sigma- and fee-dependent features from the noise model's x_obs + so the arb channel (grid + cadence) is the only path for volatility-driven + volume variation. See docs/noise_covariate_design.md for theory. + + Uses LinearHead(cad) + FixedHead(gas) + MLPNoiseHead(k_obs=4). + Cadence warm-started from Option C; noise cold-started (OLS on 4-col x_obs). + """ + import jax.numpy as jnp + from quantammsim.calibration.calibration_model import CalibrationModel + from quantammsim.calibration.heads import FixedHead, LinearHead, MLPNoiseHead + from quantammsim.calibration.joint_fit import prepare_joint_data + from quantammsim.calibration.pool_data import K_OBS_REDUCED + + jdata = prepare_joint_data( + matched_clean, drop_chain_dummies=True, + fix_gas_to_chain=True, reduced_x_obs=True) + gas_values = _build_gas_values(jdata, matched_clean) + n_pools = len(jdata.pool_data) + k_attr = jdata.x_attr.shape[1] + + model = CalibrationModel( + cadence_head=LinearHead("cad", alpha=ALPHA_CAD), + gas_head=FixedHead("gas", gas_values), + noise_head=MLPNoiseHead(hidden=hidden, alpha=ALPHA_NOISE, + k_obs=K_OBS_REDUCED), + ) + + n_p = model.n_params(n_pools, k_attr) + print(f"\n--- Reduced x_obs: LinearHead(cad) + MLPNoiseHead(k_obs={K_OBS_REDUCED}, " + f"hidden={hidden}) ({n_pools} pools, {n_p} params) ---") + + # Only warm-start cadence (noise dimension changed 8→4, skip noise warm-start) + warm_cad = {} + for pid in jdata.pool_ids: + if pid in option_c_clean: + warm_cad[pid] = {"cad": option_c_clean[pid]["log_cadence"]} + + result = model.fit(jdata, maxiter=JOINT_MAXITER, warm_start=warm_cad) + print(f" Loss: {result['init_loss']:.4f} -> {result['loss']:.4f}") + print(f" Converged: {result['converged']}") + + return result, model, jdata + + +def _extract_two_stage_per_pool(stage1_model, stage2_model, result, jdata): + """Extract per-pool params from two-stage result.""" + import jax.numpy as jnp + + stage1_result = result["stage1_result"] + stage2_result = result["stage2_result"] + n_pools = result["n_pools"] + k_attr = result["k_attr"] + + params1 = jnp.array(stage1_result["params_flat"]) + params2 = jnp.array(stage2_result["params_flat"]) + + (cs1, ce1), _, (ns1, ne1) = stage1_model._head_slices(n_pools, k_attr) + _, _, (ns2, ne2) = stage2_model._head_slices(n_pools, k_attr) + + cad_slice = params1[cs1:ce1] + noise_slice = params2[ns2:ne2] + + per_pool = [] + for i in range(n_pools): + x_attr_i = jdata.x_attr[i] + log_cad = float(stage1_model.cadence_head.predict(cad_slice, i, x_attr_i)) + log_gas = float(stage2_model.gas_head.predict( + jnp.array([]), i, x_attr_i)) # FixedHead ignores params + noise_c = np.array(stage2_model.noise_head.predict(noise_slice, i, x_attr_i)) + per_pool.append({ + "log_cadence": log_cad, + "log_gas": log_gas, + "noise_coeffs": noise_c, + "cadence_minutes": float(np.exp(log_cad)), + "gas_usd": float(np.exp(log_gas)), + }) + return per_pool + + +# ---- Per-pool predictions ---- + + +def _extract_per_pool_params(model, result, jdata): + """Extract per-pool (log_cadence, log_gas, noise_coeffs) from a CalibrationModel result.""" + import jax.numpy as jnp + + params = jnp.array(result["params_flat"]) + n_pools = result["n_pools"] + k_attr = result["k_attr"] + (cs, ce), (gs, ge), (ns, ne) = model._head_slices(n_pools, k_attr) + + cad_slice = params[cs:ce] + gas_slice = params[gs:ge] + noise_slice = params[ns:ne] + + per_pool = [] + for i in range(n_pools): + x_attr_i = jdata.x_attr[i] + log_cad = float(model.cadence_head.predict(cad_slice, i, x_attr_i)) + log_gas = float(model.gas_head.predict(gas_slice, i, x_attr_i)) + noise_c = np.array(model.noise_head.predict(noise_slice, i, x_attr_i)) + per_pool.append({ + "log_cadence": log_cad, + "log_gas": log_gas, + "noise_coeffs": noise_c, + "cadence_minutes": float(np.exp(log_cad)), + "gas_usd": float(np.exp(log_gas)), + }) + return per_pool + + +def compute_per_pool_predictions(matched, option_c_results, + model_results, reduced_models=None): + """Compute V_arb, V_noise, R² per pool for Option C and each joint model. + + model_results: list of (label, per_pool_params, pool_ids) tuples, + where per_pool_params[i] is a dict with log_cadence, log_gas, noise_coeffs. + + reduced_models: list of label strings whose noise_coeffs correspond to + reduced x_obs (4 columns). For those, build_x_obs(reduced=True) is used. + """ + from quantammsim.calibration.grid_interpolation import interpolate_pool_daily + from quantammsim.calibration.pool_data import build_x_obs + import jax.numpy as jnp + + if reduced_models is None: + reduced_models = [] + + pool_ids = sorted(matched.keys()) + + # Build lookup for each model's per-pool params + model_lookups = [] + for label, per_pool_params, m_pool_ids in model_results: + lookup = {pid: per_pool_params[i] for i, pid in enumerate(m_pool_ids)} + model_lookups.append((label, lookup)) + + def r2(v_arb, v_noise, y): + log_pred = np.log(np.maximum(v_arb + v_noise, 1e-6)) + ss_res = np.sum((log_pred - y) ** 2) + ss_tot = np.sum((y - y.mean()) ** 2) + return 1 - ss_res / max(ss_tot, 1e-10) + + predictions = {} + for pid in pool_ids: + entry = matched[pid] + panel = entry["panel"] + coeffs = entry["coeffs"] + day_indices = entry["day_indices"] + + x_obs_full = build_x_obs(panel) + x_obs_red = None # lazy-build only if needed + y_obs = panel["log_volume"].values.astype(float) + + p = { + "dates": pd.to_datetime(panel["date"].values), + "y_obs": y_obs, + "actual_vol": np.exp(y_obs), + "chain": entry["chain"], + "tokens": entry["tokens"], + "fee": entry["fee"], + "median_tvl": float(np.exp(panel["log_tvl_lag1"].median())), + "n_obs": len(y_obs), + } + + # Option C + rc = option_c_results[pid] + v_arb_all = np.array(interpolate_pool_daily( + coeffs, jnp.float64(rc["log_cadence"]), + jnp.float64(np.exp(rc["log_gas"])))) + v_arb_c = v_arb_all[day_indices] + v_noise_c = np.exp(x_obs_full @ rc["noise_coeffs"]) + p["v_arb_c"] = v_arb_c + p["v_noise_c"] = v_noise_c + p["r2_c"] = r2(v_arb_c, v_noise_c, y_obs) + p["cadence_c"] = rc["cadence_minutes"] + p["gas_c"] = rc["gas_usd"] + + # Each joint model + for label, lookup in model_lookups: + if pid in lookup: + mp = lookup[pid] + v_arb_all = np.array(interpolate_pool_daily( + coeffs, jnp.float64(mp["log_cadence"]), + jnp.float64(np.exp(mp["log_gas"])))) + v_arb = v_arb_all[day_indices] + + # Use reduced x_obs for models that were trained with it + if label in reduced_models: + if x_obs_red is None: + x_obs_red = build_x_obs(panel, reduced=True) + v_noise = np.exp(x_obs_red @ mp["noise_coeffs"]) + else: + v_noise = np.exp(x_obs_full @ mp["noise_coeffs"]) + + p[f"v_arb_{label}"] = v_arb + p[f"v_noise_{label}"] = v_noise + p[f"r2_{label}"] = r2(v_arb, v_noise, y_obs) + p[f"cadence_{label}"] = mp["cadence_minutes"] + p[f"gas_{label}"] = mp["gas_usd"] + else: + n = len(y_obs) + p[f"v_arb_{label}"] = np.full(n, np.nan) + p[f"v_noise_{label}"] = np.full(n, np.nan) + p[f"r2_{label}"] = np.nan + p[f"cadence_{label}"] = np.nan + p[f"gas_{label}"] = np.nan + + predictions[pid] = p + + return predictions + + +# ---- Tables ---- + + +def print_pool_table(predictions, method_labels): + """Print per-pool results ranked by TVL.""" + ranked = sorted(predictions.items(), key=lambda x: -x[1]["median_tvl"]) + + header = f"{'Pool':<24} {'Chain':<10} {'TVL':>12} {'N':>4}" + header += f" {'Cad_C':>6} {'R2_C':>6}" + for label in method_labels: + short = label[:8] + header += f" {'Cad_'+short:>10} {'R2_'+short:>8}" + header += f" {'Arb%_C':>6}" + + print(f"\n{'='*len(header)}") + print(header) + print(f"{'-'*len(header)}") + for pid, p in ranked: + tokens = p["tokens"] + if isinstance(tokens, str): + tok_str = "/".join(t.strip()[:6] for t in tokens.split(",")[:2]) + else: + tok_str = pid[:16] + arb_total = p["v_arb_c"] + p["v_noise_c"] + arb_frac = np.median(p["v_arb_c"] / np.maximum(arb_total, 1.0)) + + line = (f"{tok_str:<24} {p['chain']:<10} ${p['median_tvl']:>10,.0f} " + f"{p['n_obs']:>4}") + line += f" {p['cadence_c']:>5.1f}m {p['r2_c']:>6.3f}" + for label in method_labels: + cad = p[f"cadence_{label}"] + r2v = p[f"r2_{label}"] + if np.isnan(cad): + line += f" {'---':>10} {'---':>8}" + else: + line += f" {cad:>9.1f}m {r2v:>8.3f}" + line += f" {arb_frac:>5.1%}" + print(line) + + +def print_r2_comparison(predictions, method_labels): + """Print aggregate R² comparison.""" + pool_ids = sorted(predictions.keys()) + + print(f"\n{'='*70}") + print("R² comparison (per-pool, in-sample)") + print(f"{'='*70}") + + r2_c = [predictions[p]["r2_c"] for p in pool_ids] + print(f" Option C (per-pool): median={np.median(r2_c):.4f} mean={np.mean(r2_c):.4f}") + + for label in method_labels: + r2_vals = [predictions[p][f"r2_{label}"] for p in pool_ids + if np.isfinite(predictions[p][f"r2_{label}"])] + if r2_vals: + print(f" {label:<22} median={np.median(r2_vals):.4f} mean={np.mean(r2_vals):.4f}") + + +def print_loss_comparison(option_c, joint_results): + """Print joint loss comparison.""" + print(f"\n{'='*70}") + print("Joint loss comparison") + print(f"{'='*70}") + + c_losses = [r["loss"] for r in option_c.values()] + print(f" Option C (per-pool): median={np.median(c_losses):.4f} mean={np.mean(c_losses):.4f}") + + for label, result in joint_results: + print(f" {label:<22} loss={result['loss']:.4f} (from {result['init_loss']:.4f})") + + +# ---- Plots ---- + + +def plot_decomposition_pages(predictions, method, method_label, output_dir): + """Paginated V_arb + V_noise stacked area decomposition.""" + ranked = sorted(predictions.items(), key=lambda x: -x[1]["median_tvl"])[:TOP_N] + + per_page = 10 + n_pages = (len(ranked) + per_page - 1) // per_page + + for page in range(n_pages): + start = page * per_page + end = min(start + per_page, len(ranked)) + page_pools = ranked[start:end] + n_this = len(page_pools) + + ncols = 2 + nrows = (n_this + ncols - 1) // ncols + fig, axes = plt.subplots(nrows, ncols, figsize=(16, 4.5 * nrows)) + if nrows == 1 and ncols == 1: + axes = np.array([[axes]]) + elif nrows == 1: + axes = axes.reshape(1, -1) + elif ncols == 1: + axes = axes.reshape(-1, 1) + + for idx, (pid, p) in enumerate(page_pools): + ax = axes[idx // ncols][idx % ncols] + dates = p["dates"] + + v_arb_key = f"v_arb_{method}" if method != "c" else "v_arb_c" + v_noise_key = f"v_noise_{method}" if method != "c" else "v_noise_c" + r2_key = f"r2_{method}" if method != "c" else "r2_c" + cad_key = f"cadence_{method}" if method != "c" else "cadence_c" + gas_key = f"gas_{method}" if method != "c" else "gas_c" + + v_arb = p[v_arb_key] + v_noise = p[v_noise_key] + r2_val = p[r2_key] + cad = p[cad_key] + gas = p[gas_key] + + if np.any(np.isnan(v_arb)): + ax.text(0.5, 0.5, f"Dropped from {method_label}", fontsize=12, + ha="center", va="center", transform=ax.transAxes, color="gray") + ax.set_title(f"{pid[:16]} — dropped", fontsize=8) + continue + + v_total = v_arb + v_noise + arb_frac = np.median(v_arb / np.maximum(v_total, 1.0)) + actual = p["actual_vol"] + + ax.fill_between(dates, 0, np.maximum(v_arb, 0), + alpha=0.3, color="orangered", label="V_arb (grid)") + ax.fill_between(dates, np.maximum(v_arb, 0), np.maximum(v_total, 0), + alpha=0.3, color="steelblue", label="V_noise") + ax.plot(dates, actual, "k-", linewidth=0.8, alpha=0.7, label="Actual") + ax.plot(dates, np.maximum(v_total, 0), "--", color="purple", + linewidth=0.8, alpha=0.7, label="Predicted total") + + ax.set_yscale("log") + ax.set_ylabel("Daily volume (USD)", fontsize=8) + + tokens = p["tokens"] + if isinstance(tokens, str): + tok_str = "/".join(t.strip()[:8] for t in tokens.split(",")[:2]) + else: + tok_str = pid[:16] + + ax.set_title( + f"{tok_str} ({p['chain']})\n" + f"TVL ${p['median_tvl']:,.0f} | R\u00b2={r2_val:.3f} " + f"cad={cad:.1f}min gas=${gas:.2f} " + f"arb_frac={arb_frac:.1%} n={p['n_obs']}", + fontsize=8, + ) + ax.legend(fontsize=6, loc="upper right") + ax.tick_params(labelsize=7) + ax.tick_params(axis="x", rotation=30) + + for idx in range(n_this, nrows * ncols): + axes[idx // ncols][idx % ncols].set_visible(False) + + fig.suptitle( + f"Calibration decomposition: {method_label}\n" + f"page {page + 1}/{n_pages} (top {min(TOP_N, len(ranked))} by TVL)", + fontsize=11, + ) + fig.tight_layout() + safe_method = method.replace(" ", "_") + out = os.path.join(output_dir, f"{safe_method}_page{page + 1}.png") + fig.savefig(out, dpi=150, bbox_inches="tight") + plt.close(fig) + print(f" Saved: {out}") + + +def plot_summary_distributions(predictions, method_labels, output_dir): + """Histograms of cadence, R², arb fraction for each method.""" + pool_ids = sorted(predictions.keys()) + methods = ["c"] + method_labels + labels = ["Option C"] + method_labels + n_methods = len(methods) + + fig, axes = plt.subplots(n_methods, 3, figsize=(15, 4 * n_methods)) + if n_methods == 1: + axes = axes.reshape(1, -1) + + for row, (method, label) in enumerate(zip(methods, labels)): + cad_key = f"cadence_{method}" if method != "c" else "cadence_c" + r2_key = f"r2_{method}" if method != "c" else "r2_c" + + cads = [predictions[p][cad_key] for p in pool_ids + if np.isfinite(predictions[p][cad_key])] + r2s = [predictions[p][r2_key] for p in pool_ids + if np.isfinite(predictions[p][r2_key])] + arb_fracs = [] + for p in pool_ids: + v_arb_key = f"v_arb_{method}" if method != "c" else "v_arb_c" + v_noise_key = f"v_noise_{method}" if method != "c" else "v_noise_c" + v_arb = predictions[p][v_arb_key] + v_noise = predictions[p][v_noise_key] + if not np.any(np.isnan(v_arb)): + total = v_arb + v_noise + arb_fracs.append(np.median(v_arb / np.maximum(total, 1.0))) + + ax = axes[row, 0] + if cads: + ax.hist(cads, bins=20, color="orangered", alpha=0.7, edgecolor="white") + ax.axvline(np.median(cads), color="black", linestyle="--", + label=f"Median={np.median(cads):.1f}min") + ax.set_xlabel("Cadence (minutes)") + ax.set_title(f"{label}: Cadence") + ax.legend(fontsize=8) + + ax = axes[row, 1] + if r2s: + ax.hist(r2s, bins=20, color="green", alpha=0.7, edgecolor="white") + ax.axvline(np.median(r2s), color="black", linestyle="--", + label=f"Median={np.median(r2s):.3f}") + ax.set_xlabel("R\u00b2") + ax.set_title(f"{label}: R\u00b2") + ax.legend(fontsize=8) + + ax = axes[row, 2] + if arb_fracs: + ax.hist(arb_fracs, bins=20, color="steelblue", alpha=0.7, edgecolor="white") + ax.axvline(np.median(arb_fracs), color="black", linestyle="--", + label=f"Median={np.median(arb_fracs):.2f}") + ax.set_xlabel("Arb fraction") + ax.set_title(f"{label}: Arb fraction") + ax.legend(fontsize=8) + + fig.tight_layout() + out = os.path.join(output_dir, "summary_distributions.png") + fig.savefig(out, dpi=150, bbox_inches="tight") + plt.close(fig) + print(f" Saved: {out}") + + +def plot_r2_scatter(predictions, method_labels, output_dir): + """Scatter: Option C R² vs each joint method R².""" + pool_ids = sorted(predictions.keys()) + n = len(method_labels) + + fig, axes = plt.subplots(1, n, figsize=(6 * n, 5)) + if n == 1: + axes = [axes] + + for ax, label in zip(axes, method_labels): + r2_c = [] + r2_m = [] + for p in pool_ids: + rc = predictions[p]["r2_c"] + rm = predictions[p][f"r2_{label}"] + if np.isfinite(rc) and np.isfinite(rm): + r2_c.append(rc) + r2_m.append(rm) + + ax.scatter(r2_c, r2_m, alpha=0.7, s=30, edgecolors="k", linewidth=0.5) + lo = min(min(r2_c), min(r2_m)) if r2_c else 0 + hi = max(max(r2_c), max(r2_m)) if r2_c else 1 + margin = (hi - lo) * 0.05 + 0.01 + ax.plot([lo - margin, hi + margin], [lo - margin, hi + margin], + "k--", alpha=0.3, linewidth=1) + ax.set_xlabel("Option C R\u00b2") + ax.set_ylabel(f"{label} R\u00b2") + ax.set_title(f"Option C vs {label}") + + # Count wins + wins = sum(1 for c, m in zip(r2_c, r2_m) if m > c) + ax.text(0.05, 0.95, f"{label} wins: {wins}/{len(r2_c)}", + transform=ax.transAxes, fontsize=9, va="top") + + fig.tight_layout() + out = os.path.join(output_dir, "r2_scatter.png") + fig.savefig(out, dpi=150, bbox_inches="tight") + plt.close(fig) + print(f" Saved: {out}") + + +def plot_cadence_by_chain(predictions, method_labels, output_dir): + """Cadence distributions by chain for Option C and each method.""" + pool_ids = sorted(predictions.keys()) + chains = sorted(set(predictions[p]["chain"] for p in pool_ids)) + colors = plt.cm.tab10(np.linspace(0, 1, max(len(chains), 1))) + chain_color = {c: colors[i] for i, c in enumerate(chains)} + + methods = ["c"] + method_labels + labels = ["Option C"] + method_labels + n = len(methods) + + fig, axes = plt.subplots(1, n, figsize=(6 * n, 5)) + if n == 1: + axes = [axes] + + for ax, method, label in zip(axes, methods, labels): + cad_key = f"cadence_{method}" if method != "c" else "cadence_c" + for chain in chains: + cads = [predictions[p][cad_key] for p in pool_ids + if predictions[p]["chain"] == chain + and np.isfinite(predictions[p][cad_key])] + if cads: + ax.scatter([chain] * len(cads), cads, color=chain_color[chain], + alpha=0.7, s=40, edgecolors="k", linewidth=0.3) + ax.set_ylabel("Cadence (minutes)") + ax.set_title(label) + ax.tick_params(axis="x", rotation=45) + + fig.tight_layout() + out = os.path.join(output_dir, "cadence_by_chain.png") + fig.savefig(out, dpi=150, bbox_inches="tight") + plt.close(fig) + print(f" Saved: {out}") + + +# ---- JSON export ---- + + +def save_results_json(predictions, option_c_results, joint_results, output_dir): + """Save all fitted parameters and diagnostics.""" + out = {"option_c": {}} + for pid, r in option_c_results.items(): + out["option_c"][pid] = { + "log_cadence": r["log_cadence"], + "log_gas": r["log_gas"], + "noise_coeffs": r["noise_coeffs"].tolist(), + "loss": r["loss"], + "converged": bool(r["converged"]), + "cadence_minutes": r["cadence_minutes"], + "gas_usd": r["gas_usd"], + "chain": r.get("chain", ""), + "fee": r.get("fee", 0), + "tokens": r.get("tokens", ""), + } + + for label, result in joint_results: + entry = { + "loss": result["loss"], + "init_loss": result["init_loss"], + "converged": bool(result["converged"]), + "n_pools": result.get("n_pools", 0), + "k_attr": result.get("k_attr", 0), + "pool_ids": result.get("pool_ids", []), + "attr_names": result.get("attr_names", []), + } + # Include any scalar/array results the heads produced + for key in ["bias_cad", "W_cad", "bias_gas", "W_gas", + "bias_noise", "W_noise", "noise_coeffs"]: + if key in result: + val = result[key] + entry[key] = val.tolist() if hasattr(val, "tolist") else val + out[label] = entry + + # Per-pool R² for each method + pool_ids = sorted(predictions.keys()) + method_labels = [label for label, _ in joint_results] + per_pool_r2 = {} + for pid in pool_ids: + p = predictions[pid] + row = {"r2_c": p["r2_c"]} + for label in method_labels: + row[f"r2_{label}"] = p[f"r2_{label}"] + per_pool_r2[pid] = row + out["per_pool_r2"] = per_pool_r2 + + path = os.path.join(output_dir, "mlp_calibration_results.json") + with open(path, "w") as f: + json.dump(out, f, indent=2, default=str) + print(f" Saved: {path}") + + +# ---- Main ---- + + +def _save_per_pool_results(results, output_path, label="option_c_reduced"): + """Save per-pool fit results to JSON immediately.""" + out = {} + for pid, r in results.items(): + out[pid] = { + "log_cadence": r["log_cadence"], + "log_gas": r["log_gas"], + "noise_coeffs": r["noise_coeffs"].tolist(), + "loss": r["loss"], + "converged": bool(r["converged"]), + "cadence_minutes": r["cadence_minutes"], + "gas_usd": r["gas_usd"], + "chain": r.get("chain", ""), + "fee": r.get("fee", 0), + "tokens": r.get("tokens", ""), + } + os.makedirs(os.path.dirname(output_path), exist_ok=True) + with open(output_path, "w") as f: + json.dump({label: out}, f, indent=2) + print(f" Saved {len(out)} pool results to {output_path}") + + +def main(): + os.environ.setdefault("JAX_PLATFORMS", "cpu") + + print("=" * 70) + print("MLP Calibration: MLPNoiseHead vs Linear Baseline") + print("=" * 70) + + panel, matched = load_and_match() + + # Step 0: 4-covariate per-pool fits (fast, save immediately) + option_c_reduced = run_option_c_reduced(matched) + _save_per_pool_results( + option_c_reduced, + os.path.join(OUTPUT_DIR, "option_c_reduced.json"), + label="option_c_reduced", + ) + + # Step 1: Option C baseline (8-covariate) + option_c = run_option_c(matched) + + # Step 2: Filter pathological pools + matched_clean, option_c_clean = filter_pathological(matched, option_c) + + # Step 3: Fit each joint model + linear_result, linear_model, jdata = run_linear_joint( + matched_clean, option_c_clean) + mlp_noise_result, mlp_noise_model, _ = run_mlp_noise_joint( + matched_clean, option_c_clean) + mlp_full_result, mlp_full_model, _ = run_mlp_full_joint( + matched_clean, option_c_clean) + + # Two-stage: cadence identified with per-pool noise, then MLP noise + two_stage_result, ts_s1_model, ts_s2_model, _ = run_two_stage_joint( + matched_clean, option_c_clean) + + # Reduced x_obs: prune sigma/fee features from noise covariates + reduced_result, reduced_model, jdata_reduced = run_reduced_joint( + matched_clean, option_c_clean) + + # Step 4: Extract per-pool params from each model + linear_pp = _extract_per_pool_params(linear_model, linear_result, jdata) + mlp_noise_pp = _extract_per_pool_params(mlp_noise_model, mlp_noise_result, jdata) + mlp_full_pp = _extract_per_pool_params(mlp_full_model, mlp_full_result, jdata) + two_stage_pp = _extract_two_stage_per_pool( + ts_s1_model, ts_s2_model, two_stage_result, jdata) + reduced_pp = _extract_per_pool_params( + reduced_model, reduced_result, jdata_reduced) + + method_labels = ["linear", "mlp_noise", "mlp_full", "two_stage", "reduced"] + model_results_for_pred = [ + ("linear", linear_pp, jdata.pool_ids), + ("mlp_noise", mlp_noise_pp, jdata.pool_ids), + ("mlp_full", mlp_full_pp, jdata.pool_ids), + ("two_stage", two_stage_pp, jdata.pool_ids), + ("reduced", reduced_pp, jdata_reduced.pool_ids), + ] + + # Step 5: Per-pool predictions + print("\nComputing per-pool predictions...") + predictions = compute_per_pool_predictions( + matched_clean, option_c_clean, model_results_for_pred, + reduced_models=["reduced"]) + + # Step 6: Tables + print_pool_table(predictions, method_labels) + print_r2_comparison(predictions, method_labels) + print_loss_comparison(option_c_clean, [ + ("linear", linear_result), + ("mlp_noise", mlp_noise_result), + ("mlp_full", mlp_full_result), + ("two_stage", two_stage_result), + ("reduced", reduced_result), + ]) + + # Step 7: Plots + print("\nGenerating plots...") + os.makedirs(OUTPUT_DIR, exist_ok=True) + + plot_decomposition_pages(predictions, "c", "Option C (per-pool)", OUTPUT_DIR) + plot_decomposition_pages(predictions, "linear", "Linear shared noise", OUTPUT_DIR) + plot_decomposition_pages(predictions, "mlp_noise", "MLP noise (linear cad)", OUTPUT_DIR) + plot_decomposition_pages(predictions, "mlp_full", "Full MLP (MLP cad + MLP noise)", OUTPUT_DIR) + plot_decomposition_pages(predictions, "two_stage", "Two-stage (linear cad -> MLP noise)", OUTPUT_DIR) + plot_decomposition_pages(predictions, "reduced", "Reduced x_obs (k_obs=4)", OUTPUT_DIR) + + plot_summary_distributions(predictions, method_labels, OUTPUT_DIR) + plot_r2_scatter(predictions, method_labels, OUTPUT_DIR) + plot_cadence_by_chain(predictions, method_labels, OUTPUT_DIR) + + # Step 8: JSON export + save_results_json(predictions, option_c_clean, [ + ("linear", linear_result), + ("mlp_noise", mlp_noise_result), + ("mlp_full", mlp_full_result), + ("two_stage", two_stage_result), + ("reduced", reduced_result), + ], OUTPUT_DIR) + + print(f"\n{'='*70}") + print(f"Done. Output in: {OUTPUT_DIR}") + + +if __name__ == "__main__": + main() diff --git a/scripts/run_mlp_sweep.py b/scripts/run_mlp_sweep.py new file mode 100644 index 0000000..aa28115 --- /dev/null +++ b/scripts/run_mlp_sweep.py @@ -0,0 +1,429 @@ +"""Hyperparameter sweep for MLP calibration models. + +Sweeps maxiter, alpha, hidden size, maxcor, and loss_type to find settings +where the MLP converges and achieves the best per-pool R2. + +Usage: + python scripts/run_mlp_sweep.py [--phase 1|2|3|all] +""" + +import argparse +import json +import os +import time + +import numpy as np +import pandas as pd + +# ---- Config ---- +PANEL_CACHE = os.path.join( + os.path.dirname(os.path.dirname(__file__)), + "local_data", "noise_calibration", "panel.parquet", +) +GRID_DIR = os.path.join( + os.path.dirname(os.path.dirname(__file__)), + "results", "pool_grids_v2", +) +OUTPUT_DIR = os.path.join( + os.path.dirname(os.path.dirname(__file__)), + "results", "mlp_sweep", +) +OPTION_C_LOSS_CUTOFF = 5.0 + + +def load_data(): + """Load panel, match grids, run Option C, return clean data.""" + from quantammsim.calibration.per_pool_fit import fit_all_pools + from quantammsim.calibration.pool_data import ( + build_pool_attributes, + build_x_obs, + match_grids_to_panel, + replace_panel_volatility_with_binance, + ) + + panel = pd.read_parquet(PANEL_CACHE) + print("Replacing volatility with Binance minute data...") + panel = replace_panel_volatility_with_binance(panel) + matched = match_grids_to_panel(GRID_DIR, panel) + print(f"Matched: {len(matched)} pools with grids") + + # Option C baseline + print(f"\n--- Option C: per-pool fits ({len(matched)} pools, gas fixed) ---") + option_c = fit_all_pools(matched, fix_gas_to_chain=True) + n_conv = sum(1 for r in option_c.values() if r["converged"]) + losses = [r["loss"] for r in option_c.values()] + print(f" Converged: {n_conv}/{len(option_c)}") + print(f" Loss: median={np.median(losses):.4f}, mean={np.mean(losses):.4f}") + + # Drop pathological pools + dropped = [p for p, r in option_c.items() if r["loss"] > OPTION_C_LOSS_CUTOFF] + matched_clean = {k: v for k, v in matched.items() if k not in dropped} + option_c_clean = {k: v for k, v in option_c.items() if k not in dropped} + if dropped: + print(f" Dropping {len(dropped)} pools (loss > {OPTION_C_LOSS_CUTOFF})") + + return matched_clean, option_c_clean + + +def compute_per_pool_r2(model, result, jdata, matched): + """Compute per-pool R2 for a fitted model.""" + from quantammsim.calibration.grid_interpolation import interpolate_pool_daily + from quantammsim.calibration.pool_data import build_x_obs + import jax.numpy as jnp + + params = jnp.array(result["params_flat"]) + n_pools = result["n_pools"] + k_attr = result["k_attr"] + (cs, ce), (gs, ge), (ns, ne) = model._head_slices(n_pools, k_attr) + + cad_slice = params[cs:ce] + gas_slice = params[gs:ge] + noise_slice = params[ns:ne] + + r2s = [] + for i, pid in enumerate(jdata.pool_ids): + x_attr_i = jdata.x_attr[i] + log_cad = float(model.cadence_head.predict(cad_slice, i, x_attr_i)) + log_gas = float(model.gas_head.predict(gas_slice, i, x_attr_i)) + noise_c = np.array(model.noise_head.predict(noise_slice, i, x_attr_i)) + + entry = matched[pid] + panel = entry["panel"] + coeffs = entry["coeffs"] + day_indices = entry["day_indices"] + x_obs = build_x_obs(panel) + y_obs = panel["log_volume"].values.astype(float) + + v_arb_all = np.array(interpolate_pool_daily( + coeffs, jnp.float64(log_cad), jnp.float64(np.exp(log_gas)))) + v_arb = v_arb_all[day_indices] + v_noise = np.exp(x_obs @ noise_c) + log_pred = np.log(np.maximum(v_arb + v_noise, 1e-6)) + ss_res = np.sum((log_pred - y_obs) ** 2) + ss_tot = np.sum((y_obs - y_obs.mean()) ** 2) + r2s.append(1 - ss_res / max(ss_tot, 1e-10)) + + return np.array(r2s) + + +def run_single(matched_clean, option_c_clean, config): + """Run a single sweep configuration. Returns result dict.""" + from quantammsim.calibration.calibration_model import CalibrationModel + from quantammsim.calibration.heads import ( + FixedHead, LinearHead, MLPHead, MLPNoiseHead, SharedLinearNoiseHead, + ) + from quantammsim.calibration.joint_fit import prepare_joint_data + from quantammsim.calibration.loss import CHAIN_GAS_USD + + jdata = prepare_joint_data( + matched_clean, drop_chain_dummies=True, fix_gas_to_chain=True) + + gas_values = [] + for pid in jdata.pool_ids: + chain = matched_clean[pid]["chain"] + gas_usd = CHAIN_GAS_USD.get(chain, 1.0) + gas_values.append(np.log(max(gas_usd, 1e-6))) + gas_values = np.array(gas_values) + + # Build model from config + alpha_cad = config.get("alpha_cad", 0.01) + alpha_noise = config.get("alpha_noise", 0.01) + hidden = config.get("hidden", 16) + maxiter = config.get("maxiter", 500) + maxcor = config.get("maxcor", 10) + loss_type = config.get("loss_type", "l2") + cad_type = config.get("cad_type", "linear") # "linear" or "mlp" + noise_type = config.get("noise_type", "mlp") # "mlp" or "linear" + + # Cadence head + if cad_type == "mlp": + cad_head = MLPHead("cad", hidden=hidden, alpha=alpha_cad) + else: + cad_head = LinearHead("cad", alpha=alpha_cad) + + # Noise head + if noise_type == "mlp": + noise_head = MLPNoiseHead(hidden=hidden, alpha=alpha_noise) + else: + noise_head = SharedLinearNoiseHead(alpha=alpha_noise) + + model = CalibrationModel( + cadence_head=cad_head, + gas_head=FixedHead("gas", gas_values), + noise_head=noise_head, + loss_type=loss_type, + ) + + n_pools = len(jdata.pool_data) + k_attr = jdata.x_attr.shape[1] + n_params = model.n_params(n_pools, k_attr) + + # Override maxcor in the fit method by monkey-patching options + import scipy.optimize + _orig_minimize = scipy.optimize.minimize + + def patched_minimize(fun, x0, **kwargs): + opts = kwargs.get("options", {}) + opts["maxcor"] = maxcor + opts["maxiter"] = maxiter + kwargs["options"] = opts + return _orig_minimize(fun, x0, **kwargs) + + scipy.optimize.minimize = patched_minimize + try: + t0 = time.time() + result = model.fit(jdata, maxiter=maxiter, warm_start=option_c_clean) + wall_time = time.time() - t0 + finally: + scipy.optimize.minimize = _orig_minimize + + # Compute per-pool R2 + r2s = compute_per_pool_r2(model, result, jdata, matched_clean) + + return { + "config": config, + "n_params": n_params, + "n_pools": n_pools, + "init_loss": result["init_loss"], + "final_loss": result["loss"], + "converged": result["converged"], + "wall_time_s": round(wall_time, 1), + "r2_median": round(float(np.median(r2s)), 4), + "r2_mean": round(float(np.mean(r2s)), 4), + "r2_p10": round(float(np.percentile(r2s, 10)), 4), + "r2_p25": round(float(np.percentile(r2s, 25)), 4), + "r2_p75": round(float(np.percentile(r2s, 75)), 4), + "r2_p90": round(float(np.percentile(r2s, 90)), 4), + "r2_min": round(float(np.min(r2s)), 4), + "r2_max": round(float(np.max(r2s)), 4), + "n_positive_r2": int(np.sum(r2s > 0)), + } + + +def print_result(res, idx=None): + """Print a single result row.""" + c = res["config"] + prefix = f"[{idx}] " if idx is not None else "" + label = (f"{c.get('cad_type','linear')}_cad + " + f"{c.get('noise_type','mlp')}_noise") + print(f"{prefix}{label} " + f"h={c.get('hidden',16):2d} " + f"a_c={c.get('alpha_cad',0.01):.4f} " + f"a_n={c.get('alpha_noise',0.01):.4f} " + f"maxiter={c.get('maxiter',500):5d} " + f"maxcor={c.get('maxcor',10):2d} " + f"loss={c.get('loss_type','l2'):5s} | " + f"L={res['final_loss']:7.4f} " + f"conv={str(res['converged']):5s} " + f"R2_med={res['r2_median']:+.4f} " + f"R2_mean={res['r2_mean']:+.4f} " + f"R2+={res['n_positive_r2']:2d}/{res['n_pools']} " + f"{res['wall_time_s']:5.1f}s") + + +def run_phase_1(matched_clean, option_c_clean): + """Phase 1: Sweep maxiter to diagnose convergence.""" + print("\n" + "=" * 80) + print("Phase 1: maxiter sweep (MLP noise, linear cadence)") + print("=" * 80) + + configs = [] + for maxiter in [500, 2000, 5000]: + configs.append({ + "cad_type": "linear", "noise_type": "mlp", + "maxiter": maxiter, "hidden": 16, + "alpha_cad": 0.01, "alpha_noise": 0.01, + "maxcor": 10, "loss_type": "l2", + "label": f"maxiter={maxiter}", + }) + # Also sweep maxiter for full MLP + for maxiter in [500, 2000, 5000]: + configs.append({ + "cad_type": "mlp", "noise_type": "mlp", + "maxiter": maxiter, "hidden": 16, + "alpha_cad": 0.01, "alpha_noise": 0.01, + "maxcor": 10, "loss_type": "l2", + "label": f"full_mlp_maxiter={maxiter}", + }) + + results = [] + for i, cfg in enumerate(configs): + print(f"\n Running {cfg['label']}...") + res = run_single(matched_clean, option_c_clean, cfg) + print_result(res, i) + results.append(res) + return results + + +def run_phase_2(matched_clean, option_c_clean, best_maxiter=5000): + """Phase 2: Regularization grid (alpha_noise x alpha_cad).""" + print("\n" + "=" * 80) + print("Phase 2: regularization sweep (MLP noise, linear cadence)") + print("=" * 80) + + configs = [] + for alpha_noise in [0.0001, 0.001, 0.01, 0.1]: + for alpha_cad in [0.001, 0.01, 0.1]: + configs.append({ + "cad_type": "linear", "noise_type": "mlp", + "maxiter": best_maxiter, "hidden": 16, + "alpha_cad": alpha_cad, "alpha_noise": alpha_noise, + "maxcor": 10, "loss_type": "l2", + "label": f"a_n={alpha_noise}, a_c={alpha_cad}", + }) + + results = [] + for i, cfg in enumerate(configs): + print(f"\n Running {cfg['label']}...") + res = run_single(matched_clean, option_c_clean, cfg) + print_result(res, i) + results.append(res) + return results + + +def run_phase_3(matched_clean, option_c_clean, + best_maxiter=5000, best_alpha_cad=0.01, + best_alpha_noise=0.01): + """Phase 3: Architecture sweep (hidden, maxcor, loss_type).""" + print("\n" + "=" * 80) + print("Phase 3: architecture sweep") + print("=" * 80) + + configs = [] + # Hidden size + for hidden in [8, 16, 32]: + configs.append({ + "cad_type": "linear", "noise_type": "mlp", + "maxiter": best_maxiter, "hidden": hidden, + "alpha_cad": best_alpha_cad, "alpha_noise": best_alpha_noise, + "maxcor": 10, "loss_type": "l2", + "label": f"hidden={hidden}", + }) + # maxcor + for maxcor in [10, 30, 50]: + configs.append({ + "cad_type": "linear", "noise_type": "mlp", + "maxiter": best_maxiter, "hidden": 16, + "alpha_cad": best_alpha_cad, "alpha_noise": best_alpha_noise, + "maxcor": maxcor, "loss_type": "l2", + "label": f"maxcor={maxcor}", + }) + # Loss type + for loss_type in ["l2", "huber"]: + configs.append({ + "cad_type": "linear", "noise_type": "mlp", + "maxiter": best_maxiter, "hidden": 16, + "alpha_cad": best_alpha_cad, "alpha_noise": best_alpha_noise, + "maxcor": 10, "loss_type": loss_type, + "label": f"loss={loss_type}", + }) + # Full MLP with best settings + configs.append({ + "cad_type": "mlp", "noise_type": "mlp", + "maxiter": best_maxiter, "hidden": 16, + "alpha_cad": best_alpha_cad, "alpha_noise": best_alpha_noise, + "maxcor": 10, "loss_type": "l2", + "label": "full_mlp_best", + }) + + results = [] + for i, cfg in enumerate(configs): + print(f"\n Running {cfg['label']}...") + res = run_single(matched_clean, option_c_clean, cfg) + print_result(res, i) + results.append(res) + return results + + +def main(): + parser = argparse.ArgumentParser() + parser.add_argument("--phase", default="all", + choices=["1", "2", "3", "all"]) + args = parser.parse_args() + + os.makedirs(OUTPUT_DIR, exist_ok=True) + matched_clean, option_c_clean = load_data() + + # Compute Option C R2 for reference + from quantammsim.calibration.grid_interpolation import interpolate_pool_daily + from quantammsim.calibration.pool_data import build_x_obs + import jax.numpy as jnp + + r2s_c = [] + for pid in sorted(matched_clean.keys()): + entry = matched_clean[pid] + rc = option_c_clean[pid] + panel = entry["panel"] + x_obs = build_x_obs(panel) + y_obs = panel["log_volume"].values.astype(float) + day_indices = entry["day_indices"] + coeffs = entry["coeffs"] + + v_arb_all = np.array(interpolate_pool_daily( + coeffs, jnp.float64(rc["log_cadence"]), + jnp.float64(np.exp(rc["log_gas"])))) + v_arb = v_arb_all[day_indices] + v_noise = np.exp(x_obs @ rc["noise_coeffs"]) + log_pred = np.log(np.maximum(v_arb + v_noise, 1e-6)) + ss_res = np.sum((log_pred - y_obs) ** 2) + ss_tot = np.sum((y_obs - y_obs.mean()) ** 2) + r2s_c.append(1 - ss_res / max(ss_tot, 1e-10)) + + r2s_c = np.array(r2s_c) + print(f"\nOption C reference: R2 median={np.median(r2s_c):.4f}, " + f"mean={np.mean(r2s_c):.4f}, " + f"R2>0: {np.sum(r2s_c > 0)}/{len(r2s_c)}") + + all_results = {"option_c_r2_median": float(np.median(r2s_c)), + "option_c_r2_mean": float(np.mean(r2s_c)), + "phases": {}} + + if args.phase in ("1", "all"): + r1 = run_phase_1(matched_clean, option_c_clean) + all_results["phases"]["1"] = r1 + + # Pick best maxiter from phase 1 + best_maxiter = max(r1, key=lambda r: r["r2_median"])["config"]["maxiter"] + print(f"\n Best maxiter from phase 1: {best_maxiter}") + else: + best_maxiter = 5000 + + if args.phase in ("2", "all"): + r2 = run_phase_2(matched_clean, option_c_clean, best_maxiter) + all_results["phases"]["2"] = r2 + + best_r = max(r2, key=lambda r: r["r2_median"]) + best_alpha_cad = best_r["config"]["alpha_cad"] + best_alpha_noise = best_r["config"]["alpha_noise"] + print(f"\n Best from phase 2: alpha_cad={best_alpha_cad}, " + f"alpha_noise={best_alpha_noise}, R2_med={best_r['r2_median']}") + else: + best_alpha_cad = 0.01 + best_alpha_noise = 0.01 + + if args.phase in ("3", "all"): + r3 = run_phase_3(matched_clean, option_c_clean, + best_maxiter, best_alpha_cad, best_alpha_noise) + all_results["phases"]["3"] = r3 + + # Save results + out_path = os.path.join(OUTPUT_DIR, "sweep_results.json") + with open(out_path, "w") as f: + json.dump(all_results, f, indent=2, default=str) + print(f"\nResults saved to {out_path}") + + # Print summary table + print("\n" + "=" * 80) + print("SWEEP SUMMARY") + print("=" * 80) + print(f"Option C reference: R2 median={np.median(r2s_c):.4f}") + print() + for phase, results in all_results["phases"].items(): + print(f"Phase {phase}:") + for i, res in enumerate(results): + print_result(res, i) + print() + + +if __name__ == "__main__": + main() diff --git a/scripts/run_mm_noise.py b/scripts/run_mm_noise.py new file mode 100644 index 0000000..7153635 --- /dev/null +++ b/scripts/run_mm_noise.py @@ -0,0 +1,865 @@ +"""Michaelis-Menten noise model with market features. + +Replaces the linear TVL term with a Michaelis-Menten saturation curve +while keeping all market features for temporal fit: + + log(V_noise) = log_alpha_i + x_market @ gamma + + log(TVL) - log(K_i + TVL) + + V_total = V_arb(cadence_i) + exp(log_V_noise) + Loss = Huber(log(V_total) - log(V_obs)) + +The TVL feature (xobs_1) is removed from x_market and handled +structurally via the MM saturation term. All other features (dow, +BTC, token, pair vol, interactions) remain as shared linear covariates. + +Parameters: + log_alpha_i : per-pool intercept + log_K_i : per-pool half-saturation TVL + gamma : shared coefficients on non-TVL features + log_cadence_i: per-pool arb frequency (via PCHIP) + +Usage: + python scripts/run_mm_noise.py + python scripts/run_mm_noise.py --epochs 5000 --lr 3e-4 + python scripts/run_mm_noise.py --per-pool-gamma # per-pool market coeffs +""" + +import argparse +import json +import os +import pickle +import sys +import time + +import jax +import jax.numpy as jnp +import numpy as np +import pandas as pd + + +CACHE_DIR = os.path.join( + os.path.dirname(os.path.dirname(__file__)), + "results", "token_factored_calibration", "_cache", +) + + +def load_stage1(): + path = os.path.join(CACHE_DIR, "stage1.pkl") + with open(path, "rb") as f: + data = pickle.load(f) + return data["matched_clean"], data["option_c_clean"] + + +COMPETITOR_TVL_PATH = os.path.join( + os.path.dirname(os.path.dirname(__file__)), + "results", "competitor_tvl", "competitor_tvl.npz", +) + + +def build_mm_data(matched_clean, option_c_clean, trend_windows=(7,), + include_cross_pool=False, competitor_tvl_path=None): + """Build data with MM structure: separate TVL from market features. + + Loads observed competitor TVL from DeFi Llama for K. + """ + from experiments.run_linear_market_noise import build_data + from quantammsim.calibration.pool_data import _parse_tokens + + # Get full feature matrix from linear model's pipeline + data = build_data( + matched_clean, option_c_clean, + trend_windows=trend_windows, + include_market=True, + include_cross_pool=include_cross_pool, + ) + + # Remove TVL column and TVL interaction terms — TVL handled by MM + feat_names = data["feat_names"] + x_full = data["x"] + tvl_col = feat_names.index("xobs_1") + tvl_interaction_cols = [i for i, name in enumerate(feat_names) + if name.startswith("xobs_1\u00d7")] + remove_cols = {tvl_col} | set(tvl_interaction_cols) + keep_cols = [i for i in range(len(feat_names)) if i not in remove_cols] + x_market = x_full[:, keep_cols].astype(np.float32) + market_names = [feat_names[i] for i in keep_cols] + + pool_ids = data["pool_ids"] + n_pools = data["n_pools"] + + # Common date grid + all_dates = set() + for pid in pool_ids: + all_dates.update(matched_clean[pid]["panel"]["date"].values) + date_list = sorted(all_dates) + date_to_idx = {d: i for i, d in enumerate(date_list)} + n_dates = len(date_list) + + # Rebuild raw log_tvl from panel + tvl_grid = np.full((n_dates, n_pools), np.nan) + for j, pid in enumerate(pool_ids): + panel = matched_clean[pid]["panel"] + dates = panel["date"].values + log_tvls = panel["log_tvl_lag1"].values.astype(float) + for k, date in enumerate(dates): + tvl_grid[date_to_idx[date], j] = log_tvls[k] + + pool_idx = data["pool_idx"] + day_idx = data["day_idx"] + n_samples = len(pool_idx) + log_tvl = np.array([tvl_grid[day_idx[s], pool_idx[s]] + for s in range(n_samples)], dtype=np.float32) + + # Load observed competitor TVL (K) + comp_path = competitor_tvl_path or COMPETITOR_TVL_PATH + if os.path.exists(comp_path): + comp_data = np.load(comp_path, allow_pickle=True) + comp_pool_ids = list(comp_data["pool_ids"]) + comp_dates = list(comp_data["date_list"]) + # Use K_eff (network conductance) if available, else direct competitor TVL + if "k_eff" in comp_data: + comp_tvl_matrix = comp_data["k_eff"] + print(f" Using network K_eff (direct + multi-hop)") + else: + comp_tvl_matrix = comp_data["competitor_tvl"] + print(f" Using direct competitor TVL only") + + # Build date index for competitor data (normalize to YYYY-MM-DD) + comp_date_to_idx = {} + for ci, d in enumerate(comp_dates): + comp_date_to_idx[str(d)[:10]] = ci + + # Map competitor TVL to our (n_dates, n_pools) grid + comp_tvl_grid = np.full((n_dates, n_pools), np.nan) + for j, pid in enumerate(pool_ids): + if pid not in comp_pool_ids: + continue + cj = comp_pool_ids.index(pid) + for t, date in enumerate(date_list): + date_str = str(pd.Timestamp(date))[:10] + if date_str in comp_date_to_idx: + ci = comp_date_to_idx[date_str] + val = comp_tvl_matrix[ci, cj] + if np.isfinite(val) and val > 0: + comp_tvl_grid[t, j] = val + + # Forward-fill / back-fill gaps per pool + for j in range(n_pools): + col = comp_tvl_grid[:, j] + mask = np.isfinite(col) + if mask.any() and not mask.all(): + s = pd.Series(col, index=date_list).ffill().bfill() + comp_tvl_grid[:, j] = s.values + + # Flag pools with no competitor data + has_comp = np.zeros(n_pools, dtype=bool) + for j in range(n_pools): + has_comp[j] = np.isfinite(comp_tvl_grid[:, j]).any() + + n_with = has_comp.sum() + print(f" Competitor TVL: {n_with}/{n_pools} pools with data") + + # Per-sample log(competitor_tvl), floor at $1 + raw_comp = np.array([ + comp_tvl_grid[day_idx[s], pool_idx[s]] + for s in range(n_samples)], dtype=np.float64) + + # For pools without data, impute with median of pools that have data + valid_comp = raw_comp[np.isfinite(raw_comp) & (raw_comp > 0)] + fallback_val = float(np.median(valid_comp)) if len(valid_comp) > 0 else 1e6 + raw_comp = np.where(np.isfinite(raw_comp) & (raw_comp > 0), + raw_comp, fallback_val) + log_comp_tvl = np.log(np.maximum(raw_comp, 1.0)).astype(np.float32) + print(f" Fallback comp TVL for missing pools: ${fallback_val:,.0f}") + for j in range(n_pools): + if not has_comp[j]: + print(f" No competitor data: {pool_ids[j][:16]}" + f" ({matched_clean[pool_ids[j]].get('tokens', '?')})") + else: + print(f" WARNING: no competitor TVL file at {comp_path}") + log_comp_tvl = np.full(n_samples, np.log(1e6), dtype=np.float32) + has_comp = np.zeros(n_pools, dtype=bool) + + # Token info + pool_tokens = [] + for pid in pool_ids: + toks = _parse_tokens(matched_clean[pid]["tokens"]) + tok_a = toks[0] + tok_b = toks[1] if len(toks) > 1 else toks[0] + pool_tokens.append((tok_a, tok_b)) + + removed_names = [feat_names[i] for i in sorted(remove_cols)] + print(f" Removed: {removed_names}") + print(f" Market features ({len(market_names)}): {market_names}") + + return { + "x_market": x_market, + "log_tvl": log_tvl, + "log_comp_tvl": log_comp_tvl, + "has_comp": has_comp, + "y_total": data["y_total"], + "pool_idx": pool_idx, + "day_idx": day_idx, + "sample_grid_days": data["sample_grid_days"], + "pool_coeffs": data["pool_coeffs"], + "pool_gas": data["pool_gas"], + "init_log_cadences": data["init_log_cadences"], + "n_pools": n_pools, + "n_market_feat": x_market.shape[1], + "pool_ids": pool_ids, + "pool_tokens": pool_tokens, + "market_names": market_names, + "x_mean": data["x_mean"], + "x_std": data["x_std"], + } + + +# ---- Model ---- + +def forward_mm(params, x_market, log_tvl, pool_idx, log_comp_tvl=None): + """MM forward pass → log(V_noise) per sample. + + K modes (checked in order): + - Observed: log_comp_tvl provided + params has "k_scale" (2,) + K = exp(k_scale[0] + k_scale[1] * log_comp_tvl) + - Per-pool: params contains "log_K" (n_pools,) + - Shared k_params: params contains "k_params" (3,) [legacy] + """ + log_alpha = params["log_alpha"] + gamma = params["gamma"] + + alpha_i = log_alpha[pool_idx] + tvl = jnp.exp(log_tvl) + + # K + if log_comp_tvl is not None and "k_scale" in params: + # Observed competitor TVL with learned scale/offset + k_s = params["k_scale"] + log_K = k_s[0] + k_s[1] * log_comp_tvl + K = jnp.exp(log_K) + elif log_comp_tvl is not None and "k_scale" not in params and "log_K" not in params: + # Observed competitor TVL, used directly as K + K = jnp.exp(log_comp_tvl) + elif "log_K" in params: + K = jnp.exp(params["log_K"][pool_idx]) + elif "k_params" in params: + # Legacy Binance-volume mode (kept for loading old models) + K = jnp.exp(params["k_params"][0]) + else: + K = jnp.exp(jnp.array(14.5)) # fallback + + # Market features: shared or per-pool gamma + if gamma.ndim == 2: + per_sample_gamma = gamma[pool_idx] + market_term = jnp.sum(x_market * per_sample_gamma, axis=1) + else: + market_term = x_market @ gamma + + log_saturation = log_tvl - jnp.log(K + tvl) + return alpha_i + market_term + log_saturation + + +def make_loss_fn(pool_coeffs, pool_gas, n_pools): + """Loss with PCHIP arb + MM noise.""" + from quantammsim.calibration.grid_interpolation import interpolate_pool_daily + + def loss_fn(params, x_market, log_tvl, log_comp_tvl, y_total, + sample_grid_days, pool_idx, l2_alpha, huber_delta): + log_cadence = params["log_cadence"] + + # V_arb from PCHIP + n_samples = x_market.shape[0] + log_v_arb = jnp.zeros(n_samples) + for i in range(n_pools): + v_arb_all = interpolate_pool_daily( + pool_coeffs[i], jnp.float64(log_cadence[i]), pool_gas[i]) + safe_days = jnp.clip(sample_grid_days, 0, v_arb_all.shape[0] - 1) + log_v_arb = jnp.where( + pool_idx == i, + jnp.log(jnp.maximum(v_arb_all[safe_days], 1e-10)), + log_v_arb) + + # V_noise from MM + log_v_noise = forward_mm( + params, x_market, log_tvl, pool_idx, + log_comp_tvl=log_comp_tvl) + + # V_total + log_v_total = jnp.logaddexp(log_v_arb, log_v_noise) + + # Huber + residual = log_v_total - y_total + abs_r = jnp.abs(residual) + huber = jnp.where( + abs_r <= huber_delta, + 0.5 * residual ** 2, + huber_delta * (abs_r - 0.5 * huber_delta)) + + # Per-pool equal weighting + pool_counts = jnp.zeros(n_pools).at[pool_idx].add( + jnp.ones_like(pool_idx, dtype=jnp.float32)) + active = (pool_counts > 0).astype(jnp.float32) + n_active = jnp.maximum(jnp.sum(active), 1.0) + pool_counts = jnp.maximum(pool_counts, 1.0) + pool_sums = jnp.zeros(n_pools).at[pool_idx].add(huber) + mean_loss = jnp.sum((pool_sums / pool_counts) * active) / n_active + + # L2 on gamma and log_alpha + reg = l2_alpha * ( + jnp.mean(params["gamma"] ** 2) + + jnp.mean(params["log_alpha"] ** 2) + ) + + return mean_loss + reg + + return jax.jit(jax.value_and_grad(loss_fn)) + + +# ---- Training ---- + +def train(params, data, grad_fn, n_epochs, lr, l2_alpha, huber_delta, + verbose=True): + """Adam training loop.""" + m = {k: jnp.zeros_like(v) for k, v in params.items()} + v = {k: jnp.zeros_like(v) for k, v in params.items()} + b1, b2, eps = 0.9, 0.999, 1e-8 + + x_market = jnp.array(data["x_market"]) + log_tvl = jnp.array(data["log_tvl"]) + log_comp_tvl = jnp.array(data["log_comp_tvl"]) + y_total = jnp.array(data["y_total"]) + sgd = jnp.array(data["sample_grid_days"]) + pidx = jnp.array(data["pool_idx"]) + + for epoch in range(n_epochs): + loss, grads = grad_fn( + params, x_market, log_tvl, log_comp_tvl, + y_total, sgd, pidx, l2_alpha, huber_delta) + + for k in params: + g = grads[k] + m[k] = b1 * m[k] + (1 - b1) * g + v[k] = b2 * v[k] + (1 - b2) * g ** 2 + m_hat = m[k] / (1 - b1 ** (epoch + 1)) + v_hat = v[k] / (1 - b2 ** (epoch + 1)) + params[k] = params[k] - lr * m_hat / (jnp.sqrt(v_hat) + eps) + + if verbose and (epoch % 200 == 0 or epoch == n_epochs - 1): + ev = evaluate(params, data) + if "k_scale" in params: + ks = np.array(params["k_scale"]) + k_str = f" k_s=[{ks[0]:.2f},{ks[1]:.3f}]" + elif "k_params" in params: + k_p = np.array(params["k_params"]) + k_str = f" k=[{k_p[0]:.2f},{k_p[1]:.3f},{k_p[2]:.3f}]" + else: + k_str = "" + K_med = float(np.median(list(ev["K_values"].values()))) + cad = np.exp(np.array(params["log_cadence"])) + print(f" epoch {epoch:5d} loss={float(loss):.4f}" + f" R²={ev['median_r2']:.3f}" + f" K_med=${K_med/1e6:.1f}M{k_str}" + f" cad=[{cad.min():.0f},{np.median(cad):.0f},{cad.max():.0f}]") + + return params + + +# ---- Evaluation ---- + +def evaluate(params, data): + """Per-pool R² and diagnostics.""" + from quantammsim.calibration.grid_interpolation import interpolate_pool_daily + + n_pools = data["n_pools"] + pool_idx = np.array(data["pool_idx"]) + sgd = np.array(data["sample_grid_days"]) + y = np.array(data["y_total"]) + log_cadence = np.array(params["log_cadence"]) + + # V_arb + v_arb = np.zeros(len(y)) + for i in range(n_pools): + mask = pool_idx == i + if not mask.any(): + continue + v_arb_all = np.array(interpolate_pool_daily( + data["pool_coeffs"][i], jnp.float64(log_cadence[i]), + data["pool_gas"][i])) + safe = np.clip(sgd[mask], 0, len(v_arb_all) - 1) + v_arb[mask] = v_arb_all[safe] + log_v_arb = np.log(np.maximum(v_arb, 1e-10)) + + log_v_noise = np.array(forward_mm( + params, jnp.array(data["x_market"]), + jnp.array(data["log_tvl"]), + jnp.array(data["pool_idx"]), + log_comp_tvl=jnp.array(data["log_comp_tvl"]))) + + log_v_total = np.logaddexp(log_v_arb, log_v_noise) + v_noise = np.exp(log_v_noise) + v_total = np.exp(log_v_total) + + r2s = {} + noise_shares = {} + for i in range(n_pools): + mask = pool_idx == i + if mask.sum() < 2: + continue + yt = y[mask] + pt = log_v_total[mask] + ss_res = np.sum((yt - pt) ** 2) + ss_tot = np.sum((yt - yt.mean()) ** 2) + r2s[data["pool_ids"][i]] = 1 - ss_res / max(ss_tot, 1e-10) + noise_shares[data["pool_ids"][i]] = float(np.median( + v_noise[mask] / v_total[mask])) + + # Per-pool median K + K_values = {} + if "k_scale" in params: + ks = np.array(params["k_scale"]) + for i in range(n_pools): + mask = pool_idx == i + if not mask.any(): + K_values[data["pool_ids"][i]] = 0 + continue + lc = data["log_comp_tvl"][mask] + log_K_i = ks[0] + ks[1] * lc + K_values[data["pool_ids"][i]] = float(np.exp(np.median(log_K_i))) + elif "log_K" in params: + for i in range(n_pools): + K_values[data["pool_ids"][i]] = float(np.exp(params["log_K"][i])) + elif "k_params" in params: + k_p = np.array(params["k_params"]) + for i in range(n_pools): + K_values[data["pool_ids"][i]] = float(np.exp(k_p[0])) + else: + # Observed K: compute from log_comp_tvl directly + for i in range(n_pools): + mask = pool_idx == i + if mask.any(): + K_values[data["pool_ids"][i]] = float( + np.exp(np.median(data["log_comp_tvl"][mask]))) + else: + K_values[data["pool_ids"][i]] = 1e6 + + return { + "r2s": r2s, + "noise_shares": noise_shares, + "K_values": K_values, + "median_r2": float(np.median(list(r2s.values()))), + } + + +def tvl_response_check(params, data): + """Print predicted noise at various TVL levels.""" + n_pools = data["n_pools"] + pool_idx = np.array(data["pool_idx"]) + + # Median market features per pool + print(f"\n TVL Response Check (per-pool median market features):") + print(f" {'Pool':>20s} {'K ($M)':>10s} {'TVL=100K':>10s}" + f" {'TVL=1M':>10s} {'TVL=10M':>10s} {'TVL=100M':>10s}" + f" {'TVL=1B':>10s} {'ε@1M':>6s} {'ε@100M':>6s}") + + tvl_test = [1e5, 1e6, 1e7, 1e8, 1e9] + + for i in range(n_pools): + pid = data["pool_ids"][i] + toks = data["pool_tokens"][i] + label = f"{toks[0]}/{toks[1]}" + mask = pool_idx == i + if mask.sum() == 0: + continue + + # Per-pool K (median) + if "k_scale" in params: + ks = np.array(params["k_scale"]) + lc = data["log_comp_tvl"][mask] + K_i = float(np.exp(np.median(ks[0] + ks[1] * lc))) + elif "log_K" in params: + K_i = float(np.exp(params["log_K"][i])) + elif "k_params" in params: + K_i = float(np.exp(np.array(params["k_params"])[0])) + else: + # Observed K directly from competitor TVL + K_i = float(np.exp(np.median(data["log_comp_tvl"][mask]))) + x_med = np.median(data["x_market"][mask], axis=0) + + gamma = np.array(params["gamma"]) + if gamma.ndim == 2: + market_term = float(x_med @ gamma[i]) + else: + market_term = float(x_med @ gamma) + log_alpha_i = float(params["log_alpha"][i]) + + vols = [] + for tvl in tvl_test: + log_sat = np.log(tvl) - np.log(K_i + tvl) + log_v = log_alpha_i + market_term + log_sat + vols.append(np.exp(log_v)) + + # Elasticity at 1M and 100M + eps_1m = K_i / (K_i + 1e6) + eps_100m = K_i / (K_i + 1e8) + + print(f" {label:>20s} ${K_i/1e6:>9.1f}" + + "".join(f" ${v:>9,.0f}" for v in vols) + + f" {eps_1m:>6.3f} {eps_100m:>6.3f}") + + +# ---- Main ---- + +def main(): + parser = argparse.ArgumentParser( + description=__doc__, + formatter_class=argparse.RawDescriptionHelpFormatter) + parser.add_argument("--epochs", type=int, default=3000) + parser.add_argument("--lr", type=float, default=3e-4) + parser.add_argument("--l2-alpha", type=float, default=1e-3) + parser.add_argument("--huber-delta", type=float, default=1.0) + parser.add_argument("--init-log-K", type=float, default=17.0, + help="Initial log(K) ~ log($24M)") + parser.add_argument("--shared-K", action="store_true", + help="Predict K from Binance volumes (3 shared params)") + parser.add_argument("--observed-K", action="store_true", + help="Use observed competitor TVL from DeFi Llama as K") + parser.add_argument("--per-pool-gamma", action="store_true", + help="Per-pool market feature coefficients") + parser.add_argument("--no-split", action="store_true") + parser.add_argument("--trend-windows", type=int, nargs="+", default=[7]) + parser.add_argument("--include-cross-pool", action="store_true") + parser.add_argument("--tune", type=int, default=0, + help="Optuna sweep (0 = single run)") + parser.add_argument("--save-artifact", default="results/mm_noise") + args = parser.parse_args() + + os.environ.setdefault("JAX_PLATFORMS", "cpu") + + print("=" * 70) + print("Michaelis-Menten Noise Model + Market Features") + print(f" epochs={args.epochs}, lr={args.lr}, l2={args.l2_alpha}") + print(f" init log(K)={args.init_log_K} (K=${np.exp(args.init_log_K):,.0f})") + print(f" per_pool_gamma={args.per_pool_gamma}") + print("=" * 70) + + matched_clean, option_c_clean = load_stage1() + + print("\nBuilding data...") + t0 = time.time() + data = build_mm_data(matched_clean, option_c_clean, + trend_windows=tuple(args.trend_windows), + include_cross_pool=args.include_cross_pool) + n_pools = data["n_pools"] + n_market = data["n_market_feat"] + n_samples = len(data["pool_idx"]) + print(f" {n_samples} samples, {n_pools} pools," + f" {n_market} market features, {time.time() - t0:.1f}s") + + if args.tune > 0: + run_optuna(data, args.tune) + return + + # Pool summary + pool_idx = data["pool_idx"] + for i, (pid, toks) in enumerate( + zip(data["pool_ids"], data["pool_tokens"])): + mask = pool_idx == i + n = mask.sum() + if n > 0: + med_tvl = np.exp(np.median(data["log_tvl"][mask])) + print(f" {pid[:16]} {toks[0]:>8s}/{toks[1]:<8s}" + f" {n:>4d} days TVL=${med_tvl:>12,.0f}") + + # Split + if args.no_split: + train_data = data + eval_data = None + else: + day_idx = data["day_idx"] + split_day = int(day_idx.max() * 0.7) + train_mask = day_idx <= split_day + eval_mask = day_idx > split_day + train_data = {k: v[train_mask] if isinstance(v, np.ndarray) + and v.shape[0] == n_samples else v + for k, v in data.items()} + eval_data = {k: v[eval_mask] if isinstance(v, np.ndarray) + and v.shape[0] == n_samples else v + for k, v in data.items()} + print(f"\n Split: {train_mask.sum()} train, {eval_mask.sum()} eval") + + # Init + if args.per_pool_gamma: + gamma_init = jnp.zeros((n_pools, n_market)) + else: + gamma_init = jnp.zeros(n_market) + + params = { + "log_alpha": jnp.zeros(n_pools), + "gamma": gamma_init, + "log_cadence": jnp.array(data["init_log_cadences"]), + } + if args.observed_K: + # K = competitor_tvl directly. No learned params for K. + # log_comp_tvl is passed as data, not as a parameter. + pass + elif args.shared_K: + params["k_params"] = jnp.array([args.init_log_K, 0.0, 0.0]) + else: + params["log_K"] = jnp.full(n_pools, args.init_log_K) + n_params = sum(v.size for v in params.values()) + print(f"\n Parameters: {n_params}" + f" (α: {n_pools}, K: {n_pools}," + f" γ: {gamma_init.size}, cadence: {n_pools})") + + # Warm-start gamma via Ridge (numpy, no sklearn) + print(" Warm-starting γ via Ridge on residuals...") + + def _ridge(X, y, alpha=1.0): + """Ridge regression: (X'X + αI)^-1 X'y.""" + XtX = X.T @ X + alpha * np.eye(X.shape[1]) + Xty = X.T @ y + return np.linalg.solve(XtX, Xty) + + x_trn = data["x_market"] if args.no_split else train_data["x_market"] + y_trn = data["y_total"] if args.no_split else train_data["y_total"] + if args.per_pool_gamma: + pidx = data["pool_idx"] if args.no_split else train_data["pool_idx"] + for i in range(n_pools): + mask = pidx == i + if mask.sum() < 5: + continue + # Add intercept column for warm-start + X_i = np.concatenate([x_trn[mask], np.ones((mask.sum(), 1))], 1) + w = _ridge(X_i, y_trn[mask]) + params["gamma"] = params["gamma"].at[i].set( + jnp.array(w[:-1].astype(np.float32))) + params["log_alpha"] = params["log_alpha"].at[i].set(float(w[-1])) + else: + X_all = np.concatenate([x_trn, np.ones((len(y_trn), 1))], 1) + w = _ridge(X_all, y_trn) + params["gamma"] = jnp.array(w[:-1].astype(np.float32)) + + # Loss + grad_fn = make_loss_fn(data["pool_coeffs"], data["pool_gas"], n_pools) + + print(f"\nTraining ({args.epochs} epochs)...") + t0 = time.time() + params = train(params, train_data, grad_fn, args.epochs, args.lr, + args.l2_alpha, args.huber_delta) + print(f" Training time: {time.time() - t0:.1f}s") + + # Evaluate + print("\n" + "=" * 70) + print("Results (train)") + print("=" * 70) + train_eval = evaluate(params, train_data) + print(f" Median R²: {train_eval['median_r2']:.4f}") + + if "k_scale" in params: + ks = np.array(params["k_scale"]) + print(f" Observed K: offset={ks[0]:.3f}, slope={ks[1]:.3f}") + elif "k_params" in params: + k_p = np.array(params["k_params"]) + print(f" k_params: k_0={k_p[0]:.2f}, k_min={k_p[1]:.4f}, k_max={k_p[2]:.4f}") + elif "log_K" in params: + K_med = float(np.exp(np.median(np.array(params["log_K"])))) + print(f" Per-pool K: median=${K_med/1e6:.1f}M") + else: + K_med = float(np.median(list(train_eval["K_values"].values()))) + print(f" Observed K (fixed): median=${K_med/1e6:.1f}M") + + print(f"\n {'Pool':>16s} {'Tokens':>16s} {'R²':>6s}" + f" {'Noise%':>7s} {'K ($M)':>10s}") + for pid in data["pool_ids"]: + i = data["pool_ids"].index(pid) + toks = data["pool_tokens"][i] + r2 = train_eval["r2s"].get(pid, float("nan")) + ns = train_eval["noise_shares"].get(pid, float("nan")) + K = train_eval["K_values"][pid] + print(f" {pid[:16]} {toks[0]:>8s}/{toks[1]:<6s}" + f" {r2:>6.3f} {ns*100:>6.1f}% ${K/1e6:>9.1f}") + + if eval_data is not None: + print("\n" + "=" * 70) + print("Results (eval)") + print("=" * 70) + eval_result = evaluate(params, eval_data) + print(f" Median R²: {eval_result['median_r2']:.4f}") + + # TVL response + tvl_response_check(params, data) + + # Gamma coefficients + gamma = np.array(params["gamma"]) + if gamma.ndim == 1: + print(f"\n Shared γ coefficients:") + for j, name in enumerate(data["market_names"]): + print(f" {name:>30s}: {gamma[j]:>8.4f}") + + # Save + if args.save_artifact: + os.makedirs(args.save_artifact, exist_ok=True) + save_dict = {k: np.array(v) for k, v in params.items()} + np.savez(os.path.join(args.save_artifact, "model.npz"), **save_dict) + meta = { + "model": "michaelis_menten", + "pool_ids": data["pool_ids"], + "pool_tokens": data["pool_tokens"], + "market_names": data["market_names"], + "n_pools": n_pools, + "n_market_feat": n_market, + "per_pool_gamma": args.per_pool_gamma, + "hparams": { + "epochs": args.epochs, "lr": args.lr, + "l2_alpha": args.l2_alpha, "huber_delta": args.huber_delta, + "init_log_K": args.init_log_K, + }, + } + with open(os.path.join(args.save_artifact, "meta.json"), "w") as f: + json.dump(meta, f, indent=2) + print(f"\n Saved: {args.save_artifact}/") + + +def run_optuna(data, n_trials): + """Optuna hyperparameter sweep for MM noise model.""" + import optuna + optuna.logging.set_verbosity(optuna.logging.WARNING) + + n_pools = data["n_pools"] + n_market = data["n_market_feat"] + n_samples = len(data["pool_idx"]) + + # 70/30 temporal split + day_idx = data["day_idx"] + split_day = int(day_idx.max() * 0.7) + train_mask = day_idx <= split_day + eval_mask = day_idx > split_day + train_data = {k: v[train_mask] if isinstance(v, np.ndarray) + and v.shape[0] == n_samples else v + for k, v in data.items()} + eval_data = {k: v[eval_mask] if isinstance(v, np.ndarray) + and v.shape[0] == n_samples else v + for k, v in data.items()} + print(f" Optuna split: {train_mask.sum()} train, {eval_mask.sum()} eval") + + def _ridge(X, y, alpha=1.0): + XtX = X.T @ X + alpha * np.eye(X.shape[1]) + return np.linalg.solve(XtX, X.T @ y) + + def objective(trial): + lr = trial.suggest_float("lr", 1e-4, 3e-2, log=True) + l2_alpha = trial.suggest_float("l2_alpha", 1e-5, 1e-1, log=True) + huber_delta = trial.suggest_categorical("huber_delta", [0.5, 1.0, 1.5]) + init_log_K = trial.suggest_float("init_log_K", 14.0, 20.0) + n_epochs = trial.suggest_categorical("n_epochs", [2000, 3000, 5000]) + per_pool_gamma = trial.suggest_categorical("per_pool_gamma", [True, False]) + if per_pool_gamma: + gamma_init = jnp.zeros((n_pools, n_market)) + else: + gamma_init = jnp.zeros(n_market) + + params = { + "log_alpha": jnp.zeros(n_pools), + "k_params": jnp.array([init_log_K, 0.0, 0.0]), + "gamma": gamma_init, + "log_cadence": jnp.array(data["init_log_cadences"]), + } + + # Warm-start gamma + x_trn = train_data["x_market"] + y_trn = train_data["y_total"] + if per_pool_gamma: + pidx = train_data["pool_idx"] + for i in range(n_pools): + mask_i = pidx == i + if mask_i.sum() < 5: + continue + X_i = np.concatenate([x_trn[mask_i], + np.ones((mask_i.sum(), 1))], 1) + w = _ridge(X_i, y_trn[mask_i]) + params["gamma"] = params["gamma"].at[i].set( + jnp.array(w[:-1].astype(np.float32))) + params["log_alpha"] = params["log_alpha"].at[i].set( + float(w[-1])) + else: + X_all = np.concatenate([x_trn, np.ones((len(y_trn), 1))], 1) + w = _ridge(X_all, y_trn) + params["gamma"] = jnp.array(w[:-1].astype(np.float32)) + + grad_fn = make_loss_fn(data["pool_coeffs"], data["pool_gas"], n_pools) + params = train(params, train_data, grad_fn, n_epochs, lr, + l2_alpha, huber_delta, verbose=False) + + # Eval + eval_result = evaluate(params, eval_data) + med_r2 = eval_result["median_r2"] + + K_med = float(np.median([v for v in eval_result["K_values"].values()])) + k_p = np.array(params["k_params"]) + pp_str = "pp" if per_pool_gamma else "sh" + print(f" Trial {trial.number}: eval={med_r2:.4f}" + f" K_med=${K_med/1e6:.1f}M" + f" k=[{k_p[0]:.1f},{k_p[1]:.3f},{k_p[2]:.3f}]" + f" {pp_str} ep={n_epochs} lr={lr:.1e} l2={l2_alpha:.1e}" + f" hub={huber_delta}") + + # Save every trial + trial_dir = os.path.join("results", "mm_noise", "trials", + f"trial_{trial.number:04d}") + os.makedirs(trial_dir, exist_ok=True) + save_dict = {k: np.array(v) for k, v in params.items()} + np.savez(os.path.join(trial_dir, "model.npz"), **save_dict) + meta = { + "pool_ids": data["pool_ids"], + "pool_tokens": data["pool_tokens"], + "market_names": data["market_names"], + "n_pools": n_pools, + "n_market_feat": n_market, + "per_pool_gamma": per_pool_gamma, + "eval_r2": med_r2, + "hparams": { + "lr": lr, "l2_alpha": l2_alpha, "huber_delta": huber_delta, + "init_log_K": init_log_K, "n_epochs": n_epochs, + "per_pool_gamma": per_pool_gamma, + }, + } + with open(os.path.join(trial_dir, "meta.json"), "w") as f: + json.dump(meta, f, indent=2) + + return med_r2 + + study = optuna.create_study(direction="maximize") + study.optimize(objective, n_trials=n_trials) + + print(f"\n{'='*70}") + print(f"Optuna Results (MM noise)") + print(f"{'='*70}") + print(f" Best eval R²: {study.best_value:.4f}") + print(f" Best params:") + for k, v in sorted(study.best_params.items()): + print(f" {k}: {v}") + + trials = sorted(study.trials, key=lambda t: t.value if t.value else -999, + reverse=True) + print(f"\n Top 10:") + for t in trials[:10]: + if t.value is not None: + print(f" #{t.number}: eval={t.value:.4f} {t.params}") + + # Copy best to top-level + best_dir = os.path.join("results", "mm_noise", "trials", + f"trial_{study.best_trial.number:04d}") + if os.path.exists(os.path.join(best_dir, "model.npz")): + import shutil + for fn in ("model.npz", "meta.json"): + shutil.copy2(os.path.join(best_dir, fn), + os.path.join("results", "mm_noise", fn)) + print(f"\n Copied best trial ({study.best_trial.number})" + f" to results/mm_noise/") + + return study + + +if __name__ == "__main__": + main() diff --git a/scripts/run_period_sweep.sh b/scripts/run_period_sweep.sh new file mode 100644 index 0000000..0cfdfa7 --- /dev/null +++ b/scripts/run_period_sweep.sh @@ -0,0 +1,117 @@ +#!/bin/bash +# Full sweep: objectives × periods × robust temps × overfitting penalties × 400 trials +# Usage: bash scripts/run_period_sweep.sh +# Monitor: tail -5 /tmp/tune_*.log +# Results: results/sweep/ + +set -e +source ~/miniconda3/etc/profile.d/conda.sh && conda activate qsim_reclamm_public + +TRIALS=400 +MAX_PARALLEL=8 +COMMON="python scripts/tune_reclamm_calibrated_noise.py --noise-model mm_observed --artifact-dir results/mm_noise --n-trials $TRIALS" + +OBJECTIVES=( + daily_log_sharpe + daily_log_sharpe_excess + fee_revenue_over_value + returns_over_hodl + calmar + sterling + weekly_rovar +) + +# period_name start_date end_date(train+val) end_test_date [val_fraction] +PERIODS=( + "bull_2023 2023-06-01 2024-06-01 2025-06-01" + "default_2024 2024-06-01 2025-06-01 2026-03-01" + "recent_2025 2025-01-01 2025-09-01 2026-03-01" + "long_2021 2021-06-01 2025-01-01 2026-03-01 0.4" +) + +# Robust temperatures: "" = standard mean, otherwise --robust-temperature X +# Lower = more pessimistic (0.1 = very robust, 0.01 = near worst-case). +ROBUST_TEMPS=("" "0.5" "1.0" "0.1" "0.01") + +ROBUST_TEMPS=("") + + +# Overfitting penalty: "" = default (0.2), otherwise --overfitting-penalty X +# Higher = stronger train-vs-val regularization. penalty=1 means objective +# becomes val_score when train > val; penalty>1 rewards val > train. +OVERFITTING_PENALTIES=("" "1.0" "5.0") + +OVERFITTING_PENALTIES=("" "5.0") + + +OUTDIR="results/sweep" +mkdir -p "$OUTDIR" + +wait_for_slot() { + while [ "$(jobs -rp | wc -l)" -ge "$MAX_PARALLEL" ]; do + sleep 10 + done +} + +N=0 +for period_line in "${PERIODS[@]}"; do + read -r period_name start_date end_date end_test_date val_fraction <<< "$period_line" + val_flag="" + if [ -n "$val_fraction" ]; then + val_flag="--val-fraction $val_fraction" + fi + for obj in "${OBJECTIVES[@]}"; do + for temp in "${ROBUST_TEMPS[@]}"; do + for penalty in "${OVERFITTING_PENALTIES[@]}"; do + # Build tag and flags + tag="${obj}" + robust_flag="" + penalty_flag="" + if [ -n "$temp" ]; then + tag="${tag}_robust${temp}" + robust_flag="--robust-temperature $temp" + fi + if [ -n "$penalty" ]; then + tag="${tag}_penalty${penalty}" + penalty_flag="--overfitting-penalty $penalty" + fi + tag="${tag}_${period_name}" + + logfile="/tmp/tune_${tag}.log" + outfile="${OUTDIR}/${tag}.json" + + # Skip if result already exists + if [ -f "$outfile" ]; then + echo "[$N] Skipping (exists): ${tag}" + N=$((N + 1)) + continue + fi + + wait_for_slot + + echo "[$N] Launching: ${tag}" + $COMMON --objective "$obj" \ + --start-date "${start_date} 00:00:00" \ + --end-date "${end_date} 00:00:00" \ + --end-test-date "${end_test_date} 00:00:00" \ + $robust_flag $penalty_flag $val_flag \ + --output "$outfile" \ + > "$logfile" 2>&1 & + + N=$((N + 1)) + done + done + done +done + +echo "" +echo "$N jobs total (existing results skipped)" +echo "Max parallel: $MAX_PARALLEL" +echo "" +echo "Monitor: tail -5 /tmp/tune_*.log" +echo "Results: ls $OUTDIR/" +echo "Summary: grep -A3 'Best trial' /tmp/tune_*.log" +echo "" +echo "Waiting for all jobs to finish..." +wait +echo "Done." diff --git a/scripts/run_pr_sweep.py b/scripts/run_pr_sweep.py new file mode 100644 index 0000000..b737e4e --- /dev/null +++ b/scripts/run_pr_sweep.py @@ -0,0 +1,505 @@ +#!/usr/bin/env python3 +"""Price Ratio sweep for reClAMM pools. + +Sweeps PR with fixed margin/shift over a single run period. +Plots RoH, fee revenue, and final value. +Optionally runs across multiple periods and produces a summary heatmap. + +Usage: + # Single period + python scripts/run_pr_sweep.py --tokens COW ETH --start 2025-01-01 --end 2026-01-01 + + # Multi-period with heatmap + python scripts/run_pr_sweep.py --tokens COW ETH --multi-period +""" + +import argparse +import json +import os + +import jax.numpy as jnp +import matplotlib +matplotlib.use("Agg") +import matplotlib.pyplot as plt +import numpy as np +from datetime import datetime + +from quantammsim.runners.jax_runners import do_run_on_historic_data +from quantammsim.pools.reCLAMM.reclamm_reserves import set_blessed_arb + + +BG = "#162536" +TC = "#E6CE97" + +DEFAULT_PRS = [1.01, 1.1, 1.2, 1.4, 1.6, 1.8, 2.0, 2.2, 2.5, 3.0, 3.5, 4.0, 5.0, 6.0, 7.5, 10.0] + + +def _style_ax(ax): + ax.set_facecolor(BG) + ax.tick_params(colors=TC) + for s in ax.spines.values(): + s.set_color(TC) + s.set_alpha(0.3) + ax.spines["top"].set_visible(False) + ax.spines["right"].set_visible(False) + ax.grid(True, alpha=0.15, color=TC) + + +def build_noise_arrays(pool_id, tokens, start, end): + """Build or load cached MM noise arrays.""" + from quantammsim.calibration.noise_model_arrays import build_mm_simulator_arrays + + cache_dir = os.path.join("results", "mm_noise", "_sim_arrays") + os.makedirs(cache_dir, exist_ok=True) + arrays_path = os.path.join(cache_dir, f"{pool_id}_{start}_{end}_mm.npz") + + if not os.path.exists(arrays_path): + print(f" Building noise arrays for {pool_id} {start} -> {end}...") + arrays = build_mm_simulator_arrays( + token_a=tokens[0], token_b=tokens[1], + start_date=start, end_date=end, + mm_artifact_dir="results/mm_noise", + competitor_tvl_path="results/competitor_tvl/competitor_tvl.npz", + pool_id=pool_id, + ) + np.savez(arrays_path, + noise_base=arrays["noise_base"], + competitor_tvl=arrays["competitor_tvl"]) + return arrays_path + + +def run_single_period(args, start, end): + """Run PR sweep for a single period. Returns dict of metrics per PR.""" + tokens = args.tokens + margin = args.margin + shift = args.shift + price_ratios = np.array(args.prs or DEFAULT_PRS) + + # Noise arrays + noise_path = None + if args.noise_model == "mm_observed" and args.pool_id: + noise_path = build_noise_arrays(args.pool_id, tokens, start, end) + + fp = { + "rule": "reclamm", + "tokens": tokens, + "startDateString": f"{start} 00:00:00", + "endDateString": f"{end} 00:00:00", + "initial_pool_value": args.initial_pool_value, + "do_arb": True, + "arb_frequency": args.arb_frequency, + "fees": args.fees, + "gas_cost": args.gas_cost, + "arb_fees": 0.0, + "protocol_fee_split": 0.25, + "noise_trader_ratio": 0.0, + "reclamm_interpolation_method": "geometric", + "reclamm_centeredness_scaling": False, + "reclamm_use_shift_exponent": True, + } + if noise_path: + fp["noise_model"] = "mm_observed" + fp["noise_arrays_path"] = noise_path + else: + fp["noise_trader_ratio"] = 0.0 + + params_list = [ + { + "price_ratio": jnp.array(float(pr)), + "centeredness_margin": jnp.array(margin), + "shift_exponent": jnp.array(shift), + } + for pr in price_ratios + ] + + tok_str = "/".join(tokens) + print(f" PR sweep: {tok_str}, {start} -> {end}, {len(price_ratios)} PRs") + + results = do_run_on_historic_data( + run_fingerprint=fp, params=params_list, verbose=False, + ) + + # Compute metrics + rohs, fee_revs, final_vals = [], [], [] + hodl_final = None + + for i, pr in enumerate(price_ratios): + out = results[i] + val = np.array(out["value"]) + prices = np.array(out["prices"]) + reserves_0 = out["reserves"][0] + hodl = np.sum(np.array(reserves_0) * prices, axis=1) + if hodl_final is None: + hodl_final = float(hodl[-1]) + + roh = val[-1] / hodl[-1] - 1 + fr = np.array(out.get("fee_revenue", np.zeros(len(val)))) + + rohs.append(roh) + fee_revs.append(float(fr.sum())) + final_vals.append(float(val[-1])) + + # Run a base Balancer 50/50 pool as comparator (arb-only, same fees/gas) + bal_fp = { + "rule": "balancer", + "tokens": tokens, + "startDateString": f"{start} 00:00:00", + "endDateString": f"{end} 00:00:00", + "initial_pool_value": args.initial_pool_value, + "do_arb": True, + "arb_frequency": args.arb_frequency, + "fees": args.fees, + "gas_cost": args.gas_cost, + "arb_fees": 0.0, + "protocol_fee_split": 0.25, + "noise_trader_ratio": 0.0, + } + bal_params = {"initial_weights_logits": jnp.array([0.0, 0.0])} + print(f" Running Balancer 50/50 comparator...") + bal_result = do_run_on_historic_data( + run_fingerprint=bal_fp, params=bal_params, verbose=False, + ) + bal_val = np.array(bal_result["value"]) + bal_hodl = np.sum(np.array(bal_result["reserves"][0]) * np.array(bal_result["prices"]), axis=1) + bal_roh = bal_val[-1] / bal_hodl[-1] - 1 + bal_fee = float(np.array(bal_result.get("fee_revenue", np.zeros(1))).sum()) + print(f" Balancer 50/50: RoH={bal_roh:+.2%}, fee=${bal_fee:,.0f}") + + return { + "price_ratios": price_ratios, + "rohs": rohs, + "fee_revs": fee_revs, + "final_vals": final_vals, + "hodl_final": hodl_final, + "balancer_roh": bal_roh, + "balancer_fee": bal_fee, + "balancer_final": float(bal_val[-1]), + "start": start, + "end": end, + } + + +def plot_single_period(data, args, outpath): + """3-panel plot for a single period.""" + price_ratios = data["price_ratios"] + rohs = data["rohs"] + fee_revs = data["fee_revs"] + final_vals = data["final_vals"] + hodl_final = data["hodl_final"] + start, end = data["start"], data["end"] + onchain_pr = args.onchain_pr + tok_str = "/".join(args.tokens) + + fig, axes = plt.subplots(1, 2, figsize=(14, 5)) + for ax in axes: + _style_ax(ax) + + # Panel 1: RoH vs PR + axes[0].plot(price_ratios, [r * 100 for r in rohs], + "o-", color="#e74c3c", markersize=5, label="reClAMM") + axes[0].axhline(0, color="white", ls=":", alpha=0.3) + bal_roh = data.get("balancer_roh") + if bal_roh is not None: + axes[0].axhline(bal_roh * 100, color="#3498db", ls="-", alpha=0.7, + label=f"Balancer 50/50 ({bal_roh*100:+.1f}%)") + if onchain_pr: + axes[0].axvline(onchain_pr, color="#f39c12", ls="--", alpha=0.7, + label=f"On-chain PR={onchain_pr}") + selected_pr = getattr(args, "selected_pr", None) + if selected_pr: + axes[0].axvline(selected_pr, color="#9b59b6", ls="-.", alpha=0.8, linewidth=2, + label=f"Selected PR={selected_pr:.2f}") + best_pr_idx = int(np.argmax(rohs)) + best_pr = price_ratios[best_pr_idx] + axes[0].axvline(best_pr, color="#2ecc71", ls="-", alpha=0.8, linewidth=2, + label=f"Best PR={best_pr:.1f} ({rohs[best_pr_idx]*100:+.1f}%)") + axes[0].set_xlabel("Price Ratio", color=TC) + axes[0].set_ylabel("Returns over HODL (%)", color=TC) + axes[0].set_title("RoH vs Price Ratio", color=TC) + axes[0].legend(fontsize=8, facecolor=BG, edgecolor=TC, labelcolor=TC) + + # Panel 2: Fee revenue + axes[1].plot(price_ratios, [f / 1000 for f in fee_revs], + "o-", color="#2ecc71", markersize=5) + if onchain_pr: + axes[1].axvline(onchain_pr, color="#f39c12", ls="--", alpha=0.7) + if selected_pr: + axes[1].axvline(selected_pr, color="#9b59b6", ls="-.", alpha=0.8, linewidth=2) + axes[1].set_xlabel("Price Ratio", color=TC) + axes[1].set_ylabel("Fee Revenue ($K)", color=TC) + axes[1].set_title("Cumulative Fee Revenue", color=TC) + + fig.suptitle( + f"{tok_str} reCLAMM PR Sweep (margin={args.margin}, shift={args.shift}, " + f"gas=${args.gas_cost})\n{start} -> {end}", + color=TC, fontsize=12, fontweight="bold", + ) + fig.patch.set_facecolor(BG) + plt.tight_layout() + fig.savefig(outpath, dpi=200, bbox_inches="tight", facecolor=BG) + plt.close() + print(f" Saved: {outpath}") + + +def plot_heatmap(all_data, args, outpath): + """Heatmap: RoH as function of PR (x) and start date (y).""" + tok_str = "/".join(args.tokens) + price_ratios = all_data[0]["price_ratios"] + n_pr = len(price_ratios) + n_periods = len(all_data) + + roh_matrix = np.zeros((n_periods, n_pr)) + fee_matrix = np.zeros((n_periods, n_pr)) + period_labels = [] + + for j, data in enumerate(all_data): + roh_matrix[j, :] = data["rohs"] + fee_matrix[j, :] = data["fee_revs"] + period_labels.append(f"{data['start']} -> {data['end']}") + + fig, axes = plt.subplots(1, 2, figsize=(18, max(4, n_periods * 0.6 + 2))) + for ax in axes: + ax.set_facecolor(BG) + ax.tick_params(colors=TC) + + # RoH heatmap + vmax = max(abs(roh_matrix.min()), abs(roh_matrix.max())) + im0 = axes[0].imshow( + roh_matrix * 100, aspect="auto", cmap="RdYlGn", vmin=-vmax * 100, vmax=vmax * 100, + ) + axes[0].set_xticks(range(n_pr)) + axes[0].set_xticklabels([f"{p:.1f}" if p < 10 else f"{p:.0f}" for p in price_ratios], + rotation=45, fontsize=8, color=TC) + axes[0].set_yticks(range(n_periods)) + axes[0].set_yticklabels(period_labels, fontsize=8, color=TC) + axes[0].set_xlabel("Price Ratio", color=TC) + axes[0].set_title("Returns over HODL (%)", color=TC, fontsize=12) + cb0 = fig.colorbar(im0, ax=axes[0], shrink=0.8) + cb0.ax.tick_params(colors=TC) + + # Annotate cells + for j in range(n_periods): + for i in range(n_pr): + val = roh_matrix[j, i] * 100 + color = "black" if abs(val) < vmax * 50 else "white" + axes[0].text(i, j, f"{val:+.1f}", ha="center", va="center", + fontsize=6, color=color) + + # Mark on-chain PR with white cross-hatching + if args.onchain_pr: + pr_arr = np.array(price_ratios) + nearest = np.argmin(np.abs(np.log(pr_arr) - np.log(args.onchain_pr))) + for j in range(n_periods): + axes[0].add_patch(plt.Rectangle( + (nearest - 0.5, j - 0.5), 1, 1, + fill=False, edgecolor="white", linewidth=2, + hatch="//", label="On-chain PR" if j == 0 else None, + )) + + # Mark best PR per row with green border + for j in range(n_periods): + best_i = int(np.argmax(roh_matrix[j, :])) + axes[0].add_patch(plt.Rectangle( + (best_i - 0.5, j - 0.5), 1, 1, + fill=False, edgecolor="#2ecc71", linewidth=3, + label="Best RoH" if j == 0 else None, + )) + + # Best overall PR: two metrics + # 1) Geometric mean = product of (1+RoH) — penalises variance, correct for compounding + compounded = np.prod(1 + roh_matrix, axis=0) + geo_mean_roh = np.sign(compounded) * np.abs(compounded) ** (1.0 / n_periods) - 1 + best_geo_i = int(np.argmax(geo_mean_roh)) + # 2) Median — robust to outlier months + median_roh = np.median(roh_matrix, axis=0) + best_median_i = int(np.argmax(median_roh)) + + # Print both for diagnostics + print(f" Best geo-mean PR={price_ratios[best_geo_i]:.2f} " + f"(geo mean {geo_mean_roh[best_geo_i]*100:+.3f}%)") + print(f" Best median PR={price_ratios[best_median_i]:.2f} " + f"(median {median_roh[best_median_i]*100:+.3f}%)") + for i, pr in enumerate(price_ratios): + print(f" PR={pr:5.2f}: geo={geo_mean_roh[i]*100:+.3f}% " + f"median={median_roh[i]*100:+.3f}%") + + # Use median for the column shading (robust to outlier months) + best_overall_i = best_median_i + best_overall_pr = price_ratios[best_overall_i] + best_median = median_roh[best_overall_i] + for j in range(n_periods): + axes[0].add_patch(plt.Rectangle( + (best_overall_i - 0.5, j - 0.5), 1, 1, + fill=True, facecolor="#3498db", alpha=0.25, edgecolor="#3498db", + linewidth=2, linestyle="--", + label=(f"Best overall PR={best_overall_pr:.2f} " + f"(median {best_median*100:+.2f}%)") if j == 0 else None, + )) + + # Legend for markings + from matplotlib.patches import Patch + legend_elements = [] + if args.onchain_pr: + legend_elements.append(Patch(facecolor="none", edgecolor="white", + hatch="//", label="On-chain PR")) + legend_elements.append(Patch(facecolor="none", edgecolor="#2ecc71", + linewidth=3, label="Best RoH (per period)")) + legend_elements.append(Patch(facecolor="#3498db", alpha=0.25, edgecolor="#3498db", + linestyle="--", linewidth=2, + label=f"Best overall PR={best_overall_pr:.2f} " + f"(median {best_median*100:+.2f}%)")) + axes[0].legend(handles=legend_elements, loc="lower right", fontsize=7, + facecolor=BG, edgecolor=TC, labelcolor=TC) + + # Fee revenue heatmap + im1 = axes[1].imshow( + fee_matrix / 1000, aspect="auto", cmap="YlGn", + ) + axes[1].set_xticks(range(n_pr)) + axes[1].set_xticklabels([f"{p:.1f}" if p < 10 else f"{p:.0f}" for p in price_ratios], + rotation=45, fontsize=8, color=TC) + axes[1].set_yticks(range(n_periods)) + axes[1].set_yticklabels(period_labels, fontsize=8, color=TC) + axes[1].set_xlabel("Price Ratio", color=TC) + axes[1].set_title("Fee Revenue ($K)", color=TC, fontsize=12) + cb1 = fig.colorbar(im1, ax=axes[1], shrink=0.8) + cb1.ax.tick_params(colors=TC) + + for j in range(n_periods): + for i in range(n_pr): + val = fee_matrix[j, i] / 1000 + axes[1].text(i, j, f"{val:.0f}", ha="center", va="center", + fontsize=6, color="black") + + if args.onchain_pr: + for j in range(n_periods): + axes[1].add_patch(plt.Rectangle( + (nearest - 0.5, j - 0.5), 1, 1, + fill=False, edgecolor="white", linewidth=2, + hatch="//", + )) + # Best fee revenue per row + for j in range(n_periods): + best_i = int(np.argmax(fee_matrix[j, :])) + axes[1].add_patch(plt.Rectangle( + (best_i - 0.5, j - 0.5), 1, 1, + fill=False, edgecolor="#2ecc71", linewidth=3, + )) + # Best overall PR for fee revenue: maximise total fee across all periods + total_fees = np.sum(fee_matrix, axis=0) + best_fee_overall_i = int(np.argmax(total_fees)) + best_fee_overall_pr = price_ratios[best_fee_overall_i] + for j in range(n_periods): + axes[1].add_patch(plt.Rectangle( + (best_fee_overall_i - 0.5, j - 0.5), 1, 1, + fill=True, facecolor="#3498db", alpha=0.25, edgecolor="#3498db", + linewidth=2, linestyle="--", + )) + + fig.suptitle( + f"{tok_str} reClAMM: PR x Period Summary (margin={args.margin}, " + f"shift={args.shift}, gas=${args.gas_cost})", + color=TC, fontsize=13, fontweight="bold", + ) + fig.patch.set_facecolor(BG) + plt.tight_layout() + fig.savefig(outpath, dpi=200, bbox_inches="tight", facecolor=BG) + plt.close() + print(f" Saved heatmap: {outpath}") + + +def main(): + p = argparse.ArgumentParser(description=__doc__, + formatter_class=argparse.RawDescriptionHelpFormatter) + p.add_argument("--tokens", nargs=2, default=["COW", "ETH"]) + p.add_argument("--start", default=None, help="Start date (YYYY-MM-DD)") + p.add_argument("--end", default=None, help="End date (YYYY-MM-DD)") + p.add_argument("--multi-period", action="store_true", + help="Run multiple periods starting at regular intervals") + p.add_argument("--period-months", type=int, default=6, + help="Length of each period in months (default: 6)") + p.add_argument("--margin", type=float, default=0.5) + p.add_argument("--shift", type=float, default=0.1) + p.add_argument("--fees", type=float, default=0.003) + p.add_argument("--gas-cost", type=float, default=3.0) + p.add_argument("--arb-frequency", type=int, default=3) + p.add_argument("--initial-pool-value", type=float, default=600_000.0) + p.add_argument("--pool-id", default="0xd321300ef77067") + p.add_argument("--noise-model", default="mm_observed", + choices=["mm_observed", "none"]) + p.add_argument("--onchain-pr", type=float, default=None, + help="On-chain PR to mark on plot") + p.add_argument("--selected-pr", type=float, default=None, + help="Optimiser-selected PR to mark on plot") + p.add_argument("--prs", type=float, nargs="+", default=None) + p.add_argument("--output-dir", default="results/cow_sweep/pr_sweeps") + args = p.parse_args() + + os.makedirs(args.output_dir, exist_ok=True) + tok_tag = "_".join(args.tokens) + + if args.multi_period: + from dateutil.relativedelta import relativedelta + from datetime import date + period_months = args.period_months + step_months = 1 + starts = [] + d = date(2024, 1, 1) + data_end = date(2026, 4, 1) + while d + relativedelta(months=period_months) <= data_end: + starts.append(d.isoformat()) + d += relativedelta(months=step_months) + periods = [(s, (datetime.strptime(s, "%Y-%m-%d") + + relativedelta(months=period_months)).strftime("%Y-%m-%d")) + for s in starts] + + all_data = [] + for start, end in periods: + print(f"\n{'='*60}") + try: + data = run_single_period(args, start, end) + all_data.append(data) + + outpath = os.path.join( + args.output_dir, + f"pr_sweep_{tok_tag}_{start}_{end}_{args.period_months}mo_m{args.margin}_s{args.shift}.png", + ) + plot_single_period(data, args, outpath) + + # Print table + prs = data["price_ratios"] + print(f" {'PR':>8s} {'RoH':>8s} {'Fee $K':>8s} {'Final $K':>10s}") + for i, pr in enumerate(prs): + print(f" {pr:>8.2f} {data['rohs'][i]:>+7.2%} " + f"{data['fee_revs'][i]/1000:>8.0f} " + f"{data['final_vals'][i]/1000:>10.0f}") + except Exception as e: + print(f" FAILED: {e}") + + if all_data: + heatmap_path = os.path.join( + args.output_dir, + f"pr_heatmap_{tok_tag}_{args.period_months}mo_m{args.margin}_s{args.shift}.png", + ) + plot_heatmap(all_data, args, heatmap_path) + + elif args.start and args.end: + data = run_single_period(args, args.start, args.end) + outpath = os.path.join( + args.output_dir, + f"pr_sweep_{tok_tag}_{args.start}_{args.end}_m{args.margin}_s{args.shift}.png", + ) + plot_single_period(data, args, outpath) + + print(f"\n{'PR':>8s} {'RoH':>8s} {'Fee $K':>8s} {'Final $K':>10s}") + print("-" * 40) + for i, pr in enumerate(data["price_ratios"]): + print(f"{pr:>8.2f} {data['rohs'][i]:>+7.2%} " + f"{data['fee_revs'][i]/1000:>8.0f} " + f"{data['final_vals'][i]/1000:>10.0f}") + else: + print("ERROR: provide --start/--end or --multi-period") + + +if __name__ == "__main__": + main() diff --git a/scripts/run_structural_top50.py b/scripts/run_structural_top50.py new file mode 100644 index 0000000..b46aece --- /dev/null +++ b/scripts/run_structural_top50.py @@ -0,0 +1,448 @@ +"""Fit the structural mixture model and plot predicted vs actual for top 50 pools. + +Uses the cached panel (last 90 days), fits with vanilla SVI, then generates +paginated plots showing V_arb + V_noise decomposition and predicted vs actual. +""" + +import json +import os +import sys +from datetime import date, timedelta + +import matplotlib +matplotlib.use("Agg") +import matplotlib.pyplot as plt +import numpy as np +import pandas as pd + +# ---- Config ---- +PANEL_CACHE = os.path.join( + os.path.dirname(os.path.dirname(__file__)), + "local_data", "noise_calibration", "panel.parquet", +) +OUTPUT_DIR = os.path.join( + os.path.dirname(os.path.dirname(__file__)), + "results", "structural_hierarchical", +) +OUTPUT_JSON = os.path.join(OUTPUT_DIR, "structural_fit.json") +TRAIN_DAYS = 90 +SVI_STEPS = 20_000 +SVI_LR = 1e-3 +NUM_SAMPLES = 1000 +SEED = 42 +TOP_N = 50 + + +def load_and_filter_panel(): + """Load cached panel, filter to 90 days, keep pools with >= 10 obs.""" + panel = pd.read_parquet(PANEL_CACHE) + max_date = panel["date"].max() + if not isinstance(max_date, date): + max_date = pd.Timestamp(max_date).date() + cutoff = max_date - timedelta(days=TRAIN_DAYS) + panel = panel[ + panel["date"].apply( + lambda d: d >= cutoff if isinstance(d, date) + else pd.Timestamp(d).date() >= cutoff + ) + ].copy() + + if "log_tvl_lag1" not in panel.columns: + panel = panel.sort_values(["pool_id", "date"]).reset_index(drop=True) + panel["log_tvl_lag1"] = panel.groupby("pool_id")["log_tvl"].shift(1) + panel = panel.dropna(subset=["log_tvl_lag1"]).reset_index(drop=True) + + pool_counts = panel.groupby("pool_id").size() + valid = pool_counts[pool_counts >= 10].index + panel = panel[panel["pool_id"].isin(valid)].copy() + + print(f"Panel: {len(panel)} obs, {panel['pool_id'].nunique()} pools, " + f"{cutoff} to {max_date}") + return panel + + +def fit_structural(panel): + """Run SVI on the structural mixture model.""" + os.environ.setdefault("JAX_PLATFORMS", "cpu") + + from quantammsim.noise_calibration.covariate_encoding import ( + encode_covariates_structural, + ) + from quantammsim.noise_calibration.model import structural_noise_model + from quantammsim.noise_calibration.inference import run_svi + from quantammsim.noise_calibration.postprocessing import ( + check_convergence, extract_structural_params, + ) + from quantammsim.noise_calibration.output import generate_output_json + + import numpyro + numpyro.enable_x64() + + # Load gas costs for mainnet from CSV if available + gas_csv = os.path.join( + os.path.dirname(os.path.dirname(__file__)), + "results", "formula_vs_real", "mainnet_gas_cost_daily.csv", + ) + gas_arr = None + if os.path.exists(gas_csv): + gas_df = pd.read_csv(gas_csv) + # CSV has columns: unix (ms timestamp), USD (gas cost) + gas_df["date"] = pd.to_datetime(gas_df["unix"], unit="ms").dt.date + gas_lookup = dict(zip(gas_df["date"], gas_df["USD"])) + + # Build per-observation gas array + gas_vals = [] + for _, row in panel.iterrows(): + d = row["date"] + if not isinstance(d, date): + d = pd.Timestamp(d).date() + chain = row["chain"] + if chain == "MAINNET" and d in gas_lookup: + gas_vals.append(gas_lookup[d]) + elif chain == "MAINNET": + gas_vals.append(1.0) # median fallback + else: + # L2 chains: ~$0.005 + from quantammsim.noise_calibration.constants import GAS_COSTS + gas_vals.append(GAS_COSTS.get(chain, 0.005)) + gas_arr = np.array(gas_vals, dtype=np.float64) + print(f"Gas costs: loaded ({len(gas_lookup)} mainnet days from CSV)") + else: + print("Gas costs: using defaults (no mainnet CSV)") + + data = encode_covariates_structural(panel, gas=gas_arr) + + print(f"\nFitting structural model: {SVI_STEPS} SVI steps, lr={SVI_LR}") + samples, elbo_losses = run_svi( + data, + num_steps=SVI_STEPS, + lr=SVI_LR, + seed=SEED, + num_samples=NUM_SAMPLES, + model_fn=structural_noise_model, + ) + convergence = check_convergence(elbo_losses, method="svi") + + pool_params = extract_structural_params(samples, data) + + # Save output JSON + os.makedirs(OUTPUT_DIR, exist_ok=True) + inference_config = { + "method": "svi", "svi_steps": SVI_STEPS, + "svi_lr": SVI_LR, "num_samples": NUM_SAMPLES, + } + generate_output_json( + pool_params, samples, data, convergence, + OUTPUT_JSON, inference_config, + ) + + return samples, data, pool_params, elbo_losses + + +def compute_predictions(samples, data, panel): + """Compute per-observation predicted V_arb and V_noise.""" + from quantammsim.noise_calibration.formula_arb import ( + formula_arb_volume_daily_jax, + ) + import jax.numpy as jnp + + sample_dict = samples + agg_fn = np.median + + # Cadence parameters + alpha_0 = agg_fn(np.array(sample_dict["alpha_0"])) + alpha_chain = agg_fn(np.array(sample_dict["alpha_chain"]), axis=0) + alpha_tier = agg_fn(np.array(sample_dict["alpha_tier"]), axis=0) + alpha_tvl = agg_fn(np.array(sample_dict["alpha_tvl"])) + + # Hierarchical noise: reconstruct theta + B = agg_fn(np.array(sample_dict["B"]), axis=0) + eta = agg_fn(np.array(sample_dict["eta"]), axis=0) + sigma_theta = agg_fn(np.array(sample_dict["sigma_theta"]), axis=0) + L_Omega = agg_fn(np.array(sample_dict["L_Omega"]), axis=0) + + pool_idx = np.array(data["pool_idx"]) + X_pool = np.array(data["X_pool"]) + x_obs = np.array(data["x_obs"]) + chain_idx = np.array(data["chain_idx"]) + tier_idx = np.array(data["tier_idx"]) + sigma_daily = np.array(data["sigma_daily"]) + lag_log_tvl = np.array(data["lag_log_tvl"]) + fee = np.array(data["fee"]) + gas = np.array(data["gas"]) + + # Per-pool cadence + padded_chain = np.concatenate([[0.0], alpha_chain]) + padded_tier = np.concatenate([[0.0], alpha_tier]) + + N_pools = data["N_pools"] + pool_log_cadence = np.zeros(N_pools) + for p in range(N_pools): + pool_log_cadence[p] = ( + alpha_0 + + padded_chain[chain_idx[p]] + + padded_tier[tier_idx[p]] + + alpha_tvl * np.median(lag_log_tvl[pool_idx == p]) + ) + + # Per-obs V_arb + log_cad_obs = pool_log_cadence[pool_idx] + cadence_obs = np.exp(np.clip(log_cad_obs, -2.0, 6.0)) + tvl_obs = np.exp(lag_log_tvl) + + V_arb = np.array(formula_arb_volume_daily_jax( + jnp.array(sigma_daily), jnp.array(tvl_obs), + jnp.array(fee), jnp.array(gas), jnp.array(cadence_obs), + )) + + # Per-pool theta from hierarchical model + L_Sigma = np.diag(sigma_theta) @ L_Omega + theta = X_pool @ B.T + eta @ L_Sigma.T # (N_pools, K_obs_coeff) + + # Per-obs V_noise + log_V_noise = np.sum(theta[pool_idx] * x_obs, axis=1) + V_noise = np.exp(log_V_noise) + + # Predicted total + V_total_pred = V_arb + V_noise + log_V_pred = np.log(np.maximum(V_total_pred, 1e-6)) + + return V_arb, V_noise, V_total_pred, log_V_pred, cadence_obs + + +def plot_top50(panel, data, pool_params, V_arb, V_noise, log_V_pred): + """Plot top 50 pools by median TVL.""" + pool_meta = data["pool_meta"] + pool_ids = data["pool_ids"] + pool_idx = np.array(data["pool_idx"]) + y_obs = np.array(data["y_obs"]) + + # Rank pools by median TVL + pool_tvl = {} + for i, pid in enumerate(pool_ids): + mask = pool_idx == i + pool_tvl[pid] = np.median(np.exp(np.array(data["lag_log_tvl"])[mask])) + + ranked = sorted(pool_tvl.items(), key=lambda x: -x[1])[:TOP_N] + + # Build param lookup + param_lookup = {p["pool_id"]: p for p in pool_params} + + per_page = 10 + n_pages = (len(ranked) + per_page - 1) // per_page + + for page in range(n_pages): + start = page * per_page + end = min(start + per_page, len(ranked)) + page_pools = ranked[start:end] + n_this = len(page_pools) + + ncols = 2 + nrows = (n_this + ncols - 1) // ncols + fig, axes = plt.subplots(nrows, ncols, figsize=(16, 4.5 * nrows)) + if nrows == 1 and ncols == 1: + axes = np.array([[axes]]) + elif nrows == 1: + axes = axes.reshape(1, -1) + + for idx, (pid, median_tvl) in enumerate(page_pools): + ax = axes[idx // ncols][idx % ncols] + p_idx = pool_ids.index(pid) + mask = pool_idx == p_idx + + pp = panel[panel["pool_id"] == pid].sort_values("date") + dates = pd.to_datetime(pp["date"].values) + actual_vol = np.exp(y_obs[mask]) + pred_arb = V_arb[mask] + pred_noise = V_noise[mask] + pred_total = pred_arb + pred_noise + + # R2 + actual_log = y_obs[mask] + pred_log = log_V_pred[mask] + ss_res = np.sum((actual_log - pred_log) ** 2) + ss_tot = np.sum((actual_log - actual_log.mean()) ** 2) + r2 = 1 - ss_res / ss_tot if ss_tot > 0 else float("nan") + + # Arb fraction + arb_frac = np.median(pred_arb / np.maximum(pred_total, 1.0)) + + # Plot + ax.fill_between(dates, 0, pred_arb, alpha=0.3, color="orangered", + label="V_arb (LVR)") + ax.fill_between(dates, pred_arb, pred_total, alpha=0.3, + color="steelblue", label="V_noise (hier.)") + ax.plot(dates, actual_vol, "k-", linewidth=0.8, alpha=0.7, + label="Actual") + ax.plot(dates, pred_total, "--", color="purple", linewidth=0.8, + alpha=0.7, label="Predicted total") + + ax.set_yscale("log") + ax.set_ylabel("Daily volume (USD)", fontsize=8) + + meta = pool_meta[pool_meta["pool_id"] == pid] + if len(meta) > 0: + m = meta.iloc[0] + tokens = m["tokens"] + if isinstance(tokens, str): + tokens = tokens.split(",") + tok_str = "/".join(str(t)[:8] for t in tokens[:2]) + chain = str(m["chain"]) + else: + tok_str = pid[:16] + chain = "?" + + params = param_lookup.get(pid, {}) + arb_freq = params.get("arb_frequency", "?") + + ax.set_title( + f"{tok_str} ({chain})\n" + f"TVL ${median_tvl:,.0f} | R\u00b2={r2:.3f} " + f"arb_freq={arb_freq}min arb_frac={arb_frac:.1%} " + f"n={mask.sum()}", + fontsize=8, + ) + ax.legend(fontsize=6, loc="upper right") + ax.tick_params(labelsize=7) + ax.tick_params(axis="x", rotation=30) + + for idx in range(n_this, nrows * ncols): + axes[idx // ncols][idx % ncols].set_visible(False) + + fig.suptitle( + f"Structural mixture model: V_arb + V_noise decomposition " + f"— page {page + 1}/{n_pages} " + f"(top {TOP_N} by median TVL, 90d window)", + fontsize=11, + ) + fig.tight_layout() + out = os.path.join(OUTPUT_DIR, f"structural_top50_page{page + 1}.png") + fig.savefig(out, dpi=150, bbox_inches="tight") + plt.close(fig) + print(f" Saved: {out}") + + +def plot_elbo(elbo_losses): + """Plot ELBO convergence.""" + fig, axes = plt.subplots(1, 2, figsize=(14, 5)) + ax = axes[0] + ax.plot(elbo_losses, alpha=0.3, color="steelblue", linewidth=0.5) + window = min(100, len(elbo_losses) // 10) + if window > 1: + smoothed = pd.Series(elbo_losses).rolling(window).mean().values + ax.plot(smoothed, color="red", linewidth=1.5, label=f"Rolling {window}") + ax.legend() + ax.set_xlabel("Step") + ax.set_ylabel("ELBO loss") + ax.set_title("ELBO convergence") + + ax = axes[1] + start = len(elbo_losses) * 4 // 5 + ax.plot(range(start, len(elbo_losses)), elbo_losses[start:], + color="steelblue", linewidth=0.8) + ax.set_xlabel("Step") + ax.set_ylabel("ELBO loss") + ax.set_title("ELBO convergence (last 20%)") + + plt.tight_layout() + out = os.path.join(OUTPUT_DIR, "elbo_convergence.png") + plt.savefig(out, dpi=150, bbox_inches="tight") + plt.close() + print(f" Saved: {out}") + + +def plot_summary(data, pool_params, V_arb, V_noise, log_V_pred): + """Summary plots: arb frequency distribution, arb fraction, R2.""" + pool_idx = np.array(data["pool_idx"]) + y_obs = np.array(data["y_obs"]) + pool_ids = data["pool_ids"] + + fig, axes = plt.subplots(1, 3, figsize=(16, 5)) + + # 1. Arb frequency histogram + ax = axes[0] + freqs = [p["arb_frequency"] for p in pool_params] + ax.hist(freqs, bins=range(0, 62, 2), color="orangered", alpha=0.7, + edgecolor="white") + ax.set_xlabel("Arb frequency (minutes)") + ax.set_ylabel("Count") + ax.set_title(f"Arb frequency distribution (n={len(freqs)})") + ax.axvline(np.median(freqs), color="black", linestyle="--", + label=f"Median={np.median(freqs):.0f}min") + ax.legend() + + # 2. Arb fraction per pool + ax = axes[1] + arb_fracs = [] + for i, pid in enumerate(pool_ids): + mask = pool_idx == i + total = V_arb[mask] + V_noise[mask] + arb_fracs.append(np.median(V_arb[mask] / np.maximum(total, 1.0))) + ax.hist(arb_fracs, bins=30, color="steelblue", alpha=0.7, edgecolor="white") + ax.set_xlabel("Median arb fraction") + ax.set_ylabel("Count") + ax.set_title("Arb fraction distribution") + ax.axvline(np.median(arb_fracs), color="black", linestyle="--", + label=f"Median={np.median(arb_fracs):.2f}") + ax.legend() + + # 3. Per-pool R2 + ax = axes[2] + r2_vals = [] + for i, pid in enumerate(pool_ids): + mask = pool_idx == i + actual = y_obs[mask] + pred = log_V_pred[mask] + ss_res = np.sum((actual - pred) ** 2) + ss_tot = np.sum((actual - actual.mean()) ** 2) + r2_vals.append(1 - ss_res / ss_tot if ss_tot > 0 else float("nan")) + r2_vals = np.array(r2_vals) + ax.hist(r2_vals[np.isfinite(r2_vals)], bins=30, color="green", alpha=0.7, + edgecolor="white") + ax.set_xlabel("R²") + ax.set_ylabel("Count") + ax.set_title("Per-pool R² distribution") + ax.axvline(np.nanmedian(r2_vals), color="black", linestyle="--", + label=f"Median={np.nanmedian(r2_vals):.3f}") + ax.legend() + + plt.tight_layout() + out = os.path.join(OUTPUT_DIR, "structural_summary.png") + plt.savefig(out, dpi=150, bbox_inches="tight") + plt.close() + print(f" Saved: {out}") + + +def main(): + print("=" * 70) + print("Structural Mixture Model: Fit + Top 50 Plots") + print("=" * 70) + + panel = load_and_filter_panel() + samples, data, pool_params, elbo_losses = fit_structural(panel) + + print("\nComputing predictions...") + V_arb, V_noise, V_total, log_V_pred, cadence = compute_predictions( + samples, data, panel, + ) + print(f" V_arb median: ${np.median(V_arb):,.0f}") + print(f" V_noise median: ${np.median(V_noise):,.0f}") + print(f" Arb fraction (median pool): {np.median(V_arb / np.maximum(V_total, 1)):.2%}") + + print("\nGenerating plots...") + os.makedirs(OUTPUT_DIR, exist_ok=True) + plot_elbo(elbo_losses) + plot_summary(data, pool_params, V_arb, V_noise, log_V_pred) + plot_top50(panel, data, pool_params, V_arb, V_noise, log_V_pred) + + # Summary stats + print(f"\n{'=' * 70}") + print(f"Done. Output in: {OUTPUT_DIR}") + arb_freqs = [p["arb_frequency"] for p in pool_params] + print(f" Arb frequency: median={np.median(arb_freqs):.0f}min, " + f"range=[{np.min(arb_freqs)}, {np.max(arb_freqs)}]") + print(f" JSON: {OUTPUT_JSON}") + + +if __name__ == "__main__": + main() diff --git a/scripts/run_token_factored_calibration.py b/scripts/run_token_factored_calibration.py new file mode 100644 index 0000000..ad6d7b0 --- /dev/null +++ b/scripts/run_token_factored_calibration.py @@ -0,0 +1,845 @@ +"""Token-factored noise calibration v2: canonicalization + cross-pool lag features. + +Phase 0: Pooled Ridge diagnostic — does cross-pool signal exist? +Phase 1: Token-factored model with lambda_delta annealing sweep +Phase 2: LOO cross-validation (baseline vs cross-pool ablation) +Phase 3: Comparison plots and JSON export +""" + +import argparse +import json +import os +import pickle + +import matplotlib +matplotlib.use("Agg") +import matplotlib.pyplot as plt +import numpy as np +import pandas as pd + +# ---- Config ---- +PANEL_CACHE = os.path.join( + os.path.dirname(os.path.dirname(__file__)), + "local_data", "noise_calibration", "panel.parquet", +) +GRID_DIR = os.path.join( + os.path.dirname(os.path.dirname(__file__)), + "results", "pool_grids_v2", +) +OUTPUT_DIR = os.path.join( + os.path.dirname(os.path.dirname(__file__)), + "results", "token_factored_calibration", +) +OPTION_C_LOSS_CUTOFF = 5.0 +JOINT_MAXITER = 5000 +# Sorted descending for warm-start annealing (highest regularization first) +LAMBDA_DELTAS = [10.0, 5.0, 1.0, 0.5, 0.1, 0.01] + + +# ---- Data loading (shared with run_mlp_calibration.py) ---- + + +def load_and_match(): + """Load panel, match to grids.""" + from quantammsim.calibration.pool_data import ( + match_grids_to_panel, + replace_panel_volatility_with_binance, + ) + + panel = pd.read_parquet(PANEL_CACHE) + + if "log_tvl_lag1" not in panel.columns: + panel = panel.sort_values(["pool_id", "date"]).reset_index(drop=True) + panel["log_tvl_lag1"] = panel.groupby("pool_id")["log_tvl"].shift(1) + panel = panel.dropna(subset=["log_tvl_lag1"]).reset_index(drop=True) + + pool_counts = panel.groupby("pool_id").size() + valid = pool_counts[pool_counts >= 10].index + panel = panel[panel["pool_id"].isin(valid)].copy() + + print("Replacing volatility with Binance minute data...") + panel = replace_panel_volatility_with_binance(panel) + + print(f"Panel: {len(panel)} obs, {panel['pool_id'].nunique()} pools, " + f"{panel['date'].min()} to {panel['date'].max()}") + + matched = match_grids_to_panel(GRID_DIR, panel) + print(f"Matched: {len(matched)} pools with grids") + if not matched: + daily_grids = [ + f for f in os.listdir(GRID_DIR) + if f.endswith("_daily.parquet") + ] if os.path.isdir(GRID_DIR) else [] + if not daily_grids: + raise RuntimeError( + "No daily pool grids found. Build them first with " + "`python scripts/build_pool_grids.py --workers 6 --train-days 90`; " + f"expected files like {GRID_DIR}/_daily.parquet." + ) + raise RuntimeError( + "No panel pools matched the available daily grid prefixes. Rebuild " + "the grids from the current panel with " + "`python scripts/build_pool_grids.py --workers 6 --train-days 90`." + ) + return panel, matched + + +def filter_pathological(matched, option_c): + """Drop pools with high Option C loss.""" + good = {p: r for p, r in option_c.items() if r["loss"] <= OPTION_C_LOSS_CUTOFF} + dropped = set(option_c) - set(good) + matched_clean = {p: matched[p] for p in good if p in matched} + if dropped: + print(f" Dropping {len(dropped)} pools (loss > {OPTION_C_LOSS_CUTOFF}):") + for p in sorted(dropped): + print(f" {p} loss={option_c[p]['loss']:.1f}") + return matched_clean, good + + +# ---- Phase 0: Pooled Ridge Diagnostic ---- + + +def run_phase0_diagnostic(matched, option_c): + """Pooled Ridge + token-dummy Ridge — go/no-go gate.""" + from sklearn.linear_model import RidgeCV + + from quantammsim.calibration.grid_interpolation import interpolate_pool_daily + from quantammsim.calibration.pool_data import ( + build_pool_attributes, build_x_obs, encode_tokens, _parse_tokens, + ) + import jax.numpy as jnp + + print("\n" + "=" * 70) + print("Phase 0: Pooled Ridge Diagnostic — cross-pool signal?") + print("=" * 70) + + pool_ids = sorted(matched.keys()) + X_attr, _, _ = build_pool_attributes(matched) + pool_idx_map = {pid: i for i, pid in enumerate(pool_ids)} + enc = encode_tokens(matched) + + all_x, all_y, all_pool_attrs, all_token_dummies = [], [], [], [] + + for pid in pool_ids: + entry = matched[pid] + oc = option_c[pid] + coeffs = entry["coeffs"] + + v_arb_all = np.array(interpolate_pool_daily( + coeffs, jnp.float64(oc["log_cadence"]), + jnp.float64(np.exp(oc["log_gas"])))) + v_arb = v_arb_all[entry["day_indices"]] + + x_obs = build_x_obs(entry["panel"], reduced=True) + y_obs = entry["panel"]["log_volume"].values.astype(float) + y_residual = y_obs - np.log(np.maximum(v_arb, 1e-6)) + + all_x.append(x_obs) + all_y.append(y_residual) + + # Broadcast pool attrs to each obs + x_attr_row = X_attr[pool_idx_map[pid]] + all_pool_attrs.append(np.tile(x_attr_row, (len(x_obs), 1))) + + # Token dummies: one-hot for each token in the pool + n_obs = len(x_obs) + dummies = np.zeros((n_obs, enc["n_tokens"]), dtype=np.float64) + toks = _parse_tokens(entry["tokens"]) + for t in toks[:2]: + if t in enc["token_index"]: + dummies[:, enc["token_index"][t]] = 1.0 + all_token_dummies.append(dummies) + + X_obs = np.vstack(all_x) + y_combined = np.concatenate(all_y) + X_pool_attrs = np.vstack(all_pool_attrs) + X_token_dummies = np.vstack(all_token_dummies) + + # Model 1: x_obs + pool_attrs + X_combined = np.column_stack([X_obs, X_pool_attrs]) + model1 = RidgeCV(alphas=np.logspace(-2, 4, 50)) + model1.fit(X_combined, y_combined) + r2_pooled = model1.score(X_combined, y_combined) + print(f" Pooled Ridge (x_obs + pool attrs): R² = {r2_pooled:.4f}") + + # Model 2: x_obs + token_dummies + X_token = np.column_stack([X_obs, X_token_dummies]) + model2 = RidgeCV(alphas=np.logspace(-2, 4, 50)) + model2.fit(X_token, y_combined) + r2_token = model2.score(X_token, y_combined) + print(f" Token-dummy Ridge (x_obs + token dummies): R² = {r2_token:.4f}") + + # Model 3: x_obs + token_dummies + chain_dummies + log_fee + chain_dummies = np.zeros((len(y_combined), enc["n_chains"]), dtype=np.float64) + log_fees = np.zeros((len(y_combined), 1), dtype=np.float64) + offset = 0 + for pid in pool_ids: + n_obs = len(matched[pid]["day_indices"]) + ci = enc["chain_idx"][pool_idx_map[pid]] + chain_dummies[offset:offset + n_obs, ci] = 1.0 + log_fees[offset:offset + n_obs, 0] = enc["log_fees"][pool_idx_map[pid]] + offset += n_obs + + X_full = np.column_stack([X_obs, X_token_dummies, chain_dummies, log_fees]) + model3 = RidgeCV(alphas=np.logspace(-2, 4, 50)) + model3.fit(X_full, y_combined) + r2_full = model3.score(X_full, y_combined) + print(f" Full Ridge (x_obs + tokens + chains + fee): R² = {r2_full:.4f}") + + # Baseline: x_obs only + model_base = RidgeCV(alphas=np.logspace(-2, 4, 50)) + model_base.fit(X_obs, y_combined) + r2_base = model_base.score(X_obs, y_combined) + print(f" Baseline (x_obs only): R² = {r2_base:.4f}") + + print(f"\n Signal above baseline:") + print(f" Pool attrs: +{r2_pooled - r2_base:.4f}") + print(f" Token dummies: +{r2_token - r2_base:.4f}") + print(f" Full (tok+ch+fee): +{r2_full - r2_base:.4f}") + + if r2_full - r2_base < 0.01: + print("\n WARNING: Very weak cross-pool signal. " + "Token factoring may not improve over per-pool fits.") + + return { + "r2_baseline": r2_base, + "r2_pool_attrs": r2_pooled, + "r2_token_dummies": r2_token, + "r2_full": r2_full, + "n_obs_total": len(y_combined), + "n_pools": len(pool_ids), + "n_tokens": enc["n_tokens"], + "n_chains": enc["n_chains"], + } + + +# ---- Phase 1: Token-Factored Model ---- + + +def _build_gas_values(jdata, matched_clean): + """Build fixed gas values (log-space) from chain data.""" + from quantammsim.calibration.loss import CHAIN_GAS_USD + gas_values = [] + for pid in jdata.pool_ids: + chain = matched_clean[pid]["chain"] + gas_usd = CHAIN_GAS_USD.get(chain, 1.0) + gas_values.append(np.log(max(gas_usd, 1e-6))) + return np.array(gas_values) + + +def _result_to_warm_start(result): + """Extract per-pool warm_start dict from a CalibrationModel fit result. + + Returns dict: pool_id -> {log_cadence, noise_coeffs} suitable for + passing as warm_start to CalibrationModel.fit(). + """ + pool_ids = result["pool_ids"] + warm = {} + for i, pid in enumerate(pool_ids): + entry = {} + # Cadence: from PerPoolHead + if "log_cadence_per_pool" in result: + entry["log_cadence"] = float(result["log_cadence_per_pool"][i]) + # Noise: per-pool coefficients + if "noise_coeffs" in result: + entry["noise_coeffs"] = result["noise_coeffs"][i] + warm[pid] = entry + return warm + + +def run_token_factored( + matched_clean, option_c_clean, lambda_delta=1.0, + cross_pool=False, warm_start=None, +): + """Fit TokenFactoredNoiseHead with PerPoolHead(cadence) + FixedHead(gas).""" + from quantammsim.calibration.calibration_model import CalibrationModel + from quantammsim.calibration.heads import ( + FixedHead, PerPoolHead, TokenFactoredNoiseHead, + ) + from quantammsim.calibration.joint_fit import prepare_token_factored_data + from quantammsim.calibration.pool_data import K_OBS_CROSS, K_OBS_REDUCED + + k_obs = K_OBS_CROSS if cross_pool else K_OBS_REDUCED + + jdata, enc = prepare_token_factored_data( + matched_clean, cross_pool=cross_pool, + ) + n_pools = len(jdata.pool_data) + + gas_values = _build_gas_values(jdata, matched_clean) + gas_head = FixedHead("log_gas", gas_values) + cad_head = PerPoolHead("log_cadence", default=np.log(12.0)) + noise_head = TokenFactoredNoiseHead( + k_obs=k_obs, + lambda_delta=lambda_delta, + **enc, + ) + + model = CalibrationModel(cad_head, gas_head, noise_head) + n_p = model.n_params(n_pools, jdata.x_attr.shape[1]) + cp_tag = " [cross-pool]" if cross_pool else "" + print(f"\n--- Token-factored (lambda_delta={lambda_delta}){cp_tag} ---") + print(f" {n_pools} pools, {enc['n_tokens']} tokens, " + f"{enc['n_chains']} chains, {n_p} params, k_obs={k_obs}") + + ws = warm_start if warm_start is not None else option_c_clean + result = model.fit(jdata, maxiter=JOINT_MAXITER, warm_start=ws) + print(f" Loss: {result['init_loss']:.4f} -> {result['loss']:.4f}" + f" (data={result['data_loss']:.4f}, reg={result['reg_loss']:.4f})") + print(f" Converged: {result['converged']}") + + return result, model, jdata, enc + + +# ---- Phase 2: Analysis & Visualization ---- + + +def print_token_effects(result, enc): + """Print token effect table.""" + u = result["token_effects"] + Gamma = result["Gamma"] + x_token = enc["x_token"] + token_index = enc["token_index"] + inv_index = {v: k for k, v in token_index.items()} + + u_pred = x_token @ Gamma # population prediction + + print(f"\n{'='*70}") + print("Token effects (u_t) vs population prediction (x_t @ Gamma)") + print(f"{'='*70}") + print(f"{'Token':<12} {'u[0]':>8} {'pred[0]':>8} {'delta[0]':>8} " + f"{'u[1]':>8} {'pred[1]':>8}") + print("-" * 60) + for idx in range(len(inv_index)): + name = inv_index[idx] + print(f"{name:<12} {u[idx,0]:>8.3f} {u_pred[idx,0]:>8.3f} " + f"{u[idx,0]-u_pred[idx,0]:>8.3f} " + f"{u[idx,1]:>8.3f} {u_pred[idx,1]:>8.3f}") + + +def print_chain_effects(result, enc): + """Print chain effect table.""" + alpha = result["chain_effects"] + chain_index = enc["chain_index"] + inv_index = {v: k for k, v in chain_index.items()} + + print(f"\n{'='*70}") + print("Chain effects (alpha)") + print(f"{'='*70}") + k = alpha.shape[1] + header = f"{'Chain':<12}" + "".join(f" {'a['+str(j)+']':>8}" for j in range(k)) + print(header) + print("-" * (12 + 9 * k)) + for idx in range(len(inv_index)): + name = inv_index[idx] + vals = " ".join(f"{alpha[idx, j]:>8.3f}" for j in range(k)) + print(f"{name:<12} {vals}") + + +def print_delta_analysis(result, enc, jdata, matched_clean): + """Print per-pool delta analysis.""" + delta = result["noise_deltas"] + pool_ids = jdata.pool_ids + + print(f"\n{'='*70}") + print("Per-pool deltas (unexplained residual)") + print(f"{'='*70}") + print(f"{'Pool':<24} {'Tokens':<16} {'Chain':<10} " + f"{'|delta|':>8} {'delta[0]':>8}") + print("-" * 70) + + delta_norms = np.linalg.norm(delta, axis=1) + order = np.argsort(-delta_norms) + for i in order: + pid = pool_ids[i] + entry = matched_clean[pid] + print(f"{pid[:24]:<24} {entry['tokens']:<16} {entry['chain']:<10} " + f"{delta_norms[i]:>8.3f} {delta[i, 0]:>8.3f}") + + +def run_lambda_sweep(matched_clean, option_c_clean, cross_pool=False): + """Sweep lambda_delta with warm-start annealing (descending lambda). + + Each fit warm-starts from the previous result, so the sweep is + effectively a continuation path from high to low regularization. + """ + print(f"\n{'='*70}") + cp_tag = " [cross-pool]" if cross_pool else "" + print(f"Lambda_delta sweep{cp_tag}") + print(f"{'='*70}") + print(f"{'lambda':>10} {'loss':>10} {'data_loss':>10} {'reg_loss':>10} " + f"{'delta_norm':>12} {'mean_|d|':>10}") + print("-" * 65) + + results = [] + warm_start = option_c_clean + for lam in LAMBDA_DELTAS: + result, model, jdata, enc = run_token_factored( + matched_clean, option_c_clean, lambda_delta=lam, + cross_pool=cross_pool, warm_start=warm_start, + ) + delta = result["noise_deltas"] + delta_norm = float(np.linalg.norm(delta)) + mean_abs_d = float(np.mean(np.abs(delta))) + print(f"{lam:>10.2f} {result['loss']:>10.4f} " + f"{result['data_loss']:>10.4f} {result['reg_loss']:>10.4f} " + f"{delta_norm:>12.4f} {mean_abs_d:>10.4f}") + results.append({ + "lambda_delta": lam, + "loss": result["loss"], + "data_loss": result["data_loss"], + "reg_loss": result["reg_loss"], + "delta_norm": delta_norm, + "mean_abs_delta": mean_abs_d, + "converged": result["converged"], + }) + # Warm-start next iteration from this result + warm_start = _result_to_warm_start(result) + + return results + + +# ---- Phase 3: LOO Cross-Validation ---- + + +def run_loo_validation( + matched_clean, option_c_clean, lambda_delta=1.0, cross_pool=False, +): + """Leave-one-pool-out cross-validation via predict_new_pool.""" + from quantammsim.calibration.calibration_model import CalibrationModel + from quantammsim.calibration.heads import ( + FixedHead, PerPoolHead, TokenFactoredNoiseHead, + ) + from quantammsim.calibration.grid_interpolation import interpolate_pool_daily + from quantammsim.calibration.joint_fit import prepare_token_factored_data + from quantammsim.calibration.pool_data import ( + K_OBS_CROSS, K_OBS_REDUCED, build_cross_pool_x_obs, + build_x_obs, _parse_tokens, + ) + import jax.numpy as jnp + + k_obs = K_OBS_CROSS if cross_pool else K_OBS_REDUCED + pool_ids = sorted(matched_clean.keys()) + + cp_tag = " [cross-pool]" if cross_pool else "" + print(f"\n{'='*70}") + print(f"LOO Cross-Validation (lambda_delta={lambda_delta}){cp_tag}") + print(f"{'='*70}") + + loo_results = [] + for hold_out_pid in pool_ids: + # Build training set without hold-out pool + train_matched = {p: matched_clean[p] for p in pool_ids if p != hold_out_pid} + train_oc = {p: option_c_clean[p] for p in pool_ids if p != hold_out_pid} + + if len(train_matched) < 3: + continue + + # Fit on training set + jdata, enc = prepare_token_factored_data( + train_matched, cross_pool=cross_pool, + ) + gas_values = _build_gas_values(jdata, train_matched) + + noise_head = TokenFactoredNoiseHead( + k_obs=k_obs, + lambda_delta=lambda_delta, + **enc, + ) + model = CalibrationModel( + PerPoolHead("log_cadence", default=np.log(12.0)), + FixedHead("log_gas", gas_values), + noise_head, + ) + result = model.fit(jdata, maxiter=JOINT_MAXITER, warm_start=train_oc) + + # Extract noise params and predict for hold-out pool + n_train = len(jdata.pool_data) + k_attr = jdata.x_attr.shape[1] + (_, _), (_, _), (ns, ne) = model._head_slices(n_train, k_attr) + noise_params = result["params_flat"][ns:ne] + + ho_entry = matched_clean[hold_out_pid] + toks = _parse_tokens(ho_entry["tokens"]) + ho_pred = noise_head.predict_new_pool( + noise_params, toks[0], toks[1], + ho_entry["chain"], ho_entry["fee"], + n_pools=n_train, + ) + + # Evaluate hold-out R² + ho_panel = ho_entry["panel"] + y_obs_ho = ho_panel["log_volume"].values.astype(float) + + if cross_pool: + # Build cross-pool x_obs for held-out pool. + # Use matched_clean so pool's own entry is accessible; + # build_cross_pool_x_obs auto-excludes pool_id from its own peers. + x_obs_ho = build_cross_pool_x_obs( + ho_panel, matched_clean, hold_out_pid, + ) + # Trim y_obs to match (first day dropped) + y_obs_ho = y_obs_ho[1:] + day_indices_ho = ho_entry["day_indices"][1:] + else: + x_obs_ho = build_x_obs(ho_panel, reduced=True) + day_indices_ho = ho_entry["day_indices"] + + # Use Option C cadence for the hold-out pool (not predicting cadence) + oc_ho = option_c_clean[hold_out_pid] + v_arb_all = np.array(interpolate_pool_daily( + ho_entry["coeffs"], + jnp.float64(oc_ho["log_cadence"]), + jnp.float64(np.exp(oc_ho["log_gas"])), + )) + v_arb = v_arb_all[day_indices_ho] + + # Noise coefficients are k_obs-dimensional; x_obs_ho has k_obs columns + noise_coeffs = ho_pred["noise_coeffs"][:k_obs] + v_noise = np.exp(x_obs_ho @ noise_coeffs) + log_pred = np.log(np.maximum(v_arb + v_noise, 1e-6)) + ss_res = np.sum((log_pred - y_obs_ho) ** 2) + ss_tot = np.sum((y_obs_ho - y_obs_ho.mean()) ** 2) + r2_loo = 1 - ss_res / max(ss_tot, 1e-10) + + # Compare with Option C in-sample R² + x_obs_c = build_x_obs(ho_panel, reduced=True) + v_noise_c = np.exp(x_obs_c @ oc_ho["noise_coeffs"][:K_OBS_REDUCED]) + v_arb_c = v_arb_all[ho_entry["day_indices"]] + log_pred_c = np.log(np.maximum(v_arb_c + v_noise_c, 1e-6)) + y_obs_full = ho_panel["log_volume"].values.astype(float) + ss_res_c = np.sum((log_pred_c - y_obs_full) ** 2) + ss_tot_c = np.sum((y_obs_full - y_obs_full.mean()) ** 2) + r2_c = 1 - ss_res_c / max(ss_tot_c, 1e-10) + + loo_results.append({ + "pool_id": hold_out_pid, + "r2_loo": r2_loo, + "r2_option_c": r2_c, + "tokens": ho_entry["tokens"], + "chain": ho_entry["chain"], + }) + + print(f" {hold_out_pid[:16]} ({ho_entry['tokens']:<14}) " + f"R²_LOO={r2_loo:.3f} R²_C={r2_c:.3f} " + f"{'BETTER' if r2_loo > r2_c else 'worse'}") + + if loo_results: + r2s_loo = [r["r2_loo"] for r in loo_results] + r2s_c = [r["r2_option_c"] for r in loo_results] + n_better = sum(1 for r in loo_results if r["r2_loo"] > r["r2_option_c"]) + print(f"\n LOO median R²: {np.median(r2s_loo):.4f} " + f"(Option C: {np.median(r2s_c):.4f})") + print(f" LOO wins: {n_better}/{len(loo_results)}") + + return loo_results + + +# ---- Plots ---- + + +def plot_lambda_sweep(sweep_results, output_dir, suffix=""): + """Plot loss (data/reg separated) and delta norm vs lambda_delta.""" + lambdas = [r["lambda_delta"] for r in sweep_results] + data_losses = [r["data_loss"] for r in sweep_results] + reg_losses = [r["reg_loss"] for r in sweep_results] + delta_norms = [r["delta_norm"] for r in sweep_results] + + fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(12, 5)) + + ax1.semilogx(lambdas, data_losses, "o-", color="steelblue", label="data_loss") + ax1.semilogx(lambdas, reg_losses, "s--", color="orangered", label="reg_loss") + ax1.set_xlabel("lambda_delta") + ax1.set_ylabel("Loss") + ax1.set_title("Data + Reg Loss vs lambda_delta") + ax1.legend() + + ax2.semilogx(lambdas, delta_norms, "o-", color="orangered") + ax2.set_xlabel("lambda_delta") + ax2.set_ylabel("||delta||") + ax2.set_title("Delta norm vs lambda_delta") + + fig.tight_layout() + out = os.path.join(output_dir, f"lambda_sweep{suffix}.png") + fig.savefig(out, dpi=150, bbox_inches="tight") + plt.close(fig) + print(f" Saved: {out}") + + +def plot_token_effects(result, enc, output_dir): + """Bar chart of token effects (intercept coefficient).""" + u = result["token_effects"] + token_index = enc["token_index"] + inv_index = {v: k for k, v in token_index.items()} + names = [inv_index[i] for i in range(len(inv_index))] + + fig, ax = plt.subplots(figsize=(max(8, len(names) * 0.5), 5)) + x = np.arange(len(names)) + ax.bar(x, u[:, 0], color="steelblue", alpha=0.8) + ax.set_xticks(x) + ax.set_xticklabels(names, rotation=45, ha="right", fontsize=8) + ax.set_ylabel("u_t[0] (intercept effect)") + ax.set_title("Token effects on noise intercept") + ax.axhline(0, color="black", linewidth=0.5, linestyle="--") + fig.tight_layout() + out = os.path.join(output_dir, "token_effects.png") + fig.savefig(out, dpi=150, bbox_inches="tight") + plt.close(fig) + print(f" Saved: {out}") + + +def plot_loo_scatter(loo_results, output_dir, suffix=""): + """Scatter: Option C R² vs LOO R².""" + if not loo_results: + return + + r2_c = [r["r2_option_c"] for r in loo_results] + r2_loo = [r["r2_loo"] for r in loo_results] + + fig, ax = plt.subplots(figsize=(7, 6)) + ax.scatter(r2_c, r2_loo, alpha=0.7, s=40, edgecolors="k", linewidth=0.5) + lo = min(min(r2_c), min(r2_loo)) + hi = max(max(r2_c), max(r2_loo)) + margin = (hi - lo) * 0.05 + 0.01 + ax.plot([lo - margin, hi + margin], [lo - margin, hi + margin], + "k--", alpha=0.3, linewidth=1) + ax.set_xlabel("Option C R² (in-sample)") + ax.set_ylabel("Token-factored R² (LOO)") + ax.set_title(f"LOO: Token-Factored vs Option C{suffix}") + + n_better = sum(1 for c, l in zip(r2_c, r2_loo) if l > c) + ax.text(0.05, 0.95, f"LOO wins: {n_better}/{len(r2_c)}", + transform=ax.transAxes, fontsize=10, va="top") + + fig.tight_layout() + out = os.path.join(output_dir, f"loo_scatter{suffix}.png") + fig.savefig(out, dpi=150, bbox_inches="tight") + plt.close(fig) + print(f" Saved: {out}") + + +# ---- Intermediate state caching ---- + +_CACHE_DIR = os.path.join( + os.path.dirname(os.path.dirname(__file__)), + "results", "token_factored_calibration", "_cache", +) + + +def _save_stage1(matched_clean, option_c_clean, diag): + """Cache Option C fits, filtering, and Phase 0 diagnostic.""" + os.makedirs(_CACHE_DIR, exist_ok=True) + path = os.path.join(_CACHE_DIR, "stage1.pkl") + with open(path, "wb") as f: + pickle.dump({ + "matched_clean": matched_clean, + "option_c_clean": option_c_clean, + "diag": diag, + }, f) + print(f" Cached stage 1 to {path}") + + +def _load_stage1(): + """Load cached stage 1 results. Returns None if missing.""" + path = os.path.join(_CACHE_DIR, "stage1.pkl") + if not os.path.exists(path): + return None + with open(path, "rb") as f: + data = pickle.load(f) + print(f" Loaded stage 1 cache from {path}") + return data + + +def _save_baseline(result_base, enc_base, sweep_baseline, loo_baseline): + """Cache ablation 1 results.""" + os.makedirs(_CACHE_DIR, exist_ok=True) + path = os.path.join(_CACHE_DIR, "baseline.pkl") + with open(path, "wb") as f: + pickle.dump({ + "result_base": result_base, + "enc_base": enc_base, + "sweep_baseline": sweep_baseline, + "loo_baseline": loo_baseline, + }, f) + print(f" Cached baseline results to {path}") + + +def _load_baseline(): + """Load cached baseline results. Returns None if missing.""" + path = os.path.join(_CACHE_DIR, "baseline.pkl") + if not os.path.exists(path): + return None + with open(path, "rb") as f: + data = pickle.load(f) + print(f" Loaded baseline cache from {path}") + return data + + +def _export_ablation_result(result, enc): + """Build JSON-serializable dict from ablation result + encoding.""" + return { + "loss": result["loss"], + "data_loss": result["data_loss"], + "reg_loss": result["reg_loss"], + "init_loss": result["init_loss"], + "converged": result["converged"], + "n_pools": result["n_pools"], + "n_tokens": enc["n_tokens"], + "n_chains": enc["n_chains"], + "token_index": enc["token_index"], + "chain_index": enc["chain_index"], + "token_effects": result["token_effects"].tolist(), + "Gamma": result["Gamma"].tolist(), + "chain_effects": result["chain_effects"].tolist(), + "beta_fee": result["beta_fee"].tolist(), + "noise_deltas": result["noise_deltas"].tolist(), + "noise_coeffs": result["noise_coeffs"].tolist(), + } + + +# ---- Main ---- + + +def main(): + parser = argparse.ArgumentParser( + description="Token-factored noise calibration v2") + parser.add_argument( + "--cross-pool-only", action="store_true", + help="Skip baseline ablation, load from cache, run only cross-pool", + ) + parser.add_argument( + "--stage1-only", action="store_true", + help="Build stage1.pkl and exit before ablation fits", + ) + args = parser.parse_args() + + os.environ.setdefault("JAX_PLATFORMS", "cpu") + + print("=" * 70) + print("Token-Factored Noise Calibration v2") + print(" Canonicalization + Cross-Pool Lag Features") + print("=" * 70) + + # ---- Stage 1: Option C + filtering + Phase 0 ---- + cached_s1 = _load_stage1() if args.cross_pool_only else None + + if cached_s1 is not None: + matched_clean = cached_s1["matched_clean"] + option_c_clean = cached_s1["option_c_clean"] + diag = cached_s1["diag"] + print(f" Using cached stage 1: {len(matched_clean)} pools") + else: + panel, matched = load_and_match() + + from quantammsim.calibration.per_pool_fit import fit_all_pools + print(f"\n--- Option C Reduced: per-pool fits ({len(matched)} pools) ---") + option_c = fit_all_pools(matched, fix_gas_to_chain=True, reduced=True) + losses = [r["loss"] for r in option_c.values()] + print(f" Loss: median={np.median(losses):.4f}, mean={np.mean(losses):.4f}") + + matched_clean, option_c_clean = filter_pathological(matched, option_c) + diag = run_phase0_diagnostic(matched_clean, option_c_clean) + _save_stage1(matched_clean, option_c_clean, diag) + + if args.stage1_only: + print("\nStage 1 cache generated; exiting before ablation fits.") + return + + # ---- Ablation 1: Baseline ---- + if args.cross_pool_only: + cached_bl = _load_baseline() + if cached_bl is not None: + result_base = cached_bl["result_base"] + enc_base = cached_bl["enc_base"] + sweep_baseline = cached_bl["sweep_baseline"] + loo_baseline = cached_bl["loo_baseline"] + else: + print(" No baseline cache found — skipping baseline ablation.") + result_base = enc_base = sweep_baseline = loo_baseline = None + else: + print("\n" + "=" * 70) + print("ABLATION 1: Baseline (K_OBS_REDUCED=4, no cross-pool features)") + print("=" * 70) + + result_base, _, jdata_base, enc_base = run_token_factored( + matched_clean, option_c_clean, lambda_delta=1.0, cross_pool=False) + + print_token_effects(result_base, enc_base) + print_chain_effects(result_base, enc_base) + print_delta_analysis(result_base, enc_base, jdata_base, matched_clean) + + sweep_baseline = run_lambda_sweep( + matched_clean, option_c_clean, cross_pool=False) + loo_baseline = run_loo_validation( + matched_clean, option_c_clean, lambda_delta=1.0, cross_pool=False) + + _save_baseline(result_base, enc_base, sweep_baseline, loo_baseline) + + # ---- Ablation 2: Cross-pool ---- + print("\n" + "=" * 70) + print("ABLATION 2: Cross-pool lag features (K_OBS_CROSS=7)") + print("=" * 70) + + result_cross, _, jdata_cross, enc_cross = run_token_factored( + matched_clean, option_c_clean, lambda_delta=1.0, cross_pool=True) + + print_token_effects(result_cross, enc_cross) + print_chain_effects(result_cross, enc_cross) + print_delta_analysis(result_cross, enc_cross, jdata_cross, matched_clean) + + sweep_cross = run_lambda_sweep( + matched_clean, option_c_clean, cross_pool=True) + loo_cross = run_loo_validation( + matched_clean, option_c_clean, lambda_delta=1.0, cross_pool=True) + + # ---- Ablation summary ---- + print("\n" + "=" * 70) + print("ABLATION COMPARISON") + print("=" * 70) + ablations = [("Cross-pool (k=7)", loo_cross)] + if loo_baseline is not None: + ablations.insert(0, ("Baseline (k=4)", loo_baseline)) + for label, loo in ablations: + if loo: + r2s = [r["r2_loo"] for r in loo] + r2s_c = [r["r2_option_c"] for r in loo] + wins = sum(1 for r in loo if r["r2_loo"] > r["r2_option_c"]) + print(f" {label}: median R²_LOO={np.median(r2s):.4f}, " + f"median R²_C={np.median(r2s_c):.4f}, " + f"wins={wins}/{len(loo)}") + + # Plots + print("\nGenerating plots...") + os.makedirs(OUTPUT_DIR, exist_ok=True) + + if sweep_baseline is not None: + plot_lambda_sweep(sweep_baseline, OUTPUT_DIR, suffix="_baseline") + plot_lambda_sweep(sweep_cross, OUTPUT_DIR, suffix="_crosspool") + if result_base is not None: + plot_token_effects(result_base, enc_base, OUTPUT_DIR) + if loo_baseline is not None: + plot_loo_scatter(loo_baseline, OUTPUT_DIR, suffix="_baseline") + plot_loo_scatter(loo_cross, OUTPUT_DIR, suffix="_crosspool") + + # JSON export + export = { + "phase0_diagnostic": diag, + "cross_pool": _export_ablation_result(result_cross, enc_cross), + "lambda_sweep_crosspool": sweep_cross, + "loo_crosspool": loo_cross, + } + if result_base is not None: + export["baseline"] = _export_ablation_result(result_base, enc_base) + export["lambda_sweep_baseline"] = sweep_baseline + export["loo_baseline"] = loo_baseline + json_path = os.path.join(OUTPUT_DIR, "token_factored_v2_results.json") + with open(json_path, "w") as f: + json.dump(export, f, indent=2, default=str) + print(f" Saved: {json_path}") + + print(f"\n{'='*70}") + print(f"Done. Output in: {OUTPUT_DIR}") + + +if __name__ == "__main__": + main() diff --git a/scripts/select_best_params.py b/scripts/select_best_params.py new file mode 100644 index 0000000..d67f9e6 --- /dev/null +++ b/scripts/select_best_params.py @@ -0,0 +1,192 @@ +#!/usr/bin/env python3 +"""Select best reClAMM params from sweep results. + +Reads all result JSONs and corresponding log files for a given TVL config, +extracts train/val metrics, and picks the best param set. + +Usage: + python scripts/select_best_params.py --config aave_1m + python scripts/select_best_params.py --config cow_500k --method cma_es + python scripts/select_best_params.py --all # summarise all configs +""" + +import argparse +import json +import glob +import os +import re + + +SWEEP_DIR = "results/full_sweep" +LOG_DIR = "/tmp" + +# TVL configs matching run_full_sweep.sh +CONFIGS = { + "aave_1m": {"tokens": ["AAVE", "ETH"], "pool_id": "0x9d1fcf346ea1b0", "gas_cost": 1.0, "fees": 0.0025, "initial_pool_value": 1_000_000}, + "aave_5m": {"tokens": ["AAVE", "ETH"], "pool_id": "0x9d1fcf346ea1b0", "gas_cost": 1.0, "fees": 0.0025, "initial_pool_value": 5_000_000}, + "aave_20m": {"tokens": ["AAVE", "ETH"], "pool_id": "0x9d1fcf346ea1b0", "gas_cost": 1.0, "fees": 0.0025, "initial_pool_value": 20_000_000}, + "cow_500k": {"tokens": ["COW", "ETH"], "pool_id": "0xd321300ef77067", "gas_cost": 3.0, "fees": 0.003, "initial_pool_value": 500_000}, + "cow_2m": {"tokens": ["COW", "ETH"], "pool_id": "0xd321300ef77067", "gas_cost": 3.0, "fees": 0.003, "initial_pool_value": 2_000_000}, + "cow_20m": {"tokens": ["COW", "ETH"], "pool_id": "0xd321300ef77067", "gas_cost": 3.0, "fees": 0.003, "initial_pool_value": 20_000_000}, +} + + +def parse_params(param_dict): + """Normalise params from JSON — handles both Optuna (float) and CMA-ES (string array) formats.""" + out = {} + for key in ("price_ratio", "centeredness_margin", "shift_exponent"): + val = param_dict.get(key) + if val is None: + continue + if isinstance(val, str): + # CMA-ES stores as "[1.234]" + val = float(val.strip("[]")) + out[key] = float(val) + return out + + +def parse_log_metrics(log_path): + """Extract train and val metrics from a tuning log file.""" + metrics = {} + if not os.path.exists(log_path): + return metrics + with open(log_path) as f: + text = f.read() + + # Find the final "Best trial" block + m = re.search( + r"Train \(IS\):\s*(.*?)\n.*?Val \(OOS\):\s*(.*?)(?:\n|$)", + text, re.DOTALL, + ) + if not m: + return metrics + + for prefix, line in [("train_", m.group(1)), ("val_", m.group(2))]: + for pair in re.findall(r"(\w+)=([+-]?\d+\.?\d*|[+-]?inf)", line): + try: + metrics[prefix + pair[0]] = float(pair[1]) + except ValueError: + pass + + # Extract completed/failed counts + cm = re.search(r"(\d+) completed.*?(\d+) failed", text) + if cm: + metrics["n_completed"] = int(cm.group(1)) + metrics["n_failed"] = int(cm.group(2)) + + return metrics + + +def find_results(config_name, method="optuna"): + """Find all result files for a given config and method.""" + method_tag = "_cmaes" if method == "cma_es" else "" + pattern = os.path.join(SWEEP_DIR, f"*{method_tag}_{config_name}.json") + results = [] + for json_path in sorted(glob.glob(pattern)): + fname = os.path.basename(json_path).replace(".json", "") + # Derive the log file name + log_name = f"tune_full_{fname}.log" + log_path = os.path.join(LOG_DIR, log_name) + + with open(json_path) as f: + data = json.load(f) + + # The JSON has a single key = objective name + obj_name = list(data.keys())[0] + params = parse_params(data[obj_name]) + metrics = parse_log_metrics(log_path) + + results.append({ + "file": fname, + "objective": obj_name, + "params": params, + "metrics": metrics, + }) + return results + + +def rank_results(results, rank_by="val_returns_over_hodl"): + """Rank results by a metric, handling missing/inf values.""" + def sort_key(r): + v = r["metrics"].get(rank_by, float("-inf")) + if v != v or v == float("-inf"): # nan or -inf + return float("-inf") + return v + return sorted(results, key=sort_key, reverse=True) + + +def print_summary(config_name, results, top_n=5): + """Print a ranked summary table.""" + ranked = rank_results(results) + print(f"\n{'='*80}") + print(f" {config_name} — {len(results)} results (ranked by val RoH)") + print(f"{'='*80}") + print(f" {'Tag':<55s} {'PR':>6s} {'margin':>7s} {'shift':>8s} {'train_roh':>10s} {'val_roh':>10s}") + print(f" {'-'*55} {'-'*6} {'-'*7} {'-'*8} {'-'*10} {'-'*10}") + for r in ranked[:top_n]: + p = r["params"] + m = r["metrics"] + pr = f"{p.get('price_ratio', 0):.2f}" + margin = f"{p.get('centeredness_margin', 0):.3f}" + shift = f"{p.get('shift_exponent', 0):.4f}" + train = m.get("train_ret_over_hodl", m.get("train_returns_over_hodl", float("nan"))) + val = m.get("val_returns_over_hodl", float("nan")) + train_s = f"{train:+.4f}" if train == train else "N/A" + val_s = f"{val:+.4f}" if val == val else "N/A" + print(f" {r['file']:<55s} {pr:>6s} {margin:>7s} {shift:>8s} {train_s:>10s} {val_s:>10s}") + + if ranked: + best = ranked[0] + print(f"\n BEST: {best['file']}") + print(f" PR={best['params'].get('price_ratio', '?'):.4f} " + f"margin={best['params'].get('centeredness_margin', '?'):.4f} " + f"shift={best['params'].get('shift_exponent', '?'):.6f}") + return ranked + + +def export_best(config_name, ranked, method="optuna"): + """Write the best params to a consolidated JSON for downstream scripts.""" + if not ranked: + return + best = ranked[0] + method_tag = f"_{method}" if method != "optuna" else "" + out = { + "config": config_name, + "method": method, + "source": best["file"], + "params": best["params"], + "metrics": best["metrics"], + **CONFIGS[config_name], + } + out_path = os.path.join(SWEEP_DIR, f"best{method_tag}_{config_name}.json") + with open(out_path, "w") as f: + json.dump(out, f, indent=2, default=str) + print(f" Saved: {out_path}") + + +def main(): + p = argparse.ArgumentParser(description=__doc__, + formatter_class=argparse.RawDescriptionHelpFormatter) + p.add_argument("--config", default=None, help="Config name (e.g. aave_1m)") + p.add_argument("--all", action="store_true", help="Summarise all configs") + p.add_argument("--method", default="optuna", choices=["optuna", "cma_es"]) + p.add_argument("--top", type=int, default=8, help="Show top N results") + p.add_argument("--export", action="store_true", help="Export best params to JSON") + args = p.parse_args() + + configs = list(CONFIGS.keys()) if args.all else [args.config] + if not args.all and not args.config: + p.error("Specify --config or --all") + + for config_name in configs: + results = find_results(config_name, args.method) + if not results: + print(f"\n {config_name}: no results found for method={args.method}") + continue + ranked = print_summary(config_name, results, top_n=args.top) + if args.export: + export_best(config_name, ranked, args.method) + + +if __name__ == "__main__": + main() diff --git a/scripts/sim_vs_world_comparison.py b/scripts/sim_vs_world_comparison.py new file mode 100644 index 0000000..ca3046f --- /dev/null +++ b/scripts/sim_vs_world_comparison.py @@ -0,0 +1,972 @@ +#!/usr/bin/env python3 +"""Compare quantammsim reClAMM / Balancer vs reclamm-simulations repo + on-chain. + +Runs: + 1. Zero-fee Balancer pool (quantammsim) — the normalization baseline + 2. reClAMM pool with on-chain params (quantammsim) + 3. Loads reclamm-simulations results + world values from CSV + 4. Gas-experiment runs: time-varying gas from on-chain percentiles, + 50% protocol fee take, on-chain fees + +All comparisons align quantammsim's minute-level output to the world state +CSV's actual Unix timestamps, eliminating timing drift from block-time +variability. + +4-panel plot matching the reclamm-simulations format: + Top-left: Price (WETH/AAVE) — both repos overlaid + Top-right: (legend) + Bottom-left: Absolute value in WETH + Bottom-right: Value relative to feeless weighted (Balancer = 1.0) + +Usage: + python scripts/sim_vs_world_comparison.py + python scripts/sim_vs_world_comparison.py --csv /path/to/csv + python scripts/sim_vs_world_comparison.py --gas-experiment +""" + +import argparse +import numpy as np +import pandas as pd +import matplotlib +matplotlib.use("Agg") +import matplotlib.pyplot as plt +import jax.numpy as jnp +from pathlib import Path +from datetime import datetime, timezone + +from quantammsim.runners.jax_runners import do_run_on_historic_data + +# ── On-chain reClAMM params ─────────────────────────────────────────────────── +ONCHAIN_FEES = 0.0025 + +ONCHAIN_LAUNCH_PARAMS = { # deployment through 2025-12-18 + "price_ratio": 1.5014, + "centeredness_margin": 0.5, + "shift_exponent": 0.1, +} +ONCHAIN_CURRENT_PARAMS = { # post 2025-12-18 governance + "price_ratio": 4.0, + "centeredness_margin": 0.1, + "shift_exponent": 0.001, +} +GOVERNANCE_DATE = "2025-12-18" + +# CSV starts at ~17.2 WETH ≈ $50k at $2900/ETH. +INITIAL_POOL_VALUE = 50_000.0 + +# Gas cost = arb profit threshold in USD. +# reclamm-simulations uses profit_threshold = 3e-4 WETH (in token1 units). +# quantammsim's arb_thresh is in USD: 3 * 3e-4 WETH × ~$3000/ETH ≈ $2.70. +ARB_GAS_COST = 2.7 + +DEFAULT_CSV = ( + "/Users/matthew/Projects/reclamm-simulations" + "/data/sim_vs_world_values_AAVE_WETH.csv" +) +ZEROFEE_CSV = ( + "/Users/matthew/Projects/reclamm-simulations" + "/data/sim_vs_world_zerofee_centered_AAVE_WETH.csv" +) +ZEROFEE_MINUTE_CSV = ( + "/Users/matthew/Projects/reclamm-simulations" + "/data/sim_vs_world_zerofee_centered_minute_AAVE_WETH.csv" +) +WORLD_STATE_CSV = ( + "/Users/matthew/Projects/reclamm-simulations" + "/data/sim_vs_world_world_AAVE_WETH.csv" +) +DEFAULT_START = "2025-08-16 00:00:00" +DEFAULT_END = "2026-01-04 00:00:00" +DEFAULT_TOKENS = ["AAVE", "ETH"] +HALF_DAY = 720 # minutes + +# Gas experiment +GAS_CSV_DIR = Path(__file__).resolve().parent.parent / "gas_csvs" +GAS_PERCENTILES = ["50p", "75p", "90p", "95p"] +GAS_SCALE_FACTORS = [0.25, 0.5, 0.75, 1.0] +FLAT_GAS_USD = [0.0, 0.25, 0.50, 1.0, 2.0, 3.0, 5.0] +PROTOCOL_FEE_SPLIT = 0.5 + + +def parse_args(): + p = argparse.ArgumentParser( + description=__doc__, + formatter_class=argparse.RawDescriptionHelpFormatter, + ) + p.add_argument("--csv", default=DEFAULT_CSV) + p.add_argument("--start", default=DEFAULT_START) + p.add_argument("--end", default=DEFAULT_END) + p.add_argument("--tokens", nargs="+", default=DEFAULT_TOKENS) + p.add_argument("--output", default="sim_vs_world_comparison.png") + p.add_argument( + "--gas-experiment", action="store_true", + help="Run gas-experiment sweep (time-varying gas, 50%% protocol fee)", + ) + p.add_argument( + "--launch-params", action="store_true", + help="Use launch params instead of current params in gas experiment", + ) + p.add_argument( + "--gas-scale-sweep", action="store_true", + help="Sweep gas cost scale factors, rebase to world, truncate at governance", + ) + p.add_argument( + "--best-gas", action="store_true", + help="Run the 3 best gas configs vs world (clean plot)", + ) + return p.parse_args() + + +def load_onchain_initial_state(): + """Load the on-chain pool state at t=0 from the world state CSV. + + Returns (state_dict, start_time_str) where state_dict has + Ra, Rb, Va, Vb (token units) and start_time_str is rounded + to the nearest minute for alignment with minute-level price data. + """ + df = pd.read_csv(WORLD_STATE_CSV) + r = df.iloc[0] + state = { + "Ra": float(r.balance_0), + "Rb": float(r.balance_1), + "Va": float(r.virtual_0), + "Vb": float(r.virtual_1), + } + # Round to nearest minute for price data alignment + ts_sec = int(r.timestamp) + ts_minute = (ts_sec // 60) * 60 + start_str = datetime.utcfromtimestamp(ts_minute).strftime("%Y-%m-%d %H:%M:%S") + return state, start_str + + +def load_world_timestamps(): + """Load Unix timestamps (seconds) from the world state CSV.""" + df = pd.read_csv(WORLD_STATE_CSV) + return df["timestamp"].values + + +def load_world_normalized_balances(): + """Load BPT-normalized on-chain balances and timestamps. + + Normalizes balances to initial BPT supply so that value tracks a + fixed LP position (accounts for joins/exits changing BPT supply). + + Returns (norm_bal_0, norm_bal_1, timestamps_sec). + """ + df = pd.read_csv(WORLD_STATE_CSV) + bpt_0 = df["bpt_supply"].iloc[0] + norm = bpt_0 / df["bpt_supply"].values + return ( + df["balance_0"].values * norm, + df["balance_1"].values * norm, + df["timestamp"].values, + ) + + +def sample_at_timestamps(minute_vals, start_unix_sec, timestamps_sec): + """Sample a minute-level array at specific Unix timestamps. + + For each target timestamp, finds the nearest minute index in the + sim output and returns the corresponding value. + + Parameters + ---------- + minute_vals : array, shape (N,) + Minute-level sim output. + start_unix_sec : float + Unix timestamp (seconds) of minute_vals[0]. + timestamps_sec : array + Unix timestamps (seconds) to sample at. + + Returns + ------- + array : values at the nearest minute to each target timestamp. + """ + indices = np.round((timestamps_sec - start_unix_sec) / 60).astype(int) + indices = np.clip(indices, 0, len(minute_vals) - 1) + return minute_vals[indices] + + +def run_pool(tokens, start, end, rule, fees, params, gas_cost=0.0, + protocol_fee_split=0.0, gas_cost_df=None, + onchain_initial_state=None): + """Run a quantammsim pool and return minute-level results. + + Returns (val_eth, price_ratio, start_unix_sec) where val_eth and + price_ratio are minute-level arrays and start_unix_sec is the Unix + timestamp (seconds) of the first element. + """ + fp = { + "tokens": tokens, + "rule": rule, + "startDateString": start, + "endDateString": end, + "initial_pool_value": INITIAL_POOL_VALUE, + "fees": fees, + "gas_cost": gas_cost, + "arb_fees": 0.0, + "do_arb": True, + "arb_frequency": 1, + "chunk_period": 1440, + "weight_interpolation_period": 1440, + } + if rule == "reclamm": + fp["reclamm_use_shift_exponent"] = True + fp["reclamm_interpolation_method"] = "geometric" + fp["reclamm_centeredness_scaling"] = False + if protocol_fee_split != 0.0: + fp["protocol_fee_split"] = protocol_fee_split + if onchain_initial_state is not None: + fp["reclamm_initial_state"] = onchain_initial_state + + result = do_run_on_historic_data( + run_fingerprint=fp, params=params, gas_cost_df=gas_cost_df, + ) + + # Prices: sorted tokens → [AAVE, ETH] in USD + prices = np.array(result["prices"]) + eth_usd = prices[:, 1] + price_ratio = prices[:, 0] / prices[:, 1] # WETH/AAVE + + # Pool value in ETH + val_eth = np.array(result["value"]) / eth_usd + + # Compute start timestamp from startDateString + start_unix_sec = datetime.strptime( + start, "%Y-%m-%d %H:%M:%S" + ).replace(tzinfo=timezone.utc).timestamp() + + return val_eth, price_ratio, start_unix_sec + + +def load_gas_csv(percentile): + """Load a gas CSV and return a DataFrame with columns [unix, trade_gas_cost_usd]. + + Gas CSV timestamps are offset by ~59s from exact minutes. Round down + to the nearest minute so they align with the simulator's minute-level index. + """ + path = GAS_CSV_DIR / f"Gas_{percentile}.csv" + df = pd.read_csv(path) + df = df.rename(columns={"USD": "trade_gas_cost_usd"}) + df["unix"] = (df["unix"] // 60000) * 60000 # floor to minute boundary + return df + + +def run_gas_experiment(args): + """Run gas-experiment sweep and produce comparison plot.""" + tokens = args.tokens + start, end = args.start, args.end + + # ── Select params ───────────────────────────────────────────────── + if args.launch_params: + param_source = ONCHAIN_LAUNCH_PARAMS + param_label = "launch" + else: + param_source = ONCHAIN_CURRENT_PARAMS + param_label = "current" + pool_params = {k: jnp.array(v) for k, v in param_source.items()} + + # ── Baselines ────────────────────────────────────────────────────── + print("Running Balancer (zero-fee 50/50)...") + bal_params = {"initial_weights_logits": jnp.array([0.0, 0.0])} + bal_eth_min, qsim_price_min, start_sec = run_pool( + tokens, start, end, "balancer", 0.0, bal_params, + ) + + print(f"Running reClAMM ({param_label} params, flat gas, no protocol fee)...") + reclamm_flat_min, _, _ = run_pool( + tokens, start, end, "reclamm", ONCHAIN_FEES, pool_params, + gas_cost=ARB_GAS_COST, + ) + + # ── Load world values from CSV ───────────────────────────────────── + print("Loading reclamm-simulations CSV...") + df = pd.read_csv(args.csv) + + # ── Gas percentile runs ──────────────────────────────────────────── + gas_results_min = {} + for pct in GAS_PERCENTILES: + print(f"Running reClAMM ({param_label} params, gas={pct}, " + f"protocol_fee={PROTOCOL_FEE_SPLIT})...") + gas_df = load_gas_csv(pct) + val_eth_min, _, _ = run_pool( + tokens, start, end, "reclamm", ONCHAIN_FEES, pool_params, + protocol_fee_split=PROTOCOL_FEE_SPLIT, gas_cost_df=gas_df, + ) + gas_results_min[pct] = val_eth_min + + # ── Sample at world timestamps ──────────────────────────────────── + world_ts = load_world_timestamps() + n = min(len(df), len(world_ts)) + world_ts = world_ts[:n] + + bal_eth = sample_at_timestamps(bal_eth_min, start_sec, world_ts) + reclamm_flat_eth = sample_at_timestamps(reclamm_flat_min, start_sec, world_ts) + qsim_price = sample_at_timestamps(qsim_price_min, start_sec, world_ts) + gas_results = { + pct: sample_at_timestamps(v, start_sec, world_ts) + for pct, v in gas_results_min.items() + } + + csv_world = df["world"].values[:n] + csv_feeless = df["feeless weighted"].values[:n] + print(f" Aligned: {n} world-timestamp points") + t = np.arange(n) + + # Governance half-day index + gov_unix = datetime.strptime( + GOVERNANCE_DATE, "%Y-%m-%d" + ).replace(tzinfo=timezone.utc).timestamp() + gov_idx = np.searchsorted(world_ts, gov_unix) + + # ── Plot: relative to feeless weighted ───────────────────────────── + fig, (ax_price, ax_rel) = plt.subplots(2, 1, figsize=(14, 9), + gridspec_kw={"height_ratios": [1, 2]}) + + # Top: price + ax_price.plot(t, qsim_price, color="gray", alpha=0.6, linewidth=1) + ax_price.set_ylabel("AAVE/ETH") + ax_price.set_title("Price") + ax_price.set_ylim(bottom=0) + if gov_idx < n: + ax_price.axvline(x=gov_idx, color="gray", linestyle=":", alpha=0.6) + + # Bottom: relative values + ax_rel.axhline(y=1.0, color="blue", linewidth=2, label="feeless weighted") + + # Flat-gas baseline (no protocol fee) + flat_rel = reclamm_flat_eth / bal_eth + ax_rel.plot(t, flat_rel, linewidth=2, color="gray", linestyle="--", + label=f"flat gas ${ARB_GAS_COST}, no protocol fee") + + # Gas percentile runs + colors = {"50p": "#2ca02c", "75p": "#ff7f0e", "90p": "#d62728", "95p": "#9467bd"} + for pct in GAS_PERCENTILES: + vals = gas_results[pct] + rel = vals / bal_eth + ax_rel.plot(t, rel, linewidth=1.5, color=colors[pct], + label=f"gas {pct}, {int(PROTOCOL_FEE_SPLIT*100)}% protocol fee") + + # World values + world_rel = csv_world / csv_feeless + ax_rel.plot(t, world_rel, linewidth=1.5, marker=".", markersize=2, + color="brown", label="world (on-chain)") + + ax_rel.set_xlabel("half days") + ax_rel.set_ylabel("value / feeless weighted") + ax_rel.set_title("LP value relative to feeless weighted (Balancer 50/50)") + ax_rel.legend(fontsize=8, loc="lower left") + ax_rel.grid(True, alpha=0.2) + if gov_idx < n: + ax_rel.axvline(x=gov_idx, color="gray", linestyle=":", alpha=0.6) + ax_rel.text(gov_idx + 1, ax_rel.get_ylim()[1] * 0.98, + "governance", fontsize=7, color="gray", va="top") + + tokens_str = "/".join(tokens) + fig.suptitle( + f"reClAMM gas experiment ({param_label} params) — {tokens_str}\n" + f"params: {list(param_source.values())}, " + f"fees: {ONCHAIN_FEES}, protocol fee: {PROTOCOL_FEE_SPLIT}", + fontsize=10, + ) + plt.tight_layout() + out = args.output.replace(".png", f"_gas_experiment_{param_label}.png") + plt.savefig(out, dpi=150, bbox_inches="tight") + print(f"\nSaved: {out}") + plt.close() + + # ── Summary table ────────────────────────────────────────────────── + print(f"\n{'Scenario':<45} {'Final rel':>10} {'vs world':>10}") + print("-" * 65) + world_final_rel = world_rel[-1] if len(world_rel) > 0 else float("nan") + print(f"{'Flat gas, no protocol fee':<45} {flat_rel[-1]:>10.4f} " + f"{flat_rel[-1] - world_final_rel:>+10.4f}") + for pct in GAS_PERCENTILES: + rel = gas_results[pct] / bal_eth + print(f"{'Gas ' + pct + f', {int(PROTOCOL_FEE_SPLIT*100)}% protocol fee':<45} " + f"{rel[-1]:>10.4f} {rel[-1] - world_final_rel:>+10.4f}") + print(f"{'World (on-chain)':<45} {world_final_rel:>10.4f}") + + +def run_gas_scale_experiment(args): + """Sweep gas cost scale factors, rebase to world, truncate at governance.""" + tokens = args.tokens + end = args.end + + if args.launch_params: + param_source = ONCHAIN_LAUNCH_PARAMS + param_label = "launch" + else: + param_source = ONCHAIN_CURRENT_PARAMS + param_label = "current" + pool_params = {k: jnp.array(v) for k, v in param_source.items()} + + # Load on-chain initial state and derive start time + onchain_state, onchain_start = load_onchain_initial_state() + start = onchain_start + print(f"On-chain initial state: Ra={onchain_state['Ra']:.2f}, " + f"Rb={onchain_state['Rb']:.2f}, Va={onchain_state['Va']:.2f}, " + f"Vb={onchain_state['Vb']:.2f}") + print(f"Sim start time (from on-chain): {start}") + + # Load world + reclamm-simulations values + print("Loading reclamm-simulations CSV...") + df = pd.read_csv(args.csv) + + # Load world timestamps and find governance cutoff + world_ts = load_world_timestamps() + gov_unix = datetime.strptime( + GOVERNANCE_DATE, "%Y-%m-%d" + ).replace(tzinfo=timezone.utc).timestamp() + gov_idx = np.searchsorted(world_ts, gov_unix) + + # Run all (percentile, scale) combinations + results_min = {} + price_ratio_min = None + for pct in GAS_PERCENTILES: + gas_df_raw = load_gas_csv(pct) + for scale in GAS_SCALE_FACTORS: + label = f"{pct} × {scale}" + print(f"Running reClAMM ({param_label}, gas={label}, " + f"protocol_fee={PROTOCOL_FEE_SPLIT})...") + gas_df = gas_df_raw.copy() + gas_df["trade_gas_cost_usd"] = gas_df_raw["trade_gas_cost_usd"] * scale + val_eth_min, pr_min, start_sec = run_pool( + tokens, start, end, "reclamm", ONCHAIN_FEES, pool_params, + protocol_fee_split=PROTOCOL_FEE_SPLIT, gas_cost_df=gas_df, + onchain_initial_state=onchain_state, + ) + results_min[(pct, scale)] = (val_eth_min, start_sec) + if price_ratio_min is None: + price_ratio_min = pr_min + + # Flat gas cost runs + flat_results_min = {} + for gas_usd in FLAT_GAS_USD: + print(f"Running reClAMM ({param_label}, flat gas=${gas_usd}, " + f"protocol_fee={PROTOCOL_FEE_SPLIT})...") + val_eth_min, _, start_sec = run_pool( + tokens, start, end, "reclamm", ONCHAIN_FEES, pool_params, + gas_cost=gas_usd, protocol_fee_split=PROTOCOL_FEE_SPLIT, + onchain_initial_state=onchain_state, + ) + flat_results_min[gas_usd] = (val_eth_min, start_sec) + + # ── World values: on-chain balances × quantammsim prices ────────── + world_bal_0, world_bal_1, world_ts = load_world_normalized_balances() + + # reclamm-sim comparison uses its own CSV (self-consistent pricing) + csv_world = df["world"].values + csv_sim = df["simulation"].values + + n = min(gov_idx, len(world_bal_0), len(csv_world), len(csv_sim), len(world_ts)) + print(f" Truncated at governance: {n} world-timestamp points") + t = np.arange(n) + world_ts_trunc = world_ts[:n] + + # Repriced world for quantammsim comparison + price_at_world = sample_at_timestamps( + price_ratio_min, start_sec, world_ts_trunc, + ) + world_val = world_bal_0[:n] * price_at_world + world_bal_1[:n] + world_growth = world_val / world_val[0] + + # CSV-based world for reclamm-sim comparison (self-consistent pricing) + csv_world = csv_world[:n] + csv_sim = csv_sim[:n] + world_growth_csv = csv_world / csv_world[0] + recsim_growth = csv_sim / csv_sim[0] + + # Sample all sim runs at world timestamps + start_sec = flat_results_min[FLAT_GAS_USD[0]][1] + + results = {} + for key, (val_min, _) in results_min.items(): + results[key] = sample_at_timestamps(val_min, start_sec, world_ts_trunc) + + flat_results = {} + for gas_usd, (val_min, _) in flat_results_min.items(): + flat_results[gas_usd] = sample_at_timestamps(val_min, start_sec, world_ts_trunc) + + # Compute growth ratios + flat_growths = {} + for gas_usd in FLAT_GAS_USD: + vals = flat_results[gas_usd] + flat_growths[gas_usd] = vals / vals[0] + + # ── Plot (% deviation from world: positive = sim below world) ─── + fig, (ax_ts, ax_pct, ax_flat) = plt.subplots( + 1, 3, figsize=(20, 7), gridspec_kw={"width_ratios": [3, 1, 1]}, + ) + + # Left: time series of % deviation from world + ax_ts.axhline(y=0.0, color="brown", linewidth=2, label="world (on-chain)") + + # reclamm-simulations (uses CSV-based world for self-consistent pricing) + recsim_dev = (1 - recsim_growth / world_growth_csv) * 100 + ax_ts.plot(t, recsim_dev, color="red", linewidth=2, + linestyle="--", label="reclamm-sim") + + # Gas scale sweep (percentile-based) + colors = {"50p": "#2ca02c", "75p": "#ff7f0e", "90p": "#d62728", "95p": "#9467bd"} + for pct in GAS_PERCENTILES: + for scale in GAS_SCALE_FACTORS: + vals = results[(pct, scale)] + sim_growth = vals / vals[0] + dev = (1 - sim_growth / world_growth) * 100 + alpha = 0.3 + 0.7 * scale + lw = 0.8 + 1.2 * scale + if scale == 1.0: + label = f"{pct} × {scale}" + elif pct == "50p": + label = f"50p × {scale}" + else: + label = None + ax_ts.plot(t, dev, color=colors[pct], alpha=alpha, + linewidth=lw, label=label) + + # Flat gas runs + flat_cmap = plt.cm.copper + for i, gas_usd in enumerate(FLAT_GAS_USD): + c = flat_cmap(i / max(len(FLAT_GAS_USD) - 1, 1)) + dev = (1 - flat_growths[gas_usd] / world_growth) * 100 + ax_ts.plot(t, dev, color=c, linewidth=1.5, linestyle="-.", + label=f"flat ${gas_usd}") + + ax_ts.set_xlabel("half days") + ax_ts.set_ylabel("% deviation from world") + ax_ts.set_title("LP value vs world (pre-governance)") + ax_ts.legend(fontsize=6, loc="best", ncol=2) + ax_ts.grid(True, alpha=0.2) + + # Reference lines for both summary panels (as % deviation) + recsim_final_dev = (1 - recsim_growth[-1] / world_growth_csv[-1]) * 100 + + # Middle: final % deviation vs percentile scale factor + ax_pct.axhline(y=0.0, color="brown", linewidth=2, label="world") + ax_pct.axhline(y=recsim_final_dev, color="red", linewidth=1.5, + linestyle="--", label=f"reclamm-sim ({recsim_final_dev:+.2f}%)") + + for pct in GAS_PERCENTILES: + finals = [] + for scale in GAS_SCALE_FACTORS: + vals = results[(pct, scale)] + sim_growth = vals / vals[0] + finals.append((1 - sim_growth[-1] / world_growth[-1]) * 100) + ax_pct.plot(GAS_SCALE_FACTORS, finals, marker="o", + color=colors[pct], linewidth=2, label=pct) + + ax_pct.set_xlabel("gas scale factor\n(1.0 = 450k gas)") + ax_pct.set_ylabel("% deviation from world") + ax_pct.set_title("Percentile gas") + ax_pct.legend(fontsize=6) + ax_pct.grid(True, alpha=0.2) + + # Right: final % deviation vs flat gas cost + ax_flat.axhline(y=0.0, color="brown", linewidth=2, label="world") + ax_flat.axhline(y=recsim_final_dev, color="red", linewidth=1.5, + linestyle="--", label=f"reclamm-sim ({recsim_final_dev:+.2f}%)") + + flat_finals = [] + for gas_usd in FLAT_GAS_USD: + flat_finals.append( + (1 - flat_growths[gas_usd][-1] / world_growth[-1]) * 100 + ) + ax_flat.plot(FLAT_GAS_USD, flat_finals, marker="s", color="black", + linewidth=2, label="flat gas") + + ax_flat.set_xlabel("flat gas cost (USD)") + ax_flat.set_ylabel("% deviation from world") + ax_flat.set_title("Flat gas") + ax_flat.legend(fontsize=6) + ax_flat.grid(True, alpha=0.2) + + tokens_str = "/".join(tokens) + fig.suptitle( + f"reClAMM gas sweep ({param_label} params) — {tokens_str}\n" + f"params: {list(param_source.values())}, " + f"fees: {ONCHAIN_FEES}, protocol fee: {PROTOCOL_FEE_SPLIT}", + fontsize=10, + ) + plt.tight_layout() + out = args.output.replace(".png", f"_gas_scale_{param_label}.png") + plt.savefig(out, dpi=150, bbox_inches="tight") + print(f"\nSaved: {out}") + plt.close() + + # ── Summary table (% deviation from world) ───────────────────────── + print(f"\n{'Scenario':<35} {'% dev from world':>16}") + print("-" * 52) + print(f"{'reclamm-sim':<35} {recsim_final_dev:>+16.2f}%") + print() + for gas_usd in FLAT_GAS_USD: + dev = (1 - flat_growths[gas_usd][-1] / world_growth[-1]) * 100 + print(f"{'Flat $' + f'{gas_usd}':<35} {dev:>+16.2f}%") + print() + for pct in GAS_PERCENTILES: + for scale in GAS_SCALE_FACTORS: + vals = results[(pct, scale)] + sim_growth = vals / vals[0] + dev = (1 - sim_growth[-1] / world_growth[-1]) * 100 + print(f"{'Gas ' + pct + f' × {scale}':<35} {dev:>+16.2f}%") + + +def run_best_gas_experiment(args): + """Run the 3 best gas configs vs world on a clean single-panel plot.""" + tokens = args.tokens + end = args.end + + if args.launch_params: + param_source = ONCHAIN_LAUNCH_PARAMS + param_label = "launch" + else: + param_source = ONCHAIN_CURRENT_PARAMS + param_label = "current" + pool_params = {k: jnp.array(v) for k, v in param_source.items()} + + # Load on-chain initial state and derive start time + onchain_state, onchain_start = load_onchain_initial_state() + start = onchain_start + print(f"On-chain initial state: Ra={onchain_state['Ra']:.2f}, " + f"Rb={onchain_state['Rb']:.2f}, Va={onchain_state['Va']:.2f}, " + f"Vb={onchain_state['Vb']:.2f}") + print(f"Sim start time (from on-chain): {start}") + + # Find governance cutoff from world timestamps + world_ts_all = load_world_timestamps() + gov_unix = datetime.strptime( + GOVERNANCE_DATE, "%Y-%m-%d" + ).replace(tzinfo=timezone.utc).timestamp() + gov_idx = np.searchsorted(world_ts_all, gov_unix) + + # ── The best configs ───────────────────────────────────────────── + configs = [ + ("Flat $1.00", "black", "-"), + ("50p × 1.0", "#2ca02c", "-"), + ("75p × 0.75", "#ff7f0e", "-"), + ("90p × 0.25", "#d62728", "-"), + ] + + # 1) Flat $1.00 + print(f"Running reClAMM ({param_label}, flat gas=$1.00, " + f"protocol_fee={PROTOCOL_FEE_SPLIT})...") + flat1_min, price_ratio_min, start_sec = run_pool( + tokens, start, end, "reclamm", ONCHAIN_FEES, pool_params, + gas_cost=1.0, protocol_fee_split=PROTOCOL_FEE_SPLIT, + onchain_initial_state=onchain_state, + ) + + # 2) 50p × 1.0 + print(f"Running reClAMM ({param_label}, gas=50p × 1.0, " + f"protocol_fee={PROTOCOL_FEE_SPLIT})...") + gas_df_50p = load_gas_csv("50p") + g50_min, _, _ = run_pool( + tokens, start, end, "reclamm", ONCHAIN_FEES, pool_params, + protocol_fee_split=PROTOCOL_FEE_SPLIT, gas_cost_df=gas_df_50p, + onchain_initial_state=onchain_state, + ) + + # 3) 75p × 0.75 + print(f"Running reClAMM ({param_label}, gas=75p × 0.75, " + f"protocol_fee={PROTOCOL_FEE_SPLIT})...") + gas_df_75p = load_gas_csv("75p") + gas_df_75p_scaled = gas_df_75p.copy() + gas_df_75p_scaled["trade_gas_cost_usd"] *= 0.75 + g75_min, _, _ = run_pool( + tokens, start, end, "reclamm", ONCHAIN_FEES, pool_params, + protocol_fee_split=PROTOCOL_FEE_SPLIT, gas_cost_df=gas_df_75p_scaled, + onchain_initial_state=onchain_state, + ) + + # 4) 90p × 0.25 + print(f"Running reClAMM ({param_label}, gas=90p × 0.25, " + f"protocol_fee={PROTOCOL_FEE_SPLIT})...") + gas_df_90p = load_gas_csv("90p") + gas_df_90p_scaled = gas_df_90p.copy() + gas_df_90p_scaled["trade_gas_cost_usd"] *= 0.25 + g90_min, _, _ = run_pool( + tokens, start, end, "reclamm", ONCHAIN_FEES, pool_params, + protocol_fee_split=PROTOCOL_FEE_SPLIT, gas_cost_df=gas_df_90p_scaled, + onchain_initial_state=onchain_state, + ) + + # ── World values: on-chain balances × quantammsim prices ────────── + # Both sim and world valued at the same price at each point, + # so price fluctuations cancel in the growth ratio comparison. + world_bal_0, world_bal_1, world_ts = load_world_normalized_balances() + n = min(gov_idx, len(world_bal_0), len(world_ts)) + print(f" Truncated at governance: {n} world-timestamp points") + t = np.arange(n) + world_ts_trunc = world_ts[:n] + + # Sample quantammsim price ratio at world timestamps + price_at_world = sample_at_timestamps( + price_ratio_min, start_sec, world_ts_trunc, + ) + # World value in ETH = norm_AAVE * (AAVE/ETH) + norm_ETH + world_val = world_bal_0[:n] * price_at_world + world_bal_1[:n] + world_growth = world_val / world_val[0] + + run_vals = [ + sample_at_timestamps(flat1_min, start_sec, world_ts_trunc), + sample_at_timestamps(g50_min, start_sec, world_ts_trunc), + sample_at_timestamps(g75_min, start_sec, world_ts_trunc), + sample_at_timestamps(g90_min, start_sec, world_ts_trunc), + ] + + growths = [v / v[0] for v in run_vals] + + # ── Plot ────────────────────────────────────────────────────────── + fig, ax = plt.subplots(figsize=(14, 6)) + + ax.axhline(y=0.0, color="brown", linewidth=2, label="world (on-chain)") + + # Best 3 + for (label, color, ls), g in zip(configs, growths): + dev = (1 - g / world_growth) * 100 + final_dev = dev[-1] + ax.plot(t, dev, color=color, linewidth=2, linestyle=ls, + label=f"{label} (final {final_dev:+.2f}%)") + + ax.set_xlabel("half days") + ax.set_ylabel("% deviation from world") + ax.set_title( + f"Best gas configs vs world ({param_label} params) — " + f"{'/'.join(tokens)}\n" + f"params: {list(param_source.values())}, " + f"fees: {ONCHAIN_FEES}, protocol fee: {PROTOCOL_FEE_SPLIT}", + ) + ax.legend(fontsize=9, loc="best") + ax.grid(True, alpha=0.3) + + plt.tight_layout() + out = args.output.replace(".png", f"_best_gas_{param_label}.png") + plt.savefig(out, dpi=150, bbox_inches="tight") + print(f"\nSaved: {out}") + plt.close() + + # Summary + labels = [c[0] for c in configs] + print(f"\n{'Scenario':<25} {'% dev from world':>16}") + print("-" * 42) + for label, g in zip(labels, growths): + dev = (1 - g[-1] / world_growth[-1]) * 100 + print(f"{label:<25} {dev:>+16.2f}%") + + +def main(): + args = parse_args() + + if args.best_gas: + run_best_gas_experiment(args) + return + + if args.gas_scale_sweep: + run_gas_scale_experiment(args) + return + + if args.gas_experiment: + run_gas_experiment(args) + return + + # ── Load CSVs ───────────────────────────────────────────────────── + print("Loading reclamm-simulations CSV...") + df = pd.read_csv(args.csv) + n_csv = len(df) + print(f" {n_csv} half-day points") + + print("Loading zero-fee minute-level CSV...") + df_zf_min = pd.read_csv(ZEROFEE_MINUTE_CSV) + print(f" {len(df_zf_min)} minute points") + + # Load world timestamps for alignment + world_ts = load_world_timestamps() + + # ── Run quantammsim pools (minute-level) ────────────────────────── + print("Running Balancer (zero-fee 50/50)...") + bal_params = {"initial_weights_logits": jnp.array([0.0, 0.0])} + bal_eth_min, qsim_price_min, start_sec = run_pool( + args.tokens, args.start, args.end, "balancer", 0.0, bal_params, + ) + + print("Running reClAMM (launch, zero-fee, zero-gas)...") + launch_params = {k: jnp.array(v) for k, v in ONCHAIN_LAUNCH_PARAMS.items()} + reclamm_zerofee_min, _, _ = run_pool( + args.tokens, args.start, args.end, "reclamm", 0.0, launch_params, + gas_cost=0.0, + ) + + print(f"Running reClAMM (launch params, gas=${ARB_GAS_COST})...") + reclamm_launch_min, _, _ = run_pool( + args.tokens, args.start, args.end, "reclamm", ONCHAIN_FEES, launch_params, + gas_cost=ARB_GAS_COST, + ) + + print(f"Running reClAMM (current params, gas=${ARB_GAS_COST})...") + current_params = {k: jnp.array(v) for k, v in ONCHAIN_CURRENT_PARAMS.items()} + reclamm_current_min, _, _ = run_pool( + args.tokens, args.start, args.end, "reclamm", ONCHAIN_FEES, current_params, + gas_cost=ARB_GAS_COST, + ) + + # ── Sample at world timestamps ──────────────────────────────────── + n = min(n_csv, len(world_ts)) + world_ts_trunc = world_ts[:n] + + bal_eth = sample_at_timestamps(bal_eth_min, start_sec, world_ts_trunc) + reclamm_zerofee_eth = sample_at_timestamps(reclamm_zerofee_min, start_sec, world_ts_trunc) + reclamm_launch_eth = sample_at_timestamps(reclamm_launch_min, start_sec, world_ts_trunc) + reclamm_current_eth = sample_at_timestamps(reclamm_current_min, start_sec, world_ts_trunc) + qsim_price = sample_at_timestamps(qsim_price_min, start_sec, world_ts_trunc) + + print(f" Aligned: {n} world-timestamp points " + f"(qsim minutes={len(bal_eth_min)}, csv={n_csv})") + t = np.arange(n) + + csv_price = df["price"].values[:n] + csv_feeless = df["feeless weighted"].values[:n] + csv_sim = df["simulation"].values[:n] + csv_hold = df["hold"].values[:n] + csv_world = df["world"].values[:n] + + # Governance change index + gov_unix = datetime.strptime( + GOVERNANCE_DATE, "%Y-%m-%d" + ).replace(tzinfo=timezone.utc).timestamp() + gov_idx = np.searchsorted(world_ts_trunc, gov_unix) + + # Normalize quantammsim to same starting value as CSV + v0 = csv_feeless[0] + bal_norm = bal_eth * (v0 / bal_eth[0]) + zerofee_norm = reclamm_zerofee_eth * (v0 / reclamm_zerofee_eth[0]) + launch_norm = reclamm_launch_eth * (v0 / reclamm_launch_eth[0]) + current_norm = reclamm_current_eth * (v0 / reclamm_current_eth[0]) + + # Relative values (÷ respective feeless weighted baseline) + zerofee_rel = reclamm_zerofee_eth / bal_eth + launch_rel = reclamm_launch_eth / bal_eth + current_rel = reclamm_current_eth / bal_eth + csv_sim_rel = csv_sim / csv_feeless + csv_hold_rel = csv_hold / csv_feeless + csv_world_rel = csv_world / csv_feeless + + # ── Plot ────────────────────────────────────────────────────────── + fig, axs = plt.subplots(2, 2, figsize=(13, 8)) + + # Top-left: price + axs[0][0].plot(t, csv_price, label="reclamm-sim", alpha=0.8) + axs[0][0].plot(t, qsim_price, label="quantammsim", alpha=0.8, linestyle="--") + axs[0][0].set_ylabel("WETH/AAVE") + axs[0][0].set_title("Price") + axs[0][0].set_ylim(bottom=0) + axs[0][0].legend(fontsize=8) + if gov_idx < n: + axs[0][0].axvline(x=gov_idx, color="gray", linestyle=":", alpha=0.6) + + # Top-right: remove (legend is on other panels) + axs[0][1].remove() + + # Bottom-left: absolute values in WETH + axs[1][0].plot(t, bal_norm, label="qsim feeless weighted", linewidth=2, color="blue") + axs[1][0].plot(t, launch_norm, label="qsim reClAMM (launch)", linewidth=2, color="orange") + axs[1][0].plot(t, current_norm, label="qsim reClAMM (current)", linewidth=2, + color="purple", linestyle="-.") + axs[1][0].plot(t, csv_sim, label="reclamm-sim simulation", linewidth=1.5, + linestyle="--", color="red") + axs[1][0].plot(t, csv_hold, label="hold", linewidth=1.5, color="green") + axs[1][0].plot(t, csv_world, label="world values", linewidth=1.5, + marker=".", markersize=2, color="brown") + axs[1][0].set_title("Value histories") + axs[1][0].set_xlabel("half days") + axs[1][0].set_ylabel("Value in WETH") + axs[1][0].set_ylim(bottom=0) + axs[1][0].legend(fontsize=7, loc="upper right") + if gov_idx < n: + axs[1][0].axvline(x=gov_idx, color="gray", linestyle=":", alpha=0.6) + axs[1][0].text(gov_idx + 1, axs[1][0].get_ylim()[1] * 0.95, + "governance", fontsize=7, color="gray", va="top") + + # Bottom-right: relative to feeless weighted + axs[1][1].axhline(y=1.0, color="blue", linewidth=2, label="feeless weighted") + axs[1][1].plot(t, launch_rel, label="qsim reClAMM (launch)", linewidth=2, color="orange") + axs[1][1].plot(t, current_rel, label="qsim reClAMM (current)", linewidth=2, + color="purple", linestyle="-.") + axs[1][1].plot(t, csv_sim_rel, label="reclamm-sim simulation", linewidth=1.5, + linestyle="--", color="red") + axs[1][1].plot(t, csv_hold_rel, label="hold", linewidth=1.5, color="green") + axs[1][1].plot(t, csv_world_rel, label="world values", linewidth=1.5, + marker=".", markersize=2, color="brown") + axs[1][1].set_title("Value relative to feeless weighted") + axs[1][1].set_xlabel("half days") + axs[1][1].set_ylabel("relative value") + axs[1][1].legend(fontsize=7, loc="lower left") + if gov_idx < n: + axs[1][1].axvline(x=gov_idx, color="gray", linestyle=":", alpha=0.6) + + tokens_str = "/".join(args.tokens) + fig.suptitle( + f"quantammsim vs reclamm-simulations — {tokens_str}\n" + f"Launch: {list(ONCHAIN_LAUNCH_PARAMS.values())}, " + f"Current: {list(ONCHAIN_CURRENT_PARAMS.values())}, " + f"fees: {ONCHAIN_FEES}", + fontsize=10, + ) + plt.tight_layout() + plt.savefig(args.output, dpi=150, bbox_inches="tight") + print(f"\nSaved: {args.output}") + + # ── Zero-fee comparison plot (minute-level) ─────────────────────── + # Revalue reclamm-sim balances at quantammsim's price so both sides + # use the same price and the comparison is purely about balances. + # Skip row 0 of the CSV (initial state before first arb) to align + # with quantammsim's reserves[0] which is post-first-step. + ext_bal_0 = df_zf_min["balance_0"].values[1:] + ext_bal_1 = df_zf_min["balance_1"].values[1:] + n_zf = min(len(reclamm_zerofee_min), len(ext_bal_0), len(qsim_price_min)) + ext_val_repriced = ( + ext_bal_0[:n_zf] * qsim_price_min[:n_zf] + ext_bal_1[:n_zf] + ) + qsim_growth = reclamm_zerofee_min[:n_zf] / reclamm_zerofee_min[0] + ext_growth = ext_val_repriced[:n_zf] / ext_val_repriced[0] + pct_dev = (qsim_growth / ext_growth - 1) * 100 + days = np.arange(n_zf) / 1440 + + zerofee_title = ( + f"Zero-fee zero-gas reClAMM: quantammsim / reclamm-sim (minute-level) — {tokens_str}\n" + f"params: {list(ONCHAIN_LAUNCH_PARAMS.values())}" + ) + daily_smooth = pd.Series(pct_dev).rolling(1440, center=True, min_periods=720).mean() + + # Plot 1: with daily smoothing overlay + fig2, ax2 = plt.subplots(figsize=(12, 5)) + ax2.plot(days, pct_dev, linewidth=0.5, color="teal", alpha=0.6) + ax2.plot(days, daily_smooth, linewidth=2, color="darkblue", label="daily smoothed") + ax2.axhline(y=0.0, color="gray", linestyle="--", alpha=0.6) + ax2.set_xlabel("days") + ax2.set_ylabel("deviation (%)") + ax2.set_title(zerofee_title, fontsize=11) + ax2.legend(fontsize=9) + ax2.grid(True, alpha=0.3) + plt.tight_layout() + zerofee_path = args.output.replace(".png", "_zerofee_ratio.png") + plt.savefig(zerofee_path, dpi=150, bbox_inches="tight") + print(f"Saved: {zerofee_path}") + plt.close() + + # Plot 2: raw minute-level only (no smoothing) + fig3, ax3 = plt.subplots(figsize=(12, 5)) + ax3.plot(days, pct_dev, linewidth=0.5, color="teal", alpha=0.8) + ax3.axhline(y=0.0, color="gray", linestyle="--", alpha=0.6) + ax3.set_xlabel("days") + ax3.set_ylabel("deviation (%)") + ax3.set_title(zerofee_title, fontsize=11) + ax3.grid(True, alpha=0.3) + plt.tight_layout() + zerofee_raw_path = args.output.replace(".png", "_zerofee_ratio_raw.png") + plt.savefig(zerofee_raw_path, dpi=150, bbox_inches="tight") + print(f"Saved: {zerofee_raw_path}") + plt.close() + + +if __name__ == "__main__": + main() diff --git a/scripts/tune_reclamm_calibrated_noise.py b/scripts/tune_reclamm_calibrated_noise.py new file mode 100644 index 0000000..e0d6ed9 --- /dev/null +++ b/scripts/tune_reclamm_calibrated_noise.py @@ -0,0 +1,408 @@ +"""Optuna tuning of reClAMM pool parameters with calibrated noise models. + +Supports noise model modes: + --noise-model none (pure arb, no noise traders) + --noise-model calibrated (legacy 8-covariate model, AAVE/ETH only) + --noise-model market_linear (per-pool model with market features) + --noise-model mm_observed (MM model + DeFi Llama competitor TVL) + +The market_linear model uses precomputed daily arrays from the per-pool +calibrated noise model artifact (results/linear_market_noise/). It evaluates: + + log(V_noise) = base_t + tvl_coeff_t * log(effective_TVL) + +where base_t absorbs all non-TVL terms (market regime, token volatility, +pair volatility, day-of-week, cross-pool volumes) and tvl_coeff_t is the +effective TVL coefficient including interaction terms. + +Default pool: AAVE/ETH (0x9d1fcf346ea1b0). Use --tokens to override. + +Usage: + cd + source ~/miniconda3/etc/profile.d/conda.sh && conda activate qsim_reclamm_public + + # AAVE/ETH with market_linear noise (default) + python scripts/tune_reclamm_calibrated_noise.py + + # COW/ETH with no noise model + python scripts/tune_reclamm_calibrated_noise.py --tokens COW ETH --noise-model none + + # All objectives + python scripts/tune_reclamm_calibrated_noise.py --all-objectives + + # More trials + python scripts/tune_reclamm_calibrated_noise.py --n-trials 200 +""" + +import argparse +import json +import math +import numpy as np +from pathlib import Path +from quantammsim.runners.jax_runners import train_on_historic_data + +DEFAULT_POOL_ID = "0x9d1fcf346ea1b0" # AAVE/WETH Mainnet +DEFAULT_TOKENS = ["AAVE", "ETH"] + +# --- Legacy 8-covariate noise coefficients --- +NOISE_COEFFS_LEGACY = [ + -0.453, # c_0: intercept + 0.025, # c_1: log(TVL) + -0.060, # c_2: log(sigma) + 0.310, # c_3: log(TVL) * log(sigma) + -0.149, # c_4: log(TVL) * fee + 0.359, # c_5: log(sigma) * fee + 0.061, # c_6: dow_sin + 0.060, # c_7: dow_cos +] +LEGACY_LOG_CADENCE = 2.68 +LEGACY_ARB_FREQUENCY = max(1, round(math.exp(LEGACY_LOG_CADENCE))) # ~15 min + +PARAMETER_CONFIG = { + "price_ratio": {"low": 1.01, "high": 200.0, "log_scale": True, "scalar": True}, + "centeredness_margin": {"low": 0.01, "high": 0.99, "scalar": True}, + "shift_exponent": {"low": 1e-5, "high": 125.0, "log_scale": True, "scalar": True}, +} + +OBJECTIVES = [ + "daily_log_sharpe", "daily_log_sharpe_excess", + "returns_over_hodl", "fee_revenue_over_value", + "calmar", "sterling", "weekly_rovar", +] + + +def _build_market_linear_arrays(args, pool_id, tokens): + """Precompute noise arrays from the per-pool market noise model artifact.""" + from quantammsim.calibration.noise_model_arrays import build_simulator_arrays + + # Parse dates — strip time component for the array builder + start = args.start_date.split(" ")[0] + end = args.end_test_date.split(" ")[0] + + print(f" Building market_linear noise arrays for {pool_id}...") + print(f" Date range: {start} → {end}") + arrays = build_simulator_arrays( + token_a=tokens[0], + token_b=tokens[1], + start_date=start, + end_date=end, + artifact_dir=args.artifact_dir, + pool_id=pool_id, + ) + print(f" {arrays['n_days']} days, {arrays['n_minutes']} minutes") + print(f" noise_base range: [{arrays['noise_base'].min():.2f}," + f" {arrays['noise_base'].max():.2f}]") + print(f" noise_tvl_coeff range: [{arrays['noise_tvl_coeff'].min():.4f}," + f" {arrays['noise_tvl_coeff'].max():.4f}]") + + # Save arrays to disk (fingerprint can't hold numpy arrays — it gets JSON-serialized) + import os + cache_dir = os.path.join(args.artifact_dir, "_sim_arrays") + os.makedirs(cache_dir, exist_ok=True) + arrays_path = os.path.join(cache_dir, f"{pool_id}_{start}_{end}.npz") + np.savez(arrays_path, + noise_base=arrays["noise_base"], + noise_tvl_coeff=arrays["noise_tvl_coeff"], + tvl_mean=arrays["tvl_mean"], + tvl_std=arrays["tvl_std"]) + print(f" Saved arrays: {arrays_path}") + + # Get learned cadence from artifact + from quantammsim.calibration.noise_model_arrays import load_artifact, _find_pool_index + art, meta = load_artifact(args.artifact_dir) + pool_idx = _find_pool_index(pool_id, meta["pool_ids"]) + if pool_idx >= 0: + learned_cadence = float(np.exp(art["log_cadence"][pool_idx])) + print(f" Learned cadence: {learned_cadence:.1f} min") + else: + learned_cadence = 5.0 + print(f" Pool not in calibration set, using default cadence: {learned_cadence}") + + return arrays_path, max(1, round(learned_cadence)) + + +def _build_mm_observed_arrays(args, pool_id, tokens): + """Precompute noise arrays from the MM model + DeFi Llama competitor TVL.""" + from quantammsim.calibration.noise_model_arrays import ( + build_mm_simulator_arrays, load_artifact, _find_pool_index, + ) + + start = args.start_date.split(" ")[0] + end = args.end_test_date.split(" ")[0] + + print(f" Building mm_observed noise arrays for {pool_id}...") + print(f" Date range: {start} → {end}") + arrays = build_mm_simulator_arrays( + token_a=tokens[0], + token_b=tokens[1], + start_date=start, + end_date=end, + mm_artifact_dir=args.artifact_dir, + competitor_tvl_path=args.competitor_tvl_path, + pool_id=pool_id, + ) + print(f" {arrays['n_days']} days, {arrays['n_minutes']} minutes") + print(f" noise_base range: [{arrays['noise_base'].min():.2f}," + f" {arrays['noise_base'].max():.2f}]") + print(f" competitor_tvl range: [${np.exp(np.log(arrays['competitor_tvl'].max())):.0f}]") + + # Save arrays to disk + import os + cache_dir = os.path.join(args.artifact_dir, "_sim_arrays") + os.makedirs(cache_dir, exist_ok=True) + arrays_path = os.path.join(cache_dir, f"{pool_id}_{start}_{end}_mm.npz") + np.savez(arrays_path, + noise_base=arrays["noise_base"], + competitor_tvl=arrays["competitor_tvl"]) + print(f" Saved arrays: {arrays_path}") + + # Get cadence from MM model artifact + art, meta = load_artifact(args.artifact_dir) + pool_idx = _find_pool_index(pool_id, meta["pool_ids"]) + if pool_idx >= 0 and "log_cadence" in art: + learned_cadence = float(np.exp(art["log_cadence"][pool_idx])) + print(f" Learned cadence: {learned_cadence:.1f} min") + else: + learned_cadence = 5.0 + print(f" Using default cadence: {learned_cadence}") + + return arrays_path, max(1, round(learned_cadence)) + + +def _build_opt_settings(args): + """Build optimisation_settings for optuna, bfgs, or cma_es.""" + robust = ({"robust_temperature": args.robust_temperature} + if args.robust_temperature is not None else {}) + + if args.method == "bfgs": + return { + "method": "bfgs", + "n_parameter_sets": args.n_parameter_sets, + **({"val_fraction": args.val_fraction} if args.val_fraction is not None else {}), + **robust, + "bfgs_settings": { + "maxiter": args.bfgs_maxiter, + "tol": args.bfgs_tol, + "n_evaluation_points": args.bfgs_eval_points, + "compute_dtype": "float64", + }, + } + elif args.method == "cma_es": + return { + "method": "cma_es", + "n_parameter_sets": args.n_parameter_sets, + **({"val_fraction": args.val_fraction} if args.val_fraction is not None else {}), + **robust, + "optuna_settings": { + "parameter_config": PARAMETER_CONFIG, + }, + "cma_es_settings": { + "population_size": args.cma_pop_size, + "n_generations": args.cma_generations, + "sigma0": args.cma_sigma0, + "tol": 1e-8, + "n_evaluation_points": args.cma_eval_points, + "compute_dtype": "float32", + **({"overfitting_penalty": args.overfitting_penalty} + if args.overfitting_penalty is not None else {}), + }, + } + else: + return { + "method": "optuna", + "n_parameter_sets": 1, + **({"val_fraction": args.val_fraction} if args.val_fraction is not None else {}), + **robust, + "optuna_settings": { + "make_scalar": True, + "expand_around": False, + "n_trials": args.n_trials, + "multi_objective": False, + "parameter_config": PARAMETER_CONFIG, + **({"overfitting_penalty": args.overfitting_penalty} + if args.overfitting_penalty is not None else {}), + **({"min_train_returns_over_hodl": args.min_train_ret} + if args.min_train_ret is not None else {}), + }, + } + + +def build_fingerprint(objective, args, tokens, noise_arrays_path=None, arb_freq=None): + """Build run fingerprint with calibrated noise model.""" + if args.noise_model == "mm_observed" and noise_arrays_path is not None: + noise_block = { + "noise_trader_ratio": 0.0, + "noise_model": "mm_observed", + "noise_arrays_path": noise_arrays_path, + } + freq = arb_freq or 5 + elif args.noise_model == "market_linear" and noise_arrays_path is not None: + _arr = np.load(noise_arrays_path) + noise_block = { + "noise_trader_ratio": 0.0, + "noise_model": "market_linear", + "noise_arrays_path": noise_arrays_path, + "reclamm_noise_params": { + "tvl_mean": float(_arr["tvl_mean"]), + "tvl_std": float(_arr["tvl_std"]), + }, + } + freq = arb_freq or 5 + else: + noise_block = { + "noise_trader_ratio": 0.0, + "noise_model": "calibrated", + "reclamm_noise_params": { + f"c_{i}": NOISE_COEFFS_LEGACY[i] for i in range(8) + }, + } + freq = LEGACY_ARB_FREQUENCY + + return { + "rule": "reclamm", + "tokens": tokens, + "startDateString": args.start_date, + "endDateString": args.end_date, + "endTestDateString": args.end_test_date, + "initial_pool_value": args.initial_pool_value, + "do_arb": True, + "arb_frequency": freq, + "fees": args.fees, + "gas_cost": args.gas_cost, + "arb_fees": 0.0, + "protocol_fee_split": 0.25, + **noise_block, + "return_val": objective, + "reclamm_interpolation_method": args.interpolation, + "reclamm_centeredness_scaling": args.centeredness_scaling, + "reclamm_learn_arc_length_speed": False, + "reclamm_use_shift_exponent": True, + **({"bout_offset": args.bout_offset} if args.bout_offset is not None else {}), + "optimisation_settings": _build_opt_settings(args), + } + + +def run_single(objective, args, tokens, pool_id, noise_arrays_path=None, arb_freq=None): + """Run Optuna tuning for a single objective.""" + print(f"\n{'='*60}") + print(f" Objective: {objective}") + print(f" Noise model: {args.noise_model}") + print(f" Method: {args.method}") + print(f" Tokens: {'/'.join(tokens)} ({pool_id})") + print(f" Train: {args.start_date} → {args.end_date}") + print(f" Test: {args.end_date} → {args.end_test_date}") + if arb_freq: + print(f" Arb frequency: {arb_freq} min (learned)") + print(f"{'='*60}\n") + + fp = build_fingerprint(objective, args, tokens, noise_arrays_path, arb_freq) + result = train_on_historic_data(fp, verbose=True) + + if result is not None: + print(f"\n=== Result ({objective}) ===") + for k, v in result.items(): + print(f" {k}: {v}") + + return result + + +def main(): + parser = argparse.ArgumentParser( + description="Tune reClAMM params with calibrated 8-covariate noise model" + ) + parser.add_argument("--method", default="optuna", + choices=["optuna", "bfgs", "cma_es"], + help="Optimisation method") + parser.add_argument("--n-trials", type=int, default=50, + help="Optuna trials (ignored for bfgs)") + parser.add_argument("--n-parameter-sets", type=int, default=1, + help="Number of parameter sets for bfgs") + parser.add_argument("--bfgs-maxiter", type=int, default=100) + parser.add_argument("--bfgs-tol", type=float, default=1e-6) + parser.add_argument("--bfgs-eval-points", type=int, default=20, + help="Number of evaluation points for bfgs") + # CMA-ES + parser.add_argument("--cma-generations", type=int, default=300) + parser.add_argument("--cma-sigma0", type=float, default=0.5, + help="Initial step size for CMA-ES") + parser.add_argument("--cma-pop-size", type=int, default=None, + help="Population size (None = auto)") + parser.add_argument("--cma-eval-points", type=int, default=20) + parser.add_argument("--min-train-ret", type=float, default=-0.5, + help="Reject trials with IS returns_over_hodl below this") + parser.add_argument("--tokens", nargs=2, default=DEFAULT_TOKENS, + help="Token pair (default: AAVE ETH)") + parser.add_argument("--pool-id", default=DEFAULT_POOL_ID, + help="Pool ID prefix for noise model lookup") + parser.add_argument("--noise-model", default="market_linear", + choices=["calibrated", "market_linear", "mm_observed"], + help="Noise model variant") + parser.add_argument("--artifact-dir", + default="results/linear_market_noise", + help="Artifact dir for market_linear or mm_observed model") + parser.add_argument("--competitor-tvl-path", + default="results/competitor_tvl/competitor_tvl.npz", + help="Path to competitor TVL data (mm_observed only)") + parser.add_argument("--initial-pool-value", type=float, default=20_000_000.0, + help="Initial pool TVL in USD (default: 20M)") + parser.add_argument("--fees", type=float, default=0.0025, + help="Pool fee rate (default: 0.0025 matching calibration)") + parser.add_argument("--gas-cost", type=float, default=1.0) + parser.add_argument("--objective", default="fee_revenue_over_value", + choices=OBJECTIVES) + parser.add_argument("--all-objectives", action="store_true", + help="Run all three objectives sequentially") + parser.add_argument("--interpolation", default="geometric", + choices=["geometric", "constant_arc_length"]) + parser.add_argument("--centeredness-scaling", action="store_true") + parser.add_argument("--start-date", default="2024-06-01 00:00:00") + parser.add_argument("--end-date", default="2025-06-01 00:00:00", + help="End of training / start of test") + parser.add_argument("--end-test-date", default="2026-03-01 00:00:00", + help="End of test (latest available data)") + parser.add_argument("--bout-offset", type=int, default=None) + parser.add_argument("--val-fraction", type=float, default=None) + parser.add_argument("--overfitting-penalty", type=float, default=None) + parser.add_argument("--robust-temperature", type=float, default=None, + help="Robust aggregation temperature (lower=more robust)." + " None=standard mean. Try 0.5-2.0.") + parser.add_argument("--output", type=str, default=None, + help="Save results to JSON file") + parser.add_argument("--pr-max", type=float, default=None, + help="Override max price_ratio (default: 200)") + args = parser.parse_args() + + if args.pr_max is not None: + PARAMETER_CONFIG["price_ratio"]["high"] = args.pr_max + + if args.all_objectives: + objectives = OBJECTIVES + else: + objectives = [args.objective] + + tokens = args.tokens + pool_id = args.pool_id + + # Precompute noise arrays once + noise_arrays_path = None + arb_freq = None + if args.noise_model == "market_linear": + noise_arrays_path, arb_freq = _build_market_linear_arrays(args, pool_id, tokens) + elif args.noise_model == "mm_observed": + noise_arrays_path, arb_freq = _build_mm_observed_arrays(args, pool_id, tokens) + + all_results = {} + for obj in objectives: + result = run_single(obj, args, tokens, pool_id, noise_arrays_path, arb_freq) + all_results[obj] = result + + if args.output: + out_path = Path(args.output) + out_path.parent.mkdir(parents=True, exist_ok=True) + with open(out_path, "w") as f: + json.dump(all_results, f, indent=2, default=str) + print(f"\nResults saved to {out_path}") + + +if __name__ == "__main__": + main() diff --git a/setup.py b/setup.py index 20eb403..de29c34 100644 --- a/setup.py +++ b/setup.py @@ -12,6 +12,7 @@ "flask", "flask-jwt-extended", "scipy", + "scikit-learn", "seaborn", "cvxpy", "matplotlib", @@ -20,8 +21,7 @@ "pyarrow", "plotly", "bidask", - "Historic_Crypto", - "gdown", + "Historic-Crypto", "binance_historical_data", "dask", "jsonpickle", @@ -30,10 +30,12 @@ ], extras_require={ "dev": [ - "pytest>=6.0", + "pytest>=7.0", + "pytest-cov>=4.0", + "pytest-xdist>=3.0", + "pytest-timeout>=2.0", "black", "flake8", - "pytest-cov", "hypothesis", ], "docs": [ @@ -41,6 +43,10 @@ "sphinx-automodapi", "sphinx-rtd-theme", ], + "calibration": [ + "numpyro>=0.15.0", + "arviz>=0.15.0", + ], }, python_requires=">=3.9", ) diff --git a/tests/calibration/__init__.py b/tests/calibration/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/tests/calibration/conftest.py b/tests/calibration/conftest.py new file mode 100644 index 0000000..2c171c0 --- /dev/null +++ b/tests/calibration/conftest.py @@ -0,0 +1,134 @@ +"""Fixtures for calibration pipeline tests.""" + +import numpy as np +import pandas as pd +import pytest + + +# ── Constants ────────────────────────────────────────────────────────────── + +N_CADENCES = 3 +N_GAS = 3 +N_DAYS = 15 +N_POOLS = 2 +K_OBS = 8 + +CADENCES = np.array([1.0, 12.0, 60.0]) +GAS_COSTS = np.array([0.0, 1.0, 5.0]) + +# Pool ID prefixes (16 chars) that map to full 66-char pool IDs +POOL_PREFIXES = ["0xaaaa11112222aa", "0xbbbb33334444bb"] +POOL_IDS_FULL = [ + "0xaaaa11112222aa63ae5d458857e731c129069f29000200000000000000000588", + "0xbbbb33334444bb9c8ef030ab642b10820db8f56000200000000000000000014", +] + + +# ── Fixtures ────────────────────────────────────────────────────────────── + + +@pytest.fixture +def synthetic_daily_grid(): + """Small per-day grid DataFrame: 3 cadences x 3 gas_costs x 15 days. + + V_arb decreasing in cadence and gas, with daily sinusoidal variation. + """ + np.random.seed(42) + dates = pd.date_range("2025-12-01", periods=N_DAYS, freq="D") + + rows = [] + for ci, cad in enumerate(CADENCES): + for gi, gas in enumerate(GAS_COSTS): + base = 10000.0 / (1 + 0.3 * ci) / (1 + 0.5 * gi) + for di, date in enumerate(dates): + daily_var = 1 + 0.1 * np.sin(2 * np.pi * di / 7) + vol = base * daily_var + np.random.normal(0, base * 0.01) + rows.append({ + "cadence": cad, + "gas_cost": gas, + "date": date, + "daily_arb_volume": max(vol, 0), + }) + + return pd.DataFrame(rows) + + +@pytest.fixture +def synthetic_panel(): + """Minimal panel DataFrame: 2 pools x 15 days. + + Columns match the real panel.parquet schema. + """ + np.random.seed(42) + dates = pd.date_range("2025-12-01", periods=N_DAYS, freq="D") + + rows = [] + for pi, (prefix, full_id) in enumerate(zip(POOL_PREFIXES, POOL_IDS_FULL)): + chain = "MAINNET" if pi == 0 else "ARBITRUM" + base_tvl = 12.0 + pi # log TVL + base_vol = 9.0 + pi + fee = 0.003 if pi == 0 else 0.01 + + for di, date in enumerate(dates): + tvl = base_tvl + 0.05 * np.sin(2 * np.pi * di / 30) + vol = base_vol + 0.3 * np.random.randn() + sigma = 0.4 + 0.1 * np.random.randn() + rows.append({ + "pool_id": full_id, + "chain": chain, + "date": date, + "log_volume": vol, + "log_tvl": tvl, + "log_tvl_lag1": tvl - 0.01 if di > 0 else np.nan, + "volatility": max(sigma, 0.01), + "weekend": 1 if date.weekday() >= 5 else 0, + "log_fee": np.log(fee), + "swap_fee": fee, + "tier_A": "major" if pi == 0 else "mid", + "tier_B": "major", + "tokens": "BTC,ETH" if pi == 0 else "AAVE,ETH", + "total_shares": 1e6 * (1 + 0.01 * di), + }) + + df = pd.DataFrame(rows) + # Drop rows where log_tvl_lag1 is NaN (first day per pool) + df = df.dropna(subset=["log_tvl_lag1"]).reset_index(drop=True) + return df + + +@pytest.fixture +def synthetic_pool_coeffs(synthetic_daily_grid): + """PoolCoeffsDaily built from synthetic_daily_grid.""" + from quantammsim.calibration.grid_interpolation import precompute_pool_coeffs_daily + return precompute_pool_coeffs_daily(synthetic_daily_grid) + + +@pytest.fixture +def synthetic_x_obs(synthetic_panel): + """NumPy array (n_obs, K_OBS) from synthetic panel for one pool.""" + from quantammsim.calibration.pool_data import build_x_obs + pool0 = synthetic_panel[ + synthetic_panel["pool_id"] == POOL_IDS_FULL[0] + ] + return build_x_obs(pool0) + + +@pytest.fixture +def synthetic_pool_fit_result(): + """Dict with per-pool fitted params for testing learned mapping.""" + np.random.seed(42) + results = {} + for prefix in POOL_PREFIXES: + results[prefix] = { + "log_cadence": np.log(12.0) + 0.1 * np.random.randn(), + "log_gas": np.log(1.0) + 0.1 * np.random.randn(), + "noise_coeffs": np.random.randn(K_OBS) * 0.1, + "loss": 0.5 + 0.1 * np.random.rand(), + "converged": True, + "cadence_minutes": 12.0, + "gas_usd": 1.0, + "chain": "MAINNET" if prefix == POOL_PREFIXES[0] else "ARBITRUM", + "fee": 0.003 if prefix == POOL_PREFIXES[0] else 0.01, + "tokens": "BTC/ETH" if prefix == POOL_PREFIXES[0] else "AAVE/ETH", + } + return results diff --git a/tests/calibration/test_calibration_model.py b/tests/calibration/test_calibration_model.py new file mode 100644 index 0000000..95812f6 --- /dev/null +++ b/tests/calibration/test_calibration_model.py @@ -0,0 +1,655 @@ +"""Tests for quantammsim.calibration.calibration_model — composable CalibrationModel.""" + +import os +import tempfile + +import jax +import jax.numpy as jnp +import numpy as np +import pytest + +from tests.calibration.conftest import K_OBS, N_DAYS, POOL_PREFIXES + +from quantammsim.calibration.calibration_model import CalibrationModel +from quantammsim.calibration.heads import ( + FixedHead, + LinearHead, + MLPHead, + MLPNoiseHead, + PerPoolHead, + PerPoolNoiseHead, + SharedLinearNoiseHead, +) +from quantammsim.calibration.loss import CHAIN_GAS_USD + + +# ── Fixtures ──────────────────────────────────────────────────────────────── + + +@pytest.fixture +def matched_data(synthetic_daily_grid, synthetic_panel, tmp_path): + """Build matched data dict from synthetic fixtures.""" + from quantammsim.calibration.pool_data import match_grids_to_panel + + grid_dir = tmp_path / "grids" + grid_dir.mkdir() + for prefix in POOL_PREFIXES: + synthetic_daily_grid.to_parquet( + grid_dir / f"{prefix}_daily.parquet", index=False + ) + return match_grids_to_panel(str(grid_dir), synthetic_panel) + + +@pytest.fixture +def jdata_ppn(matched_data): + """JointData for per-pool noise mode (free gas).""" + from quantammsim.calibration.joint_fit import prepare_joint_data + return prepare_joint_data(matched_data) + + +@pytest.fixture +def jdata_fixed_gas(matched_data): + """JointData with gas fixed to chain costs.""" + from quantammsim.calibration.joint_fit import prepare_joint_data + return prepare_joint_data(matched_data, fix_gas_to_chain=True) + + +# ── n_params tests ────────────────────────────────────────────────────────── + + +class TestNParams: + """Verify param count for each config matches expectations.""" + + def test_option_c_free_gas(self, jdata_ppn): + n_pools = len(jdata_ppn.pool_data) + k_attr = jdata_ppn.x_attr.shape[1] + model = CalibrationModel( + PerPoolHead("cad"), PerPoolHead("gas"), PerPoolNoiseHead() + ) + # n_pools + n_pools + n_pools*K_OBS + expected = n_pools + n_pools + n_pools * K_OBS + assert model.n_params(n_pools, k_attr) == expected + + def test_option_c_fixed_gas(self, jdata_ppn): + n_pools = len(jdata_ppn.pool_data) + k_attr = jdata_ppn.x_attr.shape[1] + model = CalibrationModel( + PerPoolHead("cad"), + FixedHead("gas", np.zeros(n_pools)), + PerPoolNoiseHead(), + ) + expected = n_pools + 0 + n_pools * K_OBS + assert model.n_params(n_pools, k_attr) == expected + + def test_option_a_ppn_free(self, jdata_ppn): + n_pools = len(jdata_ppn.pool_data) + k_attr = jdata_ppn.x_attr.shape[1] + model = CalibrationModel( + LinearHead("cad"), LinearHead("gas"), PerPoolNoiseHead() + ) + expected = (1 + k_attr) + (1 + k_attr) + n_pools * K_OBS + assert model.n_params(n_pools, k_attr) == expected + + def test_option_a_shared_fixed(self, jdata_ppn): + n_pools = len(jdata_ppn.pool_data) + k_attr = jdata_ppn.x_attr.shape[1] + model = CalibrationModel( + LinearHead("cad"), + FixedHead("gas", np.zeros(n_pools)), + SharedLinearNoiseHead(), + ) + expected = (1 + k_attr) + 0 + (1 + k_attr) * K_OBS + assert model.n_params(n_pools, k_attr) == expected + + +# ── pack_init tests ───────────────────────────────────────────────────────── + + +class TestPackInit: + def test_size_matches_n_params(self, jdata_ppn): + n_pools = len(jdata_ppn.pool_data) + k_attr = jdata_ppn.x_attr.shape[1] + model = CalibrationModel( + LinearHead("cad"), LinearHead("gas"), PerPoolNoiseHead() + ) + init = model.pack_init(jdata_ppn) + assert init.shape == (model.n_params(n_pools, k_attr),) + + def test_roundtrip_slicing(self, jdata_ppn): + """Verify head slices index correctly into the packed init vector.""" + n_pools = len(jdata_ppn.pool_data) + k_attr = jdata_ppn.x_attr.shape[1] + model = CalibrationModel( + LinearHead("cad"), LinearHead("gas"), PerPoolNoiseHead() + ) + init = model.pack_init(jdata_ppn) + (cs, ce), (gs, ge), (ns, ne) = model._head_slices(n_pools, k_attr) + + assert ce - cs == model.cadence_head.n_params(n_pools, k_attr) + assert ge - gs == model.gas_head.n_params(n_pools, k_attr) + assert ne - ns == model.noise_head.n_params(n_pools, k_attr) + assert ne == len(init) + + def test_init_values_finite(self, jdata_ppn): + model = CalibrationModel( + LinearHead("cad"), LinearHead("gas"), PerPoolNoiseHead() + ) + init = model.pack_init(jdata_ppn) + assert np.all(np.isfinite(init)) + + +# ── Pool loss function tests ─────────────────────────────────────────────── + + +class TestPoolLossEquivalence: + """Verify CalibrationModel pool loss matches existing implementations.""" + + def test_option_a_ppn_loss_matches_joint_fit(self, jdata_ppn): + """At same params, CalibrationModel loss == _make_pool_loss_fn loss.""" + from quantammsim.calibration.joint_fit import ( + _make_pool_loss_fn, + make_initial_joint_params, + ) + + n_pools = len(jdata_ppn.pool_data) + k_attr = jdata_ppn.x_attr.shape[1] + + # Old code: per_pool_noise mode with free gas + old_config = { + "k_attr": k_attr, "n_pools": n_pools, + "mode": "per_pool_noise", "fix_gas": False, + } + old_init = make_initial_joint_params(jdata_ppn, mode="per_pool_noise") + + # New code: LinearHead cad/gas + PerPoolNoiseHead + model = CalibrationModel( + LinearHead("cad", alpha=0.01), + LinearHead("gas", alpha=0.01), + PerPoolNoiseHead(), + ) + new_init = model.pack_init(jdata_ppn) + + # Compare per-pool losses at old init params + for i in range(n_pools): + old_fn = _make_pool_loss_fn( + i, jdata_ppn.pool_data[i], jdata_ppn.x_attr[i], old_config + ) + new_fn = model.make_pool_loss_fn( + i, jdata_ppn.pool_data[i], jdata_ppn.x_attr[i], + n_pools, k_attr, + ) + + old_loss = float(old_fn(old_init)) + new_loss = float(new_fn(new_init)) + + # They use different param layouts, so we just verify both are + # finite and positive + assert np.isfinite(old_loss) and old_loss >= 0 + assert np.isfinite(new_loss) and new_loss >= 0 + + def test_pool_loss_differentiable(self, jdata_ppn): + n_pools = len(jdata_ppn.pool_data) + k_attr = jdata_ppn.x_attr.shape[1] + model = CalibrationModel( + LinearHead("cad"), LinearHead("gas"), PerPoolNoiseHead() + ) + init = jnp.array(model.pack_init(jdata_ppn)) + + fn = model.make_pool_loss_fn( + 0, jdata_ppn.pool_data[0], jdata_ppn.x_attr[0], + n_pools, k_attr, + ) + grad = jax.grad(fn)(init) + assert grad.shape == init.shape + assert jnp.all(jnp.isfinite(grad)) + + +# ── Joint loss function tests ────────────────────────────────────────────── + + +class TestJointLoss: + def test_joint_loss_scalar(self, jdata_ppn): + model = CalibrationModel( + LinearHead("cad"), LinearHead("gas"), PerPoolNoiseHead() + ) + loss_fn = model.make_joint_loss_fn(jdata_ppn) + init = jnp.array(model.pack_init(jdata_ppn)) + loss = loss_fn(init) + assert loss.shape == () + assert float(loss) >= 0 + + def test_joint_loss_differentiable(self, jdata_ppn): + model = CalibrationModel( + LinearHead("cad"), LinearHead("gas"), PerPoolNoiseHead() + ) + loss_fn = model.make_joint_loss_fn(jdata_ppn) + init = jnp.array(model.pack_init(jdata_ppn)) + grad = jax.grad(loss_fn)(init) + assert grad.shape == init.shape + assert jnp.all(jnp.isfinite(grad)) + + def test_regularization_included(self, jdata_ppn): + """With nonzero alpha, joint loss > sum of pool losses / n_pools.""" + model = CalibrationModel( + LinearHead("cad", alpha=10.0), + LinearHead("gas", alpha=10.0), + PerPoolNoiseHead(), + ) + loss_fn = model.make_joint_loss_fn(jdata_ppn) + init = jnp.array(model.pack_init(jdata_ppn)) + init = init.at[0].set(1.0) # nonzero W to trigger reg + + n_pools = len(jdata_ppn.pool_data) + k_attr = jdata_ppn.x_attr.shape[1] + + # Compute data loss only (sum of pool losses / n_pools) + data_loss = 0.0 + for i in range(n_pools): + fn = model.make_pool_loss_fn( + i, jdata_ppn.pool_data[i], jdata_ppn.x_attr[i], + n_pools, k_attr, + ) + data_loss += float(fn(init)) + data_loss /= n_pools + + joint_loss = float(loss_fn(init)) + # Joint loss should be >= data loss due to regularization + assert joint_loss >= data_loss - 1e-10 + + +# ── Fit tests ────────────────────────────────────────────────────────────── + + +class TestFit: + def test_fit_converges_option_a_ppn(self, jdata_ppn): + model = CalibrationModel( + LinearHead("cad", alpha=0.01), + LinearHead("gas", alpha=0.01), + PerPoolNoiseHead(), + ) + result = model.fit(jdata_ppn, maxiter=100) + assert result["loss"] <= result["init_loss"] + + def test_fit_converges_option_c_free(self, jdata_ppn): + model = CalibrationModel( + PerPoolHead("cad", default=np.log(12.0)), + PerPoolHead("gas", default=np.log(1.0)), + PerPoolNoiseHead(), + ) + result = model.fit(jdata_ppn, maxiter=100) + assert result["loss"] <= result["init_loss"] + + def test_fit_fixed_gas(self, jdata_ppn): + n_pools = len(jdata_ppn.pool_data) + gas_values = np.array([np.log(1.0)] * n_pools) + model = CalibrationModel( + PerPoolHead("cad", default=np.log(12.0)), + FixedHead("gas", gas_values), + PerPoolNoiseHead(), + ) + result = model.fit(jdata_ppn, maxiter=100) + assert result["loss"] <= result["init_loss"] + assert "gas_fixed" in result + + def test_fit_returns_required_keys(self, jdata_ppn): + model = CalibrationModel( + LinearHead("cad", alpha=0.01), + LinearHead("gas", alpha=0.01), + PerPoolNoiseHead(), + ) + result = model.fit(jdata_ppn, maxiter=20) + for key in ["loss", "init_loss", "converged", "params_flat", + "pool_ids", "attr_names", "k_attr", "n_pools"]: + assert key in result, f"Missing key: {key}" + + def test_fit_shared_noise(self, jdata_ppn): + model = CalibrationModel( + LinearHead("cad", alpha=0.01), + LinearHead("gas", alpha=0.01), + SharedLinearNoiseHead(alpha=0.01), + ) + result = model.fit(jdata_ppn, maxiter=100) + assert result["loss"] <= result["init_loss"] + assert "bias_noise" in result + assert "W_noise" in result + + +# ── Predict new pool tests ───────────────────────────────────────────────── + + +class TestPredictNewPool: + def test_predict_new_pool_linear(self, jdata_ppn): + model = CalibrationModel( + LinearHead("cad"), LinearHead("gas"), SharedLinearNoiseHead() + ) + result = model.fit(jdata_ppn, maxiter=20) + x_attr = np.zeros(result["k_attr"]) + pred = model.predict_new_pool(result, x_attr) + assert pred["cadence_minutes"] > 0 + assert pred["gas_usd"] > 0 + assert "noise_coeffs" in pred + assert len(pred["noise_coeffs"]) == K_OBS + + def test_predict_new_pool_per_pool_noise_omits_noise(self, jdata_ppn): + model = CalibrationModel( + LinearHead("cad"), LinearHead("gas"), PerPoolNoiseHead() + ) + result = model.fit(jdata_ppn, maxiter=20) + x_attr = np.zeros(result["k_attr"]) + pred = model.predict_new_pool(result, x_attr) + assert "noise_coeffs" not in pred # can't generalize + assert pred["cadence_minutes"] > 0 + + def test_predict_at_zero_equals_bias(self, jdata_ppn): + model = CalibrationModel( + LinearHead("cad"), LinearHead("gas"), SharedLinearNoiseHead() + ) + result = model.fit(jdata_ppn, maxiter=50) + x_attr = np.zeros(result["k_attr"]) + pred = model.predict_new_pool(result, x_attr) + np.testing.assert_allclose( + pred["log_cadence"], result["bias_cad"], rtol=1e-10 + ) + np.testing.assert_allclose( + pred["log_gas"], result["bias_gas"], rtol=1e-10 + ) + + +# ── Huber loss tests ─────────────────────────────────────────────────────── + + +class TestHuberLoss: + def test_huber_loss_runs(self, jdata_ppn): + model = CalibrationModel( + LinearHead("cad"), LinearHead("gas"), PerPoolNoiseHead(), + loss_type="huber", huber_delta=1.5, + ) + result = model.fit(jdata_ppn, maxiter=50) + assert result["loss"] >= 0 + + def test_huber_equals_half_l2_for_small_residuals(self): + """For residuals << delta, Huber = 0.5 * L2 (standard definition).""" + model_l2 = CalibrationModel( + LinearHead("cad"), LinearHead("gas"), PerPoolNoiseHead(), + loss_type="l2", + ) + model_huber = CalibrationModel( + LinearHead("cad"), LinearHead("gas"), PerPoolNoiseHead(), + loss_type="huber", huber_delta=100.0, # very large delta + ) + residuals = jnp.array([0.01, -0.02, 0.005]) + l2_loss = model_l2._compute_loss(residuals) + huber_loss = model_huber._compute_loss(residuals) + # Standard Huber: 0.5 * r^2 for |r| < delta + np.testing.assert_allclose( + float(huber_loss), 0.5 * float(l2_loss), rtol=1e-6 + ) + + +# ── Config equivalence tests ────────────────────────────────────────────── + + +class TestConfigEquivalence: + """Verify that CalibrationModel configs match existing option configs.""" + + def test_option_c_free_matches_old_param_count(self, jdata_ppn): + """Option C free gas: n_pools*(1+1+K_OBS) params.""" + n_pools = len(jdata_ppn.pool_data) + k_attr = jdata_ppn.x_attr.shape[1] + model = CalibrationModel( + PerPoolHead("cad"), PerPoolHead("gas"), PerPoolNoiseHead() + ) + expected = n_pools * (1 + 1 + K_OBS) + assert model.n_params(n_pools, k_attr) == expected + + def test_option_a_ppn_free_matches_old_param_count(self, jdata_ppn): + """Option A ppn free: 2 + 2*k_attr + n_pools*K_OBS params.""" + n_pools = len(jdata_ppn.pool_data) + k_attr = jdata_ppn.x_attr.shape[1] + model = CalibrationModel( + LinearHead("cad"), LinearHead("gas"), PerPoolNoiseHead() + ) + expected = 2 + 2 * k_attr + n_pools * K_OBS + assert model.n_params(n_pools, k_attr) == expected + + def test_option_a_shared_free_matches_old_param_count(self, jdata_ppn): + """Option A shared free: 2 + 2*k_attr + (1+k_attr)*K_OBS.""" + n_pools = len(jdata_ppn.pool_data) + k_attr = jdata_ppn.x_attr.shape[1] + model = CalibrationModel( + LinearHead("cad"), LinearHead("gas"), SharedLinearNoiseHead() + ) + expected = 2 + 2 * k_attr + (1 + k_attr) * K_OBS + assert model.n_params(n_pools, k_attr) == expected + + +# ── MLP integration tests ───────────────────────────────────────────────── + + +class TestMLPIntegration: + """Test CalibrationModel with MLPHead for cadence.""" + + def test_mlp_cadence_n_params(self, jdata_ppn): + n_pools = len(jdata_ppn.pool_data) + k_attr = jdata_ppn.x_attr.shape[1] + model = CalibrationModel( + MLPHead("cad", hidden=8, alpha=0.01), + LinearHead("gas", alpha=0.01), + PerPoolNoiseHead(), + ) + mlp_params = k_attr * 8 + 8 + 8 + 1 + linear_params = 1 + k_attr + noise_params = n_pools * K_OBS + assert model.n_params(n_pools, k_attr) == mlp_params + linear_params + noise_params + + def test_mlp_cadence_fit_converges(self, jdata_ppn): + model = CalibrationModel( + MLPHead("cad", hidden=8, alpha=0.01), + LinearHead("gas", alpha=0.01), + PerPoolNoiseHead(), + ) + result = model.fit(jdata_ppn, maxiter=100) + assert result["loss"] <= result["init_loss"] + assert np.isfinite(result["loss"]) + + def test_mlp_cadence_and_gas_fit(self, jdata_ppn): + model = CalibrationModel( + MLPHead("cad", hidden=8, alpha=0.01), + MLPHead("gas", hidden=8, alpha=0.01), + PerPoolNoiseHead(), + ) + result = model.fit(jdata_ppn, maxiter=100) + assert result["loss"] <= result["init_loss"] + + def test_mlp_predict_new_pool(self, jdata_ppn): + model = CalibrationModel( + MLPHead("cad", hidden=8, alpha=0.01), + MLPHead("gas", hidden=8, alpha=0.01), + SharedLinearNoiseHead(alpha=0.01), + ) + result = model.fit(jdata_ppn, maxiter=50) + x_attr = np.zeros(result["k_attr"]) + pred = model.predict_new_pool(result, x_attr) + assert pred["cadence_minutes"] > 0 + assert pred["gas_usd"] > 0 + assert "noise_coeffs" in pred + + def test_mlp_loss_differentiable(self, jdata_ppn): + import jax + n_pools = len(jdata_ppn.pool_data) + k_attr = jdata_ppn.x_attr.shape[1] + model = CalibrationModel( + MLPHead("cad", hidden=8, alpha=0.01), + LinearHead("gas", alpha=0.01), + PerPoolNoiseHead(), + ) + loss_fn = model.make_joint_loss_fn(jdata_ppn) + init = jnp.array(model.pack_init(jdata_ppn)) + grad = jax.grad(loss_fn)(init) + assert jnp.all(jnp.isfinite(grad)) + assert float(jnp.sum(jnp.abs(grad))) > 0 + + def test_mlp_with_huber_loss(self, jdata_ppn): + model = CalibrationModel( + MLPHead("cad", hidden=8, alpha=0.01), + LinearHead("gas", alpha=0.01), + PerPoolNoiseHead(), + loss_type="huber", huber_delta=1.5, + ) + result = model.fit(jdata_ppn, maxiter=50) + assert result["loss"] >= 0 + assert np.isfinite(result["loss"]) + + +# ── MLP noise integration tests ─────────────────────────────────────────── + + +class TestMLPNoiseIntegration: + """Test CalibrationModel with MLPNoiseHead — the key use case.""" + + def test_mlp_noise_fit_converges(self, jdata_ppn): + model = CalibrationModel( + LinearHead("cad", alpha=0.01), + LinearHead("gas", alpha=0.01), + MLPNoiseHead(hidden=8, alpha=0.01), + ) + result = model.fit(jdata_ppn, maxiter=100) + assert result["loss"] <= result["init_loss"] + assert np.isfinite(result["loss"]) + + def test_mlp_noise_predict_new_pool(self, jdata_ppn): + model = CalibrationModel( + LinearHead("cad", alpha=0.01), + LinearHead("gas", alpha=0.01), + MLPNoiseHead(hidden=8, alpha=0.01), + ) + result = model.fit(jdata_ppn, maxiter=50) + x_attr = np.zeros(result["k_attr"]) + pred = model.predict_new_pool(result, x_attr) + assert pred["cadence_minutes"] > 0 + assert pred["gas_usd"] > 0 + assert "noise_coeffs" in pred + assert len(pred["noise_coeffs"]) == K_OBS + + def test_mlp_noise_loss_differentiable(self, jdata_ppn): + import jax + model = CalibrationModel( + LinearHead("cad", alpha=0.01), + LinearHead("gas", alpha=0.01), + MLPNoiseHead(hidden=8, alpha=0.01), + ) + loss_fn = model.make_joint_loss_fn(jdata_ppn) + init = jnp.array(model.pack_init(jdata_ppn)) + grad = jax.grad(loss_fn)(init) + assert jnp.all(jnp.isfinite(grad)) + assert float(jnp.sum(jnp.abs(grad))) > 0 + + def test_full_mlp_model(self, jdata_ppn): + """MLP for all three heads — most expressive config.""" + model = CalibrationModel( + MLPHead("cad", hidden=8, alpha=0.01), + MLPHead("gas", hidden=8, alpha=0.01), + MLPNoiseHead(hidden=8, alpha=0.01), + ) + result = model.fit(jdata_ppn, maxiter=100) + assert result["loss"] <= result["init_loss"] + x_attr = np.zeros(result["k_attr"]) + pred = model.predict_new_pool(result, x_attr) + assert pred["cadence_minutes"] > 0 + assert "noise_coeffs" in pred + + def test_mlp_noise_with_fixed_gas(self, jdata_ppn): + n_pools = len(jdata_ppn.pool_data) + gas_values = np.array([np.log(1.0)] * n_pools) + model = CalibrationModel( + LinearHead("cad", alpha=0.01), + FixedHead("gas", gas_values), + MLPNoiseHead(hidden=8, alpha=0.01), + ) + result = model.fit(jdata_ppn, maxiter=100) + assert result["loss"] <= result["init_loss"] + + def test_mlp_noise_param_count(self, jdata_ppn): + n_pools = len(jdata_ppn.pool_data) + k_attr = jdata_ppn.x_attr.shape[1] + h = 8 + model = CalibrationModel( + LinearHead("cad"), + LinearHead("gas"), + MLPNoiseHead(hidden=h), + ) + # Linear cad: 1+k, Linear gas: 1+k, + # MLP noise: k*h + h + h*K_OBS + K_OBS + expected = (1 + k_attr) * 2 + k_attr * h + h + h * K_OBS + K_OBS + assert model.n_params(n_pools, k_attr) == expected + + +# ── Reduced k_obs=4 integration tests ──────────────────────────────────── + +K_OBS_REDUCED = 4 + + +@pytest.fixture +def jdata_reduced(matched_data): + """JointData with reduced x_obs (4 columns).""" + from quantammsim.calibration.joint_fit import prepare_joint_data + return prepare_joint_data( + matched_data, drop_chain_dummies=True, + fix_gas_to_chain=True, reduced_x_obs=True, + ) + + +class TestReducedKObsIntegration: + """CalibrationModel with k_obs=4 noise heads on reduced x_obs data.""" + + def test_reduced_n_params(self, jdata_reduced): + n_pools = len(jdata_reduced.pool_data) + k_attr = jdata_reduced.x_attr.shape[1] + h = 8 + model = CalibrationModel( + LinearHead("cad", alpha=0.01), + FixedHead("gas", np.zeros(n_pools)), + MLPNoiseHead(hidden=h, alpha=0.01, k_obs=K_OBS_REDUCED), + ) + # Linear cad: 1+k, Fixed gas: 0, + # MLP noise: k*h + h + h*4 + 4 + expected = (1 + k_attr) + 0 + k_attr * h + h + h * 4 + 4 + assert model.n_params(n_pools, k_attr) == expected + + def test_reduced_loss_runs(self, jdata_reduced): + n_pools = len(jdata_reduced.pool_data) + gas_values = np.zeros(n_pools) + model = CalibrationModel( + LinearHead("cad", alpha=0.01), + FixedHead("gas", gas_values), + MLPNoiseHead(hidden=8, alpha=0.01, k_obs=K_OBS_REDUCED), + ) + loss_fn = model.make_joint_loss_fn(jdata_reduced) + init = jnp.array(model.pack_init(jdata_reduced)) + loss = float(loss_fn(init)) + assert np.isfinite(loss) and loss >= 0 + + def test_reduced_grad_finite(self, jdata_reduced): + n_pools = len(jdata_reduced.pool_data) + gas_values = np.zeros(n_pools) + model = CalibrationModel( + LinearHead("cad", alpha=0.01), + FixedHead("gas", gas_values), + MLPNoiseHead(hidden=8, alpha=0.01, k_obs=K_OBS_REDUCED), + ) + loss_fn = model.make_joint_loss_fn(jdata_reduced) + init = jnp.array(model.pack_init(jdata_reduced)) + grad = jax.grad(loss_fn)(init) + assert jnp.all(jnp.isfinite(grad)) + + def test_reduced_fit_converges(self, jdata_reduced): + n_pools = len(jdata_reduced.pool_data) + gas_values = np.zeros(n_pools) + model = CalibrationModel( + LinearHead("cad", alpha=0.01), + FixedHead("gas", gas_values), + MLPNoiseHead(hidden=8, alpha=0.01, k_obs=K_OBS_REDUCED), + ) + result = model.fit(jdata_reduced, maxiter=100) + assert result["loss"] <= result["init_loss"] + assert np.isfinite(result["loss"]) diff --git a/tests/calibration/test_heads.py b/tests/calibration/test_heads.py new file mode 100644 index 0000000..d5bc644 --- /dev/null +++ b/tests/calibration/test_heads.py @@ -0,0 +1,1131 @@ +"""Tests for quantammsim.calibration.heads — pluggable Head components.""" + +import jax.numpy as jnp +import numpy as np +import pytest + +from tests.calibration.conftest import K_OBS, POOL_PREFIXES + +from quantammsim.calibration.heads import ( + FixedHead, + Head, + LinearHead, + MLPHead, + MLPNoiseHead, + PerPoolHead, + PerPoolNoiseHead, + SharedLinearNoiseHead, +) +from quantammsim.calibration.pool_data import K_OBS_REDUCED + + +# ── Helpers ───────────────────────────────────────────────────────────────── + +N_POOLS = 2 +K_ATTR = 5 + + +def _make_fake_jdata(): + """Minimal JointData-like object for init() testing.""" + from quantammsim.calibration.joint_fit import JointData + + pool_data = [] + for _ in range(N_POOLS): + n_obs = 14 + x_obs = np.random.randn(n_obs, K_OBS) + x_obs[:, 0] = 1.0 # intercept column + y_obs = np.random.randn(n_obs) * 0.5 + 9.0 + pool_data.append({ + "x_obs": jnp.array(x_obs), + "y_obs": jnp.array(y_obs), + "day_indices": jnp.arange(n_obs) % 10, + }) + + x_attr = jnp.array(np.random.randn(N_POOLS, K_ATTR)) + return JointData( + pool_data=pool_data, + x_attr=x_attr, + pool_ids=POOL_PREFIXES[:N_POOLS], + attr_names=[f"attr_{i}" for i in range(K_ATTR)], + ) + + +# ── Protocol compliance ──────────────────────────────────────────────────── + + +class TestProtocol: + def test_per_pool_head_is_head(self): + assert isinstance(PerPoolHead("cad"), Head) + + def test_fixed_head_is_head(self): + assert isinstance(FixedHead("gas", np.array([1.0, 2.0])), Head) + + def test_linear_head_is_head(self): + assert isinstance(LinearHead("cad"), Head) + + def test_per_pool_noise_head_is_head(self): + assert isinstance(PerPoolNoiseHead(), Head) + + def test_shared_linear_noise_head_is_head(self): + assert isinstance(SharedLinearNoiseHead(), Head) + + def test_mlp_head_is_head(self): + assert isinstance(MLPHead("cad"), Head) + + def test_mlp_noise_head_is_head(self): + assert isinstance(MLPNoiseHead(), Head) + + +# ── PerPoolHead ───────────────────────────────────────────────────────────── + + +class TestPerPoolHead: + def test_n_params(self): + h = PerPoolHead("cad") + assert h.n_params(3, 5) == 3 + assert h.n_params(10, 7) == 10 + + def test_predict_returns_indexed_value(self): + h = PerPoolHead("cad") + params = jnp.array([1.0, 2.0, 3.0]) + x_attr_i = jnp.zeros(5) + assert float(h.predict(params, 0, x_attr_i)) == 1.0 + assert float(h.predict(params, 1, x_attr_i)) == 2.0 + assert float(h.predict(params, 2, x_attr_i)) == 3.0 + + def test_regularization_is_zero(self): + h = PerPoolHead("cad") + params = jnp.array([1.0, 2.0, 3.0]) + assert float(h.regularization(params)) == 0.0 + + def test_init_default(self): + h = PerPoolHead("cad", default=np.log(12.0)) + jdata = _make_fake_jdata() + init = h.init(jdata) + assert init.shape == (N_POOLS,) + np.testing.assert_allclose(init, np.log(12.0)) + + def test_init_warm_start(self): + h = PerPoolHead("log_cadence") + jdata = _make_fake_jdata() + warm = { + POOL_PREFIXES[0]: {"log_cadence": 2.5}, + POOL_PREFIXES[1]: {"log_cadence": 3.0}, + } + init = h.init(jdata, warm_start=warm) + np.testing.assert_allclose(init, [2.5, 3.0]) + + def test_predict_new_raises(self): + h = PerPoolHead("cad") + with pytest.raises(ValueError, match="cannot predict"): + h.predict_new(np.array([1.0]), np.zeros(5)) + + def test_make_bounds(self): + h = PerPoolHead("cad") + bounds = h.make_bounds(3, 5) + assert len(bounds) == 3 + assert all(b == (None, None) for b in bounds) + + +# ── FixedHead ─────────────────────────────────────────────────────────────── + + +class TestFixedHead: + def test_n_params_is_zero(self): + h = FixedHead("gas", np.array([0.0, -4.6])) + assert h.n_params(2, 5) == 0 + + def test_predict_returns_fixed_value(self): + vals = np.array([0.0, -4.6, 1.5]) + h = FixedHead("gas", vals) + empty_slice = jnp.array([]) + x_attr_i = jnp.zeros(5) + assert float(h.predict(empty_slice, 0, x_attr_i)) == 0.0 + np.testing.assert_allclose( + float(h.predict(empty_slice, 1, x_attr_i)), -4.6 + ) + assert float(h.predict(empty_slice, 2, x_attr_i)) == 1.5 + + def test_regularization_is_zero(self): + h = FixedHead("gas", np.array([1.0])) + assert float(h.regularization(jnp.array([]))) == 0.0 + + def test_init_returns_empty(self): + h = FixedHead("gas", np.array([1.0, 2.0])) + jdata = _make_fake_jdata() + init = h.init(jdata) + assert init.shape == (0,) + + def test_predict_new_raises(self): + h = FixedHead("gas", np.array([1.0])) + with pytest.raises(ValueError, match="cannot predict"): + h.predict_new(np.array([]), np.zeros(5)) + + def test_make_bounds_empty(self): + h = FixedHead("gas", np.array([1.0])) + assert h.make_bounds(1, 5) == [] + + +# ── LinearHead ────────────────────────────────────────────────────────────── + + +class TestLinearHead: + def test_n_params(self): + h = LinearHead("cad") + assert h.n_params(3, 5) == 6 # 1 + 5 + assert h.n_params(10, 7) == 8 # 1 + 7 + + def test_predict_bias_plus_dot(self): + h = LinearHead("cad") + # params_slice = [bias, W0, W1, W2] + params = jnp.array([2.0, 0.5, -1.0, 0.3]) + x_attr_i = jnp.array([1.0, 2.0, 3.0]) + # expected = 2.0 + (0.5*1.0 + (-1.0)*2.0 + 0.3*3.0) + # = 2.0 + 0.5 - 2.0 + 0.9 = 1.4 + result = float(h.predict(params, 0, x_attr_i)) + np.testing.assert_allclose(result, 1.4) + + def test_predict_ignores_pool_idx(self): + h = LinearHead("cad") + params = jnp.array([2.0, 0.5, -1.0]) + x = jnp.array([1.0, 2.0]) + v0 = float(h.predict(params, 0, x)) + v1 = float(h.predict(params, 5, x)) + assert v0 == v1 + + def test_regularization_on_W_not_bias(self): + h = LinearHead("cad", alpha=1.0) + params = jnp.array([100.0, 3.0, 4.0]) + # reg = 1.0 * (3^2 + 4^2) = 25.0 (bias ignored) + np.testing.assert_allclose(float(h.regularization(params)), 25.0) + + def test_regularization_alpha_scaling(self): + h = LinearHead("cad", alpha=0.5) + params = jnp.array([0.0, 2.0, 0.0]) + # reg = 0.5 * 4.0 = 2.0 + np.testing.assert_allclose(float(h.regularization(params)), 2.0) + + def test_init_default_cadence(self): + h = LinearHead("cad") + jdata = _make_fake_jdata() + init = h.init(jdata) + assert init.shape == (1 + K_ATTR,) + np.testing.assert_allclose(init[0], np.log(12.0)) + np.testing.assert_allclose(init[1:], 0.0) + + def test_init_default_gas(self): + h = LinearHead("gas") + jdata = _make_fake_jdata() + init = h.init(jdata) + np.testing.assert_allclose(init[0], np.log(1.0)) + + def test_init_warm_start(self): + h = LinearHead("log_cadence") + jdata = _make_fake_jdata() + warm = { + POOL_PREFIXES[0]: {"log_cadence": 2.0}, + POOL_PREFIXES[1]: {"log_cadence": 3.0}, + } + init = h.init(jdata, warm_start=warm) + assert init.shape == (1 + K_ATTR,) + # Should have fitted OLS to recover bias/W + + def test_predict_new(self): + h = LinearHead("cad") + params = np.array([2.0, 0.5, -1.0]) + x_attr = np.array([1.0, 2.0]) + result = h.predict_new(params, x_attr) + np.testing.assert_allclose(result, 2.0 + 0.5 - 2.0) + + def test_unpack_result(self): + h = LinearHead("cad") + params = np.array([2.0, 0.5, -1.0]) + result = h.unpack_result(params, 3, 2) + assert "bias_cad" in result + assert "W_cad" in result + np.testing.assert_allclose(result["bias_cad"], 2.0) + np.testing.assert_allclose(result["W_cad"], [0.5, -1.0]) + + def test_make_bounds(self): + h = LinearHead("cad") + bounds = h.make_bounds(3, 5) + assert len(bounds) == 6 # 1 + 5 + + +# ── PerPoolNoiseHead ──────────────────────────────────────────────────────── + + +class TestPerPoolNoiseHead: + def test_n_params(self): + h = PerPoolNoiseHead() + assert h.n_params(3, 5) == 3 * K_OBS + assert h.n_params(2, 7) == 2 * K_OBS + + def test_predict_correct_slice(self): + h = PerPoolNoiseHead() + n_pools = 3 + params = jnp.arange(n_pools * K_OBS, dtype=float) + x_attr_i = jnp.zeros(5) + + for i in range(n_pools): + result = h.predict(params, i, x_attr_i) + expected = params[i * K_OBS:(i + 1) * K_OBS] + np.testing.assert_allclose(result, expected) + + def test_regularization_zero_by_default(self): + h = PerPoolNoiseHead() + params = jnp.ones(16) + assert float(h.regularization(params)) == 0.0 + + def test_regularization_with_alpha(self): + h = PerPoolNoiseHead(alpha=1.0) + params = jnp.array([3.0, 4.0]) + np.testing.assert_allclose(float(h.regularization(params)), 25.0) + + def test_init_from_ols(self): + np.random.seed(42) + h = PerPoolNoiseHead() + jdata = _make_fake_jdata() + init = h.init(jdata) + assert init.shape == (N_POOLS * K_OBS,) + assert np.all(np.isfinite(init)) + + def test_init_warm_start(self): + h = PerPoolNoiseHead() + jdata = _make_fake_jdata() + warm = { + POOL_PREFIXES[0]: {"noise_coeffs": np.ones(K_OBS) * 5.0}, + POOL_PREFIXES[1]: {"noise_coeffs": np.ones(K_OBS) * 7.0}, + } + init = h.init(jdata, warm_start=warm) + assert init.shape == (N_POOLS * K_OBS,) + np.testing.assert_allclose(init[:K_OBS], 5.0) + np.testing.assert_allclose(init[K_OBS:], 7.0) + + def test_predict_new_raises(self): + h = PerPoolNoiseHead() + with pytest.raises(ValueError, match="cannot predict"): + h.predict_new(np.zeros(K_OBS * 2), np.zeros(5)) + + def test_unpack_result(self): + h = PerPoolNoiseHead() + params = np.arange(N_POOLS * K_OBS, dtype=float) + result = h.unpack_result(params, N_POOLS, K_ATTR) + assert result["noise_coeffs"].shape == (N_POOLS, K_OBS) + + +# ── SharedLinearNoiseHead ─────────────────────────────────────────────────── + + +class TestSharedLinearNoiseHead: + def test_n_params(self): + h = SharedLinearNoiseHead() + assert h.n_params(3, 5) == (1 + 5) * K_OBS + assert h.n_params(10, 7) == (1 + 7) * K_OBS + + def test_predict_bias_plus_dot(self): + k_attr = 3 + h = SharedLinearNoiseHead() + W_full = np.zeros((1 + k_attr, K_OBS)) + W_full[0, :] = 1.0 # bias_noise = [1, 1, ..., 1] + W_full[1, 0] = 2.0 # first feature maps to first noise coeff + params = jnp.array(W_full.ravel()) + x_attr_i = jnp.array([1.0, 0.0, 0.0]) + result = h.predict(params, 0, x_attr_i) + assert result.shape == (K_OBS,) + np.testing.assert_allclose(float(result[0]), 3.0) # 1 + 2*1 + np.testing.assert_allclose(float(result[1]), 1.0) # 1 + 0 + + def test_predict_ignores_pool_idx(self): + k_attr = 2 + h = SharedLinearNoiseHead() + params = jnp.ones((1 + k_attr) * K_OBS) + x = jnp.array([1.0, 2.0]) + r0 = h.predict(params, 0, x) + r5 = h.predict(params, 5, x) + np.testing.assert_allclose(r0, r5) + + def test_regularization_on_W_not_bias(self): + k_attr = 2 + h = SharedLinearNoiseHead(alpha=1.0) + W_full = np.zeros((1 + k_attr, K_OBS)) + W_full[0, :] = 100.0 # bias — not regularized + W_full[1, 0] = 3.0 + W_full[2, 0] = 4.0 + params = jnp.array(W_full.ravel()) + # reg = 1.0 * (9 + 16) = 25.0 + np.testing.assert_allclose(float(h.regularization(params)), 25.0) + + def test_init_default(self): + np.random.seed(42) + h = SharedLinearNoiseHead() + jdata = _make_fake_jdata() + init = h.init(jdata) + assert init.shape == ((1 + K_ATTR) * K_OBS,) + assert np.all(np.isfinite(init)) + + def test_predict_new(self): + k_attr = 2 + h = SharedLinearNoiseHead() + W_full = np.zeros((1 + k_attr, K_OBS)) + W_full[0, :] = 5.0 + W_full[1, 0] = 1.0 + params = W_full.ravel() + x_attr = np.array([2.0, 0.0]) + result = h.predict_new(params, x_attr) + assert result.shape == (K_OBS,) + np.testing.assert_allclose(result[0], 7.0) + np.testing.assert_allclose(result[1], 5.0) + + def test_unpack_result(self): + h = SharedLinearNoiseHead() + k_attr = 3 + W_full = np.arange((1 + k_attr) * K_OBS, dtype=float) + result = h.unpack_result(W_full, 2, k_attr) + assert "bias_noise" in result + assert "W_noise" in result + assert result["bias_noise"].shape == (K_OBS,) + assert result["W_noise"].shape == (k_attr, K_OBS) + + +# ── MLPHead ───────────────────────────────────────────────────────────────── + + +class TestMLPHead: + def test_n_params(self): + h = MLPHead("cad", hidden=16) + # k_attr=5: 5*16 + 16 + 16 + 1 = 113 + assert h.n_params(3, 5) == 113 + # k_attr=7: 7*16 + 16 + 16 + 1 = 145 + assert h.n_params(3, 7) == 145 + + def test_n_params_custom_hidden(self): + h = MLPHead("cad", hidden=8) + # k_attr=5: 5*8 + 8 + 8 + 1 = 57 + assert h.n_params(3, 5) == 57 + + def test_predict_with_zero_W2_equals_b2(self): + """With W2=0, output should be b2 regardless of input.""" + k_attr = 3 + h = MLPHead("cad", hidden=4) + n_p = h.n_params(1, k_attr) + params = np.zeros(n_p) + params[-1] = 2.5 # b2 + x_attr_i = jnp.array([1.0, 2.0, 3.0]) + result = float(h.predict(jnp.array(params), 0, x_attr_i)) + np.testing.assert_allclose(result, 2.5) + + def test_predict_nonlinear(self): + """MLP should produce different outputs for different inputs.""" + k_attr = 3 + h = MLPHead("cad", hidden=4, seed=42) + jdata = _make_fake_jdata() + # Override x_attr to have k_attr=3 + from quantammsim.calibration.joint_fit import JointData + jdata = JointData( + pool_data=jdata.pool_data, + x_attr=jnp.array(np.random.randn(N_POOLS, k_attr)), + pool_ids=jdata.pool_ids, + attr_names=[f"a{i}" for i in range(k_attr)], + ) + init = jnp.array(h.init(jdata)) + # Set W1 to nonzero so ReLU activations vary + np.random.seed(42) + W1 = np.random.randn(k_attr * 4) * 0.5 + init = init.at[:k_attr * 4].set(jnp.array(W1)) + # Set W2 to nonzero so output varies + init = init.at[k_attr * 4 + 4:k_attr * 4 + 8].set(jnp.ones(4) * 0.1) + + x1 = jnp.array([1.0, 0.0, 0.0]) + x2 = jnp.array([0.0, 1.0, 0.0]) + v1 = float(h.predict(init, 0, x1)) + v2 = float(h.predict(init, 0, x2)) + assert v1 != v2, "MLP should produce different outputs for different inputs" + + def test_predict_ignores_pool_idx(self): + """MLP output depends only on x_attr, not pool_idx.""" + k_attr = 3 + h = MLPHead("cad", hidden=4) + params = jnp.ones(h.n_params(5, k_attr)) * 0.1 + x = jnp.array([1.0, 2.0, 3.0]) + v0 = float(h.predict(params, 0, x)) + v3 = float(h.predict(params, 3, x)) + assert v0 == v3 + + def test_regularization_on_weights_not_biases(self): + k_attr = 2 + h_alpha1 = MLPHead("cad", hidden=2, alpha=1.0) + # Layout: W1(2*2=4), b1(2), W2(2), b2(1) = 9 params + params = np.zeros(9) + params[0] = 3.0 # W1[0,0] + params[1] = 4.0 # W1[0,1] + # b1 = 0 (indices 4,5) + params[6] = 1.0 # W2[0] + params[7] = 2.0 # W2[1] + params[8] = 999.0 # b2 — should not be regularized + # reg = 1.0 * (9 + 16 + 1 + 4) = 30.0 + result = float(h_alpha1.regularization(jnp.array(params))) + np.testing.assert_allclose(result, 30.0) + + def test_regularization_alpha_scaling(self): + k_attr = 2 + h = MLPHead("cad", hidden=2, alpha=0.5) + params = np.zeros(9) + params[0] = 2.0 # W1 weight + # reg = 0.5 * 4.0 = 2.0 + np.testing.assert_allclose(float(h.regularization(jnp.array(params))), 2.0) + + def test_init_default_cadence(self): + h = MLPHead("cad", hidden=4) + jdata = _make_fake_jdata() + init = h.init(jdata) + n_p = h.n_params(N_POOLS, K_ATTR) + assert init.shape == (n_p,) + assert np.all(np.isfinite(init)) + # b2 should be log(12) + np.testing.assert_allclose(init[-1], np.log(12.0)) + + def test_init_default_gas(self): + h = MLPHead("gas", hidden=4) + jdata = _make_fake_jdata() + init = h.init(jdata) + # b2 should be log(1) = 0 + np.testing.assert_allclose(init[-1], 0.0) + + def test_init_size(self): + """Init should return correct number of parameters.""" + h = MLPHead("cad", hidden=4) + jdata = _make_fake_jdata() + init = h.init(jdata) + assert init.shape == (h.n_params(N_POOLS, K_ATTR),) + assert np.all(np.isfinite(init)) + + def test_init_warm_start(self): + h = MLPHead("log_cadence", hidden=4) + jdata = _make_fake_jdata() + warm = { + POOL_PREFIXES[0]: {"log_cadence": 2.0}, + POOL_PREFIXES[1]: {"log_cadence": 3.0}, + } + init = h.init(jdata, warm_start=warm) + # b2 should be mean of warm-start values + np.testing.assert_allclose(init[-1], 2.5) + + def test_predict_new(self): + k_attr = 3 + h = MLPHead("cad", hidden=4) + n_p = h.n_params(1, k_attr) + params = np.zeros(n_p) + params[-1] = 2.5 # b2 + x_attr = np.array([1.0, 2.0, 3.0]) + result = h.predict_new(params, x_attr) + np.testing.assert_allclose(result, 2.5) + + def test_predict_new_matches_predict(self): + """predict_new should give same result as predict for same input.""" + k_attr = 3 + h = MLPHead("cad", hidden=4, seed=42) + n_p = h.n_params(1, k_attr) + np.random.seed(99) + params = np.random.randn(n_p) * 0.1 + x_attr = np.array([0.5, -1.0, 2.0]) + + jax_result = float(h.predict(jnp.array(params), 0, jnp.array(x_attr))) + np_result = h.predict_new(params, x_attr) + np.testing.assert_allclose(jax_result, np_result, rtol=1e-6) + + def test_unpack_result(self): + k_attr = 3 + h = MLPHead("cad", hidden=4) + n_p = h.n_params(1, k_attr) + params = np.arange(n_p, dtype=float) + result = h.unpack_result(params, 2, k_attr) + assert f"mlp_cad_W1" in result + assert f"mlp_cad_b1" in result + assert f"mlp_cad_W2" in result + assert f"mlp_cad_b2" in result + assert result["mlp_cad_W1"].shape == (k_attr, 4) + assert result["mlp_cad_b1"].shape == (4,) + assert result["mlp_cad_W2"].shape == (4,) + + def test_make_bounds(self): + h = MLPHead("cad", hidden=4) + bounds = h.make_bounds(3, 5) + assert len(bounds) == h.n_params(3, 5) + + def test_jax_differentiable(self): + """MLP predict should be JAX-differentiable.""" + import jax + k_attr = 3 + h = MLPHead("cad", hidden=4) + n_p = h.n_params(1, k_attr) + np.random.seed(42) + params = jnp.array(np.random.randn(n_p) * 0.1) + x_attr_i = jnp.array([1.0, 2.0, 3.0]) + + def loss(p): + return h.predict(p, 0, x_attr_i) ** 2 + + grad = jax.grad(loss)(params) + assert grad.shape == params.shape + assert jnp.all(jnp.isfinite(grad)) + + +# ── MLPNoiseHead ──────────────────────────────────────────────────────────── + + +class TestMLPNoiseHead: + def test_n_params(self): + h = MLPNoiseHead(hidden=16) + # k_attr=5: 5*16 + 16 + 16*8 + 8 = 80+16+128+8 = 232 + assert h.n_params(3, 5) == 232 + # k_attr=7: 7*16 + 16 + 16*8 + 8 = 112+16+128+8 = 264 + assert h.n_params(3, 7) == 264 + + def test_n_params_custom_hidden(self): + h = MLPNoiseHead(hidden=8) + # k_attr=5: 5*8 + 8 + 8*8 + 8 = 40+8+64+8 = 120 + assert h.n_params(3, 5) == 120 + + def test_predict_output_shape(self): + k_attr = 3 + h = MLPNoiseHead(hidden=4) + n_p = h.n_params(1, k_attr) + params = jnp.zeros(n_p) + x_attr_i = jnp.array([1.0, 2.0, 3.0]) + result = h.predict(params, 0, x_attr_i) + assert result.shape == (K_OBS,) + + def test_predict_with_zero_W2_equals_b2(self): + """With W2=0, output should be b2 regardless of input.""" + k_attr = 3 + h = MLPNoiseHead(hidden=4) + n_p = h.n_params(1, k_attr) + params = np.zeros(n_p) + # b2 is the last K_OBS elements + params[-K_OBS:] = np.arange(K_OBS) + 1.0 + x_attr_i = jnp.array([1.0, 2.0, 3.0]) + result = h.predict(jnp.array(params), 0, x_attr_i) + np.testing.assert_allclose(result, np.arange(K_OBS) + 1.0) + + def test_predict_nonlinear(self): + """MLP should produce different outputs for different inputs.""" + k_attr = 3 + h = MLPNoiseHead(hidden=4, seed=42) + n_p = h.n_params(1, k_attr) + np.random.seed(42) + params = jnp.array(np.random.randn(n_p) * 0.1) + x1 = jnp.array([1.0, 0.0, 0.0]) + x2 = jnp.array([0.0, 1.0, 0.0]) + v1 = h.predict(params, 0, x1) + v2 = h.predict(params, 0, x2) + assert not jnp.allclose(v1, v2), "Should produce different outputs" + + def test_predict_ignores_pool_idx(self): + k_attr = 3 + h = MLPNoiseHead(hidden=4) + params = jnp.ones(h.n_params(5, k_attr)) * 0.1 + x = jnp.array([1.0, 2.0, 3.0]) + v0 = h.predict(params, 0, x) + v3 = h.predict(params, 3, x) + np.testing.assert_allclose(v0, v3) + + def test_regularization_on_weights_not_biases(self): + k_attr = 2 + h = MLPNoiseHead(hidden=2, alpha=1.0) + # Layout: W1(2*2=4), b1(2), W2(2*8=16), b2(8) = 30 params + n_p = h.n_params(1, k_attr) + assert n_p == 30 + params = np.zeros(n_p) + params[0] = 3.0 # W1[0,0] + params[1] = 4.0 # W1[0,1] + # b1 at indices 4,5 — not regularized + params[6] = 1.0 # W2[0,0] + params[7] = 2.0 # W2[0,1] + params[-1] = 999.0 # b2[-1] — not regularized + # reg = 1.0 * (9 + 16 + 1 + 4) = 30.0 + result = float(h.regularization(jnp.array(params))) + np.testing.assert_allclose(result, 30.0) + + def test_init_default(self): + np.random.seed(42) + h = MLPNoiseHead(hidden=4) + jdata = _make_fake_jdata() + init = h.init(jdata) + n_p = h.n_params(N_POOLS, K_ATTR) + assert init.shape == (n_p,) + assert np.all(np.isfinite(init)) + + def test_init_size(self): + """Init should return correct number of parameters.""" + h = MLPNoiseHead(hidden=4) + jdata = _make_fake_jdata() + init = h.init(jdata) + assert init.shape == (h.n_params(N_POOLS, K_ATTR),) + assert np.all(np.isfinite(init)) + + def test_init_b2_from_ols(self): + """b2 should be pooled OLS noise coefficients.""" + np.random.seed(42) + h = MLPNoiseHead(hidden=4) + jdata = _make_fake_jdata() + init = h.init(jdata) + b2 = init[-K_OBS:] + assert np.all(np.isfinite(b2)) + # Should be nonzero (OLS on random data) + assert np.any(b2 != 0.0) + + def test_init_warm_start(self): + h = MLPNoiseHead(hidden=4) + jdata = _make_fake_jdata() + warm = { + POOL_PREFIXES[0]: {"noise_coeffs": np.ones(K_OBS) * 5.0}, + POOL_PREFIXES[1]: {"noise_coeffs": np.ones(K_OBS) * 7.0}, + } + init = h.init(jdata, warm_start=warm) + b2 = init[-K_OBS:] + # b2 should be mean of warm-start noise: (5+7)/2 = 6 + np.testing.assert_allclose(b2, 6.0) + + def test_predict_new(self): + k_attr = 3 + h = MLPNoiseHead(hidden=4) + n_p = h.n_params(1, k_attr) + params = np.zeros(n_p) + params[-K_OBS:] = np.arange(K_OBS) + 1.0 # b2 + x_attr = np.array([1.0, 2.0, 3.0]) + result = h.predict_new(params, x_attr) + assert result.shape == (K_OBS,) + np.testing.assert_allclose(result, np.arange(K_OBS) + 1.0) + + def test_predict_new_matches_predict(self): + k_attr = 3 + h = MLPNoiseHead(hidden=4, seed=42) + n_p = h.n_params(1, k_attr) + np.random.seed(99) + params = np.random.randn(n_p) * 0.1 + x_attr = np.array([0.5, -1.0, 2.0]) + + jax_result = np.array(h.predict(jnp.array(params), 0, jnp.array(x_attr))) + np_result = h.predict_new(params, x_attr) + np.testing.assert_allclose(jax_result, np_result, rtol=1e-6) + + def test_unpack_result(self): + k_attr = 3 + h = MLPNoiseHead(hidden=4) + n_p = h.n_params(1, k_attr) + params = np.arange(n_p, dtype=float) + result = h.unpack_result(params, 2, k_attr) + assert "mlp_noise_W1" in result + assert "mlp_noise_b1" in result + assert "mlp_noise_W2" in result + assert "mlp_noise_b2" in result + assert result["mlp_noise_W1"].shape == (k_attr, 4) + assert result["mlp_noise_b1"].shape == (4,) + assert result["mlp_noise_W2"].shape == (4, K_OBS) + assert result["mlp_noise_b2"].shape == (K_OBS,) + + def test_make_bounds(self): + h = MLPNoiseHead(hidden=4) + bounds = h.make_bounds(3, 5) + assert len(bounds) == h.n_params(3, 5) + + def test_jax_differentiable(self): + import jax + k_attr = 3 + h = MLPNoiseHead(hidden=4) + n_p = h.n_params(1, k_attr) + np.random.seed(42) + params = jnp.array(np.random.randn(n_p) * 0.1) + x_attr_i = jnp.array([1.0, 2.0, 3.0]) + + def loss(p): + return jnp.sum(h.predict(p, 0, x_attr_i) ** 2) + + grad = jax.grad(loss)(params) + assert grad.shape == params.shape + assert jnp.all(jnp.isfinite(grad)) + + +# ── Reduced k_obs=4 tests ───────────────────────────────────────────────── + +K_OBS_REDUCED = 4 + + +def _make_fake_jdata_reduced(): + """JointData-like object with k_obs=4 x_obs for reduced noise testing.""" + from quantammsim.calibration.joint_fit import JointData + + pool_data = [] + for _ in range(N_POOLS): + n_obs = 14 + x_obs = np.random.randn(n_obs, K_OBS_REDUCED) + x_obs[:, 0] = 1.0 # intercept column + y_obs = np.random.randn(n_obs) * 0.5 + 9.0 + pool_data.append({ + "x_obs": jnp.array(x_obs), + "y_obs": jnp.array(y_obs), + "day_indices": jnp.arange(n_obs) % 10, + }) + + x_attr = jnp.array(np.random.randn(N_POOLS, K_ATTR)) + return JointData( + pool_data=pool_data, + x_attr=x_attr, + pool_ids=POOL_PREFIXES[:N_POOLS], + attr_names=[f"attr_{i}" for i in range(K_ATTR)], + ) + + +class TestPerPoolNoiseHeadReduced: + """PerPoolNoiseHead with k_obs=4.""" + + def test_n_params(self): + h = PerPoolNoiseHead(k_obs=4) + assert h.n_params(3, 5) == 3 * 4 + + def test_predict_correct_slice(self): + h = PerPoolNoiseHead(k_obs=4) + params = jnp.arange(3 * 4, dtype=float) + x_attr_i = jnp.zeros(5) + for i in range(3): + result = h.predict(params, i, x_attr_i) + expected = params[i * 4:(i + 1) * 4] + np.testing.assert_allclose(result, expected) + + def test_init_ols(self): + np.random.seed(42) + h = PerPoolNoiseHead(k_obs=4) + jdata = _make_fake_jdata_reduced() + init = h.init(jdata) + assert init.shape == (N_POOLS * 4,) + assert np.all(np.isfinite(init)) + + def test_roundtrip(self): + np.random.seed(42) + h = PerPoolNoiseHead(k_obs=4) + jdata = _make_fake_jdata_reduced() + init = h.init(jdata) + result = h.unpack_result(init, N_POOLS, K_ATTR) + assert result["noise_coeffs"].shape == (N_POOLS, 4) + + def test_default_unchanged(self): + h = PerPoolNoiseHead() + assert h.k_obs == K_OBS + assert h.n_params(3, 5) == 3 * K_OBS + + +class TestSharedLinearNoiseHeadReduced: + """SharedLinearNoiseHead with k_obs=4.""" + + def test_n_params(self): + h = SharedLinearNoiseHead(k_obs=4) + assert h.n_params(3, 5) == (1 + 5) * 4 + + def test_predict(self): + k_attr = 3 + h = SharedLinearNoiseHead(k_obs=4) + W_full = np.zeros((1 + k_attr, 4)) + W_full[0, :] = 1.0 + W_full[1, 0] = 2.0 + params = jnp.array(W_full.ravel()) + x_attr_i = jnp.array([1.0, 0.0, 0.0]) + result = h.predict(params, 0, x_attr_i) + assert result.shape == (4,) + np.testing.assert_allclose(float(result[0]), 3.0) + np.testing.assert_allclose(float(result[1]), 1.0) + + def test_init(self): + np.random.seed(42) + h = SharedLinearNoiseHead(k_obs=4) + jdata = _make_fake_jdata_reduced() + init = h.init(jdata) + assert init.shape == ((1 + K_ATTR) * 4,) + assert np.all(np.isfinite(init)) + + def test_default_unchanged(self): + h = SharedLinearNoiseHead() + assert h.k_obs == K_OBS + assert h.n_params(3, 5) == (1 + 5) * K_OBS + + +class TestMLPNoiseHeadReduced: + """MLPNoiseHead with k_obs=4.""" + + def test_n_params(self): + h = MLPNoiseHead(hidden=16, k_obs=4) + # k_attr=5: 5*16 + 16 + 16*4 + 4 = 80+16+64+4 = 164 + assert h.n_params(3, 5) == 164 + + def test_predict(self): + k_attr = 3 + h = MLPNoiseHead(hidden=4, k_obs=4) + n_p = h.n_params(1, k_attr) + params = jnp.zeros(n_p) + x_attr_i = jnp.array([1.0, 2.0, 3.0]) + result = h.predict(params, 0, x_attr_i) + assert result.shape == (4,) + + def test_init(self): + np.random.seed(42) + h = MLPNoiseHead(hidden=4, k_obs=4) + jdata = _make_fake_jdata_reduced() + init = h.init(jdata) + n_p = h.n_params(N_POOLS, K_ATTR) + assert init.shape == (n_p,) + assert np.all(np.isfinite(init)) + + def test_regularization(self): + k_attr = 2 + h = MLPNoiseHead(hidden=2, alpha=1.0, k_obs=4) + # Layout: W1(2*2=4), b1(2), W2(2*4=8), b2(4) = 18 params + n_p = h.n_params(1, k_attr) + assert n_p == 18 + params = np.zeros(n_p) + params[0] = 3.0 # W1[0,0] + params[1] = 4.0 # W1[0,1] + params[6] = 1.0 # W2[0,0] + params[7] = 2.0 # W2[0,1] + params[-1] = 999.0 # b2[-1] — not regularized + # reg = 1.0 * (9 + 16 + 1 + 4) = 30.0 + result = float(h.regularization(jnp.array(params))) + np.testing.assert_allclose(result, 30.0) + + def test_default_unchanged(self): + h = MLPNoiseHead() + assert h.k_obs == K_OBS + assert h.n_params(3, 5) == 232 + + +# ── TokenFactoredNoiseHead ───────────────────────────────────────────────── + + +def _make_token_factored_head(k_obs=K_OBS_REDUCED): + """Build a TokenFactoredNoiseHead from synthetic 2-pool, 3-token data.""" + from quantammsim.calibration.heads import TokenFactoredNoiseHead + + # Pool 0: (BTC=1, ETH=2) on MAINNET=1, fee=0.003 + # Pool 1: (AAVE=0, ETH=2) on ARBITRUM=0, fee=0.01 + token_a_idx = np.array([1, 0], dtype=np.int32) # BTC, AAVE + token_b_idx = np.array([2, 2], dtype=np.int32) # ETH, ETH + chain_idx = np.array([1, 0], dtype=np.int32) # MAINNET, ARBITRUM + log_fees = np.array([np.log(0.003), np.log(0.01)]) + x_token = np.array([ + [1.0, 20.0, 0.0, 0.0, 0.0], # AAVE: volatile + [1.0, 25.0, 0.0, 0.0, 0.0], # BTC: volatile + [1.0, 26.0, 0.0, 1.0, 1.0], # ETH: eth_derivative + L1_native + ]) + token_index = {"AAVE": 0, "BTC": 1, "ETH": 2} + chain_index = {"ARBITRUM": 0, "MAINNET": 1} + + head = TokenFactoredNoiseHead( + token_a_idx=token_a_idx, + token_b_idx=token_b_idx, + chain_idx=chain_idx, + log_fees=log_fees, + x_token=x_token, + n_tokens=3, + n_chains=2, + token_index=token_index, + chain_index=chain_index, + k_obs=k_obs, + lambda_delta=1.0, + lambda_token=0.1, + lambda_chain=0.1, + lambda_fee=0.01, + ) + return head + + +class TestTokenFactoredNoiseHead: + + def test_is_head(self): + head = _make_token_factored_head() + assert isinstance(head, Head) + + def test_n_params(self): + head = _make_token_factored_head(k_obs=4) + # 3 tokens * 4 + 5 d_token * 4 + 2 chains * 4 + 4 beta_fee + 2 pools * 4 + # = 12 + 20 + 8 + 4 + 8 = 52 + assert head.n_params(2, K_ATTR) == 52 + + def test_predict_returns_k_obs_vector(self): + head = _make_token_factored_head(k_obs=4) + n_p = head.n_params(2, K_ATTR) + params = jnp.zeros(n_p) + x_attr_i = jnp.zeros(K_ATTR) + result = head.predict(params, 0, x_attr_i) + assert result.shape == (4,) + + def test_predict_additivity(self): + """predict(pool_0) = u[BTC] + u[ETH] + alpha[MAINNET] + + beta_fee * log(0.003) + delta[0]""" + head = _make_token_factored_head(k_obs=4) + n_p = head.n_params(2, K_ATTR) + + # Build params with known values + params = np.zeros(n_p) + k = 4 # k_obs + # u: (3 tokens, 4) at offset 0 + u_flat = np.array([ + 1.0, 0.0, 0.0, 0.0, # AAVE + 2.0, 0.5, 0.0, 0.0, # BTC + 3.0, 1.0, 0.0, 0.0, # ETH + ]) + params[:12] = u_flat + # Gamma: (5, 4) at offset 12 — skip (doesn't affect predict) + # alpha: (2, 4) at offset 32 + alpha_flat = np.array([ + 0.1, 0.0, 0.0, 0.0, # ARBITRUM + 0.2, 0.0, 0.0, 0.0, # MAINNET + ]) + params[32:40] = alpha_flat + # beta_fee: (4,) at offset 40 + params[40:44] = np.array([0.5, 0.0, 0.0, 0.0]) + # delta: (2, 4) at offset 44 + params[44:48] = np.array([0.05, 0.0, 0.0, 0.0]) # pool 0 delta + + result = head.predict(jnp.array(params), 0, jnp.zeros(K_ATTR)) + + # Expected for pool 0: u[BTC] + u[ETH] + alpha[MAINNET] + # + beta_fee * log(0.003) + delta[0] + expected_0 = 2.0 + 3.0 + 0.2 + 0.5 * np.log(0.003) + 0.05 + np.testing.assert_allclose(float(result[0]), expected_0, rtol=1e-5) + + def test_regularization_nonneg_and_finite(self): + head = _make_token_factored_head() + n_p = head.n_params(2, K_ATTR) + np.random.seed(42) + params = jnp.array(np.random.randn(n_p) * 0.1) + reg = float(head.regularization(params)) + assert np.isfinite(reg) + assert reg >= 0.0 + + def test_regularization_zero_when_perfect(self): + """If u = x_token @ Gamma exactly, delta=0, alpha=0, beta_fee=0, + then only the Gamma-predicted part has zero token reg.""" + head = _make_token_factored_head(k_obs=4) + n_p = head.n_params(2, K_ATTR) + params = np.zeros(n_p) + # Set Gamma to something, then set u = x_token @ Gamma + np.random.seed(7) + Gamma = np.random.randn(5, 4) * 0.1 + u = head.x_token @ Gamma # (3, 4) + params[:12] = u.ravel() + params[12:32] = Gamma.ravel() + # alpha, beta_fee, delta all zero + reg = float(head.regularization(jnp.array(params))) + # Only lambda_token * 0 + lambda_chain * 0 + lambda_fee * 0 + lambda_delta * 0 + np.testing.assert_allclose(reg, 0.0, atol=1e-10) + + def test_init_cold(self): + head = _make_token_factored_head(k_obs=4) + jdata = _make_fake_jdata_reduced() + init = head.init(jdata) + n_p = head.n_params(N_POOLS, K_ATTR) + assert init.shape == (n_p,) + assert np.all(np.isfinite(init)) + + def test_init_warm_start_roundtrip(self): + """init from warm_start → predict ≈ warm_start noise_coeffs.""" + head = _make_token_factored_head(k_obs=4) + jdata = _make_fake_jdata_reduced() + + # Warm start with known noise coefficients per pool + warm = { + POOL_PREFIXES[0]: {"noise_coeffs": np.array([9.0, 0.5, 0.1, -0.2])}, + POOL_PREFIXES[1]: {"noise_coeffs": np.array([8.5, 0.3, 0.2, -0.1])}, + } + init = head.init(jdata, warm_start=warm) + params = jnp.array(init) + x_attr_dummy = jnp.zeros(K_ATTR) + + for i, pid in enumerate(jdata.pool_ids): + predicted = np.array(head.predict(params, i, x_attr_dummy)) + target = warm[pid]["noise_coeffs"] + # Should approximately recover the warm-start values + # (not exact because the lstsq decomposition is underdetermined + # with 2 pools and 3 tokens) + np.testing.assert_allclose(predicted, target, atol=0.5) + + def test_gradient_finite(self): + """jax.grad of a simple loss at init produces finite gradients.""" + import jax + head = _make_token_factored_head(k_obs=4) + jdata = _make_fake_jdata_reduced() + init = jnp.array(head.init(jdata)) + x_attr_i = jnp.zeros(K_ATTR) + + def loss(p): + c = head.predict(p, 0, x_attr_i) + return jnp.sum(c ** 2) + head.regularization(p) + + grad = jax.grad(loss)(init) + assert grad.shape == init.shape + assert jnp.all(jnp.isfinite(grad)) + + def test_unpack_result_keys(self): + head = _make_token_factored_head(k_obs=4) + n_p = head.n_params(2, K_ATTR) + np.random.seed(42) + params = np.random.randn(n_p) + result = head.unpack_result(params, 2, K_ATTR) + for key in ["token_effects", "Gamma", "chain_effects", + "beta_fee", "noise_deltas", "noise_coeffs"]: + assert key in result, f"Missing key: {key}" + assert result["token_effects"].shape == (3, 4) + assert result["Gamma"].shape == (5, 4) + assert result["chain_effects"].shape == (2, 4) + assert result["beta_fee"].shape == (4,) + assert result["noise_deltas"].shape == (2, 4) + assert result["noise_coeffs"].shape == (2, 4) + + def test_make_bounds(self): + head = _make_token_factored_head(k_obs=4) + bounds = head.make_bounds(2, K_ATTR) + assert len(bounds) == head.n_params(2, K_ATTR) + assert all(b == (None, None) for b in bounds) + + def test_predict_new_pool_seen_tokens(self): + head = _make_token_factored_head(k_obs=4) + n_p = head.n_params(2, K_ATTR) + np.random.seed(42) + params = np.random.randn(n_p) * 0.1 + result = head.predict_new_pool( + params, "BTC", "AAVE", "MAINNET", 0.003, n_pools=2 + ) + assert "noise_coeffs" in result + assert "components" in result + nc = result["noise_coeffs"] + assert nc.shape == (4,) or len(nc) == 4 + # Should equal u[BTC] + u[AAVE] + alpha[MAINNET] + beta_fee*log(0.003) + # (no delta for new pool) + comps = result["components"] + reconstructed = (comps["token_a"] + comps["token_b"] + + comps["chain"] + comps["fee"]) + np.testing.assert_allclose(nc, reconstructed, rtol=1e-6) + + def test_predict_new_pool_unseen_token(self): + head = _make_token_factored_head(k_obs=4) + n_p = head.n_params(2, K_ATTR) + np.random.seed(42) + params = np.random.randn(n_p) * 0.1 + # "LINK" is not in token_index → should fall back to Gamma + result = head.predict_new_pool( + params, "LINK", "ETH", "MAINNET", 0.003, n_pools=2 + ) + assert "noise_coeffs" in result + assert result["noise_coeffs"].shape == (4,) or len(result["noise_coeffs"]) == 4 + + def test_predict_new_pool_unseen_chain(self): + head = _make_token_factored_head(k_obs=4) + n_p = head.n_params(2, K_ATTR) + np.random.seed(42) + params = np.random.randn(n_p) * 0.1 + # "BASE" is not in chain_index → alpha = zeros + result = head.predict_new_pool( + params, "BTC", "ETH", "BASE", 0.003, n_pools=2 + ) + np.testing.assert_allclose( + result["components"]["chain"], np.zeros(4) + ) diff --git a/tests/calibration/test_joint_fit.py b/tests/calibration/test_joint_fit.py new file mode 100644 index 0000000..1fe02f2 --- /dev/null +++ b/tests/calibration/test_joint_fit.py @@ -0,0 +1,422 @@ +"""Tests for quantammsim.calibration.joint_fit — joint end-to-end optimization (Option A).""" + +import jax +import jax.numpy as jnp +import numpy as np +import pytest + +from tests.calibration.conftest import K_OBS, POOL_IDS_FULL, POOL_PREFIXES + + +@pytest.fixture +def matched_data(synthetic_daily_grid, synthetic_panel, tmp_path): + """Build matched data dict from synthetic fixtures.""" + from quantammsim.calibration.pool_data import match_grids_to_panel + + grid_dir = tmp_path / "grids" + grid_dir.mkdir() + for prefix in POOL_PREFIXES: + synthetic_daily_grid.to_parquet( + grid_dir / f"{prefix}_daily.parquet", index=False + ) + return match_grids_to_panel(str(grid_dir), synthetic_panel) + + +class TestPrepareJointData: + """Test prepare_joint_data: build batched arrays for joint optimization.""" + + def test_returns_expected_structure(self, matched_data): + from quantammsim.calibration.joint_fit import prepare_joint_data + + jdata = prepare_joint_data(matched_data) + assert hasattr(jdata, "pool_data") + assert hasattr(jdata, "x_attr") + assert hasattr(jdata, "pool_ids") + + def test_pool_count(self, matched_data): + from quantammsim.calibration.joint_fit import prepare_joint_data + + jdata = prepare_joint_data(matched_data) + assert len(jdata.pool_data) == len(matched_data) + + def test_pool_data_has_jax_arrays(self, matched_data): + from quantammsim.calibration.joint_fit import prepare_joint_data + + jdata = prepare_joint_data(matched_data) + for pd in jdata.pool_data: + assert isinstance(pd["x_obs"], jnp.ndarray) + assert isinstance(pd["y_obs"], jnp.ndarray) + assert isinstance(pd["day_indices"], jnp.ndarray) + + def test_x_attr_shape(self, matched_data): + from quantammsim.calibration.joint_fit import prepare_joint_data + + jdata = prepare_joint_data(matched_data) + n_pools = len(matched_data) + assert jdata.x_attr.shape[0] == n_pools + assert jdata.x_attr.shape[1] > 0 # K_attr + + +class TestJointLoss: + """Test joint_loss: end-to-end loss over all pools.""" + + def _make_loss_fn(self, matched_data): + from quantammsim.calibration.joint_fit import ( + make_joint_loss_fn, + make_initial_joint_params, + prepare_joint_data, + ) + + jdata = prepare_joint_data(matched_data) + init = make_initial_joint_params(jdata, mode="per_pool_noise") + loss_fn = make_joint_loss_fn(jdata, mode="per_pool_noise") + return loss_fn, init, jdata + + def test_loss_scalar(self, matched_data): + loss_fn, init, _ = self._make_loss_fn(matched_data) + loss = loss_fn(init) + assert loss.shape == () + + def test_loss_positive(self, matched_data): + loss_fn, init, _ = self._make_loss_fn(matched_data) + loss = loss_fn(init) + assert float(loss) >= 0 + + def test_loss_differentiable(self, matched_data): + loss_fn, init, _ = self._make_loss_fn(matched_data) + grad = jax.grad(loss_fn)(init) + assert grad.shape == init.shape + assert jnp.all(jnp.isfinite(grad)) + + def test_loss_grad_nonzero(self, matched_data): + loss_fn, init, _ = self._make_loss_fn(matched_data) + grad = jax.grad(loss_fn)(init) + assert float(jnp.sum(jnp.abs(grad))) > 0 + + def test_shared_cadence_gas_affects_all_pools(self, matched_data): + """Changing W_cad should affect losses from all pools.""" + from quantammsim.calibration.joint_fit import ( + make_joint_loss_fn, + make_initial_joint_params, + prepare_joint_data, + unpack_joint_params, + ) + + jdata = prepare_joint_data(matched_data) + init = make_initial_joint_params(jdata, mode="per_pool_noise") + loss_fn = make_joint_loss_fn(jdata, mode="per_pool_noise") + + # Gradient w.r.t. W_cad should be nonzero + grad = jax.grad(loss_fn)(init) + config = {"n_pools": len(jdata.pool_data), + "k_attr": jdata.x_attr.shape[1], + "mode": "per_pool_noise"} + params = unpack_joint_params(grad, config) + assert float(jnp.sum(jnp.abs(params["W_cad"]))) > 0 + + +class TestFitJoint: + """Test fit_joint: L-BFGS-B joint optimization.""" + + def test_returns_result(self, matched_data): + from quantammsim.calibration.joint_fit import fit_joint + + result = fit_joint(matched_data, mode="per_pool_noise", maxiter=20) + assert isinstance(result, dict) + for key in ["bias_cad", "bias_gas", "W_cad", "W_gas", "loss", "converged"]: + assert key in result, f"Missing key: {key}" + + def test_loss_decreases(self, matched_data): + from quantammsim.calibration.joint_fit import fit_joint + + result = fit_joint(matched_data, mode="per_pool_noise", maxiter=50) + assert result["loss"] <= result["init_loss"] + + def test_predict_new_pool(self, matched_data): + from quantammsim.calibration.joint_fit import fit_joint, predict_new_pool_joint + + result = fit_joint(matched_data, mode="per_pool_noise", maxiter=20) + # Predict for a new pool — k_attr must match training + k_attr = result["W_cad"].shape[0] + x_attr_new = np.zeros(k_attr) + x_attr_new[0] = 1.0 # intercept + pred = predict_new_pool_joint(result, x_attr_new) + assert "cadence_minutes" in pred + assert "gas_usd" in pred + assert pred["cadence_minutes"] > 0 + assert pred["gas_usd"] > 0 + + def test_init_from_option_c(self, matched_data): + """Warm-starting from Option C per-pool fits should work.""" + from quantammsim.calibration.joint_fit import fit_joint + from quantammsim.calibration.per_pool_fit import fit_all_pools + + option_c = fit_all_pools(matched_data) + result = fit_joint( + matched_data, mode="per_pool_noise", + init_from_option_c=option_c, maxiter=20, + ) + assert result["loss"] >= 0 + + +class TestModes: + """Test per_pool_noise vs shared_noise modes.""" + + def test_per_pool_noise_mode(self, matched_data): + from quantammsim.calibration.joint_fit import fit_joint + + result = fit_joint(matched_data, mode="per_pool_noise", maxiter=20) + assert "noise_coeffs" in result + n_pools = len(matched_data) + assert result["noise_coeffs"].shape == (n_pools, K_OBS) + + def test_shared_noise_mode(self, matched_data): + from quantammsim.calibration.joint_fit import fit_joint + + result = fit_joint(matched_data, mode="shared_noise", maxiter=20) + assert "W_noise" in result + k_attr = result["W_cad"].shape[0] + assert result["W_noise"].shape == (k_attr, K_OBS) + + def test_shared_noise_predict(self, matched_data): + from quantammsim.calibration.joint_fit import fit_joint, predict_new_pool_joint + + result = fit_joint(matched_data, mode="shared_noise", maxiter=20) + k_attr = result["W_cad"].shape[0] + x_attr_new = np.zeros(k_attr) + x_attr_new[0] = 1.0 # intercept + pred = predict_new_pool_joint(result, x_attr_new) + assert "noise_coeffs" in pred + assert len(pred["noise_coeffs"]) == K_OBS + + +class TestPrepareTokenFactoredData: + def test_returns_jdata_and_encoding(self, matched_data): + from quantammsim.calibration.joint_fit import prepare_token_factored_data + + jdata, token_enc = prepare_token_factored_data(matched_data) + assert hasattr(jdata, "pool_data") + assert hasattr(jdata, "x_attr") + assert "token_index" in token_enc + assert "token_a_idx" in token_enc + + def test_reduced_x_obs_by_default(self, matched_data): + from quantammsim.calibration.joint_fit import prepare_token_factored_data + from quantammsim.calibration.pool_data import K_OBS_REDUCED + + jdata, _ = prepare_token_factored_data(matched_data) + for pd in jdata.pool_data: + assert pd["x_obs"].shape[1] == K_OBS_REDUCED + + def test_encoding_matches_pools(self, matched_data): + from quantammsim.calibration.joint_fit import prepare_token_factored_data + + jdata, enc = prepare_token_factored_data(matched_data) + n_pools = len(jdata.pool_data) + assert len(enc["token_a_idx"]) == n_pools + assert len(enc["token_b_idx"]) == n_pools + assert len(enc["chain_idx"]) == n_pools + assert len(enc["log_fees"]) == n_pools + + +class TestTokenFactoredEndToEnd: + """Full pipeline: TokenFactoredNoiseHead + CalibrationModel + fit.""" + + def test_model_fits_and_converges(self, matched_data): + from quantammsim.calibration.calibration_model import CalibrationModel + from quantammsim.calibration.heads import ( + FixedHead, PerPoolHead, TokenFactoredNoiseHead, + ) + from quantammsim.calibration.joint_fit import prepare_token_factored_data + from quantammsim.calibration.loss import CHAIN_GAS_USD + from quantammsim.calibration.pool_data import K_OBS_REDUCED + + jdata, enc = prepare_token_factored_data(matched_data) + n_pools = len(jdata.pool_data) + + # Gas: fixed to chain defaults + gas_values = [] + for pid in jdata.pool_ids: + chain = matched_data[pid]["chain"] + gas_values.append(np.log(max(CHAIN_GAS_USD.get(chain, 1.0), 1e-6))) + gas_head = FixedHead("log_gas", np.array(gas_values)) + + # Cadence: per-pool + cad_head = PerPoolHead("log_cadence", default=np.log(12.0)) + + # Noise: token-factored + noise_head = TokenFactoredNoiseHead(k_obs=K_OBS_REDUCED, **enc) + + model = CalibrationModel(cad_head, gas_head, noise_head) + result = model.fit(jdata, maxiter=50) + + assert result["loss"] <= result["init_loss"] + assert "token_effects" in result + assert "noise_coeffs" in result + assert result["noise_coeffs"].shape == (n_pools, K_OBS_REDUCED) + + def test_model_with_warm_start(self, matched_data): + from quantammsim.calibration.calibration_model import CalibrationModel + from quantammsim.calibration.heads import ( + FixedHead, PerPoolHead, TokenFactoredNoiseHead, + ) + from quantammsim.calibration.joint_fit import prepare_token_factored_data + from quantammsim.calibration.loss import CHAIN_GAS_USD + from quantammsim.calibration.per_pool_fit import fit_all_pools + from quantammsim.calibration.pool_data import K_OBS_REDUCED + + # Run Option C first + option_c = fit_all_pools( + matched_data, fix_gas_to_chain=True, reduced=True + ) + + jdata, enc = prepare_token_factored_data(matched_data) + n_pools = len(jdata.pool_data) + + gas_values = [] + for pid in jdata.pool_ids: + chain = matched_data[pid]["chain"] + gas_values.append(np.log(max(CHAIN_GAS_USD.get(chain, 1.0), 1e-6))) + + noise_head = TokenFactoredNoiseHead(k_obs=K_OBS_REDUCED, **enc) + model = CalibrationModel( + PerPoolHead("log_cadence", default=np.log(12.0)), + FixedHead("log_gas", np.array(gas_values)), + noise_head, + ) + result = model.fit(jdata, maxiter=100, warm_start=option_c) + assert result["loss"] <= result["init_loss"] + + +class TestDataRegLossSeparation: + """Test that fit() reports data_loss and reg_loss separately.""" + + def test_fit_reports_data_and_reg_loss(self, matched_data): + from quantammsim.calibration.calibration_model import CalibrationModel + from quantammsim.calibration.heads import ( + FixedHead, PerPoolHead, TokenFactoredNoiseHead, + ) + from quantammsim.calibration.joint_fit import prepare_token_factored_data + from quantammsim.calibration.loss import CHAIN_GAS_USD + from quantammsim.calibration.pool_data import K_OBS_REDUCED + + jdata, enc = prepare_token_factored_data(matched_data) + n_pools = len(jdata.pool_data) + + gas_values = [] + for pid in jdata.pool_ids: + chain = matched_data[pid]["chain"] + gas_values.append(np.log(max(CHAIN_GAS_USD.get(chain, 1.0), 1e-6))) + + noise_head = TokenFactoredNoiseHead(k_obs=K_OBS_REDUCED, **enc) + model = CalibrationModel( + PerPoolHead("log_cadence", default=np.log(12.0)), + FixedHead("log_gas", np.array(gas_values)), + noise_head, + ) + result = model.fit(jdata, maxiter=50) + + assert "data_loss" in result + assert "reg_loss" in result + + def test_data_plus_reg_equals_total(self, matched_data): + from quantammsim.calibration.calibration_model import CalibrationModel + from quantammsim.calibration.heads import ( + FixedHead, PerPoolHead, TokenFactoredNoiseHead, + ) + from quantammsim.calibration.joint_fit import prepare_token_factored_data + from quantammsim.calibration.loss import CHAIN_GAS_USD + from quantammsim.calibration.pool_data import K_OBS_REDUCED + + jdata, enc = prepare_token_factored_data(matched_data) + gas_values = [] + for pid in jdata.pool_ids: + chain = matched_data[pid]["chain"] + gas_values.append(np.log(max(CHAIN_GAS_USD.get(chain, 1.0), 1e-6))) + + noise_head = TokenFactoredNoiseHead(k_obs=K_OBS_REDUCED, **enc) + model = CalibrationModel( + PerPoolHead("log_cadence", default=np.log(12.0)), + FixedHead("log_gas", np.array(gas_values)), + noise_head, + ) + result = model.fit(jdata, maxiter=50) + + np.testing.assert_allclose( + result["data_loss"] + result["reg_loss"], + result["loss"], + rtol=1e-6, + ) + + def test_data_loss_leq_total(self, matched_data): + from quantammsim.calibration.calibration_model import CalibrationModel + from quantammsim.calibration.heads import ( + FixedHead, PerPoolHead, TokenFactoredNoiseHead, + ) + from quantammsim.calibration.joint_fit import prepare_token_factored_data + from quantammsim.calibration.loss import CHAIN_GAS_USD + from quantammsim.calibration.pool_data import K_OBS_REDUCED + + jdata, enc = prepare_token_factored_data(matched_data) + gas_values = [] + for pid in jdata.pool_ids: + chain = matched_data[pid]["chain"] + gas_values.append(np.log(max(CHAIN_GAS_USD.get(chain, 1.0), 1e-6))) + + noise_head = TokenFactoredNoiseHead(k_obs=K_OBS_REDUCED, **enc) + model = CalibrationModel( + PerPoolHead("log_cadence", default=np.log(12.0)), + FixedHead("log_gas", np.array(gas_values)), + noise_head, + ) + result = model.fit(jdata, maxiter=50) + + assert result["data_loss"] <= result["loss"] + 1e-10 + assert result["reg_loss"] >= -1e-10 + + +class TestPrepareTokenFactoredCrossPool: + """Test prepare_token_factored_data with cross_pool=True.""" + + def test_cross_pool_jdata_shapes_consistent(self, matched_data): + from quantammsim.calibration.joint_fit import prepare_token_factored_data + + jdata, enc = prepare_token_factored_data(matched_data, cross_pool=True) + for pd in jdata.pool_data: + assert pd["x_obs"].shape[0] == pd["y_obs"].shape[0] + assert pd["x_obs"].shape[0] == pd["day_indices"].shape[0] + + def test_cross_pool_x_obs_has_7_cols(self, matched_data): + from quantammsim.calibration.joint_fit import prepare_token_factored_data + from quantammsim.calibration.pool_data import K_OBS_CROSS + + jdata, _ = prepare_token_factored_data(matched_data, cross_pool=True) + for pd in jdata.pool_data: + assert pd["x_obs"].shape[1] == K_OBS_CROSS + + def test_cross_pool_drops_first_obs(self, matched_data): + from quantammsim.calibration.joint_fit import prepare_token_factored_data + + jdata_base, _ = prepare_token_factored_data(matched_data, cross_pool=False) + jdata_cross, _ = prepare_token_factored_data(matched_data, cross_pool=True) + for pd_base, pd_cross in zip(jdata_base.pool_data, jdata_cross.pool_data): + assert pd_cross["y_obs"].shape[0] == pd_base["y_obs"].shape[0] - 1 + + +class TestPrepareJointDataReduced: + """Test prepare_joint_data with reduced_x_obs=True.""" + + def test_reduced_x_obs_shape(self, matched_data): + from quantammsim.calibration.joint_fit import prepare_joint_data + from quantammsim.calibration.pool_data import K_OBS_REDUCED + + jdata = prepare_joint_data(matched_data, reduced_x_obs=True) + for pd in jdata.pool_data: + assert pd["x_obs"].shape[1] == K_OBS_REDUCED + + def test_default_unchanged(self, matched_data): + from quantammsim.calibration.joint_fit import prepare_joint_data + + jdata = prepare_joint_data(matched_data) + for pd in jdata.pool_data: + assert pd["x_obs"].shape[1] == K_OBS diff --git a/tests/calibration/test_joint_fit_fixed_gas.py b/tests/calibration/test_joint_fit_fixed_gas.py new file mode 100644 index 0000000..7981ff4 --- /dev/null +++ b/tests/calibration/test_joint_fit_fixed_gas.py @@ -0,0 +1,467 @@ +"""Tests for fixed-gas mode in quantammsim.calibration.joint_fit.""" + +import jax +import jax.numpy as jnp +import numpy as np +import pytest + +from tests.calibration.conftest import K_OBS, POOL_PREFIXES + + +@pytest.fixture +def matched_data(synthetic_daily_grid, synthetic_panel, tmp_path): + """Build matched data dict from synthetic fixtures.""" + from quantammsim.calibration.pool_data import match_grids_to_panel + + grid_dir = tmp_path / "grids" + grid_dir.mkdir() + for prefix in POOL_PREFIXES: + synthetic_daily_grid.to_parquet( + grid_dir / f"{prefix}_daily.parquet", index=False + ) + return match_grids_to_panel(str(grid_dir), synthetic_panel) + + +class TestPrepareJointDataFixedGas: + """Test prepare_joint_data with fix_gas_to_chain=True.""" + + def test_pool_data_has_fixed_log_gas(self, matched_data): + from quantammsim.calibration.joint_fit import prepare_joint_data + + jdata = prepare_joint_data(matched_data, fix_gas_to_chain=True) + for pd_i in jdata.pool_data: + assert "fixed_log_gas" in pd_i + + def test_pool_data_no_fixed_log_gas_by_default(self, matched_data): + from quantammsim.calibration.joint_fit import prepare_joint_data + + jdata = prepare_joint_data(matched_data, fix_gas_to_chain=False) + for pd_i in jdata.pool_data: + assert "fixed_log_gas" not in pd_i + + def test_fixed_log_gas_values_match_chain(self, matched_data): + """fixed_log_gas should be log(CHAIN_GAS_USD[chain]).""" + from quantammsim.calibration.joint_fit import prepare_joint_data + from quantammsim.calibration.loss import CHAIN_GAS_USD + + jdata = prepare_joint_data(matched_data, fix_gas_to_chain=True) + + for i, pid in enumerate(jdata.pool_ids): + chain = matched_data[pid]["chain"] + expected_gas = CHAIN_GAS_USD.get(chain, 1.0) + expected_log_gas = np.log(max(expected_gas, 1e-6)) + np.testing.assert_allclose( + float(jdata.pool_data[i]["fixed_log_gas"]), + expected_log_gas, + rtol=1e-6, + ) + + def test_mainnet_gas_is_log_1(self, matched_data): + """MAINNET pools should have fixed_log_gas = log(1.0) = 0.0.""" + from quantammsim.calibration.joint_fit import prepare_joint_data + + jdata = prepare_joint_data(matched_data, fix_gas_to_chain=True) + found = False + for i, pid in enumerate(jdata.pool_ids): + if matched_data[pid]["chain"] == "MAINNET": + np.testing.assert_allclose( + float(jdata.pool_data[i]["fixed_log_gas"]), + 0.0, + atol=1e-6, + ) + found = True + assert found, "No MAINNET pool found in test data" + + def test_arbitrum_gas_is_log_001(self, matched_data): + """ARBITRUM pools should have fixed_log_gas = log(0.01).""" + from quantammsim.calibration.joint_fit import prepare_joint_data + + jdata = prepare_joint_data(matched_data, fix_gas_to_chain=True) + found = False + for i, pid in enumerate(jdata.pool_ids): + if matched_data[pid]["chain"] == "ARBITRUM": + np.testing.assert_allclose( + float(jdata.pool_data[i]["fixed_log_gas"]), + np.log(0.01), + rtol=1e-6, + ) + found = True + assert found, "No ARBITRUM pool found in test data" + + def test_x_attr_has_correct_shape(self, matched_data): + from quantammsim.calibration.joint_fit import prepare_joint_data + + jdata = prepare_joint_data(matched_data, fix_gas_to_chain=True) + assert jdata.x_attr.shape[0] == len(jdata.pool_ids) + assert jdata.x_attr.shape[1] == len(jdata.attr_names) + + def test_pool_data_has_obs_arrays(self, matched_data): + from quantammsim.calibration.joint_fit import prepare_joint_data + + jdata = prepare_joint_data(matched_data, fix_gas_to_chain=True) + for pd_i in jdata.pool_data: + assert pd_i["x_obs"].ndim == 2 + assert pd_i["y_obs"].ndim == 1 + assert pd_i["day_indices"].ndim == 1 + assert pd_i["x_obs"].shape[0] == pd_i["y_obs"].shape[0] + + +class TestPackUnpackJointFixedGas: + """Test packing/unpacking joint params with fix_gas=True.""" + + def test_pack_fixed_gas_shape(self): + from quantammsim.calibration.joint_fit import pack_joint_params_fixed_gas + + k_attr = 6 + n_pools = 2 + noise_params = jnp.zeros((n_pools, K_OBS)) + flat = pack_joint_params_fixed_gas( + 1.0, jnp.zeros(k_attr), noise_params + ) + # Layout: [bias_cad, W_cad(6), noise(2*8)] = 1+6+16 = 23 + assert flat.shape == (1 + k_attr + n_pools * K_OBS,) + + def test_pack_fixed_gas_shorter_than_free(self): + from quantammsim.calibration.joint_fit import ( + pack_joint_params, + pack_joint_params_fixed_gas, + ) + + k_attr = 6 + n_pools = 2 + noise = jnp.zeros((n_pools, K_OBS)) + + free = pack_joint_params(1.0, 2.0, jnp.zeros(k_attr), + jnp.zeros(k_attr), noise) + fixed = pack_joint_params_fixed_gas(1.0, jnp.zeros(k_attr), noise) + # Fixed is shorter by: 1 (bias_gas) + k_attr (W_gas) + assert free.shape[0] - fixed.shape[0] == 1 + k_attr + + def test_unpack_fixed_gas_no_gas_keys(self): + from quantammsim.calibration.joint_fit import ( + pack_joint_params_fixed_gas, + unpack_joint_params, + ) + + k_attr = 6 + n_pools = 2 + noise = jnp.zeros((n_pools, K_OBS)) + flat = pack_joint_params_fixed_gas( + 1.0, jnp.ones(k_attr) * 0.5, noise + ) + + config = {"k_attr": k_attr, "n_pools": n_pools, + "mode": "per_pool_noise", "fix_gas": True} + params = unpack_joint_params(flat, config) + + assert "bias_cad" in params + assert "W_cad" in params + assert "noise_coeffs" in params + assert "bias_gas" not in params + assert "W_gas" not in params + + def test_unpack_roundtrip_per_pool_noise(self): + from quantammsim.calibration.joint_fit import ( + pack_joint_params_fixed_gas, + unpack_joint_params, + ) + + k_attr = 4 + n_pools = 3 + bias_cad = 2.5 + W_cad = jnp.array([0.1, -0.2, 0.3, -0.4]) + noise = jnp.arange(n_pools * K_OBS, dtype=float).reshape(n_pools, K_OBS) + + flat = pack_joint_params_fixed_gas(bias_cad, W_cad, noise) + config = {"k_attr": k_attr, "n_pools": n_pools, + "mode": "per_pool_noise", "fix_gas": True} + params = unpack_joint_params(flat, config) + + np.testing.assert_allclose(params["bias_cad"], bias_cad) + np.testing.assert_allclose(params["W_cad"], W_cad) + np.testing.assert_allclose(params["noise_coeffs"], noise) + + def test_unpack_roundtrip_shared_noise(self): + from quantammsim.calibration.joint_fit import ( + pack_joint_params_fixed_gas, + unpack_joint_params, + ) + + k_attr = 4 + bias_cad = 1.5 + W_cad = jnp.array([0.5, -0.5, 0.1, -0.1]) + # shared_noise: (1+k_attr, K_OBS) = (5, 8) + noise = jnp.arange((1 + k_attr) * K_OBS, dtype=float).reshape( + 1 + k_attr, K_OBS + ) + + flat = pack_joint_params_fixed_gas(bias_cad, W_cad, noise) + config = {"k_attr": k_attr, "n_pools": 99, + "mode": "shared_noise", "fix_gas": True} + params = unpack_joint_params(flat, config) + + np.testing.assert_allclose(params["bias_cad"], bias_cad) + np.testing.assert_allclose(params["W_cad"], W_cad) + np.testing.assert_allclose(params["bias_noise"], noise[0]) + np.testing.assert_allclose(params["W_noise"], noise[1:]) + + +class TestJointLossFixedGas: + """Test joint loss function with fix_gas=True.""" + + def _make_loss_fn(self, matched_data, mode="per_pool_noise"): + from quantammsim.calibration.joint_fit import ( + make_initial_joint_params, + make_joint_loss_fn, + prepare_joint_data, + ) + + jdata = prepare_joint_data( + matched_data, fix_gas_to_chain=True + ) + init = make_initial_joint_params( + jdata, mode=mode, fix_gas=True + ) + loss_fn = make_joint_loss_fn( + jdata, mode=mode, fix_gas=True + ) + return loss_fn, init, jdata + + def test_loss_differentiable_and_nonzero_grad(self, matched_data): + loss_fn, init, _ = self._make_loss_fn(matched_data) + grad = jax.grad(loss_fn)(init) + assert grad.shape == init.shape + assert jnp.all(jnp.isfinite(grad)) + assert float(jnp.sum(jnp.abs(grad))) > 1e-10 + + def test_no_gas_regularization_but_cad_regularization_works(self, matched_data): + """alpha_gas has no effect, but alpha_cad DOES.""" + from quantammsim.calibration.joint_fit import ( + make_initial_joint_params, + make_joint_loss_fn, + prepare_joint_data, + ) + + jdata = prepare_joint_data(matched_data, fix_gas_to_chain=True) + init = make_initial_joint_params(jdata, mode="per_pool_noise", fix_gas=True) + + # alpha_gas shouldn't matter + loss_fn_a = make_joint_loss_fn( + jdata, mode="per_pool_noise", fix_gas=True, alpha_gas=0.0 + ) + loss_fn_b = make_joint_loss_fn( + jdata, mode="per_pool_noise", fix_gas=True, alpha_gas=100.0 + ) + np.testing.assert_allclose( + float(loss_fn_a(init)), float(loss_fn_b(init)), rtol=1e-6 + ) + + # alpha_cad SHOULD matter (positive control) + loss_fn_no_reg = make_joint_loss_fn( + jdata, mode="per_pool_noise", fix_gas=True, alpha_cad=0.0 + ) + loss_fn_big_reg = make_joint_loss_fn( + jdata, mode="per_pool_noise", fix_gas=True, alpha_cad=100.0 + ) + # With W_cad initialized to non-zero by warm start, these should differ. + # Even with default init (W_cad=0), perturbation test: + init_perturbed = init.at[1].set(1.0) # perturb first W_cad element + loss_no = float(loss_fn_no_reg(init_perturbed)) + loss_big = float(loss_fn_big_reg(init_perturbed)) + assert loss_big > loss_no, "alpha_cad regularization has no effect" + + def test_shared_noise_mode(self, matched_data): + loss_fn, init, _ = self._make_loss_fn( + matched_data, mode="shared_noise" + ) + loss = loss_fn(init) + assert loss.shape == () + assert float(loss) >= 0 + # Verify gradient works for shared_noise too + grad = jax.grad(loss_fn)(init) + assert jnp.all(jnp.isfinite(grad)) + + def test_init_param_count_per_pool_noise(self, matched_data): + """Verify parameter count: 1(bias_cad) + k_attr(W_cad) + n_pools*K_OBS.""" + _, init, jdata = self._make_loss_fn(matched_data) + k_attr = jdata.x_attr.shape[1] + n_pools = len(jdata.pool_data) + expected = 1 + k_attr + n_pools * K_OBS + assert init.shape[0] == expected + + def test_init_param_count_shared_noise(self, matched_data): + """Verify: 1(bias_cad) + k_attr(W_cad) + (1+k_attr)*K_OBS.""" + _, init, jdata = self._make_loss_fn( + matched_data, mode="shared_noise" + ) + k_attr = jdata.x_attr.shape[1] + expected = 1 + k_attr + (1 + k_attr) * K_OBS + assert init.shape[0] == expected + + +class TestFitJointFixedGas: + """Test fit_joint with fix_gas_to_chain=True.""" + + def test_returns_result(self, matched_data): + from quantammsim.calibration.joint_fit import fit_joint + + result = fit_joint( + matched_data, mode="per_pool_noise", + fix_gas_to_chain=True, maxiter=20, + ) + assert isinstance(result, dict) + for key in ["bias_cad", "W_cad", "loss", "converged", "fix_gas"]: + assert key in result, f"Missing key: {key}" + + def test_fix_gas_flag_stored(self, matched_data): + from quantammsim.calibration.joint_fit import fit_joint + + result = fit_joint( + matched_data, mode="per_pool_noise", + fix_gas_to_chain=True, maxiter=10, + ) + assert result["fix_gas"] is True + + def test_gas_per_pool_stored_with_correct_values(self, matched_data): + """gas_per_pool has right length and chain-level values.""" + from quantammsim.calibration.joint_fit import fit_joint + from quantammsim.calibration.loss import CHAIN_GAS_USD + + result = fit_joint( + matched_data, mode="per_pool_noise", + fix_gas_to_chain=True, maxiter=10, + ) + assert "gas_per_pool" in result + assert len(result["gas_per_pool"]) == len(result["pool_ids"]) + for i, pid in enumerate(result["pool_ids"]): + chain = matched_data[pid]["chain"] + expected = CHAIN_GAS_USD.get(chain, 1.0) + assert result["gas_per_pool"][i] == expected + + def test_loss_decreases_substantially(self, matched_data): + from quantammsim.calibration.joint_fit import fit_joint + + result = fit_joint( + matched_data, mode="per_pool_noise", + fix_gas_to_chain=True, maxiter=50, + ) + # Must decrease, not just by epsilon + assert result["loss"] < result["init_loss"] * 0.999 + + def test_w_gas_and_bias_gas_are_zeros(self, matched_data): + """With fixed gas, W_gas and bias_gas should be zero placeholders.""" + from quantammsim.calibration.joint_fit import fit_joint + + result = fit_joint( + matched_data, mode="per_pool_noise", + fix_gas_to_chain=True, maxiter=10, + ) + np.testing.assert_allclose(result["W_gas"], 0.0) + assert result["bias_gas"] == 0.0 + + def test_warm_start_from_option_c_runs(self, matched_data): + """Warm start from Option C should run without error and reduce loss.""" + from quantammsim.calibration.joint_fit import fit_joint + from quantammsim.calibration.per_pool_fit import fit_all_pools + + option_c = fit_all_pools(matched_data, fix_gas_to_chain=True) + result_warm = fit_joint( + matched_data, mode="per_pool_noise", + fix_gas_to_chain=True, + init_from_option_c=option_c, + maxiter=50, + ) + # Should at least decrease from its own init + assert result_warm["loss"] <= result_warm["init_loss"] + assert result_warm["loss"] >= 0 + + def test_shared_noise_fixed_gas(self, matched_data): + from quantammsim.calibration.joint_fit import fit_joint + + result = fit_joint( + matched_data, mode="shared_noise", + fix_gas_to_chain=True, maxiter=20, + ) + assert "W_noise" in result + assert "bias_noise" in result + assert result["fix_gas"] is True + assert result["loss"] >= 0 + + def test_predict_new_pool_fixed_gas_pinned(self, matched_data): + """predict_new_pool_joint with zero attrs → gas_usd=1.0 exactly.""" + from quantammsim.calibration.joint_fit import fit_joint, predict_new_pool_joint + + result = fit_joint( + matched_data, mode="shared_noise", + fix_gas_to_chain=True, maxiter=20, + ) + k_attr = result["W_cad"].shape[0] + x_attr_new = np.zeros(k_attr) + pred = predict_new_pool_joint(result, x_attr_new) + + assert "cadence_minutes" in pred + assert pred["cadence_minutes"] > 0 + # bias_gas=0, W_gas=zeros → log_gas=0 → gas_usd=exp(0)=1.0 + np.testing.assert_allclose(pred["gas_usd"], 1.0, rtol=1e-6) + # cadence = exp(bias_cad + 0) = exp(bias_cad) + np.testing.assert_allclose( + pred["cadence_minutes"], np.exp(result["bias_cad"]), rtol=1e-6 + ) + # shared_noise mode should include noise_coeffs + assert "noise_coeffs" in pred + assert len(pred["noise_coeffs"]) == K_OBS + + def test_predict_matches_linear_model(self, matched_data): + """predict_new_pool_joint computes cadence = exp(bias_cad + W_cad @ x).""" + from quantammsim.calibration.joint_fit import fit_joint, predict_new_pool_joint + + result = fit_joint( + matched_data, mode="shared_noise", + fix_gas_to_chain=True, maxiter=20, + ) + k_attr = result["W_cad"].shape[0] + x_test = np.random.RandomState(42).randn(k_attr) + + pred = predict_new_pool_joint(result, x_test) + + # Verify cadence prediction directly against the linear model + expected_cadence = float(np.exp( + result["bias_cad"] + result["W_cad"] @ x_test + )) + np.testing.assert_allclose( + pred["cadence_minutes"], expected_cadence, rtol=1e-6, + ) + # Gas is fixed → always exp(0) = 1.0 + np.testing.assert_allclose(pred["gas_usd"], 1.0, rtol=1e-6) + + +class TestMakeBoundsFixedGas: + """Test _make_bounds with fix_gas=True.""" + + def test_bounds_count_per_pool_noise(self): + from quantammsim.calibration.joint_fit import _make_bounds + + k_attr = 6 + n_pools = 3 + bounds = _make_bounds(k_attr, n_pools, "per_pool_noise", fix_gas=True) + # 1(bias_cad) + 6(W_cad) + 3*8(noise) = 31 + assert len(bounds) == 1 + k_attr + n_pools * K_OBS + + def test_bounds_count_shared_noise(self): + from quantammsim.calibration.joint_fit import _make_bounds + + k_attr = 6 + n_pools = 3 + bounds = _make_bounds(k_attr, n_pools, "shared_noise", fix_gas=True) + # 1(bias_cad) + 6(W_cad) + (1+6)*8(noise) = 63 + assert len(bounds) == 1 + k_attr + (1 + k_attr) * K_OBS + + def test_bounds_fewer_with_fixed_gas(self): + from quantammsim.calibration.joint_fit import _make_bounds + + k_attr = 6 + n_pools = 3 + free = _make_bounds(k_attr, n_pools, "per_pool_noise", fix_gas=False) + fixed = _make_bounds(k_attr, n_pools, "per_pool_noise", fix_gas=True) + # Difference: 1(bias_gas) + k_attr(W_gas) + assert len(free) - len(fixed) == 1 + k_attr diff --git a/tests/calibration/test_learned_mapping.py b/tests/calibration/test_learned_mapping.py new file mode 100644 index 0000000..8fbf16e --- /dev/null +++ b/tests/calibration/test_learned_mapping.py @@ -0,0 +1,152 @@ +"""Tests for quantammsim.calibration.learned_mapping — attribute -> params mapping.""" + +import numpy as np +import pytest + +from tests.calibration.conftest import K_OBS, POOL_PREFIXES + + +class TestBuildTargets: + """Test build_targets: stack per-pool fitted params into target matrix.""" + + def test_target_matrix_shape(self, synthetic_pool_fit_result): + from quantammsim.calibration.learned_mapping import build_targets + + pool_order = sorted(synthetic_pool_fit_result.keys()) + Y = build_targets(synthetic_pool_fit_result, pool_order) + assert Y.shape == (len(pool_order), 2 + K_OBS) + + def test_target_ordering_matches_attributes(self, synthetic_pool_fit_result): + from quantammsim.calibration.learned_mapping import build_targets + + pool_order = sorted(synthetic_pool_fit_result.keys()) + Y = build_targets(synthetic_pool_fit_result, pool_order) + # First pool's log_cadence should be in first row + expected_lc = synthetic_pool_fit_result[pool_order[0]]["log_cadence"] + np.testing.assert_allclose(Y[0, 0], expected_lc) + + +class TestFitMapping: + """Test fit_mapping: Ridge regression from attributes to params.""" + + def _make_data(self, n_pools=10): + np.random.seed(42) + k_attr = 4 + X = np.random.randn(n_pools, k_attr) + X[:, 0] = 1.0 # intercept + W_true = np.random.randn(k_attr, 2 + K_OBS) * 0.5 + Y = X @ W_true + np.random.randn(n_pools, 2 + K_OBS) * 0.01 + return X, Y + + def test_returns_model(self): + from quantammsim.calibration.learned_mapping import fit_mapping + + X, Y = self._make_data() + model = fit_mapping(X, Y) + assert isinstance(model, dict) + assert "weights" in model + assert "intercept" in model + + def test_predict_shape(self): + from quantammsim.calibration.learned_mapping import fit_mapping + + X, Y = self._make_data() + model = fit_mapping(X, Y) + Y_pred = X @ model["weights"] + model["intercept"] + assert Y_pred.shape == Y.shape + + def test_predict_reasonable_range(self): + from quantammsim.calibration.learned_mapping import fit_mapping, predict_pool + + X, Y = self._make_data() + # Constrain Y targets to reasonable range + Y[:, 0] = np.log(np.random.uniform(1, 60, len(Y))) # log_cadence + Y[:, 1] = np.log(np.random.uniform(0.01, 10, len(Y))) # log_gas + model = fit_mapping(X, Y) + + result = predict_pool(model, X[0]) + assert 0.5 <= result["cadence_minutes"] <= 120.0 + assert result["gas_usd"] > 0 + + def test_overfit_on_training_data(self): + from quantammsim.calibration.learned_mapping import fit_mapping + + X, Y = self._make_data(n_pools=10) + model = fit_mapping(X, Y, alpha=0.001) + Y_pred = X @ model["weights"] + model["intercept"] + ss_res = np.sum((Y - Y_pred) ** 2) + ss_tot = np.sum((Y - Y.mean(axis=0)) ** 2) + r2 = 1 - ss_res / ss_tot + assert r2 > 0.8 + + def test_leave_one_out_runs(self): + from quantammsim.calibration.learned_mapping import cross_validate_loo + + X, Y = self._make_data(n_pools=10) + cv_result = cross_validate_loo(X, Y) + assert "per_pool_errors" in cv_result + assert len(cv_result["per_pool_errors"]) == 10 + + +class TestPredictNewPool: + """Test predict_pool: predict params for a single pool.""" + + def test_predict_single_pool(self): + from quantammsim.calibration.learned_mapping import fit_mapping, predict_pool + + np.random.seed(42) + X = np.random.randn(5, 3) + X[:, 0] = 1.0 + Y = np.random.randn(5, 2 + K_OBS) + model = fit_mapping(X, Y) + result = predict_pool(model, X[0]) + assert isinstance(result, dict) + + def test_predict_cadence_and_gas(self): + from quantammsim.calibration.learned_mapping import fit_mapping, predict_pool + + np.random.seed(42) + X = np.random.randn(5, 3) + X[:, 0] = 1.0 + Y = np.random.randn(5, 2 + K_OBS) + model = fit_mapping(X, Y) + result = predict_pool(model, X[0]) + assert "cadence_minutes" in result + assert "gas_usd" in result + assert "log_cadence" in result + assert "log_gas" in result + + def test_predict_noise_coeffs(self): + from quantammsim.calibration.learned_mapping import fit_mapping, predict_pool + + np.random.seed(42) + X = np.random.randn(5, 3) + X[:, 0] = 1.0 + Y = np.random.randn(5, 2 + K_OBS) + model = fit_mapping(X, Y) + result = predict_pool(model, X[0]) + assert "noise_coeffs" in result + assert len(result["noise_coeffs"]) == K_OBS + + def test_different_chains_different_predictions(self): + from quantammsim.calibration.learned_mapping import fit_mapping, predict_pool + + np.random.seed(42) + # X with chain dummy in col 1 + X = np.random.randn(10, 4) + X[:, 0] = 1.0 + X[:5, 1] = 1.0 # chain A + X[5:, 1] = 0.0 # chain B + Y = np.random.randn(10, 2 + K_OBS) + Y[:5, :] += 1.0 # chain A has different targets + + model = fit_mapping(X, Y, alpha=0.01) + + x_a = X[0].copy() + x_b = X[0].copy() + x_b[1] = 0.0 # flip chain + + pred_a = predict_pool(model, x_a) + pred_b = predict_pool(model, x_b) + # Predictions should differ + assert pred_a["log_cadence"] != pred_b["log_cadence"] diff --git a/tests/calibration/test_loss.py b/tests/calibration/test_loss.py new file mode 100644 index 0000000..8fd2958 --- /dev/null +++ b/tests/calibration/test_loss.py @@ -0,0 +1,239 @@ +"""Tests for quantammsim.calibration.loss — per-pool loss function.""" + +import jax +import jax.numpy as jnp +import numpy as np +import pytest + +from tests.calibration.conftest import K_OBS, N_DAYS + + +class TestNoiseVolume: + """Test noise_volume: V_noise = exp(x_obs @ noise_coeffs).""" + + def test_noise_volume_shape(self, synthetic_x_obs): + from quantammsim.calibration.loss import noise_volume + + coeffs = jnp.zeros(K_OBS) + v = noise_volume(coeffs, jnp.array(synthetic_x_obs)) + assert v.shape == (synthetic_x_obs.shape[0],) + + def test_noise_volume_positive(self, synthetic_x_obs): + from quantammsim.calibration.loss import noise_volume + + coeffs = jnp.ones(K_OBS) * 0.1 + v = noise_volume(coeffs, jnp.array(synthetic_x_obs)) + assert jnp.all(v > 0) + + def test_noise_volume_intercept_only(self, synthetic_x_obs): + from quantammsim.calibration.loss import noise_volume + + c = 5.0 + coeffs = jnp.zeros(K_OBS).at[0].set(c) + v = noise_volume(coeffs, jnp.array(synthetic_x_obs)) + # x_obs[:, 0] is all 1s, so V_noise should be close to exp(c) + # (not exactly, because other columns are nonzero and contribute 0*x) + np.testing.assert_allclose(v, jnp.exp(c), rtol=1e-5) + + def test_noise_volume_tvl_effect(self, synthetic_x_obs): + from quantammsim.calibration.loss import noise_volume + + # Positive TVL coefficient: higher TVL → higher noise + coeffs = jnp.zeros(K_OBS).at[1].set(1.0) + v = noise_volume(coeffs, jnp.array(synthetic_x_obs)) + tvl_col = synthetic_x_obs[:, 1] + # Sort by TVL, check volume is monotone + order = np.argsort(tvl_col) + assert np.all(np.diff(np.array(v[order])) >= -1e-6) + + +class TestPoolLoss: + """Test pool_loss: per-pool log-space L2 loss with per-day V_arb.""" + + def _make_params(self, log_cad=None, log_gas=None, noise_coeffs=None): + from quantammsim.calibration.loss import pack_params + + if log_cad is None: + log_cad = float(jnp.log(jnp.array(12.0))) + if log_gas is None: + log_gas = float(jnp.log(jnp.array(1.0))) + if noise_coeffs is None: + noise_coeffs = jnp.zeros(K_OBS).at[0].set(8.0) + return pack_params(log_cad, log_gas, noise_coeffs) + + def _make_inputs(self, synthetic_pool_coeffs, synthetic_x_obs): + n_obs = synthetic_x_obs.shape[0] + n_days = int(synthetic_pool_coeffs.values.shape[2]) + day_indices = jnp.array(np.arange(n_obs) % n_days) + y_obs = jnp.ones(n_obs) * 9.0 + return jnp.array(synthetic_x_obs), y_obs, day_indices + + def test_loss_scalar_output(self, synthetic_pool_coeffs, synthetic_x_obs): + from quantammsim.calibration.loss import pool_loss + + params = self._make_params() + x_obs, y_obs, day_indices = self._make_inputs( + synthetic_pool_coeffs, synthetic_x_obs + ) + loss = pool_loss(params, synthetic_pool_coeffs, x_obs, y_obs, day_indices) + assert loss.shape == () + + def test_loss_zero_when_perfect(self, synthetic_pool_coeffs, synthetic_x_obs): + from quantammsim.calibration.grid_interpolation import interpolate_pool_daily + from quantammsim.calibration.loss import noise_volume, pack_params, pool_loss + + log_cad = jnp.log(jnp.array(12.0)) + log_gas = jnp.log(jnp.array(1.0)) + noise_coeffs = jnp.zeros(K_OBS).at[0].set(8.0) + + # Compute what V_pred would be + v_arb_all = interpolate_pool_daily(synthetic_pool_coeffs, log_cad, jnp.exp(log_gas)) + n_obs = synthetic_x_obs.shape[0] + n_days = int(synthetic_pool_coeffs.values.shape[2]) + day_indices = jnp.array(np.arange(n_obs) % n_days) + v_arb = v_arb_all[day_indices] + v_noise = noise_volume(noise_coeffs, jnp.array(synthetic_x_obs)) + y_obs = jnp.log(jnp.maximum(v_arb + v_noise, 1e-6)) + + params = pack_params(float(log_cad), float(log_gas), noise_coeffs) + loss = pool_loss(params, synthetic_pool_coeffs, jnp.array(synthetic_x_obs), + y_obs, day_indices) + assert float(loss) < 1e-6 + + def test_loss_positive(self, synthetic_pool_coeffs, synthetic_x_obs): + from quantammsim.calibration.loss import pool_loss + + params = self._make_params() + x_obs, y_obs, day_indices = self._make_inputs( + synthetic_pool_coeffs, synthetic_x_obs + ) + loss = pool_loss(params, synthetic_pool_coeffs, x_obs, y_obs, day_indices) + assert float(loss) >= 0 + + def test_loss_increases_with_error(self, synthetic_pool_coeffs, synthetic_x_obs): + from quantammsim.calibration.loss import pool_loss + + x_obs, y_obs, day_indices = self._make_inputs( + synthetic_pool_coeffs, synthetic_x_obs + ) + + params_ok = self._make_params(noise_coeffs=jnp.zeros(K_OBS).at[0].set(8.0)) + params_bad = self._make_params(noise_coeffs=jnp.zeros(K_OBS).at[0].set(20.0)) + + loss_ok = pool_loss(params_ok, synthetic_pool_coeffs, x_obs, y_obs, day_indices) + loss_bad = pool_loss(params_bad, synthetic_pool_coeffs, x_obs, y_obs, day_indices) + assert float(loss_bad) > float(loss_ok) + + def test_loss_differentiable(self, synthetic_pool_coeffs, synthetic_x_obs): + from quantammsim.calibration.loss import pool_loss + + params = self._make_params() + x_obs, y_obs, day_indices = self._make_inputs( + synthetic_pool_coeffs, synthetic_x_obs + ) + + grad_fn = jax.grad(pool_loss, argnums=0) + g = grad_fn(params, synthetic_pool_coeffs, x_obs, y_obs, day_indices) + assert g.shape == params.shape + + def test_loss_grad_nonzero(self, synthetic_pool_coeffs, synthetic_x_obs): + from quantammsim.calibration.loss import pool_loss + + params = self._make_params() + x_obs, y_obs, day_indices = self._make_inputs( + synthetic_pool_coeffs, synthetic_x_obs + ) + + grad_fn = jax.grad(pool_loss, argnums=0) + g = grad_fn(params, synthetic_pool_coeffs, x_obs, y_obs, day_indices) + assert float(jnp.sum(jnp.abs(g))) > 0 + + def test_loss_grad_wrt_log_cadence(self, synthetic_pool_coeffs, synthetic_x_obs): + from quantammsim.calibration.loss import pool_loss + + params = self._make_params() + x_obs, y_obs, day_indices = self._make_inputs( + synthetic_pool_coeffs, synthetic_x_obs + ) + + grad_fn = jax.grad(pool_loss, argnums=0) + g = grad_fn(params, synthetic_pool_coeffs, x_obs, y_obs, day_indices) + assert jnp.isfinite(g[0]) # log_cadence gradient + + def test_loss_grad_wrt_log_gas(self, synthetic_pool_coeffs, synthetic_x_obs): + from quantammsim.calibration.loss import pool_loss + + params = self._make_params() + x_obs, y_obs, day_indices = self._make_inputs( + synthetic_pool_coeffs, synthetic_x_obs + ) + + grad_fn = jax.grad(pool_loss, argnums=0) + g = grad_fn(params, synthetic_pool_coeffs, x_obs, y_obs, day_indices) + assert jnp.isfinite(g[1]) # log_gas gradient + + def test_loss_grad_wrt_noise_coeffs(self, synthetic_pool_coeffs, synthetic_x_obs): + from quantammsim.calibration.loss import pool_loss + + params = self._make_params() + x_obs, y_obs, day_indices = self._make_inputs( + synthetic_pool_coeffs, synthetic_x_obs + ) + + grad_fn = jax.grad(pool_loss, argnums=0) + g = grad_fn(params, synthetic_pool_coeffs, x_obs, y_obs, day_indices) + assert jnp.all(jnp.isfinite(g[2:])) # noise_coeffs gradients + + def test_loss_uses_per_day_varb(self, synthetic_pool_coeffs, synthetic_x_obs): + from quantammsim.calibration.grid_interpolation import interpolate_pool_daily + from quantammsim.calibration.loss import pool_loss, unpack_params + + params = self._make_params() + log_cad, log_gas, _ = unpack_params(params) + v_arb = interpolate_pool_daily( + synthetic_pool_coeffs, jnp.array(log_cad), jnp.exp(jnp.array(log_gas)) + ) + # Per-day V_arb should vary across days + assert float(jnp.std(v_arb)) > 0 + + def test_day_indices_alignment(self, synthetic_pool_coeffs, synthetic_x_obs): + from quantammsim.calibration.loss import pool_loss + + n_obs = synthetic_x_obs.shape[0] + n_days = int(synthetic_pool_coeffs.values.shape[2]) + params = self._make_params() + x_obs = jnp.array(synthetic_x_obs) + y_obs = jnp.ones(n_obs) * 9.0 + + # Different day_indices should give different loss + day_idx_a = jnp.zeros(n_obs, dtype=jnp.int32) # all same day + day_idx_b = jnp.array(np.arange(n_obs) % n_days) # varying days + + loss_a = pool_loss(params, synthetic_pool_coeffs, x_obs, y_obs, day_idx_a) + loss_b = pool_loss(params, synthetic_pool_coeffs, x_obs, y_obs, day_idx_b) + # Different day mappings → different losses + assert float(loss_a) != float(loss_b) + + +class TestPackUnpack: + """Test pack/unpack parameter roundtrip.""" + + def test_roundtrip(self): + from quantammsim.calibration.loss import pack_params, unpack_params + + log_cad = 2.5 + log_gas = -0.3 + noise_coeffs = jnp.array([1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0]) + + flat = pack_params(log_cad, log_gas, noise_coeffs) + lc, lg, nc = unpack_params(flat) + + np.testing.assert_allclose(lc, log_cad) + np.testing.assert_allclose(lg, log_gas) + np.testing.assert_allclose(nc, noise_coeffs) + + def test_pack_shape(self): + from quantammsim.calibration.loss import pack_params + + flat = pack_params(1.0, 2.0, jnp.zeros(K_OBS)) + assert flat.shape == (2 + K_OBS,) diff --git a/tests/calibration/test_loss_fixed_gas.py b/tests/calibration/test_loss_fixed_gas.py new file mode 100644 index 0000000..50a5122 --- /dev/null +++ b/tests/calibration/test_loss_fixed_gas.py @@ -0,0 +1,364 @@ +"""Tests for fixed-gas extensions in quantammsim.calibration.loss.""" + +import jax +import jax.numpy as jnp +import numpy as np +import pytest + +from tests.calibration.conftest import K_OBS, N_DAYS + + +class TestChainGasUSD: + """Test CHAIN_GAS_USD constants are correct and complete.""" + + def test_known_chains(self): + from quantammsim.calibration.loss import CHAIN_GAS_USD + + assert CHAIN_GAS_USD["MAINNET"] == 1.0 + assert CHAIN_GAS_USD["POLYGON"] == 0.005 + assert CHAIN_GAS_USD["GNOSIS"] == 0.001 + assert CHAIN_GAS_USD["ARBITRUM"] == 0.01 + assert CHAIN_GAS_USD["BASE"] == 0.005 + assert CHAIN_GAS_USD["SONIC"] == 0.005 + + def test_all_values_positive(self): + from quantammsim.calibration.loss import CHAIN_GAS_USD + + for chain, cost in CHAIN_GAS_USD.items(): + assert cost > 0, f"{chain} gas cost must be positive" + + def test_mainnet_most_expensive(self): + from quantammsim.calibration.loss import CHAIN_GAS_USD + + mainnet = CHAIN_GAS_USD["MAINNET"] + for chain, cost in CHAIN_GAS_USD.items(): + if chain != "MAINNET": + assert cost < mainnet, f"{chain} should be cheaper than MAINNET" + + def test_six_chains(self): + from quantammsim.calibration.loss import CHAIN_GAS_USD + + assert len(CHAIN_GAS_USD) == 6 + + +class TestPackUnpackFixedGas: + """Test pack/unpack for fixed-gas param vectors.""" + + def test_pack_shape(self): + from quantammsim.calibration.loss import pack_params_fixed_gas + + flat = pack_params_fixed_gas(2.5, jnp.zeros(K_OBS)) + assert flat.shape == (1 + K_OBS,) + + def test_pack_shape_is_one_shorter_than_free(self): + from quantammsim.calibration.loss import pack_params, pack_params_fixed_gas + + free = pack_params(2.5, 0.0, jnp.zeros(K_OBS)) + fixed = pack_params_fixed_gas(2.5, jnp.zeros(K_OBS)) + assert free.shape[0] == fixed.shape[0] + 1 + + def test_roundtrip(self): + from quantammsim.calibration.loss import ( + pack_params_fixed_gas, + unpack_params_fixed_gas, + ) + + log_cad = 2.5 + noise_coeffs = jnp.array([1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0]) + + flat = pack_params_fixed_gas(log_cad, noise_coeffs) + lc, nc = unpack_params_fixed_gas(flat) + + np.testing.assert_allclose(lc, log_cad) + np.testing.assert_allclose(nc, noise_coeffs) + + def test_unpack_log_cadence_position(self): + """log_cadence is the first element.""" + from quantammsim.calibration.loss import pack_params_fixed_gas + + flat = pack_params_fixed_gas(3.14, jnp.ones(K_OBS) * 99.0) + np.testing.assert_allclose(flat[0], 3.14) + + def test_unpack_noise_coeffs_position(self): + """noise_coeffs are elements [1:].""" + from quantammsim.calibration.loss import pack_params_fixed_gas + + nc = jnp.arange(1, K_OBS + 1, dtype=float) + flat = pack_params_fixed_gas(0.0, nc) + np.testing.assert_allclose(flat[1:], nc) + + +class TestPoolLossFixedGas: + """Test pool_loss_fixed_gas with pinned numerical values.""" + + def _make_params(self, log_cad=None, noise_coeffs=None): + from quantammsim.calibration.loss import pack_params_fixed_gas + + if log_cad is None: + log_cad = float(jnp.log(jnp.array(12.0))) + if noise_coeffs is None: + noise_coeffs = jnp.zeros(K_OBS).at[0].set(8.0) + return pack_params_fixed_gas(log_cad, noise_coeffs) + + def _make_inputs(self, synthetic_pool_coeffs, synthetic_x_obs): + n_obs = synthetic_x_obs.shape[0] + n_days = int(synthetic_pool_coeffs.values.shape[2]) + day_indices = jnp.array(np.arange(n_obs) % n_days) + y_obs = jnp.ones(n_obs) * 9.0 + return jnp.array(synthetic_x_obs), y_obs, day_indices + + def test_pinned_loss_value(self, synthetic_pool_coeffs, synthetic_x_obs): + """Loss at known params must match precomputed value.""" + from quantammsim.calibration.loss import pool_loss_fixed_gas + + params = self._make_params() + x_obs, y_obs, day_indices = self._make_inputs( + synthetic_pool_coeffs, synthetic_x_obs + ) + fixed_log_gas = jnp.log(jnp.array(1.0)) + loss = pool_loss_fixed_gas( + params, fixed_log_gas, synthetic_pool_coeffs, x_obs, y_obs, day_indices + ) + # cadence=12, gas=1.0, noise=[8,0..0], y=9.0 → pinned + np.testing.assert_allclose(float(loss), 0.001727, atol=1e-4) + + def test_pinned_loss_at_multiple_gas_values( + self, synthetic_pool_coeffs, synthetic_x_obs + ): + """Pinned loss values at gas=0.01, 1.0, 5.0.""" + from quantammsim.calibration.loss import pool_loss_fixed_gas + + params = self._make_params() + x_obs, y_obs, day_indices = self._make_inputs( + synthetic_pool_coeffs, synthetic_x_obs + ) + # Precomputed with _make_params defaults: log_cad=log(12), noise=[8,0..0] + expected = {0.01: 0.0763, 1.0: 0.00173, 5.0: 0.0313} + for gas_val, exp_loss in expected.items(): + lg = jnp.log(jnp.array(gas_val)) + loss = float(pool_loss_fixed_gas( + params, lg, synthetic_pool_coeffs, x_obs, y_obs, day_indices + )) + np.testing.assert_allclose(loss, exp_loss, atol=1e-3, + err_msg=f"gas={gas_val}") + + def test_zero_when_perfect(self, synthetic_pool_coeffs, synthetic_x_obs): + """Construct y_obs = log(V_arb + V_noise) exactly, verify loss ≈ 0.""" + from quantammsim.calibration.grid_interpolation import interpolate_pool_daily + from quantammsim.calibration.loss import ( + noise_volume, + pack_params_fixed_gas, + pool_loss_fixed_gas, + ) + + log_cad = jnp.log(jnp.array(12.0)) + fixed_log_gas = jnp.log(jnp.array(1.0)) + noise_coeffs = jnp.zeros(K_OBS).at[0].set(8.0) + + v_arb_all = interpolate_pool_daily( + synthetic_pool_coeffs, log_cad, jnp.exp(fixed_log_gas) + ) + n_obs = synthetic_x_obs.shape[0] + n_days = int(synthetic_pool_coeffs.values.shape[2]) + day_indices = jnp.array(np.arange(n_obs) % n_days) + v_arb = v_arb_all[day_indices] + v_noise = noise_volume(noise_coeffs, jnp.array(synthetic_x_obs)) + y_obs = jnp.log(jnp.maximum(v_arb + v_noise, 1e-6)) + + params = pack_params_fixed_gas(float(log_cad), noise_coeffs) + loss = pool_loss_fixed_gas( + params, fixed_log_gas, synthetic_pool_coeffs, + jnp.array(synthetic_x_obs), y_obs, day_indices, + ) + assert float(loss) < 1e-10 + + def test_matches_free_gas_at_same_value( + self, synthetic_pool_coeffs, synthetic_x_obs + ): + """Fixed-gas loss should equal free-gas loss when gas matches.""" + from quantammsim.calibration.loss import ( + pack_params, + pack_params_fixed_gas, + pool_loss, + pool_loss_fixed_gas, + ) + + log_cad = float(jnp.log(jnp.array(12.0))) + log_gas = float(jnp.log(jnp.array(1.0))) + noise_coeffs = jnp.zeros(K_OBS).at[0].set(8.0) + + x_obs, y_obs, day_indices = self._make_inputs( + synthetic_pool_coeffs, synthetic_x_obs + ) + + free_params = pack_params(log_cad, log_gas, noise_coeffs) + fixed_params = pack_params_fixed_gas(log_cad, noise_coeffs) + + loss_free = pool_loss( + free_params, synthetic_pool_coeffs, x_obs, y_obs, day_indices + ) + loss_fixed = pool_loss_fixed_gas( + fixed_params, jnp.array(log_gas), synthetic_pool_coeffs, + x_obs, y_obs, day_indices, + ) + np.testing.assert_allclose(float(loss_free), float(loss_fixed), rtol=1e-6) + + def test_loss_varies_with_gas_within_grid(self, synthetic_pool_coeffs, synthetic_x_obs): + """Gas values within grid range [0, 5] should produce distinct losses.""" + from quantammsim.calibration.loss import pool_loss_fixed_gas + + params = self._make_params() + x_obs, y_obs, day_indices = self._make_inputs( + synthetic_pool_coeffs, synthetic_x_obs + ) + # Stay within grid range (gas_costs=[0, 1, 5]) to avoid extrapolation plateau + losses = [] + for gas in [0.01, 0.1, 1.0, 3.0]: + lg = jnp.log(jnp.array(gas)) + loss = float(pool_loss_fixed_gas( + params, lg, synthetic_pool_coeffs, x_obs, y_obs, day_indices + )) + losses.append(loss) + # All 4 within-grid gas values should give distinct losses + assert len(set(f"{l:.8f}" for l in losses)) == 4 + + def test_day_indices_affect_loss(self, synthetic_pool_coeffs, synthetic_x_obs): + """Different day_indices must produce different loss — verifies per-day V_arb is used.""" + from quantammsim.calibration.loss import pool_loss_fixed_gas + + params = self._make_params() + n_obs = synthetic_x_obs.shape[0] + n_days = int(synthetic_pool_coeffs.values.shape[2]) + x_obs = jnp.array(synthetic_x_obs) + y_obs = jnp.ones(n_obs) * 9.0 + fixed_log_gas = jnp.log(jnp.array(1.0)) + + day_idx_all_zero = jnp.zeros(n_obs, dtype=jnp.int32) + day_idx_varying = jnp.array(np.arange(n_obs) % n_days) + + loss_same = pool_loss_fixed_gas( + params, fixed_log_gas, synthetic_pool_coeffs, x_obs, y_obs, day_idx_all_zero + ) + loss_vary = pool_loss_fixed_gas( + params, fixed_log_gas, synthetic_pool_coeffs, x_obs, y_obs, day_idx_varying + ) + assert float(loss_same) != float(loss_vary) + + def test_grad_wrt_params(self, synthetic_pool_coeffs, synthetic_x_obs): + """Gradient w.r.t. params_flat has correct shape and is finite.""" + from quantammsim.calibration.loss import pool_loss_fixed_gas + + params = self._make_params() + x_obs, y_obs, day_indices = self._make_inputs( + synthetic_pool_coeffs, synthetic_x_obs + ) + fixed_log_gas = jnp.log(jnp.array(1.0)) + + grad = jax.grad(pool_loss_fixed_gas, argnums=0)( + params, fixed_log_gas, synthetic_pool_coeffs, x_obs, y_obs, day_indices, + ) + assert grad.shape == (1 + K_OBS,) + assert jnp.all(jnp.isfinite(grad)) + # Gradient should be nonzero (we're not at the optimum) + assert float(jnp.sum(jnp.abs(grad))) > 1e-10 + + def test_grad_changes_with_gas( + self, synthetic_pool_coeffs, synthetic_x_obs + ): + """Gradient w.r.t. params should differ at different fixed gas values. + + fixed_log_gas affects V_arb through grid interpolation, which shifts + the loss landscape and thus the gradient. + """ + from quantammsim.calibration.loss import pool_loss_fixed_gas + + params = self._make_params() + x_obs, y_obs, day_indices = self._make_inputs( + synthetic_pool_coeffs, synthetic_x_obs + ) + + grad_fn = jax.grad(pool_loss_fixed_gas, argnums=0) + grad_low = grad_fn( + params, jnp.log(jnp.array(0.01)), + synthetic_pool_coeffs, x_obs, y_obs, day_indices + ) + grad_high = grad_fn( + params, jnp.log(jnp.array(10.0)), + synthetic_pool_coeffs, x_obs, y_obs, day_indices + ) + # Gradients should differ because V_arb differs + assert not jnp.allclose(grad_low, grad_high, atol=1e-6) + + def test_extreme_negative_noise_finite(self, synthetic_pool_coeffs, synthetic_x_obs): + """Loss should remain finite with very negative noise intercept.""" + from quantammsim.calibration.loss import pool_loss_fixed_gas, pack_params_fixed_gas + + # Very negative noise intercept → V_noise ≈ 0, but V_arb still positive + nc = jnp.zeros(K_OBS).at[0].set(-100.0) + params = pack_params_fixed_gas(float(jnp.log(jnp.array(12.0))), nc) + + n_obs = synthetic_x_obs.shape[0] + n_days = int(synthetic_pool_coeffs.values.shape[2]) + day_indices = jnp.array(np.arange(n_obs) % n_days) + x_obs = jnp.array(synthetic_x_obs) + y_obs = jnp.ones(n_obs) * 9.0 + fixed_log_gas = jnp.log(jnp.array(1.0)) + + loss = pool_loss_fixed_gas( + params, fixed_log_gas, synthetic_pool_coeffs, x_obs, y_obs, day_indices + ) + assert jnp.isfinite(loss) + # V_noise ≈ 0, so log(V_arb) ≈ 8.5 vs y=9.0 → nonzero loss + assert float(loss) > 0.01 + # Gradient should also be finite + grad = jax.grad(pool_loss_fixed_gas, argnums=0)( + params, fixed_log_gas, synthetic_pool_coeffs, x_obs, y_obs, day_indices + ) + assert jnp.all(jnp.isfinite(grad)) + + def test_boundary_clamp_cadence(self, synthetic_pool_coeffs, synthetic_x_obs): + """Cadence below grid min should clamp — loss at cad=0.5 equals cad=1.0.""" + from quantammsim.calibration.loss import pack_params_fixed_gas, pool_loss_fixed_gas + + x_obs, y_obs, day_indices = self._make_inputs( + synthetic_pool_coeffs, synthetic_x_obs + ) + nc = jnp.zeros(K_OBS).at[0].set(8.0) + fixed_log_gas = jnp.log(jnp.array(1.0)) + + params_below = pack_params_fixed_gas(float(jnp.log(jnp.array(0.5))), nc) + params_at_min = pack_params_fixed_gas(float(jnp.log(jnp.array(1.0))), nc) + + loss_below = pool_loss_fixed_gas( + params_below, fixed_log_gas, synthetic_pool_coeffs, + x_obs, y_obs, day_indices, + ) + loss_at_min = pool_loss_fixed_gas( + params_at_min, fixed_log_gas, synthetic_pool_coeffs, + x_obs, y_obs, day_indices, + ) + np.testing.assert_allclose(float(loss_below), float(loss_at_min), rtol=1e-6) + + def test_boundary_clamp_gas(self, synthetic_pool_coeffs, synthetic_x_obs): + """Gas above grid max should clamp — loss at gas=10 equals gas=5.""" + from quantammsim.calibration.loss import pool_loss_fixed_gas + + params = self._make_params() + x_obs, y_obs, day_indices = self._make_inputs( + synthetic_pool_coeffs, synthetic_x_obs + ) + + loss_above = pool_loss_fixed_gas( + params, jnp.log(jnp.array(10.0)), synthetic_pool_coeffs, + x_obs, y_obs, day_indices, + ) + loss_at_max = pool_loss_fixed_gas( + params, jnp.log(jnp.array(5.0)), synthetic_pool_coeffs, + x_obs, y_obs, day_indices, + ) + np.testing.assert_allclose(float(loss_above), float(loss_at_max), rtol=1e-6) + + def test_k_obs_matches_loss_module(self): + """K_OBS in conftest must match K_OBS in loss.py.""" + from quantammsim.calibration.loss import K_OBS as K_OBS_IMPL + assert K_OBS == K_OBS_IMPL diff --git a/tests/calibration/test_per_pool_fit.py b/tests/calibration/test_per_pool_fit.py new file mode 100644 index 0000000..32d63ab --- /dev/null +++ b/tests/calibration/test_per_pool_fit.py @@ -0,0 +1,170 @@ +"""Tests for quantammsim.calibration.per_pool_fit — L-BFGS-B per-pool fitting.""" + +import jax.numpy as jnp +import numpy as np +import pytest + +from tests.calibration.conftest import K_OBS, N_DAYS, POOL_IDS_FULL, POOL_PREFIXES + + +class TestFitSinglePool: + """Test fit_single_pool: L-BFGS-B optimization for one pool.""" + + def _make_inputs(self, synthetic_pool_coeffs, synthetic_x_obs): + n_obs = synthetic_x_obs.shape[0] + n_days = int(synthetic_pool_coeffs.values.shape[2]) + day_indices = np.arange(n_obs) % n_days + y_obs = np.ones(n_obs) * 9.0 # log(V_obs) + return synthetic_x_obs, y_obs, day_indices + + def test_returns_result_dict(self, synthetic_pool_coeffs, synthetic_x_obs): + from quantammsim.calibration.per_pool_fit import fit_single_pool + + x_obs, y_obs, day_idx = self._make_inputs( + synthetic_pool_coeffs, synthetic_x_obs + ) + result = fit_single_pool(synthetic_pool_coeffs, x_obs, y_obs, day_idx) + assert isinstance(result, dict) + for key in ["log_cadence", "log_gas", "noise_coeffs", "loss", "converged"]: + assert key in result, f"Missing key: {key}" + + def test_cadence_in_range(self, synthetic_pool_coeffs, synthetic_x_obs): + from quantammsim.calibration.per_pool_fit import fit_single_pool + + x_obs, y_obs, day_idx = self._make_inputs( + synthetic_pool_coeffs, synthetic_x_obs + ) + result = fit_single_pool(synthetic_pool_coeffs, x_obs, y_obs, day_idx) + cadence = np.exp(result["log_cadence"]) + assert 1.0 <= cadence <= 60.0 + + def test_gas_positive(self, synthetic_pool_coeffs, synthetic_x_obs): + from quantammsim.calibration.per_pool_fit import fit_single_pool + + x_obs, y_obs, day_idx = self._make_inputs( + synthetic_pool_coeffs, synthetic_x_obs + ) + result = fit_single_pool(synthetic_pool_coeffs, x_obs, y_obs, day_idx) + assert np.exp(result["log_gas"]) > 0 + + def test_noise_coeffs_length(self, synthetic_pool_coeffs, synthetic_x_obs): + from quantammsim.calibration.per_pool_fit import fit_single_pool + + x_obs, y_obs, day_idx = self._make_inputs( + synthetic_pool_coeffs, synthetic_x_obs + ) + result = fit_single_pool(synthetic_pool_coeffs, x_obs, y_obs, day_idx) + assert len(result["noise_coeffs"]) == K_OBS + + def test_loss_decreases_from_init(self, synthetic_pool_coeffs, synthetic_x_obs): + from quantammsim.calibration.loss import pack_params, pool_loss + from quantammsim.calibration.per_pool_fit import ( + fit_single_pool, + make_initial_guess, + ) + + x_obs, y_obs, day_idx = self._make_inputs( + synthetic_pool_coeffs, synthetic_x_obs + ) + init = make_initial_guess(x_obs, y_obs) + init_loss = float(pool_loss( + jnp.array(init), synthetic_pool_coeffs, + jnp.array(x_obs), jnp.array(y_obs), jnp.array(day_idx), + )) + + result = fit_single_pool(synthetic_pool_coeffs, x_obs, y_obs, day_idx) + assert result["loss"] <= init_loss + + def test_converged_flag(self, synthetic_pool_coeffs, synthetic_x_obs): + from quantammsim.calibration.per_pool_fit import fit_single_pool + + x_obs, y_obs, day_idx = self._make_inputs( + synthetic_pool_coeffs, synthetic_x_obs + ) + result = fit_single_pool(synthetic_pool_coeffs, x_obs, y_obs, day_idx) + assert isinstance(result["converged"], bool) + + +class TestFitAllPools: + """Test fit_all_pools: fit all matched pools.""" + + def test_returns_dict_per_pool( + self, synthetic_daily_grid, synthetic_panel, tmp_path + ): + from quantammsim.calibration.per_pool_fit import fit_all_pools + from quantammsim.calibration.pool_data import match_grids_to_panel + + grid_dir = tmp_path / "grids" + grid_dir.mkdir() + for prefix in POOL_PREFIXES: + synthetic_daily_grid.to_parquet( + grid_dir / f"{prefix}_daily.parquet", index=False + ) + + matched = match_grids_to_panel(str(grid_dir), synthetic_panel) + results = fit_all_pools(matched) + assert isinstance(results, dict) + + def test_all_pools_have_results( + self, synthetic_daily_grid, synthetic_panel, tmp_path + ): + from quantammsim.calibration.per_pool_fit import fit_all_pools + from quantammsim.calibration.pool_data import match_grids_to_panel + + grid_dir = tmp_path / "grids" + grid_dir.mkdir() + for prefix in POOL_PREFIXES: + synthetic_daily_grid.to_parquet( + grid_dir / f"{prefix}_daily.parquet", index=False + ) + + matched = match_grids_to_panel(str(grid_dir), synthetic_panel) + results = fit_all_pools(matched) + for prefix in matched: + assert prefix in results + + def test_results_have_metadata( + self, synthetic_daily_grid, synthetic_panel, tmp_path + ): + from quantammsim.calibration.per_pool_fit import fit_all_pools + from quantammsim.calibration.pool_data import match_grids_to_panel + + grid_dir = tmp_path / "grids" + grid_dir.mkdir() + for prefix in POOL_PREFIXES: + synthetic_daily_grid.to_parquet( + grid_dir / f"{prefix}_daily.parquet", index=False + ) + + matched = match_grids_to_panel(str(grid_dir), synthetic_panel) + results = fit_all_pools(matched) + for prefix, res in results.items(): + assert "chain" in res + assert "fee" in res + assert "tokens" in res + + +class TestInitialGuess: + """Test make_initial_guess: reasonable starting point.""" + + def test_default_init_reasonable(self, synthetic_x_obs): + from quantammsim.calibration.per_pool_fit import make_initial_guess + + n_obs = synthetic_x_obs.shape[0] + y_obs = np.ones(n_obs) * 9.0 + init = make_initial_guess(synthetic_x_obs, y_obs) + assert len(init) == 2 + K_OBS + # log_cadence ~ log(12) + np.testing.assert_allclose(init[0], np.log(12.0), atol=0.1) + # log_gas ~ log(1.0) + np.testing.assert_allclose(init[1], np.log(1.0), atol=0.1) + + def test_init_noise_from_ols(self, synthetic_x_obs): + from quantammsim.calibration.per_pool_fit import make_initial_guess + + n_obs = synthetic_x_obs.shape[0] + y_obs = np.ones(n_obs) * 9.0 + init = make_initial_guess(synthetic_x_obs, y_obs) + noise_coeffs = init[2:] + # OLS should give finite values + assert np.all(np.isfinite(noise_coeffs)) diff --git a/tests/calibration/test_per_pool_fit_fixed_gas.py b/tests/calibration/test_per_pool_fit_fixed_gas.py new file mode 100644 index 0000000..df281c7 --- /dev/null +++ b/tests/calibration/test_per_pool_fit_fixed_gas.py @@ -0,0 +1,383 @@ +"""Tests for fixed-gas mode in quantammsim.calibration.per_pool_fit.""" + +import jax.numpy as jnp +import numpy as np +import pytest + +from tests.calibration.conftest import K_OBS, N_DAYS, POOL_IDS_FULL, POOL_PREFIXES + + +class TestInitialGuessFixedGas: + """Test make_initial_guess_fixed_gas.""" + + def test_shape(self, synthetic_x_obs): + from quantammsim.calibration.per_pool_fit import make_initial_guess_fixed_gas + + n_obs = synthetic_x_obs.shape[0] + y_obs = np.ones(n_obs) * 9.0 + init = make_initial_guess_fixed_gas(synthetic_x_obs, y_obs) + assert init.shape == (1 + K_OBS,) + + def test_one_shorter_than_free(self, synthetic_x_obs): + from quantammsim.calibration.per_pool_fit import ( + make_initial_guess, + make_initial_guess_fixed_gas, + ) + + n_obs = synthetic_x_obs.shape[0] + y_obs = np.ones(n_obs) * 9.0 + free = make_initial_guess(synthetic_x_obs, y_obs) + fixed = make_initial_guess_fixed_gas(synthetic_x_obs, y_obs) + assert free.shape[0] == fixed.shape[0] + 1 + + def test_log_cadence_default(self, synthetic_x_obs): + from quantammsim.calibration.per_pool_fit import make_initial_guess_fixed_gas + + n_obs = synthetic_x_obs.shape[0] + y_obs = np.ones(n_obs) * 9.0 + init = make_initial_guess_fixed_gas(synthetic_x_obs, y_obs) + np.testing.assert_allclose(init[0], np.log(12.0), atol=0.01) + + def test_pinned_ols_coefficients(self, synthetic_x_obs): + """OLS on constant y=9.0: intercept should dominate, others near zero.""" + from quantammsim.calibration.per_pool_fit import make_initial_guess_fixed_gas + + n_obs = synthetic_x_obs.shape[0] + y_obs = np.ones(n_obs) * 9.0 + init = make_initial_guess_fixed_gas(synthetic_x_obs, y_obs) + # Intercept should be close to 9.0 (y is constant) + np.testing.assert_allclose(init[1], 9.0, atol=0.01) + # Other noise coeffs should be near zero for constant y + assert np.all(np.abs(init[2:]) < 0.1) + + def test_noise_matches_free_gas_noise(self, synthetic_x_obs): + """OLS noise coeffs should be identical for free and fixed-gas init.""" + from quantammsim.calibration.per_pool_fit import ( + make_initial_guess, + make_initial_guess_fixed_gas, + ) + + n_obs = synthetic_x_obs.shape[0] + y_obs = np.ones(n_obs) * 9.0 + free = make_initial_guess(synthetic_x_obs, y_obs) + fixed = make_initial_guess_fixed_gas(synthetic_x_obs, y_obs) + np.testing.assert_allclose(free[2:], fixed[1:]) + + def test_ols_with_heterogeneous_y(self, synthetic_x_obs): + """OLS on heterogeneous y should produce different coeffs than constant y.""" + from quantammsim.calibration.per_pool_fit import make_initial_guess_fixed_gas + + # y correlated with TVL (column 1) + y_het = synthetic_x_obs[:, 1] * 0.5 + 5.0 + np.random.RandomState(42).randn( + synthetic_x_obs.shape[0]) * 0.01 + init_het = make_initial_guess_fixed_gas(synthetic_x_obs, y_het) + init_const = make_initial_guess_fixed_gas( + synthetic_x_obs, np.ones(synthetic_x_obs.shape[0]) * 9.0 + ) + # Noise coefficients should differ substantially + assert np.max(np.abs(init_het[1:] - init_const[1:])) > 0.1 + + +class TestFitSinglePoolFixedGas: + """Test fit_single_pool with fixed_gas_usd.""" + + def _make_inputs(self, synthetic_pool_coeffs, synthetic_x_obs): + n_obs = synthetic_x_obs.shape[0] + n_days = int(synthetic_pool_coeffs.values.shape[2]) + day_indices = np.arange(n_obs) % n_days + y_obs = np.ones(n_obs) * 9.0 + return synthetic_x_obs, y_obs, day_indices + + def _make_gt_inputs(self, synthetic_pool_coeffs, synthetic_x_obs): + """Make ground-truth y_obs from known params for recovery test.""" + from quantammsim.calibration.grid_interpolation import interpolate_pool_daily + from quantammsim.calibration.loss import noise_volume + + n_obs = synthetic_x_obs.shape[0] + n_days = int(synthetic_pool_coeffs.values.shape[2]) + day_indices = np.arange(n_obs) % n_days + + TRUE_LOG_CAD = float(np.log(10.0)) + TRUE_GAS = 0.5 + TRUE_NC = np.zeros(K_OBS) + TRUE_NC[0] = 7.0 + TRUE_NC[1] = 0.3 + + v_arb = np.array(interpolate_pool_daily( + synthetic_pool_coeffs, jnp.array(TRUE_LOG_CAD), jnp.array(TRUE_GAS) + ))[day_indices] + v_noise = np.array(noise_volume(jnp.array(TRUE_NC), jnp.array(synthetic_x_obs))) + y_obs = np.log(np.maximum(v_arb + v_noise, 1e-6)) + return synthetic_x_obs, y_obs, day_indices, TRUE_LOG_CAD, TRUE_GAS, TRUE_NC + + def test_returns_result_dict(self, synthetic_pool_coeffs, synthetic_x_obs): + from quantammsim.calibration.per_pool_fit import fit_single_pool + + x_obs, y_obs, day_idx = self._make_inputs( + synthetic_pool_coeffs, synthetic_x_obs + ) + result = fit_single_pool( + synthetic_pool_coeffs, x_obs, y_obs, day_idx, fixed_gas_usd=1.0 + ) + for key in [ + "log_cadence", "log_gas", "noise_coeffs", "loss", + "converged", "gas_fixed", + ]: + assert key in result, f"Missing key: {key}" + + def test_gas_fixed_flag(self, synthetic_pool_coeffs, synthetic_x_obs): + from quantammsim.calibration.per_pool_fit import fit_single_pool + + x_obs, y_obs, day_idx = self._make_inputs( + synthetic_pool_coeffs, synthetic_x_obs + ) + result = fit_single_pool( + synthetic_pool_coeffs, x_obs, y_obs, day_idx, fixed_gas_usd=1.0 + ) + assert result["gas_fixed"] is True + + def test_free_gas_flag(self, synthetic_pool_coeffs, synthetic_x_obs): + from quantammsim.calibration.per_pool_fit import fit_single_pool + + x_obs, y_obs, day_idx = self._make_inputs( + synthetic_pool_coeffs, synthetic_x_obs + ) + result = fit_single_pool( + synthetic_pool_coeffs, x_obs, y_obs, day_idx + ) + assert result["gas_fixed"] is False + + def test_gas_usd_pinned(self, synthetic_pool_coeffs, synthetic_x_obs): + """gas_usd in result must exactly match the fixed value.""" + from quantammsim.calibration.per_pool_fit import fit_single_pool + + x_obs, y_obs, day_idx = self._make_inputs( + synthetic_pool_coeffs, synthetic_x_obs + ) + for gas_val in [0.001, 0.01, 0.5, 1.0, 5.0]: + result = fit_single_pool( + synthetic_pool_coeffs, x_obs, y_obs, day_idx, + fixed_gas_usd=gas_val, + ) + assert result["gas_usd"] == gas_val + + def test_log_gas_pinned(self, synthetic_pool_coeffs, synthetic_x_obs): + """log_gas must equal log(fixed_gas_usd).""" + from quantammsim.calibration.per_pool_fit import fit_single_pool + + x_obs, y_obs, day_idx = self._make_inputs( + synthetic_pool_coeffs, synthetic_x_obs + ) + result = fit_single_pool( + synthetic_pool_coeffs, x_obs, y_obs, day_idx, fixed_gas_usd=2.5, + ) + np.testing.assert_allclose( + result["log_gas"], np.log(2.5), rtol=1e-6, + ) + + def test_pinned_fit_on_constant_y(self, synthetic_pool_coeffs, synthetic_x_obs): + """Pinned fitted values for fixed_gas=1.0, y=9.0.""" + from quantammsim.calibration.per_pool_fit import fit_single_pool + + x_obs, y_obs, day_idx = self._make_inputs( + synthetic_pool_coeffs, synthetic_x_obs + ) + result = fit_single_pool( + synthetic_pool_coeffs, x_obs, y_obs, day_idx, fixed_gas_usd=1.0, + ) + assert result["converged"] + np.testing.assert_allclose(result["loss"], 1.30e-5, atol=5e-5) + np.testing.assert_allclose( + result["noise_coeffs"][0], 8.989, atol=0.05, + ) + assert 5.0 <= result["cadence_minutes"] <= 15.0 + + def test_ground_truth_recovery_fixed_gas( + self, synthetic_pool_coeffs, synthetic_x_obs + ): + """Fit on ground-truth y with correct gas → near-zero loss, correct cadence.""" + from quantammsim.calibration.per_pool_fit import fit_single_pool + + x_obs, y_obs, day_idx, true_lc, true_gas, true_nc = self._make_gt_inputs( + synthetic_pool_coeffs, synthetic_x_obs + ) + result = fit_single_pool( + synthetic_pool_coeffs, x_obs, y_obs, day_idx, fixed_gas_usd=true_gas, + ) + assert result["converged"] + assert result["loss"] < 1e-5 + + def test_ground_truth_recovery_free_gas( + self, synthetic_pool_coeffs, synthetic_x_obs + ): + """Fit on ground-truth y with free gas → near-zero loss.""" + from quantammsim.calibration.per_pool_fit import fit_single_pool + + x_obs, y_obs, day_idx, _, _, _ = self._make_gt_inputs( + synthetic_pool_coeffs, synthetic_x_obs + ) + result = fit_single_pool( + synthetic_pool_coeffs, x_obs, y_obs, day_idx, + ) + assert result["converged"] + assert result["loss"] < 1e-5 + + def test_loss_decreases_from_init(self, synthetic_pool_coeffs, synthetic_x_obs): + from quantammsim.calibration.loss import pool_loss_fixed_gas + from quantammsim.calibration.per_pool_fit import ( + fit_single_pool, + make_initial_guess_fixed_gas, + ) + + x_obs, y_obs, day_idx = self._make_inputs( + synthetic_pool_coeffs, synthetic_x_obs + ) + init = make_initial_guess_fixed_gas(x_obs, y_obs) + fixed_log_gas = jnp.float64(np.log(1.0)) + init_loss = float(pool_loss_fixed_gas( + jnp.array(init), fixed_log_gas, synthetic_pool_coeffs, + jnp.array(x_obs), jnp.array(y_obs), jnp.array(day_idx), + )) + + result = fit_single_pool( + synthetic_pool_coeffs, x_obs, y_obs, day_idx, fixed_gas_usd=1.0, + ) + assert result["loss"] < init_loss * 0.99 # at least 1% improvement + + def test_different_fixed_gas_different_cadence( + self, synthetic_pool_coeffs, synthetic_x_obs + ): + """Gas values spanning 5000x should produce substantially different cadences.""" + from quantammsim.calibration.per_pool_fit import fit_single_pool + + x_obs, y_obs, day_idx = self._make_inputs( + synthetic_pool_coeffs, synthetic_x_obs + ) + r_low = fit_single_pool( + synthetic_pool_coeffs, x_obs, y_obs, day_idx, fixed_gas_usd=0.001, + ) + r_high = fit_single_pool( + synthetic_pool_coeffs, x_obs, y_obs, day_idx, fixed_gas_usd=5.0, + ) + # 5000x gas range → cadences should differ substantially + assert abs(r_low["log_cadence"] - r_high["log_cadence"]) > 0.1 + + def test_fixed_vs_free_gas_on_gt_data( + self, synthetic_pool_coeffs, synthetic_x_obs + ): + """Free gas should achieve loss ≤ fixed gas (more degrees of freedom).""" + from quantammsim.calibration.per_pool_fit import fit_single_pool + + x_obs, y_obs, day_idx, _, _, _ = self._make_gt_inputs( + synthetic_pool_coeffs, synthetic_x_obs + ) + r_free = fit_single_pool( + synthetic_pool_coeffs, x_obs, y_obs, day_idx, + ) + r_fixed = fit_single_pool( + synthetic_pool_coeffs, x_obs, y_obs, day_idx, fixed_gas_usd=1.0, + ) + # Free gas has strictly more freedom → should do at least as well + assert r_free["loss"] <= r_fixed["loss"] * 1.01 + + +class TestFitAllPoolsFixedGas: + """Test fit_all_pools with fix_gas_to_chain=True.""" + + def _make_matched(self, synthetic_daily_grid, synthetic_panel, tmp_path): + from quantammsim.calibration.pool_data import match_grids_to_panel + + grid_dir = tmp_path / "grids" + grid_dir.mkdir() + for prefix in POOL_PREFIXES: + synthetic_daily_grid.to_parquet( + grid_dir / f"{prefix}_daily.parquet", index=False + ) + return match_grids_to_panel(str(grid_dir), synthetic_panel) + + def test_all_gas_fixed( + self, synthetic_daily_grid, synthetic_panel, tmp_path + ): + from quantammsim.calibration.per_pool_fit import fit_all_pools + + matched = self._make_matched( + synthetic_daily_grid, synthetic_panel, tmp_path + ) + results = fit_all_pools(matched, fix_gas_to_chain=True) + for prefix, res in results.items(): + assert res["gas_fixed"] is True + + def test_gas_matches_chain( + self, synthetic_daily_grid, synthetic_panel, tmp_path + ): + """Each pool's gas_usd should match CHAIN_GAS_USD[chain].""" + from quantammsim.calibration.loss import CHAIN_GAS_USD + from quantammsim.calibration.per_pool_fit import fit_all_pools + + matched = self._make_matched( + synthetic_daily_grid, synthetic_panel, tmp_path + ) + results = fit_all_pools(matched, fix_gas_to_chain=True) + for prefix, res in results.items(): + chain = res["chain"] + expected = CHAIN_GAS_USD.get(chain, 1.0) + assert res["gas_usd"] == expected, ( + f"{prefix} ({chain}): gas_usd={res['gas_usd']} != {expected}" + ) + + def test_free_gas_not_fixed( + self, synthetic_daily_grid, synthetic_panel, tmp_path + ): + from quantammsim.calibration.per_pool_fit import fit_all_pools + + matched = self._make_matched( + synthetic_daily_grid, synthetic_panel, tmp_path + ) + results = fit_all_pools(matched, fix_gas_to_chain=False) + for prefix, res in results.items(): + assert res["gas_fixed"] is False + + def test_both_pools_have_results( + self, synthetic_daily_grid, synthetic_panel, tmp_path + ): + from quantammsim.calibration.per_pool_fit import fit_all_pools + + matched = self._make_matched( + synthetic_daily_grid, synthetic_panel, tmp_path + ) + results = fit_all_pools(matched, fix_gas_to_chain=True) + assert len(results) == len(matched) + for prefix in matched: + assert prefix in results + assert results[prefix]["converged"] + + def test_metadata_preserved( + self, synthetic_daily_grid, synthetic_panel, tmp_path + ): + """Each result should carry chain, fee, tokens from the matched data.""" + from quantammsim.calibration.per_pool_fit import fit_all_pools + + matched = self._make_matched( + synthetic_daily_grid, synthetic_panel, tmp_path + ) + results = fit_all_pools(matched, fix_gas_to_chain=True) + for prefix, res in results.items(): + assert res["chain"] == matched[prefix]["chain"] + assert res["tokens"] == matched[prefix]["tokens"] + assert np.isfinite(res["fee"]) + + def test_mainnet_gas_1_arbitrum_gas_001( + self, synthetic_daily_grid, synthetic_panel, tmp_path + ): + """Pin: MAINNET pool gets gas=1.0, ARBITRUM pool gets gas=0.01.""" + from quantammsim.calibration.per_pool_fit import fit_all_pools + + matched = self._make_matched( + synthetic_daily_grid, synthetic_panel, tmp_path + ) + results = fit_all_pools(matched, fix_gas_to_chain=True) + for prefix, res in results.items(): + if res["chain"] == "MAINNET": + assert res["gas_usd"] == 1.0 + elif res["chain"] == "ARBITRUM": + assert res["gas_usd"] == 0.01 diff --git a/tests/calibration/test_pool_data.py b/tests/calibration/test_pool_data.py new file mode 100644 index 0000000..4864c19 --- /dev/null +++ b/tests/calibration/test_pool_data.py @@ -0,0 +1,807 @@ +"""Tests for quantammsim.calibration.pool_data — data assembly.""" + +import numpy as np +import pandas as pd +import pytest + +from tests.calibration.conftest import ( + K_OBS, + N_DAYS, + POOL_IDS_FULL, + POOL_PREFIXES, +) + + +class TestEncodeTokens: + """Test encode_tokens: token index, assignments, and covariate matrix.""" + + def _get_matched(self, synthetic_daily_grid, synthetic_panel, tmp_path): + from quantammsim.calibration.pool_data import match_grids_to_panel + + grid_dir = tmp_path / "grids" + grid_dir.mkdir() + for prefix in POOL_PREFIXES: + synthetic_daily_grid.to_parquet( + grid_dir / f"{prefix}_daily.parquet", index=False + ) + return match_grids_to_panel(str(grid_dir), synthetic_panel) + + def test_returns_expected_keys( + self, synthetic_daily_grid, synthetic_panel, tmp_path + ): + from quantammsim.calibration.pool_data import encode_tokens + + matched = self._get_matched( + synthetic_daily_grid, synthetic_panel, tmp_path + ) + result = encode_tokens(matched) + expected_keys = { + "token_index", "token_a_idx", "token_b_idx", + "x_token", "chain_idx", "chain_index", + "log_fees", "n_tokens", "n_chains", + } + assert expected_keys.issubset(result.keys()) + + def test_unique_tokens_discovered( + self, synthetic_daily_grid, synthetic_panel, tmp_path + ): + from quantammsim.calibration.pool_data import encode_tokens + + matched = self._get_matched( + synthetic_daily_grid, synthetic_panel, tmp_path + ) + result = encode_tokens(matched) + # Synthetic panel has tokens: BTC, ETH (pool 0) and AAVE, ETH (pool 1) + # Unique tokens: AAVE, BTC, ETH (sorted) + assert result["n_tokens"] == 3 + assert set(result["token_index"].keys()) == {"AAVE", "BTC", "ETH"} + # Indices should be contiguous 0..2 + assert set(result["token_index"].values()) == {0, 1, 2} + + def test_x_token_shape_and_intercept( + self, synthetic_daily_grid, synthetic_panel, tmp_path + ): + from quantammsim.calibration.pool_data import encode_tokens + + matched = self._get_matched( + synthetic_daily_grid, synthetic_panel, tmp_path + ) + result = encode_tokens(matched) + x_token = result["x_token"] + assert x_token.shape[0] == result["n_tokens"] # 3 tokens + assert x_token.shape[1] >= 4 # at least intercept + 3 binary flags + # Intercept column is all 1s + np.testing.assert_array_equal(x_token[:, 0], 1.0) + + def test_token_classifications( + self, synthetic_daily_grid, synthetic_panel, tmp_path + ): + from quantammsim.calibration.pool_data import encode_tokens + + matched = self._get_matched( + synthetic_daily_grid, synthetic_panel, tmp_path + ) + result = encode_tokens(matched) + ti = result["token_index"] + x_tok = result["x_token"] + # Column layout: [intercept, log_mcap, is_stable, is_eth_deriv, is_L1_native] + # ETH: is_eth_derivative=1, is_L1_native=1 + assert x_tok[ti["ETH"], 3] == 1.0 # is_eth_derivative + assert x_tok[ti["ETH"], 4] == 1.0 # is_L1_native + # AAVE: none of the binary flags + assert x_tok[ti["AAVE"], 2] == 0.0 # not stable + assert x_tok[ti["AAVE"], 3] == 0.0 # not eth_deriv + assert x_tok[ti["AAVE"], 4] == 0.0 # not L1_native + + def test_pool_token_mapping( + self, synthetic_daily_grid, synthetic_panel, tmp_path + ): + from quantammsim.calibration.pool_data import encode_tokens + + matched = self._get_matched( + synthetic_daily_grid, synthetic_panel, tmp_path + ) + result = encode_tokens(matched) + ti = result["token_index"] + + # Pool 0 (first sorted prefix): tokens = "BTC,ETH" + assert result["token_a_idx"][0] == ti["BTC"] + assert result["token_b_idx"][0] == ti["ETH"] + + # Pool 1 (second sorted prefix): tokens = "AAVE,ETH" + assert result["token_a_idx"][1] == ti["AAVE"] + assert result["token_b_idx"][1] == ti["ETH"] + + def test_chain_index( + self, synthetic_daily_grid, synthetic_panel, tmp_path + ): + from quantammsim.calibration.pool_data import encode_tokens + + matched = self._get_matched( + synthetic_daily_grid, synthetic_panel, tmp_path + ) + result = encode_tokens(matched) + assert result["n_chains"] == 2 + assert set(result["chain_index"].keys()) == {"ARBITRUM", "MAINNET"} + assert set(result["chain_index"].values()) == {0, 1} + + def test_chain_idx_mapping( + self, synthetic_daily_grid, synthetic_panel, tmp_path + ): + from quantammsim.calibration.pool_data import encode_tokens + + matched = self._get_matched( + synthetic_daily_grid, synthetic_panel, tmp_path + ) + result = encode_tokens(matched) + pool_ids = sorted(matched.keys()) + ci = result["chain_index"] + # Pool 0 is MAINNET, pool 1 is ARBITRUM + assert result["chain_idx"][0] == ci[matched[pool_ids[0]]["chain"]] + assert result["chain_idx"][1] == ci[matched[pool_ids[1]]["chain"]] + + def test_log_fees( + self, synthetic_daily_grid, synthetic_panel, tmp_path + ): + from quantammsim.calibration.pool_data import encode_tokens + + matched = self._get_matched( + synthetic_daily_grid, synthetic_panel, tmp_path + ) + result = encode_tokens(matched) + pool_ids = sorted(matched.keys()) + for i, pid in enumerate(pool_ids): + expected_fee = matched[pid]["fee"] + np.testing.assert_allclose( + result["log_fees"][i], np.log(expected_fee), rtol=1e-6 + ) + + +class TestMatchGridsToPanel: + """Test match_grids_to_panel: match grid parquets to panel rows.""" + + def test_match_returns_dict_per_pool( + self, synthetic_daily_grid, synthetic_panel, tmp_path + ): + from quantammsim.calibration.pool_data import match_grids_to_panel + + # Write grid for pool 0 + grid_dir = tmp_path / "grids" + grid_dir.mkdir() + synthetic_daily_grid.to_parquet( + grid_dir / f"{POOL_PREFIXES[0]}_daily.parquet", index=False + ) + + matched = match_grids_to_panel(str(grid_dir), synthetic_panel) + assert isinstance(matched, dict) + assert POOL_PREFIXES[0] in matched + + def test_match_filters_to_grid_pools_only( + self, synthetic_daily_grid, synthetic_panel, tmp_path + ): + from quantammsim.calibration.pool_data import match_grids_to_panel + + grid_dir = tmp_path / "grids" + grid_dir.mkdir() + # Only write grid for pool 0 — pool 1 should be excluded + synthetic_daily_grid.to_parquet( + grid_dir / f"{POOL_PREFIXES[0]}_daily.parquet", index=False + ) + + matched = match_grids_to_panel(str(grid_dir), synthetic_panel) + assert POOL_PREFIXES[0] in matched + assert POOL_PREFIXES[1] not in matched + + def test_match_includes_panel_obs( + self, synthetic_daily_grid, synthetic_panel, tmp_path + ): + from quantammsim.calibration.pool_data import match_grids_to_panel + + grid_dir = tmp_path / "grids" + grid_dir.mkdir() + synthetic_daily_grid.to_parquet( + grid_dir / f"{POOL_PREFIXES[0]}_daily.parquet", index=False + ) + + matched = match_grids_to_panel(str(grid_dir), synthetic_panel) + entry = matched[POOL_PREFIXES[0]] + assert "panel" in entry + assert isinstance(entry["panel"], pd.DataFrame) + assert len(entry["panel"]) > 0 + + def test_match_includes_coeffs( + self, synthetic_daily_grid, synthetic_panel, tmp_path + ): + from quantammsim.calibration.grid_interpolation import PoolCoeffsDaily + from quantammsim.calibration.pool_data import match_grids_to_panel + + grid_dir = tmp_path / "grids" + grid_dir.mkdir() + synthetic_daily_grid.to_parquet( + grid_dir / f"{POOL_PREFIXES[0]}_daily.parquet", index=False + ) + + matched = match_grids_to_panel(str(grid_dir), synthetic_panel) + assert "coeffs" in matched[POOL_PREFIXES[0]] + assert isinstance(matched[POOL_PREFIXES[0]]["coeffs"], PoolCoeffsDaily) + + def test_pool_id_prefix_matching( + self, synthetic_daily_grid, synthetic_panel, tmp_path + ): + from quantammsim.calibration.pool_data import match_grids_to_panel + + grid_dir = tmp_path / "grids" + grid_dir.mkdir() + synthetic_daily_grid.to_parquet( + grid_dir / f"{POOL_PREFIXES[0]}_daily.parquet", index=False + ) + + matched = match_grids_to_panel(str(grid_dir), synthetic_panel) + entry = matched[POOL_PREFIXES[0]] + assert entry["pool_id"] == POOL_IDS_FULL[0] + + def test_match_includes_day_indices( + self, synthetic_daily_grid, synthetic_panel, tmp_path + ): + from quantammsim.calibration.pool_data import match_grids_to_panel + + grid_dir = tmp_path / "grids" + grid_dir.mkdir() + synthetic_daily_grid.to_parquet( + grid_dir / f"{POOL_PREFIXES[0]}_daily.parquet", index=False + ) + + matched = match_grids_to_panel(str(grid_dir), synthetic_panel) + entry = matched[POOL_PREFIXES[0]] + assert "day_indices" in entry + assert len(entry["day_indices"]) == len(entry["panel"]) + + def test_day_indices_align_dates( + self, synthetic_daily_grid, synthetic_panel, tmp_path + ): + from quantammsim.calibration.pool_data import match_grids_to_panel + + grid_dir = tmp_path / "grids" + grid_dir.mkdir() + synthetic_daily_grid.to_parquet( + grid_dir / f"{POOL_PREFIXES[0]}_daily.parquet", index=False + ) + + matched = match_grids_to_panel(str(grid_dir), synthetic_panel) + entry = matched[POOL_PREFIXES[0]] + coeffs = entry["coeffs"] + day_indices = entry["day_indices"] + + # Panel dates should map to grid dates via ordinals + panel_dates = pd.to_datetime(entry["panel"]["date"]) + panel_ordinals = np.array([d.toordinal() for d in panel_dates]) + grid_ordinals = np.array(coeffs.dates) + + for i, panel_ord in enumerate(panel_ordinals): + grid_idx = day_indices[i] + assert grid_ordinals[grid_idx] == panel_ord + + +class TestBuildXObs: + """Test build_x_obs: observation covariate matrix.""" + + def test_x_obs_shape(self, synthetic_panel): + from quantammsim.calibration.pool_data import build_x_obs + + pool0 = synthetic_panel[synthetic_panel["pool_id"] == POOL_IDS_FULL[0]] + x = build_x_obs(pool0) + assert x.shape == (len(pool0), K_OBS) + + def test_x_obs_intercept_column(self, synthetic_panel): + from quantammsim.calibration.pool_data import build_x_obs + + pool0 = synthetic_panel[synthetic_panel["pool_id"] == POOL_IDS_FULL[0]] + x = build_x_obs(pool0) + np.testing.assert_array_equal(x[:, 0], 1.0) + + def test_x_obs_lagged_tvl(self, synthetic_panel): + from quantammsim.calibration.pool_data import build_x_obs + + pool0 = synthetic_panel[synthetic_panel["pool_id"] == POOL_IDS_FULL[0]] + x = build_x_obs(pool0) + np.testing.assert_allclose(x[:, 1], pool0["log_tvl_lag1"].values) + + def test_x_obs_log_sigma(self, synthetic_panel): + from quantammsim.calibration.pool_data import build_x_obs + + pool0 = synthetic_panel[synthetic_panel["pool_id"] == POOL_IDS_FULL[0]] + x = build_x_obs(pool0) + expected = np.log(np.maximum(pool0["volatility"].values, 1e-6)) + np.testing.assert_allclose(x[:, 2], expected) + + def test_x_obs_interactions(self, synthetic_panel): + from quantammsim.calibration.pool_data import build_x_obs + + pool0 = synthetic_panel[synthetic_panel["pool_id"] == POOL_IDS_FULL[0]] + x = build_x_obs(pool0) + tvl = pool0["log_tvl_lag1"].values + sigma = np.log(np.maximum(pool0["volatility"].values, 1e-6)) + fee = pool0["log_fee"].values + np.testing.assert_allclose(x[:, 3], tvl * sigma) + np.testing.assert_allclose(x[:, 4], tvl * fee) + np.testing.assert_allclose(x[:, 5], sigma * fee) + + def test_x_obs_dow_harmonics(self, synthetic_panel): + from quantammsim.calibration.pool_data import build_x_obs + + pool0 = synthetic_panel[synthetic_panel["pool_id"] == POOL_IDS_FULL[0]] + x = build_x_obs(pool0) + weekdays = pd.to_datetime(pool0["date"]).dt.weekday.values + expected_sin = np.sin(2 * np.pi * weekdays / 7) + expected_cos = np.cos(2 * np.pi * weekdays / 7) + np.testing.assert_allclose(x[:, 6], expected_sin, atol=1e-10) + np.testing.assert_allclose(x[:, 7], expected_cos, atol=1e-10) + + def test_x_obs_no_nans(self, synthetic_panel): + from quantammsim.calibration.pool_data import build_x_obs + + pool0 = synthetic_panel[synthetic_panel["pool_id"] == POOL_IDS_FULL[0]] + x = build_x_obs(pool0) + assert not np.any(np.isnan(x)) + + +class TestBuildXObsReduced: + """Test build_x_obs with reduced=True: 4-column pruned covariates.""" + + def test_reduced_shape(self, synthetic_panel): + from quantammsim.calibration.pool_data import K_OBS_REDUCED, build_x_obs + + pool0 = synthetic_panel[synthetic_panel["pool_id"] == POOL_IDS_FULL[0]] + x = build_x_obs(pool0, reduced=True) + assert x.shape == (len(pool0), K_OBS_REDUCED) + assert K_OBS_REDUCED == 4 + + def test_reduced_columns(self, synthetic_panel): + from quantammsim.calibration.pool_data import build_x_obs + + pool0 = synthetic_panel[synthetic_panel["pool_id"] == POOL_IDS_FULL[0]] + x_full = build_x_obs(pool0) + x_red = build_x_obs(pool0, reduced=True) + + # col 0: intercept + np.testing.assert_array_equal(x_red[:, 0], 1.0) + # col 1: log_tvl_lag1 (same as full col 1) + np.testing.assert_allclose(x_red[:, 1], x_full[:, 1]) + # col 2: dow_sin (same as full col 6) + np.testing.assert_allclose(x_red[:, 2], x_full[:, 6]) + # col 3: dow_cos (same as full col 7) + np.testing.assert_allclose(x_red[:, 3], x_full[:, 7]) + + def test_reduced_no_sigma(self, synthetic_panel): + from quantammsim.calibration.pool_data import build_x_obs + + pool0 = synthetic_panel[synthetic_panel["pool_id"] == POOL_IDS_FULL[0]] + x_full = build_x_obs(pool0) + x_red = build_x_obs(pool0, reduced=True) + + # Sigma-dependent columns from full (2,3,5) should not appear + sigma_cols = x_full[:, [2, 3, 5]] + for col in range(x_red.shape[1]): + for scol in range(sigma_cols.shape[1]): + if not np.allclose(sigma_cols[:, scol], 0.0): + assert not np.allclose(x_red[:, col], sigma_cols[:, scol]) + + def test_default_unchanged(self, synthetic_panel): + from quantammsim.calibration.pool_data import build_x_obs + + pool0 = synthetic_panel[synthetic_panel["pool_id"] == POOL_IDS_FULL[0]] + x = build_x_obs(pool0) + assert x.shape == (len(pool0), K_OBS) + + def test_reduced_no_nans(self, synthetic_panel): + from quantammsim.calibration.pool_data import build_x_obs + + pool0 = synthetic_panel[synthetic_panel["pool_id"] == POOL_IDS_FULL[0]] + x = build_x_obs(pool0, reduced=True) + assert not np.any(np.isnan(x)) + + +class TestBuildPoolAttributes: + """Test build_pool_attributes: pool-level feature matrix.""" + + def test_attributes_shape( + self, synthetic_daily_grid, synthetic_panel, tmp_path + ): + from quantammsim.calibration.pool_data import ( + build_pool_attributes, + match_grids_to_panel, + ) + + grid_dir = tmp_path / "grids" + grid_dir.mkdir() + for prefix in POOL_PREFIXES: + synthetic_daily_grid.to_parquet( + grid_dir / f"{prefix}_daily.parquet", index=False + ) + + matched = match_grids_to_panel(str(grid_dir), synthetic_panel) + X_attr, attr_names, pool_ids = build_pool_attributes(matched) + assert X_attr.shape[0] == len(matched) + assert X_attr.shape[1] == len(attr_names) + + def test_attributes_has_chain( + self, synthetic_daily_grid, synthetic_panel, tmp_path + ): + from quantammsim.calibration.pool_data import ( + build_pool_attributes, + match_grids_to_panel, + ) + + grid_dir = tmp_path / "grids" + grid_dir.mkdir() + for prefix in POOL_PREFIXES: + synthetic_daily_grid.to_parquet( + grid_dir / f"{prefix}_daily.parquet", index=False + ) + + matched = match_grids_to_panel(str(grid_dir), synthetic_panel) + X_attr, attr_names, pool_ids = build_pool_attributes(matched) + chain_cols = [n for n in attr_names if n.startswith("chain_")] + assert len(chain_cols) > 0 + + def test_attributes_has_log_fee( + self, synthetic_daily_grid, synthetic_panel, tmp_path + ): + from quantammsim.calibration.pool_data import ( + build_pool_attributes, + match_grids_to_panel, + ) + + grid_dir = tmp_path / "grids" + grid_dir.mkdir() + for prefix in POOL_PREFIXES: + synthetic_daily_grid.to_parquet( + grid_dir / f"{prefix}_daily.parquet", index=False + ) + + matched = match_grids_to_panel(str(grid_dir), synthetic_panel) + X_attr, attr_names, pool_ids = build_pool_attributes(matched) + assert "log_fee" in attr_names + + def test_attributes_has_log_tvl( + self, synthetic_daily_grid, synthetic_panel, tmp_path + ): + from quantammsim.calibration.pool_data import ( + build_pool_attributes, + match_grids_to_panel, + ) + + grid_dir = tmp_path / "grids" + grid_dir.mkdir() + for prefix in POOL_PREFIXES: + synthetic_daily_grid.to_parquet( + grid_dir / f"{prefix}_daily.parquet", index=False + ) + + matched = match_grids_to_panel(str(grid_dir), synthetic_panel) + X_attr, attr_names, pool_ids = build_pool_attributes(matched) + assert "mean_log_tvl" in attr_names + + def test_attributes_returns_pool_order( + self, synthetic_daily_grid, synthetic_panel, tmp_path + ): + from quantammsim.calibration.pool_data import ( + build_pool_attributes, + match_grids_to_panel, + ) + + grid_dir = tmp_path / "grids" + grid_dir.mkdir() + for prefix in POOL_PREFIXES: + synthetic_daily_grid.to_parquet( + grid_dir / f"{prefix}_daily.parquet", index=False + ) + + matched = match_grids_to_panel(str(grid_dir), synthetic_panel) + X_attr, attr_names, pool_ids = build_pool_attributes(matched) + assert isinstance(pool_ids, list) + assert len(pool_ids) == len(matched) + assert set(pool_ids) == set(matched.keys()) + + +class TestTokenCanonicalization: + """Test _CANON_MAP and canonicalization in encode_tokens.""" + + def test_canon_map_exists(self): + from quantammsim.calibration.pool_data import _CANON_MAP + assert isinstance(_CANON_MAP, dict) + + def test_canon_map_expected_mappings(self): + from quantammsim.calibration.pool_data import _CANON_MAP + assert _CANON_MAP["WETH"] == "ETH" + assert _CANON_MAP["waBasWETH"] == "ETH" + assert _CANON_MAP["waEthLidoWETH"] == "ETH" + assert _CANON_MAP["waEthLidowstETH"] == "wstETH" + assert _CANON_MAP["waGnowstETH"] == "wstETH" + assert _CANON_MAP["waBasUSDC"] == "USDC" + assert _CANON_MAP["scUSD"] == "USDC" + assert _CANON_MAP["sDAI"] == "DAI" + assert _CANON_MAP["WBTC"] == "BTC" + assert _CANON_MAP["waGnoGNO"] == "GNO" + assert _CANON_MAP["stS"] == "S" + + def test_canonicalize_passthrough(self): + from quantammsim.calibration.pool_data import _CANON_MAP + for tok in ["AAVE", "BTC", "ETH", "LINK", "ARB"]: + assert tok not in _CANON_MAP + + def test_canonicalize_function(self): + from quantammsim.calibration.pool_data import _canonicalize_token + assert _canonicalize_token("WETH") == "ETH" + assert _canonicalize_token("WBTC") == "BTC" + assert _canonicalize_token("ETH") == "ETH" + assert _canonicalize_token("AAVE") == "AAVE" + + def test_encode_tokens_canonicalize_false( + self, synthetic_daily_grid, synthetic_panel, tmp_path + ): + """With canonicalize=False, same result as v1 (no merging).""" + from quantammsim.calibration.pool_data import encode_tokens, match_grids_to_panel + + grid_dir = tmp_path / "grids" + grid_dir.mkdir() + for prefix in POOL_PREFIXES: + synthetic_daily_grid.to_parquet( + grid_dir / f"{prefix}_daily.parquet", index=False + ) + matched = match_grids_to_panel(str(grid_dir), synthetic_panel) + result = encode_tokens(matched, canonicalize=False) + # Synthetic uses BTC, ETH, AAVE — none in canon map, same either way + assert result["n_tokens"] == 3 + assert set(result["token_index"].keys()) == {"AAVE", "BTC", "ETH"} + + def test_encode_tokens_canonicalize_default( + self, synthetic_daily_grid, synthetic_panel, tmp_path + ): + """Default canonicalize=True. Synthetic data unaffected (BTC, ETH, AAVE not in map).""" + from quantammsim.calibration.pool_data import encode_tokens, match_grids_to_panel + + grid_dir = tmp_path / "grids" + grid_dir.mkdir() + for prefix in POOL_PREFIXES: + synthetic_daily_grid.to_parquet( + grid_dir / f"{prefix}_daily.parquet", index=False + ) + matched = match_grids_to_panel(str(grid_dir), synthetic_panel) + result = encode_tokens(matched) # canonicalize=True by default + assert result["n_tokens"] == 3 + assert set(result["token_index"].keys()) == {"AAVE", "BTC", "ETH"} + + def test_encode_tokens_merges_wrapped_tokens(self): + """Synthetic matched dict with WETH+USDC and waBasWETH+WBTC → ETH, BTC, USDC.""" + from quantammsim.calibration.pool_data import encode_tokens + + # Minimal matched dict — encode_tokens only uses 'tokens' and 'fee' keys + matched = { + "pool_a": { + "tokens": "WETH,USDC", + "fee": 0.003, + "chain": "MAINNET", + }, + "pool_b": { + "tokens": "waBasWETH,WBTC", + "fee": 0.003, + "chain": "BASE", + }, + } + result = encode_tokens(matched, canonicalize=True) + # WETH→ETH, waBasWETH→ETH, WBTC→BTC → unique: {BTC, ETH, USDC} + assert result["n_tokens"] == 3 + assert set(result["token_index"].keys()) == {"BTC", "ETH", "USDC"} + + # Both pools should have ETH as token A (canonicalized) + ti = result["token_index"] + assert result["token_a_idx"][0] == ti["ETH"] # pool_a: WETH→ETH + assert result["token_a_idx"][1] == ti["ETH"] # pool_b: waBasWETH→ETH + + def test_encode_tokens_canon_false_keeps_wrapped(self): + """canonicalize=False keeps WETH and waBasWETH as separate tokens.""" + from quantammsim.calibration.pool_data import encode_tokens + + matched = { + "pool_a": { + "tokens": "WETH,USDC", + "fee": 0.003, + "chain": "MAINNET", + }, + "pool_b": { + "tokens": "waBasWETH,WBTC", + "fee": 0.003, + "chain": "BASE", + }, + } + result = encode_tokens(matched, canonicalize=False) + # No merging: WETH, USDC, waBasWETH, WBTC → 4 unique tokens + assert result["n_tokens"] == 4 + assert "WETH" in result["token_index"] + assert "waBasWETH" in result["token_index"] + + +class TestCrossPoolFeatures: + """Test build_cross_pool_x_obs: cross-pool lagged volume features.""" + + @pytest.fixture + def three_pool_panel(self): + """3 pools sharing tokens, 10 days each.""" + np.random.seed(42) + dates = pd.date_range("2025-12-01", periods=10, freq="D") + rows = [] + pool_configs = [ + ("0xpool_a_full_id_padding_to_66_chars_aaaaaaaaaaaaaaaaaaaaaaaaa", + "MAINNET", "ETH,USDC", 0.003), + ("0xpool_b_full_id_padding_to_66_chars_bbbbbbbbbbbbbbbbbbbbbbbbb", + "MAINNET", "ETH,AAVE", 0.003), + ("0xpool_c_full_id_padding_to_66_chars_ccccccccccccccccccccccccc", + "ARBITRUM", "AAVE,USDC", 0.01), + ] + for full_id, chain, tokens, fee in pool_configs: + for di, date in enumerate(dates): + tvl = 12.0 + 0.05 * np.sin(2 * np.pi * di / 7) + vol = 9.0 + 0.3 * np.random.randn() + rows.append({ + "pool_id": full_id, + "chain": chain, + "date": date, + "log_volume": vol, + "log_tvl": tvl, + "log_tvl_lag1": tvl - 0.01, + "volatility": 0.4, + "log_fee": np.log(fee), + "swap_fee": fee, + "tokens": tokens, + }) + return pd.DataFrame(rows) + + @pytest.fixture + def three_pool_matched(self, three_pool_panel): + """Minimal matched dict for 3 pools (no grid needed for x_obs tests).""" + matched = {} + for full_id in three_pool_panel["pool_id"].unique(): + prefix = full_id[:16] + rows = three_pool_panel[three_pool_panel["pool_id"] == full_id].copy() + rows = rows.reset_index(drop=True) + matched[prefix] = { + "panel": rows, + "pool_id": full_id, + "chain": rows.iloc[0]["chain"], + "fee": float(np.exp(rows.iloc[0]["log_fee"])), + "tokens": rows.iloc[0]["tokens"], + "weights": [0.5, 0.5], + } + return matched + + def test_build_cross_pool_x_obs_shape(self, three_pool_matched): + from quantammsim.calibration.pool_data import ( + K_OBS_CROSS, build_cross_pool_x_obs, + ) + + pid = sorted(three_pool_matched.keys())[0] + entry = three_pool_matched[pid] + x = build_cross_pool_x_obs(entry["panel"], three_pool_matched, pid) + # Drops first day → n_obs - 1 rows, K_OBS_CROSS=7 columns + assert x.shape[1] == K_OBS_CROSS + assert x.shape[0] == len(entry["panel"]) - 1 + + def test_first_four_cols_match_reduced(self, three_pool_matched): + from quantammsim.calibration.pool_data import ( + build_cross_pool_x_obs, build_x_obs, + ) + + pid = sorted(three_pool_matched.keys())[0] + entry = three_pool_matched[pid] + x_cross = build_cross_pool_x_obs(entry["panel"], three_pool_matched, pid) + x_reduced = build_x_obs(entry["panel"], reduced=True) + # First 4 columns should match (after dropping first row) + np.testing.assert_allclose(x_cross[:, :4], x_reduced[1:, :4]) + + def test_cross_vol_token_a_excludes_self(self, three_pool_matched): + """Peer average for token A excludes pool i itself.""" + from quantammsim.calibration.pool_data import build_cross_pool_x_obs + + pool_ids = sorted(three_pool_matched.keys()) + pid_a = pool_ids[0] # ETH,USDC + pid_b = pool_ids[1] # ETH,AAVE — shares ETH with pool_a + + x_a = build_cross_pool_x_obs( + three_pool_matched[pid_a]["panel"], + three_pool_matched, pid_a, + ) + # Column 4 = cross_vol_token_a (ETH peers excl self) + # Pool b also has ETH, so pool_a's cross_vol_token_a should use pool_b's volume + panel_b = three_pool_matched[pid_b]["panel"] + log_vol_b_lagged = panel_b["log_volume"].values[:-1] # lag by 1 + np.testing.assert_allclose(x_a[:, 4], log_vol_b_lagged, rtol=1e-6) + + def test_cross_vol_is_lagged(self, three_pool_matched): + """Features at day t use log_volume at day t-1.""" + from quantammsim.calibration.pool_data import build_cross_pool_x_obs + + pool_ids = sorted(three_pool_matched.keys()) + pid = pool_ids[0] + x = build_cross_pool_x_obs( + three_pool_matched[pid]["panel"], + three_pool_matched, pid, + ) + # x has n_obs - 1 rows (first day dropped) + # Row 0 of x corresponds to day 1 and should use day 0 volume + assert x.shape[0] > 0 + + def test_cross_vol_nan_free_after_first_day(self, three_pool_matched): + from quantammsim.calibration.pool_data import build_cross_pool_x_obs + + for pid in three_pool_matched: + x = build_cross_pool_x_obs( + three_pool_matched[pid]["panel"], + three_pool_matched, pid, + ) + assert not np.any(np.isnan(x)), f"NaNs in cross-pool x_obs for {pid}" + + def test_exclude_pool_changes_features(self, three_pool_matched): + """exclude_pool removes that pool from peer averages.""" + from quantammsim.calibration.pool_data import build_cross_pool_x_obs + + pool_ids = sorted(three_pool_matched.keys()) + pid = pool_ids[0] # ETH,USDC + + x_normal = build_cross_pool_x_obs( + three_pool_matched[pid]["panel"], + three_pool_matched, pid, + ) + x_excluded = build_cross_pool_x_obs( + three_pool_matched[pid]["panel"], + three_pool_matched, pid, + exclude_pool=pool_ids[1], # exclude the ETH peer + ) + # Chain feature (col 6) may change too; token A col (4) definitely changes + # since pool_b is the only ETH peer + # With only peer excluded, cross_vol_token_a should be NaN→fallback + assert not np.allclose(x_normal[:, 4], x_excluded[:, 4]) + + def test_single_token_pool_fallback(self): + """When a token appears in only one pool, its cross_vol uses global mean.""" + from quantammsim.calibration.pool_data import build_cross_pool_x_obs + + np.random.seed(42) + dates = pd.date_range("2025-12-01", periods=5, freq="D") + rows = [] + # Pool A: LINK,USDC — LINK is unique + for di, date in enumerate(dates): + rows.append({ + "pool_id": "0xsolo_link_pool_aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", + "chain": "MAINNET", "date": date, + "log_volume": 9.0 + 0.1 * di, + "log_tvl_lag1": 12.0, "volatility": 0.4, + "log_fee": np.log(0.003), "tokens": "LINK,USDC", + }) + # Pool B: ETH,USDC + for di, date in enumerate(dates): + rows.append({ + "pool_id": "0xpeer_eth_pool__bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb", + "chain": "MAINNET", "date": date, + "log_volume": 10.0 + 0.1 * di, + "log_tvl_lag1": 13.0, "volatility": 0.4, + "log_fee": np.log(0.003), "tokens": "ETH,USDC", + }) + panel = pd.DataFrame(rows) + + matched = {} + for full_id in panel["pool_id"].unique(): + prefix = full_id[:16] + sub = panel[panel["pool_id"] == full_id].reset_index(drop=True) + matched[prefix] = { + "panel": sub, "pool_id": full_id, + "chain": sub.iloc[0]["chain"], + "fee": float(np.exp(sub.iloc[0]["log_fee"])), + "tokens": sub.iloc[0]["tokens"], "weights": [0.5, 0.5], + } + + pid_link = [p for p in matched if matched[p]["tokens"] == "LINK,USDC"][0] + x = build_cross_pool_x_obs(panel[panel["pool_id"] == matched[pid_link]["pool_id"]].reset_index(drop=True), + matched, pid_link) + # LINK has no peers → col 4 should be a fallback (global mean), not NaN + assert not np.any(np.isnan(x[:, 4])) diff --git a/tests/calibration/test_pool_data_volatility.py b/tests/calibration/test_pool_data_volatility.py new file mode 100644 index 0000000..d6e2dd1 --- /dev/null +++ b/tests/calibration/test_pool_data_volatility.py @@ -0,0 +1,453 @@ +"""Tests for Binance volatility and TOKEN_MAP in quantammsim.calibration.pool_data.""" + +import numpy as np +import pandas as pd +import pytest + +from tests.calibration.conftest import POOL_IDS_FULL + + +class TestTokenMap: + """Test TOKEN_MAP resolves Balancer tokens to Binance symbols correctly.""" + + def test_wrapped_native(self): + from quantammsim.calibration.pool_data import _resolve_binance_symbol + + assert _resolve_binance_symbol("WBTC") == "BTC" + assert _resolve_binance_symbol("WETH") == "ETH" + assert _resolve_binance_symbol("cbBTC") == "BTC" + + def test_lst_to_underlying(self): + from quantammsim.calibration.pool_data import _resolve_binance_symbol + + assert _resolve_binance_symbol("wstETH") == "ETH" + assert _resolve_binance_symbol("stETH") == "ETH" + assert _resolve_binance_symbol("rETH") == "ETH" + assert _resolve_binance_symbol("cbETH") == "ETH" + + def test_vault_tokens(self): + from quantammsim.calibration.pool_data import _resolve_binance_symbol + + assert _resolve_binance_symbol("waEthLidoWETH") == "ETH" + assert _resolve_binance_symbol("waEthLidowstETH") == "ETH" + assert _resolve_binance_symbol("waBasWETH") == "ETH" + assert _resolve_binance_symbol("waGnowstETH") == "ETH" + assert _resolve_binance_symbol("waGnoGNO") == "GNO" + + def test_stablecoins_map_to_usdc(self): + from quantammsim.calibration.pool_data import _resolve_binance_symbol + + for stable in ["DAI", "WXDAI", "sDAI", "USDT", "DOLA", "scUSD", + "USDC.e", "USDbC", "waBasUSDC"]: + assert _resolve_binance_symbol(stable) == "USDC", ( + f"{stable} should map to USDC" + ) + + def test_matic_variants(self): + from quantammsim.calibration.pool_data import _resolve_binance_symbol + + assert _resolve_binance_symbol("wPOL") == "POL" + assert _resolve_binance_symbol("WMATIC") == "POL" + assert _resolve_binance_symbol("MATIC") == "POL" + + def test_sonic_variants(self): + from quantammsim.calibration.pool_data import _resolve_binance_symbol + + assert _resolve_binance_symbol("wS") == "S" + assert _resolve_binance_symbol("stS") == "S" + + def test_passthrough_unknown(self): + from quantammsim.calibration.pool_data import _resolve_binance_symbol + + assert _resolve_binance_symbol("AAVE") == "AAVE" + assert _resolve_binance_symbol("LINK") == "LINK" + assert _resolve_binance_symbol("SNX") == "SNX" + + def test_jitosol(self): + from quantammsim.calibration.pool_data import _resolve_binance_symbol + + assert _resolve_binance_symbol("JitoSOL") == "SOL" + + +class TestGetAssetType: + """Test _get_asset_type classification.""" + + def test_stablecoins(self): + from quantammsim.calibration.pool_data import _get_asset_type + + for tok in ["USDC", "USDT", "DAI", "WXDAI", "sDAI", "DOLA", "scUSD"]: + assert _get_asset_type(tok, {}) == 0, f"{tok} should be stable (0)" + + def test_native_lst(self): + from quantammsim.calibration.pool_data import _get_asset_type + + for tok in ["WETH", "ETH", "wstETH", "WBTC", "BTC", "GNO", "S", "wS"]: + assert _get_asset_type(tok, {}) == 1, f"{tok} should be native/LST (1)" + + def test_volatile(self): + from quantammsim.calibration.pool_data import _get_asset_type + + for tok in ["AAVE", "LINK", "SNX", "CRV", "COMP"]: + assert _get_asset_type(tok, {}) == 2, f"{tok} should be volatile (2)" + + def test_mcap_override(self): + from quantammsim.calibration.pool_data import _get_asset_type + + mcaps = {"AAVE": {"asset_type": "stable", "mcap_usd": 1e9}} + assert _get_asset_type("AAVE", mcaps) == 0 # overridden to stable + + +class TestComputeBinancePairVolatility: + """Test compute_binance_pair_volatility with synthetic Binance-like data.""" + + @pytest.fixture + def fake_binance_dir(self, tmp_path): + """Create fake Binance minute parquets for ETH and AAVE.""" + np.random.seed(42) + n_minutes = 24 * 60 * 7 # 7 days of minute data + base_ts = int(pd.Timestamp("2025-01-01").timestamp() * 1000) + unix = base_ts + np.arange(n_minutes) * 60_000 + + # ETH: geometric brownian motion starting at 3000 + eth_log_returns = np.random.normal(0, 0.0005, n_minutes) + eth_prices = 3000.0 * np.exp(np.cumsum(eth_log_returns)) + eth_df = pd.DataFrame({"unix": unix, "close": eth_prices}) + eth_df.to_parquet(tmp_path / "ETH_USD.parquet", index=False) + + # AAVE: correlated with ETH but with higher vol + aave_log_returns = 0.6 * eth_log_returns + 0.4 * np.random.normal( + 0, 0.001, n_minutes + ) + aave_prices = 200.0 * np.exp(np.cumsum(aave_log_returns)) + aave_df = pd.DataFrame({"unix": unix, "close": aave_prices}) + aave_df.to_parquet(tmp_path / "AAVE_USD.parquet", index=False) + + # USDC: constant at $1 (stablecoin proxy) + usdc_df = pd.DataFrame({ + "unix": unix, "close": np.ones(n_minutes), + }) + usdc_df.to_parquet(tmp_path / "USDC_USD.parquet", index=False) + + return str(tmp_path) + + def test_pinned_volatility_values(self, fake_binance_dir): + """Exact pinned values with seed(42).""" + from quantammsim.calibration.pool_data import compute_binance_pair_volatility + + vol = compute_binance_pair_volatility("WETH", "AAVE", fake_binance_dir) + expected = np.array([ + 0.27103676, 0.23068148, 0.43763073, 0.35174542, + 0.26827274, 0.35256874, 0.27833725, + ]) + np.testing.assert_allclose(vol.values, expected, rtol=1e-4) + + def test_exactly_seven_days(self, fake_binance_dir): + from quantammsim.calibration.pool_data import compute_binance_pair_volatility + + vol = compute_binance_pair_volatility("WETH", "AAVE", fake_binance_dir) + assert len(vol) == 7 + + def test_values_positive(self, fake_binance_dir): + from quantammsim.calibration.pool_data import compute_binance_pair_volatility + + vol = compute_binance_pair_volatility("WETH", "AAVE", fake_binance_dir) + assert (vol > 0).all() + + def test_pinned_median(self, fake_binance_dir): + from quantammsim.calibration.pool_data import compute_binance_pair_volatility + + vol = compute_binance_pair_volatility("WETH", "AAVE", fake_binance_dir) + np.testing.assert_allclose(vol.median(), 0.2783, atol=0.001) + + def test_token_order_invariance(self, fake_binance_dir): + """vol(A,B) should equal vol(B,A) — log returns of reciprocal have same std.""" + from quantammsim.calibration.pool_data import compute_binance_pair_volatility + + vol_ab = compute_binance_pair_volatility("WETH", "AAVE", fake_binance_dir) + vol_ba = compute_binance_pair_volatility("AAVE", "WETH", fake_binance_dir) + np.testing.assert_allclose(vol_ab.values, vol_ba.values, rtol=1e-5) + + def test_stable_vs_volatile_uses_single_asset(self, fake_binance_dir): + """ETH/USDC should use just ETH price — verify against hand-computed ETH vol.""" + import os + + from quantammsim.calibration.pool_data import compute_binance_pair_volatility + + vol_pair = compute_binance_pair_volatility("WETH", "USDC", fake_binance_dir) + + # Hand-compute ETH-only vol for ground-truth comparison + eth = pd.read_parquet(os.path.join(fake_binance_dir, "ETH_USD.parquet")) + eth_ts = pd.DataFrame( + {"ratio": eth["close"].values}, + index=pd.to_datetime(eth["unix"].values, unit="ms", utc=True), + ) + hourly = eth_ts.resample("1h").last().dropna() + hourly["log_return"] = np.log(hourly["ratio"] / hourly["ratio"].shift(1)) + hourly = hourly.dropna() + hourly["date"] = hourly.index.date + daily_std = hourly.groupby("date")["log_return"].std() + expected = (daily_std * np.sqrt(24 * 365)).dropna() + expected = expected[expected > 0] + + np.testing.assert_allclose(vol_pair.values, expected.values, rtol=1e-5) + + def test_stable_stable_returns_none(self, fake_binance_dir): + from quantammsim.calibration.pool_data import compute_binance_pair_volatility + + vol = compute_binance_pair_volatility("USDC", "DAI", fake_binance_dir) + assert vol is None + + def test_same_underlying_returns_none(self, fake_binance_dir): + from quantammsim.calibration.pool_data import compute_binance_pair_volatility + + # WETH and wstETH both map to ETH + vol = compute_binance_pair_volatility("WETH", "wstETH", fake_binance_dir) + assert vol is None + + def test_missing_data_returns_none(self, fake_binance_dir): + from quantammsim.calibration.pool_data import compute_binance_pair_volatility + + vol = compute_binance_pair_volatility("WETH", "MAGIC", fake_binance_dir) + assert vol is None + + def test_daily_index_type(self, fake_binance_dir): + """Index should be datetime.date objects.""" + from quantammsim.calibration.pool_data import compute_binance_pair_volatility + import datetime + + vol = compute_binance_pair_volatility("WETH", "AAVE", fake_binance_dir) + for d in vol.index: + assert isinstance(d, datetime.date) + + def test_stable_a_volatile_b_uses_reciprocal(self, fake_binance_dir): + """DAI/WETH should use 1/ETH, giving same vol as WETH/DAI.""" + from quantammsim.calibration.pool_data import compute_binance_pair_volatility + + vol_forward = compute_binance_pair_volatility("WETH", "USDC", fake_binance_dir) + vol_reverse = compute_binance_pair_volatility("DAI", "WETH", fake_binance_dir) + # Both should be ETH vol (log returns of X and 1/X have same std) + np.testing.assert_allclose( + vol_forward.values, vol_reverse.values, rtol=1e-5, + ) + + +class TestReplacePanelVolatility: + """Test replace_panel_volatility_with_binance.""" + + @pytest.fixture + def fake_binance_dir(self, tmp_path): + """Minimal fake Binance data for 3 days.""" + np.random.seed(42) + n_minutes = 24 * 60 * 3 + base_ts = int(pd.Timestamp("2025-12-01").timestamp() * 1000) + unix = base_ts + np.arange(n_minutes) * 60_000 + + eth_prices = 3000.0 + np.cumsum(np.random.normal(0, 1.0, n_minutes)) + eth_df = pd.DataFrame({"unix": unix, "close": eth_prices}) + eth_df.to_parquet(tmp_path / "ETH_USD.parquet", index=False) + + btc_prices = 60000.0 + np.cumsum(np.random.normal(0, 5.0, n_minutes)) + btc_df = pd.DataFrame({"unix": unix, "close": btc_prices}) + btc_df.to_parquet(tmp_path / "BTC_USD.parquet", index=False) + + aave_prices = 200.0 + np.cumsum(np.random.normal(0, 0.5, n_minutes)) + aave_df = pd.DataFrame({"unix": unix, "close": aave_prices}) + aave_df.to_parquet(tmp_path / "AAVE_USD.parquet", index=False) + + return str(tmp_path) + + def test_does_not_modify_input(self, synthetic_panel, fake_binance_dir): + from quantammsim.calibration.pool_data import replace_panel_volatility_with_binance + + original_vol = synthetic_panel["volatility"].copy() + replace_panel_volatility_with_binance( + synthetic_panel, fake_binance_dir + ) + pd.testing.assert_series_equal(synthetic_panel["volatility"], original_vol) + + def test_no_nans_introduced(self, synthetic_panel, fake_binance_dir): + """Pools without Binance data should keep original volatility, not NaN.""" + from quantammsim.calibration.pool_data import replace_panel_volatility_with_binance + + result = replace_panel_volatility_with_binance( + synthetic_panel, fake_binance_dir + ) + assert result["volatility"].notna().all() + + def test_volatility_actually_changes(self, synthetic_panel, fake_binance_dir): + """At least some volatility values should differ after replacement.""" + from quantammsim.calibration.pool_data import replace_panel_volatility_with_binance + + original_vol = synthetic_panel["volatility"].values.copy() + result = replace_panel_volatility_with_binance( + synthetic_panel, fake_binance_dir + ) + # At least one pool has BTC,ETH or AAVE,ETH — both have Binance data, + # and dates overlap (panel starts 2025-12-01, fake data starts 2025-12-01). + n_changed = (result["volatility"].values != original_vol).sum() + assert n_changed > 0, "No volatility values were replaced" + + def test_replaced_values_are_positive(self, synthetic_panel, fake_binance_dir): + """Replaced volatility values must be positive.""" + from quantammsim.calibration.pool_data import replace_panel_volatility_with_binance + + result = replace_panel_volatility_with_binance( + synthetic_panel, fake_binance_dir + ) + assert (result["volatility"] > 0).all() + + def test_all_columns_preserved(self, synthetic_panel, fake_binance_dir): + """Output should have all original columns.""" + from quantammsim.calibration.pool_data import replace_panel_volatility_with_binance + + result = replace_panel_volatility_with_binance( + synthetic_panel, fake_binance_dir + ) + for col in synthetic_panel.columns: + assert col in result.columns + + def test_row_count_preserved(self, synthetic_panel, fake_binance_dir): + """Output should have the same number of rows.""" + from quantammsim.calibration.pool_data import replace_panel_volatility_with_binance + + result = replace_panel_volatility_with_binance( + synthetic_panel, fake_binance_dir + ) + assert len(result) == len(synthetic_panel) + + def test_replaced_values_match_binance_computation( + self, synthetic_panel, fake_binance_dir + ): + """Replaced vol values must equal compute_binance_pair_volatility exactly.""" + from quantammsim.calibration.pool_data import ( + compute_binance_pair_volatility, + replace_panel_volatility_with_binance, + ) + + result = replace_panel_volatility_with_binance( + synthetic_panel, fake_binance_dir + ) + + # BTC/ETH pool — compute expected vol independently + vol_btc_eth = compute_binance_pair_volatility("BTC", "ETH", fake_binance_dir) + assert vol_btc_eth is not None, "BTC/ETH vol should be computable" + vol_dict = vol_btc_eth.to_dict() + + pool0 = result[result["tokens"] == "BTC,ETH"].copy() + pool0_dates = pd.to_datetime(pool0["date"]).dt.date + matched_mask = pool0_dates.isin(vol_dict.keys()).values + matched = pool0[matched_mask] + assert len(matched) > 0, "No date overlap between panel and Binance data" + + for _, row in matched.iterrows(): + d = pd.to_datetime(row["date"]).date() + np.testing.assert_allclose( + row["volatility"], vol_dict[d], rtol=1e-6, + err_msg=f"BTC/ETH vol mismatch on {d}", + ) + + +class TestBuildPoolAttributeValues: + """Test build_pool_attributes returns correct numerical values, not just names.""" + + def _make_matched(self, synthetic_daily_grid, synthetic_panel, tmp_path): + from quantammsim.calibration.pool_data import match_grids_to_panel + from tests.calibration.conftest import POOL_PREFIXES + + grid_dir = tmp_path / "grids" + grid_dir.mkdir() + for prefix in POOL_PREFIXES: + synthetic_daily_grid.to_parquet( + grid_dir / f"{prefix}_daily.parquet", index=False + ) + return match_grids_to_panel(str(grid_dir), synthetic_panel) + + def test_chain_dummy_values( + self, synthetic_daily_grid, synthetic_panel, tmp_path + ): + """MAINNET pool has chain_MAINNET=1, ARBITRUM pool has chain_MAINNET=0.""" + from quantammsim.calibration.pool_data import build_pool_attributes + + matched = self._make_matched( + synthetic_daily_grid, synthetic_panel, tmp_path + ) + X_attr, attr_names, pool_ids = build_pool_attributes(matched) + + # ARBITRUM is reference (alphabetically first), MAINNET gets a dummy + chain_idx = attr_names.index("chain_MAINNET") + for i, pid in enumerate(pool_ids): + if matched[pid]["chain"] == "MAINNET": + assert X_attr[i, chain_idx] == 1.0 + else: + assert X_attr[i, chain_idx] == 0.0 + + def test_log_fee_values( + self, synthetic_daily_grid, synthetic_panel, tmp_path + ): + """log_fee should match panel values.""" + from quantammsim.calibration.pool_data import build_pool_attributes + + matched = self._make_matched( + synthetic_daily_grid, synthetic_panel, tmp_path + ) + X_attr, attr_names, pool_ids = build_pool_attributes(matched) + + fee_idx = attr_names.index("log_fee") + for i, pid in enumerate(pool_ids): + expected = np.log(matched[pid]["fee"]) + np.testing.assert_allclose(X_attr[i, fee_idx], expected, rtol=1e-3) + + def test_same_asset_type_for_btc_eth( + self, synthetic_daily_grid, synthetic_panel, tmp_path + ): + """BTC,ETH pool: both native/LST → same_asset_type=1.""" + from quantammsim.calibration.pool_data import build_pool_attributes + + matched = self._make_matched( + synthetic_daily_grid, synthetic_panel, tmp_path + ) + X_attr, attr_names, pool_ids = build_pool_attributes(matched) + + sat_idx = attr_names.index("same_asset_type") + for i, pid in enumerate(pool_ids): + if matched[pid]["tokens"] == "BTC,ETH": + assert X_attr[i, sat_idx] == 1.0 + elif matched[pid]["tokens"] == "AAVE,ETH": + # AAVE=volatile(2), ETH=native(1) → different + assert X_attr[i, sat_idx] == 0.0 + + def test_pinned_attribute_values( + self, synthetic_daily_grid, synthetic_panel, tmp_path + ): + """Pinned X_attr values for the two synthetic pools.""" + from quantammsim.calibration.pool_data import build_pool_attributes + + matched = self._make_matched( + synthetic_daily_grid, synthetic_panel, tmp_path + ) + X_attr, attr_names, pool_ids = build_pool_attributes(matched) + + # Pool 0 (0xaaaa = MAINNET, BTC/ETH, fee=0.003) + p0_idx = pool_ids.index("0xaaaa11112222aa") + np.testing.assert_allclose(X_attr[p0_idx, 0], 1.0) # chain_MAINNET + np.testing.assert_allclose( + X_attr[p0_idx, attr_names.index("log_fee")], np.log(0.003), rtol=1e-3 + ) + + # Pool 1 (0xbbbb = ARBITRUM, AAVE/ETH, fee=0.01) + p1_idx = pool_ids.index("0xbbbb33334444bb") + np.testing.assert_allclose(X_attr[p1_idx, 0], 0.0) # chain_MAINNET=0 + np.testing.assert_allclose( + X_attr[p1_idx, attr_names.index("log_fee")], np.log(0.01), rtol=1e-3 + ) + + def test_no_nans( + self, synthetic_daily_grid, synthetic_panel, tmp_path + ): + from quantammsim.calibration.pool_data import build_pool_attributes + + matched = self._make_matched( + synthetic_daily_grid, synthetic_panel, tmp_path + ) + X_attr, _, _ = build_pool_attributes(matched) + assert not np.any(np.isnan(X_attr)) diff --git a/tests/calibration/test_regression_pins.py b/tests/calibration/test_regression_pins.py new file mode 100644 index 0000000..877c23f --- /dev/null +++ b/tests/calibration/test_regression_pins.py @@ -0,0 +1,449 @@ +"""Pinned numerical regression tests for the calibration pipeline. + +These tests pin exact numerical values computed from the synthetic fixtures. +They protect against silent computation errors during refactoring — a test +that checks only shapes/signs would still pass if e.g. an index is off by +one in unpack, or a sign is flipped in regularization. + +All pinned values were computed with: + - Python 3.9, JAX 0.4.30, numpy seed 42 + - Synthetic fixtures from conftest.py (N_DAYS=15, 2 pools) +""" + +import os +import tempfile + +import jax +import jax.numpy as jnp +import numpy as np +import pandas as pd +import pytest + +from tests.calibration.conftest import ( + CADENCES, + GAS_COSTS, + K_OBS, + N_DAYS, + POOL_IDS_FULL, + POOL_PREFIXES, +) + +from quantammsim.calibration.grid_interpolation import ( + interpolate_pool_daily, + precompute_pool_coeffs_daily, +) +from quantammsim.calibration.loss import noise_volume, pack_params, pool_loss +from quantammsim.calibration.per_pool_fit import fit_all_pools, fit_single_pool +from quantammsim.calibration.pool_data import build_x_obs, match_grids_to_panel + + +# ── Helpers ──────────────────────────────────────────────────────────────── + + +@pytest.fixture +def matched_data(synthetic_daily_grid, synthetic_panel): + """Build matched data dict by writing temp parquets for both pools.""" + tmpdir = tempfile.mkdtemp() + for prefix in POOL_PREFIXES: + path = os.path.join(tmpdir, f"{prefix}_daily.parquet") + synthetic_daily_grid.to_parquet(path) + matched = match_grids_to_panel(tmpdir, synthetic_panel) + yield matched + import shutil + shutil.rmtree(tmpdir) + + +@pytest.fixture +def pool0_inputs(matched_data): + """x_obs, y_obs, day_indices, coeffs for pool 0.""" + entry = matched_data[POOL_PREFIXES[0]] + panel = entry["panel"] + x_obs = build_x_obs(panel) + y_obs = panel["log_volume"].values.astype(float) + day_indices = np.array(entry["day_indices"]) + return entry["coeffs"], x_obs, y_obs, day_indices + + +def _known_params(): + """Standard test params: cadence=12, gas=$1, noise intercept=8.""" + noise_coeffs = np.zeros(K_OBS) + noise_coeffs[0] = 8.0 + return pack_params(np.log(12.0), np.log(1.0), jnp.array(noise_coeffs)) + + +# ── Grid interpolation pins ─────────────────────────────────────────────── + + +class TestInterpolationPins: + """Verify interpolation exactness at grid knot points.""" + + def test_interpolation_exact_at_all_knots(self, synthetic_pool_coeffs): + """Interpolation at grid knot points must exactly reproduce grid values.""" + coeffs = synthetic_pool_coeffs + for ci, cad in enumerate(CADENCES): + for gi, gas in enumerate(GAS_COSTS): + log_cad = jnp.log(cad) + v_arb = interpolate_pool_daily(coeffs, log_cad, jnp.array(gas)) + grid_vals = coeffs.values[ci, gi, :] + np.testing.assert_allclose( + v_arb, grid_vals, atol=1e-4, + err_msg=f"Mismatch at cad={cad}, gas={gas}", + ) + + def test_interpolation_midpoint_value(self, synthetic_pool_coeffs): + """Pin interpolated value at a known mid-grid point.""" + v_arb = interpolate_pool_daily( + synthetic_pool_coeffs, jnp.log(6.0), jnp.array(0.5) + ) + # Pinned from JAX 0.4.30, seed 42 + assert v_arb.shape == (N_DAYS,) + np.testing.assert_allclose(float(v_arb[0]), 6579.6309, rtol=1e-4) + np.testing.assert_allclose(float(jnp.mean(v_arb)), 6621.3186, rtol=1e-4) + + def test_interpolation_monotone_in_cadence(self, synthetic_pool_coeffs): + """V_arb should decrease as cadence increases (at fixed gas).""" + coeffs = synthetic_pool_coeffs + gas = jnp.array(1.0) + cads = [1.0, 6.0, 12.0, 30.0, 60.0] + means = [ + float(jnp.mean(interpolate_pool_daily(coeffs, jnp.log(c), gas))) + for c in cads + ] + for i in range(len(means) - 1): + assert means[i] > means[i + 1], ( + f"V_arb not decreasing: cad={cads[i]}->{cads[i+1]}, " + f"mean={means[i]:.1f}->{means[i+1]:.1f}" + ) + + def test_interpolation_monotone_in_gas(self, synthetic_pool_coeffs): + """V_arb should decrease as gas cost increases (at fixed cadence).""" + coeffs = synthetic_pool_coeffs + log_cad = jnp.log(12.0) + gases = [0.0, 0.5, 1.0, 3.0, 5.0] + means = [ + float(jnp.mean(interpolate_pool_daily(coeffs, log_cad, jnp.array(g)))) + for g in gases + ] + for i in range(len(means) - 1): + assert means[i] > means[i + 1], ( + f"V_arb not decreasing: gas={gases[i]}->{gases[i+1]}, " + f"mean={means[i]:.1f}->{means[i+1]:.1f}" + ) + + def test_interpolation_differentiable(self, synthetic_pool_coeffs): + """Gradient of interpolated V_arb w.r.t. log_cadence must be finite.""" + coeffs = synthetic_pool_coeffs + + def f(log_cad): + return jnp.sum(interpolate_pool_daily(coeffs, log_cad, jnp.array(1.0))) + + grad_val = jax.grad(f)(jnp.log(12.0)) + assert jnp.isfinite(grad_val), f"Non-finite gradient: {grad_val}" + # Gradient should be negative (more cadence → less arb) + assert float(grad_val) < 0, f"Expected negative gradient, got {grad_val}" + + +# ── Loss function pins ───────────────────────────────────────────────────── + + +class TestLossPins: + """Pin exact loss values and gradients at known parameter points.""" + + def test_loss_value_pinned(self, synthetic_pool_coeffs, pool0_inputs): + """Pin the exact loss value at known params on synthetic data.""" + coeffs, x_obs, _, day_indices = pool0_inputs + params = _known_params() + y_obs = jnp.ones(x_obs.shape[0]) * 9.0 + day_indices_j = jnp.arange(x_obs.shape[0]) % N_DAYS + + loss = pool_loss(params, coeffs, jnp.array(x_obs), y_obs, day_indices_j) + # Pinned: 0.001726984975292 (JAX 0.4.30, seed 42) + np.testing.assert_allclose(float(loss), 0.001727, rtol=1e-3) + + def test_gradient_pinned(self, synthetic_pool_coeffs, pool0_inputs): + """Pin gradient values at known params.""" + coeffs, x_obs, _, day_indices = pool0_inputs + params = _known_params() + y_obs = jnp.ones(x_obs.shape[0]) * 9.0 + day_indices_j = jnp.arange(x_obs.shape[0]) % N_DAYS + + grad_fn = jax.grad(pool_loss) + grad = grad_fn(params, coeffs, jnp.array(x_obs), y_obs, day_indices_j) + grad_np = np.array(grad) + + # All gradients must be finite + assert np.all(np.isfinite(grad_np)), f"Non-finite gradients: {grad_np}" + + # Pin signs of key gradient components + # grad[0] = d_loss/d_log_cadence (negative: increasing cadence decreases V_arb, + # pushing log(V_arb + V_noise) away from y_obs=9.0) + assert grad_np[0] < 0, f"Expected negative cadence grad, got {grad_np[0]}" + # grad[1] = d_loss/d_log_gas (negative: same effect via gas) + assert grad_np[1] < 0, f"Expected negative gas grad, got {grad_np[1]}" + + # Pin magnitudes (rtol=0.01 to allow platform variance) + expected_grad = np.array([ + -0.000223, -0.000362, -0.000264, -0.003138, + 0.001071, 0.012854, 0.018232, -0.006220, + 0.013421, -0.016786, + ]) + np.testing.assert_allclose(grad_np, expected_grad, rtol=0.05, atol=1e-5) + + def test_loss_increases_with_bad_params(self, synthetic_pool_coeffs, pool0_inputs): + """Loss with wildly wrong noise intercept >> loss with good params.""" + coeffs, x_obs, _, _ = pool0_inputs + y_obs = jnp.ones(x_obs.shape[0]) * 9.0 + day_indices_j = jnp.arange(x_obs.shape[0]) % N_DAYS + x_obs_j = jnp.array(x_obs) + + params_good = _known_params() + noise_bad = np.zeros(K_OBS) + noise_bad[0] = 20.0 + params_bad = pack_params(np.log(12.0), np.log(1.0), jnp.array(noise_bad)) + + loss_good = float(pool_loss(params_good, coeffs, x_obs_j, y_obs, day_indices_j)) + loss_bad = float(pool_loss(params_bad, coeffs, x_obs_j, y_obs, day_indices_j)) + + assert loss_bad > 100.0, f"Expected loss_bad > 100, got {loss_bad}" + assert loss_bad > loss_good * 1000, "Bad params should be >1000x worse" + + +# ── Noise volume pins ────────────────────────────────────────────────────── + + +class TestNoiseVolumePins: + def test_intercept_only_equals_exp(self, synthetic_x_obs): + """With intercept-only noise coeffs, V_noise = exp(intercept) exactly.""" + coeffs = np.zeros(K_OBS) + coeffs[0] = 8.0 + v_noise = noise_volume(jnp.array(coeffs), jnp.array(synthetic_x_obs)) + # x_obs column 0 is all 1.0 (intercept), so x_obs @ coeffs = 8.0 for all obs + np.testing.assert_allclose(v_noise, np.exp(8.0), rtol=1e-6) + + def test_tvl_coeff_creates_variation(self, synthetic_x_obs): + """With nonzero TVL coeff, V_noise varies across observations.""" + coeffs = np.zeros(K_OBS) + coeffs[0] = 5.0 + coeffs[1] = 1.0 # TVL coefficient + v_noise = noise_volume(jnp.array(coeffs), jnp.array(synthetic_x_obs)) + assert float(jnp.std(v_noise)) > 0, "Expected variation from TVL coeff" + + +# ── Per-pool fit pins ────────────────────────────────────────────────────── + + +class TestPerPoolFitPins: + """Pin per-pool optimizer convergence on synthetic data.""" + + def test_fit_single_pool_converges(self, pool0_inputs): + """fit_single_pool should converge on synthetic data.""" + coeffs, x_obs, y_obs, day_indices = pool0_inputs + result = fit_single_pool(coeffs, x_obs, y_obs, day_indices) + assert result["converged"], "fit_single_pool did not converge" + + def test_fit_single_pool_loss_pinned(self, pool0_inputs): + """Pin the converged loss value.""" + coeffs, x_obs, y_obs, day_indices = pool0_inputs + result = fit_single_pool(coeffs, x_obs, y_obs, day_indices) + # Pinned: 0.0723 (JAX 0.4.30, seed 42) + np.testing.assert_allclose(result["loss"], 0.0723, rtol=0.05) + + def test_fit_single_pool_cadence_pinned(self, pool0_inputs): + """Pin the converged cadence — should find ~1.27 min on synthetic data.""" + coeffs, x_obs, y_obs, day_indices = pool0_inputs + result = fit_single_pool(coeffs, x_obs, y_obs, day_indices) + # Pinned: 1.266 minutes + np.testing.assert_allclose(result["cadence_minutes"], 1.27, rtol=0.1) + # Cadence must be in valid range + assert 1.0 <= result["cadence_minutes"] <= 60.0 + + def test_fit_single_pool_loss_lower_than_init(self, pool0_inputs): + """Fitted loss must be lower than loss at initial guess.""" + from quantammsim.calibration.per_pool_fit import make_initial_guess + + coeffs, x_obs, y_obs, day_indices = pool0_inputs + init = make_initial_guess(x_obs, y_obs) + init_loss = float( + pool_loss( + jnp.array(init), + coeffs, + jnp.array(x_obs), + jnp.array(y_obs), + jnp.array(day_indices), + ) + ) + result = fit_single_pool(coeffs, x_obs, y_obs, day_indices) + assert result["loss"] < init_loss, ( + f"Fitted loss {result['loss']:.6f} >= init loss {init_loss:.6f}" + ) + + def test_fit_all_pools_returns_all(self, matched_data): + """fit_all_pools returns results for every matched pool.""" + results = fit_all_pools(matched_data) + assert set(results.keys()) == set(matched_data.keys()) + for pid, r in results.items(): + assert "loss" in r + assert "log_cadence" in r + assert "noise_coeffs" in r + assert len(r["noise_coeffs"]) == K_OBS + + +# ── Joint fit pins ───────────────────────────────────────────────────────── + + +class TestJointFitPins: + """Pin joint optimization behavior on synthetic data.""" + + def test_joint_ppn_loss_decreases(self, matched_data): + """Joint per_pool_noise loss must decrease from initialization.""" + from quantammsim.calibration.joint_fit import fit_joint + + result = fit_joint(matched_data, mode="per_pool_noise", maxiter=100) + assert result["loss"] < result["init_loss"], ( + f"Loss didn't decrease: {result['loss']:.6f} >= {result['init_loss']:.6f}" + ) + + def test_joint_ppn_loss_pinned(self, matched_data): + """Pin the joint per_pool_noise loss value.""" + from quantammsim.calibration.joint_fit import fit_joint + + result = fit_joint(matched_data, mode="per_pool_noise", maxiter=100) + # Pinned: 0.0406 (JAX 0.4.30, seed 42) + # Use wide tolerance since optimizer path may vary across platforms + assert result["loss"] < 0.10, f"Loss too high: {result['loss']}" + assert result["loss"] < result["init_loss"] + + def test_joint_shared_noise_loss_decreases(self, matched_data): + """Joint shared_noise loss must decrease from initialization.""" + from quantammsim.calibration.joint_fit import fit_joint + + result = fit_joint(matched_data, mode="shared_noise", maxiter=100) + assert result["loss"] < result["init_loss"], ( + f"Loss didn't decrease: {result['loss']:.6f} >= {result['init_loss']:.6f}" + ) + + def test_joint_predict_new_pool_at_zero_attrs(self, matched_data): + """Predict at zero attributes → output equals bias terms.""" + from quantammsim.calibration.joint_fit import fit_joint, predict_new_pool_joint + + result = fit_joint(matched_data, mode="per_pool_noise", maxiter=50) + x_attr = np.zeros(result["k_attr"]) + pred = predict_new_pool_joint(result, x_attr) + + # At zero attributes: log_cadence = bias_cad, log_gas = bias_gas + np.testing.assert_allclose( + pred["log_cadence"], result["bias_cad"], rtol=1e-10 + ) + np.testing.assert_allclose( + pred["log_gas"], result["bias_gas"], rtol=1e-10 + ) + assert pred["cadence_minutes"] > 0 + assert pred["gas_usd"] > 0 + + def test_joint_shared_noise_predict_includes_noise(self, matched_data): + """Shared noise mode prediction includes noise_coeffs.""" + from quantammsim.calibration.joint_fit import fit_joint, predict_new_pool_joint + + result = fit_joint(matched_data, mode="shared_noise", maxiter=50) + x_attr = np.zeros(result["k_attr"]) + pred = predict_new_pool_joint(result, x_attr) + + assert "noise_coeffs" in pred, "shared_noise predict should include noise_coeffs" + assert len(pred["noise_coeffs"]) == K_OBS + # At zero attributes: noise_coeffs = bias_noise + np.testing.assert_allclose( + pred["noise_coeffs"], result["bias_noise"], rtol=1e-10 + ) + + def test_joint_ppn_noise_shape(self, matched_data): + """Per-pool noise mode produces (n_pools, K_OBS) noise coefficients.""" + from quantammsim.calibration.joint_fit import fit_joint + + n_pools = len(matched_data) + result = fit_joint(matched_data, mode="per_pool_noise", maxiter=20) + assert result["noise_coeffs"].shape == (n_pools, K_OBS) + + def test_joint_warm_start_from_option_c(self, matched_data): + """Warm start from Option C should produce a viable starting point.""" + from quantammsim.calibration.joint_fit import fit_joint + + option_c = fit_all_pools(matched_data) + result = fit_joint( + matched_data, + mode="per_pool_noise", + maxiter=100, + init_from_option_c=option_c, + ) + # The warm start may have higher init_loss than cold start because + # the linear projection of per-pool params introduces approximation + # error. But the final loss should still decrease from init. + assert result["loss"] < result["init_loss"] + + +# ── Pack/unpack roundtrip pins ───────────────────────────────────────────── + + +class TestPackUnpackPins: + def test_per_pool_loss_pack_roundtrip_exact(self): + """pack → unpack must recover exact values.""" + from quantammsim.calibration.loss import unpack_params + + log_cad = 2.4849 + log_gas = -0.6932 + noise = jnp.array([8.1, -1.2, 3.4, -0.5, 0.7, -2.1, 0.3, 0.9]) + packed = pack_params(log_cad, log_gas, noise) + + lc, lg, nc = unpack_params(packed) + np.testing.assert_allclose(float(lc), log_cad, atol=1e-10) + np.testing.assert_allclose(float(lg), log_gas, atol=1e-10) + np.testing.assert_allclose(nc, noise, atol=1e-10) + + def test_joint_pack_roundtrip_ppn(self): + """Joint per_pool_noise pack → unpack roundtrip.""" + from quantammsim.calibration.joint_fit import ( + pack_joint_params, + unpack_joint_params, + ) + + k_attr = 5 + n_pools = 3 + bias_cad = 2.5 + bias_gas = -0.1 + W_cad = jnp.arange(k_attr, dtype=float) * 0.1 + W_gas = jnp.arange(k_attr, dtype=float) * -0.05 + noise = jnp.ones((n_pools, K_OBS)) * 0.3 + + packed = pack_joint_params(bias_cad, bias_gas, W_cad, W_gas, noise) + config = {"k_attr": k_attr, "n_pools": n_pools, "mode": "per_pool_noise"} + unpacked = unpack_joint_params(packed, config) + + np.testing.assert_allclose(float(unpacked["bias_cad"]), bias_cad, atol=1e-10) + np.testing.assert_allclose(float(unpacked["bias_gas"]), bias_gas, atol=1e-10) + np.testing.assert_allclose(unpacked["W_cad"], W_cad, atol=1e-10) + np.testing.assert_allclose(unpacked["W_gas"], W_gas, atol=1e-10) + np.testing.assert_allclose(unpacked["noise_coeffs"], noise, atol=1e-10) + + def test_joint_pack_roundtrip_shared(self): + """Joint shared_noise pack → unpack roundtrip.""" + from quantammsim.calibration.joint_fit import ( + pack_joint_params, + unpack_joint_params, + ) + + k_attr = 4 + bias_cad = 1.5 + bias_gas = 0.2 + W_cad = jnp.ones(k_attr) * 0.1 + W_gas = jnp.ones(k_attr) * -0.2 + # shared_noise: (1 + k_attr, K_OBS) where row 0 is bias_noise + noise = jnp.arange((1 + k_attr) * K_OBS, dtype=float).reshape( + 1 + k_attr, K_OBS + ) + + packed = pack_joint_params(bias_cad, bias_gas, W_cad, W_gas, noise) + config = {"k_attr": k_attr, "n_pools": 2, "mode": "shared_noise"} + unpacked = unpack_joint_params(packed, config) + + np.testing.assert_allclose(float(unpacked["bias_cad"]), bias_cad, atol=1e-10) + np.testing.assert_allclose(unpacked["bias_noise"], noise[0], atol=1e-10) + np.testing.assert_allclose(unpacked["W_noise"], noise[1:], atol=1e-10) diff --git a/tests/integration/test_baseline_values.py b/tests/integration/test_baseline_values.py index 96bb6ca..5b4dea3 100644 --- a/tests/integration/test_baseline_values.py +++ b/tests/integration/test_baseline_values.py @@ -89,8 +89,8 @@ "initial_weights_logits": jnp.array([0.0, 0.0]), }, "expected": { - "final_value": 1500094.138254407, - "return_pct": 50.00941382544071, + "final_value": 1489697.5771969256, + "return_pct": 48.969757719692566, "first_weights": [0.5, 0.5], "last_weights": [0.05000921, 0.94999079], }, @@ -116,8 +116,8 @@ "initial_weights_logits": jnp.array([0.0, 0.0]), }, "expected": { - "final_value": 1368731.4974473487, - "return_pct": 36.87314974473486, + "final_value": 1352951.4582811554, + "return_pct": 35.29514582811555, "first_weights": [0.5, 0.5], "last_weights": [0.05, 0.95], }, diff --git a/tests/integration/test_dynamic_gas_fees.py b/tests/integration/test_dynamic_gas_fees.py index 5870414..8eea4cd 100644 --- a/tests/integration/test_dynamic_gas_fees.py +++ b/tests/integration/test_dynamic_gas_fees.py @@ -9,6 +9,7 @@ import jax.numpy as jnp from pathlib import Path +from quantammsim.core_simulator.dynamic_inputs import DynamicInputFrames from quantammsim.runners.jax_runners import do_run_on_historic_data @@ -78,8 +79,12 @@ class TestDynamicGasAndFees: @pytest.mark.requires_data def test_run_with_gas_and_fees(self, base_fingerprint, base_params, gas_df, fees_df, data_root): """Test simulation with both gas costs and dynamic fees.""" + dynamic_input_frames = DynamicInputFrames(gas_cost=gas_df, fees=fees_df) result = do_run_on_historic_data( - base_fingerprint, base_params, root=data_root, gas_cost_df=gas_df, fees_df=fees_df + base_fingerprint, + base_params, + root=data_root, + dynamic_input_frames=dynamic_input_frames, ) assert result is not None @@ -92,8 +97,12 @@ def test_run_with_gas_and_fees(self, base_fingerprint, base_params, gas_df, fees @pytest.mark.requires_data def test_run_with_gas_only(self, base_fingerprint, base_params, gas_df, data_root): """Test simulation with gas costs only.""" + dynamic_input_frames = DynamicInputFrames(gas_cost=gas_df) result = do_run_on_historic_data( - base_fingerprint, base_params, root=data_root, gas_cost_df=gas_df + base_fingerprint, + base_params, + root=data_root, + dynamic_input_frames=dynamic_input_frames, ) assert result is not None @@ -104,8 +113,12 @@ def test_run_with_gas_only(self, base_fingerprint, base_params, gas_df, data_roo @pytest.mark.requires_data def test_run_with_fees_only(self, base_fingerprint, base_params, fees_df, data_root): """Test simulation with dynamic fees only.""" + dynamic_input_frames = DynamicInputFrames(fees=fees_df) result = do_run_on_historic_data( - base_fingerprint, base_params, root=data_root, fees_df=fees_df + base_fingerprint, + base_params, + root=data_root, + dynamic_input_frames=dynamic_input_frames, ) assert result is not None @@ -120,8 +133,12 @@ def test_gas_reduces_final_value(self, base_fingerprint, base_params, gas_df, da result_no_gas = do_run_on_historic_data(base_fingerprint, base_params, root=data_root) # Run with gas + dynamic_input_frames = DynamicInputFrames(gas_cost=gas_df) result_with_gas = do_run_on_historic_data( - base_fingerprint, base_params, root=data_root, gas_cost_df=gas_df + base_fingerprint, + base_params, + root=data_root, + dynamic_input_frames=dynamic_input_frames, ) if "final_value" in result_no_gas and "final_value" in result_with_gas: @@ -139,8 +156,12 @@ def test_fees_reduce_final_value(self, base_fingerprint, base_params, fees_df, d result_no_fees = do_run_on_historic_data(base_fingerprint, base_params, root=data_root) # Run with fees + dynamic_input_frames = DynamicInputFrames(fees=fees_df) result_with_fees = do_run_on_historic_data( - base_fingerprint, base_params, root=data_root, fees_df=fees_df + base_fingerprint, + base_params, + root=data_root, + dynamic_input_frames=dynamic_input_frames, ) if "final_value" in result_no_fees and "final_value" in result_with_fees: diff --git a/tests/integration/test_float32_forward_pass.py b/tests/integration/test_float32_forward_pass.py index b4d878a..75d311b 100644 --- a/tests/integration/test_float32_forward_pass.py +++ b/tests/integration/test_float32_forward_pass.py @@ -89,8 +89,8 @@ def override_backend(backend): "logit_lamb": jnp.array([-0.22066515, -0.22066515]), "initial_weights_logits": jnp.array([0.0, 0.0]), }, - "expected_final_value": 1500094.138254407, - "expected_return_pct": 50.00941382544071, + "expected_final_value": 1489697.5771969256, + "expected_return_pct": 48.969757719692566, "expected_first_weights": [0.5, 0.5], "expected_last_weights": [0.05000921, 0.94999079], }, @@ -114,8 +114,8 @@ def override_backend(backend): "logit_lamb": jnp.array([2.02840786, 2.02840786]), "initial_weights_logits": jnp.array([0.0, 0.0]), }, - "expected_final_value": 1368731.4974473487, - "expected_return_pct": 36.87314974473486, + "expected_final_value": 1352951.4582811554, + "expected_return_pct": 35.29514582811555, "expected_first_weights": [0.5, 0.5], "expected_last_weights": [0.05, 0.95], }, @@ -262,7 +262,10 @@ def test_final_value_matches_baseline(self, config_name): actual = float(result["final_value"]) expected = config["expected_final_value"] rel_diff = abs(actual - expected) / expected - assert rel_diff < 0.006, ( + # forward_pass_test_2 carries an inherent conv-vs-scan estimator + # divergence (~1.3% in f64 with protocol_fee_split=0.25) on top of + # float32 noise, so the GPU-path tolerance is looser than the CPU one. + assert rel_diff < 0.015, ( f"{config_name} f32 GPU: final value {actual:.2f} vs " f"f64 baseline {expected:.2f} ({rel_diff*100:.4f}%)" ) diff --git a/tests/integration/test_gpu_path_baselines.py b/tests/integration/test_gpu_path_baselines.py index faf20ec..46fdb93 100644 --- a/tests/integration/test_gpu_path_baselines.py +++ b/tests/integration/test_gpu_path_baselines.py @@ -85,7 +85,7 @@ def override_backend(backend): "initial_weights_logits": jnp.array([0.0, 0.0]), }, "expected": { - "final_value": 1500094.138254407, + "final_value": 1489697.5771969256, "first_weights": [0.5, 0.5], "last_weights": [0.05000921, 0.94999079], }, @@ -111,7 +111,7 @@ def override_backend(backend): "initial_weights_logits": jnp.array([0.0, 0.0]), }, "expected": { - "final_value": 1368731.4974473487, + "final_value": 1352951.4582811554, "first_weights": [0.5, 0.5], "last_weights": [0.05, 0.95], }, @@ -141,7 +141,10 @@ def test_gpu_final_value_matches_baseline(self, config_name): actual_final = float(result["final_value"]) relative_diff = abs(actual_final - expected_final) / expected_final - assert relative_diff < 0.01, ( + # forward_pass_test_2 (log_k=7, weights pinned at bounds) has an + # inherent conv-vs-scan estimator divergence: 0.82% with + # protocol_fee_split=0.0, 1.33% with the 0.25 production default. + assert relative_diff < 0.015, ( f"{config_name} GPU: Final value {actual_final:.2f} vs " f"baseline {expected_final:.2f} ({relative_diff*100:.4f}%)" ) diff --git a/tests/integration/test_run_pool_simulation_path.py b/tests/integration/test_run_pool_simulation_path.py index bfdf754..f724ac1 100644 --- a/tests/integration/test_run_pool_simulation_path.py +++ b/tests/integration/test_run_pool_simulation_path.py @@ -82,8 +82,8 @@ "initial_weights_logits": jnp.array([0.0, 0.0]), }, "expected": { - "final_value": 1500094.138254407, - "return_pct": 50.00941382544071, + "final_value": 1489697.5771969256, + "return_pct": 48.969757719692566, "first_weights": [0.5, 0.5], "last_weights": [0.05000921, 0.94999079], }, @@ -111,8 +111,8 @@ "initial_weights_logits": jnp.array([0.0, 0.0]), }, "expected": { - "final_value": 1368731.4974473487, - "return_pct": 36.87314974473486, + "final_value": 1352951.4582811554, + "return_pct": 35.29514582811555, "first_weights": [0.5, 0.5], "last_weights": [0.05, 0.95], }, diff --git a/tests/noise/__init__.py b/tests/noise/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/tests/noise/conftest.py b/tests/noise/conftest.py new file mode 100644 index 0000000..5bfeadc --- /dev/null +++ b/tests/noise/conftest.py @@ -0,0 +1,274 @@ +"""Shared fixtures for noise calibration tests.""" + +from datetime import date, timedelta + +import numpy as np +import pandas as pd +import pytest + + +# --------------------------------------------------------------------------- +# synthetic_panel: 3 pools × 10 days = 30 obs (before lag drop → 27 obs) +# --------------------------------------------------------------------------- + +@pytest.fixture() +def synthetic_panel() -> pd.DataFrame: + """3 pools × 10 days with known structure. + + Pool A: MAINNET, WETH/USDC, tier_A=0, tier_B=0, fee=0.003 + Pool B: ARBITRUM, BAL/WETH, tier_A=0, tier_B=1, fee=0.01 + Pool C: BASE, RATS/WETH, tier_A=0, tier_B=2, fee=0.005 + + Dates: 2026-01-01 to 2026-01-10 + Weekend flags: Sat 2026-01-03 and Sun 2026-01-04 are weekends. + (2026-01-01 is Thursday, ..., 01-03 Sat, 01-04 Sun, 01-05 Mon, ...) + """ + np.random.seed(42) + + pools = [ + ("pool_A", "MAINNET", "WETH,USDC", 0.003, 0, 0), + ("pool_B", "ARBITRUM", "BAL,WETH", 0.01, 0, 1), + ("pool_C", "BASE", "RATS,WETH", 0.005, 0, 2), + ] + dates = [date(2026, 1, 1) + timedelta(days=i) for i in range(10)] + + records = [] + for pool_id, chain, tokens, fee, tier_a, tier_b in pools: + log_tvl_base = 14.0 + np.random.randn() * 0.5 + for d in dates: + log_tvl = log_tvl_base + np.random.randn() * 0.1 + log_vol = log_tvl - 2.0 + np.random.randn() * 0.3 + vol = 0.3 + np.random.rand() * 0.2 + is_weekend = 1.0 if d.weekday() >= 5 else 0.0 + + records.append({ + "pool_id": pool_id, + "chain": chain, + "date": d, + "log_volume": log_vol, + "log_tvl": log_tvl, + "volatility": vol, + "weekend": is_weekend, + "log_fee": np.log(max(fee, 1e-6)), + "swap_fee": fee, + "tier_A": tier_a, + "tier_B": tier_b, + "tokens": tokens, + }) + + panel = pd.DataFrame(records) + panel = panel.sort_values(["pool_id", "date"]).reset_index(drop=True) + panel["log_tvl_lag1"] = panel.groupby("pool_id")["log_tvl"].shift(1) + panel = panel.dropna(subset=["log_tvl_lag1"]).reset_index(drop=True) + + # Structural model covariates + panel["log_sigma"] = np.log(np.maximum(panel["volatility"].values, 1e-6)) + dow = panel["date"].apply( + lambda d: d.weekday() if hasattr(d, "weekday") else pd.Timestamp(d).weekday() + ) + panel["dow_sin"] = np.sin(2.0 * np.pi * dow / 7.0) + panel["dow_cos"] = np.cos(2.0 * np.pi * dow / 7.0) + panel["tvl_x_sigma"] = panel["log_tvl_lag1"] * panel["log_sigma"] + panel["tvl_x_fee"] = panel["log_tvl_lag1"] * panel["log_fee"] + panel["sigma_x_fee"] = panel["log_sigma"] * panel["log_fee"] + + return panel + + +# --------------------------------------------------------------------------- +# synthetic_encoded_data: output of encode_covariates(synthetic_panel) +# --------------------------------------------------------------------------- + +@pytest.fixture() +def synthetic_encoded_data(synthetic_panel): + from quantammsim.noise_calibration import encode_covariates + return encode_covariates(synthetic_panel) + + +# --------------------------------------------------------------------------- +# synthetic_samples: deterministic posterior-like dict +# --------------------------------------------------------------------------- + +@pytest.fixture() +def synthetic_samples(synthetic_encoded_data): + """Deterministic posterior samples with eta=0, L_Omega=I. + + With this structure: theta = X_pool @ B^T exactly. + """ + data = synthetic_encoded_data + N_pools = data["N_pools"] + K_cov = data["K_cov"] + K_coeff = 4 + S = 10 + + np.random.seed(99) + B = np.random.randn(S, K_coeff, K_cov) * 0.5 + sigma_theta = np.ones((S, K_coeff)) + L_Omega = np.tile(np.eye(K_coeff), (S, 1, 1)) + eta = np.zeros((S, N_pools, K_coeff)) + df = np.full((S,), 5.0) + sigma_eps = np.tile([0.5, 0.8, 0.6], (S, 1)) + + return { + "B": B, + "sigma_theta": sigma_theta, + "L_Omega": L_Omega, + "eta": eta, + "df": df, + "sigma_eps": sigma_eps, + } + + +# --------------------------------------------------------------------------- +# synthetic_pools_df: matches enumerate_balancer_pools output schema +# --------------------------------------------------------------------------- + +@pytest.fixture() +def synthetic_pools_df() -> pd.DataFrame: + return pd.DataFrame([ + { + "pool_id": "pool_A", + "chain": "MAINNET", + "pool_type": "WEIGHTED", + "tokens": ["WETH", "USDC"], + "token_addresses": ["0xweth", "0xusdc"], + "swap_fee": 0.003, + "current_tvl": 1_000_000, + }, + { + "pool_id": "pool_B", + "chain": "ARBITRUM", + "pool_type": "WEIGHTED", + "tokens": ["BAL", "WETH"], + "token_addresses": ["0xbal", "0xweth"], + "swap_fee": 0.01, + "current_tvl": 500_000, + }, + { + "pool_id": "pool_C", + "chain": "BASE", + "pool_type": "WEIGHTED", + "tokens": ["RATS", "WETH"], + "token_addresses": ["0xrats", "0xweth"], + "swap_fee": 0.005, + "current_tvl": 100_000, + }, + ]) + + +# --------------------------------------------------------------------------- +# synthetic_ibp_samples: IBP posterior-like dict (no eta, L_Omega, sigma_theta) +# --------------------------------------------------------------------------- + +@pytest.fixture() +def synthetic_ibp_samples(synthetic_encoded_data): + """Deterministic IBP posterior samples (marginalized model). + + Contains B, W, v_ibp, alpha_ibp — no z_logit, eta, L_Omega, sigma_theta. + Z is analytically marginalized; MAP assignments are computed from data. + """ + data = synthetic_encoded_data + K_cov = data["K_cov"] + K_coeff = 4 + K_features = 6 + S = 10 + + np.random.seed(99) + B = np.random.randn(S, K_coeff, K_cov) * 0.5 + W = np.random.randn(S, K_features, K_coeff) * 0.3 + v_ibp = np.random.beta(2, 1, size=(S, K_features)) + alpha_ibp = np.full((S,), 2.0) + sigma_w = np.full((S,), 1.0) + df = np.full((S,), 5.0) + sigma_eps = np.full((S,), 0.5) + + return { + "B": B, + "W": W, + "v_ibp": v_ibp, + "alpha_ibp": alpha_ibp, + "sigma_w": sigma_w, + "df": df, + "sigma_eps": sigma_eps, + } + + +# --------------------------------------------------------------------------- +# synthetic_ibp_dp_samples: hybrid IBP+DP posterior-like dict +# --------------------------------------------------------------------------- + +@pytest.fixture() +def synthetic_ibp_dp_samples(synthetic_encoded_data): + """Deterministic hybrid IBP+DP posterior samples. + + Contains both IBP keys (B, W, v_ibp, alpha_ibp, sigma_w) and + DP keys (v, alpha_dp, sigma_eps as vector). No z_logit, eta, L_Omega, + sigma_theta. + """ + data = synthetic_encoded_data + K_cov = data["K_cov"] + K_coeff = 4 + K_features = 6 + K_clusters = 6 + S = 10 + + np.random.seed(99) + B = np.random.randn(S, K_coeff, K_cov) * 0.5 + W = np.random.randn(S, K_features, K_coeff) * 0.3 + v_ibp = np.random.beta(2, 1, size=(S, K_features)) + alpha_ibp = np.full((S,), 2.0) + sigma_w = np.full((S,), 1.0) + v = np.random.beta(1, 2, size=(S, K_clusters - 1)) + alpha_dp = np.full((S,), 1.0) + df = np.full((S,), 5.0) + sigma_eps = np.abs(np.random.randn(S, K_clusters)) + 0.1 + + return { + "B": B, + "W": W, + "v_ibp": v_ibp, + "alpha_ibp": alpha_ibp, + "sigma_w": sigma_w, + "v": v, + "alpha_dp": alpha_dp, + "df": df, + "sigma_eps": sigma_eps, + } + + +# --------------------------------------------------------------------------- +# synthetic_snapshots_df: matches fetch_all_snapshots output schema +# --------------------------------------------------------------------------- + +# --------------------------------------------------------------------------- +# synthetic_structural_data: output of encode_covariates_structural() +# --------------------------------------------------------------------------- + +@pytest.fixture() +def synthetic_structural_data(synthetic_panel): + from quantammsim.noise_calibration.covariate_encoding import ( + encode_covariates_structural, + ) + return encode_covariates_structural(synthetic_panel) + + +# --------------------------------------------------------------------------- +# synthetic_snapshots_df: matches fetch_all_snapshots output schema +# --------------------------------------------------------------------------- + +@pytest.fixture() +def synthetic_snapshots_df() -> pd.DataFrame: + dates = [date(2026, 1, 1) + timedelta(days=i) for i in range(10)] + records = [] + np.random.seed(42) + for pool_id, chain in [("pool_A", "MAINNET"), ("pool_B", "ARBITRUM"), + ("pool_C", "BASE")]: + for d in dates: + records.append({ + "pool_id": pool_id, + "chain": chain, + "date": d, + "volume_usd": np.exp(10.0 + np.random.randn() * 0.5), + "total_liquidity_usd": np.exp(14.0 + np.random.randn() * 0.3), + }) + return pd.DataFrame(records) diff --git a/tests/noise/test_covariate_encoding.py b/tests/noise/test_covariate_encoding.py new file mode 100644 index 0000000..dececb5 --- /dev/null +++ b/tests/noise/test_covariate_encoding.py @@ -0,0 +1,326 @@ +"""Tests for encode_covariates and encode_covariates_structural.""" + +import numpy as np +import pytest + +from quantammsim.noise_calibration import encode_covariates +from quantammsim.noise_calibration.constants import K_OBS_COEFF, OBS_COEFF_NAMES + + +class TestEncodeCovariates: + def test_x_pool_shape(self, synthetic_panel): + data = encode_covariates(synthetic_panel) + N_pools = data["N_pools"] + K_cov = data["K_cov"] + assert data["X_pool"].shape == (N_pools, K_cov) + + def test_x_obs_shape(self, synthetic_panel): + data = encode_covariates(synthetic_panel) + N_obs = len(synthetic_panel) + assert data["x_obs"].shape == (N_obs, 4) + + def test_x_obs_column_0_is_intercept(self, synthetic_panel): + data = encode_covariates(synthetic_panel) + np.testing.assert_array_equal(data["x_obs"][:, 0], 1.0) + + def test_x_obs_column_1_is_lagged_tvl(self, synthetic_panel): + data = encode_covariates(synthetic_panel) + np.testing.assert_array_equal( + data["x_obs"][:, 1], + synthetic_panel["log_tvl_lag1"].values, + ) + + def test_x_obs_column_2_is_volatility(self, synthetic_panel): + data = encode_covariates(synthetic_panel) + np.testing.assert_array_equal( + data["x_obs"][:, 2], + synthetic_panel["volatility"].values, + ) + + def test_x_obs_column_3_is_weekend(self, synthetic_panel): + data = encode_covariates(synthetic_panel) + np.testing.assert_array_equal( + data["x_obs"][:, 3], + synthetic_panel["weekend"].values, + ) + + def test_intercept_column_all_ones(self, synthetic_panel): + data = encode_covariates(synthetic_panel) + np.testing.assert_array_equal(data["X_pool"][:, 0], 1.0) + + def test_chain_dummies_one_hot(self, synthetic_panel): + data = encode_covariates(synthetic_panel) + col_names = data["covariate_names"] + chain_cols = [i for i, n in enumerate(col_names) if n.startswith("chain_")] + X = data["X_pool"] + + for row in range(X.shape[0]): + chain_vals = X[row, chain_cols] + assert chain_vals.sum() <= 1.0 + + def test_reference_chain_is_alphabetically_first(self, synthetic_panel): + data = encode_covariates(synthetic_panel) + chains = sorted(synthetic_panel["chain"].unique()) + ref_chain = chains[0] # ARBITRUM + + pool_meta = data["pool_meta"] + ref_idx = pool_meta[pool_meta["chain"] == ref_chain].index[0] + col_names = data["covariate_names"] + chain_cols = [i for i, n in enumerate(col_names) if n.startswith("chain_")] + X = data["X_pool"] + assert all(X[ref_idx, c] == 0.0 for c in chain_cols) + + def test_tier_a_dummies_match(self, synthetic_panel): + data = encode_covariates(synthetic_panel) + col_names = data["covariate_names"] + tier_a_cols = [ + (i, n) for i, n in enumerate(col_names) if n.startswith("tier_A_") + ] + pool_meta = data["pool_meta"] + X = data["X_pool"] + + for idx, row in pool_meta.iterrows(): + tier_a_str = str(row["tier_A"]) + for col_idx, col_name in tier_a_cols: + expected_tier = col_name.split("_")[-1] + expected = 1.0 if tier_a_str == expected_tier else 0.0 + assert X[idx, col_idx] == expected, ( + f"Pool {idx} tier_A={tier_a_str}, col {col_name}: " + f"expected {expected}, got {X[idx, col_idx]}" + ) + + def test_tier_b_dummies_match(self, synthetic_panel): + data = encode_covariates(synthetic_panel) + col_names = data["covariate_names"] + tier_b_cols = [ + (i, n) for i, n in enumerate(col_names) if n.startswith("tier_B_") + ] + pool_meta = data["pool_meta"] + X = data["X_pool"] + + for idx, row in pool_meta.iterrows(): + tier_b_str = str(row["tier_B"]) + for col_idx, col_name in tier_b_cols: + expected_tier = col_name.split("_")[-1] + expected = 1.0 if tier_b_str == expected_tier else 0.0 + assert X[idx, col_idx] == expected + + def test_log_fee_column(self, synthetic_panel): + data = encode_covariates(synthetic_panel) + col_names = data["covariate_names"] + fee_idx = col_names.index("log_fee") + pool_meta = data["pool_meta"] + + for idx, row in pool_meta.iterrows(): + expected = np.log(max(row["swap_fee"], 1e-6)) + np.testing.assert_allclose( + data["X_pool"][idx, fee_idx], expected, rtol=1e-10, + ) + + def test_pool_idx_maps_observations(self, synthetic_panel): + data = encode_covariates(synthetic_panel) + pool_ids = data["pool_ids"] + pool_idx = data["pool_idx"] + + assert pool_idx.min() >= 0 + assert pool_idx.max() < len(pool_ids) + + for i, pid in enumerate(pool_ids): + mask = synthetic_panel["pool_id"] == pid + obs_indices = np.where(mask.values)[0] + assert (pool_idx[obs_indices] == i).all() + + def test_y_obs_matches_panel(self, synthetic_panel): + data = encode_covariates(synthetic_panel) + np.testing.assert_array_equal( + data["y_obs"], + synthetic_panel["log_volume"].values, + ) + + def test_covariate_names_length(self, synthetic_panel): + data = encode_covariates(synthetic_panel) + assert len(data["covariate_names"]) == data["K_cov"] + + def test_covariate_column_ordering(self, synthetic_panel): + """X_pool columns must follow: intercept, chain dummies, tier_A dummies, + tier_B dummies, log_fee. This ordering is load-bearing because B is + indexed by column position.""" + data = encode_covariates(synthetic_panel) + names = data["covariate_names"] + + assert names[0] == "intercept" + assert names[-1] == "log_fee" + + # Find boundaries + chain_start = None + tier_a_start = None + tier_b_start = None + fee_idx = len(names) - 1 + + for i, n in enumerate(names): + if n.startswith("chain_") and chain_start is None: + chain_start = i + if n.startswith("tier_A_") and tier_a_start is None: + tier_a_start = i + if n.startswith("tier_B_") and tier_b_start is None: + tier_b_start = i + + # Verify ordering: intercept < chains < tier_A < tier_B < log_fee + if chain_start is not None: + assert chain_start > 0 # after intercept + if tier_a_start is not None and chain_start is not None: + assert tier_a_start > chain_start + if tier_b_start is not None and tier_a_start is not None: + assert tier_b_start > tier_a_start + if tier_b_start is not None: + assert fee_idx > tier_b_start + + # All chain dummies are contiguous + chain_names = [n for n in names if n.startswith("chain_")] + if chain_names: + chain_indices = [names.index(n) for n in chain_names] + assert chain_indices == list(range(min(chain_indices), + max(chain_indices) + 1)) + + def test_output_dict_has_all_required_keys(self, synthetic_panel): + """encode_covariates must return all keys consumed by downstream + functions (predict_new_pool, generate_output_json, _save_sample_cache).""" + data = encode_covariates(synthetic_panel) + required_keys = { + "pool_idx", "X_pool", "x_obs", "y_obs", "pool_ids", "pool_meta", + "covariate_names", "tier_A_per_pool", "N_pools", "K_cov", + "ref_chain", "ref_tier_a", "ref_tier_b", "chains", + } + assert required_keys.issubset(data.keys()), ( + f"Missing keys: {required_keys - data.keys()}" + ) + + +class TestEncodeCovariatesNoTiers: + """Tests for encode_covariates(include_tiers=False).""" + + def test_no_tier_columns_in_covariate_names(self, synthetic_panel): + data = encode_covariates(synthetic_panel, include_tiers=False) + for name in data["covariate_names"]: + assert not name.startswith("tier_A_"), ( + f"Found tier_A column {name} with include_tiers=False" + ) + assert not name.startswith("tier_B_"), ( + f"Found tier_B column {name} with include_tiers=False" + ) + + def test_still_has_intercept_and_log_fee(self, synthetic_panel): + data = encode_covariates(synthetic_panel, include_tiers=False) + assert "intercept" in data["covariate_names"] + assert "log_fee" in data["covariate_names"] + + def test_still_has_chain_dummies(self, synthetic_panel): + data = encode_covariates(synthetic_panel, include_tiers=False) + chain_cols = [n for n in data["covariate_names"] + if n.startswith("chain_")] + assert len(chain_cols) > 0 + + def test_k_cov_smaller_than_with_tiers(self, synthetic_panel): + data_tiers = encode_covariates(synthetic_panel, include_tiers=True) + data_no_tiers = encode_covariates(synthetic_panel, include_tiers=False) + assert data_no_tiers["K_cov"] < data_tiers["K_cov"] + + def test_default_is_include_tiers_true(self, synthetic_panel): + data_default = encode_covariates(synthetic_panel) + data_explicit = encode_covariates(synthetic_panel, include_tiers=True) + assert data_default["K_cov"] == data_explicit["K_cov"] + + def test_tier_A_per_pool_still_present(self, synthetic_panel): + """tier_A_per_pool is still returned (for other downstream uses).""" + data = encode_covariates(synthetic_panel, include_tiers=False) + assert "tier_A_per_pool" in data + + def test_x_pool_shape_matches_k_cov(self, synthetic_panel): + data = encode_covariates(synthetic_panel, include_tiers=False) + assert data["X_pool"].shape == (data["N_pools"], data["K_cov"]) + + +class TestEncodeStructuralCovariates: + """Tests for encode_covariates_structural().""" + + @pytest.fixture() + def struct_data(self, synthetic_panel): + from quantammsim.noise_calibration.covariate_encoding import ( + encode_covariates_structural, + ) + return encode_covariates_structural(synthetic_panel) + + def test_encode_structural_x_obs_shape(self, struct_data, synthetic_panel): + N_obs = len(synthetic_panel) + assert struct_data["x_obs"].shape == (N_obs, K_OBS_COEFF) + + def test_encode_structural_x_obs_columns(self, struct_data, synthetic_panel): + """Columns must match OBS_COEFF_NAMES ordering: + [1, lag_log_tvl, log_sigma, tvl_x_sigma, tvl_x_fee, sigma_x_fee, + dow_sin, dow_cos].""" + x = struct_data["x_obs"] + np.testing.assert_array_equal(x[:, 0], 1.0) # intercept + np.testing.assert_array_equal( + x[:, 1], synthetic_panel["log_tvl_lag1"].values, + ) + np.testing.assert_array_equal( + x[:, 2], synthetic_panel["log_sigma"].values, + ) + np.testing.assert_allclose( + x[:, 3], synthetic_panel["tvl_x_sigma"].values, rtol=1e-12, + ) + np.testing.assert_allclose( + x[:, 4], synthetic_panel["tvl_x_fee"].values, rtol=1e-12, + ) + np.testing.assert_allclose( + x[:, 5], synthetic_panel["sigma_x_fee"].values, rtol=1e-12, + ) + np.testing.assert_allclose( + x[:, 6], synthetic_panel["dow_sin"].values, rtol=1e-12, + ) + np.testing.assert_allclose( + x[:, 7], synthetic_panel["dow_cos"].values, rtol=1e-12, + ) + + def test_encode_structural_has_sigma_daily(self, struct_data): + """sigma_daily = volatility / sqrt(365), de-annualised.""" + assert "sigma_daily" in struct_data + assert len(struct_data["sigma_daily"]) > 0 + + def test_encode_structural_has_gas(self, struct_data): + assert "gas" in struct_data + assert len(struct_data["gas"]) > 0 + assert (struct_data["gas"] >= 0).all() + + def test_encode_structural_has_chain_idx_tier_idx(self, struct_data): + assert "chain_idx" in struct_data + assert "tier_idx" in struct_data + assert struct_data["chain_idx"].dtype in (np.int32, np.int64) + assert struct_data["tier_idx"].dtype in (np.int32, np.int64) + + def test_encode_structural_has_fee(self, struct_data): + """fee array is raw (not log), for the formula.""" + assert "fee" in struct_data + assert (struct_data["fee"] > 0).all() + assert (struct_data["fee"] < 1).all() # fees are fractions + + def test_encode_structural_tier_idx_is_pair(self, struct_data): + """tier_idx encodes the (tier_A, tier_B) PAIR, not individual tokens. + (0,0)->0, (0,1)->1, (0,2)->2, (1,1)->3, (1,2)->4, (2,2)->5.""" + tier_idx = struct_data["tier_idx"] + pool_meta = struct_data["pool_meta"] + + for i, row in pool_meta.iterrows(): + a, b = int(row["tier_A"]), int(row["tier_B"]) + expected = a * (5 - a) // 2 + b - a + assert tier_idx[i] == expected, ( + f"Pool {i}: tier ({a},{b}) expected idx {expected}, " + f"got {tier_idx[i]}" + ) + + def test_encode_structural_n_chains_n_tiers(self, struct_data): + """n_chains and n_tiers computed from data.""" + assert "n_chains" in struct_data + assert "n_tiers" in struct_data + assert struct_data["n_chains"] >= 1 + assert struct_data["n_tiers"] >= 1 diff --git a/tests/noise/test_formula_arb.py b/tests/noise/test_formula_arb.py new file mode 100644 index 0000000..f862d51 --- /dev/null +++ b/tests/noise/test_formula_arb.py @@ -0,0 +1,142 @@ +"""Tests for JAX-differentiable LVR formula.""" + +import numpy as np +import pytest + + +class TestFormulaArbJax: + @pytest.fixture(autouse=True) + def _import(self): + from quantammsim.noise_calibration.formula_arb import ( + formula_arb_volume_daily_jax, + ) + self.formula = formula_arb_volume_daily_jax + + def test_formula_arb_zero_vol_returns_zero(self): + import jax.numpy as jnp + result = self.formula( + sigma_daily=jnp.float64(0.0), + tvl=jnp.float64(1e6), + fee=jnp.float64(0.003), + gas_usd=jnp.float64(1.0), + cadence_minutes=jnp.float64(1.0), + ) + assert float(result) == pytest.approx(0.0, abs=1e-10) + + def test_formula_arb_zero_tvl_returns_zero(self): + import jax.numpy as jnp + result = self.formula( + sigma_daily=jnp.float64(0.03), + tvl=jnp.float64(0.0), + fee=jnp.float64(0.003), + gas_usd=jnp.float64(1.0), + cadence_minutes=jnp.float64(1.0), + ) + assert float(result) == pytest.approx(0.0, abs=1e-10) + + def test_formula_arb_quadratic_in_sigma(self): + """V_arb(2σ) / V_arb(σ) ≈ 4 for small gas (correction ≈ 1).""" + import jax.numpy as jnp + sigma = jnp.float64(0.01) + tvl = jnp.float64(1e8) # large TVL so gas is negligible + fee = jnp.float64(0.003) + gas = jnp.float64(0.001) # tiny gas + cadence = jnp.float64(0.01) # very fast arb + + v1 = float(self.formula(sigma, tvl, fee, gas, cadence)) + v2 = float(self.formula(2.0 * sigma, tvl, fee, gas, cadence)) + assert v1 > 0 + ratio = v2 / v1 + assert ratio == pytest.approx(4.0, rel=0.1) + + def test_formula_arb_linear_in_tvl(self): + """V_arb(2V) / V_arb(V) ≈ 2 for small gas.""" + import jax.numpy as jnp + sigma = jnp.float64(0.02) + tvl = jnp.float64(1e8) + fee = jnp.float64(0.003) + gas = jnp.float64(0.0001) + cadence = jnp.float64(0.01) + + v1 = float(self.formula(sigma, tvl, fee, gas, cadence)) + v2 = float(self.formula(sigma, 2.0 * tvl, fee, gas, cadence)) + assert v1 > 0 + ratio = v2 / v1 + assert ratio == pytest.approx(2.0, rel=0.1) + + def test_formula_arb_gas_kills_small_pools(self): + """High gas, small TVL → V_arb ≈ 0.""" + import jax.numpy as jnp + result = self.formula( + sigma_daily=jnp.float64(0.02), + tvl=jnp.float64(1000.0), + fee=jnp.float64(0.003), + gas_usd=jnp.float64(1000.0), + cadence_minutes=jnp.float64(1.0), + ) + assert float(result) == pytest.approx(0.0, abs=1e-6) + + def test_formula_arb_matches_numpy_reference(self): + """Compare to the numpy formula in plot_formula_arb_vs_real.py.""" + import jax.numpy as jnp + + # Reference implementation (from plot_formula_arb_vs_real.py:58) + def ref(sigma_daily, tvl, fee, block_time_s, gas_usd): + if tvl <= 0 or fee <= 0 or sigma_daily <= 0: + return 0.0 + gamma = fee + delta = 2.0 * np.sqrt(2.0 * gas_usd / tvl) if gas_usd > 0 else 0.0 + bLVR = sigma_daily**2 * tvl / 8.0 + sqrt_s2_2l = sigma_daily * np.sqrt(block_time_s / (2.0 * 86400.0)) + bFEE = bLVR * max( + 1.0 - delta / (2.0 * gamma) - sqrt_s2_2l / (gamma + delta / 2.0), + 0.0, + ) + return bFEE / gamma + + test_cases = [ + (0.03, 1e6, 0.003, 1.0, 1.0), + (0.05, 5e5, 0.01, 0.5, 0.005), + (0.01, 1e7, 0.005, 2.0, 0.01), + (0.1, 1e4, 0.03, 10.0, 5.0), + (0.02, 1e5, 0.001, 1.0, 0.001), + ] + + for sigma, tvl, fee, cadence_min, gas in test_cases: + block_time_s = cadence_min * 60.0 + expected = ref(sigma, tvl, fee, block_time_s, gas) + actual = float(self.formula( + jnp.float64(sigma), jnp.float64(tvl), + jnp.float64(fee), jnp.float64(gas), + jnp.float64(cadence_min), + )) + np.testing.assert_allclose( + actual, expected, rtol=1e-10, + err_msg=f"Mismatch for sigma={sigma}, tvl={tvl}, " + f"fee={fee}, cadence={cadence_min}, gas={gas}", + ) + + def test_formula_arb_is_jax_differentiable(self): + """jax.grad w.r.t. sigma should run without error.""" + import jax + import jax.numpy as jnp + + grad_fn = jax.grad(self.formula, argnums=0) + result = grad_fn( + jnp.float64(0.03), jnp.float64(1e6), + jnp.float64(0.003), jnp.float64(1.0), + jnp.float64(1.0), + ) + assert np.isfinite(float(result)) + + def test_formula_arb_cadence_reduces_volume(self): + """Higher cadence → less frequent arb → lower volume.""" + import jax.numpy as jnp + sigma = jnp.float64(0.03) + tvl = jnp.float64(1e6) + fee = jnp.float64(0.003) + gas = jnp.float64(1.0) + + v_fast = float(self.formula(sigma, tvl, fee, gas, jnp.float64(1.0))) + v_slow = float(self.formula(sigma, tvl, fee, gas, jnp.float64(10.0))) + assert v_fast > v_slow diff --git a/tests/noise/test_model_and_inference.py b/tests/noise/test_model_and_inference.py new file mode 100644 index 0000000..ee1fda5 --- /dev/null +++ b/tests/noise/test_model_and_inference.py @@ -0,0 +1,328 @@ +"""Tests for noise_model, _get_theta_samples, _build_model_kwargs, and SVI smoke.""" + +import numpy as np +import pytest + +from quantammsim.noise_calibration import ( + noise_model, + _get_theta_samples, + _build_model_kwargs, + K_COEFF, +) + + +# =========================================================================== +# TestNoiseModelDefinition +# =========================================================================== + + +class TestNoiseModelDefinition: + def test_model_traces_without_error(self, synthetic_encoded_data): + import jax + import jax.numpy as jnp + import numpyro + import numpyro.handlers as handlers + + data = synthetic_encoded_data + kwargs = _build_model_kwargs(data) + rng_key = jax.random.PRNGKey(0) + + trace = handlers.trace(handlers.seed(noise_model, rng_key)).get_trace( + **kwargs + ) + assert trace is not None + + def test_required_sites_present(self, synthetic_encoded_data): + import jax + import numpyro.handlers as handlers + + data = synthetic_encoded_data + kwargs = _build_model_kwargs(data) + rng_key = jax.random.PRNGKey(0) + + trace = handlers.trace(handlers.seed(noise_model, rng_key)).get_trace( + **kwargs + ) + required = {"B", "sigma_theta", "L_Omega", "df", "sigma_eps", "eta", "y"} + assert required.issubset(trace.keys()) + + def test_site_shapes(self, synthetic_encoded_data): + import jax + import numpyro.handlers as handlers + + data = synthetic_encoded_data + kwargs = _build_model_kwargs(data) + rng_key = jax.random.PRNGKey(0) + + trace = handlers.trace(handlers.seed(noise_model, rng_key)).get_trace( + **kwargs + ) + N_pools = data["N_pools"] + K_cov = data["K_cov"] + + assert trace["B"]["value"].shape == (K_COEFF, K_cov) + assert trace["sigma_theta"]["value"].shape == (K_COEFF,) + assert trace["L_Omega"]["value"].shape == (K_COEFF, K_COEFF) + assert trace["df"]["value"].shape == () + assert trace["sigma_eps"]["value"].shape == (3,) + assert trace["eta"]["value"].shape == (N_pools, K_COEFF) + + def test_theta_deterministic_site(self, synthetic_encoded_data): + import jax + import numpyro.handlers as handlers + + data = synthetic_encoded_data + kwargs = _build_model_kwargs(data) + rng_key = jax.random.PRNGKey(0) + + trace = handlers.trace(handlers.seed(noise_model, rng_key)).get_trace( + **kwargs + ) + assert "theta" in trace + assert trace["theta"]["value"].shape == (data["N_pools"], K_COEFF) + + def test_prior_predictive_produces_y(self, synthetic_encoded_data): + import jax + from numpyro.infer import Predictive + + data = synthetic_encoded_data + kwargs = _build_model_kwargs(data) + kwargs["y_obs"] = None + + predictive = Predictive(noise_model, num_samples=5) + rng_key = jax.random.PRNGKey(42) + samples = predictive(rng_key, **kwargs) + assert "y" in samples + assert samples["y"].shape[0] == 5 + + +# =========================================================================== +# TestGetThetaSamples +# =========================================================================== + + +class TestGetThetaSamples: + def test_returns_theta_directly_when_present(self, synthetic_encoded_data): + data = synthetic_encoded_data + theta_direct = np.random.randn(10, data["N_pools"], K_COEFF) + sample_dict = {"theta": theta_direct} + result = _get_theta_samples(sample_dict, data["X_pool"]) + np.testing.assert_array_equal(result, theta_direct) + + def test_reconstructs_from_non_centered( + self, synthetic_encoded_data, synthetic_samples + ): + data = synthetic_encoded_data + result = _get_theta_samples(synthetic_samples, data["X_pool"]) + assert result.shape == (10, data["N_pools"], K_COEFF) + + def test_eta_zero_identity_gives_mu( + self, synthetic_encoded_data, synthetic_samples + ): + """With eta=0 and L_Omega=I, theta = X_pool @ B^T.""" + data = synthetic_encoded_data + X_pool = data["X_pool"] + B = synthetic_samples["B"] + + result = _get_theta_samples(synthetic_samples, X_pool) + + # Expected: mu[s,p,j] = sum_d X_pool[p,d] * B[s,j,d] + expected = np.einsum("pd,sjd->spj", X_pool, B) + np.testing.assert_allclose(result, expected, atol=1e-12) + + def test_output_shape(self, synthetic_encoded_data, synthetic_samples): + data = synthetic_encoded_data + result = _get_theta_samples(synthetic_samples, data["X_pool"]) + S = synthetic_samples["B"].shape[0] + assert result.shape == (S, data["N_pools"], K_COEFF) + + def test_reconstructed_matches_direct(self, synthetic_encoded_data): + """When both theta and raw params are present, reconstruction matches.""" + data = synthetic_encoded_data + N_pools = data["N_pools"] + K_cov = data["K_cov"] + S = 8 + X_pool = data["X_pool"] + + np.random.seed(123) + B = np.random.randn(S, K_COEFF, K_cov) * 0.3 + sigma_theta = np.abs(np.random.randn(S, K_COEFF)) + 0.1 + # Random lower-triangular L + L_raw = np.zeros((S, K_COEFF, K_COEFF)) + for s in range(S): + A = np.random.randn(K_COEFF, K_COEFF) + L_raw[s] = np.linalg.cholesky(A @ A.T + np.eye(K_COEFF)) + eta = np.random.randn(S, N_pools, K_COEFF) + + sample_dict = { + "B": B, "sigma_theta": sigma_theta, + "L_Omega": L_raw, "eta": eta, + } + + theta_recon = _get_theta_samples(sample_dict, X_pool) + + # Compute expected directly + mu = np.einsum("pd,sjd->spj", X_pool, B) + L_Sigma = sigma_theta[:, :, None] * L_raw + offset = np.einsum("spi,sji->spj", eta, L_Sigma) + expected = mu + offset + + np.testing.assert_allclose(theta_recon, expected, atol=1e-10) + + +# =========================================================================== +# TestBuildModelKwargs +# =========================================================================== + + +class TestBuildModelKwargs: + def test_all_outputs_are_jnp(self, synthetic_encoded_data): + import jax.numpy as jnp + + kwargs = _build_model_kwargs(synthetic_encoded_data) + for key in ["pool_idx", "X_pool", "x_obs", "y_obs", "tier_A_per_pool"]: + assert isinstance(kwargs[key], jnp.ndarray), ( + f"{key} should be jnp array" + ) + + def test_shapes_preserved(self, synthetic_encoded_data): + data = synthetic_encoded_data + kwargs = _build_model_kwargs(data) + assert kwargs["pool_idx"].shape == data["pool_idx"].shape + assert kwargs["X_pool"].shape == data["X_pool"].shape + assert kwargs["x_obs"].shape == data["x_obs"].shape + assert kwargs["y_obs"].shape == data["y_obs"].shape + assert kwargs["N_pools"] == data["N_pools"] + assert kwargs["K_cov"] == data["K_cov"] + + def test_dp_model_kwargs_exclude_tier_A(self, synthetic_encoded_data): + """When model_fn is noise_model_dp_sigma, tier_A_per_pool is excluded + and K_clusters is included.""" + from quantammsim.noise_calibration.model import noise_model_dp_sigma + + data = dict(synthetic_encoded_data) + data["K_clusters"] = 6 + kwargs = _build_model_kwargs(data, model_fn=noise_model_dp_sigma) + assert "tier_A_per_pool" not in kwargs + assert kwargs["K_clusters"] == 6 + + def test_tier_model_kwargs_include_tier_A(self, synthetic_encoded_data): + """When model_fn is noise_model (default), tier_A_per_pool is included + and K_clusters is not.""" + kwargs = _build_model_kwargs(synthetic_encoded_data) + assert "tier_A_per_pool" in kwargs + assert "K_clusters" not in kwargs + + def test_default_model_fn_is_noise_model(self, synthetic_encoded_data): + """Calling without model_fn should behave identically to model_fn=noise_model.""" + kwargs_default = _build_model_kwargs(synthetic_encoded_data) + kwargs_explicit = _build_model_kwargs( + synthetic_encoded_data, model_fn=noise_model + ) + assert set(kwargs_default.keys()) == set(kwargs_explicit.keys()) + + +# =========================================================================== +# TestSVISmoke +# =========================================================================== + + +class TestSVISmoke: + @pytest.mark.slow + def test_svi_converges_small_data(self): + """SVI on tiny synthetic data: ELBO should decrease.""" + import jax + import numpyro + + numpyro.enable_x64() + + from quantammsim.noise_calibration import encode_covariates, run_svi + + # Build minimal panel: 5 pools × 20 days + np.random.seed(42) + from datetime import date, timedelta + + pools_spec = [ + ("p0", "MAINNET", "WETH,USDC", 0.003, 0, 0), + ("p1", "ARBITRUM", "BAL,WETH", 0.01, 0, 1), + ("p2", "BASE", "RATS,WETH", 0.005, 0, 2), + ("p3", "MAINNET", "LINK,WETH", 0.005, 0, 1), + ("p4", "ARBITRUM", "AAVE,USDC", 0.003, 0, 1), + ] + dates = [date(2026, 1, 1) + timedelta(days=i) for i in range(21)] + records = [] + for pid, chain, tokens, fee, ta, tb in pools_spec: + base_tvl = 14 + np.random.randn() * 0.5 + for d in dates: + tvl = base_tvl + np.random.randn() * 0.1 + vol = tvl - 2 + np.random.randn() * 0.3 + records.append({ + "pool_id": pid, "chain": chain, "date": d, + "log_volume": vol, "log_tvl": tvl, + "volatility": 0.3 + np.random.rand() * 0.2, + "weekend": 1.0 if d.weekday() >= 5 else 0.0, + "log_fee": np.log(max(fee, 1e-6)), + "swap_fee": fee, "tier_A": ta, "tier_B": tb, + "tokens": tokens, + }) + + import pandas as pd + + panel = pd.DataFrame(records) + panel = panel.sort_values(["pool_id", "date"]).reset_index(drop=True) + panel["log_tvl_lag1"] = panel.groupby("pool_id")["log_tvl"].shift(1) + panel = panel.dropna(subset=["log_tvl_lag1"]).reset_index(drop=True) + + data = encode_covariates(panel) + samples, losses = run_svi(data, num_steps=2000, lr=1e-3, seed=0, + num_samples=50) + + # ELBO should decrease: mean of last 100 < mean of first 100 + assert np.mean(losses[-100:]) < np.mean(losses[:100]) + + @pytest.mark.slow + def test_svi_samples_have_required_keys(self): + """SVI output dict has all expected latent variable keys.""" + import jax + import numpyro + + numpyro.enable_x64() + + from quantammsim.noise_calibration import encode_covariates, run_svi + + np.random.seed(42) + from datetime import date, timedelta + import pandas as pd + + pools_spec = [ + ("p0", "MAINNET", "WETH,USDC", 0.003, 0, 0), + ("p1", "ARBITRUM", "BAL,WETH", 0.01, 0, 1), + ] + dates = [date(2026, 1, 1) + timedelta(days=i) for i in range(15)] + records = [] + for pid, chain, tokens, fee, ta, tb in pools_spec: + base_tvl = 14 + np.random.randn() * 0.5 + for d in dates: + tvl = base_tvl + np.random.randn() * 0.1 + vol = tvl - 2 + np.random.randn() * 0.3 + records.append({ + "pool_id": pid, "chain": chain, "date": d, + "log_volume": vol, "log_tvl": tvl, + "volatility": 0.3 + np.random.rand() * 0.2, + "weekend": 1.0 if d.weekday() >= 5 else 0.0, + "log_fee": np.log(max(fee, 1e-6)), + "swap_fee": fee, "tier_A": ta, "tier_B": tb, + "tokens": tokens, + }) + + panel = pd.DataFrame(records) + panel = panel.sort_values(["pool_id", "date"]).reset_index(drop=True) + panel["log_tvl_lag1"] = panel.groupby("pool_id")["log_tvl"].shift(1) + panel = panel.dropna(subset=["log_tvl_lag1"]).reset_index(drop=True) + + data = encode_covariates(panel) + samples, _ = run_svi(data, num_steps=500, lr=1e-3, seed=0, + num_samples=10) + + required = {"B", "sigma_theta", "L_Omega", "eta", "df", "sigma_eps"} + assert required.issubset(samples.keys()) diff --git a/tests/noise/test_model_dp_sigma.py b/tests/noise/test_model_dp_sigma.py new file mode 100644 index 0000000..3d2be21 --- /dev/null +++ b/tests/noise/test_model_dp_sigma.py @@ -0,0 +1,305 @@ +"""Tests for stick_breaking_weights and noise_model_dp_sigma.""" + +import numpy as np +import pytest + +from quantammsim.noise_calibration import K_COEFF +from quantammsim.noise_calibration.constants import K_CLUSTERS_DEFAULT + + +# =========================================================================== +# TestStickBreakingWeights +# =========================================================================== + + +class TestStickBreakingWeights: + def test_sums_to_one(self): + from quantammsim.noise_calibration.model import stick_breaking_weights + import jax.numpy as jnp + + v = jnp.array([0.5, 0.3, 0.4, 0.6, 0.2]) + w = stick_breaking_weights(v) + np.testing.assert_allclose(float(jnp.sum(w)), 1.0, atol=1e-6) + + def test_correct_length(self): + from quantammsim.noise_calibration.model import stick_breaking_weights + import jax.numpy as jnp + + K = 7 + v = jnp.ones(K - 1) * 0.3 + w = stick_breaking_weights(v) + assert w.shape == (K,) + + def test_non_negative(self): + from quantammsim.noise_calibration.model import stick_breaking_weights + import jax.numpy as jnp + + v = jnp.array([0.1, 0.9, 0.5, 0.7, 0.3]) + w = stick_breaking_weights(v) + assert jnp.all(w >= 0.0) + + def test_first_weight_equals_first_v(self): + from quantammsim.noise_calibration.model import stick_breaking_weights + import jax.numpy as jnp + + v = jnp.array([0.7, 0.4, 0.2]) + w = stick_breaking_weights(v) + np.testing.assert_allclose(float(w[0]), 0.7, atol=1e-6) + + def test_jit_compatible(self): + from quantammsim.noise_calibration.model import stick_breaking_weights + import jax + import jax.numpy as jnp + + v = jnp.array([0.5, 0.3, 0.4]) + w_eager = stick_breaking_weights(v) + w_jit = jax.jit(stick_breaking_weights)(v) + np.testing.assert_allclose( + np.array(w_eager), np.array(w_jit), atol=1e-6 + ) + + def test_all_v_one_concentrates_on_first(self): + """If v = [1, 1, ...], all mass goes to first component.""" + from quantammsim.noise_calibration.model import stick_breaking_weights + import jax.numpy as jnp + + v = jnp.ones(5) + w = stick_breaking_weights(v) + np.testing.assert_allclose(float(w[0]), 1.0, atol=1e-6) + np.testing.assert_allclose(float(jnp.sum(w[1:])), 0.0, atol=1e-6) + + +# =========================================================================== +# TestDPModelDefinition +# =========================================================================== + + +class TestDPModelDefinition: + def _get_dp_model_kwargs(self, data, K_clusters=6): + """Build kwargs for noise_model_dp_sigma from encoded data.""" + import jax.numpy as jnp + + return dict( + pool_idx=jnp.array(data["pool_idx"]), + X_pool=jnp.array(data["X_pool"]), + x_obs=jnp.array(data["x_obs"]), + y_obs=jnp.array(data["y_obs"]), + N_pools=data["N_pools"], + K_coeff=K_COEFF, + K_cov=data["K_cov"], + K_clusters=K_clusters, + ) + + def test_model_traces_without_error(self, synthetic_encoded_data): + from quantammsim.noise_calibration.model import noise_model_dp_sigma + import jax + import numpyro.handlers as handlers + + kwargs = self._get_dp_model_kwargs(synthetic_encoded_data) + rng_key = jax.random.PRNGKey(0) + + trace = handlers.trace( + handlers.seed(noise_model_dp_sigma, rng_key) + ).get_trace(**kwargs) + assert trace is not None + + def test_has_dp_sites(self, synthetic_encoded_data): + from quantammsim.noise_calibration.model import noise_model_dp_sigma + import jax + import numpyro.handlers as handlers + + kwargs = self._get_dp_model_kwargs(synthetic_encoded_data) + rng_key = jax.random.PRNGKey(0) + + trace = handlers.trace( + handlers.seed(noise_model_dp_sigma, rng_key) + ).get_trace(**kwargs) + + dp_sites = {"alpha_dp", "v", "sigma_eps", "log_lik"} + assert dp_sites.issubset(trace.keys()), ( + f"Missing DP sites: {dp_sites - trace.keys()}" + ) + + def test_no_y_site_when_obs_provided(self, synthetic_encoded_data): + """With y_obs provided, the marginalized model uses factor, not obs.""" + from quantammsim.noise_calibration.model import noise_model_dp_sigma + import jax + import numpyro.handlers as handlers + + kwargs = self._get_dp_model_kwargs(synthetic_encoded_data) + rng_key = jax.random.PRNGKey(0) + + trace = handlers.trace( + handlers.seed(noise_model_dp_sigma, rng_key) + ).get_trace(**kwargs) + + assert "y" not in trace, ( + "DP model should use numpyro.factor, not obs=y_obs" + ) + + def test_shared_mean_structure_sites(self, synthetic_encoded_data): + """The DP model must share the same mean structure as the tier model.""" + from quantammsim.noise_calibration.model import noise_model_dp_sigma + import jax + import numpyro.handlers as handlers + + kwargs = self._get_dp_model_kwargs(synthetic_encoded_data) + rng_key = jax.random.PRNGKey(0) + + trace = handlers.trace( + handlers.seed(noise_model_dp_sigma, rng_key) + ).get_trace(**kwargs) + + shared_sites = {"B", "sigma_theta", "L_Omega", "df", "eta", "theta"} + assert shared_sites.issubset(trace.keys()), ( + f"Missing shared sites: {shared_sites - trace.keys()}" + ) + + def test_correct_shapes(self, synthetic_encoded_data): + from quantammsim.noise_calibration.model import noise_model_dp_sigma + import jax + import numpyro.handlers as handlers + + K_clusters = 6 + kwargs = self._get_dp_model_kwargs( + synthetic_encoded_data, K_clusters=K_clusters + ) + rng_key = jax.random.PRNGKey(0) + + trace = handlers.trace( + handlers.seed(noise_model_dp_sigma, rng_key) + ).get_trace(**kwargs) + + N_pools = synthetic_encoded_data["N_pools"] + K_cov = synthetic_encoded_data["K_cov"] + + assert trace["B"]["value"].shape == (K_COEFF, K_cov) + assert trace["sigma_theta"]["value"].shape == (K_COEFF,) + assert trace["L_Omega"]["value"].shape == (K_COEFF, K_COEFF) + assert trace["df"]["value"].shape == () + assert trace["sigma_eps"]["value"].shape == (K_clusters,) + assert trace["eta"]["value"].shape == (N_pools, K_COEFF) + assert trace["v"]["value"].shape == (K_clusters - 1,) + assert trace["alpha_dp"]["value"].shape == () + + def test_no_tier_A_per_pool_in_signature(self): + """The DP model should not accept tier_A_per_pool.""" + from quantammsim.noise_calibration.model import noise_model_dp_sigma + import inspect + + sig = inspect.signature(noise_model_dp_sigma) + assert "tier_A_per_pool" not in sig.parameters + + def test_prior_predictive_produces_y(self, synthetic_encoded_data): + """With y_obs=None, the model should sample y explicitly.""" + from quantammsim.noise_calibration.model import noise_model_dp_sigma + import jax + from numpyro.infer import Predictive + + kwargs = self._get_dp_model_kwargs(synthetic_encoded_data) + kwargs["y_obs"] = None + + predictive = Predictive(noise_model_dp_sigma, num_samples=5) + rng_key = jax.random.PRNGKey(42) + samples = predictive(rng_key, **kwargs) + assert "y" in samples + assert samples["y"].shape[0] == 5 + + def test_k_clusters_configurable(self, synthetic_encoded_data): + """K_clusters=4 should produce different sigma_eps shape.""" + from quantammsim.noise_calibration.model import noise_model_dp_sigma + import jax + import numpyro.handlers as handlers + + K_clusters = 4 + kwargs = self._get_dp_model_kwargs( + synthetic_encoded_data, K_clusters=K_clusters + ) + rng_key = jax.random.PRNGKey(0) + + trace = handlers.trace( + handlers.seed(noise_model_dp_sigma, rng_key) + ).get_trace(**kwargs) + + assert trace["sigma_eps"]["value"].shape == (K_clusters,) + assert trace["v"]["value"].shape == (K_clusters - 1,) + + +# =========================================================================== +# TestDPModelSVISmoke +# =========================================================================== + + +class TestDPModelSVISmoke: + def _build_dp_panel(self): + """Build a minimal panel for DP SVI testing.""" + from datetime import date, timedelta + import pandas as pd + + np.random.seed(42) + pools_spec = [ + ("p0", "MAINNET", "WETH,USDC", 0.003, 0, 0), + ("p1", "ARBITRUM", "BAL,WETH", 0.01, 0, 1), + ("p2", "BASE", "RATS,WETH", 0.005, 0, 2), + ] + dates = [date(2026, 1, 1) + timedelta(days=i) for i in range(15)] + records = [] + for pid, chain, tokens, fee, ta, tb in pools_spec: + base_tvl = 14 + np.random.randn() * 0.5 + for d in dates: + tvl = base_tvl + np.random.randn() * 0.1 + vol = tvl - 2 + np.random.randn() * 0.3 + records.append({ + "pool_id": pid, "chain": chain, "date": d, + "log_volume": vol, "log_tvl": tvl, + "volatility": 0.3 + np.random.rand() * 0.2, + "weekend": 1.0 if d.weekday() >= 5 else 0.0, + "log_fee": np.log(max(fee, 1e-6)), + "swap_fee": fee, "tier_A": ta, "tier_B": tb, + "tokens": tokens, + }) + panel = pd.DataFrame(records) + panel = panel.sort_values(["pool_id", "date"]).reset_index(drop=True) + panel["log_tvl_lag1"] = panel.groupby("pool_id")["log_tvl"].shift(1) + panel = panel.dropna(subset=["log_tvl_lag1"]).reset_index(drop=True) + return panel + + def test_svi_converges_dp_model(self): + """SVI on DP model with tiny data: ELBO should decrease.""" + import numpyro + numpyro.enable_x64() + + from quantammsim.noise_calibration import encode_covariates, run_svi + from quantammsim.noise_calibration.model import noise_model_dp_sigma + + panel = self._build_dp_panel() + data = encode_covariates(panel, include_tiers=False) + data["K_clusters"] = 4 + + samples, losses = run_svi( + data, num_steps=2000, lr=1e-3, seed=0, + num_samples=50, model_fn=noise_model_dp_sigma, + ) + assert np.mean(losses[-100:]) < np.mean(losses[:100]) + + def test_svi_samples_have_dp_keys(self): + """SVI output for DP model has v, alpha_dp, sigma_eps.""" + import numpyro + numpyro.enable_x64() + + from quantammsim.noise_calibration import encode_covariates, run_svi + from quantammsim.noise_calibration.model import noise_model_dp_sigma + + panel = self._build_dp_panel() + data = encode_covariates(panel, include_tiers=False) + data["K_clusters"] = 4 + + samples, _ = run_svi( + data, num_steps=500, lr=1e-3, seed=0, + num_samples=10, model_fn=noise_model_dp_sigma, + ) + required = {"B", "sigma_theta", "L_Omega", "eta", "df", + "sigma_eps", "v", "alpha_dp"} + assert required.issubset(samples.keys()), ( + f"Missing keys: {required - samples.keys()}" + ) diff --git a/tests/noise/test_model_structural.py b/tests/noise/test_model_structural.py new file mode 100644 index 0000000..582cdd5 --- /dev/null +++ b/tests/noise/test_model_structural.py @@ -0,0 +1,190 @@ +"""Tests for structural_noise_model definition and SVI integration.""" + +import numpy as np +import pytest + + +class TestStructuralModelDefinition: + """Tests for the structural_noise_model numpyro model.""" + + def test_model_traces_without_error(self, synthetic_structural_data): + import jax + from numpyro.infer import Predictive + from quantammsim.noise_calibration.model import structural_noise_model + from quantammsim.noise_calibration.inference import _build_model_kwargs + + kwargs = _build_model_kwargs(synthetic_structural_data, + model_fn=structural_noise_model) + predictive = Predictive(structural_noise_model, num_samples=2) + rng_key = jax.random.PRNGKey(0) + samples = predictive(rng_key, **kwargs) + assert "y" in samples + + def test_required_sites_present(self, synthetic_structural_data): + import jax + from numpyro.infer import Predictive + from quantammsim.noise_calibration.model import structural_noise_model + from quantammsim.noise_calibration.inference import _build_model_kwargs + + kwargs = _build_model_kwargs(synthetic_structural_data, + model_fn=structural_noise_model) + predictive = Predictive(structural_noise_model, num_samples=2) + samples = predictive(jax.random.PRNGKey(0), **kwargs) + required = { + "alpha_0", "alpha_chain", "alpha_tier", "alpha_tvl", + "B", "sigma_theta", "L_Omega", "eta", "theta", + "df", "sigma_eps", "y", + } + assert required.issubset(samples.keys()), ( + f"Missing sites: {required - samples.keys()}" + ) + + def test_no_moe_sites(self, synthetic_structural_data): + """MoE sites (W_gate, beta) should not be present.""" + import jax + from numpyro.infer import Predictive + from quantammsim.noise_calibration.model import structural_noise_model + from quantammsim.noise_calibration.inference import _build_model_kwargs + + kwargs = _build_model_kwargs(synthetic_structural_data, + model_fn=structural_noise_model) + predictive = Predictive(structural_noise_model, num_samples=2) + samples = predictive(jax.random.PRNGKey(0), **kwargs) + for site in ("W_gate", "beta"): + assert site not in samples, f"MoE site '{site}' should not be present" + + def test_theta_shape(self, synthetic_structural_data): + import jax + from numpyro.infer import Predictive + from quantammsim.noise_calibration.model import structural_noise_model + from quantammsim.noise_calibration.inference import _build_model_kwargs + + kwargs = _build_model_kwargs(synthetic_structural_data, + model_fn=structural_noise_model) + predictive = Predictive(structural_noise_model, num_samples=3) + samples = predictive(jax.random.PRNGKey(0), **kwargs) + + N_pools = synthetic_structural_data["N_pools"] + K_obs = synthetic_structural_data["x_obs"].shape[1] + assert samples["theta"].shape == (3, N_pools, K_obs) + + def test_B_shape(self, synthetic_structural_data): + import jax + from numpyro.infer import Predictive + from quantammsim.noise_calibration.model import structural_noise_model + from quantammsim.noise_calibration.inference import _build_model_kwargs + + kwargs = _build_model_kwargs(synthetic_structural_data, + model_fn=structural_noise_model) + predictive = Predictive(structural_noise_model, num_samples=3) + samples = predictive(jax.random.PRNGKey(0), **kwargs) + + K_obs = synthetic_structural_data["x_obs"].shape[1] + K_cov = synthetic_structural_data["K_cov"] + assert samples["B"].shape == (3, K_obs, K_cov) + + def test_alpha_chain_shape(self, synthetic_structural_data): + import jax + from numpyro.infer import Predictive + from quantammsim.noise_calibration.model import structural_noise_model + from quantammsim.noise_calibration.inference import _build_model_kwargs + + kwargs = _build_model_kwargs(synthetic_structural_data, + model_fn=structural_noise_model) + predictive = Predictive(structural_noise_model, num_samples=3) + samples = predictive(jax.random.PRNGKey(0), **kwargs) + + n_chains = synthetic_structural_data["n_chains"] + assert samples["alpha_chain"].shape == (3, n_chains - 1) + + def test_prior_predictive_produces_y(self, synthetic_structural_data): + """y_obs=None path produces y samples.""" + import jax + from numpyro.infer import Predictive + from quantammsim.noise_calibration.model import structural_noise_model + from quantammsim.noise_calibration.inference import _build_model_kwargs + + kwargs = _build_model_kwargs(synthetic_structural_data, + model_fn=structural_noise_model) + kwargs["y_obs"] = None + predictive = Predictive(structural_noise_model, num_samples=10) + samples = predictive(jax.random.PRNGKey(42), **kwargs) + assert "y" in samples + assert samples["y"].shape[0] == 10 + + def test_prior_predictive_range_reasonable(self, synthetic_structural_data): + """Prior y should contain finite values in a plausible range.""" + import jax + from numpyro.infer import Predictive + from quantammsim.noise_calibration.model import structural_noise_model + from quantammsim.noise_calibration.inference import _build_model_kwargs + + kwargs = _build_model_kwargs(synthetic_structural_data, + model_fn=structural_noise_model) + kwargs["y_obs"] = None + predictive = Predictive(structural_noise_model, num_samples=200) + samples = predictive(jax.random.PRNGKey(42), **kwargs) + y = np.array(samples["y"]) + finite = y[np.isfinite(y)] + assert len(finite) > 0.5 * y.size, "Too many non-finite prior samples" + median = np.median(finite) + assert median > -50, f"Prior y median too low: {median}" + assert median < 100, f"Prior y median too high: {median}" + + +class TestSVIStructural: + """SVI convergence tests for structural model.""" + + @pytest.mark.slow + def test_svi_structural_converges(self, synthetic_structural_data): + """2000 SVI steps, ELBO should decrease.""" + from quantammsim.noise_calibration.inference import run_svi + from quantammsim.noise_calibration.model import structural_noise_model + + samples, losses = run_svi( + synthetic_structural_data, + num_steps=2000, + lr=5e-3, + seed=0, + num_samples=10, + model_fn=structural_noise_model, + ) + assert losses[-100:].mean() < losses[:100].mean() + + @pytest.mark.slow + def test_svi_structural_samples_have_required_keys( + self, synthetic_structural_data, + ): + from quantammsim.noise_calibration.inference import run_svi + from quantammsim.noise_calibration.model import structural_noise_model + + samples, _ = run_svi( + synthetic_structural_data, + num_steps=500, + lr=5e-3, + seed=0, + num_samples=10, + model_fn=structural_noise_model, + ) + required = {"B", "sigma_theta", "L_Omega", "eta", + "alpha_0", "alpha_chain", + "alpha_tier", "alpha_tvl", "df", "sigma_eps"} + assert required.issubset(samples.keys()), ( + f"Missing keys: {required - samples.keys()}" + ) + + @pytest.mark.slow + def test_svi_structural_no_moe_keys(self, synthetic_structural_data): + from quantammsim.noise_calibration.inference import run_svi + from quantammsim.noise_calibration.model import structural_noise_model + + samples, _ = run_svi( + synthetic_structural_data, + num_steps=500, + lr=5e-3, + seed=0, + num_samples=10, + model_fn=structural_noise_model, + ) + for key in ("W_gate", "beta"): + assert key not in samples, f"MoE key '{key}' should not be in samples" diff --git a/tests/noise/test_output.py b/tests/noise/test_output.py new file mode 100644 index 0000000..2246c98 --- /dev/null +++ b/tests/noise/test_output.py @@ -0,0 +1,305 @@ +"""Tests for generate_output_json and _save_sample_cache.""" + +import json +import os + +import numpy as np +import pytest + +from quantammsim.noise_calibration import ( + extract_noise_params, + generate_output_json, + _save_sample_cache, + K_COEFF, +) + + +# =========================================================================== +# TestGenerateOutputJSON +# =========================================================================== + + +class TestGenerateOutputJSON: + @pytest.fixture() + def _output_setup(self, tmp_path, synthetic_samples, synthetic_encoded_data): + """Produce the JSON file and return (path, data dict).""" + pool_params = extract_noise_params( + synthetic_samples, synthetic_encoded_data + ) + output_path = str(tmp_path / "test_output.json") + convergence = {"method": "svi", "final_elbo": 1234.0} + inference_config = {"method": "svi", "svi_steps": 1000} + + generate_output_json( + pool_params, synthetic_samples, synthetic_encoded_data, + convergence, output_path, inference_config, + ) + with open(output_path) as f: + data = json.load(f) + return output_path, data + + def test_writes_valid_json(self, _output_setup): + path, data = _output_setup + assert isinstance(data, dict) + + def test_top_level_keys(self, _output_setup): + _, data = _output_setup + expected = { + "model", "model_spec", "inference", "population_effects", + "convergence", "n_pools", "n_obs", "pools", + } + assert expected.issubset(data.keys()) + + def test_model_spec_fields(self, _output_setup): + _, data = _output_setup + spec = data["model_spec"] + assert "K_coeff" in spec + assert "K_cov" in spec + assert "coeff_names" in spec + assert "covariate_names" in spec + assert "likelihood" in spec + assert spec["likelihood"] == "StudentT" + assert "tvl_lag" in spec + assert spec["tvl_lag"] == "log_tvl_lag1" + + def test_population_effects_fields(self, _output_setup): + _, data = _output_setup + pe = data["population_effects"] + assert "B" in pe + assert "sigma_theta" in pe + assert "sigma_eps" in pe + assert "df" in pe + assert "correlation_matrix" in pe + + def test_pool_entries(self, _output_setup, synthetic_encoded_data): + _, data = _output_setup + pools = data["pools"] + for pid in synthetic_encoded_data["pool_ids"]: + assert pid in pools + entry = pools[pid] + assert "chain" in entry + assert "tokens" in entry + assert "theta_median" in entry + assert "noise_params" in entry + + def test_correlation_matrix_symmetric_unit_diagonal(self, _output_setup): + _, data = _output_setup + Omega = np.array(data["population_effects"]["correlation_matrix"]) + np.testing.assert_allclose(Omega, Omega.T, atol=1e-10) + np.testing.assert_allclose(np.diag(Omega), 1.0, atol=1e-10) + + def test_n_pools_and_n_obs(self, _output_setup, synthetic_encoded_data): + _, data = _output_setup + assert data["n_pools"] == synthetic_encoded_data["N_pools"] + assert data["n_obs"] == len(synthetic_encoded_data["y_obs"]) + + def test_model_name(self, _output_setup): + _, data = _output_setup + assert data["model"] == "unified_hierarchical_student_t" + + +# =========================================================================== +# TestSaveSampleCache +# =========================================================================== + + +class TestSaveSampleCache: + def test_creates_npz_and_json( + self, tmp_path, synthetic_samples, synthetic_encoded_data + ): + cache_dir = str(tmp_path / "cache") + _save_sample_cache(synthetic_samples, synthetic_encoded_data, cache_dir) + + assert os.path.exists(os.path.join(cache_dir, "unified_samples.npz")) + assert os.path.exists(os.path.join(cache_dir, "unified_data.json")) + + def test_npz_excludes_y_and_theta( + self, tmp_path, synthetic_samples, synthetic_encoded_data + ): + """Both 'y' and 'theta' must be excluded from the npz cache.""" + samples_with_extras = dict(synthetic_samples) + samples_with_extras["y"] = np.random.randn(10, 27) + samples_with_extras["theta"] = np.random.randn(10, 3, 4) + + cache_dir = str(tmp_path / "cache2") + _save_sample_cache(samples_with_extras, synthetic_encoded_data, cache_dir) + + npz_path = os.path.join(cache_dir, "unified_samples.npz") + loaded = np.load(npz_path) + assert "y" not in loaded.files + assert "theta" not in loaded.files + + def test_npz_contains_required_keys( + self, tmp_path, synthetic_samples, synthetic_encoded_data + ): + """The npz must contain B, sigma_theta, L_Omega, eta, df, sigma_eps.""" + cache_dir = str(tmp_path / "cache3") + _save_sample_cache(synthetic_samples, synthetic_encoded_data, cache_dir) + + npz_path = os.path.join(cache_dir, "unified_samples.npz") + loaded = np.load(npz_path) + required = {"B", "sigma_theta", "L_Omega", "eta", "df", "sigma_eps"} + assert required.issubset(set(loaded.files)) + + def test_json_contains_metadata_keys( + self, tmp_path, synthetic_samples, synthetic_encoded_data + ): + """The JSON cache must contain all keys needed by --predict.""" + cache_dir = str(tmp_path / "cache4") + _save_sample_cache(synthetic_samples, synthetic_encoded_data, cache_dir) + + json_path = os.path.join(cache_dir, "unified_data.json") + with open(json_path) as f: + meta = json.load(f) + required = { + "pool_ids", "covariate_names", "K_cov", "N_pools", + "ref_chain", "ref_tier_a", "ref_tier_b", "chains", + } + assert required.issubset(meta.keys()) + + +# =========================================================================== +# TestGenerateOutputJSONDP +# =========================================================================== + + +class TestGenerateOutputJSONDP: + @pytest.fixture() + def _dp_output_setup(self, tmp_path, synthetic_encoded_data): + """Produce DP model JSON output and return (path, data dict).""" + data = synthetic_encoded_data + N_pools = data["N_pools"] + K_cov = data["K_cov"] + K_clusters = 4 + S = 10 + + np.random.seed(99) + dp_samples = { + "B": np.random.randn(S, K_COEFF, K_cov) * 0.5, + "sigma_theta": np.ones((S, K_COEFF)), + "L_Omega": np.tile(np.eye(K_COEFF), (S, 1, 1)), + "eta": np.zeros((S, N_pools, K_COEFF)), + "df": np.full((S,), 5.0), + "sigma_eps": np.tile([0.3, 0.8, 1.5, 2.5], (S, 1)), + "v": np.tile([0.6, 0.3, 0.05], (S, 1)), + "alpha_dp": np.full((S,), 1.5), + } + + pool_params = extract_noise_params(dp_samples, data) + output_path = str(tmp_path / "dp_output.json") + convergence = {"method": "svi", "final_elbo": 1234.0} + inference_config = {"method": "svi", "svi_steps": 1000} + + generate_output_json( + pool_params, dp_samples, data, + convergence, output_path, inference_config, + ) + with open(output_path) as f: + result = json.load(f) + return output_path, result + + def test_sigma_eps_structure_is_dp_mixture(self, _dp_output_setup): + _, data = _dp_output_setup + assert data["model_spec"]["sigma_eps_structure"] == "dp_mixture" + + def test_model_name_includes_dp(self, _dp_output_setup): + _, data = _dp_output_setup + assert "dp_sigma" in data["model"] + + def test_has_cluster_weights(self, _dp_output_setup): + _, data = _dp_output_setup + assert "cluster_weights" in data["population_effects"] + + def test_sigma_eps_length_equals_k_clusters(self, _dp_output_setup): + _, data = _dp_output_setup + sigma_eps = data["population_effects"]["sigma_eps"] + assert len(sigma_eps) == 4 # K_clusters = 4 + + def test_cluster_weights_sum_to_one(self, _dp_output_setup): + _, data = _dp_output_setup + w = data["population_effects"]["cluster_weights"] + np.testing.assert_allclose(sum(w), 1.0, atol=1e-4) + + def test_still_has_standard_fields(self, _dp_output_setup): + _, data = _dp_output_setup + expected = { + "model", "model_spec", "inference", "population_effects", + "convergence", "n_pools", "n_obs", "pools", + } + assert expected.issubset(data.keys()) + + +# =========================================================================== +# TestGenerateOutputJSONStructural +# =========================================================================== + + +class TestGenerateOutputJSONStructural: + @pytest.fixture() + def _structural_output_setup(self, tmp_path, synthetic_structural_data): + """Produce structural model JSON output and return (path, data dict).""" + from quantammsim.noise_calibration.postprocessing import ( + extract_structural_params, + ) + from quantammsim.noise_calibration.constants import K_OBS_COEFF + + data = synthetic_structural_data + K_cov = data["K_cov"] + N_pools = data["N_pools"] + n_chains = data["n_chains"] + n_tiers = data["n_tiers"] + S = 10 + + np.random.seed(77) + structural_samples = { + "alpha_0": np.random.randn(S) * 0.1 + 2.0, + "alpha_chain": np.random.randn(S, n_chains - 1) * 0.1, + "alpha_tier": np.random.randn(S, n_tiers - 1) * 0.1, + "alpha_tvl": np.random.randn(S) * 0.01, + "B": np.random.randn(S, K_OBS_COEFF, K_cov) * 0.5, + "sigma_theta": np.ones((S, K_OBS_COEFF)), + "L_Omega": np.tile(np.eye(K_OBS_COEFF), (S, 1, 1)), + "eta": np.zeros((S, N_pools, K_OBS_COEFF)), + "df": np.full((S,), 5.0), + "sigma_eps": np.tile([0.5, 0.8, 0.6], (S, 1)), + } + + pool_params = extract_structural_params(structural_samples, data) + output_path = str(tmp_path / "structural_output.json") + convergence = {"method": "svi", "final_elbo": 999.0} + inference_config = {"method": "svi", "svi_steps": 2000} + + generate_output_json( + pool_params, structural_samples, data, + convergence, output_path, inference_config, + ) + with open(output_path) as f: + result = json.load(f) + return output_path, result + + def test_output_model_name(self, _structural_output_setup): + _, data = _structural_output_setup + assert data["model"] == "structural_mixture" + + def test_output_has_arb_params(self, _structural_output_setup): + _, data = _structural_output_setup + pe = data["population_effects"] + assert "alpha_0" in pe + assert "alpha_chain" in pe + assert "alpha_tier" in pe + assert "alpha_tvl" in pe + + def test_output_has_hierarchical_noise_params(self, _structural_output_setup): + _, data = _structural_output_setup + pe = data["population_effects"] + assert "B" in pe + assert "sigma_theta" in pe + assert "correlation_matrix" in pe + + def test_output_pools_have_arb_frequency(self, _structural_output_setup): + _, data = _structural_output_setup + pools = data["pools"] + for pid, entry in pools.items(): + assert "arb_frequency" in entry + assert isinstance(entry["arb_frequency"], int) + assert 1 <= entry["arb_frequency"] <= 60 diff --git a/tests/noise/test_panel_assembly.py b/tests/noise/test_panel_assembly.py new file mode 100644 index 0000000..dd7cd31 --- /dev/null +++ b/tests/noise/test_panel_assembly.py @@ -0,0 +1,442 @@ +"""Tests for compute_pair_volatility, assemble_panel, validate_panel.""" + +from datetime import date, timedelta + +import numpy as np +import pandas as pd +import pytest + +from quantammsim.noise_calibration import ( + compute_pair_volatility, + assemble_panel, + validate_panel, +) + + +# =========================================================================== +# TestComputePairVolatility +# =========================================================================== + + +class TestComputePairVolatility: + @pytest.fixture() + def _snap_dates(self): + """10 unique dates for snapshot stub.""" + dates = [date(2026, 1, 1) + timedelta(days=i) for i in range(10)] + return pd.DataFrame({"date": dates}) + + def test_stablecoin_pair_returns_001(self, _snap_dates): + pool_row = pd.Series({ + "tokens": ["USDC", "DAI"], + "chain": "MAINNET", + }) + vol = compute_pair_volatility(_snap_dates, pool_row, {}) + assert (vol == 0.01).all() + + def test_both_missing_non_stable(self, _snap_dates): + pool_row = pd.Series({ + "tokens": ["FOO", "BAR"], + "chain": "MAINNET", + }) + vol = compute_pair_volatility(_snap_dates, pool_row, {}) + assert (vol == 0.5).all() + + def test_one_missing_non_stable(self, _snap_dates): + np.random.seed(77) + pool_row = pd.Series({ + "tokens": ["WETH", "BAR"], + "chain": "MAINNET", + }) + prices = { + ("MAINNET", "WETH"): pd.DataFrame({ + "timestamp": [1735689600 + i * 3600 for i in range(48)], + "price": [3000.0 + np.random.randn() * 10 for _ in range(48)], + }), + } + vol = compute_pair_volatility(_snap_dates, pool_row, prices) + assert (vol == 0.5).all() + + def test_synthetic_hourly_prices_positive_finite(self, _snap_dates): + np.random.seed(42) + n_hours = 240 # 10 days x 24 hours + ts_base = 1735689600 + timestamps = [ts_base + i * 3600 for i in range(n_hours)] + prices_a = np.exp(np.cumsum(np.random.randn(n_hours) * 0.01) + 8) + prices_b = np.exp(np.cumsum(np.random.randn(n_hours) * 0.01) + 7) + + pool_row = pd.Series({ + "tokens": ["WETH", "LINK"], + "chain": "MAINNET", + }) + token_prices = { + ("MAINNET", "WETH"): pd.DataFrame({ + "timestamp": timestamps, "price": prices_a, + }), + ("MAINNET", "LINK"): pd.DataFrame({ + "timestamp": timestamps, "price": prices_b, + }), + } + vol = compute_pair_volatility(_snap_dates, pool_row, token_prices) + assert len(vol) > 0 + assert (vol > 0).all() + assert np.all(np.isfinite(vol)) + + def test_annualisation_uses_sqrt_24x365(self, _snap_dates): + """Verify the annualisation factor is sqrt(24*365).""" + np.random.seed(7) + n_hours = 240 + ts_base = 1735689600 + timestamps = [ts_base + i * 3600 for i in range(n_hours)] + raw_prices = np.exp(np.cumsum(np.random.randn(n_hours) * 0.005) + 8) + + pool_row = pd.Series({ + "tokens": ["WETH", "USDC"], + "chain": "MAINNET", + }) + token_prices = { + ("MAINNET", "WETH"): pd.DataFrame({ + "timestamp": timestamps, "price": raw_prices, + }), + } + vol = compute_pair_volatility(_snap_dates, pool_row, token_prices) + + # Reconstruct manually + df = pd.DataFrame({"timestamp": timestamps, "price": raw_prices}) + df["datetime"] = pd.to_datetime(df["timestamp"], unit="s") + df["date"] = df["datetime"].dt.date + df["ratio"] = df["price"] # WETH vs stable => ratio = price + df["log_return"] = np.log(df["ratio"] / df["ratio"].shift(1)) + df = df.dropna(subset=["log_return"]) + daily_std = df.groupby("date")["log_return"].std() + expected = daily_std * np.sqrt(24 * 365) + + common = vol.index.intersection(expected.index) + assert len(common) > 0 + np.testing.assert_allclose( + vol.loc[common].values, expected.loc[common].values, rtol=1e-10, + ) + + def test_single_token_pool_returns_empty(self, _snap_dates): + pool_row = pd.Series({"tokens": ["WETH"], "chain": "MAINNET"}) + vol = compute_pair_volatility(_snap_dates, pool_row, {}) + assert len(vol) == 0 + + def test_no_overlapping_price_dates(self, _snap_dates): + """Two tokens with non-overlapping timestamps -> fallback 0.5.""" + pool_row = pd.Series({ + "tokens": ["WETH", "LINK"], + "chain": "MAINNET", + }) + token_prices = { + ("MAINNET", "WETH"): pd.DataFrame({ + "timestamp": [1000000 + i for i in range(10)], + "price": [3000.0] * 10, + }), + ("MAINNET", "LINK"): pd.DataFrame({ + "timestamp": [9000000 + i for i in range(10)], + "price": [15.0] * 10, + }), + } + vol = compute_pair_volatility(_snap_dates, pool_row, token_prices) + assert (vol == 0.5).all() + + def test_cross_chain_price_fallback(self, _snap_dates): + """Prices keyed to a different chain SHOULD be used as fallback. + + Token prices are chain-agnostic (WETH is WETH regardless of chain), + so an ARBITRUM pool should use MAINNET WETH prices if ARBITRUM + prices aren't available. + """ + np.random.seed(55) + n_hours = 240 + ts_base = 1735689600 + timestamps = [ts_base + i * 3600 for i in range(n_hours)] + prices = np.exp(np.cumsum(np.random.randn(n_hours) * 0.01) + 8) + + pool_row = pd.Series({ + "tokens": ["WETH", "USDC"], + "chain": "ARBITRUM", + }) + token_prices = { + # Only MAINNET prices, but ARBITRUM pool should still use them + ("MAINNET", "WETH"): pd.DataFrame({ + "timestamp": timestamps, "price": prices.tolist(), + }), + } + vol = compute_pair_volatility(_snap_dates, pool_row, token_prices) + # Should get real volatility, NOT the 0.5 fallback + assert len(vol) > 0 + assert not (vol == 0.5).all(), ( + "Cross-chain price fallback should have produced real volatility" + ) + + +# =========================================================================== +# TestAssemblePanel +# =========================================================================== + + +class TestAssemblePanel: + def test_lagged_tvl_drops_first_obs_per_pool( + self, synthetic_pools_df, synthetic_snapshots_df + ): + panel = assemble_panel(synthetic_pools_df, synthetic_snapshots_df, {}) + counts = panel.groupby("pool_id").size() + assert (counts == 9).all() + + def test_lagged_tvl_exact_values( + self, synthetic_pools_df, synthetic_snapshots_df + ): + """log_tvl_lag1[t] must equal log_tvl[t-1] for same pool (shift(1)).""" + panel = assemble_panel(synthetic_pools_df, synthetic_snapshots_df, {}) + for pid in panel["pool_id"].unique(): + pool = panel[panel["pool_id"] == pid].sort_values("date") + tvl_vals = pool["log_tvl"].values + lag_vals = pool["log_tvl_lag1"].values + # After dropping the first obs, lag[i] = tvl[i-1] in the original + # pre-drop series. Since the panel is sorted by date, each + # lag value should equal the log_tvl of the chronologically + # preceding observation. We verify consecutive pairs: for rows + # i and i+1, lag[i+1] == tvl[i]. + for i in range(len(tvl_vals) - 1): + np.testing.assert_allclose( + lag_vals[i + 1], tvl_vals[i], rtol=1e-14, + err_msg=f"Pool {pid} row {i+1}: lag should equal previous tvl", + ) + + def test_weekend_flag(self, synthetic_pools_df, synthetic_snapshots_df): + panel = assemble_panel(synthetic_pools_df, synthetic_snapshots_df, {}) + for _, row in panel.iterrows(): + d = row["date"] + if not isinstance(d, date): + d = pd.Timestamp(d).date() + expected = 1.0 if d.weekday() >= 5 else 0.0 + assert row["weekend"] == expected, f"Wrong weekend flag for {d}" + + def test_log_volume_is_natural_log( + self, synthetic_pools_df, synthetic_snapshots_df + ): + """log_volume must equal ln(volume_usd), not log10 or log2.""" + panel = assemble_panel(synthetic_pools_df, synthetic_snapshots_df, {}) + snaps = synthetic_snapshots_df.copy() + # Join snapshots to panel by pool_id + date to verify exact log values + for _, row in panel.iterrows(): + pid = row["pool_id"] + d = row["date"] + snap_match = snaps[ + (snaps["pool_id"] == pid) & (snaps["date"] == d) + ] + assert len(snap_match) == 1, f"No snapshot for {pid} on {d}" + expected = np.log(snap_match.iloc[0]["volume_usd"]) + np.testing.assert_allclose( + row["log_volume"], expected, rtol=1e-14, + err_msg=f"log_volume for {pid} on {d} should be ln(volume_usd)", + ) + + def test_tier_assignment_min_first( + self, synthetic_pools_df, synthetic_snapshots_df + ): + panel = assemble_panel(synthetic_pools_df, synthetic_snapshots_df, {}) + # Pool A: WETH(0), USDC(0) -> tier_A=0, tier_B=0 + pool_a = panel[panel["pool_id"] == "pool_A"].iloc[0] + assert pool_a["tier_A"] == 0 + assert pool_a["tier_B"] == 0 + + # Pool B: BAL(1), WETH(0) -> tier_A=0, tier_B=1 + pool_b = panel[panel["pool_id"] == "pool_B"].iloc[0] + assert pool_b["tier_A"] == 0 + assert pool_b["tier_B"] == 1 + + # Pool C: RATS(2), WETH(0) -> tier_A=0, tier_B=2 + pool_c = panel[panel["pool_id"] == "pool_C"].iloc[0] + assert pool_c["tier_A"] == 0 + assert pool_c["tier_B"] == 2 + + def test_log_fee(self, synthetic_pools_df, synthetic_snapshots_df): + panel = assemble_panel(synthetic_pools_df, synthetic_snapshots_df, {}) + pool_a = panel[panel["pool_id"] == "pool_A"].iloc[0] + assert np.isclose(pool_a["log_fee"], np.log(0.003)) + + def test_all_expected_columns( + self, synthetic_pools_df, synthetic_snapshots_df + ): + panel = assemble_panel(synthetic_pools_df, synthetic_snapshots_df, {}) + expected_cols = { + "pool_id", "chain", "date", "log_volume", "log_tvl", + "log_tvl_lag1", "volatility", "weekend", "log_fee", + "swap_fee", "tier_A", "tier_B", "tokens", + } + assert expected_cols.issubset(set(panel.columns)) + + def test_zero_volume_rows_dropped(self, synthetic_pools_df): + """Rows with volume_usd <= 0 must be excluded from the panel.""" + dates = [date(2026, 1, 1) + timedelta(days=i) for i in range(5)] + records = [] + for d in dates: + records.append({ + "pool_id": "pool_A", "chain": "MAINNET", "date": d, + "volume_usd": 1000.0, + "total_liquidity_usd": 100000.0, + }) + # Add a zero-volume row + records.append({ + "pool_id": "pool_A", "chain": "MAINNET", + "date": date(2026, 1, 6), + "volume_usd": 0.0, + "total_liquidity_usd": 100000.0, + }) + snaps = pd.DataFrame(records) + panel = assemble_panel(synthetic_pools_df, snaps, {}) + pool_a = panel[panel["pool_id"] == "pool_A"] + # 5 valid rows, minus 1 for lag = 4 (the zero-volume row is skipped) + assert len(pool_a) == 4 + + def test_zero_tvl_rows_dropped(self, synthetic_pools_df): + """Rows with total_liquidity_usd <= 0 must be excluded.""" + dates = [date(2026, 1, 1) + timedelta(days=i) for i in range(5)] + records = [] + for d in dates: + records.append({ + "pool_id": "pool_A", "chain": "MAINNET", "date": d, + "volume_usd": 1000.0, + "total_liquidity_usd": 100000.0, + }) + # Add a zero-TVL row + records.append({ + "pool_id": "pool_A", "chain": "MAINNET", + "date": date(2026, 1, 6), + "volume_usd": 1000.0, + "total_liquidity_usd": 0.0, + }) + snaps = pd.DataFrame(records) + panel = assemble_panel(synthetic_pools_df, snaps, {}) + pool_a = panel[panel["pool_id"] == "pool_A"] + assert len(pool_a) == 4 + + +# =========================================================================== +# TestSyntheticPanelColumns — structural model covariates in the fixture +# =========================================================================== + + +class TestSyntheticPanelColumns: + """Tests that the synthetic_panel fixture has new structural columns.""" + + def test_panel_has_log_sigma(self, synthetic_panel): + assert "log_sigma" in synthetic_panel.columns + expected = np.log(np.maximum(synthetic_panel["volatility"].values, 1e-6)) + np.testing.assert_allclose( + synthetic_panel["log_sigma"].values, expected, rtol=1e-12, + ) + + def test_panel_has_dow_harmonics(self, synthetic_panel): + assert "dow_sin" in synthetic_panel.columns + assert "dow_cos" in synthetic_panel.columns + assert (synthetic_panel["dow_sin"] >= -1.0).all() + assert (synthetic_panel["dow_sin"] <= 1.0).all() + assert (synthetic_panel["dow_cos"] >= -1.0).all() + assert (synthetic_panel["dow_cos"] <= 1.0).all() + + def test_panel_has_interactions(self, synthetic_panel): + assert "tvl_x_sigma" in synthetic_panel.columns + assert "tvl_x_fee" in synthetic_panel.columns + assert "sigma_x_fee" in synthetic_panel.columns + + def test_dow_harmonics_correct_for_known_date(self, synthetic_panel): + """2026-01-03 is Saturday (weekday=5), so dow=5.""" + sat_rows = synthetic_panel[ + synthetic_panel["date"] == date(2026, 1, 3) + ] + if len(sat_rows) == 0: + pytest.skip("No Saturday rows in fixture") + expected_sin = np.sin(2 * np.pi * 5 / 7) + expected_cos = np.cos(2 * np.pi * 5 / 7) + np.testing.assert_allclose( + sat_rows["dow_sin"].values[0], expected_sin, atol=1e-12, + ) + np.testing.assert_allclose( + sat_rows["dow_cos"].values[0], expected_cos, atol=1e-12, + ) + + def test_interactions_use_lagged_tvl(self, synthetic_panel): + """tvl_x_sigma must use log_tvl_lag1, not log_tvl.""" + expected = ( + synthetic_panel["log_tvl_lag1"].values + * synthetic_panel["log_sigma"].values + ) + np.testing.assert_allclose( + synthetic_panel["tvl_x_sigma"].values, expected, rtol=1e-12, + ) + + +# =========================================================================== +# TestAssemblePanelStructuralColumns — new columns from real pipeline +# =========================================================================== + + +class TestAssemblePanelStructuralColumns: + """Tests that assemble_panel() produces new structural columns.""" + + def test_assemble_panel_has_log_sigma( + self, synthetic_pools_df, synthetic_snapshots_df + ): + panel = assemble_panel(synthetic_pools_df, synthetic_snapshots_df, {}) + assert "log_sigma" in panel.columns + expected = np.log(np.maximum(panel["volatility"].values, 1e-6)) + np.testing.assert_allclose( + panel["log_sigma"].values, expected, rtol=1e-12, + ) + + def test_assemble_panel_has_dow_harmonics( + self, synthetic_pools_df, synthetic_snapshots_df + ): + panel = assemble_panel(synthetic_pools_df, synthetic_snapshots_df, {}) + assert "dow_sin" in panel.columns + assert "dow_cos" in panel.columns + + def test_assemble_panel_has_interactions( + self, synthetic_pools_df, synthetic_snapshots_df + ): + panel = assemble_panel(synthetic_pools_df, synthetic_snapshots_df, {}) + assert "tvl_x_sigma" in panel.columns + assert "tvl_x_fee" in panel.columns + assert "sigma_x_fee" in panel.columns + # Interactions use lagged TVL + expected = panel["log_tvl_lag1"].values * panel["log_sigma"].values + np.testing.assert_allclose( + panel["tvl_x_sigma"].values, expected, rtol=1e-12, + ) + + +# =========================================================================== +# TestValidatePanel +# =========================================================================== + + +class TestValidatePanel: + def test_flags_constant_volume(self, synthetic_panel, capsys): + panel = synthetic_panel.copy() + mask = panel["pool_id"] == "pool_A" + panel.loc[mask, "log_volume"] = 10.0 + validate_panel(panel) + captured = capsys.readouterr() + assert "near-constant" in captured.out or "constant" in captured.out.lower() + + def test_flags_tvl_jumps(self, synthetic_panel, capsys): + panel = synthetic_panel.copy() + idx = panel[panel["pool_id"] == "pool_A"].index[1] + panel.loc[idx, "log_tvl"] = panel.loc[idx, "log_tvl"] + 5.0 + validate_panel(panel) + captured = capsys.readouterr() + assert "TVL jumps" in captured.out + + def test_flags_volume_exceeds_tvl(self, synthetic_panel, capsys): + panel = synthetic_panel.copy() + panel["log_volume"] = panel["log_tvl"] + 1.0 + validate_panel(panel) + captured = capsys.readouterr() + assert "volume > TVL" in captured.out + + def test_returns_dataframe_unchanged(self, synthetic_panel): + result = validate_panel(synthetic_panel) + pd.testing.assert_frame_equal(result, synthetic_panel) diff --git a/tests/noise/test_postprocessing.py b/tests/noise/test_postprocessing.py new file mode 100644 index 0000000..c40e5dd --- /dev/null +++ b/tests/noise/test_postprocessing.py @@ -0,0 +1,533 @@ +"""Tests for extract_noise_params, predict_new_pool, check_convergence, +assign_dp_clusters, and structural model post-processing.""" + +import numpy as np +import pytest + +from quantammsim.noise_calibration import ( + extract_noise_params, + predict_new_pool, + check_convergence, + classify_token_tier, + _get_theta_samples, + K_COEFF, + COEFF_NAMES, +) +from quantammsim.noise_calibration.constants import K_OBS_COEFF, OBS_COEFF_NAMES + + +# =========================================================================== +# TestExtractNoiseParams +# =========================================================================== + + +class TestExtractNoiseParams: + def test_output_length(self, synthetic_samples, synthetic_encoded_data): + result = extract_noise_params(synthetic_samples, synthetic_encoded_data) + assert len(result) == synthetic_encoded_data["N_pools"] + + def test_weekend_absorption(self, synthetic_samples, synthetic_encoded_data): + """b_0_eff = b_0_raw + b_weekend * (2/7).""" + result = extract_noise_params(synthetic_samples, synthetic_encoded_data) + + theta = _get_theta_samples( + synthetic_samples, synthetic_encoded_data["X_pool"] + ) + theta_med = np.median(theta, axis=0) + + for i, p in enumerate(result): + b_0_raw = theta_med[i, 0] + b_weekend = theta_med[i, 3] + expected_b_0 = b_0_raw + b_weekend * (2.0 / 7.0) + np.testing.assert_allclose( + p["noise_params"]["b_0"], expected_b_0, atol=1e-10, + ) + + def test_noise_params_keys(self, synthetic_samples, synthetic_encoded_data): + result = extract_noise_params(synthetic_samples, synthetic_encoded_data) + expected_keys = {"b_0", "b_sigma", "b_c", "b_weekend", "base_fee"} + for p in result: + assert set(p["noise_params"].keys()) == expected_keys + + def test_theta_median_length(self, synthetic_samples, synthetic_encoded_data): + result = extract_noise_params(synthetic_samples, synthetic_encoded_data) + for p in result: + assert len(p["theta_median"]) == K_COEFF + + def test_b_c_equals_theta_1(self, synthetic_samples, synthetic_encoded_data): + result = extract_noise_params(synthetic_samples, synthetic_encoded_data) + for p in result: + np.testing.assert_allclose( + p["noise_params"]["b_c"], p["theta_median"][1], atol=1e-10, + ) + + def test_b_sigma_equals_theta_2( + self, synthetic_samples, synthetic_encoded_data + ): + result = extract_noise_params(synthetic_samples, synthetic_encoded_data) + for p in result: + np.testing.assert_allclose( + p["noise_params"]["b_sigma"], p["theta_median"][2], atol=1e-10, + ) + + def test_base_fee_matches_pool( + self, synthetic_samples, synthetic_encoded_data + ): + result = extract_noise_params(synthetic_samples, synthetic_encoded_data) + pool_meta = synthetic_encoded_data["pool_meta"] + for i, p in enumerate(result): + expected_fee = pool_meta.iloc[i]["swap_fee"] + np.testing.assert_allclose( + p["noise_params"]["base_fee"], expected_fee, atol=1e-10, + ) + + def test_pool_id_and_chain_preserved( + self, synthetic_samples, synthetic_encoded_data + ): + result = extract_noise_params(synthetic_samples, synthetic_encoded_data) + pool_ids = synthetic_encoded_data["pool_ids"] + pool_meta = synthetic_encoded_data["pool_meta"] + for i, p in enumerate(result): + assert p["pool_id"] == pool_ids[i] + assert p["chain"] == str(pool_meta.iloc[i]["chain"]) + + def test_use_median_false_uses_mean( + self, synthetic_samples, synthetic_encoded_data + ): + """use_median=False must produce different values than use_median=True.""" + result_med = extract_noise_params( + synthetic_samples, synthetic_encoded_data, use_median=True + ) + result_mean = extract_noise_params( + synthetic_samples, synthetic_encoded_data, use_median=False + ) + assert len(result_mean) == len(result_med) + + # With random B samples (S=10, seed 99), median != mean for at least + # one pool. Check that at least one theta_median value differs. + any_differ = False + for pm, pn in zip(result_med, result_mean): + for tm, tn in zip(pm["theta_median"], pn["theta_median"]): + if not np.isclose(tm, tn, atol=1e-14): + any_differ = True + break + if any_differ: + break + assert any_differ, "median and mean paths produced identical values" + + +# =========================================================================== +# TestPredictNewPool +# =========================================================================== + + +class TestPredictNewPool: + def _build_z_new(self, data, chain, tokens, fee): + """Reconstruct z_new the same way predict_new_pool does internally.""" + col_names = data["covariate_names"] + z_new = np.zeros(len(col_names), dtype=np.float64) + + tiers = sorted([classify_token_tier(t) for t in tokens]) + tier_a = str(tiers[0]) + tier_b = str(tiers[1]) if len(tiers) > 1 else tier_a + + for i, name in enumerate(col_names): + if name == "intercept": + z_new[i] = 1.0 + elif name == "log_fee": + z_new[i] = np.log(max(fee, 1e-6)) + elif name == f"chain_{chain}": + z_new[i] = 1.0 + elif name == f"tier_A_{tier_a}": + z_new[i] = 1.0 + elif name == f"tier_B_{tier_b}": + z_new[i] = 1.0 + return z_new + + def test_known_chain_sets_dummy( + self, synthetic_samples, synthetic_encoded_data + ): + """ARBITRUM dummy must be 1 and must affect the prediction vs MAINNET.""" + data = synthetic_encoded_data + + result_arb = predict_new_pool( + synthetic_samples, data, + chain="ARBITRUM", tokens=["WETH", "USDC"], fee=0.003, + ) + result_main = predict_new_pool( + synthetic_samples, data, + chain="MAINNET", tokens=["WETH", "USDC"], fee=0.003, + ) + # MAINNET is the reference chain (alphabetically: ARBITRUM < BASE < MAINNET). + # Wait — ARBITRUM is alphabetically first, so ARBITRUM is the reference. + # MAINNET has a chain_MAINNET dummy. Both should produce different mu. + # If chain dummies are ignored, these would be identical. + arb_b0 = result_arb["noise_params"]["b_0"] + main_b0 = result_main["noise_params"]["b_0"] + assert not np.isclose(arb_b0, main_b0, atol=1e-10), ( + "Different chains should produce different predictions" + ) + + def test_tier_assignment_affects_prediction( + self, synthetic_samples, synthetic_encoded_data + ): + """WETH/RATS (tier 0,2) vs WETH/USDC (tier 0,0) must differ.""" + data = synthetic_encoded_data + + result_rats = predict_new_pool( + synthetic_samples, data, + chain="BASE", tokens=["WETH", "RATS"], fee=0.005, + ) + result_usdc = predict_new_pool( + synthetic_samples, data, + chain="BASE", tokens=["WETH", "USDC"], fee=0.005, + ) + # Different tier_B dummies should give different mu + rats_b0 = result_rats["noise_params"]["b_0"] + usdc_b0 = result_usdc["noise_params"]["b_0"] + assert not np.isclose(rats_b0, usdc_b0, atol=1e-10), ( + "Different tier assignments should produce different predictions" + ) + + def test_weekend_absorption_arithmetic( + self, synthetic_samples, synthetic_encoded_data + ): + """b_0 in noise_params must equal mu_median[0] + mu_median[3] * (2/7).""" + data = synthetic_encoded_data + result = predict_new_pool( + synthetic_samples, data, + chain="MAINNET", tokens=["WETH", "USDC"], fee=0.003, + ) + # Reconstruct mu_median independently + z_new = self._build_z_new(data, "MAINNET", ["WETH", "USDC"], 0.003) + B = synthetic_samples["B"] + mu_samples = np.einsum("skd,d->sk", B, z_new) + mu_median = np.median(mu_samples, axis=0) + + b_0_raw = mu_median[0] + b_weekend = mu_median[3] + expected_b_0 = b_0_raw + b_weekend * (2.0 / 7.0) + np.testing.assert_allclose( + result["noise_params"]["b_0"], expected_b_0, atol=1e-10, + ) + + def test_mu_equals_b_at_z_new( + self, synthetic_samples, synthetic_encoded_data + ): + """With known B samples, verify credible interval medians = median(B @ z_new).""" + data = synthetic_encoded_data + z_new = self._build_z_new(data, "ARBITRUM", ["WETH", "RATS"], 0.005) + + B = synthetic_samples["B"] + mu_expected = np.einsum("skd,d->sk", B, z_new) + mu_median_expected = np.median(mu_expected, axis=0) + + result = predict_new_pool( + synthetic_samples, data, + chain="ARBITRUM", tokens=["WETH", "RATS"], fee=0.005, + ) + for k, name in enumerate(COEFF_NAMES): + np.testing.assert_allclose( + result["credible_intervals_90"][name]["median"], + mu_median_expected[k], + atol=1e-10, + ) + + def test_unseen_chain_uses_reference( + self, synthetic_samples, synthetic_encoded_data + ): + """A chain not in training data should get reference-chain prediction + (all chain dummies = 0), not raise an error.""" + data = synthetic_encoded_data + result = predict_new_pool( + synthetic_samples, data, + chain="SONIC", tokens=["WETH", "USDC"], fee=0.003, + ) + # Should be same as ARBITRUM (the reference chain, all dummies 0) + result_ref = predict_new_pool( + synthetic_samples, data, + chain="ARBITRUM", tokens=["WETH", "USDC"], fee=0.003, + ) + np.testing.assert_allclose( + result["noise_params"]["b_0"], + result_ref["noise_params"]["b_0"], + atol=1e-10, + ) + + +# =========================================================================== +# TestCheckConvergence +# =========================================================================== + + +class TestCheckConvergence: + def test_svi_returns_expected_keys(self): + losses = np.random.randn(1000).cumsum() + 5000 + result = check_convergence(losses, method="svi") + assert "final_elbo" in result + assert "elbo_last_100_std" in result + assert "elbo_last_100_mean" in result + + def test_svi_method_key(self): + losses = np.linspace(5000, 1000, 500) + result = check_convergence(losses, method="svi") + assert result["method"] == "svi" + + def test_svi_elbo_last_100_std_correct(self): + np.random.seed(42) + losses = np.random.randn(500) * 10 + 1000 + result = check_convergence(losses, method="svi") + expected_std = float(np.std(losses[-100:])) + np.testing.assert_allclose( + result["elbo_last_100_std"], expected_std, atol=1e-10, + ) + + def test_svi_final_elbo_is_last_loss(self): + losses = np.array([100.0, 50.0, 25.0, 12.5]) + result = check_convergence(losses, method="svi") + assert result["final_elbo"] == 12.5 + + +# =========================================================================== +# TestAssignDPClusters +# =========================================================================== + + +class TestAssignDPClusters: + @pytest.fixture() + def dp_samples_and_data(self, synthetic_encoded_data): + """Synthetic DP posterior samples with known cluster structure.""" + data = synthetic_encoded_data + N_pools = data["N_pools"] + K_cov = data["K_cov"] + K_clusters = 4 + S = 10 + + np.random.seed(99) + B = np.random.randn(S, K_COEFF, K_cov) * 0.5 + sigma_theta = np.ones((S, K_COEFF)) + L_Omega = np.tile(np.eye(K_COEFF), (S, 1, 1)) + eta = np.zeros((S, N_pools, K_COEFF)) + df = np.full((S,), 5.0) + + # Well-separated sigma_eps clusters + sigma_eps = np.tile([0.3, 0.8, 1.5, 2.5], (S, 1)) + # Stick-breaking weights: mostly on cluster 0 + v = np.tile([0.6, 0.3, 0.05], (S, 1)) + + samples = { + "B": B, "sigma_theta": sigma_theta, "L_Omega": L_Omega, + "eta": eta, "df": df, "sigma_eps": sigma_eps, "v": v, + } + data_with_k = dict(data) + data_with_k["K_clusters"] = K_clusters + return samples, data_with_k + + def test_returns_correct_length(self, dp_samples_and_data): + from quantammsim.noise_calibration.postprocessing import assign_dp_clusters + + samples, data = dp_samples_and_data + assignments = assign_dp_clusters(samples, data) + assert len(assignments) == data["N_pools"] + + def test_valid_cluster_indices(self, dp_samples_and_data): + from quantammsim.noise_calibration.postprocessing import assign_dp_clusters + + samples, data = dp_samples_and_data + assignments = assign_dp_clusters(samples, data) + K = data["K_clusters"] + assert all(0 <= a < K for a in assignments) + + def test_returns_integer_array(self, dp_samples_and_data): + from quantammsim.noise_calibration.postprocessing import assign_dp_clusters + + samples, data = dp_samples_and_data + assignments = assign_dp_clusters(samples, data) + assert assignments.dtype in (np.int32, np.int64, int) + + +# =========================================================================== +# TestExtractNoiseParamsDP +# =========================================================================== + + +class TestExtractNoiseParamsDP: + @pytest.fixture() + def dp_samples_and_data(self, synthetic_encoded_data): + """Same as above for extract_noise_params testing.""" + data = synthetic_encoded_data + N_pools = data["N_pools"] + K_cov = data["K_cov"] + S = 10 + + np.random.seed(99) + B = np.random.randn(S, K_COEFF, K_cov) * 0.5 + sigma_theta = np.ones((S, K_COEFF)) + L_Omega = np.tile(np.eye(K_COEFF), (S, 1, 1)) + eta = np.zeros((S, N_pools, K_COEFF)) + df = np.full((S,), 5.0) + sigma_eps = np.tile([0.3, 0.8, 1.5, 2.5], (S, 1)) + v = np.tile([0.6, 0.3, 0.05], (S, 1)) + + samples = { + "B": B, "sigma_theta": sigma_theta, "L_Omega": L_Omega, + "eta": eta, "df": df, "sigma_eps": sigma_eps, "v": v, + } + return samples, data + + def test_output_length(self, dp_samples_and_data): + samples, data = dp_samples_and_data + result = extract_noise_params(samples, data) + assert len(result) == data["N_pools"] + + def test_noise_params_keys_present(self, dp_samples_and_data): + samples, data = dp_samples_and_data + result = extract_noise_params(samples, data) + expected_keys = {"b_0", "b_sigma", "b_c", "b_weekend", "base_fee"} + for p in result: + assert set(p["noise_params"].keys()) == expected_keys + + +# =========================================================================== +# TestExtractStructuralParams +# =========================================================================== + + +class TestExtractStructuralParams: + """Tests for extract_structural_params().""" + + @pytest.fixture() + def structural_samples_and_data(self, synthetic_structural_data): + """Run a quick SVI fit to get structural samples.""" + from quantammsim.noise_calibration.inference import run_svi + from quantammsim.noise_calibration.model import structural_noise_model + + samples, _ = run_svi( + synthetic_structural_data, + num_steps=500, + lr=5e-3, + seed=0, + num_samples=10, + model_fn=structural_noise_model, + ) + return samples, synthetic_structural_data + + def test_extract_returns_arb_params(self, structural_samples_and_data): + from quantammsim.noise_calibration.postprocessing import ( + extract_structural_params, + ) + samples, data = structural_samples_and_data + result = extract_structural_params(samples, data) + assert len(result) == data["N_pools"] + for p in result: + assert "arb_frequency" in p + assert isinstance(p["arb_frequency"], int) + assert 1 <= p["arb_frequency"] <= 60 + + def test_extract_returns_noise_params(self, structural_samples_and_data): + from quantammsim.noise_calibration.postprocessing import ( + extract_structural_params, + ) + samples, data = structural_samples_and_data + result = extract_structural_params(samples, data) + for p in result: + assert "noise_params" in p + coeffs = p["noise_params"] + assert len(coeffs) == K_OBS_COEFF + for name in OBS_COEFF_NAMES: + assert name in coeffs, f"Missing coefficient: {name}" + + +# =========================================================================== +# TestPredictStructural +# =========================================================================== + + +class TestPredictStructural: + @pytest.fixture() + def structural_samples_and_data(self, synthetic_structural_data): + from quantammsim.noise_calibration.inference import run_svi + from quantammsim.noise_calibration.model import structural_noise_model + + samples, _ = run_svi( + synthetic_structural_data, + num_steps=500, + lr=5e-3, + seed=0, + num_samples=10, + model_fn=structural_noise_model, + ) + return samples, synthetic_structural_data + + def test_predict_returns_cadence(self, structural_samples_and_data): + from quantammsim.noise_calibration.postprocessing import ( + predict_new_pool_structural, + ) + samples, data = structural_samples_and_data + result = predict_new_pool_structural( + samples, data, + chain="MAINNET", tokens=["WETH", "USDC"], + fee=0.003, tvl_est=1e6, + ) + assert "arb_frequency" in result + assert isinstance(result["arb_frequency"], int) + assert 1 <= result["arb_frequency"] <= 60 + + def test_predict_returns_noise_coefficients( + self, structural_samples_and_data, + ): + from quantammsim.noise_calibration.postprocessing import ( + predict_new_pool_structural, + ) + samples, data = structural_samples_and_data + result = predict_new_pool_structural( + samples, data, + chain="MAINNET", tokens=["WETH", "USDC"], + fee=0.003, tvl_est=1e6, + ) + assert "noise_params" in result + assert len(result["noise_params"]) == K_OBS_COEFF + + def test_predict_uses_B_regression(self, structural_samples_and_data): + """Different (chain, tier) → different predictions via B regression.""" + from quantammsim.noise_calibration.postprocessing import ( + predict_new_pool_structural, + ) + samples, data = structural_samples_and_data + r1 = predict_new_pool_structural( + samples, data, + chain="MAINNET", tokens=["WETH", "USDC"], + fee=0.003, tvl_est=1e6, + ) + r2 = predict_new_pool_structural( + samples, data, + chain="ARBITRUM", tokens=["BAL", "WETH"], + fee=0.01, tvl_est=5e5, + ) + # Different pool characteristics should produce different noise params + assert r1["noise_params"] != r2["noise_params"] + + def test_predict_cadence_higher_for_longtail( + self, structural_samples_and_data, + ): + """Long-tail pools should have higher cadence (less efficient arb).""" + from quantammsim.noise_calibration.postprocessing import ( + predict_new_pool_structural, + ) + samples, data = structural_samples_and_data + # This tests the structural relationship — it may not hold with + # random SVI samples on synthetic data, so we just check it doesn't + # crash and returns valid values + r1 = predict_new_pool_structural( + samples, data, + chain="MAINNET", tokens=["WETH", "USDC"], + fee=0.003, tvl_est=1e6, + ) + r2 = predict_new_pool_structural( + samples, data, + chain="BASE", tokens=["RATS", "WETH"], + fee=0.005, tvl_est=1e5, + ) + # Both should be valid + assert 1 <= r1["arb_frequency"] <= 60 + assert 1 <= r2["arb_frequency"] <= 60 diff --git a/tests/noise/test_token_classification.py b/tests/noise/test_token_classification.py new file mode 100644 index 0000000..dd540f8 --- /dev/null +++ b/tests/noise/test_token_classification.py @@ -0,0 +1,66 @@ +"""Tests for _normalise_symbol and classify_token_tier.""" + +import pytest +from quantammsim.noise_calibration import _normalise_symbol, classify_token_tier + + +# =========================================================================== +# TestNormaliseSymbol +# =========================================================================== + + +class TestNormaliseSymbol: + def test_passthrough_unknown(self): + assert _normalise_symbol("FOO") == "FOO" + + def test_known_mapping_preserved(self): + assert _normalise_symbol("WETH") == "WETH" + assert _normalise_symbol("WBTC") == "WBTC" + assert _normalise_symbol("cbBTC") == "cbBTC" + + def test_whitespace_stripped(self): + assert _normalise_symbol(" ETH ") == "ETH" + assert _normalise_symbol(" WETH ") == "WETH" + + def test_case_sensitivity_preserved(self): + # Lowercase is NOT normalised to uppercase + assert _normalise_symbol("weth") == "weth" + + +# =========================================================================== +# TestClassifyTokenTier +# =========================================================================== + + +class TestClassifyTokenTier: + def test_tier0_native_tokens(self): + for sym in ["ETH", "BTC"]: + assert classify_token_tier(sym) == 0, f"{sym} should be tier 0" + + def test_tier0_wrapped(self): + for sym in ["WETH", "WBTC", "cbBTC"]: + assert classify_token_tier(sym) == 0, f"{sym} should be tier 0" + + def test_tier0_stablecoins(self): + for sym in ["USDC", "USDT", "DAI"]: + assert classify_token_tier(sym) == 0, f"{sym} should be tier 0" + + def test_tier0_chain_natives(self): + for sym in ["MATIC", "AVAX", "GNO", "S", "wS"]: + assert classify_token_tier(sym) == 0, f"{sym} should be tier 0" + + def test_tier1_defi_bluechips(self): + for sym in ["AAVE", "BAL", "COW", "LINK", "ARB"]: + assert classify_token_tier(sym) == 1, f"{sym} should be tier 1" + + def test_tier2_unknown_tokens(self): + for sym in ["RATS", "PEPE"]: + assert classify_token_tier(sym) == 2, f"{sym} should be tier 2" + + def test_tier2_empty_string(self): + assert classify_token_tier("") == 2 + + def test_wrapped_variant_normalisation(self): + # "wS" is in the mapping table AND in _TIER_0 + assert classify_token_tier("wS") == 0 + assert classify_token_tier(" wS ") == 0 diff --git a/tests/pools/reCLAMM/helpers.py b/tests/pools/reCLAMM/helpers.py new file mode 100644 index 0000000..4d06f70 --- /dev/null +++ b/tests/pools/reCLAMM/helpers.py @@ -0,0 +1,15 @@ +"""Test-only exports for reCLAMM diagnostic kernels. + +This module centralizes access to kernels that return virtual-balance history. +Production paths should use reserve-only kernels in ``reclamm_reserves``. +""" + +from quantammsim.pools.reCLAMM.reclamm_reserves import ( + _jax_calc_reclamm_reserves_with_dynamic_inputs_full_state, + _jax_calc_reclamm_reserves_zero_fees_full_state, +) + +__all__ = [ + "_jax_calc_reclamm_reserves_zero_fees_full_state", + "_jax_calc_reclamm_reserves_with_dynamic_inputs_full_state", +] diff --git a/tests/pools/reCLAMM/test_reclamm_differentiability.py b/tests/pools/reCLAMM/test_reclamm_differentiability.py new file mode 100644 index 0000000..29fdcbe --- /dev/null +++ b/tests/pools/reCLAMM/test_reclamm_differentiability.py @@ -0,0 +1,167 @@ +"""Differentiability tests for reCLAMM STE-gated training path behavior.""" + +import jax +import jax.numpy as jnp +import numpy as np +import numpy.testing as npt + +from quantammsim.pools.creator import create_pool +from quantammsim.pools.reCLAMM.reclamm_reserves import ( + initialise_reclamm_reserves, + _jax_calc_reclamm_reserves_zero_fees, + _jax_calc_reclamm_reserves_and_fee_revenue_with_dynamic_inputs, +) +from quantammsim.runners.jax_runner_utils import Hashabledict + + +ALL_SIG_VARIATIONS_2 = jnp.array([[1, -1], [-1, 1]]) +DEFAULT_POOL_VALUE = 1_000_000.0 +DEFAULT_INITIAL_PRICES = jnp.array([2500.0, 1.0], dtype=jnp.float64) +DEFAULT_PRICE_RATIO = 4.0 +DEFAULT_SHIFT_BASE = 1.0 - 1.0 / 124000.0 +DEFAULT_SECONDS_PER_STEP = 60.0 + + +def _init_pool_state(): + return initialise_reclamm_reserves( + DEFAULT_POOL_VALUE, + DEFAULT_INITIAL_PRICES, + DEFAULT_PRICE_RATIO, + ) + + +def _trending_prices(n_steps): + return jnp.stack( + [jnp.linspace(DEFAULT_INITIAL_PRICES[0], 4200.0, n_steps), jnp.ones((n_steps,))], + axis=1, + ) + + +def test_ste_forward_outputs_are_temperature_invariant(): + """STE hard-forward path should be invariant to STE temperature.""" + reserves, Va, Vb = _init_pool_state() + n_steps = 12 + prices = _trending_prices(n_steps) + fees = jnp.full((n_steps,), 0.003, dtype=jnp.float64) + arb_thresh = jnp.zeros((n_steps,), dtype=jnp.float64) + arb_fees = jnp.full((n_steps,), 0.0005, dtype=jnp.float64) + + schedule = np.zeros((n_steps, 4), dtype=np.float64) + schedule[:, 3] = np.nan + schedule[2] = np.array([1.0, 6.0, 7.0, DEFAULT_PRICE_RATIO], dtype=np.float64) + schedule = jnp.asarray(schedule) + + low_temp_reserves, low_temp_fee_revenue = ( + _jax_calc_reclamm_reserves_and_fee_revenue_with_dynamic_inputs( + reserves, + Va, + Vb, + prices, + centeredness_margin=0.2, + daily_price_shift_base=DEFAULT_SHIFT_BASE, + seconds_per_step=DEFAULT_SECONDS_PER_STEP, + fees=fees, + arb_thresh=arb_thresh, + arb_fees=arb_fees, + price_ratio_updates=schedule, + all_sig_variations=ALL_SIG_VARIATIONS_2, + ste_temperature=3.0, + ) + ) + high_temp_reserves, high_temp_fee_revenue = ( + _jax_calc_reclamm_reserves_and_fee_revenue_with_dynamic_inputs( + reserves, + Va, + Vb, + prices, + centeredness_margin=0.2, + daily_price_shift_base=DEFAULT_SHIFT_BASE, + seconds_per_step=DEFAULT_SECONDS_PER_STEP, + fees=fees, + arb_thresh=arb_thresh, + arb_fees=arb_fees, + price_ratio_updates=schedule, + all_sig_variations=ALL_SIG_VARIATIONS_2, + ste_temperature=50.0, + ) + ) + npt.assert_allclose(high_temp_reserves, low_temp_reserves, rtol=1e-10, atol=1e-10) + npt.assert_allclose( + high_temp_fee_revenue, low_temp_fee_revenue, rtol=1e-10, atol=1e-10 + ) + + +def test_margin_gradient_is_finite_and_nonzero_in_zero_fee_kernel(): + """Centeredness-margin gradient should flow through always-on STE gates.""" + reserves, Va, Vb = _init_pool_state() + n_steps = 6 + prices = jnp.tile(DEFAULT_INITIAL_PRICES, (n_steps, 1)) + margin = jnp.float64(1.0) + + def _loss(centeredness_margin): + reserves_out = _jax_calc_reclamm_reserves_zero_fees( + reserves, + Va, + Vb, + prices, + centeredness_margin=centeredness_margin, + daily_price_shift_base=DEFAULT_SHIFT_BASE, + seconds_per_step=DEFAULT_SECONDS_PER_STEP, + ste_temperature=25.0, + ) + return jnp.sum(reserves_out[-1]) + + grad_val = jax.grad(_loss)(margin) + + assert jnp.isfinite(grad_val) + assert jnp.abs(grad_val) > 1e-9 + + +def test_pool_zero_fee_path_uses_configured_ste_temperature(): + """Pool-level path should pass STE temperature through to kernel gradients.""" + pool = create_pool("reclamm") + n_steps = 6 + prices = jnp.tile(DEFAULT_INITIAL_PRICES, (n_steps, 1)) + start_index = jnp.array([0, 0], dtype=jnp.int32) + + run_fp_low_temp = Hashabledict( + { + "n_assets": 2, + "bout_length": n_steps + 1, + "initial_pool_value": DEFAULT_POOL_VALUE, + "arb_frequency": 1, + "do_arb": True, + "ste_temperature": 2.0, + } + ) + run_fp_high_temp = Hashabledict( + { + "n_assets": 2, + "bout_length": n_steps + 1, + "initial_pool_value": DEFAULT_POOL_VALUE, + "arb_frequency": 1, + "do_arb": True, + "ste_temperature": 50.0, + } + ) + + def _loss(centeredness_margin, run_fingerprint): + params = { + "price_ratio": jnp.float64(DEFAULT_PRICE_RATIO), + "centeredness_margin": centeredness_margin, + "daily_price_shift_base": jnp.float64(DEFAULT_SHIFT_BASE), + } + reserves_out = pool.calculate_reserves_zero_fees( + params, run_fingerprint, prices, start_index + ) + return jnp.sum(reserves_out[-1]) + + margin = jnp.float64(1.0) + low_temp_grad = jax.grad(lambda m: _loss(m, run_fp_low_temp))(margin) + high_temp_grad = jax.grad(lambda m: _loss(m, run_fp_high_temp))(margin) + + assert jnp.isfinite(low_temp_grad) + assert jnp.isfinite(high_temp_grad) + assert jnp.abs(low_temp_grad) > 1e-9 + assert jnp.abs(high_temp_grad) > 1e-9 + assert jnp.abs(high_temp_grad) > jnp.abs(low_temp_grad) * 1.5 diff --git a/tests/pools/reCLAMM/test_reclamm_e2e.py b/tests/pools/reCLAMM/test_reclamm_e2e.py index 25edc98..80eeef6 100644 --- a/tests/pools/reCLAMM/test_reclamm_e2e.py +++ b/tests/pools/reCLAMM/test_reclamm_e2e.py @@ -30,8 +30,8 @@ initialise_reclamm_reserves, _jax_calc_reclamm_reserves_zero_fees, _jax_calc_reclamm_reserves_with_fees, - _jax_calc_reclamm_reserves_zero_fees_full_state, ) +from tests.pools.reCLAMM.helpers import _jax_calc_reclamm_reserves_zero_fees_full_state ALL_SIG_VARIATIONS_2 = jnp.array([[1, -1], [-1, 1]]) diff --git a/tests/pools/reCLAMM/test_reclamm_fee_revenue.py b/tests/pools/reCLAMM/test_reclamm_fee_revenue.py index 9406a96..2becdc7 100644 --- a/tests/pools/reCLAMM/test_reclamm_fee_revenue.py +++ b/tests/pools/reCLAMM/test_reclamm_fee_revenue.py @@ -9,6 +9,7 @@ import numpy as np import numpy.testing as npt +from quantammsim.core_simulator.dynamic_inputs import DynamicInputArrays from quantammsim.pools.reCLAMM.reclamm_reserves import ( initialise_reclamm_reserves, _jax_calc_reclamm_reserves_with_fees, @@ -48,6 +49,18 @@ def _init_pool(initial_pool_value=1_000_000.0, price_a=2500.0, price_b=1.0, return reserves, Va, Vb +def _mm_observed_noise_kwargs(prices, noise_base=13.8, competitor_tvl=1e7): + # lp_fee_revenue_usd is noise-only by design, so arb-only configs report 0. + # mm_observed is the simplest noise model to wire (two constants vs. e.g. + # tsoukalas which needs volatility + noise_params). + n = prices.shape[0] + return { + "noise_model": "mm_observed", + "noise_base_array": jnp.full(n, noise_base), + "competitor_tvl_array": jnp.full(n, competitor_tvl), + } + + class TestFeeRevenueShape: """_jax_calc_reclamm_reserves_and_fee_revenue_with_fees returns correct shapes.""" @@ -111,6 +124,7 @@ def test_fee_revenue_positive_on_price_jump(self): arb_thresh=0.0, arb_fees=0.0, all_sig_variations=ALL_SIG_VARIATIONS_2, + **_mm_observed_noise_kwargs(prices), ) assert float(fee_revenue.sum()) > 0, ( f"Expected positive total fee revenue on trending prices, got {float(fee_revenue.sum())}" @@ -135,6 +149,7 @@ def test_higher_fees_more_revenue(self): arb_thresh=0.0, arb_fees=0.0, all_sig_variations=ALL_SIG_VARIATIONS_2, + **_mm_observed_noise_kwargs(prices), ) _, fee_revenue_high = _jax_calc_reclamm_reserves_and_fee_revenue_with_fees( @@ -146,6 +161,7 @@ def test_higher_fees_more_revenue(self): arb_thresh=0.0, arb_fees=0.0, all_sig_variations=ALL_SIG_VARIATIONS_2, + **_mm_observed_noise_kwargs(prices), ) assert float(fee_revenue_high.sum()) > float(fee_revenue_low.sum()), ( @@ -172,6 +188,7 @@ def test_protocol_split_reduces_lp_revenue(self): arb_fees=0.0, all_sig_variations=ALL_SIG_VARIATIONS_2, protocol_fee_split=0.0, + **_mm_observed_noise_kwargs(prices), ) _, fee_revenue_half_split = _jax_calc_reclamm_reserves_and_fee_revenue_with_fees( @@ -184,6 +201,7 @@ def test_protocol_split_reduces_lp_revenue(self): arb_fees=0.0, all_sig_variations=ALL_SIG_VARIATIONS_2, protocol_fee_split=0.5, + **_mm_observed_noise_kwargs(prices), ) total_no_split = float(fee_revenue_no_split.sum()) @@ -255,6 +273,7 @@ def test_dynamic_inputs_fee_revenue(self): arb_thresh=arb_thresh, arb_fees=arb_fees, all_sig_variations=ALL_SIG_VARIATIONS_2, + **_mm_observed_noise_kwargs(prices), ) assert result_reserves.shape == (n_steps, 2) @@ -267,6 +286,7 @@ class TestPoolMethodWithFees: """pool.calculate_reserves_and_fee_revenue_with_fees returns correct tuple.""" def test_pool_method_with_fees(self): + from quantammsim.core_simulator.dynamic_inputs import DynamicInputArrays from quantammsim.pools.creator import create_pool from quantammsim.runners.jax_runner_utils import Hashabledict @@ -295,6 +315,7 @@ def test_pool_method_with_fees(self): "tokens": ("ETH", "USDC"), "numeraire": "USDC", "all_sig_variations": tuple(map(tuple, [[1, -1], [-1, 1]])), + "ste_temperature": 10.0, }) start_index = jnp.array([0, 0]) @@ -340,6 +361,7 @@ def test_pool_method_with_dynamic_inputs(self): "tokens": ("ETH", "USDC"), "numeraire": "USDC", "all_sig_variations": tuple(map(tuple, [[1, -1], [-1, 1]])), + "ste_temperature": 10.0, }) start_index = jnp.array([0, 0]) @@ -347,13 +369,21 @@ def test_pool_method_with_dynamic_inputs(self): fees_array = jnp.array([0.003]) arb_thresh_array = jnp.array([0.0]) arb_fees_array = jnp.array([0.0]) + dynamic_inputs = DynamicInputArrays( + trades=jnp.zeros((1, 3)), + fees=fees_array, + gas_cost=arb_thresh_array, + arb_fees=arb_fees_array, + lp_supply=jnp.ones((1,)), + reclamm_price_ratio_updates=jnp.array([[0.0, 0.0, 0.0, jnp.nan]]), + ) reserves, fee_revenue = pool.calculate_reserves_and_fee_revenue_with_dynamic_inputs( - params, run_fingerprint, prices, start_index, - fees_array=fees_array, - arb_thresh_array=arb_thresh_array, - arb_fees_array=arb_fees_array, - trade_array=None, + params, + run_fingerprint, + prices, + start_index, + dynamic_inputs=dynamic_inputs, ) assert reserves.shape == (n_steps, 2) @@ -398,6 +428,7 @@ def test_forward_pass_returns_fee_revenue(self): "rule": "reclamm", "training_data_kind": "historic", "do_trades": False, + "ste_temperature": 10.0, }) start_index = jnp.array([0, 0]) diff --git a/tests/pools/reCLAMM/test_reclamm_math.py b/tests/pools/reCLAMM/test_reclamm_math.py index 6f3870d..b1ce8ee 100644 --- a/tests/pools/reCLAMM/test_reclamm_math.py +++ b/tests/pools/reCLAMM/test_reclamm_math.py @@ -729,9 +729,9 @@ def test_arc_length_single_step_exact(self): def test_arc_length_constant_through_scan(self): """Through the scan, per-step Δs should be approximately constant.""" - from quantammsim.pools.reCLAMM.reclamm_reserves import ( + from quantammsim.pools.reCLAMM.reclamm_reserves import calibrate_arc_length_speed + from tests.pools.reCLAMM.helpers import ( _jax_calc_reclamm_reserves_zero_fees_full_state, - calibrate_arc_length_speed, ) Ra, Rb, Va, Vb, Q = _centered_pool(P=2.0, price_ratio=4.0) diff --git a/tests/pools/reCLAMM/test_reclamm_noise_volume.py b/tests/pools/reCLAMM/test_reclamm_noise_volume.py new file mode 100644 index 0000000..500ae07 --- /dev/null +++ b/tests/pools/reCLAMM/test_reclamm_noise_volume.py @@ -0,0 +1,996 @@ +"""Tests for reClAMM Tsoukalas noise volume model. + +Tests noise volume functions (sqrt and log variants), volatility computation, +scan step integration, pool class plumbing, and OLS calibration. +""" + +import pytest +import jax.numpy as jnp +import numpy as np +import numpy.testing as npt + +from quantammsim.pools.noise_trades import ( + reclamm_tsoukalas_sqrt_noise_volume, + reclamm_tsoukalas_log_noise_volume, + reclamm_loglinear_noise_volume, +) + +# Typical noise_params for a mid-cap pool. +# a_c=1.5 is roughly equivalent to the old a_c_real=1.0 + a_c_virt=0.5 +# for pools with comparable real and virtual TVL: 1.5/sqrt(2) ≈ 1.06 +# per-component, but we use 1.5 to ensure noise income dominates the +# arb-suppression side-effect in integration tests. +DEFAULT_NOISE_PARAMS = { + "a_0_base": 0.5, + "a_f": 0.0, + "a_sigma": 2.0, + "a_c": 1.5, + "base_fee": 0.003, +} + + +# --------------------------------------------------------------------------- +# Tests 1-7: Unit tests for noise volume functions +# --------------------------------------------------------------------------- + + +class TestPositiveOutputReasonableInputs: + """Test 1: Volume > 0 for typical inputs (both sqrt and log variants).""" + + def test_sqrt_positive_output(self): + vol = reclamm_tsoukalas_sqrt_noise_volume( + effective_value_usd=15_000_000.0, + gamma=0.997, + volatility=0.5, + arb_volume_this_period=0.0, + noise_params=DEFAULT_NOISE_PARAMS, + ) + assert float(vol) > 0, f"Expected positive noise volume, got {float(vol)}" + + def test_log_positive_output(self): + vol = reclamm_tsoukalas_log_noise_volume( + effective_value_usd=15_000_000.0, + gamma=0.997, + volatility=0.5, + arb_volume_this_period=0.0, + noise_params=DEFAULT_NOISE_PARAMS, + ) + assert float(vol) > 0, f"Expected positive noise volume, got {float(vol)}" + + +class TestZeroWhenArbExceedsPredicted: + """Test 2: noise = max(0, daily/1440 - arb), so returns 0 when arb dominates.""" + + def test_sqrt_zero_when_arb_large(self): + vol = reclamm_tsoukalas_sqrt_noise_volume( + effective_value_usd=3_000_000.0, + gamma=0.997, + volatility=0.3, + arb_volume_this_period=1e12, # Absurdly large arb + noise_params=DEFAULT_NOISE_PARAMS, + ) + assert float(vol) == 0.0, f"Expected zero noise volume, got {float(vol)}" + + def test_log_zero_when_arb_large(self): + vol = reclamm_tsoukalas_log_noise_volume( + effective_value_usd=3_000_000.0, + gamma=0.997, + volatility=0.3, + arb_volume_this_period=1e12, + noise_params=DEFAULT_NOISE_PARAMS, + ) + assert float(vol) == 0.0, f"Expected zero noise volume, got {float(vol)}" + + +class TestMonotonicInEffectiveTVL: + """Test 3: Higher effective TVL -> more predicted volume.""" + + def test_sqrt_monotonic_effective_tvl(self): + kwargs = dict( + gamma=0.997, volatility=0.5, arb_volume_this_period=0.0, + noise_params=DEFAULT_NOISE_PARAMS, + ) + vol_low = reclamm_tsoukalas_sqrt_noise_volume(effective_value_usd=3_000_000.0, **kwargs) + vol_high = reclamm_tsoukalas_sqrt_noise_volume(effective_value_usd=20_000_000.0, **kwargs) + assert float(vol_high) > float(vol_low) + + def test_log_monotonic_effective_tvl(self): + kwargs = dict( + gamma=0.997, volatility=0.5, arb_volume_this_period=0.0, + noise_params=DEFAULT_NOISE_PARAMS, + ) + vol_low = reclamm_tsoukalas_log_noise_volume(effective_value_usd=3_000_000.0, **kwargs) + vol_high = reclamm_tsoukalas_log_noise_volume(effective_value_usd=20_000_000.0, **kwargs) + assert float(vol_high) > float(vol_low) + + +class TestMonotonicInVolatility: + """Test 4: Higher volatility -> more predicted volume.""" + + def test_sqrt_monotonic_volatility(self): + kwargs = dict( + effective_value_usd=10_000_000.0, + gamma=0.997, arb_volume_this_period=0.0, + noise_params=DEFAULT_NOISE_PARAMS, + ) + vol_low = reclamm_tsoukalas_sqrt_noise_volume(volatility=0.2, **kwargs) + vol_high = reclamm_tsoukalas_sqrt_noise_volume(volatility=0.8, **kwargs) + assert float(vol_high) > float(vol_low) + + def test_log_monotonic_volatility(self): + kwargs = dict( + effective_value_usd=10_000_000.0, + gamma=0.997, arb_volume_this_period=0.0, + noise_params=DEFAULT_NOISE_PARAMS, + ) + vol_low = reclamm_tsoukalas_log_noise_volume(volatility=0.2, **kwargs) + vol_high = reclamm_tsoukalas_log_noise_volume(volatility=0.8, **kwargs) + assert float(vol_high) > float(vol_low) + + +class TestEffectiveTVLSensitivity: + """Test 5: Changing effective TVL changes output.""" + + def test_sqrt_tvl_sensitivity(self): + base = dict( + gamma=0.997, volatility=0.5, arb_volume_this_period=0.0, + noise_params=DEFAULT_NOISE_PARAMS, + ) + v1 = reclamm_tsoukalas_sqrt_noise_volume( + effective_value_usd=7_000_000.0, **base) + v2 = reclamm_tsoukalas_sqrt_noise_volume( + effective_value_usd=13_000_000.0, **base) + assert float(v1) != float(v2), "Effective TVL change should affect output" + + def test_log_tvl_sensitivity(self): + base = dict( + gamma=0.997, volatility=0.5, arb_volume_this_period=0.0, + noise_params=DEFAULT_NOISE_PARAMS, + ) + v1 = reclamm_tsoukalas_log_noise_volume( + effective_value_usd=7_000_000.0, **base) + v2 = reclamm_tsoukalas_log_noise_volume( + effective_value_usd=13_000_000.0, **base) + assert float(v1) != float(v2), "Effective TVL change should affect output" + + +class TestCustomParamsOverrideDefaults: + """Test 6: noise_params dict values are actually used.""" + + def test_sqrt_custom_params(self): + # With a_sigma=0, volatility shouldn't matter + zero_sigma_params = {**DEFAULT_NOISE_PARAMS, "a_sigma": 0.0} + v1 = reclamm_tsoukalas_sqrt_noise_volume( + effective_value_usd=10_000_000.0, + gamma=0.997, volatility=0.2, arb_volume_this_period=0.0, + noise_params=zero_sigma_params, + ) + v2 = reclamm_tsoukalas_sqrt_noise_volume( + effective_value_usd=10_000_000.0, + gamma=0.997, volatility=0.8, arb_volume_this_period=0.0, + noise_params=zero_sigma_params, + ) + npt.assert_allclose(float(v1), float(v2), rtol=1e-10, + err_msg="With a_sigma=0, volatility should not affect output") + + def test_log_custom_params(self): + zero_sigma_params = {**DEFAULT_NOISE_PARAMS, "a_sigma": 0.0} + v1 = reclamm_tsoukalas_log_noise_volume( + effective_value_usd=10_000_000.0, + gamma=0.997, volatility=0.2, arb_volume_this_period=0.0, + noise_params=zero_sigma_params, + ) + v2 = reclamm_tsoukalas_log_noise_volume( + effective_value_usd=10_000_000.0, + gamma=0.997, volatility=0.8, arb_volume_this_period=0.0, + noise_params=zero_sigma_params, + ) + npt.assert_allclose(float(v1), float(v2), rtol=1e-10, + err_msg="With a_sigma=0, volatility should not affect output") + + +class TestCalculateVolatilityArray: + """Test calculate_volatility_array on the base pool class (JIT'd, pure JAX).""" + + def _make_run_fp(self): + from quantammsim.runners.jax_runner_utils import Hashabledict + return Hashabledict({"tokens": ("ETH", "USDC"), "numeraire": "USDC"}) + + def _make_pool(self): + from quantammsim.pools.creator import create_pool + return create_pool("reclamm") + + def test_output_shape_matches_input(self): + """Volatility array length matches input price length.""" + pool = self._make_pool() + n_minutes = 1440 * 3 # 3 days + rng = np.random.default_rng(42) + log_rets = rng.normal(0, 0.001, (n_minutes, 2)) + prices = jnp.array( + np.exp(np.cumsum(log_rets, axis=0)) * np.array([2500.0, 1.0]) + ) + vol_array = pool.calculate_volatility_array(prices, self._make_run_fp()) + assert vol_array.shape == (n_minutes,), ( + f"Expected shape ({n_minutes},), got {vol_array.shape}" + ) + + def test_constant_prices_zero_vol(self): + """Constant prices should give zero volatility.""" + pool = self._make_pool() + n_minutes = 1440 * 2 + prices = jnp.tile(jnp.array([2500.0, 1.0]), (n_minutes, 1)) + vol_array = pool.calculate_volatility_array(prices, self._make_run_fp()) + npt.assert_allclose(np.array(vol_array), 0.0, atol=1e-10) + + def test_volatile_prices_positive_vol(self): + """Volatile prices should give positive volatility.""" + pool = self._make_pool() + n_minutes = 1440 * 2 + rng = np.random.default_rng(123) + log_rets = rng.normal(0, 0.01, n_minutes) + price_ratio = np.exp(np.cumsum(log_rets)) + prices = jnp.array( + np.column_stack([price_ratio * 2500.0, np.ones(n_minutes)]) + ) + vol_array = pool.calculate_volatility_array(prices, self._make_run_fp()) + assert float(jnp.mean(vol_array)) > 0, "Volatile prices should give positive vol" + + def test_partial_last_day_handled(self): + """Non-multiple-of-1440: correct shape and partial-day fill uses last day's vol.""" + pool = self._make_pool() + n_full_days = 2 + n_partial = 500 + n_minutes = 1440 * n_full_days + n_partial + + # Use volatile prices so daily vol is nonzero + rng = np.random.default_rng(77) + log_rets = rng.normal(0, 0.005, n_minutes) + price_ratio = np.exp(np.cumsum(log_rets)) + prices = jnp.array( + np.column_stack([price_ratio * 2500.0, np.ones(n_minutes)]) + ) + vol_array = pool.calculate_volatility_array(prices, self._make_run_fp()) + + assert vol_array.shape == (n_minutes,) + + # The partial-day region (last 500 minutes) should be filled + # with the last full day's volatility value + last_full_day_vol = vol_array[n_full_days * 1440 - 1] + partial_region = vol_array[n_full_days * 1440:] + npt.assert_allclose( + np.array(partial_region), + float(last_full_day_vol), + rtol=1e-10, + err_msg="Partial-day region should be filled with last full day's vol", + ) + # And the fill value should be nonzero (volatile prices) + assert float(last_full_day_vol) > 0, "Expected nonzero vol from volatile prices" + + +# --------------------------------------------------------------------------- +# Tests 8-12: Scan step integration tests +# --------------------------------------------------------------------------- + +from quantammsim.pools.reCLAMM.reclamm_reserves import ( + initialise_reclamm_reserves, + _jax_calc_reclamm_reserves_with_fees, + _jax_calc_reclamm_reserves_and_fee_revenue_with_fees, + _jax_calc_reclamm_reserves_and_fee_revenue_with_dynamic_inputs, +) + +ALL_SIG_VARIATIONS_2 = jnp.array([[1, -1], [-1, 1]]) + +# Pool config shared by integration tests +_CM = 0.2 # centeredness_margin +_DPSB = 1.0 - 1.0 / 124000.0 # daily_price_shift_base +_SPP = 60.0 # seconds_per_step (1-min arb) +_FEES = 0.003 +_PRICE_RATIO = 4.0 +_POOL_VALUE = 1_000_000.0 + + +def _init_pool(pool_value=_POOL_VALUE, price_a=2500.0, price_b=1.0, + price_ratio=_PRICE_RATIO): + initial_prices = jnp.array([price_a, price_b]) + reserves, Va, Vb = initialise_reclamm_reserves(pool_value, initial_prices, price_ratio) + return reserves, Va, Vb + + +def _make_trending_prices(start_a, end_a, price_b, n_steps): + prices_a = jnp.linspace(start_a, end_a, n_steps) + prices_b = jnp.full(n_steps, price_b) + return jnp.stack([prices_a, prices_b], axis=1) + + +class TestRatioBackwardCompatible: + """Test 8: noise_model='ratio' matches existing noise_trader_ratio path.""" + + def test_ratio_model_matches_legacy(self): + reserves, Va, Vb = _init_pool() + n_steps = 50 + prices = _make_trending_prices(2500.0, 3500.0, 1.0, n_steps) + + # Legacy path: just noise_trader_ratio + res_legacy = _jax_calc_reclamm_reserves_with_fees( + reserves, Va, Vb, prices, _CM, _DPSB, _SPP, + fees=_FEES, all_sig_variations=ALL_SIG_VARIATIONS_2, + noise_trader_ratio=1.5, + ) + + # New path: noise_model="ratio" (default) + res_new = _jax_calc_reclamm_reserves_with_fees( + reserves, Va, Vb, prices, _CM, _DPSB, _SPP, + fees=_FEES, all_sig_variations=ALL_SIG_VARIATIONS_2, + noise_trader_ratio=1.5, + noise_model="ratio", + ) + npt.assert_array_equal(res_legacy, res_new) + + +class TestArbOnlyEqualsZeroRatio: + """Test 9: noise_model='arb_only' same as noise_trader_ratio=0.""" + + def test_arb_only_matches_zero_ratio(self): + reserves, Va, Vb = _init_pool() + n_steps = 50 + prices = _make_trending_prices(2500.0, 3500.0, 1.0, n_steps) + + res_zero = _jax_calc_reclamm_reserves_with_fees( + reserves, Va, Vb, prices, _CM, _DPSB, _SPP, + fees=_FEES, all_sig_variations=ALL_SIG_VARIATIONS_2, + noise_trader_ratio=0.0, + ) + res_arb_only = _jax_calc_reclamm_reserves_with_fees( + reserves, Va, Vb, prices, _CM, _DPSB, _SPP, + fees=_FEES, all_sig_variations=ALL_SIG_VARIATIONS_2, + noise_model="arb_only", + ) + npt.assert_array_equal(res_zero, res_arb_only) + + +class TestTsoukalasSqrtIncreasesReserves: + """Test 10: Tsoukalas noise income grows real TVL vs arb-only.""" + + def test_sqrt_reserves_grow(self): + reserves, Va, Vb = _init_pool() + n_steps = 50 + prices = _make_trending_prices(2500.0, 3500.0, 1.0, n_steps) + vol_array = jnp.full(n_steps, 0.5) # Synthetic constant volatility + + res_arb_only = _jax_calc_reclamm_reserves_with_fees( + reserves, Va, Vb, prices, _CM, _DPSB, _SPP, + fees=_FEES, all_sig_variations=ALL_SIG_VARIATIONS_2, + noise_model="arb_only", + ) + res_tsoukalas = _jax_calc_reclamm_reserves_with_fees( + reserves, Va, Vb, prices, _CM, _DPSB, _SPP, + fees=_FEES, all_sig_variations=ALL_SIG_VARIATIONS_2, + noise_model="tsoukalas_sqrt", + noise_params=DEFAULT_NOISE_PARAMS, + volatility_array=vol_array, + ) + # Noise fee income should make total real value strictly greater than arb-only + val_arb = float(jnp.sum(res_arb_only[-1] * prices[-1])) + val_tsoukalas = float(jnp.sum(res_tsoukalas[-1] * prices[-1])) + assert val_tsoukalas > val_arb, ( + f"Tsoukalas reserves ({val_tsoukalas:.2f}) should be strictly > " + f"arb-only ({val_arb:.2f})" + ) + + +class TestTsoukalasDoesNotAffectVirtualBalances: + """Test 11: Within a single scan step, noise modifies real reserves + but does NOT modify Va/Vb in the carry.""" + + def test_single_step_virtual_balances_identical(self): + """Call the scan step directly for one step with arb_only and tsoukalas_sqrt. + Assert that the carry's Va and Vb are bitwise identical.""" + from quantammsim.pools.reCLAMM.reclamm_reserves import ( + _reclamm_scan_step_with_fees_and_revenue, + ) + from quantammsim.pools.G3M.optimal_n_pool_arb import ( + precalc_shared_values_for_all_signatures, + precalc_components_of_optimal_trade_across_prices, + ) + + reserves, Va, Vb = _init_pool() + # Small price shift so arb volume is small relative to predicted noise + # (a 2500→3000 jump produces arb > predicted noise/min, zeroing noise_vol) + prices_1 = jnp.array([[2510.0, 1.0]]) + + weights = jnp.array([0.5, 0.5]) + gamma = 1.0 - _FEES + + _, active_trade_dirs, tokens_to_drop, leave_one_out_idxs = ( + precalc_shared_values_for_all_signatures(ALL_SIG_VARIATIONS_2, 2) + ) + aiw, par, aoar = precalc_components_of_optimal_trade_across_prices( + weights, prices_1, gamma, tokens_to_drop, + active_trade_dirs, leave_one_out_idxs, + ) + + carry = [ + reserves, Va, Vb, + jnp.float64(1.0), # prev_lp_supply + jnp.float64(0.0), # step_idx + jnp.float64(0.0), # active_start_ratio + jnp.float64(0.0), # active_target_ratio + jnp.float64(0.0), # active_start_step + jnp.float64(0.0), # active_end_step + jnp.array(False), # active_enabled + ] + + def run_step(noise_model, noise_params=None): + input_list = [ + prices_1[0], aiw[0], par[0], aoar[0], + jnp.float64(gamma), jnp.float64(0.0), + jnp.float64(0.0), + jnp.array([0.0, 0.0, 0.0]), # price_ratio_update (no-op) + jnp.float64(1.0), # lp_supply + ] + if noise_model in ("tsoukalas_sqrt", "tsoukalas_log", "loglinear"): + input_list.append(jnp.float64(0.5)) # volatility + + return _reclamm_scan_step_with_fees_and_revenue( + carry, input_list, + weights=weights, + tokens_to_drop=tokens_to_drop, + active_trade_directions=active_trade_dirs, + n=2, + centeredness_margin=_CM, + daily_price_shift_base=_DPSB, + seconds_per_step=_SPP, + noise_model=noise_model, + noise_params=noise_params if noise_params is not None else {}, + ) + + carry_arb, (res_arb, _) = run_step("arb_only") + carry_tsoukalas, (res_tsoukalas, _) = run_step( + "tsoukalas_sqrt", DEFAULT_NOISE_PARAMS, + ) + + # Va and Vb in carry must be bitwise identical + npt.assert_array_equal(carry_arb[1], carry_tsoukalas[1], + err_msg="Va should be unaffected by noise model") + npt.assert_array_equal(carry_arb[2], carry_tsoukalas[2], + err_msg="Vb should be unaffected by noise model") + + # But real reserves SHOULD differ (noise adds fee income). + # The noise effect is small relative to reserve magnitude, so use + # exact bitwise comparison rather than allclose (whose default + # rtol=1e-5 would mask the difference). + assert not jnp.array_equal(res_arb, res_tsoukalas), ( + "Real reserves should differ between arb_only and tsoukalas_sqrt" + ) + + # Same invariant for loglinear path + loglinear_params = {"b_0": -1.4, "b_sigma": 0.1, "b_c": 1.04} + carry_loglinear, (res_loglinear, _) = run_step( + "loglinear", loglinear_params, + ) + npt.assert_array_equal(carry_arb[1], carry_loglinear[1], + err_msg="Va should be unaffected by loglinear noise model") + npt.assert_array_equal(carry_arb[2], carry_loglinear[2], + err_msg="Vb should be unaffected by loglinear noise model") + assert not jnp.array_equal(res_arb, res_loglinear), ( + "Real reserves should differ between arb_only and loglinear" + ) + + +class TestTsoukalasWithFeeRevenue: + """Test 12: Fee revenue includes noise contribution.""" + + def test_fee_revenue_includes_noise(self): + reserves, Va, Vb = _init_pool() + n_steps = 50 + prices = _make_trending_prices(2500.0, 3500.0, 1.0, n_steps) + vol_array = jnp.full(n_steps, 0.5) # Synthetic constant volatility + + _, fee_rev_arb = _jax_calc_reclamm_reserves_and_fee_revenue_with_fees( + reserves, Va, Vb, prices, _CM, _DPSB, _SPP, + fees=_FEES, all_sig_variations=ALL_SIG_VARIATIONS_2, + noise_model="arb_only", + ) + _, fee_rev_tsoukalas = _jax_calc_reclamm_reserves_and_fee_revenue_with_fees( + reserves, Va, Vb, prices, _CM, _DPSB, _SPP, + fees=_FEES, all_sig_variations=ALL_SIG_VARIATIONS_2, + noise_model="tsoukalas_sqrt", + noise_params=DEFAULT_NOISE_PARAMS, + volatility_array=vol_array, + ) + # Tsoukalas should generate strictly more fee revenue due to noise volume + total_arb = float(fee_rev_arb.sum()) + total_tsoukalas = float(fee_rev_tsoukalas.sum()) + assert total_tsoukalas > total_arb, ( + f"Tsoukalas fee revenue ({total_tsoukalas:.4f}) should exceed " + f"arb-only ({total_arb:.4f})" + ) + + +# --------------------------------------------------------------------------- +# Tests 13-14: Pool class integration tests +# --------------------------------------------------------------------------- + + +class TestNoiseModelFromFingerprint: + """Test 13: Pool reads noise_model from fingerprint.""" + + def test_tsoukalas_sqrt_from_fingerprint(self): + from quantammsim.pools.creator import create_pool + from quantammsim.runners.jax_runner_utils import Hashabledict + + pool = create_pool("reclamm") + + params = { + "price_ratio": _PRICE_RATIO, + "centeredness_margin": _CM, + "daily_price_shift_base": _DPSB, + } + + n_steps = 50 + np.random.seed(42) + price_a = 2500.0 * np.exp(np.cumsum(np.random.normal(0, 0.01, n_steps))) + prices = jnp.stack([jnp.array(price_a), jnp.ones(n_steps)], axis=1) + + # Fingerprint with Tsoukalas noise model + run_fingerprint_tsoukalas = Hashabledict({ + "n_assets": 2, + "bout_length": n_steps + 1, + "initial_pool_value": _POOL_VALUE, + "arb_frequency": 1, + "do_arb": True, + "fees": _FEES, + "gas_cost": 0.0, + "arb_fees": 0.0, + "tokens": ("ETH", "USDC"), + "numeraire": "USDC", + "all_sig_variations": tuple(map(tuple, [[1, -1], [-1, 1]])), + "noise_model": "tsoukalas_sqrt", + "reclamm_noise_params": DEFAULT_NOISE_PARAMS, + "ste_temperature": 10.0, + }) + + # Fingerprint without noise + run_fingerprint_arb_only = Hashabledict({ + "n_assets": 2, + "bout_length": n_steps + 1, + "initial_pool_value": _POOL_VALUE, + "arb_frequency": 1, + "do_arb": True, + "fees": _FEES, + "gas_cost": 0.0, + "arb_fees": 0.0, + "tokens": ("ETH", "USDC"), + "numeraire": "USDC", + "all_sig_variations": tuple(map(tuple, [[1, -1], [-1, 1]])), + "noise_model": "arb_only", + "ste_temperature": 10.0, + }) + + start_index = jnp.array([0, 0]) + + res_tsoukalas, fee_rev_tsoukalas = pool.calculate_reserves_and_fee_revenue_with_fees( + params, run_fingerprint_tsoukalas, prices, start_index, + ) + res_arb, fee_rev_arb = pool.calculate_reserves_and_fee_revenue_with_fees( + params, run_fingerprint_arb_only, prices, start_index, + ) + + assert res_tsoukalas.shape == (n_steps, 2) + assert fee_rev_tsoukalas.shape == (n_steps,) + # Tsoukalas should produce more fee revenue + assert float(fee_rev_tsoukalas.sum()) > float(fee_rev_arb.sum()) + + +class TestVolatilityComputedForTsoukalas: + """Test 14: Volatility array auto-computed when noise_model is tsoukalas_*. + + Uses >= 1440 minutes so the real vmap+dynamic_slice path is exercised + (not just the <1440 fallback). Compares against arb_only to verify the + auto-computed volatility feeds through to meaningfully different fee revenue. + """ + + def test_volatility_auto_computed_affects_fee_revenue(self): + from quantammsim.pools.creator import create_pool + from quantammsim.runners.jax_runner_utils import Hashabledict + + pool = create_pool("reclamm") + + params = { + "price_ratio": _PRICE_RATIO, + "centeredness_margin": _CM, + "daily_price_shift_base": _DPSB, + } + + # Need at least 1 day of data for real volatility computation + n_steps = 1440 + 100 # Just over 1 day + np.random.seed(42) + price_a = 2500.0 * np.exp(np.cumsum(np.random.normal(0, 0.001, n_steps))) + prices = jnp.stack([jnp.array(price_a), jnp.ones(n_steps)], axis=1) + + base_fp = { + "n_assets": 2, + "bout_length": n_steps + 1, + "initial_pool_value": _POOL_VALUE, + "arb_frequency": 1, + "do_arb": True, + "fees": _FEES, + "gas_cost": 0.0, + "arb_fees": 0.0, + "tokens": ("ETH", "USDC"), + "numeraire": "USDC", + "all_sig_variations": tuple(map(tuple, [[1, -1], [-1, 1]])), + "ste_temperature": 10.0, + } + + fp_tsoukalas = Hashabledict({ + **base_fp, + "noise_model": "tsoukalas_sqrt", + "reclamm_noise_params": DEFAULT_NOISE_PARAMS, + }) + fp_arb_only = Hashabledict({ + **base_fp, + "noise_model": "arb_only", + }) + + start_index = jnp.array([0, 0]) + + res_tsoukalas, fee_rev_tsoukalas = pool.calculate_reserves_and_fee_revenue_with_fees( + params, fp_tsoukalas, prices, start_index, + ) + _, fee_rev_arb = pool.calculate_reserves_and_fee_revenue_with_fees( + params, fp_arb_only, prices, start_index, + ) + + assert res_tsoukalas.shape == (n_steps, 2) + assert fee_rev_tsoukalas.shape == (n_steps,) + + # The auto-computed volatility must feed through to produce + # strictly more fee revenue than arb-only + total_tsoukalas = float(fee_rev_tsoukalas.sum()) + total_arb = float(fee_rev_arb.sum()) + assert total_tsoukalas > total_arb, ( + f"Tsoukalas with auto-computed volatility ({total_tsoukalas:.4f}) " + f"should exceed arb-only ({total_arb:.4f})" + ) + + +# --------------------------------------------------------------------------- +# Tests 15-16: Calibration pipeline tests +# --------------------------------------------------------------------------- + +import pandas as pd +from scripts.calibrate_reclamm_noise import run_ols_calibration + + +class TestOLSRecoversKnownParams: + """Test 15: Synthetic data with known coefficients -> OLS recovers them.""" + + def test_ols_recovery_sqrt(self): + rng = np.random.default_rng(42) + n = 200 + + true_a_0 = 0.8 + true_a_sigma = 1.5 + true_a_c = 0.6 + + vol = rng.uniform(0.2, 1.0, n) + eff_tvl = rng.uniform(3e6, 50e6, n) + + # Construct volume from known params (in $M units) + volume_M = ( + true_a_0 + + true_a_sigma * vol + + true_a_c * np.sqrt(eff_tvl / 1e6) + ) + # Add small noise + volume_M += rng.normal(0, 0.01, n) + volume_usd = volume_M * 1e6 + + df = pd.DataFrame({ + "volume_usd": volume_usd, + "volatility": vol, + "effective_tvl_usd": eff_tvl, + }) + + noise_params, diagnostics = run_ols_calibration(df, base_fee=0.003, model="sqrt") + + npt.assert_allclose(noise_params["a_0_base"], true_a_0, atol=0.05) + npt.assert_allclose(noise_params["a_sigma"], true_a_sigma, atol=0.05) + npt.assert_allclose(noise_params["a_c"], true_a_c, atol=0.05) + assert diagnostics["r_squared"] > 0.99 + + def test_ols_recovery_log(self): + rng = np.random.default_rng(123) + n = 200 + + true_a_0 = 0.5 + true_a_sigma = 2.0 + true_a_c = 0.4 + + vol = rng.uniform(0.2, 1.0, n) + eff_tvl = rng.uniform(3e6, 50e6, n) + + volume_M = ( + true_a_0 + + true_a_sigma * vol + + true_a_c * np.log(eff_tvl / 1e6) + ) + volume_M += rng.normal(0, 0.01, n) + volume_usd = volume_M * 1e6 + + df = pd.DataFrame({ + "volume_usd": volume_usd, + "volatility": vol, + "effective_tvl_usd": eff_tvl, + }) + + noise_params, diagnostics = run_ols_calibration(df, base_fee=0.003, model="log") + + npt.assert_allclose(noise_params["a_0_base"], true_a_0, atol=0.05) + npt.assert_allclose(noise_params["a_sigma"], true_a_sigma, atol=0.05) + npt.assert_allclose(noise_params["a_c"], true_a_c, atol=0.05) + assert diagnostics["r_squared"] > 0.99 + + +class TestOutputFormatCompatible: + """Test 16: Output dict has all required keys for run_fingerprint integration.""" + + def test_output_keys(self): + rng = np.random.default_rng(99) + n = 50 + df = pd.DataFrame({ + "volume_usd": rng.uniform(1e6, 10e6, n), + "volatility": rng.uniform(0.2, 0.8, n), + "effective_tvl_usd": rng.uniform(3e6, 25e6, n), + }) + + noise_params, diagnostics = run_ols_calibration(df, base_fee=0.003) + + required_keys = {"a_0_base", "a_f", "a_sigma", "a_c", "base_fee"} + assert set(noise_params.keys()) == required_keys + + # All values are float + for k, v in noise_params.items(): + assert isinstance(v, float), f"{k} should be float, got {type(v)}" + + # a_f should be 0 for static fees + assert noise_params["a_f"] == 0.0 + + # Diagnostics should have standard errors + assert "se" in diagnostics + assert set(diagnostics["se"].keys()) == {"a_0", "a_sigma", "a_c"} + + # Can be used directly as noise_params for the noise functions + vol = reclamm_tsoukalas_sqrt_noise_volume( + effective_value_usd=15e6, + gamma=0.997, volatility=0.5, arb_volume_this_period=0.0, + noise_params=noise_params, + ) + assert jnp.isfinite(vol) + + +# --------------------------------------------------------------------------- +# Tests 17-24: Loglinear (hierarchical) noise volume model +# --------------------------------------------------------------------------- + +# Typical noise_params from the hierarchical model +LOGLINEAR_NOISE_PARAMS = { + "b_0": -7.1, # grand mean + BLUP + "b_sigma": -0.003, # shared volatility effect + "b_c": 1.04, # shared TVL elasticity + "base_fee": 0.003, +} + + +class TestLoglinearPositiveOutput: + """Test 17: Volume > 0 for typical inputs.""" + + def test_loglinear_positive_output(self): + vol = reclamm_loglinear_noise_volume( + effective_value_usd=15_000_000.0, + gamma=0.997, + volatility=0.5, + arb_volume_this_period=0.0, + noise_params=LOGLINEAR_NOISE_PARAMS, + ) + assert float(vol) > 0, f"Expected positive noise volume, got {float(vol)}" + + +class TestLoglinearZeroWhenArbDominates: + """Test 18: noise = max(0, ...) so returns 0 when arb dominates.""" + + def test_loglinear_zero_when_arb_large(self): + vol = reclamm_loglinear_noise_volume( + effective_value_usd=3_000_000.0, + gamma=0.997, + volatility=0.3, + arb_volume_this_period=1e12, + noise_params=LOGLINEAR_NOISE_PARAMS, + ) + assert float(vol) == 0.0, f"Expected zero, got {float(vol)}" + + +class TestLoglinearMonotonic: + """Test 19: Higher effective TVL -> more predicted volume.""" + + def test_loglinear_monotonic_tvl(self): + kwargs = dict( + gamma=0.997, volatility=0.5, arb_volume_this_period=0.0, + noise_params=LOGLINEAR_NOISE_PARAMS, + ) + vol_low = reclamm_loglinear_noise_volume( + effective_value_usd=3_000_000.0, **kwargs) + vol_high = reclamm_loglinear_noise_volume( + effective_value_usd=20_000_000.0, **kwargs) + assert float(vol_high) > float(vol_low) + + +class TestLoglinearCustomParams: + """Test 20: noise_params dict values are actually used.""" + + def test_loglinear_custom_b_sigma(self): + # With b_sigma=0, volatility shouldn't matter + zero_sigma_params = {**LOGLINEAR_NOISE_PARAMS, "b_sigma": 0.0} + v1 = reclamm_loglinear_noise_volume( + effective_value_usd=10_000_000.0, + gamma=0.997, volatility=0.2, arb_volume_this_period=0.0, + noise_params=zero_sigma_params, + ) + v2 = reclamm_loglinear_noise_volume( + effective_value_usd=10_000_000.0, + gamma=0.997, volatility=0.8, arb_volume_this_period=0.0, + noise_params=zero_sigma_params, + ) + npt.assert_allclose(float(v1), float(v2), rtol=1e-10, + err_msg="With b_sigma=0, volatility should not affect output") + + +class TestLoglinearScanStepIntegration: + """Test 21: loglinear noise model works through the scan step.""" + + def test_loglinear_increases_reserves(self): + reserves, Va, Vb = _init_pool() + n_steps = 50 + prices = _make_trending_prices(2500.0, 3500.0, 1.0, n_steps) + vol_array = jnp.full(n_steps, 0.5) + + # Use a b_0 that gives reasonable volume at this TVL + # Pool TVL ~$1M → log(1e6) ≈ 13.8 → b_0 + 1.04*13.8 = b_0 + 14.4 + # Want log(V_daily) ≈ 13 (= ~$440k/day) → b_0 ≈ -1.4 + params = {"b_0": -1.4, "b_sigma": 0.1, "b_c": 1.04, "base_fee": 0.003} + + res_arb_only = _jax_calc_reclamm_reserves_with_fees( + reserves, Va, Vb, prices, _CM, _DPSB, _SPP, + fees=_FEES, all_sig_variations=ALL_SIG_VARIATIONS_2, + noise_model="arb_only", + ) + res_loglinear = _jax_calc_reclamm_reserves_with_fees( + reserves, Va, Vb, prices, _CM, _DPSB, _SPP, + fees=_FEES, all_sig_variations=ALL_SIG_VARIATIONS_2, + noise_model="loglinear", + noise_params=params, + volatility_array=vol_array, + ) + val_arb = float(jnp.sum(res_arb_only[-1] * prices[-1])) + val_loglinear = float(jnp.sum(res_loglinear[-1] * prices[-1])) + assert val_loglinear > val_arb, ( + f"Loglinear reserves ({val_loglinear:.2f}) should exceed " + f"arb-only ({val_arb:.2f})" + ) + + +class TestLoglinearFeeRevenue: + """Test 22: Fee revenue includes loglinear noise contribution.""" + + def test_loglinear_fee_revenue(self): + reserves, Va, Vb = _init_pool() + n_steps = 50 + prices = _make_trending_prices(2500.0, 3500.0, 1.0, n_steps) + vol_array = jnp.full(n_steps, 0.5) + + params = {"b_0": -1.4, "b_sigma": 0.1, "b_c": 1.04, "base_fee": 0.003} + + _, fee_rev_arb = _jax_calc_reclamm_reserves_and_fee_revenue_with_fees( + reserves, Va, Vb, prices, _CM, _DPSB, _SPP, + fees=_FEES, all_sig_variations=ALL_SIG_VARIATIONS_2, + noise_model="arb_only", + ) + _, fee_rev_loglinear = _jax_calc_reclamm_reserves_and_fee_revenue_with_fees( + reserves, Va, Vb, prices, _CM, _DPSB, _SPP, + fees=_FEES, all_sig_variations=ALL_SIG_VARIATIONS_2, + noise_model="loglinear", + noise_params=params, + volatility_array=vol_array, + ) + total_arb = float(fee_rev_arb.sum()) + total_loglinear = float(fee_rev_loglinear.sum()) + assert total_loglinear > total_arb, ( + f"Loglinear fee revenue ({total_loglinear:.4f}) should exceed " + f"arb-only ({total_arb:.4f})" + ) + + +class TestLoglinearPoolClassIntegration: + """Test 23: Pool reads loglinear noise_model from fingerprint.""" + + def test_loglinear_from_fingerprint(self): + from quantammsim.pools.creator import create_pool + from quantammsim.runners.jax_runner_utils import Hashabledict + + pool = create_pool("reclamm") + + params = { + "price_ratio": _PRICE_RATIO, + "centeredness_margin": _CM, + "daily_price_shift_base": _DPSB, + } + + n_steps = 50 + np.random.seed(42) + price_a = 2500.0 * np.exp(np.cumsum(np.random.normal(0, 0.01, n_steps))) + prices = jnp.stack([jnp.array(price_a), jnp.ones(n_steps)], axis=1) + + loglinear_params = { + "b_0": -1.4, "b_sigma": 0.1, "b_c": 1.04, "base_fee": 0.003, + } + + fp_loglinear = Hashabledict({ + "n_assets": 2, + "bout_length": n_steps + 1, + "initial_pool_value": _POOL_VALUE, + "arb_frequency": 1, + "do_arb": True, + "fees": _FEES, + "gas_cost": 0.0, + "arb_fees": 0.0, + "tokens": ("ETH", "USDC"), + "numeraire": "USDC", + "all_sig_variations": tuple(map(tuple, [[1, -1], [-1, 1]])), + "noise_model": "loglinear", + "reclamm_noise_params": loglinear_params, + "ste_temperature": 10.0, + }) + + fp_arb_only = Hashabledict({ + "n_assets": 2, + "bout_length": n_steps + 1, + "initial_pool_value": _POOL_VALUE, + "arb_frequency": 1, + "do_arb": True, + "fees": _FEES, + "gas_cost": 0.0, + "arb_fees": 0.0, + "tokens": ("ETH", "USDC"), + "numeraire": "USDC", + "all_sig_variations": tuple(map(tuple, [[1, -1], [-1, 1]])), + "noise_model": "arb_only", + "ste_temperature": 10.0, + }) + + start_index = jnp.array([0, 0]) + + res_loglinear, fee_rev_loglinear = pool.calculate_reserves_and_fee_revenue_with_fees( + params, fp_loglinear, prices, start_index, + ) + _, fee_rev_arb = pool.calculate_reserves_and_fee_revenue_with_fees( + params, fp_arb_only, prices, start_index, + ) + + assert res_loglinear.shape == (n_steps, 2) + assert fee_rev_loglinear.shape == (n_steps,) + assert float(fee_rev_loglinear.sum()) > float(fee_rev_arb.sum()) + + +class TestLoglinearDefaultParams: + """Test 24: Function works with default params (noise_params=None).""" + + def test_loglinear_defaults(self): + vol = reclamm_loglinear_noise_volume( + effective_value_usd=15_000_000.0, + gamma=0.997, + volatility=0.5, + arb_volume_this_period=0.0, + ) + assert jnp.isfinite(vol) + assert float(vol) > 0 diff --git a/tests/pools/reCLAMM/test_reclamm_price_ratio_updates.py b/tests/pools/reCLAMM/test_reclamm_price_ratio_updates.py new file mode 100644 index 0000000..955d16d --- /dev/null +++ b/tests/pools/reCLAMM/test_reclamm_price_ratio_updates.py @@ -0,0 +1,370 @@ +"""Tests for manual reCLAMM price-ratio schedule updates.""" + +import numpy as np +import numpy.testing as npt +import jax.numpy as jnp +import pytest + +from quantammsim.pools.reCLAMM.reclamm_reserves import ( + apply_target_price_ratio_to_virtual_balances, + compute_centeredness, + compute_price_ratio, + initialise_reclamm_reserves, + _jax_calc_reclamm_reserves_with_dynamic_inputs, + _jax_calc_reclamm_reserves_and_fee_revenue_with_dynamic_inputs, +) +from tests.pools.reCLAMM.helpers import ( + _jax_calc_reclamm_reserves_with_dynamic_inputs_full_state, +) + + +DEFAULT_INITIAL_POOL_VALUE = 1_000_000.0 +DEFAULT_INITIAL_PRICES = jnp.array([2500.0, 1.0], dtype=jnp.float64) +DEFAULT_PRICE_RATIO = 4.0 +DEFAULT_DAILY_PRICE_SHIFT_BASE = 1.0 - 1.0 / 124000.0 +DEFAULT_SECONDS_PER_STEP = 60.0 +ALL_SIG_VARIATIONS_2 = jnp.array([[1, -1], [-1, 1]]) + + +def _init_pool(price_ratio=DEFAULT_PRICE_RATIO): + reserves, Va, Vb = initialise_reclamm_reserves( + DEFAULT_INITIAL_POOL_VALUE, + DEFAULT_INITIAL_PRICES, + price_ratio, + ) + return reserves, Va, Vb + + +def _flat_prices(n_steps): + return jnp.stack( + [jnp.full((n_steps,), DEFAULT_INITIAL_PRICES[0]), jnp.ones((n_steps,))], + axis=1, + ) + + +def _empty_schedule(n_steps): + schedule = np.zeros((n_steps, 4), dtype=np.float64) + schedule[:, 3] = np.nan + return jnp.asarray(schedule) + + +def _single_event_schedule( + n_steps, + start_step, + end_step, + target_price_ratio, + start_price_ratio_override=np.nan, +): + schedule = np.zeros((n_steps, 4), dtype=np.float64) + schedule[:, 3] = np.nan + schedule[start_step, 0] = 1.0 + schedule[start_step, 1] = target_price_ratio + schedule[start_step, 2] = float(end_step) + schedule[start_step, 3] = start_price_ratio_override + return jnp.asarray(schedule) + + +class TestReclammPriceRatioUpdates: + def test_schedule_off_matches_baseline_dynamic_kernel(self): + reserves, Va, Vb = _init_pool() + n_steps = 8 + prices = _flat_prices(n_steps) + fees = jnp.zeros((n_steps,), dtype=jnp.float64) + arb_thresh = jnp.zeros((n_steps,), dtype=jnp.float64) + arb_fees = jnp.zeros((n_steps,), dtype=jnp.float64) + + baseline = _jax_calc_reclamm_reserves_with_dynamic_inputs( + reserves, + Va, + Vb, + prices, + centeredness_margin=0.2, + daily_price_shift_base=DEFAULT_DAILY_PRICE_SHIFT_BASE, + seconds_per_step=DEFAULT_SECONDS_PER_STEP, + fees=fees, + arb_thresh=arb_thresh, + arb_fees=arb_fees, + all_sig_variations=ALL_SIG_VARIATIONS_2, + ) + with_schedule = _jax_calc_reclamm_reserves_with_dynamic_inputs( + reserves, + Va, + Vb, + prices, + centeredness_margin=0.2, + daily_price_shift_base=DEFAULT_DAILY_PRICE_SHIFT_BASE, + seconds_per_step=DEFAULT_SECONDS_PER_STEP, + fees=fees, + arb_thresh=arb_thresh, + arb_fees=arb_fees, + price_ratio_updates=_empty_schedule(n_steps), + all_sig_variations=ALL_SIG_VARIATIONS_2, + ) + npt.assert_allclose(with_schedule, baseline, rtol=1e-10, atol=1e-10) + + def test_single_schedule_reaches_target_ratio_at_end_step(self): + reserves, Va, Vb = _init_pool() + n_steps = 8 + prices = _flat_prices(n_steps) + fees = jnp.zeros((n_steps,), dtype=jnp.float64) + arb_thresh = jnp.zeros((n_steps,), dtype=jnp.float64) + arb_fees = jnp.zeros((n_steps,), dtype=jnp.float64) + + end_step = 4 + schedule = _single_event_schedule( + n_steps, + start_step=1, + end_step=end_step, + target_price_ratio=9.0, + start_price_ratio_override=DEFAULT_PRICE_RATIO, + ) + reserves_out, Va_history, Vb_history = ( + _jax_calc_reclamm_reserves_with_dynamic_inputs_full_state( + reserves, + Va, + Vb, + prices, + centeredness_margin=0.0, + daily_price_shift_base=DEFAULT_DAILY_PRICE_SHIFT_BASE, + seconds_per_step=DEFAULT_SECONDS_PER_STEP, + fees=fees, + arb_thresh=arb_thresh, + arb_fees=arb_fees, + price_ratio_updates=schedule, + all_sig_variations=ALL_SIG_VARIATIONS_2, + ) + ) + + ratio_at_end = float( + compute_price_ratio( + reserves_out[end_step, 0], + reserves_out[end_step, 1], + Va_history[end_step], + Vb_history[end_step], + ) + ) + assert ratio_at_end == pytest.approx(9.0, rel=1e-5, abs=1e-5) + + def test_schedule_interpolates_geometrically_in_ratio_space(self): + reserves, Va, Vb = _init_pool() + n_steps = 8 + prices = _flat_prices(n_steps) + fees = jnp.zeros((n_steps,), dtype=jnp.float64) + arb_thresh = jnp.zeros((n_steps,), dtype=jnp.float64) + arb_fees = jnp.zeros((n_steps,), dtype=jnp.float64) + + start_step = 1 + end_step = 5 + start_ratio = 4.0 + target_ratio = 16.0 + schedule = _single_event_schedule( + n_steps, + start_step=start_step, + end_step=end_step, + target_price_ratio=target_ratio, + start_price_ratio_override=start_ratio, + ) + + reserves_out, Va_history, Vb_history = ( + _jax_calc_reclamm_reserves_with_dynamic_inputs_full_state( + reserves, + Va, + Vb, + prices, + centeredness_margin=0.0, + daily_price_shift_base=DEFAULT_DAILY_PRICE_SHIFT_BASE, + seconds_per_step=DEFAULT_SECONDS_PER_STEP, + fees=fees, + arb_thresh=arb_thresh, + arb_fees=arb_fees, + price_ratio_updates=schedule, + all_sig_variations=ALL_SIG_VARIATIONS_2, + ) + ) + + mid_step = 3 + ratio_at_mid = float( + compute_price_ratio( + reserves_out[mid_step, 0], + reserves_out[mid_step, 1], + Va_history[mid_step], + Vb_history[mid_step], + ) + ) + progress = (mid_step - start_step) / (end_step - start_step) + expected_geometric = start_ratio * (target_ratio / start_ratio) ** progress + assert ratio_at_mid == pytest.approx(expected_geometric, rel=1e-5, abs=1e-5) + + def test_schedule_stops_applying_after_end_step(self): + reserves, Va, Vb = _init_pool() + n_steps = 10 + prices = jnp.stack( + [jnp.linspace(DEFAULT_INITIAL_PRICES[0], 5000.0, n_steps), jnp.ones((n_steps,))], + axis=1, + ) + fees = jnp.zeros((n_steps,), dtype=jnp.float64) + arb_thresh = jnp.zeros((n_steps,), dtype=jnp.float64) + arb_fees = jnp.zeros((n_steps,), dtype=jnp.float64) + + end_step = 3 + schedule = _single_event_schedule( + n_steps, + start_step=1, + end_step=end_step, + target_price_ratio=9.0, + start_price_ratio_override=DEFAULT_PRICE_RATIO, + ) + reserves_out, Va_history, Vb_history = ( + _jax_calc_reclamm_reserves_with_dynamic_inputs_full_state( + reserves, + Va, + Vb, + prices, + centeredness_margin=0.0, # disable thermostat to isolate schedule behavior + daily_price_shift_base=DEFAULT_DAILY_PRICE_SHIFT_BASE, + seconds_per_step=DEFAULT_SECONDS_PER_STEP, + fees=fees, + arb_thresh=arb_thresh, + arb_fees=arb_fees, + price_ratio_updates=schedule, + all_sig_variations=ALL_SIG_VARIATIONS_2, + ) + ) + + # Reserves continue evolving under changing market prices... + assert not np.allclose( + np.asarray(reserves_out[end_step + 1]), + np.asarray(reserves_out[end_step + 2]), + ) + # ...but virtual balances should be frozen once the schedule has ended. + npt.assert_allclose( + np.asarray(Va_history[end_step + 1 :]), + np.full((n_steps - (end_step + 1),), float(Va_history[end_step])), + rtol=1e-9, + atol=1e-9, + ) + npt.assert_allclose( + np.asarray(Vb_history[end_step + 1 :]), + np.full((n_steps - (end_step + 1),), float(Vb_history[end_step])), + rtol=1e-9, + atol=1e-9, + ) + + def test_replacement_event_supersedes_active_event(self): + reserves, Va, Vb = _init_pool() + n_steps = 9 + prices = _flat_prices(n_steps) + fees = jnp.zeros((n_steps,), dtype=jnp.float64) + arb_thresh = jnp.zeros((n_steps,), dtype=jnp.float64) + arb_fees = jnp.zeros((n_steps,), dtype=jnp.float64) + + schedule = np.zeros((n_steps, 4), dtype=np.float64) + schedule[:, 3] = np.nan + # Event 1: interpolate toward 8.0 until step 6. + schedule[1] = np.array([1.0, 8.0, 6.0, DEFAULT_PRICE_RATIO], dtype=np.float64) + # Event 2 replaces at step 3 and targets 2.0 by step 4. + schedule[3] = np.array([1.0, 2.0, 4.0, np.nan], dtype=np.float64) + + reserves_out, Va_history, Vb_history = ( + _jax_calc_reclamm_reserves_with_dynamic_inputs_full_state( + reserves, + Va, + Vb, + prices, + centeredness_margin=0.0, + daily_price_shift_base=DEFAULT_DAILY_PRICE_SHIFT_BASE, + seconds_per_step=DEFAULT_SECONDS_PER_STEP, + fees=fees, + arb_thresh=arb_thresh, + arb_fees=arb_fees, + price_ratio_updates=jnp.asarray(schedule), + all_sig_variations=ALL_SIG_VARIATIONS_2, + ) + ) + + ratio_after_replacement = float( + compute_price_ratio( + reserves_out[4, 0], + reserves_out[4, 1], + Va_history[4], + Vb_history[4], + ) + ) + assert ratio_after_replacement == pytest.approx(2.0, rel=1e-4, abs=1e-4) + + @pytest.mark.parametrize( + "Ra, Rb, Va, Vb, target_price_ratio", + [ + # Centered pool, widen ratio + (500_000.0, 500_000.0, 100_000.0, 100_000.0, 9.0), + # Centered pool, narrow ratio + (500_000.0, 500_000.0, 100_000.0, 100_000.0, 2.0), + # Above center (Ra abundant) + (800_000.0, 200_000.0, 100_000.0, 100_000.0, 16.0), + # Below center (Rb abundant) + (200_000.0, 800_000.0, 100_000.0, 100_000.0, 16.0), + # Asymmetric virtuals + (300_000.0, 600_000.0, 50_000.0, 200_000.0, 5.0), + # Large ratio change + (500_000.0, 500_000.0, 100_000.0, 100_000.0, 100.0), + ], + ) + def test_price_ratio_update_preserves_centeredness( + self, Ra, Rb, Va, Vb, target_price_ratio + ): + """Mirrors Foundry fuzz test testCalculateVirtualBalancesUpdatingPriceRatio__Fuzz. + + Asserts that apply_target_price_ratio_to_virtual_balances preserves + centeredness and achieves the target price ratio. + """ + Ra, Rb = jnp.float64(Ra), jnp.float64(Rb) + Va, Vb = jnp.float64(Va), jnp.float64(Vb) + + old_centeredness, _ = compute_centeredness(Ra, Rb, Va, Vb) + Va_new, Vb_new = apply_target_price_ratio_to_virtual_balances( + Ra, Rb, Va, Vb, target_price_ratio + ) + new_centeredness, _ = compute_centeredness(Ra, Rb, Va_new, Vb_new) + new_price_ratio = float(compute_price_ratio(Ra, Rb, Va_new, Vb_new)) + + assert float(new_centeredness) == pytest.approx( + float(old_centeredness), rel=1e-6, abs=1e-10 + ), "Centeredness should be preserved" + assert new_price_ratio == pytest.approx( + target_price_ratio, rel=1e-4 + ), "Price ratio should match target" + + def test_dynamic_fee_revenue_path_with_schedule(self): + reserves, Va, Vb = _init_pool() + n_steps = 10 + prices = _flat_prices(n_steps) + fees = jnp.full((n_steps,), 0.003, dtype=jnp.float64) + arb_thresh = jnp.zeros((n_steps,), dtype=jnp.float64) + arb_fees = jnp.zeros((n_steps,), dtype=jnp.float64) + + schedule = _single_event_schedule( + n_steps, + start_step=2, + end_step=6, + target_price_ratio=5.5, + start_price_ratio_override=DEFAULT_PRICE_RATIO, + ) + reserves_out, fee_revenue = ( + _jax_calc_reclamm_reserves_and_fee_revenue_with_dynamic_inputs( + reserves, + Va, + Vb, + prices, + centeredness_margin=0.2, + daily_price_shift_base=DEFAULT_DAILY_PRICE_SHIFT_BASE, + seconds_per_step=DEFAULT_SECONDS_PER_STEP, + fees=fees, + arb_thresh=arb_thresh, + arb_fees=arb_fees, + price_ratio_updates=schedule, + all_sig_variations=ALL_SIG_VARIATIONS_2, + ) + ) + assert reserves_out.shape == (n_steps, 2) + assert fee_revenue.shape == (n_steps,) + assert jnp.all(fee_revenue >= 0.0) diff --git a/tests/pools/reCLAMM/test_reclamm_reserves.py b/tests/pools/reCLAMM/test_reclamm_reserves.py index ea77eb6..d3347e7 100644 --- a/tests/pools/reCLAMM/test_reclamm_reserves.py +++ b/tests/pools/reCLAMM/test_reclamm_reserves.py @@ -9,15 +9,18 @@ import numpy as np import numpy.testing as npt +from tests.conftest import TEST_DATA_DIR from quantammsim.pools.reCLAMM.reclamm_reserves import ( compute_invariant, compute_price_ratio, + compute_centeredness, initialise_reclamm_reserves, calibrate_arc_length_speed, _jax_calc_reclamm_reserves_zero_fees, + _jax_calc_reclamm_reserves_zero_fees_full_state, _jax_calc_reclamm_reserves_with_fees, + _jax_calc_reclamm_reserves_and_fee_revenue_with_fees, ) -from tests.conftest import TEST_DATA_DIR # For n=2: sig variations with exactly one +1 and one -1 ALL_SIG_VARIATIONS_2 = jnp.array([[1, -1], [-1, 1]]) @@ -272,6 +275,7 @@ def test_calculate_reserves_with_fees(self): "tokens": ("ETH", "USDC"), "numeraire": "USDC", "all_sig_variations": tuple(map(tuple, [[1, -1], [-1, 1]])), + "ste_temperature": 10.0, }) start_index = jnp.array([0, 0]) @@ -314,6 +318,7 @@ def test_calculate_reserves_zero_fees(self): "tokens": ("ETH", "USDC"), "numeraire": "USDC", "all_sig_variations": tuple(map(tuple, [[1, -1], [-1, 1]])), + "ste_temperature": 10.0, }) start_index = jnp.array([0, 0]) @@ -353,6 +358,7 @@ def test_calculate_weights(self): "tokens": ("ETH", "USDC"), "numeraire": "USDC", "all_sig_variations": tuple(map(tuple, [[1, -1], [-1, 1]])), + "ste_temperature": 10.0, }) start_index = jnp.array([0, 0]) @@ -485,6 +491,7 @@ def test_fingerprint_dispatch(self): "all_sig_variations": tuple(map(tuple, [[1, -1], [-1, 1]])), "reclamm_interpolation_method": "constant_arc_length", "reclamm_arc_length_speed": None, # auto-calibrate + "ste_temperature": 10.0, }) start_index = jnp.array([0, 0]) @@ -690,6 +697,7 @@ def test_learnable_arc_length_speed_forward_pass(self): "all_sig_variations": tuple(map(tuple, [[1, -1], [-1, 1]])), "reclamm_interpolation_method": "constant_arc_length", "reclamm_learn_arc_length_speed": True, + "ste_temperature": 10.0, }) start_index = jnp.array([0, 0]) @@ -815,3 +823,617 @@ def test_train_on_historic_data_optuna(self): } result = train_on_historic_data(fp, verbose=False, root=TEST_DATA_DIR) assert result is not None + + +class TestNoiseTraderRatio: + """Noise trader fee income wiring for reClAMM pools.""" + + def _run_with_noise(self, noise_trader_ratio, n_steps=50, fees=0.003): + """Run reClAMM with fees and return reserves + fee revenue.""" + reserves, Va, Vb = _init_pool() + # Trending prices so arb trades happen and noise trade has non-zero effect + prices = _make_trending_prices(2500.0, 3000.0, 1.0, n_steps) + + return _jax_calc_reclamm_reserves_and_fee_revenue_with_fees( + reserves, Va, Vb, prices, + DEFAULT_CENTEREDNESS_MARGIN, + DEFAULT_DAILY_PRICE_SHIFT_BASE, + DEFAULT_SECONDS_PER_STEP, + fees=fees, + arb_thresh=0.0, + arb_fees=0.0, + all_sig_variations=ALL_SIG_VARIATIONS_2, + noise_trader_ratio=noise_trader_ratio, + ) + + def test_noise_trader_ratio_zero_is_default(self): + """noise_trader_ratio=0.0 should produce identical results to omitting it.""" + reserves, Va, Vb = _init_pool() + prices = _make_trending_prices(2500.0, 3000.0, 1.0, 50) + + # Explicit zero + res_zero, rev_zero = _jax_calc_reclamm_reserves_and_fee_revenue_with_fees( + reserves, Va, Vb, prices, + DEFAULT_CENTEREDNESS_MARGIN, + DEFAULT_DAILY_PRICE_SHIFT_BASE, + DEFAULT_SECONDS_PER_STEP, + fees=0.003, + arb_thresh=0.0, + arb_fees=0.0, + all_sig_variations=ALL_SIG_VARIATIONS_2, + noise_trader_ratio=0.0, + ) + + # Default (omitted) + res_default, rev_default = _jax_calc_reclamm_reserves_and_fee_revenue_with_fees( + reserves, Va, Vb, prices, + DEFAULT_CENTEREDNESS_MARGIN, + DEFAULT_DAILY_PRICE_SHIFT_BASE, + DEFAULT_SECONDS_PER_STEP, + fees=0.003, + arb_thresh=0.0, + arb_fees=0.0, + all_sig_variations=ALL_SIG_VARIATIONS_2, + ) + + npt.assert_array_equal(res_zero, res_default) + npt.assert_array_equal(rev_zero, rev_default) + + def test_noise_trader_ratio_increases_reserves(self): + """Noise traders add fee income, so pool value should be higher.""" + res_no_noise, _ = self._run_with_noise(0.0) + res_noise, _ = self._run_with_noise(0.1) + + # Final pool value in USD (sum of reserves * price at last step) + final_prices = jnp.array([3000.0, 1.0]) + value_no_noise = (res_no_noise[-1] * final_prices).sum() + value_noise = (res_noise[-1] * final_prices).sum() + + assert value_noise > value_no_noise, ( + f"Noise traders should increase pool value: {value_noise} <= {value_no_noise}" + ) + + # Reserves should differ + assert not jnp.allclose(res_no_noise, res_noise), ( + "Reserves should differ with noise traders" + ) + + def test_noise_trader_ratio_through_pool_class(self): + """noise_trader_ratio flows through the pool class methods.""" + from quantammsim.pools.creator import create_pool + from quantammsim.runners.jax_runner_utils import Hashabledict + + pool = create_pool("reclamm") + + params = { + "price_ratio": DEFAULT_PRICE_RATIO, + "centeredness_margin": DEFAULT_CENTEREDNESS_MARGIN, + "daily_price_shift_base": DEFAULT_DAILY_PRICE_SHIFT_BASE, + } + + n_steps = 50 + np.random.seed(42) + price_a = 2500.0 * np.exp(np.cumsum(np.random.normal(0, 0.01, n_steps))) + prices = jnp.stack([jnp.array(price_a), jnp.ones(n_steps)], axis=1) + start_index = jnp.array([0, 0]) + + base_fp = { + "n_assets": 2, + "bout_length": n_steps + 1, + "initial_pool_value": 1_000_000.0, + "arb_frequency": 1, + "do_arb": True, + "fees": 0.003, + "gas_cost": 0.0, + "arb_fees": 0.0, + "tokens": ("ETH", "USDC"), + "numeraire": "USDC", + "all_sig_variations": tuple(map(tuple, [[1, -1], [-1, 1]])), + "ste_temperature": 10.0, + } + + fp_no_noise = Hashabledict({**base_fp, "noise_trader_ratio": 0.0}) + fp_noise = Hashabledict({**base_fp, "noise_trader_ratio": 0.1}) + + res_no = pool.calculate_reserves_with_fees( + params, fp_no_noise, prices, start_index + ) + res_yes = pool.calculate_reserves_with_fees( + params, fp_noise, prices, start_index + ) + + # Should run and produce different results + assert res_no.shape == (n_steps, 2) + assert res_yes.shape == (n_steps, 2) + assert not jnp.allclose(res_no, res_yes), ( + "Pool class should produce different reserves with noise traders" + ) + + def test_noise_trade_does_not_affect_virtual_balances(self): + """Noise trade fee income only grows real reserves, not Va/Vb.""" + from quantammsim.pools.reCLAMM.reclamm_reserves import ( + _reclamm_scan_step_with_fees_and_revenue, + precalc_shared_values_for_all_signatures, + precalc_components_of_optimal_trade_across_prices, + ) + + reserves, Va, Vb = _init_pool() + # Price must differ from init (2500) to trigger an arb trade + prices_arr = jnp.array([[3000.0, 1.0]]) + prices = prices_arr[0] + + weights = jnp.array([0.5, 0.5]) + gamma = 1.0 - 0.003 + n_assets = 2 + + _, active_trade_directions, tokens_to_drop, leave_one_out_idxs = ( + precalc_shared_values_for_all_signatures(ALL_SIG_VARIATIONS_2, n_assets) + ) + + active_initial_weights, per_asset_ratios, all_other_assets_ratios = ( + precalc_components_of_optimal_trade_across_prices( + weights, prices_arr, gamma, tokens_to_drop, + active_trade_directions, leave_one_out_idxs, + ) + ) + + carry = [ + reserves, Va, Vb, + jnp.float64(1.0), # prev_lp_supply + jnp.float64(0.0), # step_idx + jnp.float64(0.0), # active_start_ratio + jnp.float64(0.0), # active_target_ratio + jnp.float64(0.0), # active_start_step + jnp.float64(0.0), # active_end_step + jnp.array(False), # active_enabled + ] + inputs = [ + prices, + active_initial_weights[0], + per_asset_ratios[0], + all_other_assets_ratios[0], + gamma, + 0.0, # arb_thresh + 0.0, # arb_fees + jnp.array([0.0, 0.0, 0.0]), # price_ratio_update (no-op) + jnp.float64(1.0), # lp_supply + ] + + # Without noise + carry_no, _ = _reclamm_scan_step_with_fees_and_revenue( + carry, inputs, + weights=weights, + tokens_to_drop=tokens_to_drop, + active_trade_directions=active_trade_directions, + n=n_assets, + centeredness_margin=DEFAULT_CENTEREDNESS_MARGIN, + daily_price_shift_base=DEFAULT_DAILY_PRICE_SHIFT_BASE, + seconds_per_step=DEFAULT_SECONDS_PER_STEP, + noise_trader_ratio=0.0, + ) + + # With noise + carry_yes, _ = _reclamm_scan_step_with_fees_and_revenue( + carry, inputs, + weights=weights, + tokens_to_drop=tokens_to_drop, + active_trade_directions=active_trade_directions, + n=n_assets, + centeredness_margin=DEFAULT_CENTEREDNESS_MARGIN, + daily_price_shift_base=DEFAULT_DAILY_PRICE_SHIFT_BASE, + seconds_per_step=DEFAULT_SECONDS_PER_STEP, + noise_trader_ratio=0.5, + ) + + # Virtual balances should be identical + npt.assert_array_equal(carry_no[1], carry_yes[1], err_msg="Va changed") + npt.assert_array_equal(carry_no[2], carry_yes[2], err_msg="Vb changed") + + # But real reserves should differ (noise adds fee income) + assert not jnp.allclose(carry_no[0], carry_yes[0]), ( + "Real reserves should differ with noise traders" + ) + + +class TestLpSupply: + """LP supply (BPT) scaling for reClAMM pools. + + Ported from Foundry fuzz test invariants in ReClammLiquidity.t.sol: + both real AND virtual reserves scale proportionally with BPT supply, + preserving price and centeredness. + """ + + def test_lp_supply_none_matches_default(self): + """lp_supply_array=None vs omitting param → identical results.""" + reserves, Va, Vb = _init_pool() + prices = _make_trending_prices(2500.0, 3000.0, 1.0, 20) + + result_none = _jax_calc_reclamm_reserves_with_fees( + reserves, Va, Vb, prices, + DEFAULT_CENTEREDNESS_MARGIN, + DEFAULT_DAILY_PRICE_SHIFT_BASE, + DEFAULT_SECONDS_PER_STEP, + fees=0.003, + arb_thresh=0.0, + arb_fees=0.0, + all_sig_variations=ALL_SIG_VARIATIONS_2, + lp_supply_array=None, + ) + result_omit = _jax_calc_reclamm_reserves_with_fees( + reserves, Va, Vb, prices, + DEFAULT_CENTEREDNESS_MARGIN, + DEFAULT_DAILY_PRICE_SHIFT_BASE, + DEFAULT_SECONDS_PER_STEP, + fees=0.003, + arb_thresh=0.0, + arb_fees=0.0, + all_sig_variations=ALL_SIG_VARIATIONS_2, + ) + npt.assert_array_equal(result_none, result_omit) + + def test_lp_supply_constant_one_matches_default(self): + """lp_supply_array=jnp.ones(T) vs None → identical results.""" + reserves, Va, Vb = _init_pool() + n_steps = 20 + prices = _make_trending_prices(2500.0, 3000.0, 1.0, n_steps) + + result_ones = _jax_calc_reclamm_reserves_with_fees( + reserves, Va, Vb, prices, + DEFAULT_CENTEREDNESS_MARGIN, + DEFAULT_DAILY_PRICE_SHIFT_BASE, + DEFAULT_SECONDS_PER_STEP, + fees=0.003, + arb_thresh=0.0, + arb_fees=0.0, + all_sig_variations=ALL_SIG_VARIATIONS_2, + lp_supply_array=jnp.ones(n_steps), + ) + result_none = _jax_calc_reclamm_reserves_with_fees( + reserves, Va, Vb, prices, + DEFAULT_CENTEREDNESS_MARGIN, + DEFAULT_DAILY_PRICE_SHIFT_BASE, + DEFAULT_SECONDS_PER_STEP, + fees=0.003, + arb_thresh=0.0, + arb_fees=0.0, + all_sig_variations=ALL_SIG_VARIATIONS_2, + ) + npt.assert_array_equal(result_ones, result_none) + + def test_lp_supply_doubling_scales_reserves(self): + """BPT supply doubling halfway → reserves ~2x at that step. + + Ported from Foundry testAddLiquidity__Fuzz: reserves scale with BPT. + """ + reserves, Va, Vb = _init_pool() + n_steps = 40 + half = n_steps // 2 + prices = _make_constant_prices(2500.0, 1.0, n_steps) + + # No LP supply change (baseline) + result_base = _jax_calc_reclamm_reserves_with_fees( + reserves, Va, Vb, prices, + DEFAULT_CENTEREDNESS_MARGIN, + DEFAULT_DAILY_PRICE_SHIFT_BASE, + DEFAULT_SECONDS_PER_STEP, + fees=0.003, + arb_thresh=0.0, + arb_fees=0.0, + all_sig_variations=ALL_SIG_VARIATIONS_2, + ) + + # LP supply doubles at step `half` + lp_supply = jnp.concatenate([jnp.ones(half), 2.0 * jnp.ones(n_steps - half)]) + result_lp = _jax_calc_reclamm_reserves_with_fees( + reserves, Va, Vb, prices, + DEFAULT_CENTEREDNESS_MARGIN, + DEFAULT_DAILY_PRICE_SHIFT_BASE, + DEFAULT_SECONDS_PER_STEP, + fees=0.003, + arb_thresh=0.0, + arb_fees=0.0, + all_sig_variations=ALL_SIG_VARIATIONS_2, + lp_supply_array=lp_supply, + ) + + # Before doubling: identical + npt.assert_allclose(result_lp[:half], result_base[:half], rtol=1e-10) + # After doubling: reserves ~2x the baseline + npt.assert_allclose(result_lp[half], result_base[half] * 2.0, rtol=1e-6) + + def test_lp_supply_halving_scales_reserves(self): + """BPT supply halving halfway → reserves ~0.5x at that step.""" + reserves, Va, Vb = _init_pool() + n_steps = 40 + half = n_steps // 2 + prices = _make_constant_prices(2500.0, 1.0, n_steps) + + result_base = _jax_calc_reclamm_reserves_with_fees( + reserves, Va, Vb, prices, + DEFAULT_CENTEREDNESS_MARGIN, + DEFAULT_DAILY_PRICE_SHIFT_BASE, + DEFAULT_SECONDS_PER_STEP, + fees=0.003, + arb_thresh=0.0, + arb_fees=0.0, + all_sig_variations=ALL_SIG_VARIATIONS_2, + ) + + lp_supply = jnp.concatenate([jnp.ones(half), 0.5 * jnp.ones(n_steps - half)]) + result_lp = _jax_calc_reclamm_reserves_with_fees( + reserves, Va, Vb, prices, + DEFAULT_CENTEREDNESS_MARGIN, + DEFAULT_DAILY_PRICE_SHIFT_BASE, + DEFAULT_SECONDS_PER_STEP, + fees=0.003, + arb_thresh=0.0, + arb_fees=0.0, + all_sig_variations=ALL_SIG_VARIATIONS_2, + lp_supply_array=lp_supply, + ) + + # Before halving: identical + npt.assert_allclose(result_lp[:half], result_base[:half], rtol=1e-10) + # After halving: reserves ~0.5x the baseline + npt.assert_allclose(result_lp[half], result_base[half] * 0.5, rtol=1e-6) + + def test_lp_supply_preserves_price(self): + """Price ratio (Ra+Va)/(Rb+Vb) unchanged through LP supply scaling. + + Ported from Foundry: assertEq(price_before, price_after) in + testAddLiquidity__Fuzz. + """ + reserves, Va, Vb = _init_pool() + n_steps = 40 + half = n_steps // 2 + # Trending so virtual balances shift — makes price preservation non-trivial + prices = _make_trending_prices(2500.0, 3500.0, 1.0, n_steps) + + lp_supply = jnp.concatenate([jnp.ones(half), 2.0 * jnp.ones(n_steps - half)]) + + # Use full_state variant to get Va/Vb history + result_reserves, Va_hist, Vb_hist = _jax_calc_reclamm_reserves_zero_fees_full_state( + reserves, Va, Vb, prices, + DEFAULT_CENTEREDNESS_MARGIN, + DEFAULT_DAILY_PRICE_SHIFT_BASE, + DEFAULT_SECONDS_PER_STEP, + lp_supply_array=lp_supply, + ) + + # Price = (Rb + Vb) / (Ra + Va) — should be identical at step half-1 and half + # (the supply change happens at start of step `half`, before arb) + Ra_before = result_reserves[half - 1, 0] + Rb_before = result_reserves[half - 1, 1] + Va_before = Va_hist[half - 1] + Vb_before = Vb_hist[half - 1] + price_before = (Rb_before + Vb_before) / (Ra_before + Va_before) + + # At step `half`, the carry from step half-1 gets scaled, then arb runs. + # We need to check price right after scaling, before arb. + # The closest check: price at step `half` output should reflect + # scaled reserves with arb on top. Instead, verify that a constant-price + # run preserves exact ratio. + prices_const = _make_constant_prices(2500.0, 1.0, n_steps) + res_c, Va_c, Vb_c = _jax_calc_reclamm_reserves_zero_fees_full_state( + reserves, Va, Vb, prices_const, + DEFAULT_CENTEREDNESS_MARGIN, + DEFAULT_DAILY_PRICE_SHIFT_BASE, + DEFAULT_SECONDS_PER_STEP, + lp_supply_array=lp_supply, + ) + # With constant prices and no arb, price is just initial ratio throughout + price_at_half_minus_1 = (res_c[half - 1, 1] + Vb_c[half - 1]) / ( + res_c[half - 1, 0] + Va_c[half - 1] + ) + price_at_half = (res_c[half, 1] + Vb_c[half]) / ( + res_c[half, 0] + Va_c[half] + ) + npt.assert_allclose( + float(price_at_half_minus_1), float(price_at_half), rtol=1e-10, + err_msg="Price ratio should be preserved through LP supply change", + ) + + def test_lp_supply_preserves_centeredness(self): + """Centeredness unchanged through LP supply scaling. + + Ported from Foundry: centeredness invariance in testAddLiquidity__Fuzz. + """ + reserves, Va, Vb = _init_pool() + n_steps = 40 + half = n_steps // 2 + prices = _make_constant_prices(2500.0, 1.0, n_steps) + + lp_supply = jnp.concatenate([jnp.ones(half), 2.0 * jnp.ones(n_steps - half)]) + + res, Va_hist, Vb_hist = _jax_calc_reclamm_reserves_zero_fees_full_state( + reserves, Va, Vb, prices, + DEFAULT_CENTEREDNESS_MARGIN, + DEFAULT_DAILY_PRICE_SHIFT_BASE, + DEFAULT_SECONDS_PER_STEP, + lp_supply_array=lp_supply, + ) + + c_before, _ = compute_centeredness( + res[half - 1, 0], res[half - 1, 1], Va_hist[half - 1], Vb_hist[half - 1] + ) + c_after, _ = compute_centeredness( + res[half, 0], res[half, 1], Va_hist[half], Vb_hist[half] + ) + npt.assert_allclose( + float(c_before), float(c_after), rtol=1e-10, + err_msg="Centeredness should be preserved through LP supply change", + ) + + def test_lp_supply_through_pool_class(self): + """Pool class passes lp_supply_array to underlying computation.""" + from quantammsim.pools.creator import create_pool + from quantammsim.runners.jax_runner_utils import Hashabledict + + pool = create_pool("reclamm") + + params = { + "price_ratio": DEFAULT_PRICE_RATIO, + "centeredness_margin": DEFAULT_CENTEREDNESS_MARGIN, + "daily_price_shift_base": DEFAULT_DAILY_PRICE_SHIFT_BASE, + } + + n_steps = 20 + prices = _make_trending_prices(2500.0, 3000.0, 1.0, n_steps) + + run_fingerprint = Hashabledict({ + "n_assets": 2, + "bout_length": n_steps + 1, + "initial_pool_value": 1_000_000.0, + "arb_frequency": 1, + "do_arb": True, + "fees": 0.003, + "gas_cost": 0.0, + "arb_fees": 0.0, + "tokens": ("ETH", "USDC"), + "numeraire": "USDC", + "all_sig_variations": tuple(map(tuple, [[1, -1], [-1, 1]])), + "ste_temperature": 10.0, + }) + + start_index = jnp.array([0, 0]) + + from quantammsim.core_simulator.dynamic_inputs import DynamicInputArrays + + lp_supply = jnp.concatenate([jnp.ones(10), 2.0 * jnp.ones(10)]) + + di_with_lp = DynamicInputArrays( + trades=None, + fees=jnp.full(n_steps, 0.003), + gas_cost=jnp.zeros(n_steps), + arb_fees=jnp.zeros(n_steps), + lp_supply=lp_supply, + reclamm_price_ratio_updates=jnp.array([[0.0, 0.0, 0.0, jnp.nan]]), + ) + di_without_lp = DynamicInputArrays( + trades=None, + fees=jnp.full(n_steps, 0.003), + gas_cost=jnp.zeros(n_steps), + arb_fees=jnp.zeros(n_steps), + lp_supply=jnp.ones(n_steps), + reclamm_price_ratio_updates=jnp.array([[0.0, 0.0, 0.0, jnp.nan]]), + ) + + res_with_lp, _ = pool.calculate_reserves_and_fee_revenue_with_dynamic_inputs( + params, run_fingerprint, prices, start_index, + dynamic_inputs=di_with_lp, + ) + res_without_lp, _ = pool.calculate_reserves_and_fee_revenue_with_dynamic_inputs( + params, run_fingerprint, prices, start_index, + dynamic_inputs=di_without_lp, + ) + + # First 10 steps identical, then diverge + npt.assert_allclose(res_with_lp[:10], res_without_lp[:10], rtol=1e-10) + assert not jnp.allclose(res_with_lp[10:], res_without_lp[10:]), ( + "LP supply change should produce different reserves" + ) + + def test_lp_supply_with_fee_revenue(self): + """Doubling LP supply → fee revenue increases (bigger pool → bigger noise trades).""" + reserves, Va, Vb = _init_pool() + n_steps = 40 + half = n_steps // 2 + prices = _make_trending_prices(2500.0, 3500.0, 1.0, n_steps) + # lp_fee_revenue_usd is noise-only; configure mm_observed with a large + # K so noise volume scales ~linearly with pool TVL. + noise_kwargs = { + "noise_model": "mm_observed", + "noise_base_array": jnp.full(n_steps, 13.8), + "competitor_tvl_array": jnp.full(n_steps, 1e10), + } + + # Baseline: no supply change + _, rev_base = _jax_calc_reclamm_reserves_and_fee_revenue_with_fees( + reserves, Va, Vb, prices, + DEFAULT_CENTEREDNESS_MARGIN, + DEFAULT_DAILY_PRICE_SHIFT_BASE, + DEFAULT_SECONDS_PER_STEP, + fees=0.003, + arb_thresh=0.0, + arb_fees=0.0, + all_sig_variations=ALL_SIG_VARIATIONS_2, + **noise_kwargs, + ) + + # Supply doubles halfway + lp_supply = jnp.concatenate([jnp.ones(half), 2.0 * jnp.ones(n_steps - half)]) + _, rev_lp = _jax_calc_reclamm_reserves_and_fee_revenue_with_fees( + reserves, Va, Vb, prices, + DEFAULT_CENTEREDNESS_MARGIN, + DEFAULT_DAILY_PRICE_SHIFT_BASE, + DEFAULT_SECONDS_PER_STEP, + fees=0.003, + arb_thresh=0.0, + arb_fees=0.0, + all_sig_variations=ALL_SIG_VARIATIONS_2, + lp_supply_array=lp_supply, + **noise_kwargs, + ) + + # After doubling, fee revenue per step should be larger + # (pool is 2x bigger → arb trades are 2x bigger → fees are 2x) + post_double_base = rev_base[half:].sum() + post_double_lp = rev_lp[half:].sum() + assert float(post_double_lp) > float(post_double_base), ( + f"Fee revenue should increase after doubling: {post_double_lp} <= {post_double_base}" + ) + + def test_lp_supply_e2e_do_run_on_historic_data(self): + """End-to-end: lp_supply flows through do_run_on_historic_data via DynamicInputFrames.""" + import pandas as pd + from quantammsim.runners.jax_runners import do_run_on_historic_data + from quantammsim.core_simulator.dynamic_inputs import DynamicInputFrames + + fp = { + "rule": "reclamm", + "tokens": ["ETH", "USDC"], + "startDateString": "2023-01-01 00:00:00", + "endDateString": "2023-01-15 00:00:00", + "initial_pool_value": 1_000_000.0, + "do_arb": True, + "fees": 0.003, + } + params = { + "price_ratio": jnp.array(4.0), + "centeredness_margin": jnp.array(0.2), + "daily_price_shift_base": jnp.array(DEFAULT_DAILY_PRICE_SHIFT_BASE), + } + + # Baseline: no LP supply change + result_base = do_run_on_historic_data( + run_fingerprint={**fp}, + params={**params}, + root=TEST_DATA_DIR, + ) + + # LP supply doubles halfway through the period + # unix column must be in milliseconds (matches windowing_utils convention) + start_unix_ms = int(pd.Timestamp("2023-01-01").timestamp() * 1000) + mid_unix_ms = int(pd.Timestamp("2023-01-08").timestamp() * 1000) + lp_supply_df = pd.DataFrame({ + "unix": [start_unix_ms, mid_unix_ms], + "lp_supply": [1.0, 2.0], + }) + + result_lp = do_run_on_historic_data( + run_fingerprint={**fp}, + params={**params}, + dynamic_input_frames=DynamicInputFrames(lp_supply=lp_supply_df), + root=TEST_DATA_DIR, + ) + + # Final values should differ — doubling LP supply changes pool dynamics + base_val = float(result_base["final_value"]) + lp_val = float(result_lp["final_value"]) + assert base_val != lp_val, ( + f"LP supply change should affect final value: base={base_val}, lp={lp_val}" + ) + # Doubled pool should have higher final value (more reserves) + assert lp_val > base_val, ( + f"Doubled LP supply should increase final value: {lp_val} <= {base_val}" + ) diff --git a/tests/scripts/dynamic_gas_test.py b/tests/scripts/dynamic_gas_test.py index d656b6a..920a7e3 100644 --- a/tests/scripts/dynamic_gas_test.py +++ b/tests/scripts/dynamic_gas_test.py @@ -1,7 +1,9 @@ -from quantammsim.runners.jax_runners import do_run_on_historic_data import jax.numpy as jnp import pandas as pd +from quantammsim.core_simulator.dynamic_inputs import DynamicInputFrames +from quantammsim.runners.jax_runners import do_run_on_historic_data + # Print the results print("=" * 100) print("Simulation Results:") @@ -32,11 +34,21 @@ run_fingerprint["do_trades"] = False result_w_gas_and_fees = do_run_on_historic_data( - run_fingerprint, params, gas_cost_df=gas_df, fees_df=fees_df + run_fingerprint, + params, + dynamic_input_frames=DynamicInputFrames(gas_cost=gas_df, fees=fees_df), +) +result_w_gas_only = do_run_on_historic_data( + run_fingerprint, + params, + dynamic_input_frames=DynamicInputFrames(gas_cost=gas_df), ) -result_w_gas_only = do_run_on_historic_data(run_fingerprint, params, gas_cost_df=gas_df) -result_w_fees_only = do_run_on_historic_data(run_fingerprint, params, fees_df=fees_df) +result_w_fees_only = do_run_on_historic_data( + run_fingerprint, + params, + dynamic_input_frames=DynamicInputFrames(fees=fees_df), +) print(result_w_gas_and_fees["value"][-1440+1]) print(result_w_gas_only["value"][-1440+1]) diff --git a/tests/scripts/test_compare_reclamm_geometric_noise_runs.py b/tests/scripts/test_compare_reclamm_geometric_noise_runs.py new file mode 100644 index 0000000..c211e7e --- /dev/null +++ b/tests/scripts/test_compare_reclamm_geometric_noise_runs.py @@ -0,0 +1,189 @@ +"""Tests for adjacent-row sourcing in compare_reclamm_geometric_noise_runs.py.""" + +from __future__ import annotations + +import importlib.util +from pathlib import Path + +import numpy as np + + +SCRIPT_PATH = ( + Path(__file__).resolve().parents[2] + / "scripts" + / "reclamm" + / "compare_reclamm_geometric_noise_runs.py" +) + + +def load_script_module(): + spec = importlib.util.spec_from_file_location( + "test_compare_reclamm_geometric_noise_runs_module", + SCRIPT_PATH, + ) + module = importlib.util.module_from_spec(spec) + assert spec.loader is not None + spec.loader.exec_module(module) + return module + + +def test_build_run_specs_from_adjacent_row_maps_csv_cells_to_two_specs(): + module = load_script_module() + row = { + "metric_key": "noise_vs_arb_geometric_improvement_pct", + "metric_unit": "pct", + "source_noise_profile": "market_linear", + "pair_slug": "price_ratio_vs_margin", + "slice_slug": "q2", + "adjacency_axis": "horizontal", + "heatmap_value_diff_abs": 54.223889391076895, + "1_price_ratio": 1.335, + "1_centeredness_margin": 0.3184210526, + "1_daily_price_shift_exponent": 0.1975, + "1_tvl_usd": 1_000_000.0, + "1_heatmap_value": -53.0543210862, + "2_price_ratio": 1.36, + "2_centeredness_margin": 0.3184210526, + "2_daily_price_shift_exponent": 0.1975, + "2_tvl_usd": 1_000_000.0, + "2_heatmap_value": 1.1695683049, + } + + description, run_specs = module.build_run_specs_from_adjacent_row( + row, + csv_path=Path("adjacent_pairs.csv"), + row_index=0, + ) + + assert "adjacent_pairs.csv row 0" in description + assert "price_ratio_vs_margin q2" in description + assert "horizontal" in description + assert "noise_profile=market_linear" in description + assert len(run_specs) == 2 + assert run_specs[0]["name"] == "Top diff row cell 1" + assert run_specs[0]["price_ratio"] == 1.335 + assert run_specs[0]["centeredness_margin"] == 0.3184210526 + assert run_specs[0]["daily_price_shift_exponent"] == 0.1975 + assert run_specs[0]["tvl_usd"] == 1_000_000.0 + assert run_specs[0]["color"] == "C0" + assert run_specs[0]["source_noise_profile"] == "market_linear" + assert "heatmap_value=-53.054321" in run_specs[0]["reason"] + assert run_specs[1]["name"] == "Top diff row cell 2" + assert run_specs[1]["price_ratio"] == 1.36 + assert run_specs[1]["color"] == "C1" + + +def test_default_output_file_for_adjacent_csv_uses_csv_stem_and_row_index(): + module = load_script_module() + output = module.default_output_file_for_adjacent_csv( + Path("scripts/results/reclamm_heatmap_adjacency/example.csv"), + row_index=3, + ) + + assert ( + output.as_posix() + == "scripts/results/reclamm_heatmap_adjacency/example_row_3_geometric_noise_compare.png" + ) + + +def test_build_run_config_rejects_legacy_calibrated_noise_profile(): + module = load_script_module() + base_config = { + "name": "base", + "price_ratio": 1.1, + "centeredness_margin": 0.6, + "daily_price_shift_exponent": 0.1, + "initial_pool_value": 1_000_000.0, + "noise_model": "market_linear", + "reclamm_noise_params": {"foo": 1.0}, + "noise_arrays_path": "path.npz", + } + spec = { + "name": "cell", + "price_ratio": 1.335, + "centeredness_margin": 0.3184210526, + "daily_price_shift_exponent": 0.1975, + "tvl_usd": 1_000_000.0, + "source_noise_profile": "legacy_calibrated", + } + + import pytest + + with pytest.raises(ValueError): + module.build_run_config(spec, base_config=base_config) + + +def test_build_run_variants_canonicalizes_noise_and_arb_only_configs(): + module = load_script_module() + fixed_path = str( + Path(__file__).resolve().parents[2] + / "results" + / "linear_market_noise" + / "_sim_arrays" + / "0x9d1fcf346ea1b0_2024-06-01_2026-03-01.npz" + ) + base_config = { + "name": "base", + "price_ratio": 1.1, + "centeredness_margin": 0.6, + "daily_price_shift_exponent": 0.1, + "initial_pool_value": 1_000_000.0, + "noise_model": "market_linear", + "noise_arrays_path": fixed_path, + } + spec = { + "name": "cell", + "price_ratio": 1.335, + "centeredness_margin": 0.3184210526, + "daily_price_shift_exponent": 0.1975, + "tvl_usd": 1_000_000.0, + "source_noise_profile": "market_linear", + } + + class FakeThermostatCompare: + @staticmethod + def make_noise_variant_cfg(cfg, enable_noise_model): + updated = dict(cfg) + updated["enable_noise_model"] = bool(enable_noise_model) + updated["noise_model"] = "market_linear" if enable_noise_model else "arb_only" + updated["noise_arrays_path"] = fixed_path + updated["reclamm_noise_params"] = {"tvl_mean": 1.0, "tvl_std": 2.0} + return updated + + variants = module.build_run_variants(spec, base_config, FakeThermostatCompare) + + assert variants["noise"]["noise_model"] == "market_linear" + assert variants["arb"]["noise_model"] == "arb_only" + assert variants["noise"]["noise_arrays_path"] == fixed_path + assert variants["arb"]["noise_arrays_path"] == fixed_path + assert variants["noise"]["reclamm_noise_params"] == {"tvl_mean": 1.0, "tvl_std": 2.0} + assert variants["arb"]["reclamm_noise_params"] == {"tvl_mean": 1.0, "tvl_std": 2.0} + + +def test_print_run_inputs_to_terminal_includes_fingerprint_and_update_params(capsys): + module = load_script_module() + cfg = { + "name": "cell", + "variant_label": "arb-only", + } + run_fingerprint = { + "tokens": ["AAVE", "ETH"], + "fees": np.float64(0.0025), + "arb_frequency": np.int64(14), + } + update_params = { + "price_ratio": np.array(1.335), + "centeredness_margin": np.array(0.3184210526), + "daily_price_shift_base": np.array(0.99999841596), + } + + module.print_run_inputs_to_terminal(cfg, run_fingerprint, update_params) + + captured = capsys.readouterr().out + assert "Run inputs for cell (arb-only):" in captured + assert '"run_fingerprint"' in captured + assert '"update_params"' in captured + assert '"tokens": [' in captured + assert '"AAVE"' in captured + assert '"arb_frequency": 14' in captured + assert '"price_ratio": 1.335' in captured diff --git a/tests/scripts/test_compare_reclamm_thermostats.py b/tests/scripts/test_compare_reclamm_thermostats.py new file mode 100644 index 0000000..caec6ae --- /dev/null +++ b/tests/scripts/test_compare_reclamm_thermostats.py @@ -0,0 +1,1022 @@ +"""Tests for heatmap skip logic in compare_reclamm_thermostats.py.""" + +import importlib.util +from pathlib import Path +import sys +import types + +import numpy as np +import pytest + + +SCRIPT_PATH = ( + Path(__file__).resolve().parents[2] + / "scripts" + / "compare_reclamm_thermostats.py" +) + + +def _load_script_module(): + injected_modules = {} + + def inject_module(name, module): + injected_modules[name] = sys.modules.get(name) + sys.modules[name] = module + + # Minimal stubs so the script can be imported without the full runtime + # stack present in this test environment. + jax_module = types.ModuleType("jax") + jax_module.numpy = np + inject_module("jax", jax_module) + inject_module("jax.numpy", np) + + pandas_module = types.ModuleType("pandas") + pandas_module.Timestamp = lambda value: value + pandas_module.DatetimeIndex = tuple + pandas_module.DataFrame = type("DataFrame", (), {}) + pandas_module.read_parquet = lambda *args, **kwargs: None + inject_module("pandas", pandas_module) + + matplotlib_module = types.ModuleType("matplotlib") + pyplot_module = types.ModuleType("matplotlib.pyplot") + pyplot_module.cm = types.SimpleNamespace(viridis=lambda values: values) + colors_module = types.ModuleType("matplotlib.colors") + colors_module.TwoSlopeNorm = object + colors_module.Normalize = object + colors_module.SymLogNorm = object + cm_module = types.ModuleType("matplotlib.cm") + cm_module.ScalarMappable = object + inject_module("matplotlib", matplotlib_module) + inject_module("matplotlib.pyplot", pyplot_module) + inject_module("matplotlib.colors", colors_module) + inject_module("matplotlib.cm", cm_module) + + quantammsim_module = types.ModuleType("quantammsim") + runners_module = types.ModuleType("quantammsim.runners") + jax_runners_module = types.ModuleType("quantammsim.runners.jax_runners") + jax_runners_module.do_run_on_historic_data = lambda **kwargs: { + "final_value": 0.0 + } + runners_module.jax_runners = jax_runners_module + + pools_module = types.ModuleType("quantammsim.pools") + reclamm_pkg_module = types.ModuleType("quantammsim.pools.reCLAMM") + reserves_module = types.ModuleType( + "quantammsim.pools.reCLAMM.reclamm_reserves" + ) + reserves_module.calibrate_arc_length_speed = lambda *args, **kwargs: 0.0 + reserves_module.compute_price_ratio = lambda *args, **kwargs: 1.0 + reserves_module.initialise_reclamm_reserves = ( + lambda *args, **kwargs: (np.array([1.0, 1.0]), 1.0, 1.0) + ) + reclamm_pkg_module.reclamm_reserves = reserves_module + pools_module.reCLAMM = reclamm_pkg_module + + utils_module = types.ModuleType("quantammsim.utils") + data_processing_module = types.ModuleType("quantammsim.utils.data_processing") + historic_utils_module = types.ModuleType( + "quantammsim.utils.data_processing.historic_data_utils" + ) + historic_utils_module.get_historic_parquet_data = lambda *args, **kwargs: None + data_processing_module.historic_data_utils = historic_utils_module + utils_module.data_processing = data_processing_module + + quantammsim_module.runners = runners_module + quantammsim_module.pools = pools_module + quantammsim_module.utils = utils_module + + inject_module("quantammsim", quantammsim_module) + inject_module("quantammsim.runners", runners_module) + inject_module("quantammsim.runners.jax_runners", jax_runners_module) + inject_module("quantammsim.pools", pools_module) + inject_module("quantammsim.pools.reCLAMM", reclamm_pkg_module) + inject_module("quantammsim.pools.reCLAMM.reclamm_reserves", reserves_module) + inject_module("quantammsim.utils", utils_module) + inject_module("quantammsim.utils.data_processing", data_processing_module) + inject_module( + "quantammsim.utils.data_processing.historic_data_utils", + historic_utils_module, + ) + + spec = importlib.util.spec_from_file_location( + "test_compare_reclamm_thermostats_module", + SCRIPT_PATH, + ) + module = importlib.util.module_from_spec(spec) + assert spec.loader is not None + try: + spec.loader.exec_module(module) + return module + finally: + for name, original in injected_modules.items(): + if original is None: + sys.modules.pop(name, None) + else: + sys.modules[name] = original + + +@pytest.fixture +def script_module(): + return _load_script_module() + + +@pytest.fixture +def base_cfg(): + return { + "tokens": ["AAVE", "ETH"], + "start": "2024-06-01 00:00:00", + "end": "2025-06-01 00:00:00", + "fees": 0.0025, + "price_ratio": 1.10, + "centeredness_margin": 0.60, + "daily_price_shift_exponent": 0.1, + "initial_pool_value": 5_000_000.0, + } + + +@pytest.fixture +def launch_final_values(): + return { + "geometric": 1_000_000.0, + "constant_arc_length": 1_010_000.0, + } + + +def test_make_noise_variant_cfg_disables_noise_fields(script_module, base_cfg): + noisy_cfg = { + **base_cfg, + "enable_noise_model": True, + "noise_model": "market_linear", + "noise_artifact_dir": "results/linear_market_noise", + "noise_pool_id": "0x9d1fcf346ea1b0", + "gas_cost": 1.0, + "protocol_fee_split": 0.25, + "reclamm_noise_params": {"tvl_mean": 1.0, "tvl_std": 2.0}, + "noise_arrays_path": "results/linear_market_noise/_sim_arrays/aave_eth.npz", + "arb_frequency": 6, + } + + arb_only_cfg = script_module.make_noise_variant_cfg( + noisy_cfg, + enable_noise_model=False, + ) + resolved = script_module.resolve_reclamm_noise_settings(arb_only_cfg) + + assert arb_only_cfg["enable_noise_model"] is False + assert arb_only_cfg["noise_model"] == "arb_only" + assert arb_only_cfg["noise_reference_model"] == "market_linear" + assert arb_only_cfg["gas_cost"] == script_module.DEFAULT_GAS_COST + assert arb_only_cfg["protocol_fee_split"] == script_module.DEFAULT_PROTOCOL_FEE_SPLIT + assert arb_only_cfg["arb_frequency"] == script_module.FIXED_COMPARE_ARB_FREQUENCY + assert arb_only_cfg["noise_artifact_dir"] == script_module.DEFAULT_MARKET_LINEAR_ARTIFACT_DIR + assert arb_only_cfg["noise_pool_id"] == script_module.AAVE_WETH_POOL_ID + assert arb_only_cfg["noise_arrays_path"] == script_module.DEFAULT_MARKET_LINEAR_NOISE_ARRAYS_PATH + assert "reclamm_noise_params" not in arb_only_cfg + assert resolved["noise_model"] == "arb_only" + assert resolved["noise_arrays_path"] == script_module.DEFAULT_MARKET_LINEAR_NOISE_ARRAYS_PATH + assert set(resolved["reclamm_noise_params"]) == {"tvl_mean", "tvl_std"} + assert resolved["noise_summary"] == ( + f"arb_only (arb_frequency={script_module.FIXED_COMPARE_ARB_FREQUENCY})" + ) + + +def test_make_noise_variant_cfg_defaults_to_fixed_compare_arb_cadence( + script_module, + base_cfg, +): + noisy_cfg = { + **base_cfg, + "enable_noise_model": True, + "noise_model": "market_linear", + } + + arb_only_cfg = script_module.make_noise_variant_cfg( + noisy_cfg, + enable_noise_model=False, + ) + resolved_noise = script_module.resolve_reclamm_noise_settings(noisy_cfg) + + assert arb_only_cfg["arb_frequency"] == script_module.FIXED_COMPARE_ARB_FREQUENCY + assert arb_only_cfg["noise_arrays_path"] == script_module.DEFAULT_MARKET_LINEAR_NOISE_ARRAYS_PATH + assert resolved_noise["arb_frequency"] == script_module.FIXED_COMPARE_ARB_FREQUENCY + + +def test_make_fingerprint_ignores_non_axis_override_fields(script_module, base_cfg): + canonical_cfg = { + **base_cfg, + "enable_noise_model": True, + "noise_model": "market_linear", + } + noisy_override_cfg = { + **canonical_cfg, + "arb_frequency": 6, + "gas_cost": 7.0, + "protocol_fee_split": 0.9, + "arb_fees": 3.0, + "noise_artifact_dir": "custom/noise/dir", + "noise_pool_id": "override-pool", + "reclamm_noise_params": {"tvl_mean": 999.0}, + "noise_arrays_path": "custom/path.npz", + } + + canonical_fingerprint = script_module.make_fingerprint(canonical_cfg, "geometric") + overridden_fingerprint = script_module.make_fingerprint( + noisy_override_cfg, + "geometric", + ) + canonical_key = script_module._make_method_cache_key(canonical_cfg, "geometric") + overridden_key = script_module._make_method_cache_key( + noisy_override_cfg, + "geometric", + ) + + assert overridden_fingerprint == canonical_fingerprint + assert overridden_key == canonical_key + assert overridden_fingerprint["arb_frequency"] == script_module.FIXED_COMPARE_ARB_FREQUENCY + assert overridden_fingerprint["gas_cost"] == script_module.DEFAULT_GAS_COST + assert ( + overridden_fingerprint["noise_arrays_path"] + == script_module.DEFAULT_MARKET_LINEAR_NOISE_ARRAYS_PATH + ) + assert ( + overridden_fingerprint["protocol_fee_split"] + == script_module.DEFAULT_PROTOCOL_FEE_SPLIT + ) + + +def test_arb_only_fingerprint_only_changes_noise_model(script_module, base_cfg): + noisy_cfg = { + **base_cfg, + "enable_noise_model": True, + "noise_model": "market_linear", + } + + noise_fingerprint = script_module.make_fingerprint(noisy_cfg, "geometric") + arb_only_cfg = script_module.make_noise_variant_cfg(noisy_cfg, enable_noise_model=False) + arb_fingerprint = script_module.make_fingerprint(arb_only_cfg, "geometric") + + expected_arb = dict(noise_fingerprint) + expected_arb["noise_model"] = "arb_only" + + assert noise_fingerprint["noise_model"] == "market_linear" + assert noise_fingerprint["noise_arrays_path"] == script_module.DEFAULT_MARKET_LINEAR_NOISE_ARRAYS_PATH + assert arb_fingerprint == expected_arb + + +def test_make_fingerprint_keeps_path_fallback_when_arrays_not_preloaded( + script_module, + base_cfg, +): + noisy_cfg = { + **base_cfg, + "enable_noise_model": True, + "noise_model": "market_linear", + } + + fingerprint = script_module.make_fingerprint(noisy_cfg, "geometric") + + assert fingerprint["noise_arrays_path"] == script_module.DEFAULT_MARKET_LINEAR_NOISE_ARRAYS_PATH + assert "noise_base_array" not in fingerprint + assert "noise_tvl_coeff_array" not in fingerprint + + +def test_make_fingerprint_includes_preloaded_market_linear_arrays( + script_module, + base_cfg, +): + noisy_cfg = { + **base_cfg, + "enable_noise_model": True, + "noise_model": "market_linear", + } + shared_noise = { + "arrays_path": script_module.DEFAULT_MARKET_LINEAR_NOISE_ARRAYS_PATH, + "noise_base_array": np.array([1.0, 2.0]), + "noise_tvl_coeff_array": np.array([3.0, 4.0]), + "tvl_mean": 10.0, + "tvl_std": 5.0, + } + + fingerprint = script_module.make_fingerprint( + noisy_cfg, + "geometric", + market_linear_noise_data=shared_noise, + ) + + assert fingerprint["noise_arrays_path"] == script_module.DEFAULT_MARKET_LINEAR_NOISE_ARRAYS_PATH + assert np.array_equal(fingerprint["noise_base_array"], shared_noise["noise_base_array"]) + assert np.array_equal( + fingerprint["noise_tvl_coeff_array"], + shared_noise["noise_tvl_coeff_array"], + ) + + +@pytest.mark.parametrize( + ("field", "updated_value"), + [ + ("fees", 0.01), + ("start", "2024-07-01 00:00:00"), + ("end", "2025-07-01 00:00:00"), + ("tokens", ["WBTC", "ETH"]), + ], +) +def test_method_cache_key_includes_run_identity_fields( + script_module, + base_cfg, + field, + updated_value, +): + canonical_cfg = { + **base_cfg, + "enable_noise_model": True, + "noise_model": "market_linear", + } + updated_cfg = dict(canonical_cfg) + updated_cfg[field] = updated_value + + canonical_key = script_module._make_method_cache_key(canonical_cfg, "geometric") + updated_key = script_module._make_method_cache_key(updated_cfg, "geometric") + + assert updated_key != canonical_key + + +@pytest.mark.parametrize( + "cfg_overrides", + [ + {"enable_noise_model": True, "noise_model": "calibrated"}, + { + "enable_noise_model": False, + "noise_model": "arb_only", + "noise_reference_model": "calibrated", + }, + ], +) +def test_resolve_reclamm_noise_settings_rejects_legacy_modes( + script_module, + base_cfg, + cfg_overrides, +): + cfg = { + **base_cfg, + **cfg_overrides, + } + + with pytest.raises(ValueError, match="market_linear"): + script_module.resolve_reclamm_noise_settings(cfg) + + +def test_generate_heatmaps_skips_existing_pairs( + monkeypatch, + script_module, + base_cfg, + launch_final_values, +): + monkeypatch.setattr(script_module.os.path, "exists", lambda filename: True) + monkeypatch.setattr( + script_module, + "build_heatmap_matrices", + lambda **kwargs: pytest.fail("heatmap sweep should have been skipped"), + ) + monkeypatch.setattr( + script_module, + "plot_heatmap", + lambda **kwargs: pytest.fail("plotting should have been skipped"), + ) + + script_module.generate_heatmaps( + base_cfg, + price_data=None, + launch_final_values=launch_final_values, + cache={}, + ) + + +def test_generate_heatmaps_only_renders_missing_artifacts( + monkeypatch, + script_module, + base_cfg, + launch_final_values, +): + pair = script_module.get_pair_heatmap_specs(base_cfg)[0] + slice_variant = pair["fixed_slices"][0] + pair_suffix = script_module._pair_slice_suffix(pair, slice_variant) + missing_file = script_module.tvl_artifact_filename( + "reclamm_heatmap_geometric_vs_launch_geometric_symlog20", + base_cfg, + suffix=pair_suffix, + ) + + def fake_exists(filename): + if filename == missing_file: + return False + return filename.startswith("reclamm_heatmap_") + + build_calls = [] + plotted_files = [] + + def fake_build_heatmap_matrices(**kwargs): + build_calls.append(kwargs) + return { + "geometric_vs_launch_geometric_pct": np.zeros( + (len(kwargs["y_values"]), len(kwargs["x_values"])), + dtype=float, + ) + } + + def fake_plot_heatmap(**kwargs): + plotted_files.append(kwargs["filename"]) + + monkeypatch.setattr(script_module.os.path, "exists", fake_exists) + monkeypatch.setattr( + script_module, + "build_heatmap_matrices", + fake_build_heatmap_matrices, + ) + monkeypatch.setattr(script_module, "plot_heatmap", fake_plot_heatmap) + + script_module.generate_heatmaps( + base_cfg, + price_data=None, + launch_final_values=launch_final_values, + cache={}, + ) + + assert len(build_calls) == 1 + assert build_calls[0]["progress_label"] == pair_suffix + assert build_calls[0]["base_cfg"][pair["fixed_key"]] == pytest.approx( + slice_variant["value"] + ) + assert build_calls[0]["metric_keys"] == ["geometric_vs_launch_geometric_pct"] + assert plotted_files == [missing_file] + + +def test_generate_heatmaps_only_renders_missing_improvement_artifacts( + monkeypatch, + script_module, + base_cfg, + launch_final_values, +): + pair = script_module.get_pair_heatmap_specs(base_cfg)[0] + slice_variant = pair["fixed_slices"][0] + pair_suffix = script_module._pair_slice_suffix(pair, slice_variant) + missing_file = script_module.tvl_artifact_filename( + "reclamm_heatmap_noise_vs_arb_geometric_improvement_symlog20", + base_cfg, + suffix=pair_suffix, + ) + + def fake_exists(filename): + if filename == missing_file: + return False + return filename.startswith("reclamm_heatmap_") + + build_calls = [] + plotted_files = [] + + def fake_build_heatmap_matrices(**kwargs): + build_calls.append(kwargs) + return { + "noise_vs_arb_geometric_improvement_pct": np.zeros( + (len(kwargs["y_values"]), len(kwargs["x_values"])), + dtype=float, + ) + } + + def fake_plot_heatmap(**kwargs): + plotted_files.append(kwargs["filename"]) + + monkeypatch.setattr(script_module.os.path, "exists", fake_exists) + monkeypatch.setattr( + script_module, + "build_heatmap_matrices", + fake_build_heatmap_matrices, + ) + monkeypatch.setattr(script_module, "plot_heatmap", fake_plot_heatmap) + + script_module.generate_heatmaps( + base_cfg, + price_data=None, + launch_final_values=launch_final_values, + cache={}, + ) + + assert len(build_calls) == 1 + assert build_calls[0]["progress_label"] == pair_suffix + assert build_calls[0]["base_cfg"][pair["fixed_key"]] == pytest.approx( + slice_variant["value"] + ) + assert build_calls[0]["metric_keys"] == [ + "noise_vs_arb_geometric_improvement_pct" + ] + assert plotted_files == [missing_file] + + +def test_generate_three_variable_3d_heatmaps_only_renders_missing_slice( + monkeypatch, + script_module, + base_cfg, + launch_final_values, +): + missing_file = script_module.tvl_artifact_filename( + "reclamm_heatmap_3d_geometric_vs_launch_geometric_symlog20", + base_cfg, + suffix="slice_q1", + ) + + def fake_exists(filename): + if filename == missing_file: + return False + return filename.startswith("reclamm_heatmap_3d_") + + build_calls = [] + plotted_files = [] + + def fake_build_heatmap_matrices(**kwargs): + build_calls.append(kwargs) + return { + "geometric_vs_launch_geometric_pct": np.zeros( + (len(kwargs["y_values"]), len(kwargs["x_values"])), + dtype=float, + ) + } + + def fake_plot_three_variable_heatmap_3d(**kwargs): + plotted_files.append(kwargs["filename"]) + + monkeypatch.setattr(script_module.os.path, "exists", fake_exists) + monkeypatch.setattr( + script_module, + "build_heatmap_matrices", + fake_build_heatmap_matrices, + ) + monkeypatch.setattr( + script_module, + "plot_three_variable_heatmap_3d", + fake_plot_three_variable_heatmap_3d, + ) + + script_module.generate_three_variable_3d_heatmaps( + base_cfg, + price_data=None, + launch_final_values=launch_final_values, + cache={}, + ) + + assert len(build_calls) == 3 + assert {call["progress_label"] for call in build_calls} == { + "3d_price_ratio_vs_margin_shift_exp_q1", + "3d_shift_exp_vs_margin_price_ratio_q1", + "3d_price_ratio_vs_shift_exp_margin_q1", + } + assert plotted_files == [missing_file] + + +def test_run_method_final_value_cached_reuses_persisted_parquet_value( + monkeypatch, + script_module, + base_cfg, +): + cfg = dict(base_cfg) + cache_key = script_module._make_method_cache_key(cfg, "geometric") + cache_key_hash = script_module._make_method_cache_hash(cache_key) + + class FakeFrame: + empty = False + + def itertuples(self, index=False): + return [ + types.SimpleNamespace( + cache_key_hash=cache_key_hash, + final_value=1_234_567.0, + ) + ] + + monkeypatch.setattr(script_module.os.path, "exists", lambda filename: True) + monkeypatch.setattr(script_module.pd, "read_parquet", lambda *args, **kwargs: FakeFrame()) + monkeypatch.setattr( + script_module, + "do_run_on_historic_data", + lambda **kwargs: pytest.fail("persisted forward-value cache should be reused"), + ) + + cache = script_module.make_sweep_cache(price_data=None, cache_scope_cfg=cfg) + value = script_module._run_method_final_value_cached(cfg, "geometric", cache) + + assert value == pytest.approx(1_234_567.0) + + +def test_run_method_final_value_cached_passes_preloaded_market_linear_arrays( + monkeypatch, + script_module, + base_cfg, +): + captured = {} + shared_noise = { + "arrays_path": script_module.DEFAULT_MARKET_LINEAR_NOISE_ARRAYS_PATH, + "noise_base_array": np.array([11.0, 12.0]), + "noise_tvl_coeff_array": np.array([21.0, 22.0]), + "tvl_mean": 100.0, + "tvl_std": 25.0, + } + + monkeypatch.setattr( + script_module, + "_load_persistent_final_value_cache", + lambda cache: cache.update( + { + "_persistent_final_value_cache_loaded": True, + "_persistent_final_value_cache": {}, + "_persistent_final_value_next_batch_id": 0, + } + ), + ) + monkeypatch.setattr(script_module, "flush_sweep_cache", lambda *args, **kwargs: None) + + def fake_do_run_on_historic_data(**kwargs): + captured["run_fingerprint"] = kwargs["run_fingerprint"] + return {"final_value": 1_111_111.0} + + monkeypatch.setattr( + script_module, + "do_run_on_historic_data", + fake_do_run_on_historic_data, + ) + + cfg = { + **base_cfg, + "enable_noise_model": True, + "noise_model": "market_linear", + } + cache = script_module.make_sweep_cache( + price_data=None, + cache_scope_cfg=cfg, + market_linear_noise_data=shared_noise, + ) + + value = script_module._run_method_final_value_cached(cfg, "geometric", cache) + + assert value == pytest.approx(1_111_111.0) + assert np.array_equal( + captured["run_fingerprint"]["noise_base_array"], + shared_noise["noise_base_array"], + ) + assert np.array_equal( + captured["run_fingerprint"]["noise_tvl_coeff_array"], + shared_noise["noise_tvl_coeff_array"], + ) + assert ( + captured["run_fingerprint"]["noise_arrays_path"] + == script_module.DEFAULT_MARKET_LINEAR_NOISE_ARRAYS_PATH + ) + + +def test_arc_speed_artifacts_only_build_missing_line_output( + monkeypatch, + script_module, + base_cfg, + launch_final_values, +): + missing_line = script_module.tvl_artifact_filename("reclamm_line_efficiency", base_cfg, suffix="arc_speed_vs_price_ratio") + + def fake_exists(filename): + if filename == missing_line: + return False + return filename.startswith("reclamm_") + + build_calls = [] + curve_calls = [] + plotted_lines = [] + + def fake_build_heatmap_matrices(**kwargs): + build_calls.append(kwargs) + return { + "efficiency_pct": np.zeros( + (len(kwargs["y_values"]), len(kwargs["x_values"])), + dtype=float, + ) + } + + def fake_build_metric_curve(**kwargs): + curve_calls.append(kwargs) + return np.zeros(len(kwargs["x_values"]), dtype=float) + + monkeypatch.setattr(script_module.os.path, "exists", fake_exists) + monkeypatch.setattr(script_module, "RUN_CONSTANT_ARC_LENGTH", True) + monkeypatch.setattr( + script_module, + "compute_auto_calibrated_arc_length_speed", + lambda cfg, price_data: 1.23e-4, + ) + monkeypatch.setattr( + script_module, + "build_heatmap_matrices", + fake_build_heatmap_matrices, + ) + monkeypatch.setattr( + script_module, + "build_metric_curve", + fake_build_metric_curve, + ) + monkeypatch.setattr( + script_module, + "plot_heatmap", + lambda **kwargs: pytest.fail("existing heatmap should not be redrawn"), + ) + monkeypatch.setattr( + script_module, + "plot_arc_speed_line_chart", + lambda **kwargs: plotted_lines.append(kwargs["filename"]), + ) + + script_module.generate_arc_speed_efficiency_artifacts( + base_cfg=base_cfg, + launch_cfg=dict(base_cfg), + price_data=None, + launch_final_values=launch_final_values, + cache={}, + ) + + assert len(build_calls) == 1 + assert build_calls[0]["progress_label"] == "arc_speed_vs_price_ratio" + assert len(curve_calls) == 1 + assert curve_calls[0]["x_key"] == "arc_length_speed" + assert plotted_lines == [missing_line] + + +def test_compute_auto_calibrated_arc_length_speed_uses_nearest_datetime_row( + monkeypatch, + script_module, +): + real_pd = pytest.importorskip("pandas") + monkeypatch.setattr(script_module.pd, "Timestamp", real_pd.Timestamp) + monkeypatch.setattr(script_module.pd, "DatetimeIndex", real_pd.DatetimeIndex) + monkeypatch.setattr(script_module.pd, "DataFrame", real_pd.DataFrame) + monkeypatch.setattr( + script_module.pd, + "MultiIndex", + real_pd.MultiIndex, + raising=False, + ) + + price_data = real_pd.DataFrame( + { + "close_AAVE": [10.0, 30.0], + "close_ETH": [1.0, 1.0], + }, + index=real_pd.DatetimeIndex( + ["2024-06-01 00:01:00", "2024-06-01 00:03:00"] + ), + ) + cfg = { + "tokens": ["AAVE", "ETH"], + "start": "2024-06-01 00:02:10", + "price_ratio": 1.10, + "centeredness_margin": 0.60, + "daily_price_shift_exponent": 0.1, + "initial_pool_value": 1_000_000.0, + } + captured = {} + + monkeypatch.setattr( + script_module, + "initialise_reclamm_reserves", + lambda *args, **kwargs: (np.array([1.0, 1.0]), 1.0, 1.0), + ) + monkeypatch.setattr( + script_module, + "compute_price_ratio", + lambda *args, **kwargs: 1.0, + ) + + def fake_calibrate_arc_length_speed(*args, **kwargs): + captured["market_price_0"] = args[7] + return 123.0 + + monkeypatch.setattr( + script_module, + "calibrate_arc_length_speed", + fake_calibrate_arc_length_speed, + ) + + result = script_module.compute_auto_calibrated_arc_length_speed(cfg, price_data) + + assert result == pytest.approx(123.0) + assert captured["market_price_0"] == pytest.approx(30.0) + + +def test_flush_sweep_cache_writes_compact_scalar_parquet(script_module): + captured = {} + + class FakeFrame: + def __init__(self, payload): + captured["payload"] = payload + + def to_parquet(self, path, index=False, compression=None): + captured["path"] = path + captured["index"] = index + captured["compression"] = compression + + script_module.pd.DataFrame = FakeFrame + script_module.os.makedirs = lambda *args, **kwargs: captured.setdefault( + "makedirs", args[0] + ) + + cache = { + "_pending_persistent_final_values": { + "abc123": { + "cache_key_hash": "abc123", + "final_value": 123.45, + "method": "geometric", + "enable_noise_model": True, + "noise_model": "market_linear", + "price_ratio": 1.1, + "centeredness_margin": 0.6, + "daily_price_shift_exponent": 0.1, + "initial_pool_value": 5_000_000.0, + "arb_frequency": 15, + } + }, + "_persistent_final_value_cache": {}, + "_persistent_final_value_next_batch_id": 0, + "_persistent_final_value_cache_loaded": True, + "_persistent_final_value_cache_path": "results/reclamm_heatmap_forward_cache/test/forward_values_tvl_5m.parquet", + } + + script_module.flush_sweep_cache(cache, force=True) + + assert set(captured["payload"].keys()) == set( + script_module.PERSISTED_FORWARD_VALUE_COLUMNS + ) + assert captured["payload"]["cache_key_hash"] == ["abc123"] + assert captured["payload"]["method"] == ["geometric"] + assert captured["payload"]["arb_frequency"] == [15] + assert captured["index"] is False + assert captured["compression"] == "zstd" + assert captured["makedirs"].endswith("forward_values_tvl_5m.parquet") + assert captured["path"].endswith("forward_values_tvl_5m.parquet/batch_00000000.parquet") + assert cache["_pending_persistent_final_values"] == {} + assert cache["_persistent_final_value_cache"] == {"abc123": 123.45} + assert cache["_persistent_final_value_next_batch_id"] == 1 + + +def test_load_persistent_final_value_cache_reads_sharded_parquet_dir( + monkeypatch, + script_module, +): + frames = { + "results/reclamm_heatmap_forward_cache/test/forward_values_tvl_1m.parquet/batch_00000000.parquet": [ + types.SimpleNamespace( + cache_key_hash="first123", + final_value=111.0, + method="geometric", + enable_noise_model=True, + noise_model="market_linear", + price_ratio=1.1, + centeredness_margin=0.6, + daily_price_shift_exponent=0.1, + initial_pool_value=1_000_000.0, + arb_frequency=15, + ) + ], + "results/reclamm_heatmap_forward_cache/test/forward_values_tvl_1m.parquet/batch_00000001.parquet": [ + types.SimpleNamespace( + cache_key_hash="second456", + final_value=222.0, + method="geometric", + enable_noise_model=False, + noise_model="arb_only", + price_ratio=1.2, + centeredness_margin=0.7, + daily_price_shift_exponent=0.2, + initial_pool_value=1_000_000.0, + arb_frequency=15, + ) + ], + } + + class FakeFrame: + def __init__(self, rows): + self._rows = rows + self.empty = not rows + + def itertuples(self, index=False): + return list(self._rows) + + monkeypatch.setattr(script_module.os.path, "exists", lambda filename: True) + monkeypatch.setattr(script_module.os.path, "isdir", lambda filename: True) + monkeypatch.setattr( + script_module.os, + "listdir", + lambda path: ["batch_00000001.parquet", "batch_00000000.parquet"], + ) + monkeypatch.setattr( + script_module.pd, + "read_parquet", + lambda path, *args, **kwargs: FakeFrame(frames[path]), + ) + + cache = { + "_persistent_final_value_cache_loaded": False, + "_persistent_final_value_cache_path": "results/reclamm_heatmap_forward_cache/test/forward_values_tvl_1m.parquet", + } + + script_module._load_persistent_final_value_cache(cache) + + assert cache["_persistent_final_value_cache"] == { + "first123": 111.0, + "second456": 222.0, + } + assert cache["_persistent_final_value_next_batch_id"] == 2 + + +def test_load_persistent_final_value_cache_supports_legacy_two_column_parquet( + monkeypatch, + script_module, +): + class FakeFrame: + empty = False + + def itertuples(self, index=False): + return [ + types.SimpleNamespace( + cache_key_hash="legacy123", + final_value=999.0, + ) + ] + + monkeypatch.setattr(script_module.os.path, "exists", lambda filename: True) + monkeypatch.setattr(script_module.pd, "read_parquet", lambda *args, **kwargs: FakeFrame()) + + cache = { + "_persistent_final_value_cache_loaded": False, + "_persistent_final_value_cache_path": "results/reclamm_heatmap_forward_cache/test/forward_values_tvl_1m.parquet", + } + + script_module._load_persistent_final_value_cache(cache) + + assert cache["_persistent_final_value_cache"] == {"legacy123": 999.0} + assert cache["_persistent_final_value_next_batch_id"] == 0 + + +def test_make_sweep_cache_does_not_eagerly_load_persisted_parquet( + monkeypatch, + script_module, +): + calls = [] + + monkeypatch.setattr( + script_module, + "_load_persistent_final_value_cache", + lambda cache: calls.append(dict(cache)), + ) + + cache = script_module.make_sweep_cache(price_data=None, cache_scope_cfg=None) + + assert calls == [] + assert cache["_persistent_final_value_cache"] == {} + assert cache["_persistent_final_value_next_batch_id"] == 0 + assert cache["_persistent_final_value_cache_loaded"] is False + + +def test_run_comparison_cached_only_uses_geometric_runs_when_constant_arc_disabled( + monkeypatch, + script_module, + base_cfg, + launch_final_values, +): + calls = [] + + monkeypatch.setattr( + script_module, + "_make_comparison_cache_key", + lambda cfg, launch_final_values: ("cache", round(float(cfg["price_ratio"]), 6)), + ) + + def fake_run_method_final_value_cached(cfg, method, cache): + calls.append((cfg.get("enable_noise_model", False), method)) + return { + (True, "geometric"): 1_050_000.0, + (False, "geometric"): 1_000_000.0, + }[(cfg.get("enable_noise_model", False), method)] + + monkeypatch.setattr( + script_module, + "_run_method_final_value_cached", + fake_run_method_final_value_cached, + ) + + metrics = script_module.run_comparison_cached( + base_cfg, + cache={"_comparison_cache": {}, "_final_value_cache": {}, "_shared_price_data": None}, + launch_final_values=launch_final_values, + metric_keys=( + "geometric_vs_launch_geometric_pct", + "noise_vs_arb_geometric_improvement_pct", + ), + ) + + assert calls == [(True, "geometric"), (False, "geometric")] + assert metrics == { + "geometric_vs_launch_geometric_pct": pytest.approx(5.0), + "noise_vs_arb_geometric_improvement_pct": pytest.approx(5.0), + } diff --git a/tests/scripts/test_find_adjacent_heatmap_pairs.py b/tests/scripts/test_find_adjacent_heatmap_pairs.py new file mode 100644 index 0000000..6ad7a39 --- /dev/null +++ b/tests/scripts/test_find_adjacent_heatmap_pairs.py @@ -0,0 +1,307 @@ +"""Tests for cache-backed adjacent heatmap pair detection.""" + +from __future__ import annotations + +import importlib.util +from pathlib import Path + +import pytest + + +SCRIPT_PATH = ( + Path(__file__).resolve().parents[2] + / "scripts" + / "reclamm" + / "find_adjacent_heatmap_pairs.py" +) + + +def load_script_module(): + spec = importlib.util.spec_from_file_location( + "test_find_adjacent_heatmap_pairs_module", + SCRIPT_PATH, + ) + module = importlib.util.module_from_spec(spec) + assert spec.loader is not None + spec.loader.exec_module(module) + return module + + +def make_cell(x_index, y_index, heatmap_value, **overrides): + cell = { + "metric_key": "noise_vs_arb_geometric_improvement_pct", + "metric_unit": "pct", + "pair_slug": "price_ratio_vs_margin", + "slice_slug": "q2", + "slice_label": "Q2", + "fixed_key": "daily_price_shift_exponent", + "fixed_value": 0.1975, + "price_ratio": 1.01 + 0.1 * x_index, + "centeredness_margin": 0.05 + 0.1 * y_index, + "daily_price_shift_exponent": 0.1975, + "tvl_usd": 1_000_000.0, + "heatmap_value": float(heatmap_value), + "x_index": int(x_index), + "y_index": int(y_index), + } + cell.update(overrides) + return cell + + +def test_find_adjacent_rows_for_slice_filters_and_sorts_descending(): + module = load_script_module() + records_by_coord = { + (0, 0): make_cell(0, 0, 0.0), + (0, 1): make_cell(1, 0, 35.0), + (0, 2): make_cell(2, 0, -10.0), + (1, 0): make_cell(0, 1, 5.0), + (1, 1): make_cell(1, 1, -40.0), + (1, 2): make_cell(2, 1, -50.0), + } + + rows = module.find_adjacent_rows_for_slice( + metric_key="noise_vs_arb_geometric_improvement_pct", + metric_unit="pct", + records_by_coord=records_by_coord, + x_count=3, + y_count=2, + min_diff=30.0, + ) + + assert [row["heatmap_value_diff_abs"] for row in rows] == [75.0, 45.0, 45.0, 40.0, 35.0] + assert rows[0]["adjacency_axis"] == "vertical" + assert rows[0]["1_x_index"] == 1 + assert rows[0]["1_y_index"] == 0 + assert rows[0]["2_x_index"] == 1 + assert rows[0]["2_y_index"] == 1 + + horizontal_rows = module.find_adjacent_rows_for_slice( + metric_key="noise_vs_arb_geometric_improvement_pct", + metric_unit="pct", + records_by_coord=records_by_coord, + x_count=3, + y_count=2, + min_diff=30.0, + adjacency_axis="horizontal", + ) + assert {row["adjacency_axis"] for row in horizontal_rows} == {"horizontal"} + + vertical_rows = module.find_adjacent_rows_for_slice( + metric_key="noise_vs_arb_geometric_improvement_pct", + metric_unit="pct", + records_by_coord=records_by_coord, + x_count=3, + y_count=2, + min_diff=30.0, + adjacency_axis="vertical", + ) + assert {row["adjacency_axis"] for row in vertical_rows} == {"vertical"} + + +def test_build_slice_cell_grid_reconstructs_metric_values_from_cache_hashes(): + module = load_script_module() + + class FakeCompareModule: + @staticmethod + def make_noise_variant_cfg(cfg, enable_noise_model): + updated = dict(cfg) + updated["enable_noise_model"] = bool(enable_noise_model) + return updated + + @staticmethod + def _make_method_cache_key(cfg, method): + return ( + method, + bool(cfg["enable_noise_model"]), + round(float(cfg["price_ratio"]), 6), + round(float(cfg["centeredness_margin"]), 6), + round(float(cfg["daily_price_shift_exponent"]), 6), + round(float(cfg["initial_pool_value"]), 2), + ) + + @staticmethod + def _make_method_cache_hash(key): + return repr(key) + + @staticmethod + def get_initial_pool_value(cfg): + return float(cfg["initial_pool_value"]) + + base_cfg = { + "price_ratio": 1.1, + "centeredness_margin": 0.3, + "daily_price_shift_exponent": 0.2, + "initial_pool_value": 1_000_000.0, + } + pair_spec = { + "slug": "price_ratio_vs_margin", + "x_values": [1.1, 1.2], + "y_values": [0.3, 0.4], + "x_key": "price_ratio", + "y_key": "centeredness_margin", + "fixed_key": "daily_price_shift_exponent", + } + slice_variant = { + "slug": "q2", + "label": "Q2", + "value": 0.2, + } + + heatmap_targets = { + (0, 0): (130.0, 100.0), # +30% + (0, 1): (200.0, 100.0), # +100% + (1, 0): (70.0, 100.0), # -30% + (1, 1): (160.0, 100.0), # +60% + } + cache_lookup = {} + for (y_index, x_index), (noise_geo, arb_geo) in heatmap_targets.items(): + cfg = dict(base_cfg) + cfg["price_ratio"] = pair_spec["x_values"][x_index] + cfg["centeredness_margin"] = pair_spec["y_values"][y_index] + noise_cfg, noise_method = FakeCompareModule.make_noise_variant_cfg(cfg, True), "geometric" + arb_cfg, arb_method = FakeCompareModule.make_noise_variant_cfg(cfg, False), "geometric" + + noise_key = FakeCompareModule._make_method_cache_key(noise_cfg, noise_method) + arb_key = FakeCompareModule._make_method_cache_key(arb_cfg, arb_method) + cache_lookup[FakeCompareModule._make_method_cache_hash(noise_key)] = noise_geo + cache_lookup[FakeCompareModule._make_method_cache_hash(arb_key)] = arb_geo + + slice_scan = module.build_slice_cell_grid( + compare_module=FakeCompareModule, + base_cfg=base_cfg, + pair_spec=pair_spec, + slice_variant=slice_variant, + metric_key="noise_vs_arb_geometric_improvement_pct", + cache_lookup=cache_lookup, + ) + + assert slice_scan["resolved_cell_count"] == 4 + assert slice_scan["missing_hash_count"] == 0 + assert slice_scan["records_by_coord"][(0, 0)]["heatmap_value"] == pytest.approx(30.0) + assert slice_scan["records_by_coord"][(0, 1)]["heatmap_value"] == pytest.approx(100.0) + assert slice_scan["records_by_coord"][(1, 0)]["heatmap_value"] == pytest.approx(-30.0) + assert slice_scan["records_by_coord"][(1, 1)]["heatmap_value"] == pytest.approx(60.0) + + rows = module.find_adjacent_rows_for_slice( + metric_key="noise_vs_arb_geometric_improvement_pct", + metric_unit="pct", + records_by_coord=slice_scan["records_by_coord"], + x_count=2, + y_count=2, + min_diff=30.0, + ) + + assert [row["heatmap_value_diff_abs"] for row in rows] == pytest.approx([90.0, 70.0, 60.0, 40.0]) + assert rows[0]["1_heatmap_value"] == pytest.approx(-30.0) + assert rows[0]["2_heatmap_value"] == pytest.approx(60.0) + + +def test_run_top_row_geometric_comparison_dispatches_to_compare_module(monkeypatch): + module = load_script_module() + captured = {} + + class FakeCompareModule: + @staticmethod + def run_adjacent_csv_row_comparison(csv_path, row_index=0, output_file=None): + captured["csv_path"] = csv_path + captured["row_index"] = row_index + captured["output_file"] = output_file + return "fake-output.png" + + monkeypatch.setattr( + module, + "load_geometric_compare_module", + lambda module_path=None: FakeCompareModule, + ) + + output = module.run_top_row_geometric_comparison( + Path("tmp_adjacent.csv"), + output_file="custom.png", + row_index=0, + ) + + assert output == "fake-output.png" + assert captured == { + "csv_path": Path("tmp_adjacent.csv"), + "row_index": 0, + "output_file": "custom.png", + } + + +def test_autodetect_lightweight_noise_profile_keeps_market_linear(): + module = load_script_module() + compare_context = module._LightweightCompareContext() + base_cfg = compare_context.configs_for_tvl(compare_context.CONFIGS, 1_000_000.0)[1] + pair_spec = compare_context.get_pair_heatmap_specs(base_cfg)[0] + + compare_context.set_noise_profile("market_linear") + module.autodetect_lightweight_noise_profile( + compare_module=compare_context, + base_cfg=base_cfg, + pair_specs=[pair_spec], + metric_key="noise_vs_arb_geometric_improvement_pct", + slice_slug="q2", + cache_lookup={}, + ) + + assert compare_context.noise_profile == "market_linear" + + +def test_lightweight_context_rejects_legacy_noise_profile(): + module = load_script_module() + compare_context = module._LightweightCompareContext() + + with pytest.raises(ValueError): + compare_context.set_noise_profile("legacy_calibrated") + + +def test_source_variant_sets_explicit_market_and_arb_only_noise_models(): + module = load_script_module() + compare_context = module._LightweightCompareContext() + cfg = compare_context.configs_for_tvl(compare_context.CONFIGS, 1_000_000.0)[1] + + noise_cfg, noise_method = module._source_variant( + compare_context, + cfg, + "noise_geometric", + ) + arb_cfg, arb_method = module._source_variant( + compare_context, + cfg, + "arb_geometric", + ) + + assert noise_method == "geometric" + assert noise_cfg["enable_noise_model"] is True + assert noise_cfg["noise_model"] == "market_linear" + assert noise_cfg["noise_arrays_path"] == compare_context.DEFAULT_MARKET_LINEAR_NOISE_ARRAYS_PATH + + assert arb_method == "geometric" + assert arb_cfg["enable_noise_model"] is False + assert arb_cfg["noise_model"] == "arb_only" + assert arb_cfg["noise_arrays_path"] == compare_context.DEFAULT_MARKET_LINEAR_NOISE_ARRAYS_PATH + + resolved_arb = compare_context.resolve_reclamm_noise_settings(arb_cfg) + assert resolved_arb["noise_model"] == "arb_only" + assert resolved_arb["noise_arrays_path"] == compare_context.DEFAULT_MARKET_LINEAR_NOISE_ARRAYS_PATH + assert set(resolved_arb["reclamm_noise_params"]) == {"tvl_mean", "tvl_std"} + assert resolved_arb["noise_cache_key"][0] == "arb_only" + + +def test_lightweight_context_defaults_to_fixed_compare_arb_cadence(): + module = load_script_module() + compare_context = module._LightweightCompareContext() + cfg = compare_context.configs_for_tvl(compare_context.CONFIGS, 1_000_000.0)[1] + cfg["arb_frequency"] = 6 + cfg["gas_cost"] = 99.0 + cfg["protocol_fee_split"] = 0.9 + + arb_cfg = compare_context.make_noise_variant_cfg(cfg, False) + resolved_noise = compare_context.resolve_reclamm_noise_settings(cfg) + + assert arb_cfg["arb_frequency"] == compare_context.FIXED_COMPARE_ARB_FREQUENCY + assert arb_cfg["noise_model"] == "arb_only" + assert arb_cfg["noise_arrays_path"] == compare_context.DEFAULT_MARKET_LINEAR_NOISE_ARRAYS_PATH + assert resolved_noise["arb_frequency"] == compare_context.FIXED_COMPARE_ARB_FREQUENCY + assert arb_cfg["gas_cost"] == compare_context.DEFAULT_GAS_COST + assert arb_cfg["protocol_fee_split"] == compare_context.DEFAULT_PROTOCOL_FEE_SPLIT diff --git a/tests/unit/test_jax_runner_utils.py b/tests/unit/test_jax_runner_utils.py index 9cd95f6..78ba377 100644 --- a/tests/unit/test_jax_runner_utils.py +++ b/tests/unit/test_jax_runner_utils.py @@ -13,6 +13,7 @@ import pytest import numpy as np import jax.numpy as jnp +import pandas as pd class TestHashabledict: @@ -159,6 +160,21 @@ def test_static_dict_is_hashable_with_real_fingerprint(self): h = hash(hd) assert isinstance(h, int) + def test_static_dict_accepts_dynamic_input_flags(self): + """Nested dynamic-input flags must remain hashable for JIT cache keys.""" + from quantammsim.core_simulator.dynamic_inputs import default_dynamic_input_flags + from quantammsim.runners.jax_runner_utils import create_static_dict, Hashabledict + + fp = self._make_fingerprint() + static = create_static_dict( + fp, + bout_length=10080, + overrides={"dynamic_input_flags": default_dynamic_input_flags()}, + ) + + assert static["dynamic_input_flags"]["use_dynamic_inputs"] is False + assert isinstance(hash(Hashabledict(static)), int) + def test_unknown_array_fields_dropped_with_warning(self): """Arrays not in _TRAINING_ONLY_FIELDS are dropped with a warning. @@ -237,6 +253,485 @@ def test_equality_with_non_dict_returns_false(self): assert d != [1, 2, 3] +class TestDynamicInputPreparation: + """Tests for dynamic input container construction and normalization.""" + + def test_empty_dynamic_input_arrays_have_stable_shapes(self): + """The empty hot-path bundle should use singleton fee-like placeholders only.""" + from quantammsim.core_simulator.dynamic_inputs import empty_dynamic_input_arrays + + dynamic_inputs = empty_dynamic_input_arrays() + + assert dynamic_inputs.trades is None + assert dynamic_inputs.fees.shape == (1,) + assert dynamic_inputs.gas_cost.shape == (1,) + assert dynamic_inputs.arb_fees.shape == (1,) + assert dynamic_inputs.lp_supply.shape == (1,) + + def test_dynamic_input_flags_reflect_present_frames(self): + """Frame-presence flags should drive static dynamic-input dispatch.""" + from quantammsim.core_simulator.dynamic_inputs import ( + DynamicInputFrames, + dynamic_input_flags_from_frames, + ) + + flags = dynamic_input_flags_from_frames( + DynamicInputFrames( + trades=pd.DataFrame({"unix": [1], "token_in": ["ETH"], "token_out": ["USDC"], "amount_in": [1.0]}), + fees=pd.DataFrame({"unix": [1], "fees": [0.003]}), + gas_cost=pd.DataFrame({"unix": [1], "trade_gas_cost_usd": [2.0]}), + ) + ) + + assert flags["use_dynamic_inputs"] is True + assert flags["has_trades"] is True + assert flags["has_dynamic_fees"] is True + assert flags["has_dynamic_gas_cost"] is True + assert flags["has_dynamic_arb_fees"] is False + assert flags["has_lp_supply"] is False + assert flags["has_reclamm_price_ratio_updates"] is False + + def test_prepare_dynamic_inputs_preserves_fixed_hot_path_structure(self): + """Normalization should return fixed bundles plus static dispatch flags.""" + from quantammsim.core_simulator.dynamic_inputs import DynamicInputFrames + from quantammsim.runners.jax_runner_utils import prepare_dynamic_inputs + + run_fingerprint = { + "tokens": ["ETH", "USDC"], + "startDateString": "2023-01-01 00:00:00", + "endDateString": "2023-01-01 00:02:00", + "endTestDateString": "2023-01-01 00:04:00", + } + + dynamic_input_frames = DynamicInputFrames( + trades=pd.DataFrame( + { + "unix": [1672531200000, 1672531320000], + "token_in": ["ETH", "USDC"], + "token_out": ["USDC", "ETH"], + "amount_in": [1.5, 2.0], + } + ), + fees=pd.DataFrame({"unix": [1672531200000], "fees": [0.003]}), + gas_cost=pd.DataFrame({"unix": [1672531200000], "trade_gas_cost_usd": [3.25]}), + arb_fees=pd.DataFrame({"unix": [1672531200000], "arb_fees": [0.0005]}), + lp_supply=pd.DataFrame({"unix": [1672531200000], "lp_supply": [1250.0]}), + ) + + prepared = prepare_dynamic_inputs( + run_fingerprint, + dynamic_input_frames=dynamic_input_frames, + do_test_period=True, + ) + + train_inputs = prepared["train_dynamic_inputs"] + test_inputs = prepared["test_dynamic_inputs"] + flags = prepared["dynamic_input_flags"] + + assert flags["use_dynamic_inputs"] is True + assert flags["has_trades"] is True + assert flags["has_dynamic_fees"] is True + assert flags["has_dynamic_gas_cost"] is True + assert flags["has_dynamic_arb_fees"] is True + assert flags["has_lp_supply"] is True + assert flags["has_reclamm_price_ratio_updates"] is False + assert train_inputs.trades.shape == (2, 3) + assert train_inputs.fees.shape == (2,) + assert train_inputs.gas_cost.shape == (2,) + assert train_inputs.arb_fees.shape == (2,) + assert train_inputs.lp_supply.shape == (2,) + assert train_inputs.reclamm_price_ratio_updates.shape == (1, 4) + assert test_inputs.trades.shape == (2, 3) + assert test_inputs.fees.shape == (2,) + assert test_inputs.gas_cost.shape == (2,) + assert test_inputs.arb_fees.shape == (2,) + assert test_inputs.lp_supply.shape == (2,) + assert test_inputs.reclamm_price_ratio_updates.shape == (1, 4) + np.testing.assert_allclose(np.asarray(train_inputs.fees), np.array([0.003, 0.003])) + + def test_prepare_dynamic_inputs_normalizes_reclamm_price_ratio_updates(self): + """Manual reCLAMM update schedules should map to per-step event rows.""" + from quantammsim.core_simulator.dynamic_inputs import DynamicInputFrames + from quantammsim.runners.jax_runner_utils import prepare_dynamic_inputs + + run_fingerprint = { + "tokens": ["ETH", "USDC"], + "arb_frequency": 1, + "startDateString": "2023-01-01 00:00:00", + "endDateString": "2023-01-01 00:05:00", + "endTestDateString": "2023-01-01 00:06:00", + } + start_unix = pd.Timestamp(run_fingerprint["startDateString"]).value // 10**6 + updates = pd.DataFrame( + { + "unix": [start_unix + 30_000, start_unix + 40_000], + "end_unix": [start_unix + 150_000, start_unix + 220_000], + "price_ratio": [4.0, 6.0], + } + ) + + prepared = prepare_dynamic_inputs( + run_fingerprint, + dynamic_input_frames=DynamicInputFrames( + reclamm_price_ratio_updates=updates + ), + ) + flags = prepared["dynamic_input_flags"] + schedule = np.asarray( + prepared["train_dynamic_inputs"].reclamm_price_ratio_updates + ) + + assert flags["has_reclamm_price_ratio_updates"] is True + assert flags["use_dynamic_inputs"] is True + assert schedule.shape == (5, 4) + # Same-step collision should keep the later event. + assert schedule[1, 0] == pytest.approx(1.0) + assert schedule[1, 1] == pytest.approx(6.0) + assert schedule[1, 2] == pytest.approx(4.0) + assert np.isnan(schedule[1, 3]) + assert np.all(schedule[[0, 2, 3, 4], 0] == 0.0) + + def test_prepare_dynamic_inputs_accepts_reclamm_updates_as_list(self): + """List payloads should be accepted for reCLAMM update schedules.""" + from quantammsim.core_simulator.dynamic_inputs import DynamicInputFrames + from quantammsim.runners.jax_runner_utils import prepare_dynamic_inputs + + run_fingerprint = { + "tokens": ["ETH", "USDC"], + "arb_frequency": 1, + "startDateString": "2023-01-01 00:00:00", + "endDateString": "2023-01-01 00:03:00", + "endTestDateString": "2023-01-01 00:04:00", + } + start_unix = pd.Timestamp(run_fingerprint["startDateString"]).value // 10**6 + + prepared = prepare_dynamic_inputs( + run_fingerprint, + dynamic_input_frames=DynamicInputFrames( + reclamm_price_ratio_updates=[ + { + "unix": start_unix, + "end_unix": start_unix + 120_000, + "price_ratio": 5.0, + "start_price_ratio": 4.25, + } + ] + ), + ) + schedule = np.asarray( + prepared["train_dynamic_inputs"].reclamm_price_ratio_updates + ) + assert schedule.shape == (3, 4) + assert schedule[0, 0] == pytest.approx(1.0) + assert schedule[0, 1] == pytest.approx(5.0) + assert schedule[0, 2] == pytest.approx(2.0) + assert schedule[0, 3] == pytest.approx(4.25) + + def test_prepare_dynamic_inputs_accepts_reclamm_updates_as_csv_path(self, tmp_path): + """CSV payloads should be accepted for reCLAMM update schedules.""" + from quantammsim.core_simulator.dynamic_inputs import DynamicInputFrames + from quantammsim.runners.jax_runner_utils import prepare_dynamic_inputs + + run_fingerprint = { + "tokens": ["ETH", "USDC"], + "arb_frequency": 1, + "startDateString": "2023-01-01 00:00:00", + "endDateString": "2023-01-01 00:03:00", + "endTestDateString": "2023-01-01 00:04:00", + } + start_unix = pd.Timestamp(run_fingerprint["startDateString"]).value // 10**6 + csv_path = tmp_path / "reclamm_updates.csv" + pd.DataFrame( + [ + { + "unix": start_unix + 60_000, + "end_unix": start_unix + 180_000, + "price_ratio": 6.0, + "start_price_ratio": 4.1, + } + ] + ).to_csv(csv_path, index=False) + + prepared = prepare_dynamic_inputs( + run_fingerprint, + dynamic_input_frames=DynamicInputFrames( + reclamm_price_ratio_updates=str(csv_path) + ), + ) + schedule = np.asarray( + prepared["train_dynamic_inputs"].reclamm_price_ratio_updates + ) + assert schedule.shape == (3, 4) + assert schedule[1, 0] == pytest.approx(1.0) + assert schedule[1, 1] == pytest.approx(6.0) + assert schedule[1, 2] == pytest.approx(2.0) + assert schedule[1, 3] == pytest.approx(4.1) + + def test_prepare_dynamic_inputs_rejects_prewindow_reclamm_events_without_start_ratio(self): + """In-progress pre-window events must provide start_price_ratio.""" + from quantammsim.core_simulator.dynamic_inputs import DynamicInputFrames + from quantammsim.runners.jax_runner_utils import prepare_dynamic_inputs + + run_fingerprint = { + "tokens": ["ETH", "USDC"], + "arb_frequency": 1, + "startDateString": "2023-01-01 00:00:00", + "endDateString": "2023-01-01 00:05:00", + "endTestDateString": "2023-01-01 00:06:00", + } + start_unix = pd.Timestamp(run_fingerprint["startDateString"]).value // 10**6 + + with pytest.raises(ValueError, match="start_price_ratio"): + prepare_dynamic_inputs( + run_fingerprint, + dynamic_input_frames=DynamicInputFrames( + reclamm_price_ratio_updates=pd.DataFrame( + { + "unix": [start_unix - 120_000], + "end_unix": [start_unix + 120_000], + "price_ratio": [4.5], + } + ) + ), + ) + + def test_prepare_dynamic_inputs_disables_reclamm_schedule_flag_when_window_has_no_events(self): + """Schedule flag should be disabled when no event lands in train/test windows.""" + from quantammsim.core_simulator.dynamic_inputs import DynamicInputFrames + from quantammsim.runners.jax_runner_utils import prepare_dynamic_inputs + + run_fingerprint = { + "tokens": ["ETH", "USDC"], + "arb_frequency": 1, + "startDateString": "2023-01-01 00:00:00", + "endDateString": "2023-01-01 00:05:00", + "endTestDateString": "2023-01-01 00:10:00", + } + start_unix = pd.Timestamp(run_fingerprint["startDateString"]).value // 10**6 + updates = pd.DataFrame( + { + "unix": [start_unix - 300_000], + "end_unix": [start_unix - 60_000], + "price_ratio": [5.0], + "start_price_ratio": [4.0], + } + ) + + prepared = prepare_dynamic_inputs( + run_fingerprint, + dynamic_input_frames=DynamicInputFrames( + reclamm_price_ratio_updates=updates + ), + do_test_period=True, + ) + + flags = prepared["dynamic_input_flags"] + assert flags["has_reclamm_price_ratio_updates"] is False + assert flags["use_dynamic_inputs"] is False + assert prepared["train_dynamic_inputs"].reclamm_price_ratio_updates.shape == (1, 4) + assert prepared["test_dynamic_inputs"].reclamm_price_ratio_updates.shape == (1, 4) + + def test_prepare_dynamic_inputs_uses_correct_test_period_values(self): + """Test-period arrays should use values effective from the test window onward.""" + from quantammsim.core_simulator.dynamic_inputs import DynamicInputFrames + from quantammsim.runners.jax_runner_utils import prepare_dynamic_inputs + + run_fingerprint = { + "tokens": ["ETH", "USDC"], + "startDateString": "2023-01-01 00:00:00", + "endDateString": "2023-01-01 00:02:00", + "endTestDateString": "2023-01-01 00:04:00", + } + end_unix = pd.Timestamp(run_fingerprint["endDateString"]).value // 10**6 + + prepared = prepare_dynamic_inputs( + run_fingerprint, + dynamic_input_frames=DynamicInputFrames( + fees=pd.DataFrame( + {"unix": [1672531200000, end_unix], "fees": [0.003, 0.004]} + ), + gas_cost=pd.DataFrame( + { + "unix": [1672531200000, end_unix], + "trade_gas_cost_usd": [1.5, 2.5], + } + ), + arb_fees=pd.DataFrame( + {"unix": [1672531200000, end_unix], "arb_fees": [0.0001, 0.0002]} + ), + lp_supply=pd.DataFrame( + {"unix": [1672531200000, end_unix], "lp_supply": [1000.0, 2000.0]} + ), + ), + do_test_period=True, + ) + + np.testing.assert_allclose( + np.asarray(prepared["test_dynamic_inputs"].fees), + np.array([0.004, 0.004]), + ) + np.testing.assert_allclose( + np.asarray(prepared["test_dynamic_inputs"].gas_cost), + np.array([2.5, 2.5]), + ) + np.testing.assert_allclose( + np.asarray(prepared["test_dynamic_inputs"].arb_fees), + np.array([0.0002, 0.0002]), + ) + np.testing.assert_allclose( + np.asarray(prepared["test_dynamic_inputs"].lp_supply), + np.array([2000.0, 2000.0]), + ) + + def test_resolve_dynamic_input_flags_promotes_explicit_bundle(self): + """Passing a bundle directly should force dynamic-path dispatch.""" + from quantammsim.core_simulator.dynamic_inputs import ( + empty_dynamic_input_arrays, + resolve_dynamic_input_flags, + ) + + flags = resolve_dynamic_input_flags( + empty_dynamic_input_arrays(), + { + "use_dynamic_inputs": False, + "has_trades": False, + "has_dynamic_fees": False, + "has_dynamic_gas_cost": False, + "has_dynamic_arb_fees": False, + "has_lp_supply": False, + "has_reclamm_price_ratio_updates": False, + }, + ) + + assert flags["use_dynamic_inputs"] is True + + def test_resolve_dynamic_input_components_falls_back_to_static_scalars(self): + """Static scalar config should materialize as singleton arrays when no frames are present.""" + from quantammsim.core_simulator.dynamic_inputs import ( + default_dynamic_input_flags, + resolve_dynamic_input_components, + ) + + resolved = resolve_dynamic_input_components( + dynamic_inputs=None, + dynamic_input_flags=default_dynamic_input_flags(), + static_dict={"fees": 0.003, "gas_cost": 2.5, "arb_fees": 0.0001}, + ) + + assert resolved["trades"] is None + np.testing.assert_allclose(np.asarray(resolved["fees"]), np.array([0.003])) + np.testing.assert_allclose(np.asarray(resolved["gas_cost"]), np.array([2.5])) + np.testing.assert_allclose(np.asarray(resolved["arb_fees"]), np.array([0.0001])) + np.testing.assert_allclose(np.asarray(resolved["lp_supply"]), np.array([1.0])) + + def test_resolve_dynamic_input_components_prefers_dynamic_values(self): + """Dynamic arrays should override static scalar defaults for enabled fields.""" + from quantammsim.core_simulator.dynamic_inputs import ( + DynamicInputArrays, + resolve_dynamic_input_components, + ) + + dynamic_inputs = DynamicInputArrays( + trades=jnp.array([[0.0, 1.0, 5.0]]), + fees=jnp.array([0.004]), + gas_cost=jnp.array([3.0]), + arb_fees=jnp.array([0.0003]), + lp_supply=jnp.array([1500.0]), + reclamm_price_ratio_updates=jnp.array([[1.0, 4.0, 3.0, jnp.nan]]), + ) + flags = { + "use_dynamic_inputs": True, + "has_trades": True, + "has_dynamic_fees": True, + "has_dynamic_gas_cost": True, + "has_dynamic_arb_fees": True, + "has_lp_supply": True, + "has_reclamm_price_ratio_updates": True, + } + + resolved = resolve_dynamic_input_components( + dynamic_inputs=dynamic_inputs, + dynamic_input_flags=flags, + static_dict={"fees": 0.0, "gas_cost": 0.0, "arb_fees": 0.0}, + ) + + np.testing.assert_allclose(np.asarray(resolved["trades"]), np.array([[0.0, 1.0, 5.0]])) + np.testing.assert_allclose(np.asarray(resolved["fees"]), np.array([0.004])) + np.testing.assert_allclose(np.asarray(resolved["gas_cost"]), np.array([3.0])) + np.testing.assert_allclose(np.asarray(resolved["arb_fees"]), np.array([0.0003])) + np.testing.assert_allclose(np.asarray(resolved["lp_supply"]), np.array([1500.0])) + np.testing.assert_allclose( + np.asarray(resolved["reclamm_price_ratio_updates"]), + np.array([[1.0, 4.0, 3.0, np.nan]]), + equal_nan=True, + ) + + def test_materialize_dynamic_inputs_leaves_trades_optional(self): + """No-trade paths should not expand placeholder trades into the scan inputs.""" + from quantammsim.core_simulator.dynamic_inputs import ( + empty_dynamic_input_arrays, + materialize_dynamic_inputs, + ) + + materialized = materialize_dynamic_inputs( + empty_dynamic_input_arrays(), + { + "use_dynamic_inputs": True, + "has_trades": False, + "has_dynamic_fees": False, + "has_dynamic_gas_cost": False, + "has_dynamic_arb_fees": False, + "has_lp_supply": False, + "has_reclamm_price_ratio_updates": False, + }, + static_dict={"fees": 0.003, "gas_cost": 2.5, "arb_fees": 0.0001}, + scan_len=4, + do_trades=False, + ) + + assert materialized.trades is None + np.testing.assert_allclose(np.asarray(materialized.fees), np.full(4, 0.003)) + np.testing.assert_allclose(np.asarray(materialized.gas_cost), np.full(4, 2.5)) + np.testing.assert_allclose(np.asarray(materialized.arb_fees), np.full(4, 0.0001)) + np.testing.assert_allclose(np.asarray(materialized.lp_supply), np.ones(4)) + np.testing.assert_allclose( + np.asarray(materialized.reclamm_price_ratio_updates), + np.array( + [ + [0.0, 0.0, 0.0, np.nan], + [0.0, 0.0, 0.0, np.nan], + [0.0, 0.0, 0.0, np.nan], + [0.0, 0.0, 0.0, np.nan], + ] + ), + equal_nan=True, + ) + + def test_materialize_dynamic_inputs_requires_trades_when_enabled(self): + """Trade-enabled scans should fail fast if no trade path is available.""" + from quantammsim.core_simulator.dynamic_inputs import ( + empty_dynamic_input_arrays, + materialize_dynamic_inputs, + ) + + with pytest.raises(ValueError, match="Trades must be provided"): + materialize_dynamic_inputs( + empty_dynamic_input_arrays(), + { + "use_dynamic_inputs": True, + "has_trades": False, + "has_dynamic_fees": True, + "has_dynamic_gas_cost": False, + "has_dynamic_arb_fees": False, + "has_lp_supply": False, + "has_reclamm_price_ratio_updates": False, + }, + static_dict={"fees": 0.003, "gas_cost": 0.0, "arb_fees": 0.0}, + scan_len=2, + do_trades=True, + ) + + class TestGetSigVariations: """Tests for get_sig_variations function.""" @@ -557,7 +1052,7 @@ def test_excludes_training_fields(self): fp = { "tokens": ["BTC", "ETH"], "optimisation_settings": {"lr": 0.01}, # Should be excluded - "startDateString": "2023-01-01", # Should be excluded + "startDateString": "2023-01-01", # Kept — needed by calibrated noise model "chunk_period": 60, "weight_interpolation_period": 60, "initial_pool_value": 1000000.0, @@ -573,7 +1068,9 @@ def test_excludes_training_fields(self): result = create_static_dict(fp, bout_length=1440) assert "optimisation_settings" not in result - assert "startDateString" not in result + # startDateString stays in the static dict — the calibrated noise + # model needs it in forward passes + assert "startDateString" in result def test_with_overrides(self): """Test that overrides are applied.""" diff --git a/tests/unit/test_jax_runners_comprehensive.py b/tests/unit/test_jax_runners_comprehensive.py index dcdd9dd..5269022 100644 --- a/tests/unit/test_jax_runners_comprehensive.py +++ b/tests/unit/test_jax_runners_comprehensive.py @@ -10,6 +10,7 @@ """ import pytest import numpy as np +import pandas as pd import jax.numpy as jnp import jax from copy import deepcopy @@ -18,6 +19,7 @@ from quantammsim.runners.jax_runners import ( train_on_historic_data, do_run_on_historic_data, + do_run_on_historic_data_with_provided_coarse_weights, ) from quantammsim.runners.jax_runner_utils import ( NestedHashabledict, @@ -31,8 +33,10 @@ create_static_dict, ) from quantammsim.runners.default_run_fingerprint import run_fingerprint_defaults +from quantammsim.core_simulator.dynamic_inputs import DynamicInputFrames from quantammsim.core_simulator.param_utils import recursive_default_set, check_run_fingerprint from quantammsim.pools.creator import create_pool +from quantammsim.utils.data_processing.historic_data_utils import get_data_dict from tests.conftest import TEST_DATA_DIR @@ -389,6 +393,328 @@ def test_multiple_param_sets(self, defaulted_run_fingerprint, sample_params): assert isinstance(results, list) assert len(results) == 2 + def test_dynamic_trades_change_balancer_reserves(self, defaulted_run_fingerprint): + """Dynamic trade input should change the reserve path in the runner.""" + fp = deepcopy(defaulted_run_fingerprint) + fp["rule"] = "balancer" + fp["do_arb"] = False + fp["fees"] = 0.0 + fp["gas_cost"] = 0.0 + fp["arb_fees"] = 0.0 + params = {"initial_weights_logits": jnp.array([0.0, 0.0])} + + trade_unix = pd.Timestamp(fp["startDateString"]).value // 10**6 + trades_df = pd.DataFrame( + { + "unix": [trade_unix], + "token_in": ["ETH"], + "token_out": ["USDC"], + "amount_in": [100.0], + } + ) + + result_without_trades = do_run_on_historic_data( + fp, + params=params, + root=TEST_DATA_DIR, + verbose=False, + ) + result_with_trades = do_run_on_historic_data( + fp, + params=params, + root=TEST_DATA_DIR, + verbose=False, + dynamic_input_frames=DynamicInputFrames(trades=trades_df), + ) + + assert not np.allclose( + np.asarray(result_without_trades["reserves"]), + np.asarray(result_with_trades["reserves"]), + ) + assert result_with_trades["reserves"][0, 0] > result_without_trades["reserves"][0, 0] + assert result_with_trades["reserves"][0, 1] < result_without_trades["reserves"][0, 1] + + def test_dynamic_arb_fees_match_scalar_arb_fees(self, defaulted_run_fingerprint): + """Constant dynamic arb fees should match the scalar arb-fee path.""" + fp = deepcopy(defaulted_run_fingerprint) + fp["rule"] = "balancer" + fp["fees"] = 0.003 + fp["do_arb"] = True + params = {"initial_weights_logits": jnp.array([0.0, 0.0])} + + arb_fee = 0.002 + arb_fees_df = pd.DataFrame( + { + "unix": [pd.Timestamp(fp["startDateString"]).value // 10**6], + "arb_fees": [arb_fee], + } + ) + + result_scalar = do_run_on_historic_data( + fp, + params=params, + root=TEST_DATA_DIR, + verbose=False, + arb_fees=arb_fee, + ) + result_dynamic = do_run_on_historic_data( + fp, + params=params, + root=TEST_DATA_DIR, + verbose=False, + dynamic_input_frames=DynamicInputFrames(arb_fees=arb_fees_df), + ) + + np.testing.assert_allclose( + np.asarray(result_dynamic["value"]), + np.asarray(result_scalar["value"]), + rtol=1e-6, + atol=1e-6, + ) + + def test_dynamic_lp_supply_changes_momentum_runner_path(self, defaulted_run_fingerprint, sample_params): + """LP supply changes should affect the main momentum runner path.""" + fp = deepcopy(defaulted_run_fingerprint) + fp["protocol_fee_split"] = 0.25 + + start_unix = pd.Timestamp(fp["startDateString"]).value // 10**6 + midpoint_unix = start_unix + 3 * 1440 * 60 * 1000 + constant_lp_supply_df = pd.DataFrame( + { + "unix": [start_unix], + "lp_supply": [1.0], + } + ) + stepped_lp_supply_df = pd.DataFrame( + { + "unix": [start_unix, midpoint_unix], + "lp_supply": [1.0, 2.0], + } + ) + + result_without_lp_supply = do_run_on_historic_data( + fp, + params=sample_params, + root=TEST_DATA_DIR, + verbose=False, + ) + result_with_constant_lp_supply = do_run_on_historic_data( + fp, + params=sample_params, + root=TEST_DATA_DIR, + verbose=False, + dynamic_input_frames=DynamicInputFrames(lp_supply=constant_lp_supply_df), + ) + result_with_stepped_lp_supply = do_run_on_historic_data( + fp, + params=sample_params, + root=TEST_DATA_DIR, + verbose=False, + dynamic_input_frames=DynamicInputFrames(lp_supply=stepped_lp_supply_df), + ) + + np.testing.assert_allclose( + np.asarray(result_with_constant_lp_supply["value"]), + np.asarray(result_without_lp_supply["value"]), + rtol=1e-6, + atol=1e-6, + ) + assert not np.allclose( + np.asarray(result_with_stepped_lp_supply["reserves"][-1]), + np.asarray(result_without_lp_supply["reserves"][-1]), + ) + assert float(result_with_stepped_lp_supply["final_value"]) != pytest.approx( + float(result_without_lp_supply["final_value"]) + ) + + def test_provided_coarse_weights_respect_scalar_and_dynamic_gas(self, defaulted_run_fingerprint, sample_params): + """Provided-coarse-weight path should honor both scalar gas and dynamic gas arrays.""" + fp = deepcopy(defaulted_run_fingerprint) + fp["protocol_fee_split"] = 0.0 + + data_dict = get_data_dict( + list_of_tickers=fp["tokens"], + run_fingerprint=fp, + data_kind=fp["optimisation_settings"]["training_data_kind"], + root=TEST_DATA_DIR, + max_memory_days=fp["max_memory_days"], + start_date_string=fp["startDateString"], + end_time_string=fp["endDateString"], + start_time_test_string=fp["endDateString"], + end_time_test_string=fp["endTestDateString"], + max_mc_version=fp["optimisation_settings"]["max_mc_version"], + do_test_period=False, + ) + + coarse_unix_values = ( + pd.date_range( + start=pd.Timestamp(fp["startDateString"]), + end=pd.Timestamp(fp["endDateString"]), + freq=f"{fp['chunk_period']}min", + ) + .astype(np.int64) + // 10**6 + ) + coarse_weights = { + "weights": jnp.tile(jnp.array([[0.5, 0.5]]), (len(coarse_unix_values), 1)), + "unix_values": jnp.asarray(coarse_unix_values), + } + + params = deepcopy(sample_params) + initial_prices = jnp.asarray(data_dict["prices"][data_dict["start_idx"]], dtype=jnp.float64) + params["initial_reserves"] = (jnp.array([0.5, 0.5]) * fp["initial_pool_value"]) / initial_prices + + gas_cost = 50.0 + gas_cost_df = pd.DataFrame( + { + "unix": [pd.Timestamp(fp["startDateString"]).value // 10**6], + "trade_gas_cost_usd": [gas_cost], + } + ) + + result_no_gas = do_run_on_historic_data_with_provided_coarse_weights( + fp, + coarse_weights=coarse_weights, + params=params, + root=TEST_DATA_DIR, + verbose=False, + ) + result_scalar_gas = do_run_on_historic_data_with_provided_coarse_weights( + fp, + coarse_weights=coarse_weights, + params=params, + root=TEST_DATA_DIR, + verbose=False, + gas_cost=gas_cost, + ) + result_dynamic_gas = do_run_on_historic_data_with_provided_coarse_weights( + fp, + coarse_weights=coarse_weights, + params=params, + root=TEST_DATA_DIR, + verbose=False, + dynamic_input_frames=DynamicInputFrames(gas_cost=gas_cost_df), + ) + + assert float(result_scalar_gas["final_value"]) != pytest.approx( + float(result_no_gas["final_value"]) + ) + np.testing.assert_allclose( + np.asarray(result_dynamic_gas["value"]), + np.asarray(result_scalar_gas["value"]), + rtol=1e-6, + atol=1e-6, + ) + + def test_reclamm_schedule_only_dynamic_input_changes_path(self, defaulted_run_fingerprint): + """Schedule-only reCLAMM dynamic inputs should alter reserve trajectory.""" + fp = deepcopy(defaulted_run_fingerprint) + fp["rule"] = "reclamm" + fp["do_arb"] = True + fp["fees"] = 0.0 + fp["gas_cost"] = 0.0 + fp["arb_fees"] = 0.0 + fp["reclamm_interpolation_method"] = "geometric" + params = { + "price_ratio": jnp.array(4.0), + "centeredness_margin": jnp.array(0.2), + "daily_price_shift_base": jnp.array(1.0 - 1.0 / 124000.0), + } + + start_unix = pd.Timestamp(fp["startDateString"]).value // 10**6 + schedule_df = pd.DataFrame( + { + "unix": [start_unix + 2 * 60_000], + "end_unix": [start_unix + 8 * 60_000], + "price_ratio": [7.5], + } + ) + + baseline = do_run_on_historic_data( + fp, + params=params, + root=TEST_DATA_DIR, + verbose=False, + ) + with_schedule = do_run_on_historic_data( + fp, + params=params, + root=TEST_DATA_DIR, + verbose=False, + dynamic_input_frames=DynamicInputFrames( + reclamm_price_ratio_updates=schedule_df + ), + ) + + assert not np.allclose( + np.asarray(baseline["reserves"]), + np.asarray(with_schedule["reserves"]), + ) + + def test_reclamm_schedule_test_period_does_not_leak_into_train(self, defaulted_run_fingerprint): + """Test-only schedule updates should affect test path only.""" + fp = deepcopy(defaulted_run_fingerprint) + fp["rule"] = "reclamm" + fp["do_arb"] = True + fp["fees"] = 0.0 + fp["gas_cost"] = 0.0 + fp["arb_fees"] = 0.0 + params = { + "price_ratio": jnp.array(4.0), + "centeredness_margin": jnp.array(0.2), + "daily_price_shift_base": jnp.array(1.0 - 1.0 / 124000.0), + } + + test_start_unix = pd.Timestamp(fp["endDateString"]).value // 10**6 + baseline_updates = pd.DataFrame( + { + "unix": [test_start_unix + 60_000], + "end_unix": [test_start_unix + 6 * 60_000], + # Keep baseline on the initial ratio so it is a no-op schedule. + "price_ratio": [4.0], + "start_price_ratio": [4.0], + } + ) + test_only_updates = pd.DataFrame( + { + "unix": [test_start_unix + 60_000], + "end_unix": [test_start_unix + 6 * 60_000], + "price_ratio": [8.0], + } + ) + + train_base, test_base = do_run_on_historic_data( + fp, + params=params, + root=TEST_DATA_DIR, + verbose=False, + do_test_period=True, + dynamic_input_frames=DynamicInputFrames( + reclamm_price_ratio_updates=baseline_updates + ), + ) + train_sched, test_sched = do_run_on_historic_data( + fp, + params=params, + root=TEST_DATA_DIR, + verbose=False, + do_test_period=True, + dynamic_input_frames=DynamicInputFrames( + reclamm_price_ratio_updates=test_only_updates + ), + ) + + np.testing.assert_allclose( + np.asarray(train_sched["value"]), + np.asarray(train_base["value"]), + rtol=1e-6, + atol=1e-6, + ) + assert not np.allclose( + np.asarray(test_sched["value"]), + np.asarray(test_base["value"]), + ) + # ============================================================================ # Validation and Early Stopping Tests diff --git a/tests/unit/test_lint_bugs.py b/tests/unit/test_lint_bugs.py index 95f7ee6..bbb49e6 100644 --- a/tests/unit/test_lint_bugs.py +++ b/tests/unit/test_lint_bugs.py @@ -117,16 +117,13 @@ def calculate_reserves_zero_fees(self, params, static_dict, prices, start_index) prices = jnp.ones((20, 2)) start_index = jnp.array([0, 0]) - # __wrapped__ arg order: params, start_index, prices, trades, fees, - # gas_cost, arb_fees, pool, static_dict + # __wrapped__ arg order: params, start_index, prices, dynamic_inputs, + # pool, static_dict result = forward_pass.__wrapped__( {}, # params start_index, # start_index prices, # prices - None, # trades_array - None, # fees_array - None, # gas_cost_array - None, # arb_fees_array + None, # dynamic_inputs _MockPool(), # pool static_dict, # static_dict )