Implementing Low Volatility Factor and Betting Against Beta in QuantConnect

You can implement low volatility factor and betting against beta strategies on QuantConnect by inheriting from QCAlgorithm, using coarse-to-fine universe selection, and calculating weekly volatility or covariance-based beta in the FineSelectionFunction before rebalancing monthly.

The awesome-systematic-trading repository provides research-grade implementations of these classic equity factors for QuantConnect's Lean engine. Both strategies follow a monthly rebalancing schedule and demonstrate how to combine fundamental data with statistical factor calculations in an event-driven backtesting framework.

Strategy Architecture Overview

Both implementations share a common pipeline architecture that leverages QuantConnect's universe selection API:

Component Purpose Implementation Details
Algorithm class Inherit from QCAlgorithm and drive the back-test. LowVolatilityFactorEffectStocks and BettingAgainstBetaFactorinStocks (see source files).
Universe selection Daily coarse selection → monthly fine selection. AddUniverse(self.CoarseSelectionFunction, self.FineSelectionFunction) with a scheduled Selection call (DateRules.MonthEnd / DateRules.MonthStart).
Price rolling windows Store recent daily prices for volatility or beta calculation. Custom SymbolData (low-vol) or RollingWindow[float] (BAB).
Factor computation Compute weekly volatility (np.std of weekly returns) or beta (cov/var). Implemented in the FineSelectionFunction.
Portfolio construction Select top-/bottom-ranked symbols, compute leverage, and rebalance. Long-only in low-vol; long + short with leverage caps in BAB.
Execution Convert factor weights to holdings each rebalancing date. OnData checks selection_flag then issues SetHoldings/Liquidate.
Fee model Apply a simple linear transaction fee (0.005 % of trade value). CustomFeeModel class used for all added securities.

Low Volatility Factor Implementation

The low volatility strategy ranks stocks by recent weekly volatility and goes long the least volatile quartile. According to the source code in static/strategies/low-volatility-factor-effect-in-stocks.py, the implementation follows a three-stage selection process.

Universe Selection and SymbolData

The CoarseSelectionFunction filters for US equities with fundamental data, warming up a custom SymbolData rolling window for each candidate. This helper class maintains a RollingWindow of daily closing prices used to compute weekly returns.

class SymbolData:
    def __init__(self, symbol):
        self.symbol = symbol
        self.price = RollingWindow[float](5)  # Store 5 days for weekly volatility

    
    def update(self, value):
        self.price.Add(value)

Volatility Calculation and Rebalancing

In the FineSelectionFunction, the algorithm reconstructs weekly prices from the rolling window, calculates weekly returns, and computes volatility using np.std. It sorts the universe by volatility and selects the bottom quartile (lowest volatility) into self.long.

