Docs
Open Studio

API reference

finlab.optimize

On this page5

finlab.optimize takes the stock-selection conditions you wrote, backtests every combination of them, and lines the results up side by side. Use it when you have several technical, fundamental or size conditions and want to know which ones work best together.

Which function to use

You want toUse
Backtest every combination of several conditionssim_conditions()
Compare backtests you already ranReportCollection
Get the metric table for every combinationreport_collection.get_stats()
Compare metrics as a bar chart or heatmapreport_collection.plot_stats()
Compare every combination's equity curvereport_collection.plot_creturns()

Test every combination of conditions

sim_conditions()

Backtests every combination of the given conditions, so you can find the intersection that performs best.

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
ParameterTypeDefaultDescription
conditionsdictrequiredCondition name to boolean DataFrame, e.g. {'c1': c1, 'c2': c2}. Names appear as the combination labels ('c1 & c2').
hold_untildict or NoneNoneKeyword arguments for FinlabDataFrame.hold_until() applied to every combination, e.g. {'exit': exits, 'stop_loss': 0.1}. None backtests the combined condition directly.
*args, **kwargsPassed to finlab.backtest.sim(), e.g. resample='M', position_limit=0.1, upload=False.

Returns a ReportCollection holding one backtest report per combination.

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.

Compare backtest reports

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. sim_conditions() returns one; you can also build one from reports you already have.

from finlab import data, backtest
from finlab.optimize.combinations import ReportCollection

data.set_market('us')
close = data.get('price:adj_close')
position = close >= close.rolling(300).max()

collection = ReportCollection({
    'monthly': backtest.sim(position, resample='M', upload=False),
    'weekly': backtest.sim(position, resample='W', upload=False),
})
collection.get_stats()
ParameterTypeDefaultDescription
reportsdict[str, Report]requiredName 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).

report_collection.get_stats()

Returns the metric comparison table, one column per strategy and one row per metric, and also stores it in stats.

stats = report_collection.get_stats()
stats.loc[['cagr', 'daily_sharpe', 'max_drawdown']]
MetricDescription
cagrAnnualized strategy return (compound, CAGR)
daily_sharpeAnnualized Sharpe ratio
daily_sortinoAnnualized Sortino ratio
max_drawdownMaximum drawdown of the strategy (negative)
avg_drawdownAverage drawdown (negative)
ytdStrategy return so far this year
win_ratioShare of winning trades
avg_returnAverage return per trade
avg_maeAverage maximum adverse excursion per trade (negative)
avg_bmfeAverage maximum favorable excursion reached before the maximum adverse excursion; higher values leave more room to take profit before a stop-loss
avg_gmfeAverage maximum favorable excursion per trade
avg_mddAverage maximum drawdown per trade (negative)

report_collection.plot_stats()

Visualizes the metric comparison table as a grouped bar chart or a heatmap.

report_collection.plot_stats('bar', indicators=['cagr', 'daily_sharpe']).show()
report_collection.plot_stats('heatmap', heatmap_sort_by='daily_sharpe')
ParameterTypeDefaultDescription
modestr'bar''bar': grouped bar chart per metric. 'heatmap': percentile rank of every metric across strategies.
heatmap_sort_bystr or list[str]'avg_score'Column(s) the heatmap is sorted by, descending. 'avg_score' is the average percentile across all metrics; higher means a stronger overall profile.
indicatorslist[str] or NoneNoneShow only these metrics; None shows all.

Returns a plotly.graph_objects.Figure for 'bar', a styled pd.DataFrame for 'heatmap', and None for any other mode.

report_collection.plot_creturns()

Draws every strategy's cumulative return as a line chart, ordered by final value, to compare equity curves.

report_collection.plot_creturns().show()

Returns a plotly.graph_objects.Figure.

Common questions

What is avg_score on the heatmap?

plot_stats('heatmap') turns each metric into a percentile rank across the strategies. avg_score is the average of those ranks, so a higher score means a stronger overall profile. The heatmap is sorted by avg_score by default; pass heatmap_sort_by to sort by another column or several.

Why doesn't the bar chart appear?

plot_stats('bar') and plot_creturns() return a plotly Figure; call .show() to draw it. The heatmap is a styled DataFrame and displays on its own in a notebook.

Next steps

Source code is on GitHub.

↑↓ Move Enter Open Esc Close