Support Required to Resolve “No Candle” Error in Code Execution

Dear Support Team,

I am currently working on writing and implementing my trading strategy code. However, I am repeatedly encountering a “No Candle” error during execution.

I have checked the code, but the issue is still occurring, and I am unable to proceed with strategy execution.

Could you please investigate this issue and help me identify and resolve the root cause? Your support in fixing this error at the earliest would be highly appreciated.

Thank you for your assistance.

Hi @Mohsin_Khan ,

Could you share the complete error message or a screenshot, along with the code used to fetch the candle data? This will help us check the issue and guide you with the solution.

“”"

dhan_client.py - Dhan API v2 adapter for RS Breakout v3 Nifty500

All functions return parsed JSON or raise on critical errors.

“”"

import logging

import math

import time

from datetime import date, datetime

import requests

import config as cfg

logger = logging.getLogger(“dhan_client”)

# ── helpers ──────────────────────────────────────────────────────────────────

def _headers():

return {

    "access-token": cfg.ACCESS_TOKEN,

    "client-id":    cfg.CLIENT_ID,

    "Content-Type": "application/json",

}

def _get(path, params=None, timeout=10):

url = cfg.BASE_URL + path

for attempt in range(1, 4):

    try:

        r = requests.get(url, headers=\_headers(), params=params, timeout=timeout)

        r.raise_for_status()

        return r.json()

    except requests.RequestException as e:

        logger.warning("GET %s attempt %d/3 failed: %s", path, attempt, e)

        if attempt < 3:

            time.sleep(2 \*\* attempt)

logger.error("GET %s failed after 3 attempts", path)

return None

def _post(path, payload, timeout=10):

url = cfg.BASE_URL + path

for attempt in range(1, 4):

    try:

        r = requests.post(url, headers=\_headers(), json=payload, timeout=timeout)

        logger.debug("POST %s attempt %d status=%d", path, attempt, r.status_code)

        return r.json()

    except requests.RequestException as e:

        logger.warning("POST %s attempt %d/3 failed: %s", path, attempt, e)

        if attempt < 3:

            time.sleep(2 \*\* attempt)

logger.error("POST %s failed after 3 attempts", path)

return None

# ── market data ───────────────────────────────────────────────────────────────

def search_scrip(symbol: str) → dict | None:

"""Search for a symbol and return the best NSE_EQ match."""

data = \_get("/scrips/search", params={"searchText": symbol})

if not data:

    return None

*# data is a list of matches; pick NSE_EQ*

if isinstance(data, list):

    for item in data:

        if item.get("exch_seg") == "NSE_EQ" or item.get("exchangeSegment") == "NSE_EQ":

            return item

    return data\[0\] if data else None

return data

def get_quote(security_id: str, exchange_segment: str = “NSE_EQ”) → dict | None:

"""Fetch live market quote for a single symbol."""

payload = {exchange_segment: \[int(security_id)\]}

data = \_post("/marketfeed/quote", payload)

if not data:

    return None

try:

    seg_data = data.get("data", {}).get(exchange_segment, {})

    return seg_data.get(str(security_id)) or seg_data.get(int(security_id))

except (KeyError, TypeError):

    return data

def get_quotes_batch(security_ids: list, exchange_segment: str = “NSE_EQ”) → dict:

"""Batch quote fetch. Returns dict keyed by security_id string."""

if not security_ids:

    return {}

payload = {exchange_segment: \[int(sid) for sid in security_ids\]}

data = \_post("/marketfeed/quote", payload)

if not data:

    return {}

try:

    return data.get("data", {}).get(exchange_segment, {})

except (KeyError, TypeError):

    return {}

def get_candles(security_id: str,

            exchange_segment: str = "NSE_EQ",

            interval: str = "5",

            from_date: str = None,

            to_date: str = None) -> list:

"""Fetch intraday candles for NSE_EQ with robust date handling and parsing."""

from datetime import date, datetime, timedelta



valid_intervals = {"1", "5", "15", "25", "60"}

iv = str(interval).strip()

if iv not in valid_intervals:

    iv = "5"



today_dt = date.today()

today_str = today_dt.strftime("%Y-%m-%d")

prev_str = (today_dt - timedelta(days=5)).strftime("%Y-%m-%d")



*# Accurate date range attempts for reliable data fetching*

date_attempts = \[

    (from_date or f"{today_str} 09:15:00", to_date or f"{today_str} 15:30:00"),

    (from_date or prev_str, to_date or today_str),

\]



