API reference
finlab.analysis
On this page11
finlab.analysis checks a backtest from several angles: liquidity, volatility, stability across periods, excess return and drawdowns, so you can tell whether a strategy holds up before trading it. Every analysis runs through report.run_analysis(); when the check you need is not built in, inherit Analysis and write your own.
Which analysis to use
| You want to know | Use | Key output |
|---|---|---|
| Where to set stop-loss and take-profit | MaeMfeAnalysis | 12 sub-charts (MAE/MFE/win rate/Edge Ratio) |
| Whether performance is steady across years, months and recent periods | PeriodStatsAnalysis | Annual, monthly, and recent performance |
| Whether the strategy beats its benchmark, and how closely it moves with it | AlphaBetaAnalysis | Excess return, market sensitivity |
| How deep drawdowns go and how long they last | DrawdownAnalysis | Drawdown events, durations |
| How returns change when a factor threshold is added | InequalityAnalysis | Mean trade return above vs. below each factor value |
| Whether large capital could fill its trades (Taiwan market only) | LiquidityAnalysis | Limit-up/down, low volume, disposal stock ratios |
| An analysis of your own | Inherit Analysis | Up to you |
Run an analysis on a backtest
Run the backtest first, then pass the module name to report.run_analysis(). Other arguments go to the module:
from finlab import data
from finlab.backtest import sim
# Complete backtest
data.set_market('us')
with data.us_universe(index='SP500'):
close = data.get('price:adj_close')
position = close > close.average(20)
report = sim(position, resample='M')
# 1. Volatility analysis (MAE/MFE)
report.display_mae_mfe_analysis()
# 2. Period stability analysis
report.run_analysis('PeriodStatsAnalysis')
# 3. Alpha/Beta analysis
report.run_analysis('AlphaBetaAnalysis')A string is shorthand; you can also build the module object and pass it, with the same result:
# Shorthand
report.run_analysis('PeriodStatsAnalysis')
# Equivalent
from finlab.analysis.periodStatsAnalysis import PeriodStatsAnalysis
report.run_analysis(PeriodStatsAnalysis())report.run_analysis(analysis) calls calculate_trade_info(), adds the returned columns to the trades, then calls analyze() and, unless display=False, returns display().
Find stop-loss and take-profit levels
MaeMfeAnalysis
Measures how far each trade went against you (MAE) and in your favor (MFE) while it was held, drawn as 12 sub-charts. The usual entry point is report.display_mae_mfe_analysis(), the same as report.run_analysis('MaeMfeAnalysis'):
# Violin plot mode: 'group' or 'overlay'; X axis of the MFE scatter: 'mae' or 'return'
report.display_mae_mfe_analysis(violinmode='overlay', mfe_scatter_x='return')finlab.analysis.MaeMfeAnalysis
MaeMfeAnalysis(violinmode, mfe_scatter_x, **kwargs)Parameters
violinmodestrdefault'group'mfe_scatter_xstrdefault'mae'kwargsobjectdefault{}
Check stability across periods
PeriodStatsAnalysis()
Compares the strategy with its benchmark (the S&P 500 for U.S. stocks) over different periods.
report.run_analysis('PeriodStatsAnalysis')The result has one column for the benchmark and one for the strategy, with these metrics: calmar_ratio, sortino_ratio, sharpe_ratio, profit_factor (total gains / total losses), tail_ratio, return (CAGR) and volatility. They are computed on daily, monthly and yearly returns (overall_daily, overall_monthly, overall_yearly), followed by tables per calendar year and for the most recent month, quarter, half year, year and three years (M, Q, HY, Y, 3Y).
Evaluation criteria:
- Strategy sharpe_ratio < benchmark: Strategy needs improvement
- Some years perform particularly poorly: Check for specific market conditions
- Recent performance significantly declining: Strategy may be losing effectiveness
Measure excess return and drawdowns
AlphaBetaAnalysis
Computes the strategy's alpha (annualized excess return) and beta (market sensitivity) against its benchmark.
report.run_analysis('AlphaBetaAnalysis')finlab.analysis.AlphaBetaAnalysis
AlphaBetaAnalysis()Ideal metrics:
- Alpha > 0: Strategy generates excess returns
- Beta 0.5-1.5: Moderate market correlation
- Beta < 0: Strategy is negatively correlated with the market (suitable for hedging)
DrawdownAnalysis
Lists the drawdown events and their durations for the strategy and its benchmark.
report.run_analysis('DrawdownAnalysis')finlab.analysis.DrawdownAnalysis
DrawdownAnalysis()Methods and properties
display
display
display() -> dict[str, dict[str, object]]Display drawdown analysis result.
Risk control:
- Max drawdown > -50%: Risk is too high
- Drawdown duration > 1 year: Strategy may be failing long-term
See how a factor threshold changes returns
InequalityAnalysis()
Analyzes how trade returns change when a condition factor < x or factor > x is added, for every threshold x. Trades are sorted by the factor value on date_type. The upper charts show the mean target of the trades kept by each threshold minus the mean of all trades (titled <name> < x and <name> > x); the lower charts show the factor value at each cut, by number of trades. target outliers beyond 3 standard deviations are clipped, and the first 50 cuts, which keep too few trades, are not plotted.
from finlab import data
from finlab.analysis.inequalityAnalysis import InequalityAnalysis
from finlab.backtest import sim
data.set_market('us')
with data.us_universe(index='SP500'):
close = data.get('price:adj_close')
market_value = data.get('etl:us_market_value')
position = close > close.average(20)
report = sim(position, resample='M')
report.run_analysis(InequalityAnalysis(name='market_value', df=market_value))| Parameter | Type | Default | Description |
|---|---|---|---|
name | str | required | Name of the factor; used as the trade column name (<name>@<date_type>) and chart label. |
df | pd.DataFrame or None | None | Factor values (dates by symbols). If None, data.get(name) is fetched automatically, so name must then be a dataset key. |
date_type | str | 'entry_sig_date' | Date on which the factor is read: 'entry_date', 'entry_sig_date', 'exit_date' or 'exit_sig_date'. |
target | str | 'return' | Trade column to evaluate; any column of report.get_trades(). |
How to read the chart:
- A curve that rises steadily as the threshold moves means the factor separates good trades from bad ones
- A flat curve near zero means filtering on this factor does not change returns
- Thresholds that keep only a handful of trades are noisy; check the trade count in the lower charts
Check trade liquidity (Taiwan market)
LiquidityAnalysis()
Measures how often a strategy's trades hit liquidity problems on the entry and exit dates.
# Shorthand
report.run_analysis('LiquidityAnalysis', required_volume=100000)
# Equivalent
from finlab.analysis.liquidityAnalysis import LiquidityAnalysis
report.run_analysis(LiquidityAnalysis(required_volume=100000))| Parameter | Type | Default | Description |
|---|---|---|---|
required_volume | int | 200000 | Minimum shares traded on the entry/exit day. |
required_turnover | int | 1000000 | Minimum traded value on the entry/exit day, so that cheap stocks with enough shares but little value traded are still flagged. |
The result is a table with an entry and an exit row. Columns give the share of trades that:
buy_high: bought within 5% of the daily price limit (long entries, short exits)sell_low: sold within 5% of the daily price limit (long exits, short entries)low_volume_stocks: traded fewer shares thanrequired_volumelow_turnover_stocks: traded less value thanrequired_turnover- plus the share of alert, disposal and full-delivery stocks on the signal dates
Risk threshold guidelines:
buy_highorsell_low> 10%: Orders may not execute successfullylow_volume_stocks> 20%: Large capital may not be fully deployed- Disposal stocks > 5%: Strategy may be selecting high-risk targets
Write a custom analysis module
Analysis
To develop a custom analysis, inherit finlab.analysis.Analysis and implement analyze() and display().
class Analysis(ABC)| Method | Description |
|---|---|
is_market_supported(market) -> bool | Whether the analysis supports the backtest's market. Default True. |
calculate_trade_info(report) -> list | Optional. Return extra trade information as [name, df, date_type] lists: df is a DataFrame (dates by symbols) whose value on each trade's date_type ('entry_sig_date', 'entry_date', ...) becomes the trade column <name>@<date_type>. Default []. |
analyze(report) | Required. Analyze the report; report.get_trades() already contains the columns from calculate_trade_info(). |
display() | Required. Display the result; returning a Plotly figure is recommended. |
from finlab import data
from finlab.analysis import Analysis
class MarketValueAnalysis(Analysis):
def calculate_trade_info(self, report):
return [['market_value', data.get('etl:us_market_value'), 'entry_sig_date']]
def analyze(self, report):
trades = report.get_trades()
assert 'market_value@entry_sig_date' in trades.columns
self.result = trades[['market_value@entry_sig_date', 'return']]
return self.result
def display(self):
return self.resultAverage trade return by industry, using the industry of each U.S. stock from the backtest's market:
from finlab.analysis import Analysis
class IndustryAnalysis(Analysis):
"""Average trade return by industry"""
def analyze(self, report):
trades = report.get_trades()
industry = trades['stock_id'].map(report.market.get_industry())
self.result = (
trades.groupby(industry)['return']
.agg(['count', 'mean'])
.sort_values('mean', ascending=False)
)
return self.result.to_dict()
def display(self):
return self.result
# Usage
report.run_analysis(IndustryAnalysis())Recommended order
# Step 1: Basic performance review
report.display()
# Step 2: Volatility analysis (mandatory)
report.display_mae_mfe_analysis()
# Step 3: Period stability
report.run_analysis('PeriodStatsAnalysis')
# Step 4: Alpha/Beta
report.run_analysis('AlphaBetaAnalysis')
# Taiwan stocks only: liquidity risk (mandatory for large capital)
# report.run_analysis('LiquidityAnalysis', required_volume=100000)Different strategy types call for different modules:
| Strategy type | Key analysis module |
|---|---|
| Short-term trading (intraday, weekly) | MaeMfeAnalysis |
| Long-term investing (monthly, quarterly) | PeriodStatsAnalysis |
| Large capital strategies on Taiwan stocks | LiquidityAnalysis |
| Market-neutral strategies | AlphaBetaAnalysis |
| High-frequency trading | DrawdownAnalysis |
Common questions
Can I use LiquidityAnalysis on U.S. stocks?
No. The price limits and the alert, disposal and full-delivery stock rules it checks are Taiwan rules, and is_market_supported() returns True only for the Taiwan stock market. For U.S. strategies, use MaeMfeAnalysis, PeriodStatsAnalysis and AlphaBetaAnalysis.
How do I get the numbers without a chart?
Pass display=False; run_analysis() then returns the result of analyze() instead of display():
result = report.run_analysis('PeriodStatsAnalysis', display=False)Next steps
- Strategy analysis modules Every module's parameters, how to read the results, custom analyses, and what to check for each strategy type.
- Backtesting The trade log and MAE/MFE columns of a backtest report.
- Complete strategy workflow From research and validation to live trading.
Source code is on GitHub.
Was this page helpful?