Skip to content

Strategy Analysis Modules

After backtesting, looking only at the cumulative return curve and Sharpe ratio may not be comprehensive enough. The finlab.analysis module provides 6 built-in analysis tools that evaluate strategy quality from multiple angles including liquidity risk, volatility characteristics, and period stability, helping you make more informed trading decisions.

Overview

FinLab includes the following 6 analysis modules:

Module Purpose Use Case
LiquidityAnalysis Liquidity risk assessment Large capital strategies, low-liquidity assets
MaeMfeAnalysis Volatility analysis (MAE/MFE) Optimizing stop-loss/take-profit, understanding strategy characteristics
PeriodStatsAnalysis Period stability analysis Reviewing yearly, monthly, and recent performance
InequalityAnalysis Inequality factor analysis Checking if strategy relies on specific stocks
AlphaBetaAnalysis Alpha/Beta analysis Measuring excess return and market correlation
DrawdownAnalysis Drawdown analysis Understanding max drawdown timing and magnitude

Basic Usage

All analysis modules use the same calling convention:

from finlab import data
from finlab.backtest import sim

# Complete backtest
close = data.get('price:收盤價')
position = close > close.average(20)
report = sim(position, resample='M')

# Run analysis - Method 1: Using module name (string)
report.run_analysis('LiquidityAnalysis', required_volume=100000)

# Run analysis - Method 2: Using module instance
from finlab.analysis.liquidityAnalysis import LiquidityAnalysis
report.run_analysis(LiquidityAnalysis(required_volume=100000))

Method 1 is recommended

Passing the module name as a string is more concise, and FinLab handles module loading automatically.

1. LiquidityAnalysis - Liquidity Risk Assessment

Description

Detects whether the strategy encounters liquidity issues during entry/exit, including:

  • Insufficient volume: Daily trading volume below requirements
  • Insufficient turnover: Daily trading value below requirements
  • Price limits: Buying at upper limit or selling at lower limit (unable to execute)
  • Alert/Disposition/Full-delivery stocks: High-risk securities with trading restrictions

Parameters

LiquidityAnalysis(
    required_volume=200000,      # Required daily trading volume (default: 200,000 shares)
    required_turnover=1000000    # Required daily turnover (default: 1,000,000)
)

Usage Example

# Assess strategy liquidity risk
report.run_analysis('LiquidityAnalysis', required_volume=100000)

Liquidity Assessment

Interpreting Output

The table shows the probability of each risk item occurring at entry and exit:

Risk Item entry exit Description
buy_high 5.2% 1.3% Percentage of trades buying at or near the upper price limit
sell_low 0.8% 4.5% Percentage of trades selling at or near the lower price limit
low_volume_stocks 12.1% 10.3% Percentage of trades with insufficient volume
low_turnover_stocks 8.7% 7.2% Percentage of trades with insufficient turnover
Alert stocks 2.1% 1.8% Percentage of trades involving alert stocks
Disposition stocks 0.5% 0.3% Percentage of trades involving disposition stocks
Full-delivery stocks 0.2% 0.1% Percentage of trades involving full-delivery stocks

Risk threshold suggestions

  • buy_high or sell_low > 10%: Orders may not execute smoothly
  • low_volume_stocks > 20%: Large capital may not fully deploy
  • Disposition stocks > 5%: Strategy may be targeting high-risk securities

Decision Recommendations

  • Insufficient liquidity: Add screening conditions (e.g., "volume > 1000 lots")
  • Frequent price limit hits: Adjust entry/exit timing (e.g., use open price instead of close)
  • Too many alert/disposition stocks: Add risk control conditions to exclude high-risk securities

2. MaeMfeAnalysis - Volatility Analysis

Description

MAE/MFE analysis is the core tool for optimizing stop-loss/take-profit:

  • MAE (Maximum Adverse Excursion): Maximum adverse movement during holding period (max loss)
  • BMFE (Before-MAE MFE): Maximum favorable movement before MAE occurred
  • GMFE (Global MFE): Maximum favorable movement during holding period (max profit)
  • MDD (Maximum Drawdown): Maximum drawdown during holding period

Through 12 subplots, gain a comprehensive understanding of each trade's volatility characteristics.

Parameters