chart_data = None

for f_dt, t_dt in date_attempts:

    payload = {

        "securityId": str(security_id),

        "exchangeSegment": exchange_segment,

        "instrument": "EQUITY" if "EQ" in exchange_segment else "INDEX",

        "interval": iv,

        "fromDate": f_dt,

        "toDate": t_dt,

    }

    data = \_post("/charts/intraday", payload)

    if not data or not isinstance(data, dict):

        continue

    candidate = data.get("data", data)

    if isinstance(candidate, dict) and (candidate.get("open") or candidate.get("start_Time") or candidate.get("timestamp")):

        chart_data = candidate

        break

    if isinstance(candidate, list) and len(candidate) > 0:

        chart_data = candidate

        break



if not chart_data:

    return \[\]



candles = \[\]



if isinstance(chart_data, dict) and "open" in chart_data:

    opens = chart_data.get("open", \[\])

    highs = chart_data.get("high", \[\])

    lows = chart_data.get("low", \[\])

    closes = chart_data.get("close", \[\])

    volumes = chart_data.get("volume", \[\])

    timestamps = (

        chart_data.get("start_Time")

        or chart_data.get("timestamp")

        or chart_data.get("time")

        or \[\]

    )



    for i in range(len(opens)):

        candles.append({

            "timestamp": timestamps\[i\] if i < len(timestamps) else i,

            "open": float(opens\[i\]),

            "high": float(highs\[i\]),

            "low": float(lows\[i\]),

            "close": float(closes\[i\]),

            "volume": float(volumes\[i\]) if i < len(volumes) else 0.0,

        })

elif isinstance(chart_data, list):

    for c in chart_data:

        if isinstance(c, (list, tuple)) and len(c) >= 5:

            candles.append({

                "timestamp": c\[0\],

                "open": float(c\[1\]),

                "high": float(c\[2\]),

                "low": float(c\[3\]),

                "close": float(c\[4\]),

                "volume": float(c\[5\]) if len(c) > 5 else 0.0,

            })

        elif isinstance(c, dict):

            candles.append({

                "timestamp": c.get("timestamp") or c.get("start_Time") or c.get("time"),

                "open": float(c.get("open", c.get("o", 0))),

                "high": float(c.get("high", c.get("h", 0))),

                "low": float(c.get("low", c.get("l", 0))),

                "close": float(c.get("close", c.get("c", 0))),

                "volume": float(c.get("volume", c.get("v", 0))),

            })



candles.sort(key=lambda x: x\["timestamp"\] if x\["timestamp"\] is not None else 0)

return candles

def get_fund_limit() → dict:

"""Return available cash / margin. Keys: availabelBalance, sodLimit, etc."""

data = \_get("/fundlimit")

return data or {}

# alias used by main.py

def get_funds() → dict:

return get_fund_limit()

def get_positions() → list:

"""Return list of open positions."""

data = \_get("/positions")

if isinstance(data, list):

    return data

return (data or {}).get("data", \[\])

# ── order management ──────────────────────────────────────────────────────────

def place_order(security_id, exchange_segment=“NSE_EQ”, transaction_type=“BUY”,

            quantity=1, order_type="LIMIT", product_type="INTRADAY",

            price=0.0, tag="") -> dict:

"""

Place a single equity order. Returns the full API response dict.

Supports both keyword-arg call (from main.py) and legacy dict call.

"""

payload = {

    "dhanClientId":    cfg.CLIENT_ID,

    "transactionType": transaction_type,

    "exchangeSegment": exchange_segment,

    "productType":     product_type,

    "orderType":       order_type,

    "validity":        "DAY",

    "securityId":      str(security_id),

    "quantity":        int(quantity),

    "price":           round(float(price), 2),

    "disclosedQuantity": 0,

    "afterMarketOrder":  False,

    "correlationId":   tag or "",

}

logger.info("Placing %s %s order: secId=%s qty=%d price=%.2f",

            transaction_type, order_type, security_id, quantity, price)

return \_post("/orders", payload) or {}

def get_order_status(order_id: str) → dict:

"""Return order status dict for a given order ID."""

data = \_get(f"/orders/{order_id}")

return data or {}

def cancel_order(order_id: str) → dict:

"""Cancel an open order. Returns API response."""

url = cfg.BASE_URL + f"/orders/{order_id}"

