Code
The working scripts live in a private repository. These are the parts where the exact formula matters. They're plain Python 3 standard library, and they read Yahoo Finance's public daily and 15-minute chart data.
Trend gate and moving averages
A stock passes the trend gate when its last close is above its 50-day simple moving average and that average is higher than it was 5 sessions ago.
SLOPE_LB = 5 # sessions back used to judge a rising average
def sma(xs, n, end=None):
end = len(xs) if end is None else end
return sum(xs[end - n:end]) / n
d50 = sma(closes, 50)
above_50 = closes[-1] > d50
dma50_rising = d50 > sma(closes, 50, len(closes) - SLOPE_LB)
RSI (Wilder, 14)
def rsi_wilder(closes, n=14):
ch = [b - a for a, b in zip(closes, closes[1:])]
g = sum(max(c, 0) for c in ch[:n]) / n
l = sum(max(-c, 0) for c in ch[:n]) / n
for c in ch[n:]:
g = (g * (n - 1) + max(c, 0)) / n
l = (l * (n - 1) + max(-c, 0)) / n
return 100.0 if l == 0 else 100 - 100 / (1 + g / l)
Relative strength against the Nifty
The 20-session return of the stock minus that of the Nifty 50 (^NSEI), in percentage points.
r20 = (closes[-1] / closes[-21] - 1) * 100
rs20_pp = r20 - (nifty_close[last_date] / nifty_close[date_21_back] - 1) * 100
Opening-range noise
The median 09:15–09:45 high-to-low range over the last 20 completed sessions, compared with the stop distance. Above 1.0 means an ordinary opening swing is bigger than the stop.
NOISE_OR30_X_STOP = 1.0 # flag if median opening range > 1.0 x stop distance
NOISE_ATR_PCT = 2.5 # backup flag: daily ATR(14) > 2.5% of price
or30_med = median(opening_ranges[-20:]) # first two 15-minute bars of each day
or30_vs_stop = or30_med / (entry - stop)
noise_flag = atr14_pct > NOISE_ATR_PCT or or30_vs_stop > NOISE_OR30_X_STOP
Position size for a fixed rupee risk
The trader risks at most ₹150 per trade. The stop-loss order is placed ₹4 below the stop to make sure it fills, so that buffer counts as risk too.
shares = int(150 // (entry - (stop - 4)))
shares = min(shares, int(15000 // entry)) # notional cap