def FineSelectionFunction(self, fine):
    # Weekly volatility calculation

    volatilities = {}
    for stock in fine:
        if stock.Symbol in self.data and self.data[stock.Symbol].price.IsReady:
            prices = [x for x in self.data[stock.Symbol].price]
            weekly_returns = [(prices[i] - prices[i+1]) / prices[i+1] for i in range(4)]
            volatilities[stock.Symbol] = np.std(weekly_returns)
    
    # Select lowest volatility quartile

    sorted_by_vol = sorted(volatilities.items(), key=lambda x: x[1])
    self.long = [x[0] for x in sorted_by_vol[:len(sorted_by_vol)//4]]
    return self.long

The OnData method liquidates positions that fall out of the quartile and equally weights the remaining symbols.

Betting Against Beta Implementation

The betting against beta (BAB) strategy creates a zero-beta portfolio by going long low-beta stocks and short high-beta stocks. The implementation in static/strategies/betting-against-beta-factor-in-stocks.py uses SPY as the market proxy.

Beta Calculation Against SPY

The FineSelectionFunction computes each stock's beta against SPY using the covariance-variance formula. It gathers the 1,000 most liquid US stocks with price > $5 during coarse selection, storing daily prices in a RollingWindow[float].

def FineSelectionFunction(self, fine):
    # Calculate market (SPY) returns variance

    spy_prices = [x for x in self.spy_data]
    spy_returns = [(spy_prices[i] - spy_prices[i+1]) / spy_prices[i+1] 
                   for i in range(len(spy_prices)-1)]
    spy_var = np.var(spy_returns)
    
    betas = {}
    for stock in fine:
        if stock.Symbol in self.data and self.data[stock.Symbol].IsReady:
            prices = [x for x in self.data[stock.Symbol]]
            stock_returns = [(prices[i] - prices[i+1]) / prices[i+1] 
                           for i in range(len(prices)-1)]
            # Covariance with SPY / Variance of SPY

            covariance = np.cov(stock_returns, spy_returns)[0][1]
            betas[stock.Symbol] = covariance / spy_var

Zero-Beta Portfolio Construction

The algorithm sorts stocks by beta, creating a long decile (lowest beta) and short decile (highest beta). It calculates leverage for each side as the inverse of the mean beta, capped at self.leverage_cap = 2.


# Create long and short portfolios

sorted_by_beta = sorted(betas.items(), key=lambda x: x[1])
self.long = [x[0] for x in sorted_by_beta[:len(sorted_by_beta)//10]]
self.short = [x[0] for x in sorted_by_beta[-len(sorted_by_beta)//10:]]

# Calculate zero-beta leverage

self.long_lvg = min(1 / np.mean([betas[s] for s in self.long]), self.leverage_cap)
self.short_lvg = min(1 / np.mean([betas[s] for s in self.short]), self.leverage_cap)

In OnData, the strategy applies these leverage values to create dollar-neutral positions: long positions scaled by self.long_lvg and short positions scaled by self.short_lvg.

Custom Fee Model Configuration

Both strategies apply a CustomFeeModel to all securities, charging 0.005% of trade value (0.00005 multiplier) per transaction.

class CustomFeeModel(FeeModel):
    def GetOrderFee(self, parameters):
        fee = parameters.Security.Price * parameters.Order.AbsoluteQuantity * 0.00005
        return OrderFee(CashAmount(fee, "USD"))

# Applied during security addition

security.SetFeeModel(CustomFeeModel())

Summary

  • Low volatility factor and betting against beta are implemented in static/strategies/low-volatility-factor-effect-in-stocks.py and static/strategies/betting-against-beta-factor-in-stocks.py respectively.
  • Both strategies use coarse-to-fine universe selection with monthly rebalancing via DateRules.MonthEnd or DateRules.MonthStart.
  • The low volatility strategy calculates weekly volatility using np.std on 5-day rolling windows and goes long the lowest quartile.
  • The BAB strategy computes beta against SPY using cov/var, constructs long/short deciles, and applies inverse-beta leverage capped at 2x to achieve zero beta exposure.
  • A custom fee model applies 0.005% transaction costs to all trades in both implementations.

Frequently Asked Questions

How does the low volatility strategy calculate weekly volatility?

The strategy stores daily closing prices in a RollingWindow[float] (5 days) within the SymbolData class. During FineSelectionFunction, it reconstructs weekly returns from these prices and calculates volatility using np.std of the weekly return series, selecting the lowest quartile for the long portfolio.

What leverage constraints apply to the betting against beta strategy?

The BAB strategy calculates leverage for each side as the inverse of the mean beta of that side's portfolio (long or short), then applies a hard cap of self.leverage_cap = 2. This ensures the zero-beta portfolio construction remains within 2x leverage limits while maintaining market neutrality.

Can these strategies run standalone without additional alpha models?

Yes. Both LowVolatilityFactorEffectStocks and BettingAgainstBetaFactorinStocks are self-contained implementations that handle universe selection, factor calculation, and execution logic internally. You can run them directly by instantiating the classes within your QCAlgorithm without adding separate alpha models, as demonstrated in the code examples above.

Where does the beta calculation get its market proxy data?

The betting against beta implementation uses SPY (SPDR S&P 500 ETF) as the market proxy. The algorithm maintains a separate rolling window of SPY prices and calculates the market variance and covariance with individual stocks to derive the beta coefficient using the standard beta = cov(stock, market) / var(market) formula.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →