Skip to main content

Overview

ANK includes a built-in optimizer that sweeps strategy parameters (like target_ltv_bps, band_bps, initial_deposit_units) and scores runs based on weighted objectives (PnL, Sharpe, drawdown, etc.).

Quick Start

1

Create sweep.yaml

Define the parameter grid:
base_config: "apps/cli/examples/sim.yaml"
params:
  target_ltv_bps: [6000, 6500, 7000, 7500]
  band_bps: [50, 100, 250]
  initial_deposit_units: [5000, 10000, 20000]
top_k: 10
out_csv: "apps/cli/examples/opt_results.csv"
2

Create objectives.yaml

Define scoring weights:
weights:
  terminal_net_e18:  1.0
  min_hf_bps:        0.2
  max_drawdown_e18: -0.5
  vol_e18:          -0.2
  • Positive weights = maximize (e.g., terminal_net_e18)
  • Negative weights = minimize (e.g., max_drawdown_e18, vol_e18)
3

Run optimizer

cargo run -p ank-cli --bin optimize -- \
  --sweep apps/cli/examples/sweep.yaml \
  --objectives apps/cli/examples/objectives.yaml

sweep.yaml Format

base_config: "apps/cli/examples/sim.yaml"  # Base simulation config

params:
  # Each param is a list of values to try
  target_ltv_bps: [6000, 6500, 7000, 7500, 8000]
  band_bps: [50, 100, 150, 200, 250, 300]
  initial_deposit_units: [5000, 7500, 10000, 15000, 20000]

top_k: 10                                  # Print top-K results to stdout
out_csv: "opt_results.csv"                 # Save all results to CSV

Parameter Grid

The optimizer runs a Cartesian product of all param values:
  • target_ltv_bps: 5 values
  • band_bps: 6 values
  • initial_deposit_units: 5 values
Total runs: 5 × 6 × 5 = 150 simulations
Grid size grows exponentially. For 4 params with 10 values each, you’ll run 10,000 simulations. Use smaller grids or switch to genetic/DE algorithms for large spaces.

objectives.yaml Format

weights:
  # Maximize terminal equity
  terminal_net_e18: 1.0

  # Maximize minimum health factor (safety)
  min_hf_bps: 0.2

  # Minimize max drawdown (negative weight)
  max_drawdown_e18: -0.5

  # Minimize volatility (negative weight)
  vol_e18: -0.2

  # Optional: penalize low Sharpe
  sharpe: 0.3

Scoring Formula

The optimizer computes a weighted sum for each run:
score = Σ (weight_i × metric_i)
Example:
score = 1.0 × terminal_net_e18
      + 0.2 × min_hf_bps
      - 0.5 × max_drawdown_e18
      - 0.2 × vol_e18
Higher scores are better. The optimizer ranks all candidates and prints the top-K.

Available Metrics

From RiskSummary (see Risk Analytics):
MetricTypeDescription
terminal_net_e18u128Final equity (e18)
initial_net_e18u128Starting equity (e18)
total_pnl_e18i128PnL = terminal - initial (e18)
min_hf_bpsu64Minimum health factor (bps)
max_hf_bpsu64Maximum health factor (bps)
max_drawdown_e18u128Worst drawdown (e18)
vol_e18f64Annualized volatility
sharpef64Sharpe ratio (rf=0)
sortinof64Sortino ratio (rf=0)
total_returnf64Total return (%)
aprf64Annualized return (linear)
apyf64Annualized yield (compounded)
win_ratef64Fraction of winning ticks

Output: opt_results.csv

The optimizer writes all runs to CSV:
target_ltv_bps,band_bps,initial_deposit_units,score,terminal_net_e18,min_hf_bps,max_drawdown_e18,vol_e18
7000,250,10000,15234.5,12000000000000000000000,8500,500000000000000000000,0.12
7500,200,15000,14987.3,13500000000000000000000,7800,650000000000000000000,0.15
...
You can load this in Python/R for further analysis:
import pandas as pd

df = pd.read_csv('opt_results.csv')
df_sorted = df.sort_values('score', ascending=False)
print(df_sorted.head(10))

Top-K Results (stdout)

The optimizer also prints the top-K to console:
=== Top 10 Results ===

Rank 1: score=15234.5
  target_ltv_bps: 7000
  band_bps: 250
  initial_deposit_units: 10000
  terminal_net_e18: 12000000000000000000000
  min_hf_bps: 8500
  max_drawdown_e18: 500000000000000000000
  sharpe: 1.85

Rank 2: score=14987.3
  target_ltv_bps: 7500
  band_bps: 200
  initial_deposit_units: 15000
  ...

Advanced: Genetic Algorithm

For large parameter spaces, use Genetic Algorithm (GA) or Differential Evolution (DE):
use ank_opt::{Optimizer, Algo, Budget, Space};

let space = Space::new()
  .real("target_ltv_bps", 6000.0, 8000.0)
  .real("band_bps", 50.0, 300.0)
  .int("initial_deposit_units", 5000, 20000).step(1000);

let mut opt = Optimizer::new(
  Algo::ga(42).pop_size(30).gens(50),
  None  // No scheduler
);

let result = opt.run(&mut objective, &space, Budget::trials(500))?;
println!("Best score: {}", result.best.score);

DE (Differential Evolution)

let mut opt = Optimizer::new(
  Algo::de(42).pop_size(20).f(0.7).cr(0.9),
  None
);
let mut opt = Optimizer::new(
  Algo::random(42),
  None
);

Multi-Objective Optimization

Balancing conflicting goals (e.g., max return vs. min risk):
weights:
  # High return
  terminal_net_e18: 1.0

  # Low risk
  max_drawdown_e18: -0.8
  vol_e18: -0.3

  # Safety constraint
  min_hf_bps: 0.5
Tweak weights to explore the Pareto frontier (e.g., run multiple sweeps with different weight ratios).

Example: Find Best Leverage Band

sweep.yaml

base_config: "sim.yaml"
params:
  target_ltv_bps: [6500, 7000, 7500]
  band_bps: [100, 150, 200, 250, 300]
top_k: 5
out_csv: "band_sweep.csv"

objectives.yaml

weights:
  terminal_net_e18: 1.0
  min_hf_bps: 0.3        # Prefer safer strategies
  max_drawdown_e18: -0.6

Run

cargo run -p ank-cli --bin optimize -- \
  --sweep band_sweep.yaml \
  --objectives objectives.yaml

Interpret Results

Look for:
  • Highest score = best risk-adjusted return
  • min_hf_bps ≥ 10500 = HF always above 1.05 (safe)
  • Low max_drawdown_e18 = stable equity curve

Tips for Effective Optimization

1

Start with coarse grids

Use 3-5 values per param initially. Refine around promising regions.
2

Normalize weights

Large metrics (like terminal_net_e18 ~ 1e22) dominate small ones (like sharpe ~ 2.0). Scale weights accordingly or normalize metrics.
3

Use price shocks

Run sweeps with crash scenarios to find robust params.
4

Cross-validate

Test top-K params on different price paths to avoid overfitting.

Parallelization (Future)

ANK’s optimizer currently runs serially. For 1000+ runs, consider:
  • Splitting the grid into chunks and running multiple processes
  • Using a job scheduler (SLURM, AWS Batch)
  • Implementing a parallel backend (Rayon)

Next Steps

Build docs developers (and LLMs) love