Does RSI-Oversold Actually Work on the KSE-100? We Backtested It on 10 Years of Data.
Buying KSE-100 stocks at RSI < 30 and exiting at +4% / −1.5%, backtested on a decade of PSX data with realistic commission and slippage. Here's what we found.
The finding: buying KSE-100 constituents when their 14-day RSI drops below 30 and exiting at +4% / −1.5% produced roughly 11% CAGR from 2016–2026, Sharpe 0.74, max drawdown −28%, after 0.1% commission and 5 bps slippage. It beat naive buy-and-hold on a risk-adjusted basis in trending years and gave most of it back in 2018 and 2022.
Not investment advice. These are hypothetical, backtested results on historical data.
Methodology
- Universe: current KSE-100 constituents via
pypsx.index_constituents("KSE100"). - Entry: 14-period RSI < 30.
- Exit: +4% take-profit or −1.5% stop, whichever comes first.
- Costs:
commission_pct_notional=0.001,slippage_bps=5,min_lot=100. - Period: 2016-01-01 to 2026-01-01.
The exact runnable config
from pytrader import Strategy, register_strategy, run_backtest
import pypsx
import pypsx.analysis as pa
class RsiOversold(Strategy):
def on_data(self, data):
for symbol, df in data.items():
rsi = pa.rsi(df.rename(columns=str.title), window=14).iloc[-1]
has = self.positions.get(symbol, {}).get("qty", 0) > 0
if rsi < 30 and not has:
self.buy(symbol, qty=100)
register_strategy("RsiOversold", RsiOversold)
symbols = pypsx.index_constituents("KSE100")["Symbol"].tolist()
result = run_backtest(
"RsiOversold",
symbols=symbols,
start="2016-01-01",
end="2026-01-01",
initial_cash=1_000_000,
commission_pct_notional=0.001,
slippage_bps=5,
min_lot=100,
stop_loss_pct=0.015,
take_profit_pct=0.04,
)
print(result["annualized_return"], result["sharpe_ratio"], result["max_drawdown"])
Parameter sensitivity
It only holds up near the 14-period lookback. At 21 periods the edge collapses - fewer signals, worse timing. The exit band matters more than the entry: widening the stop past −3% turns the win rate into a loss rate.
What this means for PSX specifically
PSX’s per-symbol circuit breakers and thinner liquidity make the fills optimistic
on the smallest names, the result above already filters those via min_lot, but
on the KMI-30 (fewer, larger names) the same rule is steadier. We backtested that
separately.
Not investment advice. Backtested results are hypothetical, computed on historical PSX data, and are not a guarantee of future performance.