Skip to content

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()

sim_conditions(conditions, hold_until=None, *args, **kwargs) -> ReportCollection

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

class ReportCollection(reports: dict[str, Report])

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()

ReportCollection.get_stats() -> pd.DataFrame

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()

ReportCollection.plot_creturns() -> plotly.graph_objects.Figure

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:

  1. First backtest individual conditions, remove those with poor performance
  2. Test in batches (separate technical and fundamental)
  3. Use longer resample periods (e.g., 'Q')

Resources