equity.curiosta.com
Not investment advice. This is a personal learning log. The authors are not SEBI-registered investment advisers or research analysts. Nothing here is a recommendation to buy or sell any security. Intraday levels are published only after the session has closed, as a record of what was tested. The portfolio is simulated, and past results don't predict future ones. Do your own research.

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