for attempt in range(1, 4):

    try:

        r = requests.delete(url, headers=\_headers(), timeout=10)

        return r.json()

    except requests.RequestException as e:

        logger.warning("DELETE /orders/%s attempt %d/3 failed: %s", order_id, attempt, e)

        if attempt < 3:

            time.sleep(2 \*\* attempt)

logger.error("cancel_order %s failed after 3 attempts", order_id)

return {}

if _name_ == “_main_”:

from main import run

run()

@Tradehull_Imran “”"

main.py - Orchestrator for RS Breakout v3 Nifty500

Runs on Dhan Dext scheduler. Paper mode by default.

Flow each scan cycle:

1. Load Nifty500 universe

2. Fetch live quotes

3. Score all six conditions

4. Rank by momentum → top-N shortlist

5. For each shortlisted symbol: check 3-min breakout

6. If all conditions + breakout pass → size position → place (paper or live)

7. Monitor positions → SL / target / square-off

“”"

import json

import logging

import math

import sys

import time

from datetime import datetime, date, timedelta

import config as cfg

import dhan_client as dhan

from strategy import (

evaluate_all_conditions,

check_breakout,

calc_position_size,

passes_chart_quality_filter,

)

logging.basicConfig(

level=logging.INFO,

format="%(asctime)s \[%(levelname)s\] %(name)s: %(message)s",

handlers=\[logging.StreamHandler(sys.stdout)\],

)

logger = logging.getLogger(“main”)

state = {

"trades_today":      0,

"symbol_trades":     {},

"open_positions":    {},

"daily_pnl":         0.0,

"halted":            False,

"prev_shortlist":    set(),

"paper_orders":      \[\],

"scan_count":        0,

}

NIFTY500_SYMBOLS: list = []

