Skip to content

Latest commit

 

History

13 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

CPR Scanner

A two-phase Python scanner for Nifty 500 stocks using the Inside Breakout SRAEGY strategy based on CPR (Central Pivot Range) and Camarilla pivot points.

Quick Start

# Phase 1 — After market close, build today's watchlist
py cpr_scanner.py

# Phase 2 — After market open, monitor for breakouts (Ctrl+C to stop)
py cpr_breakout.py

Or with options:

py cpr_scanner.py --date 2026-08-17 --output data/watchlist.json
py cpr_breakout.py --watchlist data/watchlist.json --interval 5

Strategy Overview

Phase 1 — Pre-Market (after market close)

  1. Grabs the prior 3 trading days of daily OHLC for all 500 Nifty constituents via yfinance
  2. Computes CPR (Pivot/TC/BC) and Camarilla (H1–H4, L1–L4) levels
  3. Filters stocks using 6 conditions (see below) to produce a tight watchlist
  4. Saves filtered watchlist to data/watchlist.json

Phase 2 — Post-Market-Open

  1. Polls NSELive.stock_quote (via jugaad_data.nse.NSELive) every 5 seconds for live prices
  2. Builds 5-minute candles from tick volume
  3. Scans watchlist for signal/breakout candles (conditions 4–6)
  4. Alerts on valid entry with stop-loss and 1:1 / 1:2 targets (70% / 30% split)

Filter Rules

Phase 1 — should_include_in_watchlist(current_hlc, prior_hlc, weekly_hlc)bool

All six conditions below must pass for a stock to appear on the watchlist. Yesterday's OHLC is the "current" period; the day before is "prior". Weekly levels come from the prior Monday–Friday window.

1A — Current CPR width ≤ 0.15% of close

Goal: Find stocks with unusually tight consolidation. Math: width = abs(TC − BC), where BC = (H + L) / 2 and TC = 2×PP − BC. CPR% = width / close × 100. Rule: CPR% ≤ 0.15% (from config.CPR_WIDTH_THRESHOLD = 0.0015). Interpretation: A narrow CPR means the daily pivot range is compressed — buyers and sellers are roughly balanced at the same prices. This is the hallmark of a coiled spring: volatility is low and a breakout is imminent. Wide CPR (> 0.15%) means the stock is trending or choppy — not a setup.

1B — Current CPR width < prior CPR width

Goal: Confirm the consolidation is tightening, not static. Rule: cur_width < pri_width. Interpretation: If yesterday's CPR was narrow but today's is wider, the range is expanding (momentum building upward, but the entry is too late). Only stocks where the CPR is narrowing day-over-day are selected, signaling continued compression.

2A — Current Camarilla H3 < prior H3

Goal: Ensure the upper Camarilla resistance zone is contracting (moving down). Camarilla H3: Close + 0.275 × (H − L). Rule: cur_h3 < pri_h3. Interpretation: H3 is the "breakout pivot" level. When today's H3 is below yesterday's H3, it means the breakout threshold is descending — price is coiling tighter and the resistance ceiling is lowering. This creates a tighter "ceiling" the price must punch through.

2B — Current Camarilla L3 > prior L3

Goal: Ensure the lower Camarilla support zone is contracting upward. Camarilla L3: Close − 0.275 × (H − L). Rule: cur_l3 > pri_l3. Interpretation: When today's L3 is above yesterday's L3, the "floor" is rising. Combined with a falling H3, this confirms the Camarilla range (H3 to L3) is contracting from both ends — a textbook inside-day compression.

3 — Current Camarilla H4 ≥ prior CPR BC

Goal: Ensure the current day's Camarilla H4 (immediate ceiling) sits between or above yesterday's CPR range. Camarilla H4: Close + 0.55 × (H − L). Rule: cur_h4 >= pri_bc (i.e., not below the bottom of the CPR band). Interpretation: If H4 falls below yesterday's CPR BC, the stock is in a downtrending range (lower highs relative to yesterday's pivot). Requiring H4 ≥ BC means the current day's immediate ceiling sits at or above yesterday's CPR midpoint — the stock is consolidating, not weakening. H4 can sit anywhere from BC up — inside the CPR band (between BC and TC) or above TC (strong bullish).

