finlab.report
Backtest report module. The Report object is generated by the sim() function and contains complete backtesting performance information.
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; assigning report.benchmark = ... afterward does not update all charts and statistics. ETF strategies can subclass USFundMarket the same way, and Taiwan strategies TWMarket. See Market and the custom market guide for the full get_benchmark() contract.
Use Cases
- View backtest performance (annualized return, Sharpe ratio, max drawdown)
- Analyze trade records (entry/exit dates, holding periods, returns)
- Visualize equity curves and drawdowns
- Save reports for future reference
- Upload to FinLab cloud for live tracking
- Run in-depth analysis (liquidity, stock selection ability, holdings distribution)
Quick Examples
Basic Usage: Generate and View Report
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()
Get Performance Metrics
# Get performance statistics (dict)
stats = report.get_stats()
print(f"Annualized return: {stats['cagr'] * 100:.2f}%")
print(f"Sharpe ratio: {stats['daily_sharpe']:.2f}")
print(f"Max drawdown: {stats['max_drawdown'] * 100:.2f}%")
print(f"Win rate: {stats['win_ratio'] * 100:.2f}%")
# Get structured performance metrics (nested dict)
metrics = report.get_metrics()
print(f"Annualized return: {metrics['profitability']['annualReturn']:.2%}")
print(f"Sharpe ratio: {metrics['ratio']['sharpeRatio']:.2f}")
print(f"Max drawdown: {metrics['risk']['maxDrawdown']:.2%}")
View Trade Records
# Get all trade records
trades = report.get_trades()
print(f"Total trades: {len(trades)}")
print(trades.head())
# Filter profitable trades
profitable_trades = trades[trades['return'] > 0]
print(f"Profitable trade ratio: {len(profitable_trades) / len(trades):.1%}")
Detailed Guide
See Backtesting Tutorial for: - Complete backtesting workflow - All methods of the Report object - Performance metric interpretation - Trade record analysis
API Reference
Report
The Report object is generated by the sim() function and contains complete backtesting performance information.
Key Report Methods:
| Method | Description | Usage Frequency |
|---|---|---|
display() |
Display full report (with visualizations) | Very High |
get_stats() |
Get performance statistics (dict) | Very High |
get_metrics() |
Get structured performance metrics (nested dict) | Very High |
get_trades() |
Get trade records (DataFrame) | Very High |
get_mae_mfe() |
Get Maximum Adverse/Favorable Excursion | High |
position_info() |
Get recent holdings and rebalancing info | High |
upload() |
Upload to FinLab cloud | Medium |
run_analysis() |
Run analysis plugin modules | Medium |
display_mae_mfe_analysis() |
Display volatility analysis charts | Medium |
to_text() |
Text format backtest summary | Low |
to_terminal() |
Terminal ASCII chart display | Low |
to_html(path, title=None) |
Export HTML report with an optional browser tab title | Low |
to_pickle() / from_pickle() |
Save/load report | Low |
display()
Display the full backtest report including visualizations and performance metrics.
Usage Examples:
Display Contents: - 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.
get_stats()
Get performance statistics.
Signature: get_stats(riskfree_rate=0.02)
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).
Returns: dict
Usage Examples:
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}%")
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 |
Aliases shared with get_metrics(): 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.
get_metrics()
Get structured performance metrics (nested dict), suitable for cloud upload or programmatic processing.
Signature: get_metrics(stats_=None, riskfree_rate=0.02)
Returns: dict with the following categories:
Usage Examples:
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%}")
Return Structure:
| 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) |
get_trades()
Get detailed information for all trade records.
Returns: pd.DataFrame
Usage Examples:
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())
DataFrame Columns:
| 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 |
get_mae_mfe()
Get Maximum Adverse Excursion (MAE) and Maximum Favorable Excursion (MFE) analysis.
Usage Examples:
# Get MAE/MFE data
mae_mfe = report.get_mae_mfe()
# Visualize MAE/MFE distribution
report.display_mae_mfe_analysis()
Applications: - Setting stop-loss levels: Observe MAE distribution to determine a reasonable stop-loss threshold - Setting take-profit levels: Observe MFE distribution to determine a reasonable take-profit threshold - Optimizing entry/exit: If MFE is large but final return is small, take-profit is too late
MAE/MFE Interpretation
- MAE (Maximum Adverse Excursion): Maximum unrealized loss during holding period
- MFE (Maximum Favorable Excursion): Maximum unrealized profit during holding period
- Ideal scenario: Large MFE, small MAE, indicating good entry timing
upload()
Upload the backtest report to FinLab cloud for live tracking.
Usage Examples:
# Upload report
report.upload(name='My Momentum Strategy')
# View later in FinLab Studio:
# https://studio.finlab.finance/en/strategies
Prerequisites
- Must be logged in to FinLab (
finlab.login()) - Requires VIP membership
- After upload, can be used for live trading and performance tracking
run_analysis()
Run strategy analysis plugin modules.
Signature: run_analysis(analysis, display=True, **kwargs)
Usage Examples:
# 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)
See Strategy Analysis Modules for detailed documentation.
to_text()
Present the backtest report in text format, suitable for notifications and logs. The text labels are currently written in Chinese; for English output, format report.get_stats() yourself as in Get Performance Metrics.
Signature: to_text(name=None)
Usage Examples:
to_terminal()
Display cumulative return ASCII chart and drawdown in the terminal.
Signature: to_terminal(height=12, width=80, show_benchmark=True, show_drawdown=True)
Usage Examples:
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)
Installation
Requires asciichartpy: pip install 'finlab[terminal]'
to_pickle() / from_pickle()
Save and load reports.
Usage Examples:
# 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()
When to Use
- Backtesting takes a long time and you want to save results
- Need to compare backtest reports from different periods
- Share backtest results with others
FAQ
Q: How do I compare performance of multiple strategies?
# 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)
Q: 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")
Q: How do I customize the report visualization 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()
Resources
- Backtesting Tutorial - Complete backtesting workflow
- Strategy Analysis Modules - In-depth analysis tools
- Multi-Strategy Portfolio Management - Portfolio object
- Complete Strategy Development Workflow - End-to-end workflow
Print the strategy check-up in Python
print(report.to_checkup_text(lang="en"))
checkup = report.get_checkup(lang="en") # JSON-safe dictionary
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 None with
status="insufficient_data" and do not pass. Absent metrics are omitted, and
empty trades have an explicit note. Liquidity appears only for supported Taiwan
markets. The default language is zh-tw; get_metrics() keeps its existing output.