finlab.analysis
The strategy analysis module provides in-depth analysis tools to evaluate strategy quality from multiple angles: liquidity risk, volatility characteristics, period stability, and more.
Use Cases
- Liquidity risk detection (Taiwan market): Large-capital strategies must check for insufficient volume, limit-up/limit-down issues, etc.
- Optimize stop-loss/take-profit: Use MAE/MFE analysis to find optimal stop-loss and take-profit levels
- Evaluate long-term stability: Analyze whether strategy performance is consistent across different years and months
- Assess excess returns: Use Alpha/Beta analysis to measure whether the strategy outperforms the benchmark
- Custom analysis logic: Inherit the
Analysisclass to develop proprietary analysis modules
Quick Examples
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')
Built-in Analysis Module Comparison
| Module | Purpose | Key Output |
|---|---|---|
LiquidityAnalysis |
Liquidity risk detection (Taiwan market only) | Limit-up/down, low volume, disposal stock ratios |
MaeMfeAnalysis |
Volatility analysis | 12 sub-charts (MAE/MFE/win rate/Edge Ratio) |
PeriodStatsAnalysis |
Period stability | Annual, monthly, and recent performance |
InequalityAnalysis |
Factor threshold analysis | Mean trade return above vs. below each factor value |
AlphaBetaAnalysis |
Alpha/Beta | Excess return, market sensitivity |
DrawdownAnalysis |
Drawdown analysis | Drawdown events, durations |
Detailed Guide
See Strategy Analysis Module Guide for:
- Detailed functionality and parameter descriptions for each module
- How to interpret output and make decisions
- How to inherit the
Analysisclass to develop custom analysis modules - Analysis priorities for different strategy types
- Decision trees based on analysis results
API Reference
LiquidityAnalysis - Liquidity Risk Detection
Taiwan market only
is_market_supported() returns True only for the Taiwan stock market, whose price limits and alert, disposal and full-delivery stock rules this analysis checks.
Measures how often a strategy's trades hit liquidity problems on the entry and exit dates.
| Parameter | Type | Description |
|---|---|---|
required_volume |
int |
Minimum shares traded on the entry/exit day. Default 200000. |
required_turnover |
int |
Minimum traded value on the entry/exit day, so that cheap stocks with enough shares but little value traded are still flagged. Default 1000000. |
# Shorthand
report.run_analysis('LiquidityAnalysis', required_volume=100000)
# Equivalent
from finlab.analysis.liquidityAnalysis import LiquidityAnalysis
report.run_analysis(LiquidityAnalysis(required_volume=100000))
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
InequalityAnalysis - Inequality Factor Analysis
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.
| Parameter | Type | Description |
|---|---|---|
name |
str |
Name of the factor; used as the trade column name (<name>@<date_type>) and chart label. |
df |
pd.DataFrame or 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 |
Date on which the factor is read: 'entry_date', 'entry_sig_date', 'exit_date' or 'exit_sig_date'. |
target |
str |
Trade column to evaluate; any column of report.get_trades(). |
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))
Reading 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
PeriodStatsAnalysis - Period Stability Analysis
Compares the strategy with its benchmark (the S&P 500 for U.S. stocks) over different periods.
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
AlphaBetaAnalysis - Alpha/Beta Analysis
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 - Drawdown Analysis
finlab.analysis.drawdownAnalysis.DrawdownAnalysis
Risk Control
- Max drawdown > -50%: Risk is too high
- Drawdown duration > 1 year: Strategy may be failing long-term
MaeMfeAnalysis - Volatility Analysis
finlab.analysis.maeMfeAnalysis.MaeMfeAnalysis
Stop-Loss/Take-Profit Optimization
- Stop-loss level = MAE 75th percentile x 1.2
- Take-profit level = GMFE 75th percentile x 0.8
- Missed Win-profits PCT < 20%: Suitable for trailing stop
Custom Analysis Modules
To develop a custom analysis system, inherit finlab.analysis.Analysis to implement your analysis.
report.run_analysis(analysis) calls calculate_trade_info(), adds the returned columns to the trades, then calls analyze() and, unless display=False, returns display().
| 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.result
Custom Example
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())
Best Practices
Standard Analysis Workflow
# 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)
By Strategy Type
| 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 |