Add Upstox swing signal generator with production-ready improvements and comprehensive analysis #2
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Reviewed Upstox swing trading signal generator for production readiness, efficiency, and calculation correctness. Original code has correct technical indicators but critical efficiency and production deficiencies.
Analysis & Findings
Calculation Correctness: ✅ All indicators (SMA, EMA, RSI, ATR, ADX, Bollinger Bands) verified correct via 33 unit tests. Wilder's smoothing properly implemented.
Efficiency Issues:
Production Gaps:
Security: CodeQL scan clean (0 vulnerabilities)
Changes
Original Code (
upstox_swing_signals.py)Added as reference with documented issues
Production Version (
upstox_swing_signals_improved.py)Performance optimizations:
Production features:
APIError,AuthenticationError,RateLimitErrorBug fix: SMA division by zero when
period=0Testing (
test_upstox_indicators.py)33 unit tests covering all indicators, edge cases (empty data, invalid periods, extreme values). 100% pass rate.
Documentation
upstox_code_review.md(900 lines): 14-page technical review covering efficiency analysis, production gaps, security concerns, prioritized fixesEXECUTIVE_SUMMARY.md: High-level findings, performance benchmarks, deployment recommendationsSECURITY_SUMMARY.md: CodeQL results, attack vectors, production checklistREADME_UPSTOX.md: Deployment guide with configuration, usage, performance comparisonPerformance Impact
Verdict
Recommend deploying improved version starting with 50-100 symbols.
Original prompt
#!/usr/bin/env python3
"""
Robust daily swing-signal generator using Upstox historical candles.
Highlights
Fetches historical data up to today (last available session).
Cleans and validates data (timestamps, NaNs, duplicates, non-positive prices).
Verifies indicator prerequisites per symbol and logs sufficiency.
Implements a well-known swing strategy:
Trend filter + Pullback + Re-entry/confirmation
Produces BUY / SELL / HOLD with concise colored reasons.
Uses environment variables to configure tokens and universe.
Environment
Usage
python upstox_swing_signals.py
"""
import os
import sys
import csv
import time
import math
import logging
from typing import List, Tuple, Optional, Set, Dict, Any
from datetime import datetime, date, timedelta, timezone
from urllib.parse import quote
import requests
------------- Logging -------------
LOG_LEVEL = os.getenv("LOG_LEVEL", "INFO").upper()
logging.basicConfig(
level=getattr(logging, LOG_LEVEL, logging.INFO),
format="%(asctime)s | %(levelname)s | %(message)s",
datefmt="%Y-%m-%d %H:%M:%S",
)
logger = logging.getLogger("swing")
------------- Upstox API config -------------
BASE_URL = "https://api.upstox.com/v3/historical-candle"
CSV_PATH = os.getenv("NIFTY500_CSV", "ind_nifty500list.csv")
EXCHANGE_PREFIX = os.getenv("UPSTOX_EXCHANGE", "NSE_EQ")
SERIES_FILTER = {s.strip().upper() for s in os.getenv("SERIES_FILTER", "EQ").split(",") if s.strip()}
LIMIT = int(os.getenv("UPSTOX_LIMIT", "0")) # 0 = no limit
SLEEP_PER_CALL = float(os.getenv("SLEEP_PER_CALL", "0.35"))
SLEEP_PER_INSTRUMENT = float(os.getenv("SLEEP_PER_INSTRUMENT", "0.5"))
Fetch window for daily bars
DAILY_MIN_BARS = int(os.getenv("DAILY_MIN_BARS", "250")) # for indicator stability
DAILY_FETCH_BUFFER_DAYS = int(os.getenv("DAILY_FETCH_BUFFER_DAYS", "550")) # more to ensure >= 250 valid bars
Access token
ACCESS_TOKEN = os.getenv("UPSTOX_ACCESS_TOKEN", "").strip()
if not ACCESS_TOKEN:
logger.error("Please set the UPSTOX_ACCESS_TOKEN environment variable.")
sys.exit(1)
HEADERS = {
"Content-Type": "application/json",
"Accept": "application/json",
"Authorization": f"Bearer {ACCESS_TOKEN}",
}
------------- Time helpers -------------
IST = timezone(timedelta(hours=5, minutes=30))
def now_ist() -> datetime:
return datetime.now(tz=IST)
def today_ist() -> date:
return now_ist().date()
def fmt(d: date) -> str:
return d.strftime("%Y-%m-%d")
def parse_ts(ts: str) -> Optional[datetime]:
try:
if ts.endswith("Z"):
ts = ts.replace("Z", "+00:00")
dt = datetime.fromisoformat(ts)
if dt.tzinfo is None:
dt = dt.replace(tzinfo=IST)
return dt
except Exception:
return None
------------- API helpers -------------
def fetch_candles(instrument_key: str, unit: str, interval: int, to_date: str, from_date: str, retries: int = 2):
"""
Upstox path order: .../{unit}/{interval}/{to_date}/{from_date}
"""
encoded_instrument_key = quote(instrument_key, safe="")
url = f"{BASE_URL}/{encoded_instrument_key}/{unit}/{interval}/{to_date}/{from_date}"
last_err = None
for attempt in range(retries + 1):
try:
resp = requests.get(url, headers=HEADERS, timeout=60)
if resp.status_code == 200:
return resp.json()
last_err = RuntimeError(f"HTTP {resp.status_code}: {resp.text}")
# backoff only for rate-limits/transient
if resp.status_code in (429, 500, 502, 503, 504):
sleep_s = 0.6 * (attempt + 1)
logger.warning("Transient HTTP %s for %s; retrying in %.1fs", resp.status_code, instrument_key, sleep_s)
time.sleep(sleep_s)
continue
break
except Exception as e:
last_err = e
sleep_s = 0.6 * (attempt + 1)
logger.warning("Fetch error %s for %s; retrying in %.1fs", type(e).name, instrument_key, sleep_s)
time.sleep(sleep_s)
raise last_err if last_err else RuntimeError("Unknown fetch error")
def extract_ohlc_with_dt(candles: List[List[Any]]) -> Tuple[List[datetime], List[float], List[float], List[float]]:
"""
Cleans rows, enforces monotonic timestamps, ded...
✨ Let Copilot coding agent set things up for you — coding agent works faster and does higher quality work when set up for your repo.