NIFTY500_SYMBOLS_LIST = “”“RELIANCE,BHARTIARTL,HDFCBANK,ICICIBANK,TCS,SBIN,BAJFINANCE,LT,LICI,INFY,KOTAKBANK,HINDUNILVR,SUNPHARMA,ADANIPORTS,HCLTECH,TITAN,AXISBANK,MARUTI,ADANIPOWER,ULTRACEMCO,ITC,ADANIENT,M&M,HAL,ETERNAL,JSWSTEEL,NTPC,ONGC,BAJAJ-AUTO,BEL,BAJAJFINSV,COALINDIA,DIVISLAB,POWERGRID,NESTLEIND,ASIANPAINT,HINDZINC,TATASTEEL,DMART,SHRIRAMFIN,GRASIM,ADANIGREEN,EICHERMOT,HINDALCO,TVSMOTOR,WIPRO,IOC,INDIGO,SOLARINDS,SBILIFE,DLF,MOTHERSON,TECHM,TORNTPHARM,PIDILITIND,HYUNDAI,ADANIENSOL,TMCV,TRENT,PNB,UNIONBANK,JIOFIN,BHEL,VBL,ICICIAMC,ABB,BOSCHLTD,TATACAP,CGPOWER,CHOLAFIN,BAJAJHLDNG,IDEA,LTM,BPCL,CUMMINSIND,BANKBARODA,POWERINDIA,SIEMENS,GROWW,HDFCLIFE,INDIANB,ZYDUSLIFE,POLYCAB,INDHOTEL,TATAPOWER,APOLLOHOSP,LGEINDIA,BRITANNIA,LENSKART,CIPLA,GAIL,LAURUSLABS,CANBK,PFC,PAYTM,JINDALSTEL,DRREDDY,HEROMOTOCO,LLOYDSME,INDUSTOWER,MARICO,MEESHO,TMPV,IRFC,HDFCAMC,LODHA,TATACONSUM,MUTHOOTFIN,ABCAPITAL,GVT&D,ENRIN,NYKAA,MANKIND,GMRAIRPORT,UNITDSPR,AUROPHARMA,PERSISTENT,AMBUJACEM,VEDL,LUPIN,GODREJCP,ASHOKLEY,OFSS,FEDERALBNK,COFORGE,JSWENERGY,IDBI,SHREECEM,BHARTIHEXA,BHARATFORG,ICICIGI,MAXHEALTH,RECLTD,MCX,NAUKRI,MAZDOCK,JSWINFRA,OIL,HINDPETRO,NHPC,AUBANK,SRF,NTPCGREEN,DIXON,SAIL,WAAREEENER,PHOENIXLTD,INDUSINDBK,HAVELLS,BAJAJHFL,DABUR,WELCORP,IDFCFIRSTB,ICICILIFE,GLENMARK,UNOMINDA,PRESTIGE,BANKINDIA,LTF,ALKEM,NAM-INDIA,TORNTPOWER,OBEROIRLTY,NMDC,ATGL,MAHABANK,APARINDS,GICRE,JSL,APLAPOLLO,MOTILALOFS,RADICO,NATIONALUM,IOB,BERGEPAINT,SCHAEFFLER,FORTIS,RBLBANK,ABBOTINDIA,KALYANKJIL,ATHERENERG,BIOCON,SBICARD,SUZLON,COROMANDEL,MRF,YESBANK,IPCALAB,LINDEINDIA,MPHASIS,TATACOMM,POLICYBZR,SONACOMS,GLAND,FLUOROCHEM,SWIGGY,ANTHEM,GODREJPROP,CPPLUS,PIRAMALFIN,SUNDARMFIN,AEGISLOG,COLPAL,FACT,SUPREMEIND,UPL,PETRONET,AIIL,HDBFS,MFSL,LTTS,AJANTPHARM,VMM,BALKRISIND,GLAXO,360ONE,TIINDIA,NAVINFLUOR,PREMIERENE,HINDCOPPER,M&MFIN,PATANJALI,RVNL,PAGEIND,JKCEMENT,BDL,ANANDRATHI,HFCL,ASTRAL,POONAWALLA,HEXT,KEI,IRCTC,PTCIL,KPRMILL,THERMAX,PWL,CRISIL,NH,EMCURE,ASTERDM,AIAENG,ITCHOTELS,KARURVYSYA,3MINDIA,MEDANTA,NLCINDIA,SHYAMMETL,ENDURANCE,VOLTAS,STARHEALTH,CONCOR,GODREJIND,PIIND,BLUESTARCO,LALPATHLAB,NUVAMA,DALBHARAT,UBL,COCHINSHIP,SAILIFE,ESCORTS,HUDCO,WOCKPHARMA,MRPL,PNBHOUSING,EXIDEIND,IREDA,REDINGTON,KIMS,TATAINVEST,DELHIVERY,HONAUT,IKS,SYRMA,CRAFTSMAN,UCOBANK,LICHSGFIN,AEGISVOPAK,MANAPPURAM,TATATECH,JUBLFOOD,SCHNEIDER,NIACL,ACMESOLAR,CENTRALBK,GODFRYPHLP,KIRLOSENG,ABSLAMC,ACUTAAS,ANGELONE,PPLPHARMA,GODIGIT,HSCL,NETWEB,APOLLOTYRE,NEULANDLAB,GABRIEL,ZFCVINDIA,RRKABEL,BANDHANBNK,CHOLAHLDNG,GILLETTE,TATAELXSI,MSUMI,SUNTV,ASAHIINDIA,CGCL,IIFL,AWL,KPIL,CUB,CHENNPETRO,ACC,FORCEMOT,KAYNES,GESHIP,HBLENGINE,GRSE,DATAPATTNS,PFOCUS,AMBER,ITI,SAGILITY,TIMKEN,JYOTICNC,FSL,IGL,WELSPUNLIV,CEMPRO,DEEPAKNTR,GRANULES,URBANCO,IRB,CARBORUNIV,THELEELA,CASTROLIND,AADHARHFC,ABDL,AFFLE,SJVN,NBCC,BELRISE,EIHOTEL,SUMICHEM,BRIGADE,KAJARIACER,PINELABS,TENNIND,RAMCOCEM,ECLERX,CHALET,COHANCE,CAPLIPOINT,FINCABLES,CESC,ANANTRAJ,TBOTEK,PFIZER,CREDITACC,IGIL,ATUL,EMMVEE,EMAMILTD,ELGIEQUIP,CAMS,IFCI,JSWCEMENT,SARDAEN,AARTIIND,BEML,CHOICEIN,ONESOURCE,TRAVELFOOD,BAYERCROP,ERIS,DCMSHRIRAM,JINDALSAW,JUBLPHARMA,SYNGENE,ZYDUSWELL,TATACHEM,POLYMED,CHAMBLFERT,KPITTECH,DEVYANI,MINDACORP,DEEPAKFERT,VIJAYA,VTL,NATCOPHARM,TRITURBINE,HONASA,USHAMART,PARADEEP,OLAELEC,KFINTECH,INDGN,PVRINOX,JSWDULUX,ENGINERSIN,CARTRADE,BALRAMCHIN,GMDCLTD,ZENTEC,NAVA,GRAPHITE,PGEL,SPLPETRO,CIEINDIA,NIVABUPA,FIVESTAR,LTFOODS,SAMMAANCAP,GALLANTT,TEGA,CYIENT,CONCORDBIO,ARE&M,CROMPTON,SOBHA,ANURAS,J&KBANK,CEATLTD,CCL,ACE,RAINBOW,GPIL,NUVOCO,CANHLIFE,ABREL,BIKAJI,JBMA,DOMS,ZENSARTECH,EIDPARRY,SCI,NSLNISP,PCBL,RKFORGE,UTIAMC,HOMEFIRST,TRIDENT,INOXWIND,JMFINANCIL,MGL,WHIRLPOOL,BLUEDART,SIGNATURE,JAINREC,GRAVITA,APTUS,INDIACEM,TECHNOE,JUBLINGREA,INDIAMART,CANFINHOME,SAREGAMA,ELECON,IRCON,TARIL,RITES,IEX,TITAGARH,JKTYRE,AAVAS,KEC,JPPOWER,SBFC,LEMONTREE,ABLBL,BLS,BSOFT,BLUEJET,INTELLECT,BATAINDIA,AFCONS,FIRSTCRY,BBTC,OLECTRA,MMTC,CLEAN,SWANCORP,JWL,TEJASNET,TTML,RAILTEL,NCC,RHIM,RPOWER,NEWGEN,SONATSOFTW,ZEEL,SAPPHIRE,ABFRL,LATENTVIEW,MAPMYINDIA,HEGAM,BSE,CDSL”“”.split(“,”)

