finlab.optimize
Strategy parameter optimization and combination testing module.
Use Cases
- Test all combinations of multiple conditions: When you have multiple stock selection conditions (technical, fundamental, size), find the best intersection
- Quickly find the best strategy combination: Automate testing of all possible combinations, identifying the best performer from dozens of strategies
- Visually compare performance: Use heatmaps, bar charts, and line charts to quickly compare combinations
Quick Examples
from finlab import data
from finlab.optimize.combinations import sim_conditions
data.set_market('us')
with data.us_universe(index='SP500'):
close = data.get('price:adj_close')
revenue = data.get('us_income_statement:revenue')
operating_income = data.get('us_income_statement:operating_income')
# Define 3 conditions
c1 = (close > close.average(20)) & (close > close.average(60))
c2 = operating_income.pct_change(4) > 0 # Operating income up year over year
c3 = revenue.pct_change(4) > 0.1 # Revenue up more than 10% year over year
exits = close < close.average(20)
conditions = {'c1': c1, 'c2': c2, 'c3': c3}
# Test all combinations (7 total: c1, c2, c3, c1&c2, c1&c3, c2&c3, c1&c2&c3)
report_collection = sim_conditions(
conditions=conditions,
hold_until={'exit': exits, 'stop_loss': 0.1},
resample='M',
position_limit=0.1,
upload=False,
)
# Visual comparison
report_collection.plot_stats('heatmap') # Heatmap
report_collection.plot_stats('bar').show() # Bar chart
report_collection.plot_creturns().show() # Cumulative return line chart
Detailed Guide
See Strategy Parameter Optimization Guide for:
- Why strategy optimization is needed
- How to interpret 12 performance metrics
- How to avoid overfitting
- Optimization techniques with stop-loss/take-profit
- Practical workflow recommendations
API Reference
sim_conditions()
Backtests every combination of the given stock-selection conditions, so you can find the intersection that performs best. With n conditions it runs 2^n - 1 backtests: each condition alone, then every & intersection. Quarterly conditions are converted to filing dates before they are combined. A combination whose backtest fails is logged and skipped.
| Parameter | Type | Description |
|---|---|---|
conditions |
dict |
Condition name to boolean DataFrame, e.g. {'c1': c1, 'c2': c2}. Names appear as the combination labels ('c1 & c2'). |
hold_until |
dict or None |
Keyword arguments for FinlabDataFrame.hold_until() applied to every combination, e.g. {'exit': exits, 'stop_loss': 0.1}. Default None backtests the combined condition directly. |
*args, **kwargs |
Passed to finlab.backtest.sim(), e.g. resample='M', position_limit=0.1, upload=False. |
Returns: ReportCollection holding one backtest report per combination.
Advanced Usage
- Filter specific metrics:
report_collection.plot_stats('bar', indicators=['daily_sharpe', 'win_ratio']) - Custom sorting:
report_collection.plot_stats('heatmap', heatmap_sort_by='daily_sharpe') - Access specific combination:
report = report_collection.reports['c1 & c3']
ReportCollection
Compares backtest reports side by side to find the strongest strategy in a set, or to see how a strategy changes as more conditions are stacked on it.
| Parameter | Type | Description |
|---|---|---|
reports |
dict |
Name to backtest report, e.g. {'strategy1': sim(...), 'strategy2': sim(...)}. |
Attributes: reports (the dict passed in) and stats (the table from the last get_stats() call, None before).
get_stats()
Returns the metric comparison table: one column per strategy, one row per metric. The result is also stored in stats.
| Metric | Description |
|---|---|
daily_mean |
Annualized strategy return |
daily_sharpe |
Annualized Sharpe ratio |
daily_sortino |
Annualized Sortino ratio |
max_drawdown |
Maximum drawdown of the strategy (negative) |
avg_drawdown |
Average drawdown (negative) |
ytd |
Strategy return so far this year |
win_ratio |
Share of winning trades |
avg_return |
Average return per trade |
avg_mae |
Average maximum adverse excursion per trade (negative) |
avg_bmfe |
Average maximum favorable excursion reached before the maximum adverse excursion; higher values leave more room to take profit before a stop-loss |
avg_gmfe |
Average maximum favorable excursion per trade |
avg_mdd |
Average maximum drawdown per trade (negative) |
plot_stats()
ReportCollection.plot_stats(mode: str = 'bar', heatmap_sort_by: str = 'avg_score', indicators: list[str] | None = None) -> go.Figure | pd.DataFrame | None
Visualizes the metric comparison table.
| Parameter | Type | Description |
|---|---|---|
mode |
str |
'bar': grouped bar chart per metric. 'heatmap': percentile rank of every metric across strategies. |
heatmap_sort_by |
str or list[str] |
Column(s) the heatmap is sorted by, descending. 'avg_score' (default) is the average percentile across all metrics; higher means a stronger overall profile. |
indicators |
list[str] or None |
Show only these metrics; default shows all. |
Returns: plotly.graph_objects.Figure for 'bar', a styled pd.DataFrame for 'heatmap', None for any other mode.
plot_creturns()
Line chart of every strategy's cumulative return, ordered by final value, to compare equity curves.
Performance Tips
When the number of conditions exceeds 6, there will be over 63 combinations, leading to long computation times. Recommendations:
- First backtest individual conditions, remove those with poor performance
- Test in batches (separate technical and fundamental)
- Use longer resample periods (e.g., 'Q')