Parameter Optimization¶
Parameter optimization is a crucial part of quantitative strategy development. AKQuant provides powerful optimization tools to help you explore how parameters affect strategy performance and evaluate strategy robustness.
Currently, two main optimization modes are supported:
- Grid Search:
run_grid_search - Walk-Forward Optimization:
run_walk_forward
1. Grid Search¶
Grid Search is an exhaustive parameter optimization method. It iterates through given parameter combinations and runs backtests on the same historical data to find the best-performing parameter set.
Use Cases¶
- Exploring strategy sensitivity to parameters.
- Determining reasonable parameter ranges.
- Finding the "theoretical ceiling" of a strategy on historical data.
Usage¶
Use the akquant.run_grid_search function:
from akquant import run_grid_search, Strategy
# 1. Define Strategy
class MyStrategy(Strategy):
def __init__(self, ma_period, stop_loss):
# ...
# 2. Define Parameter Grid
param_grid = {
"ma_period": [10, 20, 30],
"stop_loss": [0.01, 0.02, 0.05]
}
# 3. Run Grid Search
results = run_grid_search(
strategy=MyStrategy,
param_grid=param_grid,
data=df,
sort_by="sharpe_ratio", # Sort by Sharpe Ratio
ascending=False # Descending order
)
print(results.head())
Multi-Objective Optimization¶
In real trading, a single metric is often insufficient (e.g., Sharpe Ratio alone might select a survivor that traded only once). AKQuant supports sorting based on multiple metrics:
results = run_grid_search(
strategy=MyStrategy,
param_grid=param_grid,
data=df,
sort_by=["sharpe_ratio", "total_return"], # Primary: Sharpe, Secondary: Total Return
ascending=[False, False] # Both descending
)
Result Filtering¶
To avoid "survivorship bias" or filter out parameter combinations that do not meet risk control requirements (e.g., too few trades, excessive drawdown), you can use the result_filter callback:
def result_filter(metrics):
# Filter conditions:
# 1. Trade count >= 50
# 2. Sharpe Ratio > 1.0
# 3. Max Drawdown < 20%
return (
metrics.get("trade_count", 0) >= 50 and
metrics.get("sharpe_ratio", 0) > 1.0 and
metrics.get("max_drawdown_pct", 1.0) < 0.2
)
results = run_grid_search(
...,
result_filter=result_filter
)
Core Parameters¶
strategy: Strategy class.param_grid: Parameter dictionary where keys are parameter names and values are lists of parameter values.data: Backtest data.sort_by: Metric(s) to sort results. Supports single string or list of strings.ascending: Sort direction. Supports boolean or list of booleans.result_filter: (Optional) Callback function to filter results based on metrics.warmup_calc: (Optional) Callback function for dynamic warmup period calculation.constraint: (Optional) Callback function for parameter constraints to filter invalid combinations.
Resource Control & Error Handling¶
When dealing with massive parameter combinations or potential infinite loops/OOM issues, use the following parameters:
results = run_grid_search(
...,
timeout=60.0, # Max 60s per task, skip if timeout
max_tasks_per_child=1 # Force restart worker after each task
)
timeout: Timeout for a single backtest task (seconds). If a task exceeds this time, it will be marked as failed and skipped. Useful for preventing infinite loops.max_tasks_per_child: Worker process restart frequency. Setting to1forces a new process for each task, effectively preventing memory leaks (OOM) and cleaning up timeout threads.
Persistence & Resume¶
For scenarios with extremely large parameter sets (e.g., > 10,000 combinations), running on a single machine might take days. AKQuant supports real-time result persistence to SQLite, enabling breakpoint resume.
db_path: Path to SQLite database file.- Real-time Saving: Results (params, metrics, duration, error) are written to DB immediately after each task completes.
- Resume: Upon restart, the program checks the DB. If a parameter combination has already been run (based on JSON matching), it skips it and only runs the remaining tasks.
- Reuse: You can query the DB directly for analysis without re-running backtests.
⚠️ Risk Warning¶
Grid Search is prone to Overfitting. The "optimal parameters" selected might just happen to fit the noise of the historical data and often fail in future live trading. Do not use the optimal results from Grid Search directly for live trading.
2. Walk-Forward Optimization¶
Walk-Forward Optimization (WFO) is a validation method closer to real-world scenarios. It simulates the passage of time by slicing data into multiple [Train | Test] windows, continuously rolling "In-Sample Optimization" and "Out-of-Sample Validation".
Use Cases¶
- Validating real strategy robustness.
- Evaluating strategy performance on "unknown" data.
- Generating parameter paths that adjust dynamically over time.
Principle¶
The core idea of WFO is: Always use past data to determine current parameters.
- Window 1:
- Train (In-Sample): Use Jan-Mar data for Grid Search to find optimal parameter A.
- Test (Out-of-Sample): Use parameter A to backtest on Apr data.
- Window 2:
- Train: Roll to Feb-Apr data, re-run Grid Search to find optimal parameter B.
- Test: Use parameter B to backtest on May data.
- Concatenation: Concatenate results from all test segments to form the final equity curve.
Usage¶
Use the akquant.run_walk_forward function:
from akquant import run_walk_forward
# Run WFO
wfo_results = run_walk_forward(
strategy=MyStrategy,
param_grid=param_grid,
data=df,
train_period=250, # Train window length (e.g., 250 bars)
test_period=60, # Test/Rolling step (e.g., 60 bars)
metric="sharpe_ratio", # Optimization target
initial_cash=100_000.0,
warmup_calc=warmup_calc, # Support dynamic warmup
constraint=param_constraint # Support parameter constraints
)
# wfo_results contains the concatenated equity curve and parameters used for each segment
print(wfo_results)
Core Parameters¶
train_period: Training window length (number of Bars). Longer windows mean more stable parameters; shorter windows adapt faster to changes.test_period: Test window length (number of Bars). Usually also the rolling step size.metric: The metric used to select optimal parameters on the training set (e.g.,sharpe_ratio,total_return).
3. Advanced Features¶
Dynamic Warmup¶
Different parameter combinations may require different warmup periods (e.g., long-term moving average strategies need more history). You can dynamically specify this via the warmup_calc callback:
def warmup_calc(params):
# Warmup period = Long window + 1
return params["long_window"] + 1
run_grid_search(..., warmup_calc=warmup_calc)
Parameter Constraint¶
Some parameter combinations are logically invalid (e.g., short window > long window). You can filter these out in advance using the constraint callback to save computational resources:
def param_constraint(params):
# Only keep combinations where short window < long window
return params["short_window"] < params["long_window"]
run_grid_search(..., constraint=param_constraint)
4. Grid Search vs Walk-Forward Comparison¶
| Feature | Grid Search | Walk-Forward Optimization |
|---|---|---|
| Data Usage | Uses all data for one-time optimization | Rolling data slices, strict train/test separation |
| Parameter Result | 1 Set Global static parameters | Multiple Sets Dynamic parameters |
| Overfitting Risk | Very High (Looking at the answer to find the solution) | Low (Simulating real unknown environment) |
| Core Purpose | Explore parameter sensitivity, find "theoretical ceiling" | Validate strategy robustness, evaluate "real-world expectation" |
| Code Mapping | akquant.run_grid_search |
akquant.run_walk_forward |