def load_universe():

*# Universe is embedded (500 Nifty500 constituents, sourced 2026-10-26).*

*# Live NSE CSV fetch fails from the cloud runner (SSL), so no network call here.*

symbols = \[s.strip().upper() for s in NIFTY500_SYMBOLS_LIST if s.strip()\]

logger.info("Loaded %d Nifty500 symbols (embedded list)", len(symbols))

return symbols

def normalise_quote(raw):

if not raw:

    return None

try:

    ohlc = raw.get("ohlc") or {}

    q = {

        "ltp":          float(raw.get("last_price") or raw.get("lastTradedPrice") or 0),

        "open":         float(ohlc.get("open") or 0),

        "high":         float(ohlc.get("high") or 0),

        "low":          float(ohlc.get("low") or 0),

        "close":        float(ohlc.get("close") or 0),

        "volume":       float(raw.get("volume") or 0),

        "avgprice":     float(raw.get("average_price") or 0),

        "year_high":    float(raw.get("52_week_high") or 0),

        "buyquantity":  float(raw.get("buy_quantity") or 0),

        "sellquantity": float(raw.get("sell_quantity") or 0),

        "change_per":   float(raw.get("net_change") or 0),

        "timestamp":    raw.get("last_trade_time") or datetime.now().isoformat(),

    }

    if q\["ltp"\] <= 0:

        return None

    if q\["avgprice"\] <= 0 and q\["high"\] > 0:

        q\["avgprice"\] = (q\["high"\] + q\["low"\] + q\["ltp"\]) / 3

    return q

except Exception as e:

    logger.debug("normalise_quote error: %s", e)

    return None

_sec_id_cache = {}

_scrip_map = {}

def load_scrip_master():

*# Dhan's public instrument master (NSE equities) -> symbol to security id*

import csv, io, requests

r = requests.get("https://images.dhan.co/api-data/api-scrip-master.csv", timeout=60)

r.raise_for_status()

for row in csv.DictReader(io.StringIO(r.text)):

    if row.get("SEM_EXM_EXCH_ID") == "NSE" and row.get("SEM_SEGMENT") == "E" and row.get("SEM_INSTRUMENT_NAME") == "EQUITY":

        key = (row.get("SEM_TRADING_SYMBOL") or "").strip().upper()

        sid = (row.get("SEM_SMST_SECURITY_ID") or "").strip()

        if key and sid:

            \_scrip_map\[key\] = sid

logger.info("Scrip master loaded: %d NSE equities", len(\_scrip_map))

def get_security_id(symbol):

if not \_scrip_map:

    try:

        load_scrip_master()

    except Exception as ex:

        logger.error("Scrip master load failed: %s", ex)

        return None

sid = \_scrip_map.get(symbol.strip().upper())

if sid:

    \_sec_id_cache\[symbol\] = sid

return sid

def market_time(fmt=“%H:%M”):

