Docs
Open Studio

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 knowUseKey output
Where to set stop-loss and take-profitMaeMfeAnalysis12 sub-charts (MAE/MFE/win rate/Edge Ratio)
Whether performance is steady across years, months and recent periodsPeriodStatsAnalysisAnnual, monthly, and recent performance
Whether the strategy beats its benchmark, and how closely it moves with itAlphaBetaAnalysisExcess return, market sensitivity
How deep drawdowns go and how long they lastDrawdownAnalysisDrawdown events, durations
How returns change when a factor threshold is addedInequalityAnalysisMean trade return above vs. below each factor value
Whether large capital could fill its trades (Taiwan market only)LiquidityAnalysisLimit-up/down, low volume, disposal stock ratios
An analysis of your ownInherit AnalysisUp 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')
class

finlab.analysis.MaeMfeAnalysis

MaeMfeAnalysis(violinmode, mfe_scatter_x, **kwargs)

Parameters

violinmode str default 'group'
mfe_scatter_x str default 'mae'
kwargs object default {}
Backtest report · interactive Open full size

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')
class

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')
class

finlab.analysis.DrawdownAnalysis

DrawdownAnalysis()

Methods and properties

display
function

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))
ParameterTypeDefaultDescription
namestrrequiredName of the factor; used as the trade column name (<name>@<date_type>) and chart label.
dfpd.DataFrame or NoneNoneFactor values (dates by symbols). If None, data.get(name) is fetched automatically, so name must then be a dataset key.
date_typestr'entry_sig_date'Date on which the factor is read: 'entry_date', 'entry_sig_date', 'exit_date' or 'exit_sig_date'.
targetstr'return'Trade column to evaluate; any column of report.get_trades().
Backtest report · interactive Open full size

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))
ParameterTypeDefaultDescription
required_volumeint200000Minimum shares traded on the entry/exit day.
required_turnoverint1000000Minimum 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 than required_volume
  • low_turnover_stocks: traded less value than required_turnover
  • plus the share of alert, disposal and full-delivery stocks on the signal dates

Risk threshold guidelines:

  • buy_high or sell_low > 10%: Orders may not execute successfully
  • low_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)
MethodDescription
is_market_supported(market) -> boolWhether the analysis supports the backtest's market. Default True.
calculate_trade_info(report) -> listOptional. 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.result

Average 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())
# 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 typeKey analysis module
Short-term trading (intraday, weekly)MaeMfeAnalysis
Long-term investing (monthly, quarterly)PeriodStatsAnalysis
Large capital strategies on Taiwan stocksLiquidityAnalysis
Market-neutral strategiesAlphaBetaAnalysis
High-frequency tradingDrawdownAnalysis

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

Source code is on GitHub.

↑↓ Move Enter Open Esc Close