API reference
finlab.report
On this page9
backtest.sim() returns a Report object holding the whole backtest: the equity curve, the performance metrics and every trade. This page lists every Report method: viewing the report, getting the numbers, inspecting trades, deeper analysis, saving and uploading.
from finlab import data
from finlab.backtest import sim
# Build strategy and backtest
data.set_market('us')
close = data.get('price:adj_close')[['AAPL', 'MSFT', 'NVDA', 'AMZN', 'GOOGL']]
position = close > close.average(20)
# Generate backtest report
report = sim(position, resample='M')
# Display report (includes visualizations)
report.display()Which function to use
| You want to | Use |
|---|---|
| See the full interactive report | report.display() |
| Get annual return, Sharpe ratio and other figures (dict) | report.get_stats() |
| Get grouped performance metrics (nested dict) | report.get_metrics() |
| See every trade (DataFrame) | report.get_trades() |
| See how far each trade went against you and for you (MAE/MFE) | report.get_mae_mfe(), report.display_mae_mfe_analysis() |
| See recent holdings and rebalancing info | report.position_info() |
| Upload to FinLab cloud for live tracking | report.upload() |
| Run liquidity, stock-selection or holdings analysis | report.run_analysis() |
| Read the strategy check-up in plain words | report.to_checkup_text(), report.get_checkup() |
| Get a text summary for notifications or logs | report.to_text() |
| See the equity curve in a terminal | report.to_terminal() |
| Save an HTML file to open in a browser | report.to_html() |
| Save the report and load it later | report.to_pickle(), Report.from_pickle() |
| Change the benchmark | A custom Market's get_benchmark() |
View the report
report.display()
Displays the full backtest report, including charts and performance metrics.
report.display()
# Save the interactive report as an HTML file
report.to_html('report.html')The report shows:
- Equity curve (strategy vs benchmark)
- Drawdown curve
- Performance metrics table (annualized return, Sharpe ratio, max drawdown, etc.)
The report is an interactive dashboard rendered in the notebook, not a plotly figure. Outside IPython (a plain script), display() prints a text summary; use report.to_html('report.html') and open the file in a browser.
Here is a sample strategy's report; you can explore it right here:
report.to_html()
Saves the interactive report as a standalone HTML file, with an optional browser tab title.
report.to_html('report.html', title='My Momentum Strategy')| Parameter | Type | Default | Description |
|---|---|---|---|
path | str | required | Output HTML file path |
title | str or None | None | Browser tab title; FinLab Report when not given |
report.to_terminal()
Draws the cumulative return and drawdown as an ASCII chart in the terminal.
report.to_terminal()
# Custom height and width
report.to_terminal(height=8, width=60)
# Hide benchmark and drawdown
report.to_terminal(show_benchmark=False, show_drawdown=False)| Parameter | Type | Default | Description |
|---|---|---|---|
height | int | 12 | Chart height |
width | int | 80 | Chart width |
show_benchmark | bool | True | Whether to draw the benchmark |
show_drawdown | bool | True | Whether to show the drawdown |
Requires asciichartpy: pip install 'finlab[terminal]'.
Get performance numbers
report.get_stats()
Returns the performance statistics as a dict.
stats = report.get_stats()
print(f"Backtest period: {stats['start']} ~ {stats['end']}")
print(f"Annualized return: {stats['cagr'] * 100:.2f}%")
print(f"Sharpe ratio: {stats['daily_sharpe']:.2f}")
print(f"Sortino ratio: {stats['daily_sortino']:.2f}")
print(f"Max drawdown: {stats['max_drawdown'] * 100:.2f}%")
print(f"Avg drawdown: {stats['avg_drawdown'] * 100:.2f}%")
print(f"Win rate: {stats['win_ratio'] * 100:.2f}%")| Parameter | Type | Default | Description |
|---|---|---|---|
riskfree_rate | float | 0.02 | Risk-free rate |
resample is deprecated: it never had an effect and now emits a DeprecationWarning. Stats are always computed on daily returns (the daily_*, monthly_* and yearly_* keys cover each frequency).
Key return fields:
| Field | Description |
|---|---|
start | Backtest start date (str) |
end | Backtest end date (str) |
cagr | Annualized return (CAGR) |
daily_mean | Mean daily return × 252, not the annualized return |
daily_sharpe | Sharpe ratio |
daily_sortino | Sortino ratio |
max_drawdown | Maximum drawdown |
avg_drawdown | Average drawdown |
win_ratio | Win rate |
return_table | Monthly/yearly return table (dict) |
drawdown_details | Max drawdown details |
The same numbers are also available under the get_metrics() names, so either naming works:
get_stats() key | Alias (get_metrics() name) |
|---|---|
cagr | annualReturn |
daily_sharpe | sharpeRatio |
daily_sortino | sortinoRatio |
calmar | calmarRatio |
daily_vol | volatility |
max_drawdown | maxDrawdown |
avg_drawdown | avgDrawdown |
avg_drawdown_days | avgDrawdownDays |
twelve_month_win_perc | m12WinRate |
win_ratio has no alias: it counts closed trades only, while get_metrics()'s winRate also counts open trades.
report.get_metrics()
Returns grouped performance metrics as a nested dict, suitable for cloud upload or processing in code.
metrics = report.get_metrics()
# Profitability metrics
print(f"Annual return: {metrics['profitability']['annualReturn']:.2%}")
print(f"Alpha: {metrics['profitability']['alpha']:.4f}")
print(f"Beta: {metrics['profitability']['beta']:.4f}")
# Risk metrics
print(f"Max drawdown: {metrics['risk']['maxDrawdown']:.2%}")
print(f"VaR: {metrics['risk']['valueAtRisk']:.2%}")
# Ratio metrics
print(f"Sharpe ratio: {metrics['ratio']['sharpeRatio']:.2f}")
print(f"Sortino ratio: {metrics['ratio']['sortinoRatio']:.2f}")
# Win rate metrics
print(f"Win rate: {metrics['winrate']['winRate']:.2%}")| Parameter | Type | Default | Description |
|---|---|---|---|
stats_ | dict or None | None | Statistics of the backtest result; computed when None |
riskfree_rate | float | 0.02 | Risk-free rate |
Categories and fields returned:
| Category | Fields | Description |
|---|---|---|
backtest | startDate, endDate, feeRatio, taxRatio, tradeAt, market, freq | Backtest settings |
profitability | annualReturn, alpha, beta, avgNStock, maxNStock | Profitability metrics |
risk | maxDrawdown, avgDrawdown, avgDrawdownDays, valueAtRisk, cvalueAtRisk | Risk metrics |
ratio | sharpeRatio, sortinoRatio, calmarRatio, volatility, profitFactor, tailRatio | Ratio metrics |
winrate | winRate, m12WinRate, expectancy, mae, mfe | Win rate metrics |
liquidity | capacity, disposalStockRatio, warningStockRatio, fullDeliveryStockRatio | Liquidity metrics (Taiwan markets only) |
report.to_checkup_text() / report.get_checkup()
Lists the strategy check-up in plain words, with an explanation and a pass/fail verdict for each metric, without opening a browser.
print(report.to_checkup_text(lang="en"))
checkup = report.get_checkup(lang="en") # JSON-safe dictionary| Parameter | Type | Default | Description |
|---|---|---|---|
lang | str | 'zh-tw' | 'zh-tw' or 'en' |
The HTML and Python check-ups share their rules and explanations. Each entry in categories includes question, summary, health_label, passed, total and checks. Checks include key, name, explanation, value, formatted, rule, target, passed and status.
- Unavailable values are
Nonewithstatus="insufficient_data"and do not pass. - Absent metrics are omitted, empty trades have an explicit note, and liquidity appears only for supported Taiwan markets.
get_metrics()keeps its existing output.
Inspect trades
report.get_trades()
Returns every trade as a pd.DataFrame, one row per trade.
trades = report.get_trades()
# Analyze trade performance
print(f"Total trades: {len(trades)}")
print(f"Average return: {trades['return'].mean():.2%}")
print(f"Win rate: {(trades['return'] > 0).mean():.2%}")
# Filter big wins or big losses
big_wins = trades[trades['return'] > 0.2]
big_losses = trades[trades['return'] < -0.1]
# Statistics by stock
trades_by_stock = trades.groupby('stock_id')['return'].agg(['count', 'mean'])
print(trades_by_stock.sort_values('mean', ascending=False).head())| Column | Description |
|---|---|
stock_id | Asset code only (e.g. AAPL) |
name | Asset name (e.g. Apple Inc.; empty string when unknown) |
symbol | Code and name (e.g. AAPL Apple Inc.), kept for compatibility |
entry_sig_date | Entry signal date |
exit_sig_date | Exit signal date |
entry_date | Entry date |
exit_date | Exit date |
position | Holding weight |
period | Holding days |
return | Return rate |
trade_price@entry_date | Entry price |
trade_price@exit_date | Exit price |
mae | Maximum adverse excursion during holding |
gmfe | Maximum favorable excursion during holding |
bmfe | Maximum favorable excursion before MAE |
mdd | Maximum drawdown during holding |
pdays | Number of profitable days |
industry | Industry sector |
report.get_mae_mfe()
Returns each trade's Maximum Adverse Excursion (MAE) and Maximum Favorable Excursion (MFE).
mae_mfe = report.get_mae_mfe()Use MAE/MFE for:
- Setting stop-loss levels: Observe the MAE distribution to determine a reasonable stop-loss threshold
- Setting take-profit levels: Observe the MFE distribution to determine a reasonable take-profit threshold
- Optimizing entry/exit: If MFE is large but the final return is small, take-profit is too late
report.display_mae_mfe_analysis()
Draws the MAE/MFE volatility analysis charts; the same as report.run_analysis('MaeMfeAnalysis').
report.display_mae_mfe_analysis()report.position_info()
Returns recent holdings and rebalancing info.
info = report.position_info()The result is a dict with one entry per stock (status, weight, next_weight, entry_date, exit_date, return, entry_price), plus keys such as update_date, next_trading_date, trade_at, freq, market, stop_loss and take_profit.
Run deeper analysis
report.run_analysis()
Runs a strategy analysis plugin module, such as liquidity, alpha/beta, period stats or drawdown.
# Pass analysis module name (string)
report.run_analysis('Liquidity')
report.run_analysis('MaeMfe')
report.run_analysis('AlphaBeta')
report.run_analysis('PeriodStats')
report.run_analysis('Drawdown')
# Don't display charts, only get results
result = report.run_analysis('Liquidity', display=False)| Parameter | Type | Default | Description |
|---|---|---|---|
analysis | str or analysis object | required | Analysis module name (the trailing Analysis may be left out), or an analysis object |
display | bool | True | True shows the charts; False only returns the analysis result |
**kwargs | When passing a name, the parameters for that analysis module |
See finlab.analysis for each module's parameters and output.
Save, share and upload
report.to_text()
Presents the backtest report as text, suitable for notifications and logs.
text = report.to_text(name='My Strategy')
print(text)| Parameter | Type | Default | Description |
|---|---|---|---|
name | str or None | None | Strategy name |
The text labels are currently written in Chinese; for English output, format report.get_stats() yourself as in Get performance numbers.
report.to_pickle() / Report.from_pickle()
Saves the report to a file and loads it later, without rerunning the backtest.
# Save report
report.to_pickle('my_strategy_report.pkl')
# Load report
from finlab.core.report import Report
loaded_report = Report.from_pickle('my_strategy_report.pkl')
loaded_report.display()Useful when the backtest takes a long time and you want to keep the result, when you compare backtest reports from different periods, or when you share backtest results with others.
report.upload()
Uploads the backtest report to FinLab cloud for live tracking.
# Upload report
report.upload(name='My Momentum Strategy')
# View later in FinLab Studio:
# https://studio.finlab.finance/en/strategies| Parameter | Type | Default | Description |
|---|---|---|---|
name | str or None | None | Strategy name |
Use a custom benchmark
Before running the backtest, subclass the strategy's Market (for example USMarket), override get_benchmark() to return a date-indexed price or cumulative-value Series, and pass the market to sim(). This example benchmarks against the adjusted prices of QQQ:
from finlab import data
from finlab.backtest import sim
from finlab.markets.us import USMarket
class QQQBenchmarkMarket(USMarket):
@staticmethod
def get_benchmark():
return data.get("us_fund_price:adj_close")["QQQ"]
data.set_market("us")
close = data.get("price:adj_close")[["AAPL", "MSFT", "NVDA", "AMZN", "GOOGL"]]
position = close > close.average(20)
report = sim(position, market=QQQBenchmarkMarket(), resample="M", upload=False)
report.display()- Return prices or cumulative values covering the backtest period, not daily returns.
- To change the benchmark, rerun
sim()with the new Market; assigningreport.benchmark = ...afterward does not update all charts and statistics. - ETF strategies can subclass
USFundMarketthe same way, and Taiwan strategiesTWMarket.
See finlab.market and the custom market guide for the full get_benchmark() contract.
Common questions
How do I compare several strategies?
Put each report's get_stats() into one table:
# Two strategies on the same five stocks
position1 = close > close.average(20)
position2 = close > close.average(60)
report1 = sim(position1, resample='M')
report2 = sim(position2, resample='M')
stats1 = report1.get_stats()
stats2 = report2.get_stats()
import pandas as pd
comparison = pd.DataFrame(
{
'Strategy A': {
'Annual Return': stats1['cagr'],
'Sharpe Ratio': stats1['daily_sharpe'],
'Max Drawdown': stats1['max_drawdown'],
},
'Strategy B': {
'Annual Return': stats2['cagr'],
'Sharpe Ratio': stats2['daily_sharpe'],
'Max Drawdown': stats2['max_drawdown'],
},
}
)
print(comparison)To compare many at once, use ReportCollection from finlab.optimize.
Why can't I see my report?
# Cause 1: Forgot to call .display()
report # Does not display
report.display() # Correct
# Cause 2: Jupyter Notebook environment issue
# Add at the beginning of the notebook:
%matplotlib inline
# Cause 3: Data is empty (no trades)
trades = report.get_trades()
if len(trades) == 0:
print("Strategy has no trade records, please check entry conditions")In a plain Python script, display() only prints a text summary; save the report with report.to_html() and open it in a browser instead.
How do I customize the report's style?
The interactive report has a fixed layout. To style your own chart, plot the report's series with plotly:
import plotly.graph_objects as go
fig = go.Figure()
fig.add_scatter(
x=report.daily_creturn.index,
y=report.daily_creturn,
name='strategy',
line_color='blue',
)
fig.add_scatter(
x=report.daily_benchmark.index,
y=report.daily_benchmark,
name='benchmark',
line_color='gray',
)
# Change title and chart size
fig.update_layout(title='My Strategy Backtest Report', width=1200, height=600)
fig.show()Next steps
- Backtesting The full backtest workflow, how to read the metrics, and trade analysis.
- finlab.analysis Liquidity, MAE/MFE, period stats and other in-depth analysis.
- finlab.portfolio Combine several reports into one portfolio.
- Complete strategy workflow From research and backtesting to live trading.
Was this page helpful?