6 — Weekly CPR is not fully above current CPR

Goal: Prevent selecting stocks where the weekly trend is decisively bullish, which could mean the "tight daily range" is just a rest within a larger up-move (less reliable breakout). Rule: If weekly_TC > current_TC and weekly_BC > current_BCskip the stock. Interpretation: When the weekly CPR band sits entirely above the daily CPR band, the stock is in a strong weekly uptrend. Daily compressions in a strong trend tend to resolve upward (good for breakout momentum, but not "Inside Breakout" setups which prefer range-bound consolidations). Only stocks where the weekly CPR intersects or overlaps the daily CPR are kept.


Phase 2 — check_breakout(signal_candle, breakout_candle, h4, prev_cpr_high, resting_low=None)dict

Conditions 4–6 are evaluated in real time during market hours. A "signal candle" is the first candle that closes above Camarilla H4; the "breakout candle" is the next candle that must confirm the breakout.

4 — Signal candle closes above H4

Goal: Confirm the compression has resolved upward. Rule: signal_candle.close > h4. Interpretation: The CPR/Camarilla narrowing (Phase 1) means the stock is coiled. A candle closing above H4 means price has broken out of the tight range. The closing price is used (not the high) to ensure sustained momentum, not just an intrabar spike.

5 — Breakout candle closes above max(signal.high, prev_cpr.tc)

Goal: Confirm the breakout has follow-through and cleared key resistance. Rule: breakout_candle.close > max(signal_candle.high, previous_day's_CPR_TC). Interpretation: prev_cpr.tc (yesterday's TC) is the prior day's upper pivot boundary. If the breakout candle closes above both the signal candle's high (no pullback below entry) and yesterday's TC (confirmed bullish), the breakout is genuine. If it fails to clear either, it is a false breakout (a "fakey").

6 — Breakout volume > signal volume

Goal: Confirm retail/option activity isn't driving a dead-cat bounce. Rule: breakout_candle.volume > signal_candle.volume. Interpretation: The signal candle broke out on real volume. The breakout candle must show even higher volume — if volume dries up on confirmation, the move lacks institutional backing and is likely a pullback. Higher volume on the breakout candle confirms conviction.


Entry / Stop / Target

Variable Calculation Description
entry breakout_candle.high + ENTRY_BUFFER (0.5) Buy a few ticks above the breakout candle's high
stop max(signal_candle.low, resting_low) Below the signal candle's low, or the lowest point touched between signal and breakout
risk entry − stop Per-share risk
target1 entry + risk 1:1 reward-to-risk
target2 entry + 2 × risk 1:2 reward-to-risk

Position sizing: 70% exited at target1, 30% at target2 (see exit_target1_pct in the returned dict).


API Reference

cpr_scanner/cpr_utils.py

calculate_cpr(high, low, close) -> dict Calculates Central Pivot Range (standard industry formula):

  • PP = (H + L + C) / 3
  • BC = (H + L) / 2 (Bottom Central)
  • TC = 2 × PP − BC (Top Central)
  • width = abs(TC − BC) = (2C − H − L) / 3 (absolute value ensures positive width)
  • cpr_pct = (width / close) × 100

Returns: {"pp", "tc", "bc", "width", "cpr_pct"}

calculate_camarilla(high, low, close) -> dict Calculates 8 Camarilla pivot levels using the range (H − L):

  • H4 = C + 0.55 × rng, H3 = C + 0.275 × rng, H2 = C + 0.183 × rng, H1 = C + 0.0916 × rng
  • L1 = C − 0.0916 × rng, L2 = C − 0.183 × rng, L3 = C − 0.275 × rng, L4 = C − 0.55 × rng

Returns: {"h1", "h2", "h3", "h4", "l1", "l2", "l3", "l4"}

