Strategy · backtested

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.

pyPSX· 13 July 2026· 1 min read· KSE-100· 2016–2026
CAGR
11.0%
Sharpe
0.74
Max drawdown
-28.0%
Win rate
58.0%

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.