Training Pools
Why Train Pools?
QuantAMM pools implement trading strategies with configurable parameters that determine how they respond to market conditions. While you can set these parameters manually, finding good values often requires systematic optimization.
Training helps you:
Maximize desired metrics (Sharpe ratio, returns, etc.)
Adapt strategies to specific market conditions
Balance between responsiveness and stability
Validate strategy effectiveness
Basic Training Setup
To train a pool, you need to define:
A run fingerprint (configuration) including initial parameters
Training data specifications
Choosing an Optimization Approach
We provide two main approaches to parameter optimization:
Gradient Descent leverages directly JAX’s ability to compute gradients. This is ideal when:
You have a good starting point and want to fine-tune
The relationship between parameters and performance is smooth
You have GPU acceleration
You are training in the zero-fees regime (as then there are extremely fast GPU-accelerated algorithms)
Gradient-Free Search works better when:
You’re exploring a wide parameter space
The performance landscape might be non-smooth (the pool strategy does not have to be differentiable with respect to the strategy parameters)
You are training in the presence of fees (as the GPU-accelerated algorithms are not as fast, as fees make modelling AMMs intrinsically sequential)
Parameter Spaces
QuantAMM strategies often use transformed parameters for better optimization. For example:
Memory length (\(\text{days}\)) → logit(λ)
Ensures λ stays between 0 and 1 by construction
Which thus allows unconstrained optimization
\(\lambda = \text{sigmoid}(\text{logit_lambda})\)
Strategy aggressiveness (k) → log2(k)
Handles wide range of scales
Maintains positivity
\(k = 2^{\text{\log2(k)}}\)
See Constrained vs unconstrained parameters for more examples.
Getting Started
Let’s optimize a QuantAMMmomentum strategy for a BTC/ETH pool:
from quantammsim.runners.jax_runners import train_on_historic_data
# Basic configuration
run_fingerprint = {
"tokens": ["BTC", "ETH"],
"rule": "momentum",
"startDateString": "2024-01-01 00:00:00",
"endDateString": "2024-03-01 00:00:00",
"endTestDateString": "2024-04-01 00:00:00",
"chunk_period": 60,
"return_val": "daily_log_sharpe",
"initial_pool_value": 1000000.0
}
Data Handling
The simulator supports flexible data configuration:
Training/Test Split:
Specify date ranges for training and testing
Automatic data windowing
Data Processing:
Minute-level granularity
Automatic resampling
Missing data handling
Price normalization
Training Windows:
Fixed or random windows
Overlapping periods
Custom bout lengths
Gradient Descent Training
For gradient-based optimization:
Set optimization parameters:
run_fingerprint["optimisation_settings"].update({
"method": "gradient_descent",
"optimiser": "adam", # Optimizer type
"base_lr": 0.01, # Learning rate
"batch_size": 16, # Training batch size
"n_parameter_sets": 4, # Number of parameter sets to train in parallel
"n_iterations": 10000, # Total iterations
"decay_lr_plateau": 200, # Iterations of no improvement before decay
"decay_lr_ratio": 0.8, # Learning rate decay on plateau
})
Set off and monitor training:
result = train_on_historic_data(
run_fingerprint,
iterations_per_print=100, # Progress update frequency
verbose=True # Detailed logging
)
Gradient-Free Optimization
Configure Optuna settings:
Optuna provides sophisticated optimization without the need for gradients:
run_fingerprint["optimisation_settings"]["optuna_settings"].update({
"method": "optuna",
"n_trials": 100, # Total optimization trials
"n_jobs": 4, # Parallel workers
"timeout": 7200, # Max runtime in seconds
"n_startup_trials": 10, # Random trials before optimization
"early_stopping": {
"enabled": True,
"patience": 100, # Trials without improvement
"min_improvement": 0.001 # Minimum relative improvement
},
"parameter_config": {
"memory_length": {
"low": 1,
"high": 200,
"log_scale": True, # Search on log scale
"scalar": False # Different values per asset
}
}
})
The parameter_config supports extensive customization per parameter, including:
Search range bounds
Linear vs logarithmic scaling
Per-asset vs global parameters (see below)
If no parameter_config is provided for a parameter, the simulator will use the default parameter_config.
If a strategy is not differentiable with respect to a parameter, you will have to use this gradient-free optimization approach.
Set off and monitor training:
result = train_on_historic_data(
run_fingerprint,
iterations_per_print=100, # Progress update frequency
verbose=True # Detailed logging
)
Parameter & Run Configuration
Pools will automatically initialise as JAX arrays all the parameters that they are set to train.
If you wish to set particular initial values for them (for gradient descent training), or the range/sampling method for them (for gradient-free training), you can do so via the run_fingerprint dictionary.
The simulator provides extensive parameter configuration options through the run_fingerprint dictionary. Default values are provided but can be overridden:
"bout_offset": 24 * 60 * 7, # Training window offset (in minutes)
"maximum_change": 3e-4, # Max weight change per update
"chunk_period": 1440, # Strategy update frequency in minutes (1 day)
"weight_interpolation_period": 1440 # Weight update frequency (1 day)
})
Advanced parameters include:
use_alt_lamb: Alternative lambda parameterization so different parts of estimators can have different memory lengths (not supported in QuantAMM V1)use_pre_exp_scaling: Pre-exponential scalingweight_interpolation_method: “linear” or “optimal” (only linear is supported in QuantAMM V1)arb_frequency: Arbitrage check frequency (in minutes)arb_quality: Arbitrage execution efficiency (0-1) (only used for CoW AMM pools)
Bout offset is the number of minutes to offset the training window by. Given how much it can affect the results, it is worth understanding how it works.
Understanding Bout Offset
Bout offset is a crucial parameter that controls how training windows are constructed relative to price data. It specifies an offset in minutes from the duration of the specified training period, in effect shortening the training period by the specified amount.
So if a training run is 4 months long, and the bout offset is 1 month, each window of price data actually used during training will be 3 months long. Those windows can start as early as the first moment of the training period and end as the last moment of the training period.
Configuration
run_fingerprint.update({ "bout_offset": 24 * 60 * 7, # One week in minutes })
Common Configurations
Daily offset: 24 * 60 minutes
Weekly offset: 24 * 60 * 7 minutes (default)
Custom periods: Any duration
Training Considerations
Larger offsets reduce available training data
They make the strategy care more about achieving the return metric over the effective (bout-offset reduced) training period
Best Practices:
If you want your pool to be more sensitive to partial market conditions, use a longer offset, reducing the length of the windows of data used for training.
If you want your pool to optimise more strongly for performance over the exact start date to end date of the training period, use a shorter offset, increasing the length of the windows of data used for training.
Advanced Features
Custom Return Metrics:
run_fingerprint["return_val"] = "daily_log_sharpe" # or "sharpe", "returns", "sortino" among others
Multi-period Training (Gradient-free only):
run_fingerprint.update({
"optimisation_settings": {
"method": "optuna",
"optuna_settings": {
"multi_objective": True,
}
}
})
This sets the optimisation to be multi-objective, and will optimise the chosen return metric for a range of different periods within the training data, a mixture of sequenital, periodically placed windows and randomly placed windows (to avoid periodic biases). This is useful for finding a good set of parameters that perform well across a range of market conditions. The mean and standard deviation of the return metric across the different periods is returned in the result, along with the worst value.
Note
This is only supported in the gradient-free optimisation approac, but gradient-based optimisation naturally has this property as each window of data used for training is a) reduced in length by the bout offset and b) randomly placed.
Constrain a parameter to be “universal” (i.e. the same value for all assets) (Gradient-free only):
run_fingerprint["optimisation_settings"]["optuna_settings"]["parameter_config"]["k_per_day"]["scalar"] = True
This is set on a per-parameter basis, and will force the parameter to be the same for all assets. Constraining parameters in this way can help with generalisation.
Set initial parameter values (Gradient-descent only):
run_fingerprint.update({
"initial_memory_length": 10.0, # Starting memory length in days
"initial_k_per_day": 20, # Starting strategy aggressiveness
})
Performance Considerations
Use GPU acceleration when available
Batch size, n_parameter_sets (for gradient descent) and n_jobs (for gradient-free) affect memory usage
Monitor for overfitting using test period and other post-run analysis
See Also
QuantAMM: Pools as Portfolios — Strategy details and parameters
Walk-Forward Analysis — Walk-forward validation for assessing robustness
Hyperparameter Tuning — Automated tuning of training hyperparameters using walk-forward evaluation
Ensemble Training — Ensemble training for implicit regularisation
Robustness Features — Regularisation techniques
Metrics Reference — Available training and evaluation metrics
TFMM litepaper — Theoretical background