MaeMfeAnalysis(
    violinmode='group',      # Violin plot mode: 'group' or 'overlay'
    mfe_scatter_x='mae'      # MFE scatter plot X-axis: 'mae' or 'return'
)

Usage Example

# Method 1: Using the display_mae_mfe_analysis() shortcut
report.display_mae_mfe_analysis()

# Method 2: Using run_analysis()
report.run_analysis('MaeMfeAnalysis', violinmode='group')

Volatility Analysis

12 Subplot Descriptions

Row 1

  1. Win Ratio: Trade win rate distribution (profit vs loss histogram)
  2. Blue: profitable trades, Pink: losing trades
  3. Green dashed line: average return

  4. Edge Ratio: MFE/MAE ratio at different holding periods

  5. Value > 1 means profit magnitude exceeds loss magnitude
  6. Observe the strategy's "edge time window"

  7. MAE vs Return: Relationship between MAE and final return

  8. Horizontal dashed line: MAE 75th percentile (Q3)
  9. Used for setting stop-loss points

Row 2

  1. GMFE vs MAE: Maximum profit vs maximum loss
  2. Q3 dashed lines: Range of 75% of trades' GMFE and MAE
  3. Used for evaluating take-profit potential

  4. BMFE vs MAE: Maximum profit before MAE occurred

  5. Higher values mean more opportunity to take profit before stop-loss triggers
  6. Used for optimizing "trailing stop" strategies

  7. MDD vs GMFE: Maximum drawdown vs maximum profit

  8. Orange line: MDD = GMFE baseline
  9. Points below: GMFE > MDD (good signal, trailing stop viable)
  10. Points above: MDD > GMFE (bad signal, missing profits)

Row 3

7-9. MAE/BMFE/GMFE Distribution: Distribution and density of each metric - Histogram + density curve - Blue: profitable trades, Pink: losing trades - Q3 dashed line: 75th percentile

Row 4

  1. Indices Stats (Violin Plot): Statistical distribution of 6 key metrics
  2. return, mae, bmfe, gmfe, mdd, pdays_ratio
  3. group mode: Shows profit/loss trade differences separately (recommended)
  4. overlay mode: All trades overlaid

Decision Recommendations

Optimize stop-loss/take-profit based on MAE/MFE analysis:

Setting Stop-Loss

# 1. Check MAE 75th percentile (Q3)
trades = report.get_trades()
mae_q3 = trades['mae'].quantile(0.75)  # e.g., -8.5%

# 2. Set stop-loss slightly wider than Q3 to avoid over-stopping
stop_loss = abs(mae_q3) * 1.2  # Set to 10%

Setting Take-Profit

# Check GMFE 75th percentile
gmfe_q3 = trades['gmfe'].quantile(0.75)  # e.g., 15%

# Set take-profit
take_profit = gmfe_q3 * 0.8  # Set to 12%, ensuring most profits are captured

Trailing Stop Decision

If the "MDD vs GMFE" chart shows:

  • Missed Win-profits PCT < 20%: Trailing stop is suitable
  • Missed Win-profits PCT > 40%: Trailing stop is not suitable (exits too early, missing larger gains)
# Use trailing stop (via sim()'s trail_stop parameter)
position = position.hold_until(
    exit=exits,
    stop_loss=0.1
)
report = sim(position, resample='M', trail_stop=0.08)  # Exit on 8% pullback

3. PeriodStatsAnalysis - Period Stability Analysis

Description

Analyzes strategy performance across different periods (yearly, monthly, recent) and compares with a benchmark to verify stability.

Usage Example

report.run_analysis('PeriodStatsAnalysis')

Interpreting Output

Produces multiple tables including:

1. Overall Stats

Compares strategy vs benchmark on daily, monthly, and yearly metrics:

Metric benchmark strategy
overall_daily / calmar_ratio 0.149 0.066
overall_daily / sharpe_ratio 0.532 0.306
overall_monthly / return 0.084 0.048

2. Yearly Stats

Heatmap showing 7 metrics per year (calmar_ratio, sharpe_ratio, return, volatility, etc.).

3. Recent Stats

Shows performance for M (month), Q (quarter), HY (half-year), Y (year), 3Y (three years).

Decision Recommendations

  • Strategy sharpe_ratio < benchmark: Risk-adjusted return is worse than the market; improvement needed
  • Some years perform very poorly: Check if specific market conditions were encountered (e.g., bear market)
  • Recent performance significantly declined: Strategy may be failing; re-optimization needed

