Equity Advisor
How the system works — interactive overview
🇮🇳 India Monthly
⚡ Swing Trading
🧠 Intelligence Layer
🏦
India Monthly Portfolio
Long-term investing — Nifty 500 screener, AI advisory, automated queue execution
Goal: Pick the top 10 Indian stocks monthly from Nifty 500 using fundamentals + sentiment + momentum. You review the AI advisory, decide which to buy, queue them — and the system executes at 9:15 AM, places a trailing stop-loss GTT on fill, and updates stops daily at 2:30 PM.
🚂
Railway
API server, screener, sentiment services. Always-on cloud.
🖥️
Oracle VPS
Zerodha order execution. Static IP required by SEBI.
📱
Dashboard
Netlify-hosted. Your control panel on mobile.
🔍
Monthly Screener runs
Railway Monthly · Manual
📊Screens entire Nifty 500 universe — ~500 stocks ranked by composite score
⚙️Score = fundamentals (ROE, PE, PEG, revenue growth, D/E) + momentum (1M, 3M, 6M) + sector sentiment bonus/penalty
📐ATR-based stop-loss computed per stock using 14-day historical data from yfinance
🤖Claude LLM generates monthly advisory: action (HOLD / ADD / EXIT), holdings health, top picks with rationale
📤Results posted to: /portfolio/picks/upload · /portfolio/advisory/upload
screener.py → /portfolio/advisory (advisory + top_picks list) screener.py → /portfolio/picks (full bucket with ATR data)
📱
You review Advisory on dashboard
Netlify
📋Monthly Advisory section shows: AI action recommendation, holdings health (HOLD/TRIM/EXIT verdict per stock), top picks ranked by score
🎯Per-pick entry info fetched live from Zerodha: 20-DMA, 50-DMA, optimal entry, 14-day ATR trailing stop
📦Per-pick actions: Q = queue for 9:15 AM · + = add to portfolio immediately · ORDER = send to Zerodha now
Stock Picks section (top of India tab) shows only your actual live holdings. Advisory section shows screener recommendations — separate until you consciously move them.
🕘
9:15 AM — Orders placed automatically
Oracle VPS Cron 9:15 AM IST
📂india_entry.py fetches all queued BUY entries from Railway
💹Gets live open price from Zerodha (kite_executor.py on VPS port 5001)
⚖️Decision logic:
live > max gate (+3%) → ⏭ SKIP (gap-up, R/R broken) live ≤ 20-DMA entry → MARKET order at open live between entry → LIMIT order at 20-DMA and gate
📲Telegram notification sent with order summary
🔄Status updated: queuedorder_placed
Fill detected → GTT stop-loss placed
Railway Zerodha Webhook
📡Zerodha sends a postback webhook to Railway /kite/postback instantly when order fills
🏦Stock auto-added to your live portfolio (portfolio_live.json) — appears in Stock Picks section on dashboard
🛡️Trailing stop-loss GTT placed automatically at: fill_price − 1.5×ATR
🔄Status: order_placedfilled · GTT ID stored in queue
If limit order doesn't fill by 3:15 PM: india_cancel.py cancels it on Zerodha and resets status back to queued — retries automatically tomorrow at 9:15 AM until price pulls back to 20-DMA.
🔁
2:30 PM daily — Trailing stop ratcheted up
Oracle VPS Cron 2:30 PM IST
📈trailing_stop.py fetches all filled India entries from Railway
💹Gets today's high from Zerodha for each position
📐New stop = today's high − 1.5×ATR
☝️Stop is only ever moved up — never lowered. If new stop ≤ current stop, no change.
🔔Updates GTT on Zerodha + sends Telegram if stop moved
Exit: When price hits your GTT stop, Zerodha auto-sells. The postback fires → stock removed from live portfolio → trade logged to closed positions.
📊
Performance tracked vs Nifty 50 & 500
Railway
📸Daily P&L snapshots saved to performance_history.json
📉Dashboard chart shows portfolio cumulative return vs Nifty 50 and Nifty 500 benchmarks (1W / 1M / 3M / 1Y)
💰Closed positions tracked with realised P&L, win rate, and date range
📅 Daily Schedule (IST)
9:15 AM
📥 india_entry.py — Place buy orders for queued stocks
3:15 PM
⏸ india_cancel.py — Cancel unfilled LIMITs, re-queue for tomorrow
2:30 PM
📐 trailing_stop.py — Ratchet up GTT stops using day's high
Any time
⚡ /kite/postback — Fill detected, GTT placed, portfolio updated
Swing Trading
Short-term trades — daily scanner, news sentiment, R/R-based entries with auto GTT targets
Goal: Identify 5–10 day momentum setups daily from Nifty 500. The scanner runs overnight, scores stocks on technical + sentiment signals, and presents candidates. You review and queue — orders execute at 9:15 AM with automated stop-loss and two take-profit GTTs placed on fill.
🌙
Nightly scan — candidates identified
Railway 10:30 PM IST daily
📡swing_news_sentiment.py fetches today's news headlines for each Nifty 500 sector
🤖Claude LLM scores each sector: positive / mild_positive / neutral / cautious / negative
📊Technical scan scores stocks on: RSI, MACD, Bollinger, volume surge, 20-DMA position, momentum
🎯Combines tech score + sentiment score → top candidates with entry, stop, T1 (1.5×R), T2 (2.5×R), R/R ratio
🔀History blending: 40% today's signal + 60% 7-day rolling average → smooths out single-day noise
📤Results posted to Railway: /signals/upload (swing_news_sentiment) + /swing/candidates/upload
SCAN_DAYS = 1 (today's news only, blended with history) ATR_MULT = 2.0 (stop = entry − 2×ATR) T1 = entry + 1.5 × risk T2 = entry + 2.5 × risk
👁️
You review swing candidates on dashboard
Netlify
🃏Swing tab shows candidate cards sorted by score — entry price, stop-loss, T1, T2, conviction level, R/R ratio
🏷️Signal badges on each card: RSI oversold, MACD cross, BB squeeze, volume surge, gap-up, etc.
🌐Category Sentiment section shows LLM's sector verdict (positive/cautious) — feeds into candidate scoring
📦Per-candidate actions: Q = queue for tomorrow's open · Buy Now = enter immediately at LTP
Queuing opens a modal with live Zerodha price, entry basis, ATR computed from 200-day OHLCV, suggested quantity for your risk budget.
🕘
9:15 AM — Swing entry orders placed
Oracle VPS Cron 9:15 AM IST
📂swing_entry.py fetches all queued swing entries from Railway
💹Checks live open price vs optimal_entry and limit_price gate
⚖️Same logic as India: gap-up → skip, at/below entry → MARKET, between entry and gate → LIMIT
📲Telegram alert with entry summary for each stock
🎯
Fill detected → Stop + T1 + T2 GTTs placed
Railway Zerodha Webhook
📡Zerodha postback fires at /kite/postback instantly on fill
📦Stock added to Swing Live Positions panel on dashboard
🛡️Stop-loss GTT placed at scan's computed stop price (ATR-based)
🏆Target 1 GTT placed automatically: entry + 1.5 × risk (50% of position)
🏆Target 2 GTT placed automatically: entry + 2.5 × risk (remaining 50%)
GTT 1 (stop): sell ALL @ stop_loss (protective) GTT 2 (T1): sell 50% @ target1 (partial profit) GTT 3 (T2): sell 50% @ target2 (full exit) All three placed atomically on fill — no manual action needed.
Fully automated: When T1 fires, the postback handler automatically cancels the old stop GTT (full quantity) and places a new stop GTT for the remaining 50% shares at the same price. You get a Telegram confirmation. No manual action needed.
📐
2:30 PM daily — Trailing stop updated
Oracle VPS Cron 2:30 PM IST
📈Same trailing_stop.py handles swing positions alongside India monthly
💹Fetches today's high for each open swing position
☝️If today's high − 1.5×ATR > current stop → update the stop GTT upward
📆Max hold period tracked — alerts if position has been open longer than scan's max_days
🎯 Risk/Reward Structure
Stop
2×ATR
Target 1
1.5×R
Target 2
2.5×R
Min R/R
1.5×
Max Hold
10 days
🧠
Intelligence Layer
News sentiment, earnings signals, LLM analysis — feeds both India Monthly and Swing services
Goal: Provide context-aware market intelligence that adjusts stock scores and surfaces macro themes. Three pipelines run on Railway and feed their outputs into a shared signal store — the screener and swing scanner consume these signals when making decisions.
📰
Swing News Sentiment — daily sector analysis
Railway Service 10:30 PM IST daily
🌐Scrapes today's financial news headlines from NSE/BSE sectors
🤖Claude LLM classifies each sector's news sentiment:
positive / mild_positive / neutral / cautious / negative
🔀History blending: raw signal weighted 40% today + 60% 7-day rolling average
📤Posts to Railway: swing_news_sentiment + swing_sentiment_history
📊Dashboard: "Category Sentiment (LLM Analysed)" section shows per-sector verdict with reasoning
⚙️Consumed by: Swing scanner (sector bonus/penalty) + Monthly screener (sentiment_adj score)
Score adj: positive → +5 mild_positive → +2 neutral → 0 cautious → −2 negative → −5
📈
Monthly Earnings Sentiment — macro + earnings analysis
Railway Service Monthly · before screener
📋Analyses recent earnings seasons: beat/miss rates per sector, guidance tone, revenue trends
🏛️Reads RBI/SEBI/PIB policy signals — rate decisions, liquidity, sector-specific regulations
🤖Claude LLM synthesises into per-sector earnings signal: positive / neutral / cautious / negative
🔗First tries to load swing_sentiment_history from Railway — if available, blends with earnings data for richer context
📤Posts monthly_earnings_sentiment to Railway signals store
⚙️Consumed by: Monthly screener's earnings_adj score adjustment per stock
Cross-service data sharing: swing_news_sentiment POSTs its history to /signals/upload so monthly_earnings_sentiment can read it even though they're separate Railway services with separate volumes.
🏛️
Signal Store — shared API across all services
Railway API
🗄️Railway API's /signals endpoint serves all collected intelligence in one response
📦Contains: swing_news_sentiment · monthly_earnings_sentiment · swing_sentiment_history · policy_signals · llm_synthesis
💾Each signal type persisted to /data/{type}.json on Railway's persistent volume
📱Dashboard reads /signals at load time — powers Category Sentiment, AI Macro Verdict, sector badges
GET /signals → { swing_news_sentiment: { signals: { IT: "positive", ... } }, monthly_earnings_sentiment: { ... }, swing_sentiment_history: [ {date, signals}, ... ], policy_signals: { ... }, llm_synthesis: { ... } }
🔄
How signals flow into stock scores
Screener Logic
1️⃣Base score = fundamentals (ROE, PE, PEG, D/E, revenue growth) + momentum (1M, 3M, 6M)
2️⃣Sector sentiment adj = swing_news_sentiment verdict for the stock's NSE sector (±0 to ±5 pts)
3️⃣Earnings adj = monthly_earnings_sentiment verdict for sector (±0 to ±5 pts)
4️⃣Final score = globally normalised across all 500 stocks → top 10 selected
🚫Stocks in sectors with negative sentiment are excluded entirely from picks (circuit breaker)
final_score = base + sentiment_adj + earnings_adj (normalised 0–100 across Nifty 500 universe) negative sector → stock excluded regardless of fundamentals
📊 Signal Key
positive +5 score bonus · sector included · confidence for new entries
mild_positive +2 score bonus · sector included · moderate confidence
neutral No adjustment · fundamentals drive the decision
cautious −2 score penalty · sector still eligible · watch closely
negative −5 penalty + sector excluded entirely from picks