Per-Asset Weight Bounds
In addition to the uniform minimum_weight guardrail that applies equally to all assets,
quantammsim supports per-asset minimum and maximum weight constraints. This allows you to
specify different allocation limits for each asset in the pool.
Use Cases
Per-asset bounds are useful when you want to:
Ensure a stablecoin maintains a minimum allocation (e.g., USDC always >= 10%)
Cap exposure to volatile assets (e.g., meme coins <= 30%)
Implement risk management constraints specific to each asset
Model regulatory or mandate-driven allocation limits
Usage
To use per-asset bounds, create a pool with the bounded__ prefix:
from quantammsim.pools.creator import create_pool
# Create a bounded momentum pool
pool = create_pool("bounded__momentum")
# Also works with other strategies
pool = create_pool("bounded__mean_reversion_channel")
Then specify the bounds in your parameters:
params = {
# ... other strategy parameters ...
"min_weights_per_asset": jnp.array([0.25, 0.20, 0.10]), # BTC, ETH, USDC
"max_weights_per_asset": jnp.array([0.60, 0.55, 0.30]),
}
Algorithm
The per-asset bounds use a clip-and-redistribute algorithm:
Step 1: Initial Clip
Step 2: Calculate Slack
Step 3: Redistribute Proportionally
If \(\sum w'_i < 1\) (deficit):
If \(\sum w'_i > 1\) (surplus):
Step 4: Final Normalisation
Restrictions on Bounds
For a feasible solution to exist, the bounds must satisfy these constraints:
Constraint |
Formula |
Meaning |
|---|---|---|
Sum of minimums |
\(\sum \min_i \leq 1\) |
Must be possible to satisfy all minimums |
Sum of maximums |
\(\sum \max_i \geq 1\) |
Must be possible to reach total weight of 1 |
Per-asset ordering |
\(\min_i < \max_i \; \forall i\) |
Each asset must have a valid range |
Non-negative |
\(\min_i \geq 0 \; \forall i\) |
Weights cannot be negative |
Upper bound |
\(\max_i \leq 1 \; \forall i\) |
No single asset can exceed 100% |
Example: Valid vs Invalid Bounds
For a 3-asset pool:
Valid:
min = [0.2, 0.2, 0.2],max = [0.5, 0.5, 0.5]\(\sum \min = 0.6 \leq 1\) ✓
\(\sum \max = 1.5 \geq 1\) ✓
Invalid:
min = [0.4, 0.4, 0.4]\(\sum \min = 1.2 > 1\) ✗ (impossible to satisfy all minimums)
Invalid:
max = [0.3, 0.3, 0.3]\(\sum \max = 0.9 < 1\) ✗ (impossible to reach total weight of 1)
Key Properties
Guaranteed feasibility: If the bounds satisfy the constraints above, a valid weight vector always exists.
Proportional redistribution: Slack is redistributed proportionally, so assets with more room to adjust absorb more of the deficit/surplus.
Preserves relative ordering: Assets closer to their bounds move less than those with more slack.
Layered on existing guardrails: Per-asset bounds are applied BEFORE the uniform
minimum_weightguardrail, so both constraints must be satisfied.
Integration with Run Fingerprints
When using per-asset bounds in a simulation, specify the rule with the bounded__ prefix:
run_fingerprint = {
"tokens": ["BTC", "ETH", "USDC"],
"rule": "bounded__momentum",
"minimum_weight": 0.05, # Uniform guardrail (still applies)
# ... other settings ...
}
params = {
"min_weights_per_asset": jnp.array([0.25, 0.20, 0.10]),
"max_weights_per_asset": jnp.array([0.60, 0.55, 0.30]),
# ... other strategy parameters ...
}
result = do_run_on_historic_data(run_fingerprint, params)