multiticker / core /groww.py
Jitendra12421's picture
Upload folder using huggingface_hub
8fe2df2 verified
Raw
History Blame Contribute Delete
3.85 kB
"""
Groww charting API client.
Fetches 1-minute OHLCV candle data for NSE CASH segment tickers.
Handles:
- Retry with exponential backoff (3 attempts)
- None / missing values in candle arrays
- Cumulative volume -> per-candle volume conversion
"""
import time
import logging
import traceback
from datetime import datetime
import numpy as np
import pandas as pd
import requests
logger = logging.getLogger("live_trader")
GROWW_HEADERS = {
"User-Agent": (
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 "
"(KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36"
),
"Accept": "application/json",
}
def fetch_groww_candles(ticker, days=5, max_retries=3):
"""
Fetch 1-min OHLCV from Groww for the last *days* calendar days.
Returns a DataFrame [open, high, low, close, volume] with DateTimeIndex,
or None on complete failure.
"""
end_ts = int(time.time() * 1000)
start_ts = end_ts - (days * 24 * 3600 * 1000)
url = (
f"https://groww.in/v1/api/charting_service/v2/chart/exchange/NSE"
f"/segment/CASH/{ticker}"
f"?endTimeInMillis={end_ts}"
f"&intervalInMinutes=1"
f"&startTimeInMillis={start_ts}"
)
for attempt in range(1, max_retries + 1):
try:
resp = requests.get(url, headers=GROWW_HEADERS, timeout=15)
resp.raise_for_status()
data = resp.json()
if "candles" not in data or not data["candles"]:
logger.warning(f"[{ticker}] No candles in API response (attempt {attempt})")
if attempt < max_retries:
time.sleep(2 * attempt)
continue
return None
rows = []
for c in data["candles"]:
# Skip candles with None / missing OHLCV values
if c[1] is None or c[2] is None or c[3] is None or c[4] is None or c[5] is None:
continue
try:
dt = datetime.fromtimestamp(c[0])
rows.append({
"date": dt,
"open": float(c[1]),
"high": float(c[2]),
"low": float(c[3]),
"close": float(c[4]),
"cum_vol": float(c[5]),
})
except (TypeError, ValueError):
continue
if not rows:
logger.warning(f"[{ticker}] All candles had None values (attempt {attempt})")
if attempt < max_retries:
time.sleep(2 * attempt)
continue
return None
df = pd.DataFrame(rows)
df.set_index("date", inplace=True)
df.sort_index(inplace=True)
# Groww volume is cumulative per day -> difference it
df["date_only"] = df.index.date
df["volume"] = df.groupby("date_only")["cum_vol"].diff().fillna(df["cum_vol"])
df["volume"] = np.where(df["volume"] < 0, df["cum_vol"], df["volume"])
df.drop(columns=["cum_vol", "date_only"], inplace=True)
logger.info(f"[{ticker}] Fetched {len(df)} candles "
f"({df.index.min()} -> {df.index.max()})")
return df
except requests.exceptions.RequestException as e:
logger.error(f"[{ticker}] API error attempt {attempt}/{max_retries}: {e}")
if attempt < max_retries:
time.sleep(3 * attempt)
except Exception as e:
logger.error(f"[{ticker}] Unexpected error attempt {attempt}/{max_retries}: {e}")
logger.debug(traceback.format_exc())
if attempt < max_retries:
time.sleep(3 * attempt)
return None