return datetime.now().strftime(fmt)

def is_market_open():

t = market_time()

return "09:15" <= t <= "15:30"

def is_entry_allowed():

return market_time() < cfg.NO_NEW_ENTRIES_AFTER

def is_square_off_time():

return market_time() >= cfg.SQUARE_OFF_TIME

def risk_gate_ok(symbol, equity):

if state\["halted"\]:

    return False, "system_halted"

if state\["daily_pnl"\] <= -(equity \* cfg.MAX_DAILY_LOSS_PCT_OF_EQUITY / 100):

    state\["halted"\] = True

    return False, "daily_loss_limit_hit"

if len(state\["open_positions"\]) >= cfg.MAX_OPEN_POSITIONS:

    return False, f"max_open_positions={cfg.MAX_OPEN_POSITIONS}"

if state\["trades_today"\] >= cfg.MAX_TRADES_PER_DAY:

    return False, f"max_trades_per_day={cfg.MAX_TRADES_PER_DAY}"

sym_count = state\["symbol_trades"\].get(symbol, 0)

if sym_count >= cfg.MAX_TRADES_PER_SYMBOL_PER_DAY:

    return False, f"max_trades_per_symbol={cfg.MAX_TRADES_PER_SYMBOL_PER_DAY}"

if not is_entry_allowed():

    return False, f"after_no_entry_time={cfg.NO_NEW_ENTRIES_AFTER}"

return True, "ok"

def paper_order(symbol, qty, entry, stop, target, tag):

oid = f"PAPER-{symbol}-{int(time.time())}"

record = {

    "order_id": oid, "symbol": symbol,

    "qty": qty, "entry": entry,

    "stop": stop, "target": target,

    "tag": tag,

    "ts": datetime.now().isoformat(),

}

state\["paper_orders"\].append(record)

logger.info("\[PAPER\] %s", json.dumps(record))

return oid

def live_order(symbol, security_id, qty, entry, stop, target):

if not (cfg.MODE == "live" and cfg.LIVE_TRADING_ENABLED):

    logger.error("live_order called but live mode disabled -- aborting")

    return None

result = dhan.place_order(

    security_id=security_id,

    exchange_segment="NSE_EQ",

    transaction_type="BUY",

    quantity=qty,

    order_type=cfg.ORDER_TYPE,

    product_type=cfg.PRODUCT_TYPE,

    price=entry,

    tag=f"RSB-{symbol}",

)

if result and result.get("orderId"):

    oid = result\["orderId"\]

    logger.info("\[LIVE\] ORDER PLACED symbol=%s qty=%d entry=%.2f "

                "stop=%.2f target=%.2f order_id=%s",

                symbol, qty, entry, stop, target, oid)

    return oid

logger.error("\[LIVE\] ORDER FAILED symbol=%s result=%s", symbol, result)

return None

def scan_once():

global NIFTY500_SYMBOLS

state\["scan_count"\] += 1



if not is_market_open():

    logger.info("Market closed -- skipping scan %d", state\["scan_count"\])

    return



if is_square_off_time():

    \_square_off_all()

    return



if not NIFTY500_SYMBOLS:

    NIFTY500_SYMBOLS = load_universe()

    if not NIFTY500_SYMBOLS:

        logger.error("Universe empty -- cannot scan")

        return



funds_data = dhan.get_funds()

equity = 0.0

if funds_data:

    equity = float(funds_data.get("availabelBalance")

                   or funds_data.get("availableBalance", 0))

if equity <= 0:

    logger.warning("Equity=%.2f -- position sizing will fail", equity)



scored = \[\]

missing = 0



*# Batch-fetch quotes (Dhan marketfeed accepts many ids per call)*

sids = \[\]

for symbol in NIFTY500_SYMBOLS:

    sid\_ = get_security_id(symbol)

    if sid\_:

        sids.append(sid\_)

    else:

        missing += 1

quotes = {}

for k in range(0, len(sids), 200):

    chunk = \[int(x) for x in sids\[k:k + 200\]\]

    time.sleep(1.2)

    resp = dhan.\_post("/marketfeed/quote", {"NSE_EQ": chunk})

    if resp:

        quotes.update(((resp.get("data") or {}).get("NSE_EQ")) or {})

logger.info("Quotes fetched: %d of %d", len(quotes), len(sids))