get_nifty500_symbols() -> list[str] Downloads the Nifty 500 constituent list from niftyindices.com (with offline CSV fallback at docs/ind_nifty500list.csv). Results are cached in the module-level _nifty500_symbols variable for the lifetime of the process.

fetch_daily_data(symbol, from_date, to_date) -> pd.DataFrame | None Fetches daily OHLC data via yfinance (Ticker(f"{symbol}.NS").history(start, end)). Adds 1 day to to_date because yfinance's end date is exclusive. If the latest row has NaN OHLC (publication lag), patches via ticker.info (currentPrice, dayHigh, dayLow). Drops remaining NaN rows requiring at least 3 valid trading days.

fetch_weekly_data(symbol, today) -> dict | None Fetches the prior week's Mon–Fri OHLC. Walks back to the last completed Friday. Returns {"high": max(high), "low": min(low), "close": last_close} or None if insufficient data.

should_include_in_watchlist(current_hlc, prior_hlc, weekly_hlc) -> bool Applies the 6 pre-market filter conditions. Returns True only if all pass.

scan_premarket(today, output_path) -> list[dict] Iterates all Nifty 500 symbols → fetches daily + weekly data → applies should_include_in_watchlist. Appends passing stocks as {"symbol", "ohlc", "cpr", "weekly_cpr", "camarilla"} dicts. Writes results to data/watchlist.json.

CandleBuilder(bucket_minutes=5) Aggregates live tick data into fixed-interval candles. update(price, volume, ts) returns a completed candle dict {"bucket", "open", "high", "low", "close", "volume", "open_time"} or None mid-bucket.

check_breakout(signal_candle, breakout_candle, h4, prev_cpr_high, resting_low=None) -> dict Validates the 3 breakout conditions (4–6). On success returns {"valid": True, "entry", "stop", "risk", "target1", "target2", "exit_target1_pct"}. On failure returns {"valid": False, "reason": slug}.

get_live_tick(symbol, nse=None) -> dict | None Fetches the latest live quote via NSELive.stock_quote. Returns {"last_price", "volume", "ts"} or None on error.

scan_breakout(watchlist_path, poll_interval, max_iterations) -> None Main Phase 2 loop: loads watchlist → polls live ticks → builds candles → detects signal candles (close > H4) → validates breakout candles (close > max(signal.high, CPR TC) + volume surge) → prints entry/stop/target alerts.

cpr_scanner/cpr_premarket.py

Phase 1 CLI entrypoint. Args: --date (YYYY-MM-DD, default today), --output (default data/watchlist.json).

cpr_scanner/cpr_breakout.py

Phase 2 CLI entrypoint. Args: --watchlist (default data/watchlist.json), --interval (seconds, default 5). Handles KeyboardInterrupt for clean exit.

cpr_scanner/config.py

Constant Value Description
NIFTY500_CSV_URL https://www.niftyindices.com/IndexConstituent/ind_nifty500list.csv Network source for constituents
NIFTY500_LOCAL_CSV docs/ind_nifty500list.csv Offline fallback CSV path
CPR_WIDTH_THRESHOLD 0.0015 0.15% of close — max CPR width to pass condition 1A
ENTRY_BUFFER 0.5 Price units above breakout candle high for entry placement
POLL_INTERVAL_SECONDS 5 Seconds between live tick polls

Setup

pip install yfinance jugaad-data pandas pytest
pip install pyright
PYTHONPATH=. python -m pytest tests/

Notes

  • The CPR width threshold of 0.15% (CPR_WIDTH_THRESHOLD = 0.0015) is narrow. Typical daily CPR widths with the standard formula range 0.03–1.5%. Increase this value if you want a wider watchlist.
  • The Nifty 500 list is downloaded from niftyindices.com with a fallback to docs/ind_nifty500list.csv.
  • Historical data comes from yfinance; live market data comes from jugaad_data.nse.NSELive.

About

CPR Inside Breakout SRAEGY scanner for Nifty 500 stocks

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages