PEG Strategy: Finding Undervalued Growth Stocks with Price/Earnings-to-Growth Ratio
Low P/E Doesn't Mean Cheap: Beware of Value Traps
Many investors get excited when they see a low P/E ratio, thinking they've found a bargain. But have you ever asked: Why is the market giving it such a low valuation?
The answer might be: the company is in decline.
A company with a P/E of just 5x isn't actually cheap if its revenue is shrinking and profits are declining year after year—the market is simply reflecting its dim future.
Conversely, a company with a P/E of 25x might be truly undervalued if its earnings are growing 50% annually.
This is why Peter Lynch introduced the PEG (Price/Earnings-to-Growth) ratio in his book "One Up On Wall Street":
PEG = P/E / Earnings Growth Rate
When PEG < 1, the stock price is cheap relative to its growth; PEG > 2 may indicate overvaluation.
Today we'll use data to verify: Does the PEG strategy really work in the Taiwan stock market? Through 4 rounds of iterative optimization, we found a strategy with 25.6% CAGR and only 37% maximum drawdown.
PEG Strategy Performance in Taiwan Stocks
Before diving into optimization, let's see how the basic PEG strategy performs.
Build a basic PEG strategy using FinLab: select the 10 stocks with the lowest PEG, rebalance monthly. Conditions: PEG between 0-2, positive operating profit growth, basic liquidity.
Show Code
from finlab import data
from finlab.backtest import sim
# Get data
pe = data.get("price_earning_ratio:本益比")
營業利益成長率 = data.get("fundamental_features:營業利益成長率")
當月營收 = data.get("monthly_revenue:當月營收")
volume = data.get("price:成交股數")
# Calculate PEG
peg = pe / 營業利益成長率
# Build conditions
cond = (
(peg > 0) & (peg < 2) & # Reasonable PEG range
(營業利益成長率 > 0) & # Ensure positive growth
(當月營收.average(3) / 當月營收.average(12) > 1.1) & # Revenue momentum
((volume / 1000).average(20) > 300) # Liquidity
)
# Select 10 stocks with lowest PEG
position = (peg * cond)
position = position[position > 0].is_smallest(10)
position = position.reindex(當月營收.index_str_to_date().index, method="ffill")
# Backtest
report = sim(position, resample="M", stop_loss=0.15, upload=False)
stats = report.get_stats()
print(f"CAGR: {stats['cagr']:.1%}")
print(f"Sharpe Ratio: {stats['monthly_sharpe']:.2f}")
print(f"Max Drawdown: {abs(stats['max_drawdown']):.1%}")Results:
| Metric | Value |
|---|---|
| CAGR | 9.7% |
| Sharpe Ratio | 0.40 |
| Max Drawdown | 50.5% |
The basic strategy does beat savings deposits, but has two obvious problems:
- Mediocre returns: 9.7% is only slightly better than the market
- Excessive drawdown: 50% max drawdown is hard for most people to stomach
This is what we need to optimize.
AI-Assisted Iterative Optimization: From 9.7% to 25.6%
Here's the core of this article: the complete process of 4 rounds of AI-assisted optimization.
I set three optimization targets:
| Metric | Target | Reason |
|---|---|---|
| CAGR | > 15% | Significantly outperform market |
| Sharpe Ratio | > 0.6 | Reasonable risk-adjusted return |
| Max Drawdown | < 40% | Tolerable for average investors |
Iteration 1: Exploring Parameter Space
Test different parameter combinations: stricter PEG thresholds, adding RSI momentum, different holding sizes, different rebalancing frequencies. Find which factors have the most impact on performance.
Show Code
# Test weekly rebalancing + RSI momentum filter
rsi = data.indicator("RSI", timeperiod=14)
roe = data.get("fundamental_features:ROE稅後")
cond_rsi = (
(peg > 0) & (peg < 1.5) &
(營業利益成長率 > 5) &
(當月營收.average(3) / 當月營收.average(12) > 1.1) &
((volume / 1000).average(20) > 500) &
(roe > 5) &
(pe > 5) & (pe < 30) &
(rsi > 50) # RSI momentum upward
)
position = (peg * cond_rsi)
position = position[position > 0].is_smallest(10)
position = position.reindex(當月營收.index_str_to_date().index, method="ffill")
# Weekly rebalancing
report = sim(position, resample="W", stop_loss=0.10, upload=False)Testing revealed an important phenomenon:
| Configuration | CAGR | Sharpe | MDD |
|---|---|---|---|
| Monthly (baseline) | 9.2% | 0.38 | 49.6% |
| Weekly + RSI | 28.8% | 0.83 | 54.8% |
Finding: Increasing rebalancing frequency caused CAGR to jump from 9.2% to 28.8%! But max drawdown also reached 54.8%, still exceeding our target.
Iteration 2: Attempting to Reduce Drawdown
Try various risk control methods to reduce drawdown: stricter stop-loss, trailing stop, take-profit.
Show Code
# Test different stop-loss/trailing stop configurations
configs = [
{"stop_loss": 0.10, "trail_stop": None}, # Baseline
{"stop_loss": 0.08, "trail_stop": None}, # Strict stop-loss
{"stop_loss": 0.10, "trail_stop": 0.05}, # Trailing stop
{"stop_loss": 0.10, "take_profit": 0.20}, # Take profit
]
for cfg in configs:
report = sim(position, resample="W", **cfg, upload=False)
# ... record resultsSurprisingly:
| Risk Control | CAGR | MDD | Conclusion |
|---|---|---|---|
| 10% stop-loss | 28.8% | 54.8% | Baseline |
| 8% stop-loss | 27.9% | 58.3% | MDD increased |
| 5% trailing | 14.1% | 63.2% | Much worse |
| 20% take-profit | 28.5% | 53.2% | Slight improvement |
Finding: Traditional stop-loss/trailing stop doesn't work for growth stock strategies! Because growth stocks fall together during systemic declines, stop-losses only lock in losses.
Iteration 3: Adjusting Strategy Structure
Since risk controls don't work, let's adjust the strategy structure itself.
Test different rebalancing frequencies (weekly, bi-weekly, monthly, quarterly) and holding sizes, observe impact on MDD.
Show Code
# Test bi-weekly rebalancing
report = sim(position, resample="2W", stop_loss=0.12, upload=False)| Frequency | CAGR | Sharpe | MDD |
|---|---|---|---|
| Weekly | 28.8% | 0.83 | 54.8% |
| Bi-weekly | 24.9% | 0.83 | 48.4% |
| Monthly | 16.0% | 0.57 | 55.0% |
| Quarterly | 25.6% | 0.93 | 37.0% |
Key Finding: Reducing rebalancing frequency effectively lowers MDD! Quarterly rebalancing has only 37% MDD while maintaining 25%+ returns.
Backtest Results
After 4 iterations, we found a strategy configuration that meets all targets.
| Metric | Basic | Optimized | Target | Status |
|---|---|---|---|---|
| CAGR | 9.7% | 25.6% | > 15% | ✅ |
| Sharpe Ratio | 0.40 | 0.93 | > 0.6 | ✅ |
| Max Drawdown | 50.5% | 37.0% | < 40% | ✅ |
Strategy Analysis: Why Does Quarterly Rebalancing Have the Lowest MDD?
This finding seems counterintuitive: reducing trading frequency actually reduces risk? Here's why:
- Avoid chasing highs and selling lows: High-frequency rebalancing tends to buy at peaks and sell at bottoms
- Reduce transaction costs: Frequent trading accumulates fees and slippage that erode returns
- Let winners run: Good stocks need time to develop; selling too early misses the big moves
- Filter short-term noise: Quarterly timeframe only considers medium-term trends, not daily fluctuations
Growth stock strategy MDD is hard to reduce with traditional risk controls—you must adjust the strategy structure itself.
Want to build your own strategy?
Describe your stock-picking ideas in natural language. AI automatically validates, backtests, and gives you answers
Start Free