Key Concepts
Behavioral Finance
Introduction
Behavioral finance studies how cognitive biases and emotional responses cause investors to make irrational decisions. These systematic errors create persistent market inefficiencies that algorithmic trading strategies can exploit. Understanding the behavioral "why" behind a strategy helps you design better hypotheses, avoid overfitting to noise, and recognize when an edge might disappear.
Traditional finance assumes that market participants act rationally and that prices reflect all available information. In practice, investors consistently deviate from rationality in predictable ways. They follow the crowd, anchor to irrelevant reference points, overreact to recent news, and avoid realizing losses. These patterns repeat across markets and time periods because they stem from deep-seated cognitive tendencies.
Algorithmic trading is well suited to exploit behavioral biases because algorithms execute rules without emotion. However, the algo trader who designs the rules is still susceptible to the same biases. Knowing which biases drive your strategy and which biases threaten your decision-making process makes you a more effective quant.
This section covers four strategy families and the biases behind them:
- Momentum - Assets that have performed well recently tend to continue performing well. Exploits herd mentality, anchoring bias, and confirmation bias.
- Mean Reversion - Assets that deviate from historical averages tend to revert. Exploits overreaction, the disposition effect, and availability bias.
- Value Factor - Undervalued assets tend to appreciate as the market corrects its pessimism. Exploits loss aversion, recency bias, and status quo bias.
- Quality Factor - High-quality companies tend to outperform because investors chase exciting stories instead. Exploits narrative fallacy, overconfidence, and the endowment effect.
The section also includes a reference page covering the 12 most dangerous investment biases and example algorithms for each strategy type.
Momentum
Momentum strategies buy assets that have performed well recently and sell assets that have performed poorly. The core observation is that recent winners tend to keep winning and recent losers tend to keep losing over intermediate time horizons (3 to 12 months). This effect has been documented across equities, bonds, commodities, and currencies.
Behavioral biases that create momentum
Several cognitive biases cause momentum to persist in financial markets. Herd mentality amplifies trends as investors follow the crowd into rising assets. Anchoring bias causes slow adjustment to new information, creating gradual price drifts that momentum captures. Confirmation bias reinforces trends as investors seek information that supports their existing positions and dismiss contradictory signals.
Algorithmic approach
A typical cross-sectional momentum algorithm follows these steps:
- Define a universe of assets (for example, US equities with sufficient liquidity).
- Rank each asset by its trailing return over a lookback period (commonly 12 months with a 1-month skip to avoid short-term reversal).
- Go long the top decile of performers. Optionally, go short the bottom decile.
- Rebalance the portfolio monthly or at a fixed interval.
The 1-month skip at the end of the lookback period is important because very short-term returns (1 month or less) tend to reverse rather than continue. This short-term reversal is a separate effect driven by liquidity and microstructure, not the behavioral biases that drive intermediate-term momentum.
Biases that threaten momentum traders
While momentum strategies exploit behavioral biases in others, the algo trader designing and managing the strategy is susceptible to several biases:
- Herd mentality - Piling into popular momentum strategies when they are already crowded increases exposure to momentum crashes.
- Anchoring - Anchoring to a recent period of strong momentum returns can cause you to over-allocate to the strategy.
- Overconfidence - Holding momentum positions too long because you believe the trend will continue beyond what the evidence supports.
Mean Reversion
Mean reversion strategies bet that assets which have deviated significantly from their historical average will revert back. When a price moves far above or below its mean, the strategy takes a contrarian position and profits when the price returns to normal levels. Mean reversion operates on shorter time horizons than momentum, typically days to weeks.
Behavioral biases that create reversion opportunities
Several cognitive biases cause prices to overshoot and then revert. Recency bias causes investors to overweight recent events and extrapolate short-term trends, creating overreactions that correct over time. The disposition effect drives investors to sell winners too early and hold losers too long, creating predictable reversion patterns. Availability bias causes dramatic events to receive outsized attention, producing temporary mispricings that fade as investors process the actual impact.
Algorithmic approach
A typical mean reversion algorithm follows these steps:
- Select one or more assets and compute a measure of deviation from the mean. Common measures include z-scores, Bollinger Bands, and RSI.
- When the deviation exceeds a threshold (for example, price drops below the lower Bollinger Band), enter a contrarian position.
- Exit the position when the price reverts to the mean or crosses a take-profit level.
- Use stop-losses to protect against cases where the deviation reflects a genuine regime change rather than a temporary overreaction.
Pairs trading is a common mean reversion variant. You identify two correlated assets, monitor the spread between them, and trade the convergence when the spread widens beyond its historical norm.
Biases that threaten mean reversion traders
Mean reversion traders face their own behavioral pitfalls:
- Recency bias - Extrapolating a recent deviation as the "new normal" instead of recognizing it as a temporary overreaction. This can cause you to exit too early or avoid entering at all.
- Overconfidence - Believing a deviation will revert when it actually reflects a fundamental shift. Not every drop is a buying opportunity; sometimes the new price is the correct price.
Value Factor
Value strategies buy assets that appear undervalued relative to their fundamentals and sell assets that appear overvalued. The value premium is one of the most studied anomalies in finance: stocks with low price-to-earnings ratios, low price-to-book ratios, or high dividend yields have historically outperformed their expensive peers over long horizons.
Behavioral biases that create value opportunities
Several cognitive biases cause the market to misprice value stocks. Loss aversion causes investors to dump declining stocks to avoid further pain, pushing prices below intrinsic value. Recency bias leads investors to overweight recent poor performance and project it into the future, treating temporarily struggling companies as permanently impaired. Status quo bias makes investors reluctant to buy unfamiliar or out-of-favor stocks, leaving undervalued stocks cheap for longer.
Algorithmic approach
A typical value algorithm follows these steps:
- Define a universe of equities with sufficient liquidity and market capitalization.
- Screen for value using fundamental data: low P/E ratio, low price-to-book ratio, high dividend yield, or a composite of multiple value factors.
- Rank the universe by the value metric and select the most undervalued stocks.
- Construct an equal-weight or value-weight portfolio of the selected stocks.
- Rebalance quarterly to allow time for the value thesis to play out while keeping the portfolio current.
Value strategies require patience because undervalued stocks can remain cheap for extended periods. The quarterly rebalance frequency reflects this longer time horizon compared to momentum or mean reversion strategies.
Biases that threaten value traders
Value investors face specific behavioral challenges:
- Loss aversion - Value stocks often continue to decline after you buy them. The same bias that creates the opportunity also makes it difficult to hold positions through drawdowns.
- Narrative fallacy - Requiring a compelling "story" for why a cheap stock will recover. Sometimes the quantitative signal is sufficient, and waiting for a narrative can cause you to miss the opportunity.
Quality Factor
Quality factor strategies invest in companies with strong fundamentals: high profitability, stable earnings, low financial leverage, and consistent growth. These "boring but profitable" companies tend to outperform over time, despite receiving less attention than high-growth or turnaround stories.
Behavioral biases that create the quality premium
Several cognitive biases cause investors to undervalue quality stocks. The narrative fallacy draws investors toward stocks with exciting stories while steady, predictable businesses get ignored. Overconfidence bias leads investors to take concentrated bets on speculative stocks while overlooking quality companies with proven track records. The endowment effect makes investors reluctant to sell overvalued glamour stocks and rotate into higher-quality alternatives.
Algorithmic approach
A typical quality factor algorithm follows these steps:
- Define a universe of equities with sufficient liquidity and market capitalization.
- Screen for quality using fundamental metrics: high return on equity (ROE), low debt-to-equity ratio, stable or growing earnings, and strong gross margins.
- Rank the universe by a composite quality score that combines multiple metrics.
- Construct a portfolio of the highest-ranked quality stocks.
- Rebalance quarterly as new fundamental data becomes available.
Quality strategies pair well with value strategies because they address a common criticism of pure value investing: buying cheap stocks that are cheap for good reason (value traps). Adding a quality filter helps you avoid deteriorating businesses and focus on undervalued companies with strong fundamentals.
Biases that threaten quality traders
Quality factor investors face their own behavioral challenges:
- Narrative fallacy - Even algo traders can be tempted to override quality signals in favor of an exciting growth story. Trust the quantitative metrics over qualitative narratives.
- Overconfidence - Believing that a high-risk, high-reward bet will pay off better than a diversified portfolio of quality companies. The quality premium is modest but consistent, and chasing larger returns often destroys value.
Dangerous Investment Biases
The following 12 cognitive biases are among the most dangerous for investors. Each bias creates exploitable market inefficiencies, but each can also undermine your own trading decisions. For every bias, consider two questions: how can your algorithm exploit this bias in others, and how can you prevent this bias from affecting your algorithm design?
1. Anchoring bias
Anchoring is the tendency to over-rely on the first piece of information you encounter. Investors anchor to purchase prices, 52-week highs, analyst price targets, or round numbers. This causes them to adjust too slowly when new information changes an asset's fair value.
Susceptible strategies: Momentum (slow adjustment creates the trends that momentum exploits), Value Factor (anchoring to past prices instead of current intrinsic value).
Algorithmic antidote: Use relative metrics (percentile ranks, z-scores) instead of absolute price levels. Recompute signals from scratch at each rebalance rather than adjusting from previous values.
2. Availability bias
Availability bias is the tendency to overweight information that is easy to recall, usually because it is recent, dramatic, or emotionally charged. A vivid market crash receives more mental weight than a slow, steady recovery. This causes investors to overreact to dramatic events and create temporary mispricings.
Susceptible strategies: Mean Reversion (dramatic events cause temporary mispricings that revert as emotions fade).
Algorithmic antidote: Base decisions on systematic data analysis rather than salient events. Weight all data points equally unless you have a quantitative reason to do otherwise.
3. Confirmation bias
Confirmation bias is the tendency to seek information that supports your existing beliefs and ignore information that contradicts them. An investor who is bullish on a stock will focus on positive news and dismiss negative signals. This selective processing reinforces trends and delays corrections.
Susceptible strategies: Momentum (reinforces the belief that trends will continue), All types (ignoring disconfirming signals in backtests).
Algorithmic antidote: Validate strategies with out-of-sample data and stress tests. Actively look for conditions where your strategy fails. If you can't find scenarios where it loses money, your testing process likely has confirmation bias built into it.
4. Disposition effect
The disposition effect is the tendency to sell winning investments and hold losing ones. Investors want to realize gains (which feels good) and avoid realizing losses (which feels bad). This creates predictable patterns: winning stocks face selling pressure and losing stocks face holding pressure, both of which eventually correct.
Susceptible strategies: Mean Reversion (disposition-driven selling and holding creates reversion patterns), Momentum (undermines trend following by encouraging premature profit-taking).
Algorithmic antidote: Remove your entry price from the decision process entirely. Use trailing stops or signal-based exits rather than profit targets tied to your cost basis.
5. Endowment effect
The endowment effect is the tendency to value something more simply because you own it. Investors demand a higher price to sell a stock they own than they would pay to buy the same stock. This creates inertia in portfolios and causes investors to hold overvalued positions longer than they should.
Susceptible strategies: Quality Factor (overvaluing flashy holdings over fundamentally strong alternatives), Value Factor (refusing to sell a holding that has become overvalued).
Algorithmic antidote: Rebalance on a fixed schedule using objective ranking criteria. An algorithm that ranks the entire universe at each rebalance does not "remember" what it owns and is immune to the endowment effect.
6. Herd mentality
Herd mentality is the tendency to follow the crowd rather than think independently. When investors see others buying, they buy. When they see others selling, they sell. This collective behavior amplifies price movements beyond what fundamentals justify and creates the trends that momentum strategies exploit.
Susceptible strategies: Momentum (crowd behavior amplifies trends that the strategy rides).
Algorithmic antidote: Monitor crowding metrics and position concentration. Reduce exposure when too many market participants are on the same side of a trade. Momentum crashes often occur when a crowded trade reverses suddenly.
7. Loss aversion
Losses feel approximately twice as painful as equivalent gains feel pleasurable. This asymmetry causes investors to hold losing positions too long (hoping to break even) and sell winning positions too early (locking in gains). Loss aversion creates undervaluation in declining stocks and suppresses returns in rising stocks.
Susceptible strategies: Value Factor (difficulty holding underperformers through drawdowns), Momentum (cutting winners short instead of letting them run).
Algorithmic antidote: Define exit rules based on quantitative signals, not profit or loss relative to your entry price. Your algorithm should not know or care about its purchase price when making sell decisions.
8. Narrative fallacy
The narrative fallacy is the tendency to create compelling stories to explain random events. Investors prefer stocks with an exciting narrative (a disruptive technology, a charismatic founder) over stocks with strong but boring fundamentals. This preference channels capital toward glamour stocks and away from quality and value stocks.
Susceptible strategies: Quality Factor (investors prefer exciting stories over fundamentals), Value Factor (requiring a "story" to justify buying an undervalued stock instead of trusting the data).
Algorithmic antidote: Let quantitative signals drive decisions. If your algorithm requires a human-readable narrative to justify every trade, you are introducing narrative bias into a systematic process.
9. Overconfidence bias
Overconfidence is the tendency to overestimate your knowledge, ability, or the precision of your predictions. Overconfident investors trade too frequently, take concentrated positions, and underestimate risk. In algo trading, overconfidence manifests as overfitting to historical data and deploying strategies with insufficient out-of-sample validation.
Susceptible strategies: All types (overtrading, over-concentration, and insufficient diversification).
Algorithmic antidote: Use conservative position sizing. Require a strategy to pass multiple independent tests before deploying capital. Track your prediction accuracy honestly and adjust your confidence accordingly.
10. Recency bias
Recency bias is the tendency to overweight recent events when making decisions. Investors extrapolate recent trends, assuming that what happened last quarter will continue next quarter. This causes overreaction to short-term news and creates mispricings that revert over time.
Susceptible strategies: Mean Reversion (overreaction creates the mean-reversion opportunities), Value Factor (recent poor performance causes investors to undervalue fundamentally sound companies).
Algorithmic antidote: Use longer lookback periods that incorporate multiple market regimes. Evaluate strategy performance across full market cycles, not just recent periods.
11. Status quo bias
Status quo bias is the preference for the current state of affairs. Changing your portfolio feels risky, even when the evidence supports a change. Investors stick with familiar holdings and avoid unfamiliar opportunities, which delays the correction of mispricings.
Susceptible strategies: Value Factor (reluctance to buy out-of-favor or unfamiliar stocks).
Algorithmic antidote: Automate rebalancing so that portfolio changes happen systematically. An algorithm that executes a defined rebalancing rule does not experience the discomfort of change.
12. Sunk cost fallacy
The sunk cost fallacy is the tendency to continue investing in something because of the resources already committed, even when continuing is irrational. Investors double down on losing positions because they have "already invested so much" rather than evaluating the position on its current merits.
Susceptible strategies: Value Factor (averaging down on a stock when the original value thesis is broken).
Algorithmic antidote: Evaluate every position at every rebalance as if you were building the portfolio from scratch. The question is not "should I hold this?" but "would I buy this today at this price?"
Examples
The following examples demonstrate algorithmic strategies that exploit behavioral biases. Each example targets a different strategy family and the biases that create its edge.
Example 1: Cross-Sectional Momentum Strategy
The following algorithm exploits herd mentality and anchoring bias by going long equities with the strongest trailing 12-month returns (skipping the most recent month to avoid short-term reversal). It rebalances monthly using a fundamental universe to identify the top momentum stocks.
public class MomentumBiasAlgorithm : QCAlgorithm
{
private List<Symbol> _selected = new();
public override void Initialize()
{
SetStartDate(2024, 9, 1);
SetEndDate(2024, 12, 31);
SetCash(100000);
// Use a universe of liquid US equities.
UniverseSettings.Resolution = Resolution.Daily;
AddUniverse(FundamentalSelection);
// Rebalance on the first trading day of each month.
Schedule.On(
DateRules.MonthStart(),
TimeRules.AfterMarketOpen("SPY", 30),
Rebalance
);
}
private IEnumerable<Symbol> FundamentalSelection(IEnumerable<Fundamental> fundamental)
{
// Filter for liquid equities with prices above $5.
return fundamental
.Where(x => x.HasFundamentalData && x.Price > 5m && x.DollarVolume > 10000000)
.OrderByDescending(x => x.DollarVolume)
.Take(500)
.Select(x => x.Symbol);
}
private void Rebalance()
{
// Calculate trailing 12-month return with a 1-month skip for each security.
var momentum = new Dictionary<Symbol, double>();
foreach (var security in ActiveSecurities.Values)
{
// Request 13 months of history: 12-month return skipping the most recent month.
var history = History<TradeBar>(security.Symbol, 280, Resolution.Daily).ToList();
if (history.Count < 252) continue;
// Skip the most recent 21 trading days (~1 month) to avoid short-term reversal.
var endPrice = history[history.Count - 22].Close;
var startPrice = history[0].Close;
if (startPrice == 0) continue;
momentum[security.Symbol] = (double)(endPrice / startPrice - 1m);
}
if (momentum.Count == 0) return;
// Select the top 10% of momentum stocks.
var topCount = Math.Max(1, momentum.Count / 10);
_selected = momentum
.OrderByDescending(x => x.Value)
.Take(topCount)
.Select(x => x.Key)
.ToList();
// Equal-weight the portfolio across the selected stocks.
var weight = 1m / _selected.Count();
foreach (var symbol in _selected)
{
SetHoldings(symbol, weight);
}
// Liquidate positions no longer in the selection.
foreach (var holding in Portfolio.Values)
{
if (holding.Invested && !_selected.Contains(holding.Symbol))
{
Liquidate(holding.Symbol);
}
}
}
} class MomentumBiasAlgorithm(QCAlgorithm):
def initialize(self) -> None:
self.set_start_date(2024, 9, 1)
self.set_end_date(2024, 12, 31)
self.set_cash(100000)
# Use a universe of liquid US equities.
self.universe_settings.resolution = Resolution.DAILY
self.add_universe(self.fundamental_selection)
# Rebalance on the first trading day of each month.
self.schedule.on(
self.date_rules.month_start(),
self.time_rules.after_market_open("SPY", 30),
self.rebalance
)
self.selected = []
def fundamental_selection(self, fundamental: list[Fundamental]) -> list[Symbol]:
# Filter for liquid equities with prices above $5.
filtered = [x for x in fundamental if x.has_fundamental_data and x.price > 5 and x.dollar_volume > 10000000]
sorted_by_volume = sorted(filtered, key=lambda x: x.dollar_volume, reverse=True)[:500]
return [x.symbol for x in sorted_by_volume]
def rebalance(self) -> None:
# Calculate trailing 12-month return with a 1-month skip for each security.
momentum = {}
for symbol, security in self.active_securities.items():
# Request 13 months of history: 12-month return skipping the most recent month.
history = self.history[TradeBar](symbol, 280, Resolution.DAILY)
history_list = list(history)
if len(history_list) < 252:
continue
# Skip the most recent 21 trading days (~1 month) to avoid short-term reversal.
end_price = history_list[-22].close
start_price = history_list[0].close
if start_price == 0:
continue
momentum[symbol] = float(end_price / start_price - 1)
if not momentum:
return
# Select the top 10% of momentum stocks.
top_count = max(1, len(momentum) // 10)
sorted_momentum = sorted(momentum.items(), key=lambda x: x[1], reverse=True)[:top_count]
self.selected = [x[0] for x in sorted_momentum]
# Equal-weight the portfolio across the selected stocks.
weight = 1.0 / len(self.selected)
for symbol in self.selected:
self.set_holdings(symbol, weight)
# Liquidate positions no longer in the selection.
for holding in self.portfolio.values():
if holding.invested and holding.symbol not in self.selected:
self.liquidate(holding.symbol)
Example 2: Bollinger Band Mean Reversion Strategy
The following algorithm exploits overreaction and the disposition effect. It uses Bollinger Bands on SPY to identify overbought and oversold conditions. When the price drops below the lower band, the algorithm enters a long position betting on reversion to the mean. It exits when the price returns to the middle band.
public class MeanReversionBiasAlgorithm : QCAlgorithm
{
private Symbol _spy;
private BollingerBands _bb;
public override void Initialize()
{
SetStartDate(2024, 9, 1);
SetEndDate(2024, 12, 31);
SetCash(100000);
// Subscribe to SPY daily data.
_spy = AddEquity("SPY", Resolution.Daily).Symbol;
// Create a 20-day Bollinger Band with 2 standard deviations.
_bb = BB(_spy, 20, 2m, MovingAverageType.Simple, Resolution.Daily);
// Warm up the indicator with historical data.
SetWarmUp(20, Resolution.Daily);
}
public override void OnData(Slice slice)
{
if (IsWarmingUp || !_bb.IsReady) return;
if (!slice.Bars.ContainsKey(_spy)) return;
var price = slice.Bars[_spy].Close;
if (!Portfolio[_spy].Invested)
{
// Enter long when price drops below the lower Bollinger Band (oversold / overreaction).
if (price < _bb.LowerBand.Current.Value)
{
SetHoldings(_spy, 1m, tag: $"Enter long at {price:F2} (lower band: {_bb.LowerBand.Current.Value:F2})");
}
}
else if (Portfolio[_spy].IsLong)
{
// Exit when price reverts to the middle band (mean).
if (price >= _bb.MiddleBand.Current.Value)
{
Liquidate(_spy, tag: $"Exit at {price:F2} (middle band: {_bb.MiddleBand.Current.Value:F2})");
}
}
}
} class MeanReversionBiasAlgorithm(QCAlgorithm):
def initialize(self) -> None:
self.set_start_date(2024, 9, 1)
self.set_end_date(2024, 12, 31)
self.set_cash(100000)
# Subscribe to SPY daily data.
self.spy = self.add_equity("SPY", Resolution.DAILY).symbol
# Create a 20-day Bollinger Band with 2 standard deviations.
self.bollinger = self.bb(self.spy, 20, 2, MovingAverageType.SIMPLE, Resolution.DAILY)
# Warm up the indicator with historical data.
self.set_warm_up(20, Resolution.DAILY)
def on_data(self, slice: Slice) -> None:
if self.is_warming_up or not self.bollinger.is_ready:
return
if self.spy not in slice.bars:
return
price = slice.bars[self.spy].close
if not self.portfolio[self.spy].invested:
# Enter long when price drops below the lower Bollinger Band (oversold / overreaction).
if price < self.bollinger.lower_band.current.value:
self.set_holdings(self.spy, 1, tag=f"Enter long at {price:.2f} (lower band: {self.bollinger.lower_band.current.value:.2f})")
elif self.portfolio[self.spy].is_long:
# Exit when price reverts to the middle band (mean).
if price >= self.bollinger.middle_band.current.value:
self.liquidate(self.spy, tag=f"Exit at {price:.2f} (middle band: {self.bollinger.middle_band.current.value:.2f})")
Example 3: Fundamental Value Strategy
The following algorithm exploits loss aversion and recency bias by screening for undervalued equities using the price-to-earnings ratio. Investors dump stocks with recent poor performance, pushing prices below intrinsic value. The algorithm identifies these opportunities using fundamental data and rebalances quarterly.
public class ValueBiasAlgorithm : QCAlgorithm
{
private List<Symbol> _selected = new();
private int _lastRebalanceMonth = -1;
public override void Initialize()
{
SetStartDate(2024, 9, 1);
SetEndDate(2024, 12, 31);
SetCash(100000);
// Use a fundamental universe for value screening.
UniverseSettings.Resolution = Resolution.Daily;
AddUniverse(FundamentalSelection);
}
private IEnumerable<Symbol> FundamentalSelection(IEnumerable<Fundamental> fundamental)
{
// Only rebalance quarterly (January, April, July, October).
if (Time.Month != 1 && Time.Month != 4 && Time.Month != 7 && Time.Month != 10)
{
return Universe.Unchanged;
}
if (Time.Month == _lastRebalanceMonth) return Universe.Unchanged;
_lastRebalanceMonth = Time.Month;
// Screen for value: liquid stocks with positive earnings and low P/E ratios.
var withEarnings = fundamental
.Where(x => x.HasFundamentalData && x.Price > 5m && x.DollarVolume > 5000000
&& x.ValuationRatios.PERatio > 0 && x.ValuationRatios.PERatio < 50
&& x.MarketCap > 1000000000)
.ToList();
if (withEarnings.Count() == 0) return Enumerable.Empty<Symbol>();
// Select the 20 stocks with the lowest P/E ratio (most undervalued).
_selected = withEarnings
.OrderBy(x => x.ValuationRatios.PERatio)
.Take(20)
.Select(x => x.Symbol)
.ToList();
return _selected;
}
public override void OnSecuritiesChanged(SecurityChanges changes)
{
// Liquidate stocks removed from the universe.
foreach (var security in changes.RemovedSecurities)
{
if (security.Invested)
{
Liquidate(security.Symbol);
}
}
// Equal-weight the portfolio across selected value stocks.
if (_selected.Any())
{
var weight = 1m / _selected.Count();
foreach (var symbol in _selected)
{
SetHoldings(symbol, weight);
}
}
}
} class ValueBiasAlgorithm(QCAlgorithm):
def initialize(self) -> None:
self.set_start_date(2024, 9, 1)
self.set_end_date(2024, 12, 31)
self.set_cash(100000)
# Use a fundamental universe for value screening.
self.universe_settings.resolution = Resolution.DAILY
self.add_universe(self.fundamental_selection)
self.selected = []
self.last_rebalance_month = -1
def fundamental_selection(self, fundamental: list[Fundamental]) -> list[Symbol]:
# Only rebalance quarterly (January, April, July, October).
if self.time.month not in [1, 4, 7, 10]:
return Universe.UNCHANGED
if self.time.month == self.last_rebalance_month:
return Universe.UNCHANGED
self.last_rebalance_month = self.time.month
# Screen for value: liquid stocks with positive earnings and low P/E ratios.
with_earnings = [x for x in fundamental
if x.has_fundamental_data and x.price > 5 and x.dollar_volume > 5000000
and 0 < x.valuation_ratios.pe_ratio < 50
and x.market_cap > 1e9]
if not with_earnings:
return []
# Select the 20 stocks with the lowest P/E ratio (most undervalued).
sorted_by_pe = sorted(with_earnings, key=lambda x: x.valuation_ratios.pe_ratio)[:20]
self.selected = [x.symbol for x in sorted_by_pe]
return self.selected
def on_securities_changed(self, changes: SecurityChanges) -> None:
# Liquidate stocks removed from the universe.
for security in changes.removed_securities:
if security.invested:
self.liquidate(security.symbol)
# Equal-weight the portfolio across selected value stocks.
if self.selected:
weight = 1.0 / len(self.selected)
for symbol in self.selected:
self.set_holdings(symbol, weight)
Example 4: Quality Factor Strategy
The following algorithm exploits narrative fallacy and overconfidence by screening for high-quality companies that the market overlooks in favor of more exciting stories. It uses return on equity and debt-to-equity ratio as quality metrics, constructs an equal-weight portfolio, and rebalances quarterly.
public class QualityBiasAlgorithm : QCAlgorithm
{
private List<Symbol> _selected = new();
private int _lastRebalanceMonth = -1;
public override void Initialize()
{
SetStartDate(2024, 9, 1);
SetEndDate(2024, 12, 31);
SetCash(100000);
// Use a fundamental universe for quality screening.
UniverseSettings.Resolution = Resolution.Daily;
AddUniverse(FundamentalSelection);
}
private IEnumerable<Symbol> FundamentalSelection(IEnumerable<Fundamental> fundamental)
{
// Only rebalance quarterly (January, April, July, October).
if (Time.Month != 1 && Time.Month != 4 && Time.Month != 7 && Time.Month != 10)
{
return Universe.Unchanged;
}
if (Time.Month == _lastRebalanceMonth) return Universe.Unchanged;
_lastRebalanceMonth = Time.Month;
// Screen for quality: liquid stocks with high ROE and low debt-to-equity.
var quality = fundamental
.Where(x => x.HasFundamentalData && x.Price > 5m && x.DollarVolume > 5000000
&& x.OperationRatios.ROE.Value > 0
&& x.OperationRatios.TotalDebtEquityRatio.Value >= 0
&& x.OperationRatios.TotalDebtEquityRatio.Value < 1.5
&& x.MarketCap > 1000000000)
.ToList();
if (quality.Count() == 0) return Enumerable.Empty<Symbol>();
// Rank by ROE descending (higher ROE = higher quality).
_selected = quality
.OrderByDescending(x => x.OperationRatios.ROE.Value)
.Take(20)
.Select(x => x.Symbol)
.ToList();
return _selected;
}
public override void OnSecuritiesChanged(SecurityChanges changes)
{
// Liquidate stocks removed from the universe.
foreach (var security in changes.RemovedSecurities)
{
if (security.Invested)
{
Liquidate(security.Symbol);
}
}
// Equal-weight the portfolio across selected quality stocks.
if (_selected.Any())
{
var weight = 1m / _selected.Count();
foreach (var symbol in _selected)
{
SetHoldings(symbol, weight);
}
}
}
} class QualityBiasAlgorithm(QCAlgorithm):
def initialize(self) -> None:
self.set_start_date(2024, 9, 1)
self.set_end_date(2024, 12, 31)
self.set_cash(100000)
# Use a fundamental universe for quality screening.
self.universe_settings.resolution = Resolution.DAILY
self.add_universe(self.fundamental_selection)
self.selected = []
self.last_rebalance_month = -1
def fundamental_selection(self, fundamental: list[Fundamental]) -> list[Symbol]:
# Only rebalance quarterly (January, April, July, October).
if self.time.month not in [1, 4, 7, 10]:
return Universe.UNCHANGED
if self.time.month == self.last_rebalance_month:
return Universe.UNCHANGED
self.last_rebalance_month = self.time.month
# Screen for quality: liquid stocks with high ROE and low debt-to-equity.
quality = [x for x in fundamental
if x.has_fundamental_data and x.price > 5 and x.dollar_volume > 5000000
and x.operation_ratios.roe.value > 0
and 0 <= x.operation_ratios.total_debt_equity_ratio.value < 1.5
and x.market_cap > 1e9]
if not quality:
return []
# Rank by ROE descending (higher ROE = higher quality).
sorted_by_roe = sorted(quality, key=lambda x: x.operation_ratios.roe.value, reverse=True)[:20]
self.selected = [x.symbol for x in sorted_by_roe]
return self.selected
def on_securities_changed(self, changes: SecurityChanges) -> None:
# Liquidate stocks removed from the universe.
for security in changes.removed_securities:
if security.invested:
self.liquidate(security.symbol)
# Equal-weight the portfolio across selected quality stocks.
if self.selected:
weight = 1.0 / len(self.selected)
for symbol in self.selected:
self.set_holdings(symbol, weight)