# Implementing Low Volatility Factor and Betting Against Beta in QuantConnect

> Implement low volatility and betting against beta strategies on QuantConnect. Learn to use coarse to fine universe selection and calculate weekly volatility or beta for monthly rebalancing.

- Repository: [Papers With Backtest/awesome-systematic-trading](https://github.com/paperswithbacktest/awesome-systematic-trading)
- Tags: how-to-guide
- Published: 2026-08-01

---

**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`](https://github.com/paperswithbacktest/awesome-systematic-trading/blob/main/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.

```python
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`.

```python
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`](https://github.com/paperswithbacktest/awesome-systematic-trading/blob/main/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]`.

```python
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`.

```python

# 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.

```python
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`](https://github.com/paperswithbacktest/awesome-systematic-trading/blob/main/static/strategies/low-volatility-factor-effect-in-stocks.py) and [`static/strategies/betting-against-beta-factor-in-stocks.py`](https://github.com/paperswithbacktest/awesome-systematic-trading/blob/main/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.