for symbol in NIFTY500_SYMBOLS:

    sid = get_security_id(symbol)

    if not sid:

        continue

    raw = quotes.get(str(sid))

    if not raw:

        missing += 1

        continue

    q = normalise_quote(raw)

    if not q:

        missing += 1

        continue

    cond = evaluate_all_conditions(q)

    if cond\["momentum"\] is not None:

        scored.append({"symbol": symbol, "sid": sid, "quote": q, "cond": cond})



logger.info("Scan #%d: %d/%d scored, %d missing",

            state\["scan_count"\], len(scored), len(NIFTY500_SYMBOLS), missing)



if not scored:

    return



scored.sort(key=lambda x: x\["cond"\].get("momentum") or -math.inf, reverse=True)



for i, s in enumerate(scored\[:cfg.LOG_TOP_N\]):

    c = s\["cond"\]

    logger.info("RANK %02d | %-12s | mom=%.2f | rs=%.2f | sm=%.2f | "

                "br=%.3f | om=%.2f | dist=%.2f%% | pass=%s",

                i+1, s\["symbol"\],

                c.get("momentum") or 0, c.get("rs_score") or 0,

                c.get("smart_momentum") or 0, c.get("buying_ratio") or 0,

                c.get("open_move") or 0,

                c.get("distance_to_year_high_pct") or 999, c\["passed"\])



shortlist_now = set(s\["symbol"\] for s in scored\[:cfg.SHORTLIST_TOP_N\])



for candidate in scored\[:cfg.SHORTLIST_TOP_N\]:

    symbol = candidate\["symbol"\]

    cond   = candidate\["cond"\]

    sid    = candidate\["sid"\]

    q      = candidate\["quote"\]



    if symbol in state\["open_positions"\]:

        continue



    if not cond\["passed"\]:

        logger.info("REJECT %s | %s", symbol, " | ".join(cond\["reasons"\]))

        continue



    if cfg.REQUIRE_TWO_CONSECUTIVE_SCANS:

        if symbol not in state\["prev_shortlist"\]:

            logger.info("STABILITY_SKIP %s -- not in prev shortlist", symbol)

            continue



    ok, reason = risk_gate_ok(symbol, equity)

    if not ok:

        logger.info("RISK_GATE %s: %s", symbol, reason)

        continue



    ltp = q\["ltp"\]

    today_str = date.today().strftime("%Y-%m-%d")

    candles = dhan.get_candles(sid, "NSE_EQ",

                               from_date=today_str,

                               to_date=today_str,

                               interval=str(cfg.CANDLE_INTERVAL_MINUTES))

    if not candles:

        logger.info("NO_CANDLES %s", symbol)

        continue




    *# ── Chart Quality Filter (CQF) ─────────────────────────────────────────────*

    if cfg.CQF_ENABLED:

        hist_to   = today_str

        hist_from = (date.today() - timedelta(days=300)).strftime("%Y-%m-%d")

        try:

            daily_candles = dhan.get_candles(sid, "NSE_EQ",

                                         from_date=hist_from,

                                         to_date=hist_to,

                                         interval="1440")  *# daily*

        except Exception as ex:

            logger.warning("CQF daily fetch failed for %s: %s -- skipping", symbol, ex)

            continue

        year_high = candidate.get("high", q.get("high", 0))

        cqf = passes_chart_quality_filter(

            daily_candles or \[\], year_high)

        logger.info("CQF %s: passed=%s | %s", symbol, cqf\["passed"\], cqf\["reason"\])

        if not cqf\["passed"\]:

            continue

    *# ───────────────────────────────────────────────────────────────────────────*



    breakout = check_breakout(candles)

    logger.info("BREAKOUT %s: confirmed=%s | %s",

                symbol, breakout\["confirmed"\], breakout\["reason"\])



    if not breakout\["confirmed"\]:

        continue



    entry_price = ltp

    stop_price  = breakout\["breakout_candle"\]\["low"\]

    if stop_price >= entry_price:

        logger.info("REJECT %s: stop=%.2f>=entry=%.2f", symbol, stop_price, entry_price)

        continue



    qty, err = calc_position_size(equity, entry_price, stop_price)

    if qty <= 0:

        logger.info("REJECT %s: qty=0 (%s)", symbol, err)

        continue



    rr = cfg.REWARD_RISK_RATIO

    target_price = entry_price + rr \* (entry_price - stop_price)



    logger.info("SIGNAL %s | entry=%.2f stop=%.2f target=%.2f qty=%d",

                symbol, entry_price, stop_price, target_price, qty)



    if cfg.MODE == "paper":

        oid = paper_order(symbol, qty, entry_price, stop_price,

                          target_price, f"RSB-{symbol}")

    elif cfg.MODE == "live" and cfg.LIVE_TRADING_ENABLED:

        oid = live_order(symbol, sid, qty, entry_price, stop_price, target_price)

    else:

        logger.warning("Live mode not fully enabled -- trade skipped")

        continue



    if oid:

        state\["open_positions"\]\[symbol\] = {

            "order_id": oid, "entry": entry_price,

            "stop": stop_price, "target": target_price, "qty": qty,

        }

        state\["trades_today"\] += 1

        state\["symbol_trades"\]\[symbol\] = (

            state\["symbol_trades"\].get(symbol, 0) + 1)