4. InequalityAnalysis - Inequality Factor Analysis

Description

Checks whether strategy returns are overly concentrated in a few stocks, evaluating strategy robustness.

Inequality Factor Analysis

Usage Example

report.run_analysis('InequalityAnalysis')

Interpreting Output

Produces two charts:

  1. Cumulative Returns by Stock: Cumulative contribution curve by stock
  2. If the curve reaches 80% within the top 10% of stocks, returns are overly concentrated

  3. Gini Coefficient: Gini coefficient (0-1)

  4. Close to 0: Returns are evenly distributed (good)
  5. Close to 1: Returns are extremely concentrated (high risk)

Decision Recommendations

  • Gini > 0.7: Strategy is overly dependent on a few stocks; diversification needed
  • Top 5% stocks contribute > 50% of returns: Remove these stocks and test strategy robustness

5. AlphaBetaAnalysis - Alpha/Beta Analysis

Description

Measures strategy performance relative to a benchmark index:

  • Alpha: Excess return (the strategy's unique value)
  • Beta: Market sensitivity (correlation with the market)

Usage Example

report.run_analysis('AlphaBetaAnalysis')

Interpreting Output

Shows three sections:

  1. Overall Summary -- Alpha and Beta overview:

    Metric Value Description
    Alpha 5.00% Annualized excess return (true contribution after removing market risk)
    Beta 0.80 Market sensitivity (market up 1%, strategy expected up 0.8%)
  2. Yearly Alpha/Beta -- Annual Alpha and Beta breakdown to observe strategy stability

  3. Recent Alpha/Beta -- Performance for full period, recent 1M, 3M, 6M, 1Y

To get raw data (dict):

result = report.run_analysis('AlphaBetaAnalysis', display=False)
# result['overall']  -> {'alpha': 0.05, 'beta': 0.8}
# result['yearly']   -> {'alpha': [...], 'beta': [...], 'year': [...]}
# result['recent']   -> {'alpha': [...], 'beta': [...], 'ndays': [...]}

Decision Recommendations

  • Alpha > 0: Strategy has excess returns; worth executing
  • Alpha < 0: Strategy underperforms the benchmark; improvement needed
  • Beta > 1.5: Strategy volatility is extremely high; high risk
  • Beta < 0: Strategy is negatively correlated with the market (suitable for hedging)

6. DrawdownAnalysis - Drawdown Analysis

Description

Detailed analysis of all drawdown events:

  • Maximum drawdown magnitude and timing
  • Average drawdown magnitude
  • Drawdown duration

Usage Example

report.run_analysis('DrawdownAnalysis')

Interpreting Output

Produces tables and charts:

  1. Drawdown Events Table: Detailed information for all drawdown events
  2. start_date: Drawdown start date
  3. end_date: Drawdown end date
  4. duration: Duration in days
  5. max_dd: Maximum drawdown magnitude

  6. Drawdown Plot: Drawdown time series chart

Decision Recommendations

  • Max drawdown > -50%: Risk is too high; additional risk controls needed
  • Drawdown duration > 1 year: Strategy may be ineffective long-term
  • Frequent recent drawdowns: Market conditions may have changed; re-evaluate strategy

Custom Analysis Modules

If built-in analysis is insufficient, you can inherit the Analysis class to develop custom analysis.

Basic Example: Industry Analysis

Analyze the industry of each trade:

from finlab import data
from finlab.analysis import Analysis

class IndustryAnalysis(Analysis):

    def calculate_trade_info(self, report):
        """Calculate industry information for each trade"""
        industry = data.get('etl:industry')
        return [
            ['Industry', industry, 'entry_sig_date']
        ]

    def analyze(self, report):
        """Analyze industry distribution"""
        trades = report.get_trades()
        industry_return = trades.groupby('Industry@entry_sig_date')['return'].agg(['count', 'mean'])
        self.result = industry_return.sort_values('mean', ascending=False)
        return self.result.to_dict()

    def display(self):
        """Display analysis results"""
        return self.result

# Use custom analysis
report.run_analysis(IndustryAnalysis())

Advanced Example: Monthly Win Rate Analysis

Analyze whether win rates differ by month:

class MonthlyWinRateAnalysis(Analysis):

    def analyze(self, report):
        trades = report.get_trades()
        trades['entry_month'] = pd.to_datetime(trades['entry_date']).dt.month

        monthly_stats = trades.groupby('entry_month').agg({
            'return': ['count', lambda x: (x > 0).mean(), 'mean']
        }).round(3)

        monthly_stats.columns = ['Trade Count', 'Win Rate', 'Average Return']
        self.result = monthly_stats
        return self.result.to_dict()

    def display(self):
        import plotly.express as px
        fig = px.bar(self.result, y='Win Rate', title='Monthly Win Rate Analysis')
        return fig

# Usage
report.run_analysis(MonthlyWinRateAnalysis())

Analysis Class API

When inheriting the Analysis class, you can override the following methods:

class Analysis:

    def is_market_supported(self, market):
        """Check if the market is supported

        Returns:
            bool: True if supported, False otherwise
        """
        return True

    def calculate_trade_info(self, report):
        """Calculate additional trade information

        Returns:
            list: Format is [[field_name, data DataFrame, date column used], ...]

        Examples:
            return [
                ['Industry', industry_data, 'entry_sig_date'],
                ['Market Cap', market_cap, 'entry_date']
            ]
        """
        return []

    def analyze(self, report):
        """Execute analysis logic

        Args:
            report: Backtest report object

        Returns:
            dict: Analysis results (for cloud upload)
        """
        pass

    def display(self):
        """Display analysis results

        Returns:
            Visualization object (Plotly Figure or Pandas Styler recommended)
        """
        pass

Purpose of calculate_trade_info

calculate_trade_info() executes before analyze(), automatically adding additional information to the report.get_trades() DataFrame.

For example, returning [['Industry', industry, 'entry_sig_date']] adds an 'Industry@entry_sig_date' column to trades.


Combining Multiple Analysis Modules

In practice, running multiple analyses simultaneously is recommended:

from finlab import data
from finlab.backtest import sim

# Complete backtest
close = data.get('price:收盤價')
position = close > close.average(20)
report = sim(position, resample='M')

# 1. Liquidity assessment
report.run_analysis('LiquidityAnalysis', required_volume=100000)

# 2. Volatility analysis
report.display_mae_mfe_analysis()

# 3. Period stability analysis
report.run_analysis('PeriodStatsAnalysis')

# 4. Alpha/Beta analysis
report.run_analysis('AlphaBetaAnalysis')

# 5. Custom industry analysis
report.run_analysis(IndustryAnalysis())

Best Practices

1. Standard Analysis Workflow

After each strategy backtest, the following checks are recommended:

# Step 1: Basic performance review
report.display()

# Step 2: Liquidity risk (must-check for large capital)
if capital > 1000000:
    report.run_analysis('LiquidityAnalysis', required_volume=100000)

# Step 3: Volatility analysis (must-do, for optimizing stop-loss/take-profit)
report.display_mae_mfe_analysis()

# Step 4: Period stability (check long-term stability)
report.run_analysis('PeriodStatsAnalysis')

# Step 5: Alpha/Beta (check for excess return)
report.run_analysis('AlphaBetaAnalysis')

2. Analysis Focus by Strategy Type

Strategy Type Key Analysis Module
Short-term trading (intraday, weekly) MaeMfeAnalysis (optimize stop-loss/take-profit)
Long-term investing (monthly, quarterly) PeriodStatsAnalysis (check long-term stability)
Large capital strategies (> 10M) LiquidityAnalysis (avoid liquidity risk)
Market neutral strategies AlphaBetaAnalysis (confirm Beta close to 0)
High-frequency trading DrawdownAnalysis (confirm drawdowns are manageable)

3. Analysis Result Decision Tree

Backtest Complete
  +- Liquidity risk > 20%?
  |   +- Yes -> Add volume screening conditions
  |   +- No  -> Continue
  +- Sharpe ratio < 1?
  |   +- Yes -> Optimize screening conditions or stop-loss/take-profit
  |   +- No  -> Continue
  +- Max drawdown > -30%?
  |   +- Yes -> Add risk controls (stop-loss, position sizing)
  |   +- No  -> Continue
  +- Alpha < 0?
  |   +- Yes -> Strategy has no excess return; needs redesign
  |   +- No  -> Continue
  +- All checks passed -> Proceed to out-of-sample testing

Reference Resources