52-Week Low Quality: 24-Year India Backtest (BSE, NSE)
52-Week Low Quality in India: 24-Year Backtest (BSE, NSE)
India's result looks reasonable on paper: 7.11% CAGR over 24 years, with $10k growing to $51,143. Then you see the Sharpe ratio: 0.023. That's barely above zero. The strategy generated returns, but the risk taken to generate them was enormous. Here's what happened and why.
Contents
Method
- Data source: Ceta Research (FMP financial data warehouse)
- Universe: BSE + NSE, market cap > ₹500M (~$6M USD local threshold)
- Period: 2002–2025 (95 quarters)
- Rebalancing: Quarterly (January, April, July, October), equal weight
- Max stocks: 30, minimum 5 to deploy capital
- Benchmark: S&P 500 Total Return (SPY)
- Cash rule: Hold cash if fewer than 5 stocks qualify
- Transaction costs: 0.1% per trade (one-way)
- Filing lag: 45-day lag on all fundamental data
Full methodology: backtests/METHODOLOGY.md
What We Found
| Metric | Portfolio | S&P 500 (SPY) |
|---|---|---|
| CAGR | 7.11% | 9.67% |
| Total Return | 411.43% | 789.45% |
| Max Drawdown | -59.85% | -45.53% |
| Volatility (ann.) | 26.7% | 16.63% |
| Sharpe Ratio | 0.023 | 0.431 |
| Sortino Ratio | 0.041 | — |
| Calmar Ratio | 0.119 | — |
| Beta | 0.673 | 1.00 |
| Alpha | -1.52% | — |
| Up Capture | 71.66% | — |
| Down Capture | 42.88% | — |
| Win Rate (vs SPY) | 48.42% | — |
| Cash Periods | 24/95 | — |
| Avg Stocks (when invested) | 24.2 | — |
The Sharpe of 0.023 is the headline number. SPY's Sharpe over the same period is 0.431. This strategy took on 26.7% annualized volatility, deeper drawdowns than the index (-59.85% vs -45.53%), and still underperformed by 2.55% annually.
The down-capture of 42.88% is genuinely good. When SPY falls, this portfolio tends to fall less than half as much. But the up-capture of 71.66% means it also captures only 72% of SPY's gains. When the index gains 30%, this typically gains 21%. That asymmetry, sustained over 24 years, creates a significant terminal wealth gap. $10k became $51,143 here vs $89,485 in SPY.

$10k invested in January 2002. India portfolio (blue) reaches $51,143 vs SPY (grey) $89,485 by December 2025.
Year-by-Year
| Year | Portfolio | S&P 500 | Excess |
|---|---|---|---|
| 2002 | 0% (cash) | -19.92% | — |
| 2003 | 0% (cash) | +24.12% | — |
| 2004 | 0% (cash) | +10.25% | — |
| 2005 | 0% (cash) | +7.21% | — |
| 2006 | +32.18% | +13.65% | +18.53% |
| 2007 | +57.27% | +4.40% | +52.87% |
| 2008 | -25.37% | -34.31% | +8.94% |
| 2009 | +52.42% | +24.73% | +27.69% |
| 2010 | +16.54% | +14.31% | +2.23% |
| 2011 | -38.67% | +2.46% | -41.13% |
| 2012 | +36.90% | +17.09% | +19.81% |
| 2013 | -4.87% | +27.77% | -32.64% |
| 2014 | +56.15% | +14.50% | +41.65% |
| 2015 | -8.43% | -0.07% | -8.36% |
| 2016 | +12.74% | +14.45% | -1.71% |
| 2017 | +24.37% | +21.64% | +2.73% |
| 2018 | -14.22% | -5.23% | -8.99% |
| 2019 | -16.28% | +32.31% | -48.59% |
| 2020 | +8.95% | +15.64% | -6.69% |
| 2021 | +31.47% | +31.33% | +0.14% |
| 2022 | +0.17% | -18.99% | +19.16% |
| 2023 | +18.99% | +26.00% | -7.01% |
| 2024 | +14.83% | +25.28% | -10.45% |
The range here is extreme. 2007: +57.27% when SPY returned +4.40%. 2014: +56.15% when SPY returned +14.50%. 2011: -38.67% when SPY returned +2.46%. 2019: -16.28% when SPY returned +32.31%. These aren't modest deviations. The strategy swings violently against the benchmark in both directions.

Year-by-year comparison. The large positive spikes (2007, 2009, 2014) are matched by significant negative years (2011, 2019). The variance is the story.
What the Data Is Telling You
The 24 cash periods (roughly six full years of inactivity) are the starting point. India's listed universe on BSE and NSE took time to develop enough qualifying stocks. In the early 2000s, fewer companies met both the proximity-to-52-week-low threshold and the Piotroski F-score >= 7 quality bar. The strategy sat in cash for the first four years of the backtest. BSE and NSE's structural maturation through the mid-2000s is the cause, not a parameter choice.
When the strategy does fire, the quality screen works as intended. The 2007 return of +57.27% and 2014 return of +56.15% show what happens when quality companies at 52-week lows get revalued. India's equity market was in strong growth phases in both years. Companies with Piotroski scores >= 7 that happened to be depressed at the start of those quarters participated fully in the re-rating.
The 2008 result (-25.37% vs SPY -34.31%) confirms the quality filter provides crash protection. Companies with strong balance sheets, positive cash flow, and improving fundamentals don't go to zero. They just get cheaper before recovering.
The two most damaging years are 2011 and 2019. In 2011, India's market was dealing with inflation, rate hikes, and currency pressure. BSE/NSE stocks at 52-week lows kept falling. In 2019, the opposite dynamic: US equities surged while India dealt with a domestic credit crisis and slowing GDP growth. The strategy was correctly identifying depressed quality stocks in India, but those stocks didn't recover while US markets ripped higher.
The down-capture of 42.88% is the genuine positive here. Across all years when SPY fell, this portfolio fell less than half as much. That's structural. Quality companies with low debt don't need to raise capital in downturns, they don't face the forced selling that hammers leveraged balance sheets, and their businesses continue generating cash. The problem is that this protection doesn't offset the cost of missing SPY's upside during growth years.
Part of a Series: Global | US | Switzerland | Hong Kong | Germany | China | Canada
Run It Yourself
-- 52-Week Low Quality Screen: BSE + NSE (India)
-- Stocks within 15% of 52-week low with Piotroski F-Score >= 7
-- Point-in-time with 45-day filing lag
WITH price_history AS (
SELECT
e.symbol,
e.date,
e.adjClose,
MIN(e.adjClose) OVER (
PARTITION BY e.symbol
ORDER BY e.date
ROWS BETWEEN 251 PRECEDING AND CURRENT ROW
) AS low_52wk
FROM read_parquet('/opt/insydia/data/data_source=fmp/tick_data/eod/*.parquet') e
JOIN read_parquet('/opt/insydia/data/data_source=fmp/company/profile/*.parquet') p
ON e.symbol = p.symbol
WHERE p.exchange IN ('BSE', 'NSE')
AND e.date <= CURRENT_DATE
AND e.date >= CURRENT_DATE - INTERVAL '400' DAY
),
latest_price AS (
SELECT symbol, adjClose AS current_price, low_52wk,
ROW_NUMBER() OVER (PARTITION BY symbol ORDER BY date DESC) AS rn
FROM price_history
WHERE date <= CURRENT_DATE
),
near_low AS (
SELECT symbol, current_price, low_52wk,
(current_price / low_52wk - 1) AS pct_above_low
FROM latest_price
WHERE rn = 1 AND low_52wk > 0
AND (current_price / low_52wk - 1) < 0.15
),
piotroski AS (
-- Profitability signals
SELECT
i.symbol,
-- F1: positive net income
CASE WHEN i.netIncome > 0 THEN 1 ELSE 0 END AS f1_net_income,
-- F2: positive operating cash flow
CASE WHEN c.operatingCashFlow > 0 THEN 1 ELSE 0 END AS f2_ocf,
-- F3: ROA improving
CASE WHEN (i.netIncome / NULLIF(b.totalAssets, 0)) >
LAG(i.netIncome / NULLIF(b.totalAssets, 0)) OVER (PARTITION BY i.symbol ORDER BY i.date)
THEN 1 ELSE 0 END AS f3_roa_improving,
-- F4: OCF > net income (accruals)
CASE WHEN c.operatingCashFlow > i.netIncome THEN 1 ELSE 0 END AS f4_accruals,
-- F5: long-term debt decreased
CASE WHEN (b.longTermDebt / NULLIF(b.totalAssets, 0)) <
LAG(b.longTermDebt / NULLIF(b.totalAssets, 0)) OVER (PARTITION BY b.symbol ORDER BY b.date)
THEN 1 ELSE 0 END AS f5_leverage,
-- F6: current ratio improved
CASE WHEN (b.totalCurrentAssets / NULLIF(b.totalCurrentLiabilities, 0)) >
LAG(b.totalCurrentAssets / NULLIF(b.totalCurrentLiabilities, 0)) OVER (PARTITION BY b.symbol ORDER BY b.date)
THEN 1 ELSE 0 END AS f6_liquidity,
-- F7: no dilution
CASE WHEN i.weightedAverageShsOut <=
LAG(i.weightedAverageShsOut) OVER (PARTITION BY i.symbol ORDER BY i.date)
THEN 1 ELSE 0 END AS f7_no_dilution,
-- F8: gross margin improved
CASE WHEN (i.grossProfit / NULLIF(i.revenue, 0)) >
LAG(i.grossProfit / NULLIF(i.revenue, 0)) OVER (PARTITION BY i.symbol ORDER BY i.date)
THEN 1 ELSE 0 END AS f8_gross_margin,
-- F9: asset turnover improved
CASE WHEN (i.revenue / NULLIF(b.totalAssets, 0)) >
LAG(i.revenue / NULLIF(b.totalAssets, 0)) OVER (PARTITION BY i.symbol ORDER BY i.date)
THEN 1 ELSE 0 END AS f9_asset_turnover
FROM read_parquet('/opt/insydia/data/data_source=fmp/statements/income_statement/*.parquet') i
JOIN read_parquet('/opt/insydia/data/data_source=fmp/statements/balance_sheet_statement/*.parquet') b
ON i.symbol = b.symbol AND i.date = b.date AND i.period = b.period
JOIN read_parquet('/opt/insydia/data/data_source=fmp/statements/cash_flow_statement/*.parquet') c
ON i.symbol = c.symbol AND i.date = c.date AND i.period = c.period
WHERE i.period = 'FY'
AND CAST(i.date AS DATE) <= CURRENT_DATE - INTERVAL '45' DAY
),
piotroski_scored AS (
SELECT symbol,
(f1_net_income + f2_ocf + f3_roa_improving + f4_accruals +
f5_leverage + f6_liquidity + f7_no_dilution + f8_gross_margin + f9_asset_turnover) AS fscore,
ROW_NUMBER() OVER (PARTITION BY symbol ORDER BY date DESC) AS rn
FROM piotroski
),
quality AS (
SELECT k.symbol, k.marketCap,
ROW_NUMBER() OVER (PARTITION BY k.symbol ORDER BY k.date DESC) AS rn
FROM read_parquet('/opt/insydia/data/data_source=fmp/statements/key_metrics/*.parquet') k
WHERE CAST(k.date AS DATE) <= CURRENT_DATE - INTERVAL '45' DAY
AND k.period = 'FY'
)
SELECT
n.symbol,
ROUND(n.current_price, 2) AS current_price,
ROUND(n.low_52wk, 2) AS low_52wk,
ROUND(n.pct_above_low * 100, 2) AS pct_above_low,
ps.fscore AS piotroski_score,
ROUND(q.marketCap / 1e6, 0) AS mktcap_mm
FROM near_low n
JOIN piotroski_scored ps ON n.symbol = ps.symbol AND ps.rn = 1
JOIN quality q ON n.symbol = q.symbol AND q.rn = 1
WHERE ps.fscore >= 7
AND q.marketCap > 50000000
ORDER BY n.pct_above_low ASC
LIMIT 30
Run this screen on Ceta Research →
Limitations
Cash periods are real performance. The 24 quarters of cash represent roughly six years of zero returns from the strategy. The early cash periods coincide with some of India's best market years, so the opportunity cost is real.
Currency mismatch. Returns are in INR. The benchmark is in USD. INR has depreciated materially against USD over this period. An international investor converting back to USD would see lower actual returns than shown here.
The Sharpe tells the truth. A Sharpe of 0.023 means near-zero risk-adjusted return. For the volatility taken (26.7%), the return generated (7.11%) doesn't justify the risk. A plain SPY allocation gave 9.67% at 16.63% volatility.
India's Sensex vs SPY comparison is imperfect. India's domestic benchmark outperformed SPY over this period. Measuring alpha against SPY understates what a local investor would consider outperformance. Against a BSE Sensex benchmark, results look different.
Concentration risk. With an average of 24.2 stocks when invested, and quarterly rebalancing, turnover is substantial. The 0.1% one-way cost assumption may understate slippage on less liquid BSE-listed mid-caps near 52-week lows.
Run It Yourself
Explore the data behind this analysis on Ceta Research. Query our financial data warehouse with SQL, build custom screens, and run your own backtests across 70,000+ stocks on 20 exchanges.
Data: Ceta Research (FMP financial data warehouse), 2002–2025. Full methodology: backtests/METHODOLOGY.md. Backtest code: backtests/52-week-low/. Past performance doesn't guarantee future results. Educational content only, not investment advice.