state\["prev_shortlist"\] = shortlist_now

def monitor_positions():

for symbol, pos in list(state\["open_positions"\].items()):

    sid = get_security_id(symbol)

    if not sid:

        continue

    raw = dhan.get_quote(sid)

    q = normalise_quote(raw) if raw else None

    if not q:

        continue

    ltp = q\["ltp"\]

    if ltp <= pos\["stop"\]:

        logger.info("SL_HIT %s ltp=%.2f stop=%.2f", symbol, ltp, pos\["stop"\])

        \_exit_position(symbol, pos, ltp, "SL")

    elif ltp >= pos\["target"\]:

        logger.info("TARGET_HIT %s ltp=%.2f target=%.2f", symbol, ltp, pos\["target"\])

        \_exit_position(symbol, pos, ltp, "TARGET")

def _exit_position(symbol, pos, exit_price, reason):

pnl = (exit_price - pos\["entry"\]) \* pos\["qty"\]

state\["daily_pnl"\] += pnl

logger.info("EXIT %s @ %.2f reason=%s pnl=%.2f cumulative_pnl=%.2f",

            symbol, exit_price, reason, pnl, state\["daily_pnl"\])

del state\["open_positions"\]\[symbol\]

def _square_off_all():

if not state\["open_positions"\]:

    return

logger.info("SQUARE_OFF_TIME -- closing all %d positions",

            len(state\["open_positions"\]))

for symbol, pos in list(state\["open_positions"\].items()):

    sid = get_security_id(symbol)

    raw = dhan.get_quote(sid) if sid else None

    q = normalise_quote(raw) if raw else None

    ltp = q\["ltp"\] if q else pos\["entry"\]

    \_exit_position(symbol, pos, ltp, "SQUARE_OFF")

def run():

logger.info("=" \* 60)

logger.info("RS BREAKOUT v3 NIFTY500  |  mode=%s  |  live_enabled=%s",

            cfg.MODE, cfg.LIVE_TRADING_ENABLED)

if cfg.MODE == "live" and cfg.LIVE_TRADING_ENABLED:

    logger.warning("LIVE TRADING ACTIVE -- real orders will be placed")

logger.info("=" \* 60)



NIFTY500_SYMBOLS\[:\] = load_universe()

if not NIFTY500_SYMBOLS:

    logger.error("Cannot start -- universe empty")

    return

logger.info("Universe: %d symbols loaded", len(NIFTY500_SYMBOLS))



*# Wait until market opens (09:15)*

while market_time() < "09:15":

    logger.info("Waiting for market open 09:15 (now %s)", market_time())

    time.sleep(30)



scan_interval = 60



*# Continuous loop: keep running every scan_interval until market close (15:30)*

while is_market_open():

    try:

        if not is_square_off_time():

            scan_once()

        monitor_positions()

    except Exception as e:

        logger.exception("Cycle error (will retry next cycle): %s", e)

    logger.info("Cycle complete. Open positions: %d | Trades today: %d | PnL: %.2f",

                len(state\["open_positions"\]), state\["trades_today"\], state\["daily_pnl"\])

    time.sleep(scan_interval)



logger.info("Market closed (%s). Exiting run().", market_time())

if _name_ == “_main_”:

run()

@Tradehull_Imran

image

Hi @Mohsin_Khan ,

The NO_CANDLES error is happening because the candle requests are being sent too fast. Dhan allows only 5 data requests per second, so after a few quick requests it starts rejecting them.

In dhan_client.py, inside get_candles(), add a small delay just above the _post("/charts/intraday", payload) line:

time.sleep(0.5)

If it still happens, add this line right after the _post call to see Dhan’s actual error message, and share the log with us:

logger.info("Dhan candle response for %s: %s", security_id, data)