Khanna, Videh Rakesh Rakesh commited on
Commit
8d1eb7b
·
1 Parent(s): 4bb9928

Add NVIDIA NIM as free-tier LLM provider; research + UI updates

Browse files

- Wire NVIDIA NIM (build.nvidia.com) into llm_client provider chain as
a zero-cost 7th cloud provider via shared _try_openai_compatible driver
(inherits timeout/retry/cooldown/daily-exhausted machinery)
- Default to fast nemotron-super-49b (70b slug read-times-out on free tier)
- Update CLAUDE.md, project_context.json provider-chain metadata
- gitignore paper_trading.db.local_backup* and models_holdout_eval/
- Misc research scripts + UI/database updates

.gitignore CHANGED
@@ -24,10 +24,12 @@ venv/
24
  paper_trading.db.bak*
25
  paper_trading.db.backup
26
  paper_trading.db.pre_regrade*
 
27
  server.log
28
  learnings.json
29
  ohlcv_cache.db
30
  ml_predictor/training_data.csv
 
31
  ml_predictor/training_data_extra.csv
32
  ml_predictor/models/manifest.json.bak*
33
  research/ml_backtest_results.csv
 
24
  paper_trading.db.bak*
25
  paper_trading.db.backup
26
  paper_trading.db.pre_regrade*
27
+ paper_trading.db.local_backup*
28
  server.log
29
  learnings.json
30
  ohlcv_cache.db
31
  ml_predictor/training_data.csv
32
+ ml_predictor/models_holdout_eval/
33
  ml_predictor/training_data_extra.csv
34
  ml_predictor/models/manifest.json.bak*
35
  research/ml_backtest_results.csv
CLAUDE.md CHANGED
@@ -108,7 +108,7 @@ Inspired by TauricResearch/TradingAgents multi-agent debate pattern.
108
  - `{provider}:{model}` — single-call fallback
109
  - `heuristic` — no API key
110
 
111
- **LLM backends (provider chain):** OpenRouter free tier → Groq (llama-3.3-70b-versatile → llama-3.1-8b-instant) → Cerebras (llama-3.3-70b → llama-3.1-8b) → HuggingFace Router (novita, llama-3.1-8b-instruct) → **Gemini (2.5-flash, ~1,500/day) → SambaNova (70B, persistent free)** → Ollama (local only, `OLLAMA_ENDPOINT` must be set). GitHub Models removed — `GITHUB_TOKEN` unused. Gemini/SambaNova are appended LAST in `_CLOUD_PROVIDERS` so the happy path is unchanged, but the dynamic availability sort auto-promotes them when the first four degrade — adding large independent daily capacity so `_all_cloud_daily_exhausted()` (which triggers the slow single-Ollama funnel) rarely fires. Both no-op without their API key.
112
 
113
  **AI unavailable root cause** — "⚠ AI unavailable" means ALL cloud providers failed AND Ollama failed/unavailable. Most common cause: free-tier rate limits exhausted during a 150-stock batch scan (~150–450 LLM calls). Fix: ensure OpenRouter + Groq + Cerebras + HF keys are all set in HF Spaces Secrets. The "Signals active: 0" part of the error is a separate issue — the stock has no technical strategy signals firing, independent of AI.
114
 
@@ -399,6 +399,12 @@ SAMBANOVA_API_KEY 6th fallback. SambaNova Cloud — persistent free 70B/405B.
399
  https://cloud.sambanova.ai. No-op if unset.
400
  SAMBANOVA_MODEL Default Meta-Llama-3.3-70B-Instruct.
401
  SAMBANOVA_FALLBACK_MODELS Comma-separated fallbacks (default: Meta-Llama-3.1-8B-Instruct).
 
 
 
 
 
 
402
  FRED_API_KEY Optional. Free FRED API key for US macro indicators (fred_data.py).
403
  Falls back to yfinance proxies if not set.
404
  Get one at: https://fred.stlouisfed.org/docs/api/api_key.html
 
108
  - `{provider}:{model}` — single-call fallback
109
  - `heuristic` — no API key
110
 
111
+ **LLM backends (provider chain):** OpenRouter free tier → Groq (llama-3.3-70b-versatile → llama-3.1-8b-instant) → Cerebras (llama-3.3-70b → llama-3.1-8b) → HuggingFace Router (novita, llama-3.1-8b-instruct) → **Gemini (2.5-flash, ~1,500/day) → SambaNova (70B, persistent free) → NVIDIA NIM (nemotron-super-49b, free)** → Ollama (local only, `OLLAMA_ENDPOINT` must be set). GitHub Models removed — `GITHUB_TOKEN` unused. Gemini/SambaNova/NVIDIA are appended LAST in `_CLOUD_PROVIDERS` so the happy path is unchanged, but the dynamic availability sort auto-promotes them when the first four degrade — adding large independent daily capacity so `_all_cloud_daily_exhausted()` (which triggers the slow single-Ollama funnel) rarely fires. All three no-op without their API key. NVIDIA routes through the shared `_try_openai_compatible` driver, so it inherits the identical timeout/retry/cooldown/daily-exhausted machinery.
112
 
113
  **AI unavailable root cause** — "⚠ AI unavailable" means ALL cloud providers failed AND Ollama failed/unavailable. Most common cause: free-tier rate limits exhausted during a 150-stock batch scan (~150–450 LLM calls). Fix: ensure OpenRouter + Groq + Cerebras + HF keys are all set in HF Spaces Secrets. The "Signals active: 0" part of the error is a separate issue — the stock has no technical strategy signals firing, independent of AI.
114
 
 
399
  https://cloud.sambanova.ai. No-op if unset.
400
  SAMBANOVA_MODEL Default Meta-Llama-3.3-70B-Instruct.
401
  SAMBANOVA_FALLBACK_MODELS Comma-separated fallbacks (default: Meta-Llama-3.1-8B-Instruct).
402
+ NVIDIA_API_KEY 7th fallback. NVIDIA NIM (build.nvidia.com) — free API key, OpenAI-compatible,
403
+ large independent free-tier capacity (Llama-3.3-70B / Nemotron / DeepSeek /
404
+ Qwen). Zero cost. Get one at https://build.nvidia.com. No-op if unset.
405
+ NVIDIA_MODEL Default nvidia/llama-3.3-nemotron-super-49b-v1 (the meta/llama-3.3-70b-instruct
406
+ slug read-times-out >30s on the free tier — dropped 2026-07-24).
407
+ NVIDIA_FALLBACK_MODELS Comma-separated fallbacks (default: meta/llama-3.1-8b-instruct).
408
  FRED_API_KEY Optional. Free FRED API key for US macro indicators (fred_data.py).
409
  Falls back to yfinance proxies if not set.
410
  Get one at: https://fred.stlouisfed.org/docs/api/api_key.html
database.py CHANGED
@@ -129,7 +129,22 @@ def _atomic_snapshot(dest: str) -> bool:
129
  return False
130
 
131
 
 
 
 
 
 
 
 
 
 
 
 
 
 
132
  def hf_upload_db():
 
 
133
  token = _hf_token()
134
  if not token or not os.path.exists(DB_PATH):
135
  return
@@ -177,6 +192,10 @@ def setup_hf_persistence():
177
  _hf_download_db()
178
  import atexit
179
  atexit.register(_checkpoint_on_exit)
 
 
 
 
180
  threading.Thread(target=_backup_loop, daemon=True).start()
181
 
182
 
 
129
  return False
130
 
131
 
132
+ def _backup_enabled() -> bool:
133
+ """Only the real HF Space should back the DB up to the shared Hub repo.
134
+
135
+ Otherwise a stray local `python app.py` that has the production HF_TOKEN in .env will
136
+ also run the backup loop and clobber the Space's data (multi-writer race → data loss).
137
+ HF Spaces always set SPACE_ID / SPACE_HOST. Set FORCE_DB_BACKUP=1 to override for a
138
+ single, intentional non-Space writer.
139
+ """
140
+ if os.environ.get("FORCE_DB_BACKUP") == "1":
141
+ return True
142
+ return bool(os.environ.get("SPACE_ID") or os.environ.get("SPACE_HOST"))
143
+
144
+
145
  def hf_upload_db():
146
+ if not _backup_enabled():
147
+ return
148
  token = _hf_token()
149
  if not token or not os.path.exists(DB_PATH):
150
  return
 
192
  _hf_download_db()
193
  import atexit
194
  atexit.register(_checkpoint_on_exit)
195
+ if not _backup_enabled():
196
+ print("[DB] Backup loop disabled (not an HF Space; set FORCE_DB_BACKUP=1 to override). "
197
+ "Startup restore still ran; this instance will NOT upload to HF Hub.", flush=True)
198
+ return
199
  threading.Thread(target=_backup_loop, daemon=True).start()
200
 
201
 
llm_client.py CHANGED
@@ -60,11 +60,13 @@ _PROVIDER_STATUS: dict = {
60
  "cerebras": {"avail_at": 0.0, "daily_exhausted": False, "fail_streak": 0},
61
  "huggingface": {"avail_at": 0.0, "daily_exhausted": False, "fail_streak": 0},
62
  # Extra free tiers — large independent daily capacity (Gemini ~1,500 req/day, SambaNova
63
- # persistent free 70B). Appended LAST so the happy path is unchanged, but the dynamic
64
- # availability sort auto-promotes them to the front the moment the first four degrade —
65
- # which is exactly when the exhaustion→single-Ollama funnel used to bite. No-op without keys.
 
66
  "gemini": {"avail_at": 0.0, "daily_exhausted": False, "fail_streak": 0},
67
  "sambanova": {"avail_at": 0.0, "daily_exhausted": False, "fail_streak": 0},
 
68
  }
69
  _PROVIDER_DAILY_RESET: str = "" # "YYYY-MM-DD" IST string; reset daily flags on date change
70
 
@@ -105,7 +107,7 @@ _OLLAMA_WARMUP_BACKOFF_SECS: int = 45
105
  _OLLAMA_CHAT_TIMEOUT: int = 70 # 70s: warmup confirms model is loaded, so 70s is enough for 512 tokens
106
 
107
  # Canonical cloud provider order (original preference before runtime reordering)
108
- _CLOUD_PROVIDERS = ["openrouter", "groq", "cerebras", "huggingface", "gemini", "sambanova"]
109
  _PROVIDER_ORDER = _CLOUD_PROVIDERS
110
 
111
 
@@ -261,6 +263,7 @@ _PROBE_CONFIG: dict = {
261
  "huggingface": ("HF_TOKEN", "https://router.huggingface.co/novita/v3/openai/chat/completions", "HF_INFERENCE_MODEL", "meta-llama/Llama-3.1-8B-Instruct"),
262
  "gemini": ("GEMINI_API_KEY", "https://generativelanguage.googleapis.com/v1beta/openai/chat/completions", "GEMINI_MODEL", "gemini-flash-latest"),
263
  "sambanova": ("SAMBANOVA_API_KEY", "https://api.sambanova.ai/v1/chat/completions", "SAMBANOVA_MODEL", "Meta-Llama-3.3-70B-Instruct"),
 
264
  }
265
 
266
 
@@ -694,6 +697,29 @@ def make_chat_call(
694
  api_key, models_to_try, daily_on_429=False,
695
  )
696
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
697
  # ── Provider name → function map ─────────────────────────────────────────
698
  _PROVIDER_FNS = {
699
  "openrouter": _try_openrouter,
@@ -702,6 +728,7 @@ def make_chat_call(
702
  "huggingface": _try_huggingface,
703
  "gemini": _try_gemini,
704
  "sambanova": _try_sambanova,
 
705
  }
706
 
707
  # ── Ollama (own server, no rate limits — handled outside cloud semaphore) ─
 
60
  "cerebras": {"avail_at": 0.0, "daily_exhausted": False, "fail_streak": 0},
61
  "huggingface": {"avail_at": 0.0, "daily_exhausted": False, "fail_streak": 0},
62
  # Extra free tiers — large independent daily capacity (Gemini ~1,500 req/day, SambaNova
63
+ # persistent free 70B, NVIDIA NIM free 70B/Nemotron). Appended LAST so the happy path is
64
+ # unchanged, but the dynamic availability sort auto-promotes them to the front the moment the
65
+ # first four degrade — which is exactly when the exhaustion→single-Ollama funnel used to bite.
66
+ # No-op without keys.
67
  "gemini": {"avail_at": 0.0, "daily_exhausted": False, "fail_streak": 0},
68
  "sambanova": {"avail_at": 0.0, "daily_exhausted": False, "fail_streak": 0},
69
+ "nvidia": {"avail_at": 0.0, "daily_exhausted": False, "fail_streak": 0},
70
  }
71
  _PROVIDER_DAILY_RESET: str = "" # "YYYY-MM-DD" IST string; reset daily flags on date change
72
 
 
107
  _OLLAMA_CHAT_TIMEOUT: int = 70 # 70s: warmup confirms model is loaded, so 70s is enough for 512 tokens
108
 
109
  # Canonical cloud provider order (original preference before runtime reordering)
110
+ _CLOUD_PROVIDERS = ["openrouter", "groq", "cerebras", "huggingface", "gemini", "sambanova", "nvidia"]
111
  _PROVIDER_ORDER = _CLOUD_PROVIDERS
112
 
113
 
 
263
  "huggingface": ("HF_TOKEN", "https://router.huggingface.co/novita/v3/openai/chat/completions", "HF_INFERENCE_MODEL", "meta-llama/Llama-3.1-8B-Instruct"),
264
  "gemini": ("GEMINI_API_KEY", "https://generativelanguage.googleapis.com/v1beta/openai/chat/completions", "GEMINI_MODEL", "gemini-flash-latest"),
265
  "sambanova": ("SAMBANOVA_API_KEY", "https://api.sambanova.ai/v1/chat/completions", "SAMBANOVA_MODEL", "Meta-Llama-3.3-70B-Instruct"),
266
+ "nvidia": ("NVIDIA_API_KEY", "https://integrate.api.nvidia.com/v1/chat/completions", "NVIDIA_MODEL", "nvidia/llama-3.3-nemotron-super-49b-v1"),
267
  }
268
 
269
 
 
697
  api_key, models_to_try, daily_on_429=False,
698
  )
699
 
700
+ def _try_nvidia():
701
+ # NVIDIA NIM (build.nvidia.com) — free API key, OpenAI-compatible, large independent
702
+ # daily capacity across many models (Llama-3.3-70B, Nemotron, DeepSeek, Qwen). Zero cost.
703
+ # daily_on_429=False: NIM free-tier 429s are per-minute RPM limits that recover in seconds,
704
+ # so a short cooldown keeps it in rotation instead of benching it until midnight.
705
+ if not _is_provider_available("nvidia", fast_fail_on_rate_limit):
706
+ return None
707
+ api_key = os.environ.get("NVIDIA_API_KEY", "").strip()
708
+ if not api_key:
709
+ logger.debug("NVIDIA NIM skipped — NVIDIA_API_KEY not set")
710
+ return None
711
+ primary = (os.environ.get("NVIDIA_MODEL") or "nvidia/llama-3.3-nemotron-super-49b-v1").strip()
712
+ fallback_raw = os.environ.get(
713
+ "NVIDIA_FALLBACK_MODELS",
714
+ "meta/llama-3.1-8b-instruct",
715
+ )
716
+ fallbacks = [m.strip() for m in fallback_raw.split(",") if m.strip() and m.strip() != primary]
717
+ models_to_try = [primary] + fallbacks[:3]
718
+ return _try_openai_compatible(
719
+ "nvidia", "https://integrate.api.nvidia.com/v1",
720
+ api_key, models_to_try, daily_on_429=False,
721
+ )
722
+
723
  # ── Provider name → function map ─────────────────────────────────────────
724
  _PROVIDER_FNS = {
725
  "openrouter": _try_openrouter,
 
728
  "huggingface": _try_huggingface,
729
  "gemini": _try_gemini,
730
  "sambanova": _try_sambanova,
731
+ "nvidia": _try_nvidia,
732
  }
733
 
734
  # ── Ollama (own server, no rate limits — handled outside cloud semaphore) ─
project_context.json ADDED
@@ -0,0 +1,129 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "projectInfo": {
3
+ "name": "PaperTrade",
4
+ "description": "NSE Indian-equity short-term price-direction prediction engine + paper-trading book. Predicts 1D/3D/INTRADAY direction using backtested technical strategies, a standalone supervised ML quantile model, macro gates, news sentiment, and an LLM bull/bear/fundamentals debate. Served via a Flask web app and an MCP server.",
5
+ "platform": "Python backend (Flask web app + MCP server) with an offline-trained scikit-learn ML model",
6
+ "language": "Python 3.13",
7
+ "deployment": {
8
+ "primary": "Hugging Face Spaces (persistent /data disk, container). Think HF Spaces first for storage/paths/env.",
9
+ "local": "python app.py from project root",
10
+ "notes": [
11
+ "Use os.path.dirname(__file__) or HF paths for data files; NOT pathlib.Path(__file__).parent.",
12
+ "Pickle/file caches do NOT survive HF container restarts — use SQLite (paper_trading.db) instead.",
13
+ "Static JS/CSS is inline in templates (HF CSP blocks external CDNs).",
14
+ "Secrets: .env locally; HF Spaces Secrets tab in prod; sync via export_env_secrets.py."
15
+ ]
16
+ }
17
+ },
18
+ "architecturalKnowledge": {
19
+ "pattern": "Layered prediction pipeline + service modules (not MVC/MVVM)",
20
+ "confidence": 95,
21
+ "layers": [
22
+ "Entry points: app.py (Flask UI + paper-trading book + validation), stock_predictor_mcp.py (MCP tools)",
23
+ "Prediction core: predictor_core.py (public API predict_stock_v2 / rank_stocks_v2)",
24
+ "Signal/feature layer: trial_run.py (S1..S20 strategy signals), ml_combiner.py (ML feature funcs)",
25
+ "Context providers: macro_context.py, fred_data.py, news_sentiment.py, fundamentals.py, sector_pulse.py, fii_flow.py, social_sentiment.py, intraday_live.py, price_targets.py",
26
+ "LLM layer: ai_forecast.py (bull/bear/fundamentals debate) -> llm_client.py (provider chain) / ollama_client.py",
27
+ "Data layer: data_sources.py (multi-source OHLCV + live price), universe.py (dynamic NSE universe), database.py (SQLite: trades, orders, snapshots, postmortems)",
28
+ "Standalone ML: ml_predictor/ (features -> dataset -> train -> infer, 21 committed joblib estimators)",
29
+ "Research/backtests: research/ (offline analysis, never imported by prod prediction path)"
30
+ ],
31
+ "dataFlowPredictionPipeline": [
32
+ "1. Market gates (VIX>25 hard block; VIX 20-25 size cut; Nifty<EMA200 -40% expected; macro/FRED risk-off cuts)",
33
+ "2. Strategy signals (20+ NSE-backtested booleans, fire if triggered in last 5 bars)",
34
+ "3. ML feature score (11 weighted features -> 0-100 + logistic prob)",
35
+ "4. News sentiment (Claude Haiku -> BULLISH/NEUTRAL/BEARISH)",
36
+ "5. Sector pulse (NSE sector heatmap -> leading/lagging flag)",
37
+ "6. Fundamentals (PE/D-E/ROE/FCF score, 24h cache)",
38
+ "7. AI forecast (up to 4-call LLM debate -> synthesis JSON) with heuristic fallback",
39
+ "8. Confidence scoring (additive -> HIGH/MEDIUM/LOW)",
40
+ "9. Risk (ATR14 stop, R:R targets scaled to timeframe)"
41
+ ]
42
+ },
43
+ "technologyStack": {
44
+ "web": "Flask (server-rendered templates/index.html, inline JS/CSS in static/)",
45
+ "mcp": "stock_predictor_mcp.py exposes predict_stocks, rank_best_stocks",
46
+ "ml": "scikit-learn HistGradientBoosting quantile regressors + isotonic-calibrated direction classifier; joblib persistence. No lightgbm/xgboost/torch (HF-safe).",
47
+ "dataSources": "yfinance + NSE archives + Twelve Data + Alpha Vantage (fallback chain in data_sources.py)",
48
+ "llmProviderChain": "OpenRouter free -> Groq -> Cerebras -> HuggingFace Router -> Gemini -> SambaNova -> NVIDIA NIM -> Ollama (local last-resort). GitHub Models removed.",
49
+ "database": "SQLite (paper_trading.db) — trades, pending orders, prediction snapshots, postmortems, ohlcv_cache blob",
50
+ "testing": "pytest (tests/test_api_contract.py — Flask endpoint schema/type checks)",
51
+ "caches": [
52
+ ".universe_cache.json (24h fresh / 7d stale, /data on HF)",
53
+ "fred_macro_cache.json (24h)",
54
+ "fundamentals_cache.json (24h/ticker)",
55
+ "sector pulse (5-min in-memory)",
56
+ "ohlcv_cache table in paper_trading.db (SQLite blob, NOT file-based)"
57
+ ]
58
+ },
59
+ "moduleGraph": {
60
+ "predictor_core.py": {"role": "Main prediction API (used by MCP + Flask). MUST stay stable.", "publicApi": ["predict_stock_v2(ticker, start_date, end_date, ...)", "rank_stocks_v2(...)", "timeframe_to_dates(tf)", "get_ml_feature_score()", "DEFAULT_UNIVERSE"]},
61
+ "trial_run.py": {"role": "All strategy signal generators S1..S20, S_CTRIO, S_CAPFLOW, S_SEASONAL etc. NSE-verified stats in predictor_core._STRATEGY_STATS_DEFAULT — do not change without re-running backtest.", "publicApi": ["gen_s1..gen_s20", "gen_s_confluence_trio", "gen_mfs", "gen_nira", "gen_ped", "gen_supertrend"]},
62
+ "ml_combiner.py": {"role": "ML feature functions used by predictor_core.get_ml_feature_score", "publicApi": ["bollinger_position", "ema_stack_score", "shadow_flag", "build_feature_matrix"]},
63
+ "ai_forecast.py": {"role": "LLM bull/bear/fundamentals debate -> synthesis. Trigger guardrails + ATR clamp (prod-only). Output: should_buy, entry_price, direction, ranges.", "publicApi": ["get_ai_forecast(...)"]},
64
+ "llm_client.py": {"role": "Provider chain + Ollama fallback + rate-limit handling", "publicApi": ["reset_ollama_state()"]},
65
+ "data_sources.py": {"role": "Multi-source OHLCV + live price with fallback chains. Do NOT change signatures.", "publicApi": ["fetch_ohlcv(ticker, period)", "fetch_live_price(ticker, allow_delayed=True)", "cached_tickers(period)", "fetch_market_data(period_days)"]},
66
+ "universe.py": {"role": "Dynamic full-NSE universe (~2062 EQ stocks). Replaces old nse_universe.py.", "publicApi": ["get_universe(force_refresh=False)", "refresh_universe()"]},
67
+ "database.py": {"role": "SQLite paper-trading book + prediction validation audit trail", "publicApi": ["get_open_trades_with_live_prices()", "save_prediction_snapshot(...)", "get_prediction_snapshots(...)", "get_validation_summary(...)", "save_postmortem(...)"]},
68
+ "macro_context.py": {"role": "Macro gates (S&P500, USD/INR, crude) + FRED regime gate", "publicApi": ["get_macro_gate()", "global_risk_on"]},
69
+ "fred_data.py": {"role": "US macro indicators", "publicApi": ["get_fred_macro()", "get_fred_gate()"]},
70
+ "fundamentals.py": {"role": "Stock fundamentals scorer (PE/D-E/rev/FCF/ROE). MUST stay stable.", "publicApi": ["get_fundamentals(ticker)"]},
71
+ "sector_pulse.py": {"role": "NSE 10-sector heatmap + rotation. MUST stay stable.", "publicApi": ["get_sector_pulse()", "get_sector_for_ticker(ticker)"]},
72
+ "risk_engine.py": {"role": "Portfolio risk metrics (Sharpe, drawdown, beta, Kelly)", "publicApi": ["compute_risk_metrics()"]},
73
+ "top5_picker.py": {"role": "Top picks (up to 20) INTRADAY/1D/3D, concurrent, ATR-ranked, progressive streaming", "publicApi": ["get_top5_picks(top_n=20, _universe_size=700, progress_cb=None)", "get_weekly_picks(...)"]},
74
+ "app.py": {"role": "Flask UI + paper-trading book + validation + all /api routes", "publicApi": ["Flask endpoints (see flaskEndpoints)"]},
75
+ "stock_predictor_mcp.py": {"role": "MCP server entry point. Imports predictor_core.", "publicApi": ["predict_stocks", "rank_best_stocks"]},
76
+ "ml_predictor/features.py": {"role": "Shared point-in-time feature builder (37 lookahead-safe features)", "publicApi": ["compute_features(...)", "FEATURE_COLUMNS"]},
77
+ "ml_predictor/infer.py": {"role": "MLPredictor — quantile forecast per TF. Batch via _raw_predict (fast); per-row _predict_tf is ~1000x slower.", "publicApi": ["get_ml_predictor()", "MLPredictor.predict_all_tf(ticker, live_price, today_high, news_score)", "MLPredictor._raw_predict(tf, X)", "MLPredictor._derive(...)"]},
78
+ "ml_predictor/train.py": {"role": "Fit 21 estimators + manifest.json (offline)", "publicApi": ["train"]},
79
+ "ml_predictor/dataset.py": {"role": "Build training_data.csv from ohlcv cache (offline). Labels up/dn = max/min excursion; dir = excess-of-Nifty.", "publicApi": ["_fwd_labels(...)"]}
80
+ },
81
+ "flaskEndpoints": {
82
+ "predictions": ["POST /api/predict", "POST /api/rank", "GET /api/top5", "GET /api/watchlist-picks", "GET /api/watchlist-pick/<ticker>", "GET /api/universe", "POST /api/universe/refresh", "GET /api/search", "GET /api/chart/<ticker>", "GET /api/live-price/<ticker>", "GET /api/ml-predict/<ticker>"],
83
+ "context": ["GET /api/sector-pulse", "GET /api/fundamentals/<ticker>", "GET /api/portfolio", "GET /api/portfolio-insight/<ticker>", "GET /api/signal-accuracy"],
84
+ "paperTrading": ["GET /api/open-trades", "GET /api/trades/open", "GET /api/trades/history", "POST /api/trades", "POST /api/trades/<id>/close", "GET /api/trades/<id>/price", "POST /api/trades/check-stops", "GET /api/orders/pending", "POST /api/orders/check", "POST /api/orders/<id>/cancel", "GET|POST /api/watchlist", "DELETE /api/watchlist/<ticker>"],
85
+ "validation": ["GET /api/prediction-snapshots", "GET /api/prediction-validation", "GET /api/prediction-misses", "GET /api/validation/pending", "POST /api/validation/execute", "GET /api/validation/summary", "GET /api/postmortems", "POST /api/postmortem"]
86
+ },
87
+ "criticalConstraintsDoNotBreak": [
88
+ "predict_stock_v2 / rank_stocks_v2 / timeframe_to_dates signatures (used by MCP + Flask).",
89
+ "data_sources.fetch_ohlcv / fetch_live_price signatures.",
90
+ "universe.get_universe, fundamentals.get_fundamentals, sector_pulse.get_sector_pulse, fred_data.get_fred_macro/get_fred_gate signatures.",
91
+ "ml_predictor: compute_features, MLPredictor.predict_all_tf, MLPredictor.predict signatures.",
92
+ "trial_run.py strategy stats in predictor_core._STRATEGY_STATS_DEFAULT are NSE-verified — do not change without re-running the backtest.",
93
+ "Do not add lightgbm/xgboost/torch (HF Spaces image safety).",
94
+ "Do not put data files behind pathlib.Path(__file__).parent; do not rely on file/pickle caches surviving HF restarts."
95
+ ],
96
+ "codeGenerationGuidelines": {
97
+ "paths": "os.path.dirname(__file__) or HF /data paths; never pathlib(__file__).parent for data.",
98
+ "persistence": "SQLite (paper_trading.db) for anything that must survive HF restarts — not pickle/file caches.",
99
+ "frontend": "Inline JS/CSS only (HF CSP blocks external URLs). Bump cache-buster ?v=YYYYMMDD<letter> in templates/index.html on JS/CSS change.",
100
+ "stability": "Keep listed public signatures stable; prod prediction path must not import research/.",
101
+ "ml": "Batch model inference with MLPredictor._raw_predict over a full matrix; never loop _predict_tf per row in bulk jobs.",
102
+ "testing": "Add/extend tests/test_api_contract.py for new endpoints (schema + field types).",
103
+ "backtestMetrics": "Distinguish MidHit (band-midpoint, ~90%, soft) from DirAcc/DirHit (directional, ~40-50%, the real metric). Use training_data_extra.csv (has 12 extra features); training_data.csv lacks them."
104
+ },
105
+ "envVars": {
106
+ "llmKeys": ["OPENROUTER_API_KEY", "GROQ_API_KEY", "CEREBRAS_API_KEY", "HF_TOKEN", "GEMINI_API_KEY", "SAMBANOVA_API_KEY", "NVIDIA_API_KEY", "OLLAMA_ENDPOINT", "OLLAMA_MODEL"],
107
+ "dataKeys": ["FRED_API_KEY (optional)", "ALPHA_VANTAGE_API_KEY (optional)"],
108
+ "unused": ["GITHUB_TOKEN (GitHub Models removed)", "ANTHROPIC_API_KEY (currently empty)"],
109
+ "tuning": ["BACKTEST_LLM_PACE_SECS", "ML_EXCESS_LABELS", "ML_INTRADAY_FAR_MULT/MED_MULT/NEAR_MULT", "HF_ML_MODEL_REPO_ID"]
110
+ },
111
+ "researchScripts": {
112
+ "note": "Offline analysis only; never imported by the production prediction path.",
113
+ "key": ["research/backtest.py (LLM prompt accuracy 1D/3D/5D)", "research/ml_backtest.py (ML accuracy + target-exit P&L; authoritative DirAcc/MidHit table)", "research/ml_selection_backtest.py (top-N selection edge)", "research/ml_intraday_backtest.py (true 15-min intraday)", "research/validate_on_trades.py (validate on real paper-trade dates)", "research/strategy_combo_swing.py (strategy-confluence swing study — batched inference)"]
114
+ },
115
+ "knowledgeBaseAndMemory": {
116
+ "livingTruth": ["CLAUDE.md (authoritative architecture + pipeline + calibration notes)", "project_context.json (this file — structured index)"],
117
+ "memoryDir": "memory/ (thin pointers + legacy findings)",
118
+ "repoScopedNotes": "/memories/repo/ (agent notes: metrics, calibration, provider chain, gotchas)",
119
+ "refreshPolicy": "Update CLAUDE.md + project_context.json after each meaningful feature (see .github/skills/context-updater)."
120
+ },
121
+ "contextMetadata": {
122
+ "generatedDate": "2026-07-24",
123
+ "commitHash": "4bb9928",
124
+ "pythonFilesAtRoot": 30,
125
+ "source": "Derived from CLAUDE.md (633 lines) + repository structure",
126
+ "maturity": "Established/Enterprise (30+ root modules, ml_predictor package, research suite)",
127
+ "pathPolicy": "Relative paths only; no machine-specific or personal directories."
128
+ }
129
+ }
research/ml_selection_backtest.py CHANGED
@@ -37,6 +37,7 @@ if _PROJ_ROOT not in sys.path:
37
  from ml_predictor.features import FEATURE_COLUMNS # noqa: E402
38
  from ml_predictor.infer import MLPredictor # noqa: E402
39
  from ml_predictor.dataset import _cached_tickers, _load_ticker, DEFAULT_STEP # noqa: E402
 
40
 
41
  DEFAULT_CSV = os.path.join(_PROJ_ROOT, "ml_predictor", "training_data.csv")
42
  OUT_CSV = os.path.join(os.path.dirname(os.path.abspath(__file__)), "ml_selection_results.csv")
@@ -102,7 +103,8 @@ def _fwd_ret(close: pd.Series, date: pd.Timestamp, h: int) -> float:
102
 
103
  def run(csv_path: str = DEFAULT_CSV, top_n: int = 10, step: int = 1,
104
  one_date: str | None = None, rank_mode: str = "expmove",
105
- min_conf: str = "LOW", filters: set | None = None) -> pd.DataFrame:
 
106
  filters = filters or set()
107
  predictor = MLPredictor()
108
  if not predictor.available:
@@ -111,7 +113,12 @@ def run(csv_path: str = DEFAULT_CSV, top_n: int = 10, step: int = 1,
111
  df = pd.read_csv(csv_path)
112
  df["date"] = pd.to_datetime(df["date"])
113
  holdout_start = predictor.manifest.get("holdout_start")
114
- oos = df[df["date"] >= pd.to_datetime(holdout_start)].copy() if holdout_start else df
 
 
 
 
 
115
  print(f" Model: rank by {_SELECTOR_TF} BULLISH · mode={rank_mode} · min_conf={min_conf} · "
116
  f"filters={sorted(filters) or 'none'} · top-{top_n} picks/day · cost {ROUND_TRIP_COST_PCT}% round-trip")
117
  print(f" Out-of-sample rows: {len(oos):,} · loading close series for realized fwd returns…")
@@ -198,10 +205,51 @@ def run(csv_path: str = DEFAULT_CSV, top_n: int = 10, step: int = 1,
198
  _one_day_report(one_date, picks_df, days_df)
199
  else:
200
  _summary(days_df, picks_df, top_n)
 
 
201
  print(f"\n ✓ Wrote per-pick detail → {OUT_CSV}")
202
  return days_df
203
 
204
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
205
  def _summary(days_df: pd.DataFrame, picks_df: pd.DataFrame, top_n: int):
206
  active = days_df[days_df["n_picks"] > 0]
207
  print("\n" + "=" * 78)
@@ -263,10 +311,13 @@ def main():
263
  help="only pick stocks at/above this confidence")
264
  ap.add_argument("--filters", default="", help="comma-separated quality gates: "
265
  "trend,momentum,adx,trigger,lowvol,notob")
 
 
266
  args = ap.parse_args()
267
  filters = {f.strip() for f in args.filters.split(",") if f.strip()}
268
  run(args.csv, top_n=args.top, step=args.step, one_date=args.date,
269
- rank_mode=args.rank, min_conf=args.min_conf, filters=filters)
 
270
 
271
 
272
  if __name__ == "__main__":
 
37
  from ml_predictor.features import FEATURE_COLUMNS # noqa: E402
38
  from ml_predictor.infer import MLPredictor # noqa: E402
39
  from ml_predictor.dataset import _cached_tickers, _load_ticker, DEFAULT_STEP # noqa: E402
40
+ from research.strategy_validation_funnel import _sharpe, _max_drawdown # noqa: E402
41
 
42
  DEFAULT_CSV = os.path.join(_PROJ_ROOT, "ml_predictor", "training_data.csv")
43
  OUT_CSV = os.path.join(os.path.dirname(os.path.abspath(__file__)), "ml_selection_results.csv")
 
103
 
104
  def run(csv_path: str = DEFAULT_CSV, top_n: int = 10, step: int = 1,
105
  one_date: str | None = None, rank_mode: str = "expmove",
106
+ min_conf: str = "LOW", filters: set | None = None,
107
+ six_filter: bool = False) -> pd.DataFrame:
108
  filters = filters or set()
109
  predictor = MLPredictor()
110
  if not predictor.available:
 
113
  df = pd.read_csv(csv_path)
114
  df["date"] = pd.to_datetime(df["date"])
115
  holdout_start = predictor.manifest.get("holdout_start")
116
+ # --six-filter validates the ML SELECTOR itself through the doc's funnel, which needs an
117
+ # in-sample (IS) leg too — so evaluate the FULL date range and split at holdout_start.
118
+ if six_filter:
119
+ oos = df.copy()
120
+ else:
121
+ oos = df[df["date"] >= pd.to_datetime(holdout_start)].copy() if holdout_start else df
122
  print(f" Model: rank by {_SELECTOR_TF} BULLISH · mode={rank_mode} · min_conf={min_conf} · "
123
  f"filters={sorted(filters) or 'none'} · top-{top_n} picks/day · cost {ROUND_TRIP_COST_PCT}% round-trip")
124
  print(f" Out-of-sample rows: {len(oos):,} · loading close series for realized fwd returns…")
 
205
  _one_day_report(one_date, picks_df, days_df)
206
  else:
207
  _summary(days_df, picks_df, top_n)
208
+ if six_filter and not one_date:
209
+ _six_filter_verdict(days_df, pd.to_datetime(holdout_start) if holdout_start else None)
210
  print(f"\n ✓ Wrote per-pick detail → {OUT_CSV}")
211
  return days_df
212
 
213
 
214
+ def _six_filter_verdict(days_df: pd.DataFrame, split):
215
+ """Run the '9,120-backtest' doc's 6-filter funnel on the ML SELECTION basket itself.
216
+ Treats each decision day's top-N basket 3-day return as one trade; splits IS/OOS at the
217
+ model's holdout_start. NOTE: the OOS leg is only as long as the manifest holdout window —
218
+ if that is a handful of days the verdict is directional, not conclusive."""
219
+ HOLD = 3
220
+ d = days_df.copy()
221
+ d = d[d["n_picks"] > 0]
222
+ d["_dt"] = pd.to_datetime(d["date"])
223
+ col = "basket_3d"
224
+ if split is None:
225
+ is_r = []
226
+ oos_r = list(d[col].dropna())
227
+ else:
228
+ is_r = list(d[d["_dt"] < split][col].dropna())
229
+ oos_r = list(d[d["_dt"] >= split][col].dropna())
230
+ is_s, oos_s = _sharpe(is_r, HOLD), _sharpe(oos_r, HOLD)
231
+ mdd = _max_drawdown(oos_r)
232
+ n_oos = len(oos_r)
233
+ checks = [
234
+ ("[01] OOS Sharpe > 0.5", oos_s > 0.5, f"{oos_s:+.2f}"),
235
+ ("[02] Max DD better than -35%", mdd > -35.0, f"{mdd:.1f}%"),
236
+ ("[03] OOS Sharpe < 2.5 (not absurd)", oos_s < 2.5, f"{oos_s:+.2f}"),
237
+ ("[04] OOS <= IS*1.3 + 0.5 (not overfit)", oos_s <= is_s * 1.3 + 0.5, f"OOS {oos_s:+.2f} / IS {is_s:+.2f}"),
238
+ ("[05] At least 30 OOS trades", n_oos >= 30, f"{n_oos}"),
239
+ ("[06] IS Sharpe > 0", is_s > 0, f"{is_s:+.2f}"),
240
+ ]
241
+ print("\n" + "=" * 78)
242
+ print(" 6-FILTER VALIDATION — is the ML top-N SELECTION strategy a real OOS edge?")
243
+ print(f" IS trades={len(is_r)} OOS trades={n_oos} (3-day basket return per decision day)")
244
+ print("=" * 78)
245
+ for label, ok, val in checks:
246
+ print(f" {'PASS' if ok else 'FAIL'} {label:<42} {val}")
247
+ verdict = "SURVIVES all 6 filters" if all(c[1] for c in checks) else "does NOT survive"
248
+ print(f"\n → The ML selection strategy {verdict}.")
249
+ if n_oos < 30:
250
+ print(" ⚠ OOS window is short (manifest holdout is small) — treat as directional only.")
251
+
252
+
253
  def _summary(days_df: pd.DataFrame, picks_df: pd.DataFrame, top_n: int):
254
  active = days_df[days_df["n_picks"] > 0]
255
  print("\n" + "=" * 78)
 
311
  help="only pick stocks at/above this confidence")
312
  ap.add_argument("--filters", default="", help="comma-separated quality gates: "
313
  "trend,momentum,adx,trigger,lowvol,notob")
314
+ ap.add_argument("--six-filter", action="store_true",
315
+ help="validate the ML selection basket through the doc's 6-filter funnel (IS vs OOS)")
316
  args = ap.parse_args()
317
  filters = {f.strip() for f in args.filters.split(",") if f.strip()}
318
  run(args.csv, top_n=args.top, step=args.step, one_date=args.date,
319
+ rank_mode=args.rank, min_conf=args.min_conf, filters=filters,
320
+ six_filter=args.six_filter)
321
 
322
 
323
  if __name__ == "__main__":
research/ml_selection_results.csv CHANGED
@@ -1,63 +1,722 @@
1
  date,ticker,exp_up_q50,confidence,ret_1d_net,ret_3d_net,ret_5d_net
2
- 2026-04-15,TECILCHEM.NS,8.03,LOW,-2.117,0.306,6.286
3
- 2026-04-15,EMSLIMITED.NS,5.6,MEDIUM,1.694,10.13,10.8
4
- 2026-04-15,AVONMORE.NS,5.47,LOW,-1.302,-5.693,-5.539
5
- 2026-04-15,DAMCAPITAL.NS,5.3,MEDIUM,-1.375,-0.116,3.593
6
- 2026-04-15,TRU.NS,5.08,LOW,-0.801,3.206,-0.133
7
- 2026-04-17,21STCENMGM.NS,3.9,LOW,1.675,5.778,10.036
8
- 2026-04-21,GUJRAFFIA.NS,4.34,LOW,4.285,1.795,-0.99
9
- 2026-04-21,MEDICAMEQ.NS,3.5,LOW,2.354,-1.757,-5.36
10
- 2026-04-21,HAPPYFORGE.NS,3.49,LOW,2.664,1.991,0.567
11
- 2026-04-23,GENESYS.NS,4.6,LOW,-7.021,-5.489,-4.544
12
- 2026-04-23,ZEEMEDIA.NS,4.31,LOW,-5.22,-2.474,-4.877
13
- 2026-04-23,CUPID.NS,4.04,LOW,-0.335,-1.714,5.401
14
- 2026-04-23,HEG.NS,3.64,LOW,-2.537,-1.684,-11.124
15
- 2026-04-23,NOCIL.NS,3.57,LOW,1.201,0.751,-2.069
16
- 2026-04-27,SRD.NS,5.57,LOW,-1.48,-0.767,-0.723
17
- 2026-04-27,LAL.NS,5.44,LOW,-0.436,-0.572,-2.885
18
- 2026-04-27,PRUDMOULI.NS,5.42,LOW,1.761,0.912,1.882
19
- 2026-04-27,GATECHDVR.NS,5.1,LOW,-0.3,-2.428,1.828
20
- 2026-04-27,DATAPATTNS.NS,4.99,LOW,2.22,2.771,3.061
21
- 2026-04-29,KRISHNADEF.NS,4.1,LOW,0.575,-1.657,-0.908
22
- 2026-04-29,GOCOLORS.NS,3.87,LOW,-7.73,-12.001,-6.666
23
- 2026-05-01,ASMS.NS,5.58,LOW,-0.939,0.466,1.999
24
- 2026-05-01,CARTRADE.NS,4.7,LOW,1.443,11.028,20.12
25
- 2026-05-01,CONCORDBIO.NS,3.12,LOW,4.358,6.705,2.493
26
- 2026-05-05,TMB.NS,3.56,LOW,3.578,-0.08,-9.229
27
- 2026-05-11,URAVIDEF.NS,4.03,LOW,-2.075,-3.072,-4.154
28
- 2026-05-13,YATRA.NS,5.94,LOW,-3.171,-3.399,-2.696
29
- 2026-05-13,GSLSU.NS,4.8,LOW,4.196,3.398,1.335
30
- 2026-05-13,BELLACASA.NS,4.78,LOW,-1.515,-5.848,-4.491
31
- 2026-05-13,DYCL.NS,4.56,LOW,-3.831,-14.513,-11.162
32
- 2026-05-13,GARUDA.NS,4.27,LOW,0.543,-1.461,1.192
33
- 2026-05-15,TERASOFT.NS,8.21,LOW,4.695,15.43,17.754
34
- 2026-05-15,GALLANTT.NS,6.65,LOW,-7.751,-10.375,-7.67
35
- 2026-05-15,EMKAY.NS,6.64,LOW,6.631,12.657,13.813
36
- 2026-05-15,ABINFRA.NS,6.34,LOW,0.386,-1.824,-1.062
37
- 2026-05-15,BALAJITELE.NS,6.05,LOW,-11.087,-11.067,-9.874
38
- 2026-05-19,SRD.NS,8.12,LOW,5.348,6.115,3.15
39
- 2026-05-19,STALLION.NS,5.91,LOW,-0.517,0.333,8.985
40
- 2026-05-19,HYBRIDFIN.NS,5.76,LOW,-1.638,-1.264,-5.118
41
- 2026-05-19,GATECHDVR.NS,5.43,LOW,-0.3,-0.3,-2.3
42
- 2026-05-19,PRUDMOULI.NS,5.28,LOW,2.714,2.513,0.839
43
- 2026-05-21,BCG.NS,5.73,LOW,0.366,-1.822,-2.393
44
- 2026-05-21,BBOX.NS,4.53,LOW,0.047,2.452,4.117
45
- 2026-05-21,TALBROAUTO.NS,4.17,LOW,12.061,8.791,8.666
46
- 2026-05-21,POWERINDIA.NS,3.97,LOW,-2.755,-1.548,2.718
47
- 2026-05-21,GRWRHITECH.NS,3.85,LOW,-3.397,4.642,3.713
48
- 2026-05-29,MACPOWER.NS,6.69,LOW,-5.976,-2.517,3.51
49
- 2026-06-02,HYBRIDFIN.NS,6.47,LOW,-1.308,-4.393,-2.317
50
- 2026-06-02,LAL.NS,5.81,LOW,-2.641,-2.511,-3.291
51
- 2026-06-02,ROSSTECH.NS,4.8,LOW,-0.92,-2.919,-3.095
52
- 2026-06-02,PRUDMOULI.NS,4.6,LOW,-3.564,-3.062,-6.389
53
- 2026-06-02,PRIMO.NS,4.46,LOW,-3.025,14.469,8.14
54
- 2026-06-04,EASEMYTRIP.NS,6.52,LOW,1.053,18.046,24.662
55
- 2026-06-16,GICL.NS,6.08,MEDIUM,-2.765,-4.252,-7.142
56
- 2026-06-16,MEDICAMEQ.NS,5.18,LOW,-1.836,0.183,5.736
57
- 2026-06-18,CUPID.NS,5.32,MEDIUM,4.269,7.265,7.632
58
- 2026-06-26,PFOCUS.NS,11.15,MEDIUM,-0.038,10.236,12.108
59
- 2026-06-26,RAJTV.NS,10.84,LOW,0.253,-0.695,-5.755
60
- 2026-06-26,GANGAFORGE.NS,10.57,LOW,-0.3,3.033,28.736
61
- 2026-06-26,ABCOTS.NS,10.5,MEDIUM,-1.281,-2.462,-3.182
62
- 2026-06-26,AVANTIFEED.NS,10.2,MEDIUM,-3.137,-2.077,-0.986
63
- 2026-07-14,NOVAAGRI.NS,6.98,LOW,-1.993,4.511,
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
  date,ticker,exp_up_q50,confidence,ret_1d_net,ret_3d_net,ret_5d_net
2
+ 2022-05-12,AYMSYNTEX.NS,14.38,MEDIUM,4.784,10.444,6.356
3
+ 2022-05-12,ZENITHSTL.NS,13.84,MEDIUM,3.824,14.133,24.442
4
+ 2022-05-12,LAMBODHARA.NS,13.84,MEDIUM,5.141,14.334,8.581
5
+ 2022-05-12,ADSL.NS,13.8,MEDIUM,9.533,27.297,27.529
6
+ 2022-05-12,SONAL.NS,13.65,MEDIUM,16.16,17.868,11.035
7
+ 2022-05-19,ZENITHSTL.NS,13.48,MEDIUM,4.659,14.576,25.32
8
+ 2022-05-19,KOHINOOR.NS,13.01,MEDIUM,4.561,15.151,26.783
9
+ 2022-05-19,ASIANTILES.NS,10.44,MEDIUM,0.441,-12.491,-19.493
10
+ 2022-05-19,NITIRAJ.NS,9.04,MEDIUM,-3.066,-3.211,-8.306
11
+ 2022-05-19,ATLANTAA.NS,8.55,LOW,3.379,2.71,-2.976
12
+ 2022-06-09,SIL.NS,13.58,MEDIUM,4.692,15.301,15.145
13
+ 2022-06-09,ZENITHSTL.NS,13.42,MEDIUM,-5.259,-13.936,-21.788
14
+ 2022-06-09,KOHINOOR.NS,12.95,MEDIUM,4.675,15.383,27.187
15
+ 2022-06-09,ACCURACY.NS,12.44,MEDIUM,19.649,24.719,19.522
16
+ 2022-06-09,GICL.NS,10.92,MEDIUM,4.633,14.947,26.606
17
+ 2022-06-14,CHEMPLASTS.NS,6.81,MEDIUM,-1.15,-10.252,-8.31
18
+ 2022-06-14,APTUS.NS,6.7,MEDIUM,0.221,2.804,5.257
19
+ 2022-06-30,KOHINOOR.NS,12.93,MEDIUM,-5.263,-14.499,-22.839
20
+ 2022-06-30,HECPROJECT.NS,10.26,MEDIUM,-6.952,2.275,-1.802
21
+ 2022-06-30,IMAGICAA.NS,10.24,MEDIUM,4.7,15.45,4.2
22
+ 2022-06-30,LASA.NS,9.61,MEDIUM,19.544,14.291,20.128
23
+ 2022-06-30,BCG.NS,8.48,MEDIUM,4.644,15.17,26.973
24
+ 2022-07-21,SPCENET.NS,13.19,MEDIUM,4.017,14.088,24.88
25
+ 2022-07-21,PCJEWELLER.NS,12.12,MEDIUM,-5.252,-14.462,-22.715
26
+ 2022-07-21,KEEPLEARN.NS,12.05,MEDIUM,3.271,12.2,22.914
27
+ 2022-07-21,REGENCERAM.NS,11.8,MEDIUM,-0.3,8.302,24.431
28
+ 2022-07-21,AKASH.NS,11.45,MEDIUM,4.613,15.258,27.029
29
+ 2022-08-12,KEEPLEARN.NS,13.54,MEDIUM,4.417,13.851,25.172
30
+ 2022-08-12,SPCENET.NS,13.15,MEDIUM,4.351,14.816,26.057
31
+ 2022-08-12,REGENCERAM.NS,12.75,MEDIUM,4.645,15.085,26.623
32
+ 2022-08-12,JETFREIGHT.NS,9.97,MEDIUM,-2.388,-7.261,-3.316
33
+ 2022-08-12,EKC.NS,8.92,MEDIUM,2.52,6.097,-3.625
34
+ 2022-09-06,KEEPLEARN.NS,13.89,MEDIUM,4.462,14.938,26.367
35
+ 2022-09-06,REGENCERAM.NS,13.31,MEDIUM,4.565,15.105,26.727
36
+ 2022-09-06,SAKHTISUG.NS,12.91,MEDIUM,-7.621,-16.371,-17.621
37
+ 2022-09-06,SERVOTECH.NS,10.76,MEDIUM,4.665,15.391,15.657
38
+ 2022-09-06,KRITIKA.NS,10.19,MEDIUM,4.609,15.268,14.847
39
+ 2022-09-19,PRITIKAUTO.NS,5.71,LOW,3.49,-0.008,-7.589
40
+ 2022-09-27,REGENCERAM.NS,13.17,MEDIUM,4.575,4.311,-5.702
41
+ 2022-09-27,MKPL.NS,11.81,MEDIUM,4.692,15.448,27.291
42
+ 2022-09-27,VARDHACRLC.NS,9.58,MEDIUM,2.884,2.785,3.979
43
+ 2022-09-27,63MOONS.NS,8.89,MEDIUM,4.212,2.772,7.412
44
+ 2022-09-27,SIMPLEXINF.NS,8.89,MEDIUM,1.899,7.791,3.13
45
+ 2022-09-30,FINOPB.NS,4.3,LOW,-3.34,-7.489,-9.708
46
+ 2022-10-06,KOTYARK.NS,6.69,MEDIUM,4.659,3.591,4.438
47
+ 2022-10-06,SMLT.NS,6.24,LOW,-1.495,-5.117,-6.543
48
+ 2022-10-14,PASUPTAC.NS,4.58,LOW,0.462,-0.148,0.005
49
+ 2022-10-19,RKDL.NS,10.81,MEDIUM,-5.146,-14.177,-14.397
50
+ 2022-10-19,ORIENTALTL.NS,10.2,MEDIUM,-3.729,-2.586,-0.3
51
+ 2022-10-19,DELPHIFX.NS,10.02,LOW,-6.45,-8.276,-3.765
52
+ 2022-10-19,LANDSMILL.NS,9.87,MEDIUM,-0.3,-0.3,-0.3
53
+ 2022-10-19,ZENITHEXPO.NS,9.84,MEDIUM,-5.291,-14.528,-22.876
54
+ 2022-10-28,NYKAA.NS,8.55,MEDIUM,16.903,16.939,12.013
55
+ 2022-11-02,KPIGREEN.NS,5.26,LOW,-2.244,0.966,14.701
56
+ 2022-11-07,EUROBOND.NS,4.53,LOW,-2.269,-4.238,-3.945
57
+ 2022-11-11,MKPL.NS,13.14,MEDIUM,4.7,14.43,22.738
58
+ 2022-11-11,RGL.NS,11.78,MEDIUM,-6.564,9.013,1.529
59
+ 2022-11-11,KARMAENG.NS,9.32,LOW,4.642,0.194,-4.748
60
+ 2022-11-11,AURIGROW.NS,9.31,MEDIUM,-5.062,-12.998,-6.649
61
+ 2022-11-11,EVERESTIND.NS,9.15,MEDIUM,-3.918,5.015,1.566
62
+ 2022-11-16,ROLEXRINGS.NS,3.73,LOW,2.612,5.848,3.893
63
+ 2022-11-21,KOTYARK.NS,6.21,LOW,0.63,-1.152,-1.374
64
+ 2022-11-29,PAYTM.NS,6.32,LOW,-0.755,10.663,6.055
65
+ 2022-11-29,DATAPATTNS.NS,4.14,LOW,2.971,3.581,0.936
66
+ 2022-12-02,SKIPPER.NS,11.24,MEDIUM,6.005,5.544,18.522
67
+ 2022-12-02,WILLAMAGOR.NS,10.66,MEDIUM,4.542,-5.353,-13.563
68
+ 2022-12-02,PIGL.NS,10.43,MEDIUM,-5.225,-12.24,-16.419
69
+ 2022-12-02,NILASPACES.NS,10.14,MEDIUM,-3.871,-5.062,-9.824
70
+ 2022-12-02,DRCSYSTEMS.NS,9.58,MEDIUM,1.713,11.378,8.291
71
+ 2022-12-12,KOTYARK.NS,5.52,LOW,3.7,-0.899,3.137
72
+ 2022-12-23,CELEBRITY.NS,14.39,MEDIUM,19.7,21.033,19.367
73
+ 2022-12-23,WEIZMANIND.NS,13.99,MEDIUM,17.721,21.386,23.891
74
+ 2022-12-23,SIMPLEXINF.NS,13.58,MEDIUM,5.628,12.236,11.459
75
+ 2022-12-23,BRNL.NS,13.56,MEDIUM,16.941,24.465,22.741
76
+ 2022-12-23,DEVIT.NS,13.41,MEDIUM,19.657,33.585,36.626
77
+ 2022-12-28,AWL.NS,4.31,LOW,4.352,4.135,0.746
78
+ 2022-12-28,IONEXCHANG.NS,3.99,LOW,-2.613,3.19,2.829
79
+ 2023-01-02,KOTYARK.NS,8.48,MEDIUM,1.443,7.508,2.321
80
+ 2023-01-02,BAJAJHCARE.NS,3.47,LOW,0.254,-3.998,-6.212
81
+ 2023-01-13,SPECTRUM.NS,9.34,MEDIUM,4.664,9.882,21.126
82
+ 2023-01-13,TOUCHWOOD.NS,8.96,MEDIUM,4.677,15.397,27.227
83
+ 2023-01-13,KAMATHOTEL.NS,8.6,LOW,-2.311,5.158,7.373
84
+ 2023-01-13,BTML.NS,8.54,MEDIUM,2.6,7.059,-0.768
85
+ 2023-01-13,SHRADHA.NS,8.3,MEDIUM,4.686,15.41,26.898
86
+ 2023-01-23,NYKAA.NS,5.94,MEDIUM,7.395,0.622,9.279
87
+ 2023-01-23,NRL.NS,4.68,LOW,-0.185,-18.159,-11.007
88
+ 2023-01-27,POLICYBZR.NS,4.95,LOW,4.407,-0.063,6.288
89
+ 2023-02-01,MEDICAMEQ.NS,6.31,MEDIUM,1.886,-2.389,1.463
90
+ 2023-02-01,GMRP&UI.NS,5.02,LOW,-1.083,-0.3,-1.083
91
+ 2023-02-01,ETERNAL.NS,4.32,LOW,-1.65,-1.131,12.473
92
+ 2023-02-01,STARHEALTH.NS,4.3,LOW,2.7,3.985,5.53
93
+ 2023-02-06,SWANCORP.NS,11.4,MEDIUM,3.124,6.528,6.895
94
+ 2023-02-06,SECURKLOUD.NS,10.46,MEDIUM,1.271,-1.609,-6.19
95
+ 2023-02-06,PAISALO.NS,10.41,MEDIUM,-2.41,-2.086,-5.332
96
+ 2023-02-06,PANACHE.NS,9.62,MEDIUM,4.659,10.385,20.774
97
+ 2023-02-06,HECPROJECT.NS,9.5,MEDIUM,-3.801,-4.714,-2.126
98
+ 2023-02-09,KRISHIVAL.NS,6.58,LOW,4.093,0.784,0.784
99
+ 2023-02-09,AWL.NS,5.75,MEDIUM,-1.164,-10.83,-5.372
100
+ 2023-02-09,OBCL.NS,5.57,LOW,-2.542,-6.258,-13.817
101
+ 2023-02-14,BAJAJHCARE.NS,6.23,LOW,-2.246,-3.738,-4.875
102
+ 2023-02-14,RITCO.NS,4.67,LOW,-3.492,-0.028,-4.375
103
+ 2023-02-17,SBC.NS,8.3,MEDIUM,4.144,2.24,4.144
104
+ 2023-02-17,NPST.NS,5.21,LOW,-0.3,15.442,21.729
105
+ 2023-02-17,GOCOLORS.NS,4.13,LOW,2.694,-1.145,-4.593
106
+ 2023-02-22,GUJRAFFIA.NS,5.63,LOW,2.652,-1.592,-2.514
107
+ 2023-02-22,RATEGAIN.NS,4.34,LOW,-0.981,-1.952,1.019
108
+ 2023-02-22,DATAPATTNS.NS,4.22,LOW,-3.642,-6.813,3.601
109
+ 2023-02-27,TREEHOUSE.NS,11.98,MEDIUM,9.61,31.682,34.385
110
+ 2023-02-27,SAMBHAAV.NS,11.87,MEDIUM,-0.3,12.427,8.791
111
+ 2023-02-27,SOMATEX.NS,11.71,MEDIUM,4.689,15.346,21.695
112
+ 2023-02-27,NECCLTD.NS,11.47,MEDIUM,-3.927,-9.626,-4.704
113
+ 2023-02-27,SPIC.NS,10.86,MEDIUM,-0.127,2.291,8.422
114
+ 2023-03-02,OBCL.NS,9.66,MEDIUM,-5.661,16.854,54.281
115
+ 2023-03-02,AWL.NS,4.95,LOW,4.692,15.441,13.459
116
+ 2023-03-13,PRITIKAUTO.NS,5.03,LOW,1.68,0.36,-5.581
117
+ 2023-03-16,KRISHNADEF.NS,3.83,LOW,-1.356,-1.039,-1.004
118
+ 2023-03-21,SOMATEX.NS,12.9,MEDIUM,4.643,4.298,-5.817
119
+ 2023-03-21,ARIHANTCAP.NS,10.09,MEDIUM,0.959,-2.678,-4.776
120
+ 2023-03-21,ABMINTLLTD.NS,10.01,MEDIUM,4.693,-5.504,-14.716
121
+ 2023-03-21,ADANIGREEN.NS,9.76,MEDIUM,4.7,15.221,4.705
122
+ 2023-03-21,SUMEETINDS.NS,9.39,MEDIUM,4.245,13.336,4.245
123
+ 2023-03-24,OBCL.NS,6.43,MEDIUM,-2.956,-8.542,-6.436
124
+ 2023-03-29,NRL.NS,9.3,MEDIUM,5.739,17.516,17.013
125
+ 2023-03-29,CARTRADE.NS,6.36,LOW,5.983,9.753,8.128
126
+ 2023-03-29,BAJAJHCARE.NS,6.13,MEDIUM,6.024,7.115,9.263
127
+ 2023-03-29,INDOBORAX.NS,5.59,LOW,1.345,8.424,9.421
128
+ 2023-03-29,MEDPLUS.NS,5.53,LOW,2.088,0.614,5.492
129
+ 2023-04-05,EMUDHRA.NS,3.89,LOW,5.12,10.233,5.559
130
+ 2023-04-17,TPHQ.NS,12.39,MEDIUM,4.369,14.695,25.96
131
+ 2023-04-17,WSI.NS,12.04,MEDIUM,4.611,15.269,27.077
132
+ 2023-04-17,ABINFRA.NS,11.5,MEDIUM,4.567,15.187,14.892
133
+ 2023-04-17,REGENCERAM.NS,10.82,MEDIUM,4.582,4.402,-5.725
134
+ 2023-04-17,MARINE.NS,10.38,MEDIUM,-0.3,2.041,-0.523
135
+ 2023-04-20,GUJRAFFIA.NS,7.56,LOW,-2.779,-4.928,-9.556
136
+ 2023-04-20,AXITA.NS,2.79,MEDIUM,1.284,6.036,11.615
137
+ 2023-04-25,GRAUWEIL.NS,4.33,LOW,2.696,1.788,0.926
138
+ 2023-04-25,NYKAA.NS,3.71,LOW,-0.3,4.531,5.771
139
+ 2023-05-09,TPHQ.NS,13.58,MEDIUM,4.561,14.978,26.552
140
+ 2023-05-09,MICEL.NS,11.24,MEDIUM,2.542,-7.535,-7.794
141
+ 2023-05-09,ASMS.NS,10.06,MEDIUM,4.528,14.183,25.217
142
+ 2023-05-09,MAGNUM.NS,9.72,LOW,4.563,15.202,26.904
143
+ 2023-05-09,SHRIPISTON.NS,8.97,MEDIUM,4.7,15.456,23.255
144
+ 2023-05-22,FIBERWEB.NS,4.39,LOW,4.638,-2.152,-1.072
145
+ 2023-05-22,MALLCOM.NS,4.15,LOW,-1.988,-1.545,13.436
146
+ 2023-05-30,VENUSREM.NS,11.33,MEDIUM,0.397,1.58,-3.764
147
+ 2023-05-30,ACL.NS,10.96,MEDIUM,4.679,15.173,10.73
148
+ 2023-05-30,JYOTISTRUC.NS,10.6,MEDIUM,4.298,4.872,4.298
149
+ 2023-05-30,LAMBODHARA.NS,9.49,MEDIUM,1.568,-1.221,-1.058
150
+ 2023-05-30,JITFINFRA.NS,9.43,MEDIUM,4.693,15.437,22.142
151
+ 2023-06-02,AXITA.NS,4.58,LOW,-5.308,-7.813,-8.647
152
+ 2023-06-02,RAINBOW.NS,3.84,LOW,-0.959,3.646,-1.158
153
+ 2023-06-20,JITFINFRA.NS,12.27,MEDIUM,4.691,15.44,27.291
154
+ 2023-06-20,KAVDEFENCE.NS,10.55,MEDIUM,-5.012,-13.913,-22.29
155
+ 2023-06-20,MINDTECK.NS,10.48,LOW,-3.542,-12.128,-13.599
156
+ 2023-06-20,CSLFINANCE.NS,10.42,LOW,-2.641,0.648,-0.346
157
+ 2023-06-20,BCG.NS,10.18,MEDIUM,4.685,-5.436,-14.65
158
+ 2023-06-23,AXITA.NS,4.35,LOW,0.268,-1.626,-1.626
159
+ 2023-06-28,ASIANENE.NS,7.52,LOW,4.667,5.248,5.248
160
+ 2023-06-28,HARDWYN.NS,6.97,MEDIUM,4.614,15.238,14.308
161
+ 2023-07-04,CHOICEIN.NS,4.59,LOW,0.899,1.847,0.944
162
+ 2023-07-12,GOLDTECH.NS,10.82,MEDIUM,-5.284,-0.3,-0.3
163
+ 2023-07-12,PCJEWELLER.NS,9.96,MEDIUM,-5.214,-14.377,-21.681
164
+ 2023-07-12,PREMEXPLN.NS,9.47,MEDIUM,1.944,22.349,25.227
165
+ 2023-07-12,SEPC.NS,8.83,LOW,0.152,-2.562,0.605
166
+ 2023-07-12,SECURKLOUD.NS,8.82,MEDIUM,9.642,13.033,2.624
167
+ 2023-07-20,KOTYARK.NS,5.21,LOW,-3.109,6.126,7.317
168
+ 2023-07-20,ROTO.NS,4.58,MEDIUM,-1.877,-3.656,-6.116
169
+ 2023-07-25,NPST.NS,6.74,MEDIUM,4.699,15.457,27.319
170
+ 2023-07-28,SPORTKING.NS,3.52,LOW,-0.743,-1.236,-1.717
171
+ 2023-08-02,ACL.NS,9.31,MEDIUM,0.089,-7.471,-6.434
172
+ 2023-08-02,SKMEGGPROD.NS,8.93,LOW,4.696,9.108,20.307
173
+ 2023-08-02,KILITCH.NS,8.69,LOW,-2.078,-2.566,-3.905
174
+ 2023-08-02,WSI.NS,8.13,MEDIUM,-1.348,-5.215,-8.938
175
+ 2023-08-02,JYOTISTRUC.NS,8.09,MEDIUM,4.439,11.548,11.548
176
+ 2023-08-07,IONEXCHANG.NS,4.85,MEDIUM,0.29,4.787,7.673
177
+ 2023-08-07,EMIL.NS,4.14,LOW,-2.93,2.687,8.082
178
+ 2023-08-10,RAMRAT.NS,3.9,LOW,1.712,-3.073,-5.406
179
+ 2023-08-16,JSLL.NS,8.42,MEDIUM,4.699,-4.391,-9.595
180
+ 2023-08-16,KPIGREEN.NS,4.48,LOW,-1.337,-0.707,-1.578
181
+ 2023-08-16,SYRMA.NS,4.04,LOW,2.472,3.785,5.989
182
+ 2023-08-21,RATEGAIN.NS,4.36,LOW,3.868,3.587,4.647
183
+ 2023-08-24,DOLPHIN.NS,11.19,MEDIUM,4.683,15.442,27.258
184
+ 2023-08-24,RKDL.NS,10.85,MEDIUM,4.674,15.407,27.187
185
+ 2023-08-24,TNTELE.NS,9.19,MEDIUM,4.7,14.7,26.129
186
+ 2023-08-24,IRISDOREME.NS,8.87,MEDIUM,-1.535,-1.467,3.681
187
+ 2023-08-24,KALYANIFRG.NS,8.46,MEDIUM,4.692,6.062,16.945
188
+ 2023-08-29,VENUSPIPES.NS,4.15,LOW,-0.093,0.71,1.519
189
+ 2023-08-29,WINDLAS.NS,3.71,LOW,1.801,8.536,9.426
190
+ 2023-08-29,TEGA.NS,3.63,LOW,0.219,2.586,1.02
191
+ 2023-09-06,ABCOTS.NS,7.43,MEDIUM,-0.694,-0.694,-1.73
192
+ 2023-09-06,EMUDHRA.NS,4.71,MEDIUM,3.183,3.7,-1.725
193
+ 2023-09-06,STEELCAS.NS,4.26,LOW,6.736,11.258,6.352
194
+ 2023-09-06,NPST.NS,2.97,LOW,-4.239,-7.631,-6.224
195
+ 2023-09-14,UNITECH.NS,12.23,MEDIUM,3.404,10.811,21.922
196
+ 2023-09-14,DOLPHIN.NS,10.92,MEDIUM,1.693,5.789,10.059
197
+ 2023-09-14,LGHL.NS,8.54,MEDIUM,3.758,12.717,17.924
198
+ 2023-09-14,THOMASCOTT.NS,7.54,LOW,4.659,8.846,13.199
199
+ 2023-09-14,SPMLINFRA.NS,7.51,MEDIUM,4.649,14.447,10.003
200
+ 2023-09-20,IONEXCHANG.NS,4.01,LOW,-1.648,-2.745,-3.715
201
+ 2023-09-25,GRAUWEIL.NS,3.43,LOW,0.96,2.928,1.039
202
+ 2023-09-25,GOYALALUM.NS,2.31,LOW,3.229,13.229,24.406
203
+ 2023-09-28,JSLL.NS,4.56,LOW,-1.775,1.8,10.151
204
+ 2023-09-28,ABCOTS.NS,3.33,MEDIUM,-0.3,9.486,14.929
205
+ 2023-10-04,AKSHAR.NS,6.42,LOW,1.326,0.513,7.017
206
+ 2023-10-04,KRISHNADEF.NS,4.39,LOW,7.136,3.736,4.064
207
+ 2023-10-09,RUCHINFRA.NS,11.39,MEDIUM,4.7,7.641,3.524
208
+ 2023-10-09,STEELXIND.NS,10.95,MEDIUM,5.989,21.084,14.165
209
+ 2023-10-09,BEDMUTHA.NS,10.64,MEDIUM,1.863,-2.168,-6.001
210
+ 2023-10-09,NKIND.NS,10.43,LOW,19.7,44.785,67.158
211
+ 2023-10-09,FLEXITUFF.NS,9.5,MEDIUM,-2.246,-6.039,-9.736
212
+ 2023-10-20,RELCHEMQ.NS,4.66,LOW,-0.346,2.834,7.006
213
+ 2023-10-20,NPST.NS,3.92,LOW,4.7,14.72,26.479
214
+ 2023-10-20,MAPMYINDIA.NS,3.0,LOW,-4.811,0.435,5.032
215
+ 2023-10-26,GMRP&UI.NS,6.27,LOW,4.594,0.516,4.431
216
+ 2023-10-26,TARSONS.NS,6.26,MEDIUM,3.987,4.714,4.233
217
+ 2023-10-26,KAYNES.NS,4.93,MEDIUM,3.527,3.708,2.855
218
+ 2023-10-26,PASUPTAC.NS,4.89,LOW,0.476,-3.922,-4.052
219
+ 2023-10-26,DCMSRIND.NS,4.41,LOW,1.728,1.444,0.065
220
+ 2023-10-31,ABMINTLLTD.NS,9.27,MEDIUM,4.667,4.38,-5.744
221
+ 2023-10-31,TICL.NS,8.82,MEDIUM,-0.046,20.82,26.926
222
+ 2023-10-31,RMDRIP.NS,8.63,MEDIUM,4.623,15.358,15.035
223
+ 2023-10-31,MAANALU.NS,8.43,LOW,3.767,-6.09,3.511
224
+ 2023-10-31,CTE.NS,8.08,LOW,-6.322,-6.389,-5.442
225
+ 2023-11-03,IONEXCHANG.NS,4.81,LOW,0.296,5.975,4.735
226
+ 2023-11-03,KRISHIVAL.NS,4.41,LOW,-0.3,-5.3,-9.748
227
+ 2023-11-08,PRUDENT.NS,4.06,LOW,-1.029,1.736,7.178
228
+ 2023-11-13,DYCL.NS,7.94,MEDIUM,4.688,8.393,4.034
229
+ 2023-11-17,GMRP&UI.NS,7.66,LOW,-5.265,-1.364,0.764
230
+ 2023-11-17,RADHIKAJWE.NS,5.99,LOW,5.978,2.727,2.615
231
+ 2023-11-17,AKSHAR.NS,4.84,LOW,4.388,14.544,11.419
232
+ 2023-11-22,UNITECH.NS,11.37,MEDIUM,4.144,13.033,24.144
233
+ 2023-11-22,MADHUCON.NS,10.43,MEDIUM,4.298,-5.472,-6.047
234
+ 2023-11-22,KEEPLEARN.NS,10.11,MEDIUM,4.602,2.641,12.445
235
+ 2023-11-22,63MOONS.NS,8.69,MEDIUM,4.699,10.417,21.754
236
+ 2023-11-22,GAYAHWS.NS,8.39,MEDIUM,-4.146,-7.992,-7.992
237
+ 2023-11-28,AWL.NS,7.0,LOW,-0.63,-2.653,8.941
238
+ 2023-12-01,ASIANENE.NS,7.66,LOW,4.693,14.542,18.917
239
+ 2023-12-01,GATEWAY.NS,4.02,LOW,-1.18,0.58,-0.398
240
+ 2023-12-06,JSLL.NS,5.2,LOW,-2.342,5.933,0.638
241
+ 2023-12-06,INTLCONV.NS,4.62,LOW,-1.654,-3.603,-3.061
242
+ 2023-12-11,GMRP&UI.NS,7.99,MEDIUM,-0.003,4.547,-3.861
243
+ 2023-12-11,PAYTM.NS,5.16,MEDIUM,-6.407,-7.991,-6.521
244
+ 2023-12-11,DCMSRIND.NS,4.48,LOW,-3.327,0.624,-0.141
245
+ 2023-12-14,ICDSLTD.NS,11.22,MEDIUM,4.618,15.274,10.993
246
+ 2023-12-14,KAMATHOTEL.NS,9.45,MEDIUM,-5.297,-4.812,-3.948
247
+ 2023-12-14,HAVISHA.NS,9.27,MEDIUM,-4.004,-11.411,-7.707
248
+ 2023-12-14,DRCSYSTEMS.NS,8.82,MEDIUM,4.62,3.267,-0.669
249
+ 2023-12-14,HECPROJECT.NS,8.75,MEDIUM,4.674,15.355,27.205
250
+ 2023-12-19,AXITA.NS,4.84,LOW,-6.957,10.643,5.802
251
+ 2023-12-22,MONARCH.NS,3.88,LOW,0.736,-2.507,-2.192
252
+ 2023-12-28,SBGLP.NS,3.67,LOW,1.274,3.161,1.68
253
+ 2024-01-02,HYBRIDFIN.NS,8.55,MEDIUM,4.65,4.65,0.69
254
+ 2024-01-05,GTL.NS,10.98,MEDIUM,4.434,4.138,-4.146
255
+ 2024-01-05,CGCL.NS,10.04,MEDIUM,14.307,8.073,8.691
256
+ 2024-01-05,CUPID.NS,9.88,MEDIUM,4.698,15.437,27.296
257
+ 2024-01-05,MCL.NS,9.46,LOW,9.48,-1.179,-2.168
258
+ 2024-01-05,GLOBE.NS,8.69,MEDIUM,4.194,6.442,1.947
259
+ 2024-01-18,ABCOTS.NS,1.83,LOW,4.7,20.66,33.02
260
+ 2024-01-24,SUKHJITS.NS,3.7,LOW,2.066,9.496,10.102
261
+ 2024-01-30,GANGAFORGE.NS,11.44,MEDIUM,4.528,14.872,17.976
262
+ 2024-01-30,URJA.NS,10.91,MEDIUM,9.588,26.335,14.214
263
+ 2024-01-30,HUBTOWN.NS,10.81,MEDIUM,4.691,6.096,3.545
264
+ 2024-01-30,LAMBODHARA.NS,10.63,LOW,1.334,-3.671,-3.058
265
+ 2024-01-30,UNITECH.NS,10.54,MEDIUM,4.508,15.085,18.61
266
+ 2024-02-02,FINOPB.NS,4.19,LOW,19.07,19.748,0.975
267
+ 2024-02-07,TIPSFILMS.NS,5.31,LOW,1.165,-7.39,-8.051
268
+ 2024-02-07,NYKAA.NS,4.07,LOW,-3.366,-7.263,-2.057
269
+ 2024-02-12,ABCOTS.NS,10.35,MEDIUM,4.7,4.7,27.283
270
+ 2024-02-12,FIBERWEB.NS,7.39,MEDIUM,-0.582,7.446,9.841
271
+ 2024-02-12,NPST.NS,6.95,MEDIUM,4.699,6.358,-2.398
272
+ 2024-02-12,INTLCONV.NS,6.68,MEDIUM,-3.651,3.37,1.243
273
+ 2024-02-12,GANESHBE.NS,6.23,MEDIUM,6.535,7.853,6.562
274
+ 2024-02-15,INDOAMIN.NS,5.79,LOW,3.302,0.923,0.787
275
+ 2024-02-15,KAMOPAINTS.NS,5.23,LOW,-0.813,2.292,1.83
276
+ 2024-02-15,ETHOSLTD.NS,3.44,LOW,-0.252,3.841,11.447
277
+ 2024-02-20,ZEELEARN.NS,9.34,MEDIUM,-2.362,-5.455,-8.547
278
+ 2024-02-20,TIJARIA.NS,9.17,MEDIUM,4.103,14.165,25.486
279
+ 2024-02-20,AURIGROW.NS,9.06,LOW,-6.55,-0.3,5.95
280
+ 2024-02-20,TARMAT.NS,8.97,MEDIUM,19.687,72.439,94.13
281
+ 2024-02-20,MANAKCOAT.NS,8.63,LOW,8.915,-2.417,-9.266
282
+ 2024-02-28,RHL.NS,6.52,LOW,1.651,7.854,7.804
283
+ 2024-02-28,VIRINCHI.NS,4.92,LOW,-0.553,-0.934,-5.243
284
+ 2024-02-28,FORCEMOT.NS,3.5,LOW,4.614,0.386,-8.573
285
+ 2024-03-07,INDOAMIN.NS,5.52,LOW,-7.207,-15.419,-15.688
286
+ 2024-03-07,FUSION.NS,5.47,LOW,-3.514,-9.64,-4.985
287
+ 2024-03-07,GSLSU.NS,4.96,LOW,-2.535,-15.281,-11.474
288
+ 2024-03-07,DCMSRIND.NS,4.48,LOW,-4.894,-9.625,-4.374
289
+ 2024-03-07,RATEGAIN.NS,3.71,LOW,1.121,-6.169,-5.928
290
+ 2024-03-13,PATINTLOG.NS,14.07,MEDIUM,8.86,6.57,2.245
291
+ 2024-03-13,ABMINTLLTD.NS,13.95,MEDIUM,5.249,14.572,16.903
292
+ 2024-03-13,ELGIRUBCO.NS,13.92,MEDIUM,8.125,14.908,6.046
293
+ 2024-03-13,ORIENTCER.NS,13.74,MEDIUM,4.7,10.589,9.7
294
+ 2024-03-13,SONAL.NS,13.71,MEDIUM,12.649,15.012,15.993
295
+ 2024-03-18,TRU.NS,8.0,LOW,-0.574,13.947,7.28
296
+ 2024-03-18,USK.NS,6.75,MEDIUM,-2.441,0.414,-0.198
297
+ 2024-03-18,EXXARO.NS,5.92,LOW,-1.108,-4.772,-7.466
298
+ 2024-03-18,SHRIRAMPPS.NS,5.88,LOW,-3.565,0.166,6.043
299
+ 2024-03-18,INOXGREEN.NS,4.68,LOW,-4.591,-4.019,-2.589
300
+ 2024-03-21,GATECHDVR.NS,9.3,MEDIUM,-1.729,-10.3,-10.3
301
+ 2024-03-21,VIRINCHI.NS,7.47,MEDIUM,3.373,1.703,9.717
302
+ 2024-03-21,SHAH.NS,5.93,LOW,4.539,-1.913,2.926
303
+ 2024-03-21,KOTYARK.NS,5.76,LOW,-2.132,12.531,13.082
304
+ 2024-03-21,SMLT.NS,5.55,LOW,-3.423,3.359,4.641
305
+ 2024-03-27,VCL.NS,7.93,MEDIUM,-5.3,4.7,14.7
306
+ 2024-03-27,AERONEU.NS,7.13,LOW,-2.108,6.495,7.097
307
+ 2024-03-27,MEGASTAR.NS,5.33,MEDIUM,-4.504,3.904,3.396
308
+ 2024-03-27,RELCHEMQ.NS,5.26,LOW,-0.94,2.593,21.205
309
+ 2024-03-27,FIBERWEB.NS,5.0,LOW,-3.18,14.58,15.06
310
+ 2024-04-02,AKSHAR.NS,7.91,MEDIUM,3.782,11.945,7.863
311
+ 2024-04-02,GMRP&UI.NS,4.97,LOW,4.652,15.308,23.381
312
+ 2024-04-05,IZMO.NS,10.37,LOW,-5.661,-6.823,-13.565
313
+ 2024-04-05,TARIL.NS,10.0,MEDIUM,1.568,11.54,22.94
314
+ 2024-04-05,SIMPLEXINF.NS,8.35,MEDIUM,1.669,5.724,9.936
315
+ 2024-04-05,CUPID.NS,8.08,MEDIUM,-1.732,2.913,-5.875
316
+ 2024-04-05,SPARC.NS,7.98,LOW,4.693,-1.211,-10.862
317
+ 2024-04-10,TECILCHEM.NS,4.51,LOW,-5.322,-2.047,-0.3
318
+ 2024-04-16,SOLEX.NS,9.68,MEDIUM,4.697,15.453,5.775
319
+ 2024-04-16,TPHQ.NS,6.83,MEDIUM,3.404,-4.004,-7.707
320
+ 2024-04-16,GICL.NS,6.51,LOW,4.635,13.919,12.626
321
+ 2024-04-16,HARDWYN.NS,5.65,LOW,-0.769,-4.206,-3.581
322
+ 2024-04-16,RHL.NS,5.33,LOW,1.747,3.142,3.39
323
+ 2024-04-22,KPIGREEN.NS,7.59,LOW,4.7,15.46,4.176
324
+ 2024-04-22,GUJRAFFIA.NS,5.26,MEDIUM,0.808,3.578,14.104
325
+ 2024-04-22,PRITIKAUTO.NS,2.77,LOW,1.682,5.646,6.006
326
+ 2024-04-25,GSLSU.NS,6.31,LOW,-1.355,13.201,4.76
327
+ 2024-04-25,ALIVUS.NS,4.08,LOW,8.288,10.146,10.286
328
+ 2024-04-30,TIJARIA.NS,11.28,MEDIUM,1.565,5.294,9.49
329
+ 2024-04-30,SKYGOLD.NS,9.13,MEDIUM,4.699,11.282,10.947
330
+ 2024-04-30,ATALREAL.NS,8.96,MEDIUM,4.565,-5.165,-6.246
331
+ 2024-04-30,JITFINFRA.NS,8.62,LOW,4.699,6.658,2.019
332
+ 2024-04-30,KAVDEFENCE.NS,8.6,MEDIUM,4.462,14.938,26.684
333
+ 2024-05-06,INOXGREEN.NS,4.9,LOW,-5.08,-11.465,-11.764
334
+ 2024-05-06,TECILCHEM.NS,2.18,LOW,-1.331,-2.98,-4.424
335
+ 2024-05-09,MONARCH.NS,6.15,MEDIUM,-0.252,6.346,8.063
336
+ 2024-05-09,NRL.NS,5.91,LOW,1.6,-1.046,0.039
337
+ 2024-05-09,ELDEHSG.NS,5.54,MEDIUM,5.321,9.565,13.172
338
+ 2024-05-09,NDLVENTURE.NS,5.32,LOW,3.384,2.842,2.138
339
+ 2024-05-09,SOLEX.NS,4.78,MEDIUM,-4.122,5.731,16.599
340
+ 2024-05-14,SYRMA.NS,6.23,LOW,-0.112,-1.053,7.77
341
+ 2024-05-14,GANESHBE.NS,4.4,LOW,0.547,-0.495,-0.105
342
+ 2024-05-17,KAYNES.NS,10.33,LOW,9.957,8.21,8.314
343
+ 2024-05-17,HYBRIDFIN.NS,9.1,MEDIUM,9.7,20.7,32.7
344
+ 2024-05-17,MWL.NS,4.66,LOW,0.88,-0.233,0.273
345
+ 2024-05-23,RADAAN.NS,9.77,MEDIUM,3.7,-0.3,-8.3
346
+ 2024-05-23,DREDGECORP.NS,8.53,LOW,4.697,-4.789,-5.028
347
+ 2024-05-23,MICEL.NS,8.53,LOW,-5.3,-12.581,-14.335
348
+ 2024-05-23,GLOBAL.NS,8.34,MEDIUM,-2.76,-8.091,-6.692
349
+ 2024-05-23,SUPREMEINF.NS,8.3,MEDIUM,1.663,1.619,-2.353
350
+ 2024-05-31,RELIABLE.NS,2.42,LOW,-5.212,-5.212,-5.212
351
+ 2024-06-05,PAKKA.NS,6.38,MEDIUM,1.407,11.77,13.745
352
+ 2024-06-05,PRITIKAUTO.NS,6.04,MEDIUM,4.529,7.104,14.187
353
+ 2024-06-05,SBGLP.NS,5.61,LOW,6.793,7.196,8.518
354
+ 2024-06-05,MEGASTAR.NS,5.6,LOW,5.766,6.954,9.339
355
+ 2024-06-05,INTLCONV.NS,5.41,LOW,5.338,7.109,7.633
356
+ 2024-06-10,DATAPATTNS.NS,5.05,LOW,0.283,2.529,15.866
357
+ 2024-06-10,ATL.NS,4.7,LOW,-0.165,-0.204,5.52
358
+ 2024-06-13,SANOFI.NS,11.53,MEDIUM,4.7,10.105,4.846
359
+ 2024-06-13,TCIFINANCE.NS,10.98,MEDIUM,4.682,15.28,26.964
360
+ 2024-06-13,ZENITHEXPO.NS,9.64,MEDIUM,4.698,15.458,27.318
361
+ 2024-06-13,KAYA.NS,9.59,LOW,14.611,11.45,20.921
362
+ 2024-06-13,DRCSYSTEMS.NS,9.2,MEDIUM,16.858,18.314,14.177
363
+ 2024-06-19,TECILCHEM.NS,10.3,MEDIUM,4.697,-5.566,-14.835
364
+ 2024-06-19,TRU.NS,6.85,LOW,3.884,-1.173,3.133
365
+ 2024-06-24,TPHQ.NS,8.65,MEDIUM,4.245,14.094,7.276
366
+ 2024-06-24,TIPSFILMS.NS,5.1,MEDIUM,4.757,-0.156,0.773
367
+ 2024-06-27,STARTECK.NS,5.12,LOW,-1.074,-0.02,1.448
368
+ 2024-06-27,HARSHA.NS,4.55,LOW,1.272,3.206,7.479
369
+ 2024-06-27,DHARMAJ.NS,4.53,MEDIUM,1.547,21.645,25.51
370
+ 2024-07-05,GTLINFRA.NS,12.25,MEDIUM,-5.385,-14.828,-23.302
371
+ 2024-07-05,CENTEXT.NS,10.54,MEDIUM,-5.337,-4.019,5.822
372
+ 2024-07-05,SUVEN.NS,10.17,MEDIUM,9.415,5.091,4.283
373
+ 2024-07-05,MADHUCON.NS,9.97,MEDIUM,4.65,8.061,18.964
374
+ 2024-07-05,COUNCODOS.NS,9.74,LOW,-2.437,-8.562,-0.3
375
+ 2024-07-10,TECILCHEM.NS,5.72,LOW,4.698,9.921,-0.885
376
+ 2024-07-10,INOXGREEN.NS,4.39,LOW,1.391,1.78,0.515
377
+ 2024-07-10,AARTECH.NS,2.77,LOW,1.699,3.738,-2.385
378
+ 2024-07-15,GATECHDVR.NS,8.91,MEDIUM,4.665,15.066,14.594
379
+ 2024-07-15,RELIABLE.NS,6.88,LOW,4.696,14.769,26.453
380
+ 2024-07-15,SONAL.NS,5.49,LOW,1.984,-0.395,-2.393
381
+ 2024-07-15,RHL.NS,5.49,LOW,-2.693,-4.076,-4.041
382
+ 2024-07-15,NETWEB.NS,4.18,LOW,2.743,-0.869,-0.76
383
+ 2024-07-19,AERONEU.NS,9.52,MEDIUM,1.784,14.091,12.474
384
+ 2024-07-19,HARSHA.NS,4.39,LOW,1.438,5.023,4.11
385
+ 2024-07-24,ETHOSLTD.NS,4.54,LOW,-2.668,-2.956,-3.256
386
+ 2024-07-24,DIAMINESQ.NS,4.27,LOW,6.186,0.145,2.515
387
+ 2024-07-24,SRGHFL.NS,3.52,LOW,-3.798,0.002,-0.325
388
+ 2024-07-29,NIBL.NS,10.56,LOW,7.677,-8.347,-4.791
389
+ 2024-07-29,FLEXITUFF.NS,10.42,MEDIUM,4.696,15.449,10.858
390
+ 2024-07-29,SUMEETINDS.NS,10.06,MEDIUM,4.573,7.751,3.302
391
+ 2024-07-29,ESTER.NS,9.71,MEDIUM,2.975,8.114,-0.17
392
+ 2024-07-29,HUBTOWN.NS,9.3,MEDIUM,4.697,11.832,23.324
393
+ 2024-08-01,TECILCHEM.NS,5.18,LOW,-3.795,0.998,-2.946
394
+ 2024-08-06,TIPSFILMS.NS,7.33,MEDIUM,-0.268,19.582,15.866
395
+ 2024-08-06,TPHQ.NS,6.6,LOW,3.297,8.333,4.736
396
+ 2024-08-06,SONAL.NS,6.39,LOW,0.959,-1.511,0.862
397
+ 2024-08-06,RHL.NS,6.17,LOW,8.527,1.335,-0.962
398
+ 2024-08-06,ATAM.NS,5.84,LOW,8.674,9.322,4.038
399
+ 2024-08-09,KPIGREEN.NS,5.51,LOW,2.883,-6.188,-5.887
400
+ 2024-08-09,EMSLIMITED.NS,4.69,LOW,5.355,1.932,-0.469
401
+ 2024-08-09,GUJRAFFIA.NS,4.53,LOW,-4.179,-1.983,0.87
402
+ 2024-08-09,SUPRIYA.NS,4.51,LOW,13.592,11.147,18.449
403
+ 2024-08-09,DHARMAJ.NS,4.49,LOW,3.635,-1.865,2.055
404
+ 2024-08-14,AKSHAR.NS,6.9,MEDIUM,1.238,15.597,12.52
405
+ 2024-08-14,LEMERITE.NS,6.12,LOW,-0.525,-0.25,-0.125
406
+ 2024-08-14,GSLSU.NS,5.88,MEDIUM,-4.227,1.655,0.731
407
+ 2024-08-14,RADHIKAJWE.NS,5.1,MEDIUM,8.35,40.598,40.168
408
+ 2024-08-14,REDTAPE.NS,4.99,LOW,0.04,2.725,8.033
409
+ 2024-08-20,VINNY.NS,12.2,MEDIUM,-5.342,-14.754,-23.325
410
+ 2024-08-20,E2E.NS,10.84,MEDIUM,4.7,11.11,9.48
411
+ 2024-08-20,MURUDCERA.NS,10.64,MEDIUM,-6.206,-4.479,-7.077
412
+ 2024-08-20,HGM.NS,10.25,LOW,4.424,2.283,0.884
413
+ 2024-08-20,KINGFA.NS,9.99,LOW,-5.771,-6.002,-8.792
414
+ 2024-08-28,SOLEX.NS,5.24,LOW,-4.41,5.594,-16.817
415
+ 2024-08-28,RELIABLE.NS,3.67,LOW,1.698,5.805,8.851
416
+ 2024-09-05,LEMERITE.NS,2.76,LOW,1.024,1.244,1.048
417
+ 2024-09-10,INDOTHAI.NS,11.11,MEDIUM,4.693,15.447,14.586
418
+ 2024-09-10,MODIRUBBER.NS,10.06,MEDIUM,-5.302,-9.67,-9.146
419
+ 2024-09-10,SIMPLEXINF.NS,7.54,LOW,4.7,15.456,9.615
420
+ 2024-09-10,AYMSYNTEX.NS,7.26,MEDIUM,1.697,5.817,10.103
421
+ 2024-09-10,NORBTEAEXP.NS,7.11,MEDIUM,4.655,15.363,27.137
422
+ 2024-09-18,CCCL.NS,12.46,MEDIUM,4.695,15.433,27.244
423
+ 2024-09-23,IRIS.NS,4.75,MEDIUM,1.697,5.814,10.096
424
+ 2024-09-26,ALIVUS.NS,4.16,MEDIUM,0.754,9.876,12.657
425
+ 2024-09-26,KAMOPAINTS.NS,3.03,MEDIUM,-20.3,-42.716,-53.668
426
+ 2024-10-01,ABCOTS.NS,8.55,MEDIUM,3.614,9.813,4.313
427
+ 2024-10-01,SURANASOL.NS,8.51,MEDIUM,4.694,-5.551,-5.792
428
+ 2024-10-01,RPOWER.NS,7.75,MEDIUM,4.691,-5.565,-5.82
429
+ 2024-10-01,AQYLON.NS,5.27,MEDIUM,1.698,5.813,10.094
430
+ 2024-10-01,AYMSYNTEX.NS,4.85,MEDIUM,-2.222,-4.077,-5.999
431
+ 2024-10-07,SHRIRAMPPS.NS,5.88,MEDIUM,6.105,6.959,5.408
432
+ 2024-10-07,INOXGREEN.NS,5.66,MEDIUM,5.955,5.051,1.28
433
+ 2024-10-07,LORDSCHLO.NS,5.44,MEDIUM,4.522,12.538,7.788
434
+ 2024-10-07,AMNPLST.NS,4.84,LOW,2.432,3.798,0.541
435
+ 2024-10-07,BIKAJI.NS,4.61,LOW,0.678,-0.446,0.338
436
+ 2024-10-23,CREATIVEYE.NS,10.45,MEDIUM,4.676,-5.659,-14.941
437
+ 2024-10-23,AQYLON.NS,5.99,MEDIUM,1.698,5.813,10.098
438
+ 2024-10-23,TARAPUR.NS,5.97,MEDIUM,1.691,-2.392,-6.307
439
+ 2024-10-23,SUMEETINDS.NS,0.85,LOW,-0.3,-0.3,-0.3
440
+ 2024-10-31,GATECHDVR.NS,6.48,LOW,2.761,6.843,-0.3
441
+ 2024-10-31,RAMRAT.NS,4.68,LOW,2.606,1.249,0.839
442
+ 2024-10-31,FIVESTAR.NS,4.65,LOW,0.459,-5.65,-8.293
443
+ 2024-11-05,SBGLP.NS,3.53,LOW,5.236,6.061,3.376
444
+ 2024-11-13,LANDSMILL.NS,11.28,MEDIUM,4.388,14.544,25.481
445
+ 2024-11-13,TERASOFT.NS,10.6,MEDIUM,4.695,15.453,27.306
446
+ 2024-11-13,AMBICAAGAR.NS,8.23,LOW,-4.038,-1.71,-5.152
447
+ 2024-11-13,SUPERSPIN.NS,8.22,LOW,1.695,-0.619,-4.61
448
+ 2024-11-13,TFL.NS,7.91,MEDIUM,-1.905,-0.68,-6.423
449
+ 2024-11-25,RELIABLE.NS,5.62,LOW,2.116,4.938,2.037
450
+ 2024-11-25,ASIANENE.NS,5.01,LOW,-1.191,4.373,6.342
451
+ 2024-11-25,COMSYN.NS,4.94,LOW,-0.271,7.292,4.264
452
+ 2024-11-25,GATECHDVR.NS,4.63,LOW,0.876,6.759,16.171
453
+ 2024-11-25,MVGJL.NS,4.52,LOW,1.184,3.642,2.712
454
+ 2024-11-28,DHARMAJ.NS,4.49,LOW,-0.956,0.268,5.819
455
+ 2024-11-28,IRIS.NS,3.41,LOW,1.698,4.555,6.735
456
+ 2024-12-03,KAMOPAINTS.NS,4.0,LOW,1.651,5.737,5.615
457
+ 2024-12-06,THOMASCOTT.NS,10.27,LOW,4.698,15.456,27.028
458
+ 2024-12-06,TOUCHWOOD.NS,9.93,MEDIUM,5.126,-7.204,-5.426
459
+ 2024-12-06,MANAKCOAT.NS,9.51,MEDIUM,4.692,6.497,1.399
460
+ 2024-12-06,CTE.NS,8.74,LOW,0.852,0.075,-0.242
461
+ 2024-12-06,ENERGYDEV.NS,8.59,LOW,4.675,15.426,15.126
462
+ 2024-12-11,DIGIDRIVE.NS,9.06,LOW,3.378,3.735,-3.221
463
+ 2024-12-11,AVONMORE.NS,7.16,LOW,-4.038,5.368,6.169
464
+ 2024-12-11,TRU.NS,4.79,MEDIUM,-1.209,1.639,12.064
465
+ 2024-12-16,TPHQ.NS,10.96,MEDIUM,3.955,13.317,4.806
466
+ 2024-12-24,GMRP&UI.NS,3.99,LOW,4.697,15.457,27.312
467
+ 2024-12-30,FLEXITUFF.NS,9.07,LOW,4.693,7.724,3.43
468
+ 2024-12-30,ASIANHOTNR.NS,8.87,MEDIUM,4.699,7.369,-3.114
469
+ 2024-12-30,GVPTECH.NS,8.72,LOW,4.676,15.298,9.269
470
+ 2024-12-30,AKI.NS,8.68,LOW,1.375,-0.539,-6.361
471
+ 2024-12-30,TARACHAND.NS,8.56,LOW,-4.116,-3.098,-8.313
472
+ 2025-01-02,AARTECH.NS,9.92,LOW,4.693,15.452,4.163
473
+ 2025-01-07,ATLASCYCLE.NS,11.22,MEDIUM,4.698,20.962,39.752
474
+ 2025-01-10,IRIS.NS,6.8,LOW,4.354,6.962,2.717
475
+ 2025-01-15,ACUTAAS.NS,4.43,LOW,-0.009,-0.82,-3.929
476
+ 2025-01-15,RATNAVEER.NS,4.17,LOW,2.121,12.458,5.848
477
+ 2025-01-15,GSLSU.NS,3.75,LOW,6.001,17.696,7.484
478
+ 2025-01-15,AKSHAR.NS,3.39,LOW,-1.67,-1.67,-5.779
479
+ 2025-01-20,TERASOFT.NS,6.25,MEDIUM,1.696,5.808,10.092
480
+ 2025-01-20,SUPERSPIN.NS,5.86,LOW,4.584,3.792,-6.373
481
+ 2025-01-20,RAJTV.NS,5.43,LOW,1.081,5.308,2.093
482
+ 2025-01-20,PANACHE.NS,5.43,MEDIUM,1.688,5.79,4.037
483
+ 2025-01-20,SVLL.NS,4.93,MEDIUM,1.694,5.726,1.535
484
+ 2025-01-28,PRUDENT.NS,8.31,LOW,-2.33,1.921,0.704
485
+ 2025-01-28,ABSLAMC.NS,8.26,LOW,7.121,11.354,6.769
486
+ 2025-01-28,PRUDMOULI.NS,8.24,LOW,2.611,6.344,5.478
487
+ 2025-01-28,DJML.NS,7.3,LOW,0.296,5.003,9.518
488
+ 2025-01-28,RHL.NS,6.32,LOW,-4.805,-1.201,-1.201
489
+ 2025-01-31,MANYAVAR.NS,6.5,LOW,3.941,2.34,2.093
490
+ 2025-02-07,NORBTEAEXP.NS,4.82,MEDIUM,4.691,15.407,27.239
491
+ 2025-02-07,BOHRAIND.NS,0.64,LOW,4.693,4.693,4.693
492
+ 2025-02-17,ORTINGLOBE.NS,8.5,MEDIUM,-11.968,-5.559,-2.765
493
+ 2025-02-17,NRL.NS,8.24,LOW,-7.087,-3.583,-6.24
494
+ 2025-02-17,ASIANENE.NS,7.86,LOW,-5.878,-0.174,15.234
495
+ 2025-02-17,ATAM.NS,7.47,LOW,-6.285,2.347,0.738
496
+ 2025-02-17,NETWEB.NS,7.32,LOW,-2.464,17.788,12.106
497
+ 2025-03-03,NAGREEKEXP.NS,12.94,MEDIUM,5.795,17.671,12.087
498
+ 2025-03-03,MANGALAM.NS,12.6,MEDIUM,-0.679,11.043,7.023
499
+ 2025-03-03,VASWANI.NS,12.54,MEDIUM,-4.624,8.458,7.211
500
+ 2025-03-03,WANBURY.NS,12.49,MEDIUM,9.071,23.002,28.323
501
+ 2025-03-03,SGL.NS,12.22,MEDIUM,6.338,19.875,12.451
502
+ 2025-03-06,RBZJEWEL.NS,8.77,LOW,-0.803,-6.896,-7.635
503
+ 2025-03-06,PYRAMID.NS,7.73,LOW,1.258,-5.448,-8.298
504
+ 2025-03-06,MANOMAY.NS,7.33,LOW,6.299,7.294,13.224
505
+ 2025-03-06,HPAL.NS,6.86,LOW,0.902,-7.591,-11.0
506
+ 2025-03-06,SIGNATURE.NS,6.44,LOW,4.345,4.828,6.261
507
+ 2025-03-17,SBGLP.NS,8.59,MEDIUM,-0.3,15.382,20.011
508
+ 2025-03-17,DYCL.NS,8.43,LOW,-0.3,-1.822,4.794
509
+ 2025-03-17,BALUFORGE.NS,7.17,LOW,-0.3,14.978,49.941
510
+ 2025-03-17,PAKKA.NS,6.66,LOW,-0.3,9.89,6.432
511
+ 2025-03-17,MANYAVAR.NS,5.76,LOW,-0.3,5.957,4.745
512
+ 2025-03-25,SUPREMEINF.NS,6.29,LOW,4.699,4.649,8.882
513
+ 2025-03-25,BOHRAIND.NS,5.91,MEDIUM,-2.311,-6.205,-9.946
514
+ 2025-03-25,AQYLON.NS,5.83,MEDIUM,1.696,5.809,1.617
515
+ 2025-03-28,EXXARO.NS,6.89,LOW,2.798,6.757,4.003
516
+ 2025-03-28,USK.NS,6.44,LOW,3.07,5.998,0.75
517
+ 2025-03-28,DAVANGERE.NS,6.21,LOW,3.384,-0.037,-5.037
518
+ 2025-03-28,MODTHREAD.NS,6.17,LOW,-4.683,2.229,2.229
519
+ 2025-03-28,DIGIDRIVE.NS,5.89,LOW,2.105,11.169,2.956
520
+ 2025-04-03,KOTYARK.NS,6.14,MEDIUM,4.692,1.16,-2.859
521
+ 2025-04-03,CURAA.NS,5.28,LOW,4.673,15.167,39.394
522
+ 2025-04-08,STEELCAS.NS,11.5,MEDIUM,-5.996,14.302,11.645
523
+ 2025-04-08,RKSWAMY.NS,11.1,LOW,-0.871,2.769,3.473
524
+ 2025-04-08,SYRMA.NS,11.07,MEDIUM,-0.668,12.959,14.902
525
+ 2025-04-08,MALLCOM.NS,10.68,MEDIUM,0.849,6.099,11.167
526
+ 2025-04-08,DYCL.NS,10.29,MEDIUM,-4.613,9.114,7.351
527
+ 2025-04-15,SOLEX.NS,6.07,LOW,1.694,5.812,10.079
528
+ 2025-04-21,NACLIND.NS,11.15,MEDIUM,4.699,-5.54,-14.78
529
+ 2025-04-21,UEL.NS,8.26,LOW,4.69,15.446,9.659
530
+ 2025-04-21,SHREERAMA.NS,7.47,MEDIUM,4.693,1.73,-4.552
531
+ 2025-04-21,SADBHAV.NS,7.4,MEDIUM,4.676,15.35,15.19
532
+ 2025-04-21,HERANBA.NS,7.36,LOW,4.697,0.536,-3.463
533
+ 2025-04-29,CURAA.NS,7.02,MEDIUM,1.699,5.797,10.066
534
+ 2025-05-05,KFINTECH.NS,4.87,LOW,-4.538,-2.405,2.467
535
+ 2025-05-05,IRIS.NS,4.49,LOW,-1.156,-6.767,-4.932
536
+ 2025-05-05,TRU.NS,4.19,LOW,0.714,4.628,11.874
537
+ 2025-05-08,GMRP&UI.NS,10.7,LOW,-3.403,4.837,11.125
538
+ 2025-05-08,MEDICAMEQ.NS,10.04,LOW,-0.973,3.267,5.972
539
+ 2025-05-08,RISHABH.NS,9.46,LOW,-3.029,4.835,13.691
540
+ 2025-05-08,DATAPATTNS.NS,9.33,LOW,3.918,12.55,18.894
541
+ 2025-05-08,SENCO.NS,9.32,LOW,-0.539,5.635,7.931
542
+ 2025-05-13,AARTISURF.NS,10.88,LOW,9.691,20.959,13.386
543
+ 2025-05-13,BYKE.NS,10.41,LOW,2.885,6.444,9.209
544
+ 2025-05-13,ZENTEC.NS,8.87,LOW,4.698,15.456,22.319
545
+ 2025-05-13,AVROIND.NS,8.05,LOW,-0.213,9.591,6.492
546
+ 2025-05-13,TNTELE.NS,7.94,LOW,4.084,3.145,-1.031
547
+ 2025-05-21,CURAA.NS,6.03,MEDIUM,1.693,5.808,7.92
548
+ 2025-05-26,TRU.NS,6.66,MEDIUM,4.681,8.8,13.11
549
+ 2025-05-26,KRISHIVAL.NS,4.4,LOW,4.699,7.634,8.808
550
+ 2025-05-29,SOLEX.NS,6.0,LOW,1.7,5.816,10.1
551
+ 2025-06-03,ARROWGREEN.NS,6.58,LOW,-1.344,-5.255,-9.015
552
+ 2025-06-03,UEL.NS,5.68,LOW,4.694,15.437,21.218
553
+ 2025-06-11,CURAA.NS,5.98,MEDIUM,1.696,5.81,10.089
554
+ 2025-06-16,TRU.NS,6.11,MEDIUM,1.646,5.676,9.915
555
+ 2025-06-24,WSI.NS,5.21,LOW,1.697,5.805,10.075
556
+ 2025-06-24,SUMEETINDS.NS,4.78,LOW,4.688,15.42,27.263
557
+ 2025-06-24,MACPOWER.NS,4.29,LOW,1.699,4.821,0.671
558
+ 2025-07-02,WAAREEINDO.NS,6.86,LOW,-0.3,4.699,4.699
559
+ 2025-07-02,CURAA.NS,6.4,MEDIUM,1.697,5.812,10.092
560
+ 2025-07-07,NIRAJISPAT.NS,10.33,MEDIUM,1.691,5.8,7.918
561
+ 2025-07-07,TRU.NS,6.15,MEDIUM,1.672,5.772,1.516
562
+ 2025-07-10,DIGJAMLMTD.NS,7.64,MEDIUM,1.7,5.782,10.031
563
+ 2025-07-10,TPHQ.NS,5.18,MEDIUM,0.824,3.071,1.947
564
+ 2025-07-15,SAMPANN.NS,9.81,LOW,9.69,5.798,8.263
565
+ 2025-07-15,CENTRUM.NS,8.27,LOW,-1.224,-1.599,-3.647
566
+ 2025-07-15,GAYAHWS.NS,7.78,LOW,-2.56,0.83,3.09
567
+ 2025-07-15,LANDSMILL.NS,7.57,LOW,4.462,13.986,2.557
568
+ 2025-07-15,DIACABS.NS,6.05,MEDIUM,2.69,-2.648,-3.371
569
+ 2025-07-18,TECILCHEM.NS,5.16,LOW,4.669,15.39,18.237
570
+ 2025-07-23,CURAA.NS,5.87,MEDIUM,1.697,5.813,10.093
571
+ 2025-07-23,WAAREEINDO.NS,2.53,LOW,-0.3,4.699,4.699
572
+ 2025-08-05,DNAMEDIA.NS,7.41,LOW,19.553,23.72,22.004
573
+ 2025-08-05,UEL.NS,5.62,LOW,4.688,12.933,12.933
574
+ 2025-08-05,GAYAHWS.NS,4.77,MEDIUM,1.543,5.23,0.622
575
+ 2025-08-13,GATECH.NS,5.85,LOW,-0.3,8.211,16.721
576
+ 2025-08-13,CURAA.NS,5.08,MEDIUM,1.697,5.813,10.097
577
+ 2025-08-13,KOTYARK.NS,5.0,LOW,19.116,71.639,70.802
578
+ 2025-08-13,WAAREEINDO.NS,3.24,MEDIUM,-0.3,4.697,4.697
579
+ 2025-08-19,DHARMAJ.NS,4.92,LOW,1.689,5.796,3.923
580
+ 2025-08-28,DRCSYSTEMS.NS,9.15,LOW,-3.775,1.775,7.688
581
+ 2025-08-28,NRAIL.NS,8.02,LOW,-5.301,-9.533,-10.069
582
+ 2025-08-28,RADAAN.NS,7.75,LOW,4.534,8.461,6.649
583
+ 2025-08-28,KRITIKA.NS,7.72,LOW,19.674,24.084,23.695
584
+ 2025-08-28,JITFINFRA.NS,6.85,LOW,-6.332,-1.702,24.695
585
+ 2025-09-05,WAAREEINDO.NS,1.15,LOW,4.691,4.691,4.691
586
+ 2025-09-15,RVTH.NS,6.86,LOW,4.696,15.453,27.311
587
+ 2025-09-18,KAVDEFENCE.NS,8.28,MEDIUM,1.745,12.193,23.714
588
+ 2025-09-18,IZMO.NS,8.26,MEDIUM,4.697,15.452,27.314
589
+ 2025-09-18,BAFNAPH.NS,7.76,MEDIUM,4.7,15.451,27.304
590
+ 2025-09-18,GAYAHWS.NS,6.3,MEDIUM,4.515,14.885,26.367
591
+ 2025-09-18,NORBTEAEXP.NS,5.79,MEDIUM,1.692,-2.368,-1.609
592
+ 2025-09-26,PRUDMOULI.NS,8.9,LOW,0.518,-3.819,-4.708
593
+ 2025-09-26,WAAREEINDO.NS,1.35,LOW,4.694,9.934,21.224
594
+ 2025-10-10,SECMARK.NS,6.87,LOW,3.564,1.491,9.303
595
+ 2025-10-29,UYFINCORP.NS,3.55,LOW,2.996,8.255,9.448
596
+ 2025-11-03,SOMATEX.NS,9.13,MEDIUM,4.698,-4.607,-13.939
597
+ 2025-11-17,NIRAJISPAT.NS,10.05,LOW,4.197,4.535,3.027
598
+ 2025-11-20,TARSONS.NS,3.63,LOW,-1.477,2.756,1.805
599
+ 2025-11-25,PANACHE.NS,7.36,LOW,2.971,12.74,13.027
600
+ 2025-11-25,FLEXITUFF.NS,6.76,MEDIUM,4.693,15.373,27.162
601
+ 2025-11-25,AJOONI.NS,6.09,LOW,9.997,10.226,9.997
602
+ 2025-11-25,MICEL.NS,5.81,LOW,-1.225,3.054,9.161
603
+ 2025-11-25,THEMISMED.NS,5.46,LOW,3.951,4.632,2.599
604
+ 2025-12-08,VCL.NS,10.13,MEDIUM,4.659,-5.259,-13.936
605
+ 2025-12-08,TRANSRAILL.NS,6.12,LOW,3.957,1.197,7.74
606
+ 2025-12-11,KAYNES.NS,6.2,LOW,5.242,3.288,-0.176
607
+ 2025-12-11,DIGJAMLMTD.NS,5.93,LOW,4.688,12.332,5.703
608
+ 2025-12-16,ARVEE.NS,7.3,LOW,19.697,65.453,39.556
609
+ 2025-12-19,TECILCHEM.NS,5.18,LOW,7.255,4.289,8.486
610
+ 2025-12-19,STALLION.NS,4.64,LOW,3.34,7.896,18.978
611
+ 2025-12-19,USK.NS,4.33,LOW,4.45,4.147,-0.43
612
+ 2025-12-19,CHEMPLASTS.NS,3.21,LOW,6.816,5.203,2.865
613
+ 2025-12-19,AXITA.NS,2.84,LOW,0.011,1.02,2.107
614
+ 2025-12-30,PVSL.NS,5.86,LOW,1.293,6.145,4.047
615
+ 2025-12-30,NAVKARURB.NS,4.51,LOW,4.336,14.27,22.879
616
+ 2025-12-30,TRANSRAILL.NS,3.34,LOW,1.519,4.288,-0.803
617
+ 2026-01-02,KAMOPAINTS.NS,3.62,LOW,0.518,18.358,14.921
618
+ 2026-01-12,KALAMANDIR.NS,5.5,LOW,0.382,-0.358,5.119
619
+ 2026-01-12,CHEMPLASTS.NS,3.64,LOW,15.548,14.194,10.529
620
+ 2026-01-15,SMLT.NS,10.22,LOW,0.036,-7.892,-7.581
621
+ 2026-01-15,KOTYARK.NS,10.15,LOW,-2.425,-6.489,-6.232
622
+ 2026-01-15,VIRINCHI.NS,9.91,LOW,-2.57,-8.925,-7.926
623
+ 2026-01-15,MASTERTR.NS,9.5,LOW,0.406,-5.79,-2.72
624
+ 2026-01-15,ASIANENE.NS,8.93,LOW,-1.447,-3.139,-2.089
625
+ 2026-01-20,KRISHIVAL.NS,7.09,LOW,0.713,1.695,-0.585
626
+ 2026-01-20,OLAELEC.NS,7.08,LOW,0.714,-2.173,-1.774
627
+ 2026-01-20,XTGLOBAL.NS,6.86,LOW,-4.914,-6.277,-4.704
628
+ 2026-01-20,YATRA.NS,6.33,LOW,3.818,-1.533,3.343
629
+ 2026-01-20,BALUFORGE.NS,5.57,LOW,-3.657,-9.012,-14.548
630
+ 2026-01-29,SUVIDHAA.NS,7.51,LOW,9.911,3.925,6.39
631
+ 2026-01-29,UNITECH.NS,6.72,LOW,-2.8,-2.8,14.018
632
+ 2026-01-29,IZMO.NS,6.42,LOW,-0.007,14.502,18.552
633
+ 2026-01-29,LAXMIDENTL.NS,5.89,LOW,2.25,13.786,12.326
634
+ 2026-01-29,EPACK.NS,5.83,LOW,2.075,5.676,10.761
635
+ 2026-02-03,SOLEX.NS,5.57,LOW,1.55,0.464,11.121
636
+ 2026-02-11,LORDSCHLO.NS,4.4,LOW,0.301,-3.668,-7.222
637
+ 2026-02-16,IXIGO.NS,6.15,LOW,0.382,4.908,-4.848
638
+ 2026-02-19,DELPHIFX.NS,7.32,LOW,1.045,2.706,5.238
639
+ 2026-02-19,RMDRIP.NS,3.24,LOW,0.386,2.143,-34.732
640
+ 2026-02-27,TOUCHWOOD.NS,8.4,LOW,-3.805,-2.168,-4.928
641
+ 2026-02-27,BYKE.NS,6.57,LOW,-4.354,-2.303,-5.738
642
+ 2026-03-05,EMUDHRA.NS,5.48,LOW,1.672,2.535,6.394
643
+ 2026-03-05,PROTEAN.NS,5.0,LOW,-1.225,-2.691,0.708
644
+ 2026-03-10,SRD.NS,6.63,LOW,-2.679,-0.553,-5.857
645
+ 2026-03-10,CURAA.NS,6.55,MEDIUM,4.696,15.456,27.309
646
+ 2026-03-10,INDOFARM.NS,6.18,LOW,-1.561,-2.094,-1.71
647
+ 2026-03-10,EPIGRAL.NS,5.98,LOW,-1.696,-2.191,-4.234
648
+ 2026-03-10,NOVAAGRI.NS,5.72,LOW,-2.561,-4.54,-5.918
649
+ 2026-03-13,INFOBEAN.NS,11.71,LOW,8.363,14.151,12.823
650
+ 2026-03-13,SUNDRMBRAK.NS,11.25,LOW,0.637,0.04,-2.901
651
+ 2026-03-13,RICOAUTO.NS,11.23,LOW,-0.224,6.748,2.983
652
+ 2026-03-13,CAPACITE.NS,11.21,MEDIUM,-4.767,16.445,18.337
653
+ 2026-03-13,LUMAXIND.NS,11.14,MEDIUM,1.539,9.288,4.728
654
+ 2026-03-18,HYBRIDFIN.NS,9.15,LOW,-0.791,-0.791,0.744
655
+ 2026-03-18,FINOPB.NS,7.6,LOW,-2.887,-8.226,-19.276
656
+ 2026-03-18,SOLEX.NS,6.3,LOW,-2.725,2.204,3.624
657
+ 2026-03-18,CHEMPLASTS.NS,5.93,LOW,-0.423,-6.43,-2.601
658
+ 2026-03-18,GUJRAFFIA.NS,5.87,LOW,-3.466,-2.402,-2.109
659
+ 2026-03-23,XELPMOC.NS,12.63,LOW,7.859,-0.413,-0.109
660
+ 2026-03-23,RAMANEWS.NS,12.53,MEDIUM,3.632,1.199,0.433
661
+ 2026-03-23,CAMLINFINE.NS,12.23,MEDIUM,2.537,-4.844,-8.548
662
+ 2026-03-23,BTML.NS,12.07,MEDIUM,-0.13,2.085,6.174
663
+ 2026-03-23,BALAJITELE.NS,11.96,MEDIUM,2.191,-1.161,3.626
664
+ 2026-03-27,KRISHNADEF.NS,11.18,LOW,-3.416,5.558,5.45
665
+ 2026-03-27,TRU.NS,10.9,LOW,-8.069,1.294,11.453
666
+ 2026-03-27,SRGHFL.NS,10.83,LOW,3.978,4.709,12.512
667
+ 2026-03-27,KROSS.NS,10.57,MEDIUM,-3.362,-2.376,0.937
668
+ 2026-03-27,YATRA.NS,10.38,LOW,-6.768,2.349,0.952
669
+ 2026-04-02,PUNJABCHEM.NS,11.44,LOW,-0.481,5.621,9.98
670
+ 2026-04-02,SRD.NS,11.24,LOW,0.845,-1.243,-0.21
671
+ 2026-04-02,SPECTRUM.NS,11.18,LOW,-2.838,-8.184,-12.981
672
+ 2026-04-02,LAL.NS,10.79,LOW,1.367,3.172,8.45
673
+ 2026-04-02,IXIGO.NS,10.75,LOW,-1.999,6.92,1.747
674
+ 2026-04-08,SEPC.NS,10.21,MEDIUM,7.25,0.697,8.674
675
+ 2026-04-08,JHS.NS,8.97,LOW,1.215,3.197,9.024
676
+ 2026-04-08,NACLIND.NS,8.61,LOW,-0.048,-2.879,5.693
677
+ 2026-04-08,SUVIDHAA.NS,8.54,LOW,7.947,8.978,4.855
678
+ 2026-04-08,GREENPLY.NS,8.43,LOW,0.638,1.438,9.492
679
+ 2026-04-13,SURAJLTD.NS,10.31,LOW,3.204,-1.359,-5.529
680
+ 2026-04-13,CHEMPLASTS.NS,10.19,LOW,1.266,1.223,5.984
681
+ 2026-04-13,GSLSU.NS,10.16,LOW,4.402,3.297,2.851
682
+ 2026-04-13,GUJRAFFIA.NS,9.76,LOW,-0.602,-1.004,1.635
683
+ 2026-04-13,DDEVPLSTIK.NS,9.72,LOW,4.161,6.694,4.253
684
+ 2026-04-17,DJML.NS,8.71,LOW,-1.65,0.704,-1.185
685
+ 2026-04-17,SADBHAV.NS,6.88,LOW,1.616,3.148,-0.875
686
+ 2026-04-17,DBSTOCKBRO.NS,6.47,LOW,2.531,1.943,0.104
687
+ 2026-04-17,ABMINTLLTD.NS,5.6,LOW,4.59,-5.611,-13.727
688
+ 2026-04-17,21STCENMGM.NS,4.76,LOW,1.675,5.778,10.036
689
+ 2026-04-22,TECILCHEM.NS,8.39,LOW,-0.158,-7.686,-9.249
690
+ 2026-04-22,SRGHFL.NS,6.53,LOW,-4.462,-5.12,-6.646
691
+ 2026-04-22,MAPMYINDIA.NS,6.16,LOW,-1.999,-3.025,-4.035
692
+ 2026-04-22,LORDSCHLO.NS,6.06,LOW,0.821,-0.557,-1.569
693
+ 2026-04-22,DIAMINESQ.NS,5.56,LOW,-1.262,-0.067,-3.687
694
+ 2026-04-27,EPIGRAL.NS,6.85,LOW,-1.351,-1.619,7.558
695
+ 2026-04-27,SHIVAUM.NS,6.38,LOW,-5.3,-2.234,-2.731
696
+ 2026-04-27,GATECHDVR.NS,5.86,LOW,-0.3,-2.428,1.828
697
+ 2026-04-27,SRD.NS,5.81,LOW,-1.48,-0.767,-0.723
698
+ 2026-04-27,DENTA.NS,5.72,LOW,0.109,-0.617,1.123
699
+ 2026-04-30,GVPIL.NS,7.5,LOW,-0.3,3.655,10.174
700
+ 2026-04-30,AQYLON.NS,7.39,LOW,1.69,4.178,2.188
701
+ 2026-04-30,KRISHANA.NS,5.07,LOW,-0.3,5.332,13.098
702
+ 2026-04-30,JINDALSAW.NS,4.82,LOW,-0.3,4.293,8.548
703
+ 2026-04-30,IFGLEXPOR.NS,4.39,LOW,-0.3,1.286,-1.143
704
+ 2026-05-05,FINOPB.NS,5.03,LOW,1.157,1.265,-3.744
705
+ 2026-05-13,SYRMA.NS,5.25,LOW,-0.971,-6.644,-3.356
706
+ 2026-05-13,AZAD.NS,4.56,LOW,2.75,-9.625,-5.508
707
+ 2026-05-18,PRAENG.NS,8.28,LOW,4.622,6.815,1.552
708
+ 2026-05-18,THEINVEST.NS,6.85,LOW,1.546,0.103,-0.129
709
+ 2026-05-18,PANSARI.NS,6.71,LOW,-0.512,0.878,10.086
710
+ 2026-05-18,AEROENTER.NS,6.21,LOW,-0.885,-2.989,-2.999
711
+ 2026-05-18,MANUGRAPH.NS,6.21,LOW,3.954,25.763,20.968
712
+ 2026-05-21,EXICOM.NS,10.41,LOW,14.151,6.98,6.444
713
+ 2026-05-21,BCG.NS,8.58,LOW,0.366,-1.822,-2.393
714
+ 2026-06-08,SUMEETINDS.NS,6.82,LOW,-3.17,0.988,8.409
715
+ 2026-06-11,HUBTOWN.NS,7.02,LOW,2.737,11.864,9.415
716
+ 2026-06-11,TDPOWERSYS.NS,5.16,LOW,5.633,3.563,12.4
717
+ 2026-06-16,GICL.NS,6.11,LOW,-2.765,-4.252,-7.142
718
+ 2026-06-16,SHIVAUM.NS,4.65,LOW,0.768,1.042,-3.066
719
+ 2026-06-19,VIPULLTD.NS,8.2,LOW,0.598,10.741,16.217
720
+ 2026-06-19,ZEELEARN.NS,6.15,MEDIUM,4.689,5.936,0.947
721
+ 2026-06-19,GRMOVER.NS,6.0,LOW,-0.429,-2.733,-1.447
722
+ 2026-06-19,AVANTIFEED.NS,5.24,LOW,-1.468,0.795,-0.868
research/model_family_compare.py ADDED
@@ -0,0 +1,162 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env python3
2
+ """research/model_family_compare.py — settle "would Random Forest / KNN / a merged-strategy
3
+ model beat the current gradient-boosted trees?" with an EMPIRICAL out-of-sample A/B.
4
+
5
+ Trains several model families on the SAME features + SAME time-split as the production ML
6
+ model and compares them on the metric that actually matters for swing trades: OUT-OF-SAMPLE
7
+ DIRECTION ACCURACY (predicted dir vs realised excess-of-Nifty dir_1D / dir_3D), plus the
8
+ tradeable BULLISH-precision (of the stocks it calls BULLISH, how many actually were).
9
+
10
+ Model families compared:
11
+ • GBT — HistGradientBoostingClassifier (what production uses)
12
+ • RandForest — RandomForestClassifier
13
+ • KNN — KNeighborsClassifier (standardised features)
14
+ • LogReg — LogisticRegression (linear baseline, standardised)
15
+ • +Strat — GBT with the S1..S20 strategy trigger flags ADDED (tests "merge strategies")
16
+
17
+ Also runs a META-LABELING probe: train a 2nd model to predict whether the GBT's own call is
18
+ correct, then check if gating to its high-confidence subset raises DirAcc (López de Prado's
19
+ meta-labeling — the principled way to "self-learn which contexts are reliable").
20
+
21
+ Research only; reads training_data_extra.csv; never touches the production model.
22
+
23
+ Usage:
24
+ python research/model_family_compare.py # dir_3D, 6-month OOS
25
+ python research/model_family_compare.py --tf 1D --holdout-months 6
26
+ """
27
+ from __future__ import annotations
28
+
29
+ import argparse
30
+ import os
31
+ import sys
32
+ import warnings
33
+
34
+ import numpy as np
35
+ import pandas as pd
36
+
37
+ warnings.filterwarnings("ignore")
38
+
39
+ _PROJ_ROOT = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
40
+ if _PROJ_ROOT not in sys.path:
41
+ sys.path.insert(0, _PROJ_ROOT)
42
+
43
+ from ml_predictor.features import FEATURE_COLUMNS # noqa: E402
44
+
45
+ _CSV = os.path.join(_PROJ_ROOT, "ml_predictor", "training_data_extra.csv")
46
+ _DIRC = {"1D": "dir_1D", "3D": "dir_3D"}
47
+ # Strategy trigger flags already present in the feature CSV (the "merge strategies" inputs).
48
+ _TRIG_COLS = [f"trigger_T{n}" for n in range(1, 8)]
49
+
50
+
51
+ def _metrics(y_true, y_pred, label=""):
52
+ from sklearn.metrics import accuracy_score, f1_score
53
+ acc = accuracy_score(y_true, y_pred)
54
+ f1 = f1_score(y_true, y_pred, average="macro")
55
+ # BULLISH precision — of everything called BULLISH, how many really were (the tradeable edge)
56
+ bull_mask = y_pred == "BULLISH"
57
+ bull_prec = float((y_true[bull_mask] == "BULLISH").mean()) if bull_mask.sum() else float("nan")
58
+ bull_n = int(bull_mask.sum())
59
+ return {"model": label, "acc": acc, "macro_f1": f1, "bull_prec": bull_prec, "bull_n": bull_n}
60
+
61
+
62
+ def run(tf: str, holdout_months: int):
63
+ from sklearn.ensemble import HistGradientBoostingClassifier, RandomForestClassifier
64
+ from sklearn.neighbors import KNeighborsClassifier
65
+ from sklearn.linear_model import LogisticRegression
66
+ from sklearn.preprocessing import StandardScaler
67
+ from sklearn.pipeline import make_pipeline
68
+
69
+ df = pd.read_csv(_CSV)
70
+ df["date"] = pd.to_datetime(df["date"])
71
+ target = _DIRC[tf]
72
+ feats = [c for c in FEATURE_COLUMNS if c in df.columns]
73
+ df = df.dropna(subset=feats + [target])
74
+ cutoff = df["date"].max() - pd.DateOffset(months=holdout_months)
75
+ tr = df[df["date"] <= cutoff]
76
+ te = df[df["date"] > cutoff]
77
+ print(f" TF={tf} · target={target} · features={len(feats)}")
78
+ print(f" Train ≤ {cutoff.date()}: {len(tr):,} rows · OOS > {cutoff.date()}: {len(te):,} rows")
79
+ print(f" OOS class balance: " + ", ".join(f"{k} {v:.0%}" for k, v in te[target].value_counts(normalize=True).items()))
80
+ if len(te) < 200:
81
+ raise SystemExit("OOS too small — lower --holdout-months or rebuild the CSV.")
82
+
83
+ Xtr, ytr = tr[feats].to_numpy(float), tr[target].to_numpy()
84
+ Xte, yte = te[feats].to_numpy(float), te[target].to_numpy()
85
+
86
+ results = []
87
+ # 1) GBT — production family
88
+ gbt = HistGradientBoostingClassifier(max_iter=300, learning_rate=0.06, class_weight="balanced", random_state=0)
89
+ gbt.fit(Xtr, ytr)
90
+ results.append(_metrics(yte, gbt.predict(Xte), "GBT (production family)"))
91
+ # 2) Random Forest
92
+ rf = RandomForestClassifier(n_estimators=400, max_depth=12, class_weight="balanced", n_jobs=-1, random_state=0)
93
+ rf.fit(Xtr, ytr)
94
+ results.append(_metrics(yte, rf.predict(Xte), "RandomForest"))
95
+ # 3) KNN (standardised)
96
+ knn = make_pipeline(StandardScaler(), KNeighborsClassifier(n_neighbors=45, weights="distance", n_jobs=-1))
97
+ knn.fit(Xtr, ytr)
98
+ results.append(_metrics(yte, knn.predict(Xte), "KNN (k=45, scaled)"))
99
+ # 4) Logistic Regression (linear baseline)
100
+ lr = make_pipeline(StandardScaler(), LogisticRegression(max_iter=2000, class_weight="balanced"))
101
+ lr.fit(Xtr, ytr)
102
+ results.append(_metrics(yte, lr.predict(Xte), "LogReg (linear)"))
103
+ # 5) GBT + explicit strategy trigger flags ("merge strategies")
104
+ trig = [c for c in _TRIG_COLS if c in df.columns]
105
+ if trig:
106
+ feats2 = feats + trig
107
+ gbt2 = HistGradientBoostingClassifier(max_iter=300, learning_rate=0.06, class_weight="balanced", random_state=0)
108
+ gbt2.fit(tr[feats2].to_numpy(float), ytr)
109
+ results.append(_metrics(yte, gbt2.predict(te[feats2].to_numpy(float)), f"GBT + {len(trig)} strat flags"))
110
+
111
+ # ── Majority-class baseline (what you beat by doing nothing) ──
112
+ maj = pd.Series(ytr).mode()[0]
113
+ results.append(_metrics(yte, np.array([maj] * len(yte)), f"Baseline (always {maj})"))
114
+
115
+ print("\n" + "=" * 78)
116
+ print(f" MODEL-FAMILY A/B — out-of-sample direction accuracy ({target})")
117
+ print("=" * 78)
118
+ print(f" {'Model':<28}{'DirAcc':>8}{'MacroF1':>9}{'BULLprec':>10}{'BULL_n':>8}")
119
+ print(" " + "-" * 66)
120
+ for r in results:
121
+ bp = f"{r['bull_prec']:.0%}" if r["bull_prec"] == r["bull_prec"] else "—"
122
+ print(f" {r['model']:<28}{r['acc']:>7.1%}{r['macro_f1']:>9.2f}{bp:>10}{r['bull_n']:>8}")
123
+
124
+ # ── META-LABELING probe: can a 2nd model predict when GBT is right? ──
125
+ print("\n" + "=" * 78)
126
+ print(" META-LABELING PROBE — gate to contexts where GBT is predicted reliable")
127
+ print("=" * 78)
128
+ # In-sample cross-fitted 'GBT correct?' labels to avoid leakage: refit GBT on a sub-split.
129
+ from sklearn.model_selection import cross_val_predict
130
+ base = HistGradientBoostingClassifier(max_iter=300, learning_rate=0.06, class_weight="balanced", random_state=0)
131
+ tr_pred = cross_val_predict(base, Xtr, ytr, cv=3, method="predict")
132
+ correct = (tr_pred == ytr).astype(int)
133
+ meta = HistGradientBoostingClassifier(max_iter=300, learning_rate=0.06, random_state=0)
134
+ meta.fit(Xtr, correct)
135
+ # Base GBT already fit above (gbt). Its OOS calls + meta's P(correct):
136
+ p_correct = meta.predict_proba(Xte)[:, 1]
137
+ base_pred = gbt.predict(Xte)
138
+ base_acc = (base_pred == yte).mean()
139
+ for thr in (0.5, 0.6, 0.7):
140
+ keep = p_correct >= thr
141
+ if keep.sum() < 20:
142
+ print(f" P(correct)≥{thr:.1f}: too few kept ({int(keep.sum())})")
143
+ continue
144
+ gated_acc = (base_pred[keep] == yte[keep]).mean()
145
+ bull = keep & (base_pred == "BULLISH")
146
+ bull_prec = (yte[bull] == "BULLISH").mean() if bull.sum() else float("nan")
147
+ print(f" P(correct)≥{thr:.1f}: kept {keep.mean():>4.0%} of rows · DirAcc {gated_acc:.1%} "
148
+ f"(vs {base_acc:.1%} ungated) · BULLprec {bull_prec:.0%} (n={int(bull.sum())})")
149
+ print("\n Read: if gating to high P(correct) raises DirAcc above ungated, meta-labeling is the")
150
+ print(" real lever — a 2nd model that learns WHICH setups to trust (self-learns from outcomes).")
151
+
152
+
153
+ def main():
154
+ ap = argparse.ArgumentParser()
155
+ ap.add_argument("--tf", default="3D", choices=["1D", "3D"])
156
+ ap.add_argument("--holdout-months", type=int, default=6)
157
+ args = ap.parse_args()
158
+ run(args.tf, args.holdout_months)
159
+
160
+
161
+ if __name__ == "__main__":
162
+ main()
research/strategy_combo_swing.csv ADDED
The diff for this file is too large to render. See raw diff
 
research/strategy_combo_swing.py ADDED
@@ -0,0 +1,263 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env python3
2
+ """research/strategy_combo_swing.py — does STRATEGY CONFLUENCE (2-3 signals firing
3
+ together) improve SWING-trade (1D / 3D) hit rate?
4
+
5
+ For a broad sample of already-cached NSE tickers this walks each trading day in a lookback
6
+ window (point-in-time, no lookahead), records:
7
+ • which strategy signals (S1..S20, S_CTRIO, MFS, …) were active that day (5-bar window,
8
+ exactly like predictor_core.run_strategy_signals)
9
+ • the ML model's 1D/3D call + its median target
10
+ • whether the median target was actually hit over the forward window
11
+
12
+ then answers three swing-trading questions:
13
+ 1. Does median-hit RISE with the NUMBER of strategies co-firing (0 / 1 / 2 / 3+)?
14
+ 2. Which specific 2-strategy PAIRS give the best median-hit?
15
+ 3. Which specific 3-strategy TRIPLES give the best median-hit?
16
+ Reported for ALL calls and for ML-BULLISH-only calls (the actual swing-long entries).
17
+
18
+ No network: uses the OHLCV cache (fetch_ohlcv hits the SQLite cache for warmed tickers).
19
+
20
+ Usage:
21
+ python research/strategy_combo_swing.py # 200 cached tickers, 90-day window
22
+ python research/strategy_combo_swing.py --tickers 350 --days 120
23
+ python research/strategy_combo_swing.py --tfs 3D --min-n 40 --bullish-only
24
+ """
25
+ from __future__ import annotations
26
+
27
+ import argparse
28
+ import itertools
29
+ import os
30
+ import sys
31
+ from collections import defaultdict
32
+
33
+ import numpy as np
34
+ import pandas as pd
35
+
36
+ _PROJ_ROOT = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
37
+ if _PROJ_ROOT not in sys.path:
38
+ sys.path.insert(0, _PROJ_ROOT)
39
+
40
+ from ml_predictor.features import FEATURE_COLUMNS, compute_features # noqa: E402
41
+ from ml_predictor.infer import MLPredictor # noqa: E402
42
+ from research.ml_backtest import _graded_hit # noqa: E402
43
+ from data_sources import cached_tickers, fetch_ohlcv # noqa: E402
44
+ import trial_run as T # noqa: E402
45
+
46
+ _HORIZON = {"1D": 1, "3D": 3}
47
+ _LOOKBACK = 5 # a signal is "active" for 5 bars, matching run_strategy_signals
48
+ _DIRC = {"1D": "dir_1D", "3D": "dir_3D"} # realized excess-of-Nifty direction label
49
+
50
+
51
+ # gen(name → callable(sc,sh,sl,sv,nifty,vix)) mirroring predictor_core.run_strategy_signals
52
+ def _gen_map():
53
+ n = lambda f: (lambda sc, sh, sl, sv, ni, vx: f(sc, sh, sl, sv, ni))
54
+ b = lambda f: (lambda sc, sh, sl, sv, ni, vx: f(sc, sh, sl, sv))
55
+ v = lambda f: (lambda sc, sh, sl, sv, ni, vx: f(sc, sh, sl, sv, vx))
56
+ nv = lambda f: (lambda sc, sh, sl, sv, ni, vx: f(sc, sh, sl, sv, ni, vx))
57
+ return {
58
+ "S1": n(T.gen_s1), "S2": n(T.gen_s2), "S3": b(T.gen_s3), "MFS": n(T.gen_mfs),
59
+ "NIRA": n(T.gen_nira), "PED": b(T.gen_ped),
60
+ "SUPER": (lambda sc, sh, sl, sv, ni, vx: T.gen_supertrend(sc, sh, sl)),
61
+ "S4": v(T.gen_s4), "S5": v(T.gen_s5), "S6": nv(T.gen_s6),
62
+ "S4V2": nv(T.gen_s4v2), "S5V2": nv(T.gen_s5v2), "S6V2": nv(T.gen_s6v2),
63
+ "S7": nv(T.gen_s7), "S8": nv(T.gen_s8), "S9": nv(T.gen_s9), "S10": nv(T.gen_s10),
64
+ "S11": nv(T.gen_s11), "S_CAPFLOW": nv(T.gen_s_capflow),
65
+ "S_CTRIO": nv(T.gen_s_confluence_trio), "S_SEASONAL": nv(T.gen_s_seasonal),
66
+ "S12": nv(T.gen_s12), "S13": nv(T.gen_s13), "S14": nv(T.gen_s14), "S15": nv(T.gen_s15),
67
+ "S16": nv(T.gen_s16), "S17": nv(T.gen_s17), "S18": nv(T.gen_s18), "S19": nv(T.gen_s19),
68
+ "S20": nv(T.gen_s20),
69
+ }
70
+
71
+
72
+ def _active_by_pos(tk, sc, sh, sl, sv, nifty_c, vix_c, n_bars: int, gens) -> list[set]:
73
+ """active_by_pos[p] = set of strategies active at bar position p (fired within last 5 bars)."""
74
+ active = [set() for _ in range(n_bars)]
75
+ pos_of = {d: i for i, d in enumerate(sc.index)}
76
+ for name, fn in gens.items():
77
+ try:
78
+ sigs = fn(sc, sh, sl, sv, nifty_c, vix_c)
79
+ except Exception:
80
+ continue
81
+ for d, t in sigs:
82
+ p = pos_of.get(d)
83
+ if p is None:
84
+ continue
85
+ for q in range(p, min(p + _LOOKBACK, n_bars)):
86
+ active[q].add(name)
87
+ return active
88
+
89
+
90
+ def _indices():
91
+ try:
92
+ import yfinance as yf
93
+ raw = yf.download(["^NSEI", "^INDIAVIX"], period="2y", auto_adjust=True, progress=False)
94
+ return raw["Close"]["^NSEI"].dropna(), raw["Close"]["^INDIAVIX"].dropna()
95
+ except Exception:
96
+ return None, None
97
+
98
+
99
+ _CSV = os.path.join(_PROJ_ROOT, "ml_predictor", "training_data_extra.csv")
100
+ _UP = {"1D": "up_1D", "3D": "up_3D"}
101
+ _DN = {"1D": "dn_1D", "3D": "dn_3D"}
102
+
103
+
104
+ def run(n_tickers: int, days: int, tfs, min_n: int, bullish_only: bool, seed: int):
105
+ predictor = MLPredictor()
106
+ if not predictor.available:
107
+ raise SystemExit("ml_predictor model not loaded — run `python ml_predictor/train.py` first.")
108
+ # FAST PATH: features + realized excursions come precomputed from training_data_extra.csv
109
+ # (no per-day recompute, no forward fetch); only the strategy active-sets need OHLCV frames.
110
+ print(f" Loading precomputed features {_CSV} …")
111
+ data = pd.read_csv(_CSV)
112
+ data["date"] = pd.to_datetime(data["date"])
113
+ csv_tickers = set(data["ticker"].unique())
114
+ cached = cached_tickers("2y")
115
+ pool = sorted(csv_tickers & cached) # need OHLCV (strategy signals) AND feature rows
116
+ if not pool:
117
+ raise SystemExit("no overlap between cached OHLCV and feature CSV.")
118
+ import random
119
+ random.Random(seed).shuffle(pool)
120
+ pool = sorted(pool[:n_tickers])
121
+ data = data[data["ticker"].isin(pool)].copy()
122
+ nifty_c, vix_c = _indices()
123
+ gens = _gen_map()
124
+
125
+ # ── Pass 1: collect every sample's feature row + metadata (strategy set, realized excursions).
126
+ # Model inference is BATCHED afterwards (one _raw_predict call per TF over the whole matrix)
127
+ # instead of one 7-estimator call per row — the row-by-row path is ~1000× slower.
128
+ print(f"\n Confluence swing study — {len(pool)} tickers · last {days} rows/ticker · "
129
+ f"TFs {','.join(tfs)} · model cutoff {predictor.manifest.get('train_cutoff')}")
130
+ feat_rows = [] # list[np.ndarray] (one per sample)
131
+ meta = [] # list[dict] strats + realized up/dn per tf
132
+ for ti, tk in enumerate(pool, 1):
133
+ if ti % 25 == 0 or ti == len(pool):
134
+ print(f" … {ti}/{len(pool)} tickers ({len(feat_rows)} rows collected)")
135
+ sub = data[data["ticker"] == tk].sort_values("date")
136
+ if days and days > 0:
137
+ sub = sub.tail(days)
138
+ if sub.empty:
139
+ continue
140
+ try:
141
+ sc, sh, sl, sv = fetch_ohlcv(tk, "2y")
142
+ except Exception:
143
+ continue
144
+ active = _active_by_pos(tk, sc, sh, sl, sv, nifty_c, vix_c, len(sc), gens)
145
+ pos_of = {d: i for i, d in enumerate(sc.index)}
146
+ feat_mat = sub[FEATURE_COLUMNS].values
147
+ dates = sub["date"].values
148
+ up_vals = {tf: sub[_UP[tf]].values for tf in tfs}
149
+ dn_vals = {tf: sub[_DN[tf]].values for tf in tfs}
150
+ dirc_vals = {tf: sub[_DIRC[tf]].values for tf in tfs}
151
+ for ri in range(len(sub)):
152
+ full_p = pos_of.get(pd.Timestamp(dates[ri]))
153
+ strat_set = active[full_p] if full_p is not None else set()
154
+ feat_rows.append(feat_mat[ri])
155
+ meta.append({"strats": frozenset(strat_set),
156
+ "up": {tf: float(up_vals[tf][ri]) for tf in tfs},
157
+ "dn": {tf: float(dn_vals[tf][ri]) for tf in tfs},
158
+ "true_dir": {tf: str(dirc_vals[tf][ri]) for tf in tfs}})
159
+ if not feat_rows:
160
+ raise SystemExit("no samples collected.")
161
+
162
+ # ── Pass 2: BATCH model inference per TF, then cheap pure-Python derivation + grading.
163
+ X = np.asarray(feat_rows, dtype=float)
164
+ print(f" running batched inference on {len(X):,} rows × {len(tfs)} TFs …")
165
+ rows = [] # {tf, strats:frozenset, dir, median}
166
+ for tf in tfs:
167
+ median_w = float(predictor.manifest.get("tf", {}).get(tf, {}).get("median_train_width", 1.5)) or 1.5
168
+ q, proba_m, classes = predictor._raw_predict(tf, X) # ONE batch call per TF
169
+ for i, mrow in enumerate(meta):
170
+ row_q = {k: float(v[i]) for k, v in q.items()}
171
+ pr = predictor._derive(row_q, proba_m[i], classes, tf, 100.0, 1.5,
172
+ median_w, None, None, 0, 100.0)
173
+ if bullish_only and pr["direction"] != "BULLISH":
174
+ continue
175
+ g = _graded_hit(pr["direction"], 100.0, pr["target_price_lo"],
176
+ pr["target_price_hi"], mrow["up"][tf], mrow["dn"][tf])
177
+ rows.append({"tf": tf, "strats": mrow["strats"],
178
+ "dir": pr["direction"],
179
+ "dir_correct": 1 if pr["direction"] == mrow["true_dir"][tf] else 0,
180
+ "median": 1 if g == "MIDPOINT_HIT" else 0})
181
+ df = pd.DataFrame(rows)
182
+ if df.empty:
183
+ raise SystemExit("no samples collected.")
184
+ _report(df, tfs, min_n, bullish_only)
185
+ out = os.path.join(os.path.dirname(os.path.abspath(__file__)), "strategy_combo_swing.csv")
186
+ df.assign(strats=df["strats"].apply(lambda s: "|".join(sorted(s)))).to_csv(out, index=False)
187
+ print(f"\n ✓ samples written → {out}")
188
+ return df
189
+
190
+
191
+ def _report(df: pd.DataFrame, tfs, min_n: int, bullish_only: bool):
192
+ scope = "ML-BULLISH calls only" if bullish_only else "ALL ML calls"
193
+ print("\n" + "═" * 96)
194
+ print(f" STRATEGY CONFLUENCE FOR SWING TRADES — does co-firing lift the swing metrics? ({scope})")
195
+ print(" DIR-ACC = predicted direction matched the realised move (the metric that's actually ~40-50%")
196
+ print(" and worth improving). MID-HIT = band-midpoint reached (already ~90%, little headroom).")
197
+ print("═" * 96)
198
+
199
+ def bucket(n):
200
+ return "0" if n == 0 else ("1" if n == 1 else ("2" if n == 2 else "3+"))
201
+
202
+ for tf in tfs:
203
+ t = df[df["tf"] == tf]
204
+ if t.empty:
205
+ continue
206
+ base_dir = t["dir_correct"].mean()
207
+ base_med = t["median"].mean()
208
+ print(f"\n ── {tf} ── baseline DIR-ACC {base_dir:.0%} | MID-HIT {base_med:.0%} (N={len(t):,})")
209
+
210
+ t = t.assign(k=t["strats"].apply(lambda s: bucket(len(s))))
211
+ print(" confluence count → DIR-ACC (MID-HIT):")
212
+ for kb in ["0", "1", "2", "3+"]:
213
+ g = t[t["k"] == kb]
214
+ if len(g):
215
+ da, mh = g["dir_correct"].mean(), g["median"].mean()
216
+ print(f" {kb:<3} signals N={len(g):>6,} DIR-ACC {da:>4.0%}"
217
+ f" lift {da - base_dir:>+4.0%} (MID-HIT {mh:>4.0%})")
218
+
219
+ # best PAIRS / TRIPLES ranked by directional accuracy (the improvable metric)
220
+ _combo_table(t, 2, min_n, base_dir, "PAIRS")
221
+ _combo_table(t, 3, min_n, base_dir, "TRIPLES")
222
+
223
+ print("\n Read: if DIR-ACC climbs from '1 signal' → '2' → '3+', confluence sharpens the swing")
224
+ print(" DIRECTION call (the real edge). The best PAIRS/TRIPLES with enough N are the combos to trade.")
225
+
226
+
227
+ def _combo_table(t: pd.DataFrame, k: int, min_n: int, base: float, label: str):
228
+ counts = defaultdict(lambda: [0, 0]) # combo → [dir-correct hits, n]
229
+ for strats, dc in zip(t["strats"], t["dir_correct"]):
230
+ if len(strats) < k:
231
+ continue
232
+ for combo in itertools.combinations(sorted(strats), k):
233
+ c = counts[combo]
234
+ c[0] += dc
235
+ c[1] += 1
236
+ scored = [(combo, hn[1], hn[0] / hn[1]) for combo, hn in counts.items() if hn[1] >= min_n]
237
+ scored.sort(key=lambda r: r[2], reverse=True)
238
+ print(f" best {label} by DIR-ACC (min N={min_n}):")
239
+ if not scored:
240
+ print(f" (no {k}-combo reached N={min_n} in this sample — widen --tickers/--days or lower --min-n)")
241
+ return
242
+ for combo, n, mh in scored[:8]:
243
+ print(f" {'+'.join(combo):<26} N={n:>5} DIR-ACC {mh:>4.0%} lift {mh - base:>+4.0%}"
244
+ f"{' ⟵' if mh - base > 0.08 else ''}")
245
+
246
+
247
+ def main():
248
+ ap = argparse.ArgumentParser()
249
+ ap.add_argument("--tickers", type=int, default=200, help="how many cached tickers to sample")
250
+ ap.add_argument("--days", type=int, default=90, help="lookback trading days per ticker")
251
+ ap.add_argument("--tfs", default="1D,3D", help="swing timeframes (subset of 1D,3D)")
252
+ ap.add_argument("--min-n", type=int, default=30, help="min samples for a combo to be reported")
253
+ ap.add_argument("--bullish-only", action="store_true", help="restrict to ML-BULLISH (swing-long) calls")
254
+ ap.add_argument("--seed", type=int, default=7)
255
+ args = ap.parse_args()
256
+ tfs = [x.strip().upper() for x in args.tfs.split(",") if x.strip().upper() in _HORIZON]
257
+ if not tfs:
258
+ raise SystemExit("no valid --tfs (choose from 1D,3D)")
259
+ run(args.tickers, args.days, tfs, args.min_n, args.bullish_only, args.seed)
260
+
261
+
262
+ if __name__ == "__main__":
263
+ main()
research/strategy_validation_funnel.csv ADDED
@@ -0,0 +1,31 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ strategy,category,n_is,n_oos,is_sharpe,oos_sharpe,oos_winrate,oos_mean_ret,max_dd,survived,first_fail
2
+ MFS,Composite,862,3403,-1.06,-0.05,48.0,-0.03,-100.0,False,01 OOS>0.5
3
+ S5V2,MeanRev,847,393,-0.54,0.45,51.0,0.2,-67.5,False,01 OOS>0.5
4
+ S5,MeanRev,3302,2051,-1.08,0.04,49.0,0.02,-99.9,False,01 OOS>0.5
5
+ S1,MeanRev,1,1,0.0,0.0,0.0,-0.74,0.0,False,01 OOS>0.5
6
+ S_CAPFLOW,MeanRev,220,107,-0.37,-0.25,41.0,-0.19,-60.4,False,01 OOS>0.5
7
+ S4,MeanRev,365,144,-0.9,-1.16,47.0,-0.68,-84.1,False,01 OOS>0.5
8
+ S8,MeanRev,40,12,-1.0,-2.29,33.0,-1.31,-9.6,False,01 OOS>0.5
9
+ S10,MeanRev,244,91,-1.16,-2.55,41.0,-1.29,-78.1,False,01 OOS>0.5
10
+ S6,MeanRev,77,30,-1.3,-2.58,40.0,-1.37,-40.0,False,01 OOS>0.5
11
+ S7,MeanRev,21,8,-1.37,-2.84,38.0,-0.94,-5.5,False,01 OOS>0.5
12
+ S16,MeanRev,19,10,0.34,-3.5,40.0,-0.93,-9.8,False,01 OOS>0.5
13
+ S18,MeanRev,23,9,-4.66,-3.81,33.0,-0.83,-8.7,False,01 OOS>0.5
14
+ S4V2,MeanRev,124,28,-1.65,-4.19,46.0,-3.36,-61.1,False,01 OOS>0.5
15
+ S_CTRIO,MeanRev,12,4,-0.34,-9.29,0.0,-6.09,-9.6,False,01 OOS>0.5
16
+ S6V2,MeanRev,15,7,-1.01,-9.58,14.0,-3.95,-20.7,False,01 OOS>0.5
17
+ S11,MeanRev,10,3,-2.01,-9.87,0.0,-2.81,-2.6,False,01 OOS>0.5
18
+ S12,Seasonal,0,158,0.0,2.16,59.0,1.17,-44.3,False,02 DD>-35
19
+ S_SEASONAL,Seasonal,428,282,-1.94,0.95,45.0,0.58,-67.8,False,02 DD>-35
20
+ S13,Seasonal,1920,0,-0.83,0.0,0.0,0.0,0.0,False,01 OOS>0.5
21
+ S19,Trend,17,18,-1.45,2.7,61.0,1.54,-9.0,False,03 OOS<2.5
22
+ SUPER,Trend,1180,463,-0.68,0.1,47.0,0.06,-97.0,False,01 OOS>0.5
23
+ S2,Trend,120,234,0.7,-0.01,42.0,-0.01,-70.4,False,01 OOS>0.5
24
+ S3,Trend,4194,1039,0.04,-0.04,48.0,-0.03,-96.7,False,01 OOS>0.5
25
+ S9,Trend,120,68,-0.61,-0.14,47.0,-0.09,-54.3,False,01 OOS>0.5
26
+ NIRA,Trend,147,275,1.46,-0.31,41.0,-0.2,-80.0,False,01 OOS>0.5
27
+ PED,Trend,2476,1149,-0.84,-0.74,42.0,-0.48,-100.0,False,01 OOS>0.5
28
+ S20,Trend,366,497,-0.34,-1.26,39.0,-0.79,-99.5,False,01 OOS>0.5
29
+ S14,Trend,198,51,-1.66,-2.79,41.0,-1.39,-56.2,False,01 OOS>0.5
30
+ S17,Volatility,70,43,-0.43,0.55,40.0,0.32,-20.4,False,04 !overfit
31
+ S15,Volatility,222,75,-1.7,-0.99,43.0,-0.4,-36.3,False,01 OOS>0.5
research/strategy_validation_funnel.py ADDED
@@ -0,0 +1,260 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env python3
2
+ """research/strategy_validation_funnel.py — apply the "9,120-backtest" doc's 6-filter
3
+ validation funnel to THIS project's strategy signals, and answer the doc's headline claim:
4
+ is MEAN REVERSION really the only category that survives out-of-sample?
5
+
6
+ For each strategy signal (S1..S20, S_CTRIO, MFS, NIRA, PED, …) this builds a simple
7
+ long-on-signal trade series from the cached OHLCV (enter at signal close, exit `--hold`
8
+ trading days later, minus round-trip cost), splits it chronologically into in-sample (IS)
9
+ and out-of-sample (OOS), then runs the doc's six filters:
10
+
11
+ [01] OOS Sharpe > 0.5
12
+ [02] max drawdown better than -35%
13
+ [03] OOS Sharpe < 2.5 (not absurd / likely a bug)
14
+ [04] OOS Sharpe <= IS*1.3 + 0.5 (anti-overfit)
15
+ [05] >= --min-trades OOS trades
16
+ [06] IS Sharpe > 0
17
+
18
+ Then it aggregates SURVIVAL RATE and MEAN OOS SHARPE **by category** and prints it next to
19
+ the doc's own funnel so you can compare directly.
20
+
21
+ Offline only (cached tickers); never imported by the production prediction path.
22
+
23
+ Usage:
24
+ python research/strategy_validation_funnel.py # 300 tickers, 3-day hold
25
+ python research/strategy_validation_funnel.py --tickers 500 --hold 5 --min-trades 30
26
+ """
27
+ from __future__ import annotations
28
+
29
+ import argparse
30
+ import os
31
+ import sys
32
+ from collections import defaultdict
33
+
34
+ import numpy as np
35
+ import pandas as pd
36
+
37
+ _PROJ_ROOT = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
38
+ if _PROJ_ROOT not in sys.path:
39
+ sys.path.insert(0, _PROJ_ROOT)
40
+
41
+ from data_sources import cached_tickers, fetch_ohlcv # noqa: E402
42
+ from research.strategy_combo_swing import _gen_map, _indices # noqa: E402
43
+
44
+ ROUND_TRIP_COST_PCT = 0.30
45
+ TRADING_DAYS = 252
46
+
47
+ # ── Category map (mirrors the doc's taxonomy) — classified from each signal's logic ──
48
+ CATEGORY = {
49
+ # Mean reversion: oversold / dip / RSI-recovery / capitulation
50
+ "S1": "MeanRev", "S4": "MeanRev", "S4V2": "MeanRev", "S5": "MeanRev", "S5V2": "MeanRev",
51
+ "S6": "MeanRev", "S6V2": "MeanRev", "S7": "MeanRev", "S8": "MeanRev", "S10": "MeanRev",
52
+ "S11": "MeanRev", "S16": "MeanRev", "S18": "MeanRev", "S_CAPFLOW": "MeanRev", "S_CTRIO": "MeanRev",
53
+ # Trend / momentum: EMA-MACD-ADX, supertrend, breakout, gap-drift
54
+ "S2": "Trend", "S3": "Trend", "NIRA": "Trend", "SUPER": "Trend", "S9": "Trend",
55
+ "S14": "Trend", "S19": "Trend", "S20": "Trend", "PED": "Trend",
56
+ # Composite multi-factor
57
+ "MFS": "Composite",
58
+ # Volatility compression (squeeze / NR7)
59
+ "S15": "Volatility", "S17": "Volatility",
60
+ # Seasonal (no doc equivalent — reported separately)
61
+ "S_SEASONAL": "Seasonal", "S12": "Seasonal", "S13": "Seasonal",
62
+ }
63
+
64
+ # Doc's own funnel (for side-by-side comparison)
65
+ _DOC = {
66
+ "MeanRev": {"tested": 4080, "survived": 344, "rate": 8.4, "best": "Ultimate Osc 1.59"},
67
+ "Trend": {"tested": 3450, "survived": 108, "rate": 3.1, "best": "Turtle 1.18"},
68
+ "Volume": {"tested": 690, "survived": 42, "rate": 6.1, "best": "Money Flow Idx 1.02"},
69
+ "Composite": {"tested": 270, "survived": 11, "rate": 4.1, "best": "Triple Screen 0.97"},
70
+ "Volatility": {"tested": 360, "survived": 14, "rate": 3.9, "best": "Squeeze Break 0.81"},
71
+ "Pattern": {"tested": 240, "survived": 5, "rate": 2.1, "best": "Three Bar Rev 0.75"},
72
+ }
73
+
74
+
75
+ def _sharpe(rets: list[float], hold: int) -> float:
76
+ """Annualised Sharpe of a per-trade return series (each trade ≈ one `hold`-day sample)."""
77
+ a = np.asarray(rets, dtype=float)
78
+ if a.size < 2:
79
+ return 0.0
80
+ sd = a.std(ddof=1)
81
+ if sd <= 1e-9:
82
+ return 0.0
83
+ return float(a.mean() / sd * np.sqrt(TRADING_DAYS / max(hold, 1)))
84
+
85
+
86
+ def _max_drawdown(rets_in_order: list[float]) -> float:
87
+ """Max drawdown (%) of the equity curve from compounding trades in date order."""
88
+ if not rets_in_order:
89
+ return 0.0
90
+ eq = np.cumprod([1 + r / 100.0 for r in rets_in_order])
91
+ peak = np.maximum.accumulate(eq)
92
+ dd = (eq - peak) / peak
93
+ return float(dd.min() * 100.0)
94
+
95
+
96
+ def run(n_tickers: int, hold: int, oos_frac: float, min_trades: int, seed: int):
97
+ gens = _gen_map()
98
+ nifty_c, vix_c = _indices()
99
+ pool = sorted(cached_tickers("2y"))
100
+ if not pool:
101
+ raise SystemExit("no cached tickers — warm the OHLCV cache first.")
102
+ import random
103
+ random.Random(seed).shuffle(pool)
104
+ pool = sorted(pool[:n_tickers])
105
+
106
+ trades = defaultdict(list) # strategy -> list[(date, ret_pct_net)]
107
+ all_dates = []
108
+ print(f" Building long-on-signal trades — {len(pool)} tickers · {hold}-day hold · "
109
+ f"cost {ROUND_TRIP_COST_PCT}% · {len(gens)} strategies")
110
+ for ti, tk in enumerate(pool, 1):
111
+ if ti % 50 == 0 or ti == len(pool):
112
+ print(f" … {ti}/{len(pool)} tickers")
113
+ try:
114
+ sc, sh, sl, sv = fetch_ohlcv(tk, "2y")
115
+ c = sc[tk].dropna()
116
+ except Exception:
117
+ continue
118
+ if len(c) < 260:
119
+ continue
120
+ pos = {d: i for i, d in enumerate(c.index)}
121
+ cvals = c.values
122
+ for name, fn in gens.items():
123
+ try:
124
+ sigs = fn(sc, sh, sl, sv, nifty_c, vix_c)
125
+ except Exception:
126
+ continue
127
+ for d, t in sigs:
128
+ i = pos.get(d)
129
+ if i is None or i + hold >= len(cvals) or cvals[i] <= 0:
130
+ continue
131
+ ret = (cvals[i + hold] / cvals[i] - 1.0) * 100.0 - ROUND_TRIP_COST_PCT
132
+ trades[name].append((d, ret))
133
+ all_dates.append(d)
134
+ if not all_dates:
135
+ raise SystemExit("no trades generated.")
136
+
137
+ dmin, dmax = min(all_dates), max(all_dates)
138
+ split = dmin + (dmax - dmin) * (1 - oos_frac)
139
+ print(f"\n Date span {pd.Timestamp(dmin).date()} → {pd.Timestamp(dmax).date()} · "
140
+ f"IS < {pd.Timestamp(split).date()} ≤ OOS (OOS = last {oos_frac:.0%})")
141
+
142
+ rows = []
143
+ cat_oos = defaultdict(list) # category -> pooled OOS trade returns (trade-weighted)
144
+ for name in gens:
145
+ tl = sorted(trades.get(name, []), key=lambda x: x[0])
146
+ is_r = [r for d, r in tl if d < split]
147
+ oos_r = [r for d, r in tl if d >= split]
148
+ cat_oos[CATEGORY.get(name, "Other")].extend(oos_r)
149
+ is_s, oos_s = _sharpe(is_r, hold), _sharpe(oos_r, hold)
150
+ mdd = _max_drawdown([r for d, r in tl if d >= split])
151
+ n_oos = len(oos_r)
152
+ f = {
153
+ "01 OOS>0.5": oos_s > 0.5,
154
+ "02 DD>-35": mdd > -35.0,
155
+ "03 OOS<2.5": oos_s < 2.5,
156
+ "04 !overfit": oos_s <= is_s * 1.3 + 0.5,
157
+ "05 N>=min": n_oos >= min_trades,
158
+ "06 IS>0": is_s > 0,
159
+ }
160
+ survived = all(f.values())
161
+ failed = [k for k, ok in f.items() if not ok]
162
+ rows.append({
163
+ "strategy": name, "category": CATEGORY.get(name, "Other"),
164
+ "n_is": len(is_r), "n_oos": n_oos,
165
+ "is_sharpe": round(is_s, 2), "oos_sharpe": round(oos_s, 2),
166
+ "oos_winrate": round(100 * np.mean([r > 0 for r in oos_r]), 0) if oos_r else 0.0,
167
+ "oos_mean_ret": round(float(np.mean(oos_r)), 2) if oos_r else 0.0,
168
+ "max_dd": round(mdd, 1), "survived": survived,
169
+ "first_fail": failed[0] if failed else "",
170
+ })
171
+ df = pd.DataFrame(rows).sort_values(["category", "oos_sharpe"], ascending=[True, False])
172
+ _report(df, min_trades, cat_oos, hold)
173
+ out = os.path.join(os.path.dirname(os.path.abspath(__file__)), "strategy_validation_funnel.csv")
174
+ df.to_csv(out, index=False)
175
+ print(f"\n ✓ per-strategy detail → {out}")
176
+ return df
177
+
178
+
179
+ def _pooled(cat_oos: dict, hold: int):
180
+ """Trade-weighted per-category stats: pool ALL OOS trades in a category into one series."""
181
+ out = {}
182
+ for cat, rets in cat_oos.items():
183
+ if not rets:
184
+ continue
185
+ out[cat] = {"n": len(rets), "sharpe": _sharpe(rets, hold),
186
+ "win": 100 * np.mean([r > 0 for r in rets]),
187
+ "mean": float(np.mean(rets))}
188
+ return out
189
+
190
+
191
+ def _report(df: pd.DataFrame, min_trades: int, cat_oos: dict, hold: int):
192
+ print("\n" + "═" * 92)
193
+ print(" STRATEGY VALIDATION FUNNEL — doc's 6 filters applied to THIS project's signals")
194
+ print("═" * 92)
195
+ print(f" {'Strategy':<12}{'Category':<12}{'N_oos':>6}{'IS_Shp':>8}{'OOS_Shp':>9}"
196
+ f"{'Win%':>6}{'MeanRet':>9}{'MaxDD':>8} Verdict")
197
+ print(" " + "-" * 88)
198
+ for _, r in df.iterrows():
199
+ verdict = "✓ SURVIVES" if r["survived"] else f"✗ {r['first_fail']}"
200
+ print(f" {r['strategy']:<12}{r['category']:<12}{int(r['n_oos']):>6}{r['is_sharpe']:>8.2f}"
201
+ f"{r['oos_sharpe']:>9.2f}{r['oos_winrate']:>5.0f}%{r['oos_mean_ret']:>+9.2f}"
202
+ f"{r['max_dd']:>7.1f}% {verdict}")
203
+
204
+ print("\n" + "═" * 92)
205
+ print(f" SURVIVAL BY CATEGORY (our result vs the doc's 9,120-backtest funnel)")
206
+ print("═" * 92)
207
+ print(f" {'Category':<12}{'Tested':>7}{'Surv':>6}{'Rate':>7}{'MeanOOS_Shp':>13}{'BestSurvivor':>22}"
208
+ f" | {'DocRate':>8}{'DocBest':>20}")
209
+ print(" " + "-" * 108)
210
+ order = ["MeanRev", "Trend", "Composite", "Volatility", "Seasonal", "Other"]
211
+ cats = [c for c in order if c in set(df["category"])]
212
+ for cat in cats:
213
+ g = df[df["category"] == cat]
214
+ tested = len(g)
215
+ surv = int(g["survived"].sum())
216
+ rate = 100 * surv / tested if tested else 0
217
+ mean_oos = g["oos_sharpe"].mean()
218
+ best = g.sort_values("oos_sharpe", ascending=False).iloc[0]
219
+ best_lbl = f"{best['strategy']} {best['oos_sharpe']:.2f}"
220
+ doc = _DOC.get(cat, {})
221
+ doc_rate = f"{doc.get('rate', float('nan')):.1f}%" if doc else "—"
222
+ doc_best = doc.get("best", "—")
223
+ print(f" {cat:<12}{tested:>7}{surv:>6}{rate:>6.0f}%{mean_oos:>13.2f}{best_lbl:>22}"
224
+ f" | {doc_rate:>8}{doc_best:>20}")
225
+
226
+ # Headline comparison to the doc's claim (TRADE-WEIGHTED, robust to tiny-N strategies)
227
+ pooled = _pooled(cat_oos, hold)
228
+ print("\n ── TRADE-WEIGHTED category OOS (pool every trade in the category into one series) ──")
229
+ print(f" {'Category':<12}{'N_trades':>9}{'OOS_Sharpe':>12}{'Win%':>7}{'MeanRet%':>10}")
230
+ print(" " + "-" * 50)
231
+ ranked = sorted(pooled.items(), key=lambda kv: kv[1]["sharpe"], reverse=True)
232
+ for cat, s in ranked:
233
+ print(f" {cat:<12}{s['n']:>9,}{s['sharpe']:>+12.2f}{s['win']:>6.0f}%{s['mean']:>+10.2f}")
234
+
235
+ top_cat = ranked[0][0] if ranked else ""
236
+ mr = pooled.get("MeanRev", {}).get("sharpe", float("nan"))
237
+ tr = pooled.get("Trend", {}).get("sharpe", float("nan"))
238
+ print("\n ── VERDICT vs the doc's claim (\"mean reversion is the only category that works OOS\") ──")
239
+ if top_cat == "MeanRev":
240
+ print(f" → CONFIRMS the doc: MeanRev leads trade-weighted OOS Sharpe ({mr:+.2f} vs Trend {tr:+.2f}).")
241
+ else:
242
+ print(f" → DIFFERS from the doc: '{top_cat}' leads here; MeanRev {mr:+.2f} vs Trend {tr:+.2f}.")
243
+ print(" Doc tested US/crypto-style assets 2010-2025; this is NSE single-stock signals traded RAW")
244
+ print(" (enter@signal, exit after hold, no stop/target/ML/AI gating). Not the deployed system.")
245
+ print(" NOTE: point-in-time signals (no lookahead); calendar split. Low-N categories = low-confidence.")
246
+
247
+
248
+ def main():
249
+ ap = argparse.ArgumentParser()
250
+ ap.add_argument("--tickers", type=int, default=300, help="cached tickers to sample")
251
+ ap.add_argument("--hold", type=int, default=3, help="holding period in trading days")
252
+ ap.add_argument("--oos-frac", type=float, default=0.30, help="fraction of the date span held out OOS")
253
+ ap.add_argument("--min-trades", type=int, default=30, help="doc filter [05]: min OOS trades to survive")
254
+ ap.add_argument("--seed", type=int, default=7)
255
+ args = ap.parse_args()
256
+ run(args.tickers, args.hold, args.oos_frac, args.min_trades, args.seed)
257
+
258
+
259
+ if __name__ == "__main__":
260
+ main()
research/watchlist_forward_eval.csv ADDED
@@ -0,0 +1,28 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ ticker,date,tf,direction,confidence,conf_prob,dir_basis,ret_lo,ret_hi,target_lo,target_hi,expected_target,strategies,highest_price,highest_date,lowest_price,entered_range,median_hit,full_range_hit,direction_hit,status
2
+ AXISCADES.NS,2026-07-15,INTRADAY,BULLISH,HIGH,0.825,absolute,0.15,2.94,1613.12,1658.05,1624.28,S5,1652.6,2026-07-15,1595.6,1,1,0,1,DONE
3
+ AXISCADES.NS,2026-07-15,1D,BEARISH,LOW,0.394,vs_nifty,-4.22,0.0,1542.73,1610.7,1581.94,S5,1622.9,2026-07-16,1557.0,1,1,0,1,DONE
4
+ AXISCADES.NS,2026-07-15,3D,BEARISH,LOW,0.37,vs_nifty,-6.91,-0.13,1499.4,1608.61,1559.15,S5,1622.9,2026-07-16,1521.5,1,1,0,1,DONE
5
+ DIACABS.NS,2026-07-15,INTRADAY,NEUTRAL,LOW,0.349,absolute,-0.5,0.5,225.36,227.62,,S3|MFS|NIRA,230.5,2026-07-15,224.85,1,1,1,1,DONE
6
+ DIACABS.NS,2026-07-15,1D,NEUTRAL,LOW,0.388,vs_nifty,-1.0,1.0,224.23,228.75,,S3|MFS|NIRA,229.0,2026-07-16,221.01,1,1,1,1,DONE
7
+ DIACABS.NS,2026-07-15,3D,NEUTRAL,LOW,0.377,vs_nifty,-1.0,1.0,224.23,228.75,,S3|MFS|NIRA,244.61,2026-07-20,216.8,0,0,0,0,DONE
8
+ HINDZINC.NS,2026-07-15,INTRADAY,BULLISH,HIGH,0.798,absolute,0.17,2.28,528.65,539.78,531.47,,536.5,2026-07-15,527.15,1,1,0,1,DONE
9
+ HINDZINC.NS,2026-07-15,1D,NEUTRAL,MEDIUM,0.491,vs_nifty,-1.0,1.0,522.47,533.03,,,530.9,2026-07-16,521.1,0,0,0,0,DONE
10
+ HINDZINC.NS,2026-07-15,3D,NEUTRAL,MEDIUM,0.414,vs_nifty,-1.0,1.0,522.47,533.03,,,530.9,2026-07-16,514.95,1,1,1,1,DONE
11
+ RML.NS,2026-07-15,INTRADAY,BEARISH,MEDIUM,0.512,absolute,-3.0,-0.06,1213.76,1250.55,1242.55,S3|MFS,1270.0,2026-07-15,1230.1,1,1,0,1,DONE
12
+ RML.NS,2026-07-15,1D,NEUTRAL,LOW,0.405,vs_nifty,-1.0,1.0,1238.79,1263.81,,S3|MFS,1283.9,2026-07-16,1235.1,1,1,1,1,DONE
13
+ RML.NS,2026-07-15,3D,BEARISH,LOW,0.371,vs_nifty,-7.59,-0.07,1156.33,1250.42,1215.19,S3|MFS,1283.9,2026-07-16,1167.6,1,1,0,1,DONE
14
+ SCI.NS,2026-07-15,INTRADAY,BEARISH,HIGH,0.769,absolute,-2.69,-0.12,279.91,287.3,285.18,S4|S5|S5V2|S10,290.0,2026-07-15,282.9,1,1,0,1,DONE
15
+ SCI.NS,2026-07-15,1D,NEUTRAL,MEDIUM,0.431,vs_nifty,-1.0,1.0,284.77,290.53,,S4|S5|S5V2|S10,296.0,2026-07-16,288.5,1,1,1,1,DONE
16
+ SCI.NS,2026-07-15,3D,NEUTRAL,LOW,0.374,vs_nifty,-1.0,1.0,284.77,290.53,,S4|S5|S5V2|S10,296.0,2026-07-16,278.55,0,0,0,0,DONE
17
+ SHAILY.NS,2026-07-15,INTRADAY,BULLISH,HIGH,0.818,absolute,0.14,2.94,2697.27,2772.69,2714.85,S5|S5V2|S8,2748.5,2026-07-15,2680.0,1,1,0,1,DONE
18
+ SHAILY.NS,2026-07-15,1D,NEUTRAL,LOW,0.401,vs_nifty,-1.0,1.0,2666.57,2720.43,,S5|S5V2|S8,2731.8,2026-07-16,2666.3,1,1,1,1,DONE
19
+ SHAILY.NS,2026-07-15,3D,NEUTRAL,LOW,0.363,vs_nifty,-1.0,1.0,2666.57,2720.43,,S5|S5V2|S8,2884.6,2026-07-20,2666.3,0,0,0,0,DONE
20
+ STAR.NS,2026-07-15,INTRADAY,BULLISH,HIGH,0.763,absolute,0.13,2.29,1060.98,1083.86,1067.3,S5|S5V2,1077.7,2026-07-15,1053.0,1,1,0,1,DONE
21
+ STAR.NS,2026-07-15,1D,NEUTRAL,MEDIUM,0.468,vs_nifty,-1.0,1.0,1049.0,1070.2,,S5|S5V2,1069.0,2026-07-16,1044.0,1,1,1,1,DONE
22
+ STAR.NS,2026-07-15,3D,NEUTRAL,LOW,0.393,vs_nifty,-1.0,1.0,1049.0,1070.2,,S5|S5V2,1100.0,2026-07-17,1007.5,1,1,1,1,DONE
23
+ TATASTEEL.NS,2026-07-15,INTRADAY,BULLISH,HIGH,0.815,absolute,0.12,1.89,185.48,188.76,186.26,S5,189.33,2026-07-15,184.81,1,1,1,1,DONE
24
+ TATASTEEL.NS,2026-07-15,1D,NEUTRAL,MEDIUM,0.518,vs_nifty,-1.0,1.0,183.41,187.11,,S5,186.75,2026-07-16,184.91,1,1,1,1,DONE
25
+ TATASTEEL.NS,2026-07-15,3D,NEUTRAL,MEDIUM,0.461,vs_nifty,-1.0,1.0,183.41,187.11,,S5,187.19,2026-07-20,183.41,1,1,1,1,DONE
26
+ WHEELS.NS,2026-07-15,INTRADAY,BULLISH,MEDIUM,0.605,absolute,0.09,2.64,1478.93,1516.61,1487.34,S5,1510.0,2026-07-15,1466.1,1,1,0,1,DONE
27
+ WHEELS.NS,2026-07-15,1D,NEUTRAL,LOW,0.364,vs_nifty,-1.0,1.0,1462.82,1492.38,,S5,1495.1,2026-07-16,1459.6,1,1,1,1,DONE
28
+ WHEELS.NS,2026-07-15,3D,BEARISH,LOW,0.382,vs_nifty,-7.04,0.14,1373.58,1479.67,1432.34,S5,1495.1,2026-07-16,1447.1,1,0,0,1,DONE
research/watchlist_forward_eval.py ADDED
@@ -0,0 +1,383 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env python3
2
+ """research/watchlist_forward_eval.py — PER-STOCK forward check on the watchlist.
3
+
4
+ For a single SELECTED DATE, this reconstructs the model's point-in-time prediction for every
5
+ watchlist ticker (no lookahead — features/strategies use only bars on/before that date), then
6
+ walks FORWARD over the real bars that followed and reports, PER STOCK, PER TIMEFRAME:
7
+
8
+ • the prediction made that day (direction, confidence, range, expected/median target)
9
+ • which STRATEGY signals (S1..S20, S_CTRIO, …) fired that day
10
+ • each future date and the HIGH the stock actually printed that day
11
+ • the single HIGHEST price the stock reached over the horizon (and the low)
12
+ • FULL-RANGE hit — did price reach the far (optimistic) bound of the range?
13
+ • MEDIAN hit — did price touch the expected/median target?
14
+ • ENTERED-RANGE — did price reach the near bound (enter the band at all)?
15
+ • DIRECTION hit — did it move the predicted way?
16
+
17
+ Nothing is aggregated into a single blended accuracy — every stock is printed on its own.
18
+ A short, clearly-separated STRATEGY-LIFT diagnostic at the end answers the second question
19
+ ("which strategies can be added to raise confidence and price-hit") by comparing, over a
20
+ lookback window, the median-hit rate of ML-alone vs ML when a strategy also fired.
21
+
22
+ Usage:
23
+ python research/watchlist_forward_eval.py # auto-picks a date 6 trading days back
24
+ python research/watchlist_forward_eval.py --date 2026-07-15
25
+ python research/watchlist_forward_eval.py --tickers TATASTEEL.NS,HINDZINC.NS --date 2026-07-15
26
+ python research/watchlist_forward_eval.py --date 2026-07-15 --tfs 1D,3D
27
+ python research/watchlist_forward_eval.py --date 2026-07-15 --lift-days 40 # widen strategy-lift sample
28
+ python research/watchlist_forward_eval.py --no-lift # skip the strategy diagnostic
29
+ """
30
+ from __future__ import annotations
31
+
32
+ import argparse
33
+ import os
34
+ import sqlite3
35
+ import sys
36
+ from collections import defaultdict
37
+
38
+ import numpy as np
39
+ import pandas as pd
40
+
41
+ _PROJ_ROOT = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
42
+ if _PROJ_ROOT not in sys.path:
43
+ sys.path.insert(0, _PROJ_ROOT)
44
+
45
+ from ml_predictor.features import FEATURE_COLUMNS, TIMEFRAMES, compute_features # noqa: E402
46
+ from ml_predictor.infer import MLPredictor # noqa: E402
47
+ from predictor_core import run_strategy_signals # noqa: E402
48
+
49
+ # horizon in forward trading days per TF (0 == same-day intraday proxy: entry day's own H/L)
50
+ _HORIZON = {"INTRADAY": 0, "1D": 1, "3D": 3}
51
+ _NEUTRAL_CAP = {"INTRADAY": 0.90, "1D": 1.0, "3D": 1.0} # |close move| under which NEUTRAL "holds"
52
+
53
+
54
+ # ── data helpers ──────────────────────────────────────────────────────────────
55
+ def _watchlist() -> list[str]:
56
+ db = os.path.join(_PROJ_ROOT, "paper_trading.db")
57
+ con = sqlite3.connect("file:%s?mode=ro" % db, uri=True)
58
+ try:
59
+ return [r[0] for r in con.execute("SELECT ticker FROM watchlist ORDER BY ticker").fetchall()]
60
+ finally:
61
+ con.close()
62
+
63
+
64
+ def _indices():
65
+ try:
66
+ import yfinance as yf
67
+ raw = yf.download(["^NSEI", "^INDIAVIX"], period="2y", auto_adjust=True, progress=False)
68
+ return raw["Close"]["^NSEI"].dropna(), raw["Close"]["^INDIAVIX"].dropna()
69
+ except Exception:
70
+ return None, None
71
+
72
+
73
+ def _resolve_idx(index: pd.DatetimeIndex, sel: pd.Timestamp) -> int | None:
74
+ """Position of the last trading bar on/before `sel`."""
75
+ pos = index.searchsorted(sel, side="right") - 1
76
+ return int(pos) if pos >= 0 else None
77
+
78
+
79
+ def _forward(c, h, l, idx: int, horizon: int):
80
+ """Return (dates, highs, lows, p0) for the forward window, or None if not enough bars.
81
+ horizon 0 → the entry day's own bar (INTRADAY same-day proxy)."""
82
+ p0 = float(c.iloc[idx])
83
+ if p0 <= 0:
84
+ return None
85
+ if horizon == 0:
86
+ return [c.index[idx]], [float(h.iloc[idx])], [float(l.iloc[idx])], p0
87
+ if idx + horizon >= len(c):
88
+ return None
89
+ js = range(idx + 1, idx + horizon + 1)
90
+ return ([c.index[j] for j in js], [float(h.iloc[j]) for j in js], [float(l.iloc[j]) for j in js], p0)
91
+
92
+
93
+ def _hits(pred: dict, tf: str, p0: float, highs, lows, close_ret_pct: float) -> dict:
94
+ """Compute entered-range / median / full-range / direction hits from actual forward H/L."""
95
+ d = (pred.get("direction") or "NEUTRAL").upper()
96
+ hi = max(highs)
97
+ lo = min(lows)
98
+ tp_lo = pred["target_price_lo"] # BULLISH: near ; BEARISH: deep(far)
99
+ tp_hi = pred["target_price_hi"] # BULLISH: far ; BEARISH: shallow(near)
100
+ exp = pred.get("expected_target_price")
101
+ if d == "BULLISH":
102
+ entered = hi >= tp_lo
103
+ full = hi >= tp_hi
104
+ median = (exp is not None) and (hi >= exp)
105
+ direction = hi > p0
106
+ elif d == "BEARISH":
107
+ entered = lo <= tp_hi
108
+ full = lo <= tp_lo
109
+ median = (exp is not None) and (lo <= exp)
110
+ direction = lo < p0
111
+ else: # NEUTRAL / range-bound — "hit" == it actually stayed in the band
112
+ cap = _NEUTRAL_CAP.get(tf, 1.0)
113
+ held = abs(close_ret_pct) <= cap
114
+ entered = held
115
+ full = held
116
+ median = held
117
+ direction = held
118
+ return {"entered": bool(entered), "median": bool(median), "full": bool(full),
119
+ "direction": bool(direction), "high": hi, "low": lo}
120
+
121
+
122
+ def _strategies_on(ticker, sc, sh, sl, sv, nifty_c, vix_c, idx: int) -> list[str]:
123
+ """Strategy signals that fired within the last 5 bars ending at `idx` (point-in-time)."""
124
+ end = idx + 1
125
+ try:
126
+ res = run_strategy_signals(ticker, sc.iloc[:end], sh.iloc[:end], sl.iloc[:end],
127
+ sv.iloc[:end], nifty_c, vix_c=vix_c)
128
+ return res.get("active", [])
129
+ except Exception:
130
+ return []
131
+
132
+
133
+ # ── core ──────────────────────────────────────────────────────────────────────
134
+ def run(tickers, sel_date: pd.Timestamp, tfs, lift_days: int, do_lift: bool, lift_only: bool = False):
135
+ predictor = MLPredictor()
136
+ if not predictor.available:
137
+ raise SystemExit("ml_predictor model not loaded — run `python ml_predictor/train.py` first.")
138
+ from data_sources import fetch_ohlcv
139
+ nifty_c, vix_c = _indices()
140
+ cutoff = predictor.manifest.get("train_cutoff")
141
+
142
+ rows = [] # CSV rows (detail at the selected date)
143
+ lift_rows = [] # (tf, strat_fired_set, ml_dir, ml_conf, median_hit) over the lookback window
144
+
145
+ print("\n" + "═" * 96)
146
+ mode = "STRATEGY-LIFT ONLY" if lift_only else "per-stock detail + lift"
147
+ print(f" WATCHLIST FORWARD CHECK — prediction date {sel_date.date()} · "
148
+ f"{len(tickers)} tickers · model cutoff {cutoff} · {mode}")
149
+ if not lift_only:
150
+ print(" (every stock shown individually — no blended accuracy number)")
151
+ print("═" * 96)
152
+
153
+ done_n = 0
154
+ for tk in tickers:
155
+ done_n += 1
156
+ if lift_only and (done_n % 10 == 0 or done_n == len(tickers)):
157
+ print(f" … sampled {done_n}/{len(tickers)} tickers")
158
+ try:
159
+ sc, sh, sl, sv = fetch_ohlcv(tk, "2y")
160
+ c, h, l, v = sc[tk].dropna(), sh[tk].dropna(), sl[tk].dropna(), sv[tk].dropna()
161
+ except Exception as e:
162
+ print(f"\n {tk:<14} ! OHLCV unavailable ({e})")
163
+ continue
164
+ if len(c) < 210:
165
+ print(f"\n {tk:<14} ! too little history ({len(c)} bars)")
166
+ continue
167
+
168
+ idx = _resolve_idx(c.index, sel_date)
169
+ if idx is None:
170
+ print(f"\n {tk:<14} ! selected date precedes available history")
171
+ continue
172
+
173
+ eff_date = c.index[idx]
174
+ price = float(c.iloc[idx])
175
+ feat = compute_features(c, h, l, v, nifty_c, vix_c, date=eff_date)
176
+ if feat is None:
177
+ print(f"\n {tk:<14} ! features unavailable at {eff_date.date()}")
178
+ continue
179
+ feat_row = [feat.get(k, float("nan")) for k in FEATURE_COLUMNS]
180
+ atr14 = (feat["atr_pct"] / 100 * price) if np.isfinite(feat.get("atr_pct", np.nan)) else None
181
+ active = _strategies_on(tk, sc, sh, sl, sv, nifty_c, vix_c, idx)
182
+
183
+ # ── header per stock (skipped in lift-only mode) ──
184
+ if not lift_only:
185
+ note = "" if eff_date.normalize() == sel_date.normalize() else \
186
+ f" (nearest trading day ≤ {sel_date.date()})"
187
+ print("\n" + "─" * 96)
188
+ print(f" {tk:<14} @ {eff_date.date()}{note} entry ₹{price:,.2f}")
189
+ print(f" strategies firing: {', '.join(active) if active else '(none)'}"
190
+ f" [{len(active)} active]")
191
+
192
+ for tf in ([] if lift_only else tfs):
193
+ hz = _HORIZON[tf]
194
+ pred = predictor._predict_tf(feat_row, tf, price, atr14, None, None, 0, anchor_close=price)
195
+ d = pred["direction"]
196
+ conf = pred["confidence"]
197
+ p = pred.get("confidence_prob")
198
+ basis = pred.get("dir_basis", "absolute")
199
+ band = f"₹{pred['target_price_lo']:,.2f} … ₹{pred['target_price_hi']:,.2f} " \
200
+ f"({pred['predicted_return_lo']:+.2f}% … {pred['predicted_return_hi']:+.2f}%)"
201
+ exp = pred.get("expected_target_price")
202
+ exp_s = f"₹{exp:,.2f}" if exp is not None else "— (range-bound)"
203
+ basis_tag = " vs Nifty" if basis == "vs_nifty" else ""
204
+
205
+ print(f" ── {tf} ── ML {d}{basis_tag} · conf {conf}"
206
+ f"{f' (p={p:.2f})' if p is not None else ''}")
207
+ print(f" range {band}")
208
+ print(f" expected/median target {exp_s}")
209
+
210
+ fwd = _forward(c, h, l, idx, hz)
211
+ if fwd is None:
212
+ print(" forward: PENDING — not enough bars after the selected date yet")
213
+ rows.append({"ticker": tk, "date": str(eff_date.date()), "tf": tf, "direction": d,
214
+ "confidence": conf, "conf_prob": p, "dir_basis": basis,
215
+ "ret_lo": pred["predicted_return_lo"], "ret_hi": pred["predicted_return_hi"],
216
+ "target_lo": pred["target_price_lo"], "target_hi": pred["target_price_hi"],
217
+ "expected_target": exp, "strategies": "|".join(active),
218
+ "status": "PENDING"})
219
+ continue
220
+
221
+ dates, highs, lows, p0 = fwd
222
+ close_ret = (float(c.iloc[idx + hz]) / p0 - 1) * 100 if hz > 0 else 0.0
223
+ hit = _hits(pred, tf, p0, highs, lows, close_ret)
224
+ hi_px, hi_i = max(zip(highs, range(len(highs))))
225
+ hi_date = dates[hi_i]
226
+
227
+ # per future day
228
+ print(" forward days (actual):")
229
+ for dt, hh, ll in zip(dates, highs, lows):
230
+ mv = (hh / p0 - 1) * 100
231
+ print(f" {pd.Timestamp(dt).date()} high ₹{hh:,.2f} ({mv:+.2f}%) "
232
+ f"low ₹{ll:,.2f} ({(ll / p0 - 1) * 100:+.2f}%)")
233
+ print(f" highest reached ₹{hit['high']:,.2f} ({(hit['high'] / p0 - 1) * 100:+.2f}%) "
234
+ f"on {pd.Timestamp(hi_date).date()} · lowest ₹{hit['low']:,.2f} "
235
+ f"({(hit['low'] / p0 - 1) * 100:+.2f}%)")
236
+ mk = lambda b: "✓" if b else "✗"
237
+ print(f" → entered-range {mk(hit['entered'])} median-hit {mk(hit['median'])} "
238
+ f"full-range {mk(hit['full'])} direction {mk(hit['direction'])}")
239
+
240
+ rows.append({"ticker": tk, "date": str(eff_date.date()), "tf": tf, "direction": d,
241
+ "confidence": conf, "conf_prob": p, "dir_basis": basis,
242
+ "ret_lo": pred["predicted_return_lo"], "ret_hi": pred["predicted_return_hi"],
243
+ "target_lo": pred["target_price_lo"], "target_hi": pred["target_price_hi"],
244
+ "expected_target": exp, "strategies": "|".join(active),
245
+ "highest_price": round(hit["high"], 2), "highest_date": str(pd.Timestamp(hi_date).date()),
246
+ "lowest_price": round(hit["low"], 2),
247
+ "entered_range": int(hit["entered"]), "median_hit": int(hit["median"]),
248
+ "full_range_hit": int(hit["full"]), "direction_hit": int(hit["direction"]),
249
+ "status": "DONE"})
250
+
251
+ # ── strategy-lift sampling over a lookback window (per this stock) ──
252
+ if do_lift:
253
+ start = max(210, idx - lift_days + 1)
254
+ for j in range(start, idx + 1):
255
+ dj = c.index[j]
256
+ fj = compute_features(c, h, l, v, nifty_c, vix_c, date=dj)
257
+ if fj is None:
258
+ continue
259
+ frow = [fj.get(k, float("nan")) for k in FEATURE_COLUMNS]
260
+ pj = float(c.iloc[j])
261
+ aj = (fj["atr_pct"] / 100 * pj) if np.isfinite(fj.get("atr_pct", np.nan)) else None
262
+ act_j = set(_strategies_on(tk, sc, sh, sl, sv, nifty_c, vix_c, j))
263
+ for tf in tfs:
264
+ hz = _HORIZON[tf]
265
+ fwd = _forward(c, h, l, j, hz)
266
+ if fwd is None:
267
+ continue
268
+ pr = predictor._predict_tf(frow, tf, pj, aj, None, None, 0, anchor_close=pj)
269
+ _, hh, ll, p0 = fwd
270
+ cret = (float(c.iloc[j + hz]) / p0 - 1) * 100 if hz > 0 else 0.0
271
+ hit = _hits(pr, tf, p0, hh, ll, cret)
272
+ lift_rows.append({"tf": tf, "strats": act_j, "ml_dir": pr["direction"],
273
+ "ml_conf": pr["confidence"], "median": int(hit["median"]),
274
+ "entered": int(hit["entered"])})
275
+
276
+ if not lift_only:
277
+ _write_csv(rows)
278
+ if do_lift and lift_rows:
279
+ _strategy_lift(pd.DataFrame(lift_rows), tfs)
280
+ return rows
281
+
282
+
283
+ def _write_csv(rows):
284
+ if not rows:
285
+ return
286
+ out = os.path.join(os.path.dirname(os.path.abspath(__file__)), "watchlist_forward_eval.csv")
287
+ pd.DataFrame(rows).to_csv(out, index=False)
288
+ print("\n" + "─" * 96)
289
+ print(f" ✓ Per-stock detail written → {out}")
290
+
291
+
292
+ def _strategy_lift(df: pd.DataFrame, tfs):
293
+ """Which strategies raise the MEDIAN-hit rate when they co-fire with the ML call?
294
+
295
+ For each TF this compares ML-alone median-hit vs ML+strategy median-hit over the sampled
296
+ lookback window. A positive 'lift' means: on the days that strategy fired, the model's
297
+ median target was reached MORE often than its own baseline — i.e. adding that strategy as a
298
+ confirmation gate would raise both the confidence you can place in the call and the hit rate.
299
+ """
300
+ print("\n" + "═" * 96)
301
+ print(" STRATEGY-LIFT DIAGNOSTIC — which signals, added as a confirm gate, raise the median-hit rate")
302
+ print(" (sampled point-in-time over the lookback window; lift = strat-day hit% − ML-baseline hit%)")
303
+ print("═" * 96)
304
+ # collect the strategy universe seen
305
+ all_strats = sorted({s for row in df["strats"] for s in row})
306
+ for tf in tfs:
307
+ t = df[df["tf"] == tf]
308
+ if t.empty:
309
+ continue
310
+ base = t["median"].mean()
311
+ base_ent = t["entered"].mean()
312
+ print(f"\n ── {tf} ── ML-baseline: median-hit {base:.0%} · entered-range {base_ent:.0%} "
313
+ f"(N={len(t)})")
314
+ print(f" {'strategy':<12}{'#days':>7}{'median-hit':>13}{'lift':>9}{'entered':>10}")
315
+ scored = []
316
+ for s in all_strats:
317
+ m = t[t["strats"].apply(lambda st: s in st)]
318
+ if len(m) < 3: # too few to be meaningful
319
+ continue
320
+ mh = m["median"].mean()
321
+ scored.append((s, len(m), mh, mh - base, m["entered"].mean()))
322
+ # sort by lift desc
323
+ scored.sort(key=lambda r: r[3], reverse=True)
324
+ if not scored:
325
+ print(" (no strategy fired often enough over this window to measure)")
326
+ continue
327
+ for s, n, mh, lift, ent in scored:
328
+ flag = " ⟵ helps" if lift > 0.05 and n >= 4 else ""
329
+ print(f" {s:<12}{n:>7}{mh:>12.0%}{lift:>+9.0%}{ent:>10.0%}{flag}")
330
+ print("\n Read: a strategy with a clearly positive 'lift' and enough '#days' is a candidate to")
331
+ print(" gate/upgrade the ML call on (raises confidence + price-hit). Zero/negative lift = the")
332
+ print(" model already prices that signal in, so adding it changes nothing.")
333
+
334
+
335
+ def _universe_sample(n: int, seed: int = 7) -> list[str]:
336
+ """A deterministic sample of the dynamic NSE universe (for broad strategy-lift validation)."""
337
+ from universe import get_universe
338
+ uni = sorted(get_universe().keys())
339
+ if n >= len(uni):
340
+ return uni
341
+ import random
342
+ random.Random(seed).shuffle(uni)
343
+ return sorted(uni[:n])
344
+
345
+
346
+ def main():
347
+ ap = argparse.ArgumentParser()
348
+ ap.add_argument("--date", default=None, help="prediction date YYYY-MM-DD (default: ~6 trading days back)")
349
+ ap.add_argument("--tickers", default=None, help="comma-separated override (default = DB watchlist)")
350
+ ap.add_argument("--universe", type=int, default=0,
351
+ help="validate on a deterministic N-ticker sample of the NSE universe (implies --lift-only)")
352
+ ap.add_argument("--tfs", default="INTRADAY,1D,3D", help="comma-separated subset of INTRADAY,1D,3D")
353
+ ap.add_argument("--lift-days", type=int, default=30, help="lookback trading days for strategy-lift sampling")
354
+ ap.add_argument("--no-lift", action="store_true", help="skip the strategy-lift diagnostic")
355
+ ap.add_argument("--lift-only", action="store_true",
356
+ help="only compute the strategy-lift diagnostic (suppress per-stock detail)")
357
+ args = ap.parse_args()
358
+
359
+ if args.universe > 0:
360
+ tickers = _universe_sample(args.universe)
361
+ args.lift_only = True
362
+ elif args.tickers:
363
+ tickers = [t.strip() for t in args.tickers.split(",")]
364
+ else:
365
+ tickers = _watchlist()
366
+ if not tickers:
367
+ raise SystemExit("watchlist is empty — pass --tickers or --universe N")
368
+ tfs = [t.strip().upper() for t in args.tfs.split(",") if t.strip().upper() in TIMEFRAMES]
369
+ if not tfs:
370
+ raise SystemExit("no valid timeframes in --tfs")
371
+
372
+ if args.date:
373
+ sel = pd.Timestamp(args.date)
374
+ else:
375
+ # default: 6 calendar days back (≈ leaves forward bars for 3D). Resolved per-ticker to a bar.
376
+ sel = pd.Timestamp.today().normalize() - pd.Timedelta(days=6)
377
+ print(f" (no --date given; using {sel.date()} so 1D/3D horizons have realized forward bars)")
378
+
379
+ run(tickers, sel, tfs, args.lift_days, not args.no_lift, lift_only=args.lift_only)
380
+
381
+
382
+ if __name__ == "__main__":
383
+ main()
static/app.js CHANGED
@@ -873,8 +873,15 @@ async function _fetchAndUpdateTfCell(ticker, tf, pick, attempt = 0) {
873
  if (!cell) return;
874
 
875
  const tfLabel = tf === 'INTRADAY' ? 'Today' : tf;
 
 
 
 
 
 
876
  cell.className = 'tf-cell tf-cell--loading';
877
- cell.innerHTML = `<div class="tf-label">${tfLabel}</div><div class="tf-cell-spinner">⟳</div><div style="font-size:11px;color:var(--text-muted);margin-top:4px">🤖 AI loading…</div>`;
 
878
 
879
  try {
880
  const res = await fetch('/api/watchlist-pick/' + encodeURIComponent(ticker) + '/' + tf, {cache: 'no-store'});
@@ -910,7 +917,8 @@ async function _fetchAndUpdateTfCell(ticker, tf, pick, attempt = 0) {
910
  // (the ML estimate is already shown), don't surface a terminal error.
911
  if (attempt < _AI_RETRY_MAX) {
912
  cell.className = 'tf-cell tf-cell--loading';
913
- cell.innerHTML = `<div class="tf-label">${tfLabel}</div><div class="tf-cell-spinner">⟳</div><div style="font-size:11px;color:var(--text-muted);margin-top:4px">🤖 AI loading…</div>`;
 
914
  setTimeout(() => _fetchAndUpdateTfCell(ticker, tf, pick, attempt + 1), 60000);
915
  } else {
916
  cell.className = 'tf-cell';
@@ -1073,11 +1081,13 @@ async function _fetchAndFillMl(ticker, force = false, tfs = ['INTRADAY', '1D', '
1073
  if (!ml || force || stale) {
1074
  const res = await fetch('/api/ml-predict/' + encodeURIComponent(ticker) + '?archive=1', { cache: 'no-store' });
1075
  ml = await res.json();
1076
- // Don't durably cache a forecast fetched while NSE was closed its INTRADAY cell
1077
- // is stubbed "market closed", and caching it would keep showing that stub after the
1078
- // market opens (09:15 IST). Skipping the cache makes the next render refetch a real
1079
- // INTRADAY forecast once the session is live.
1080
- if (!ml || !ml.intraday_market_closed) {
 
 
1081
  _mlCache.set(ticker, ml);
1082
  _mlCacheTs.set(ticker, Date.now());
1083
  }
@@ -1134,8 +1144,6 @@ function renderPickCard(pick, idx, idPrefix = 'pick', mode = 'top5') {
1134
  const warning = pick.warning || '';
1135
 
1136
  const pickPrice = pick.price || 0;
1137
- const sl3d = (tfs['3D'] || {}).stop_loss || 0;
1138
- const tgt3d = (tfs['3D'] || {}).expected_target_price || (tfs['3D'] || {}).min_target || 0;
1139
  let bestTf = pick.best_tf || null;
1140
  // Recommendation source: AI by default; if the AI produced no actionable best timeframe
1141
  // (all AI cells N/A), fall back to the ML model's strongest directional call.
@@ -1504,6 +1512,18 @@ const _top5AiRetried = new Set(); // 'ticker|tf' cells that already have a back
1504
  function _renderTop5CardsInto(cardsEl, idPrefix, picks, bannerHtml = '') {
1505
  _lastTop5 = { cardsEl, idPrefix, picks, banner: bannerHtml };
1506
  const ordered = _applyTop5Sort(picks);
 
 
 
 
 
 
 
 
 
 
 
 
1507
  const sortBar = `<div class="top5-sort-bar">
1508
  <span class="top5-sort-lbl">Rank by</span>
1509
  ${[['ai','AI'],['ml','🤖 ML'],['blend','Blend']].map(([m,l]) =>
@@ -1513,7 +1533,13 @@ function _renderTop5CardsInto(cardsEl, idPrefix, picks, bannerHtml = '') {
1513
  cardsEl.innerHTML = sortBar + bannerHtml + ordered.map((p, i) => renderPickCard(p, i, idPrefix)).join('');
1514
  ordered.forEach(p => {
1515
  const tvId = 'tv-' + idPrefix + '-' + p.ticker.replace(/[^a-zA-Z0-9]/g, '_');
1516
- observeTvChart(tvId, p.ticker);
 
 
 
 
 
 
1517
  _fetchAndFillMl(p.ticker); // instant ML row; AI row keeps its own loader
1518
  // Top picks have no per-card ↺ Retry button, so without this any TF that came back
1519
  // 'ai_unavailable'/'timeout' would sit on "AI loading… retrying automatically" forever.
@@ -2453,7 +2479,10 @@ function openTradeModal(ticker, name, price, stopLoss = 0, target = 0, planData
2453
  // Cache hit — apply synchronously, no network round-trip needed.
2454
  const pick = cached.pick;
2455
  const tfs = pick.timeframes || {};
2456
- const tfKey = tfs['3D'] ? '3D' : (tfs['1D'] ? '1D' : Object.keys(tfs)[0]);
 
 
 
2457
  const tf = tfs[tfKey];
2458
  if (tf && !_pendingTradeData.prediction_data) {
2459
  _pendingTradeData = {
@@ -2496,7 +2525,9 @@ function openTradeModal(ticker, name, price, stopLoss = 0, target = 0, planData
2496
  if (!d || !d.pick) return;
2497
  const pick = d.pick;
2498
  const tfs = pick.timeframes || {};
2499
- const tfKey = tfs['3D'] ? '3D' : (tfs['1D'] ? '1D' : Object.keys(tfs)[0]);
 
 
2500
  const tf = tfs[tfKey];
2501
  if (!tf || _pendingTradeData.prediction_data) return; // already populated by the time this resolves
2502
  _pendingTradeData = {
@@ -2812,8 +2843,24 @@ function renderValidationHistoryCard(h) {
2812
  };
2813
  const gradeInfo = h.hit_grade ? GRADE_BADGE[h.hit_grade] : null;
2814
  const midpoint = (tpLo && tpHi) ? (tpLo + tpHi) / 2 : null;
2815
- const reachedStr = (h.point_reached != null && midpoint != null && entry > 0)
2816
- ? `Midpoint ₹${num(midpoint,2)} · reached ₹${num(h.point_reached,2)} <span class="vh-pct">(${pct(h.point_reached) >= 0 ? '+' : ''}${pct(h.point_reached)}%)</span>`
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2817
  : null;
2818
 
2819
  const rangeStr = (tpLo && tpHi)
 
873
  if (!cell) return;
874
 
875
  const tfLabel = tf === 'INTRADAY' ? 'Today' : tf;
876
+ // Keep the ML block in the loading cell (with its slot id) so the independent ML forecast
877
+ // stays visible while the slow AI call is in flight — ML must never disappear behind AI.
878
+ const _loadingCellHtml = `<div class="tf-label">${tfLabel}</div><div class="tf-cell-spinner">⟳</div>`
879
+ + `<div style="font-size:11px;color:var(--text-muted);margin-top:4px">🤖 AI loading…</div>`
880
+ + `<div class="tf-ai-ml-sep"><span class="tf-ai-ml-sep-lbl">🤖 ML MODEL</span></div>`
881
+ + `<div class="tf-ml-block"><div class="tf-ml-slot" id="ml-${safeId}-${tf}"><div class="tf-ml-mini-loader">🤖 ML…</div></div></div>`;
882
  cell.className = 'tf-cell tf-cell--loading';
883
+ cell.innerHTML = _loadingCellHtml;
884
+ _fetchAndFillMl(ticker, false, [tf]); // keep ML visible during the AI fetch (cached → instant)
885
 
886
  try {
887
  const res = await fetch('/api/watchlist-pick/' + encodeURIComponent(ticker) + '/' + tf, {cache: 'no-store'});
 
917
  // (the ML estimate is already shown), don't surface a terminal error.
918
  if (attempt < _AI_RETRY_MAX) {
919
  cell.className = 'tf-cell tf-cell--loading';
920
+ cell.innerHTML = _loadingCellHtml;
921
+ _fetchAndFillMl(ticker, false, [tf]); // keep ML visible during the retry wait
922
  setTimeout(() => _fetchAndUpdateTfCell(ticker, tf, pick, attempt + 1), 60000);
923
  } else {
924
  cell.className = 'tf-cell';
 
1081
  if (!ml || force || stale) {
1082
  const res = await fetch('/api/ml-predict/' + encodeURIComponent(ticker) + '?archive=1', { cache: 'no-store' });
1083
  ml = await res.json();
1084
+ // Always cache the payload even when NSE is closed. The ML row (especially 1D/3D,
1085
+ // which don't move while the market is shut) must render instantly from cache on every
1086
+ // re-render, otherwise each AI-retry rebuild regenerates the '🤖 ML…' placeholder and the
1087
+ // ML forecast appears stuck/hung behind the slow AI call. INTRADAY freshness is preserved
1088
+ // by the 5-min stale window and the market-hours refresh tick, which refetch the INTRADAY
1089
+ // slot once the session is live again.
1090
+ if (ml) {
1091
  _mlCache.set(ticker, ml);
1092
  _mlCacheTs.set(ticker, Date.now());
1093
  }
 
1144
  const warning = pick.warning || '';
1145
 
1146
  const pickPrice = pick.price || 0;
 
 
1147
  let bestTf = pick.best_tf || null;
1148
  // Recommendation source: AI by default; if the AI produced no actionable best timeframe
1149
  // (all AI cells N/A), fall back to the ML model's strongest directional call.
 
1512
  function _renderTop5CardsInto(cardsEl, idPrefix, picks, bannerHtml = '') {
1513
  _lastTop5 = { cardsEl, idPrefix, picks, banner: bannerHtml };
1514
  const ordered = _applyTop5Sort(picks);
1515
+ // Preserve already-mounted chart nodes across re-renders. Streaming polls this every ~4s and
1516
+ // the sort toggle re-renders too; a naive innerHTML rebuild tore down + remounted every live
1517
+ // chart each time, causing visible blinking. Stash each mounted chart's wrapper by ticker id
1518
+ // and splice it back into the fresh (empty) slot instead of remounting.
1519
+ const chartStash = {};
1520
+ ordered.forEach(p => {
1521
+ const tvId = 'tv-' + idPrefix + '-' + p.ticker.replace(/[^a-zA-Z0-9]/g, '_');
1522
+ const existing = document.getElementById(tvId);
1523
+ if (existing && existing.dataset.chartMounted) {
1524
+ chartStash[tvId] = existing.closest('.chart-wrap') || existing;
1525
+ }
1526
+ });
1527
  const sortBar = `<div class="top5-sort-bar">
1528
  <span class="top5-sort-lbl">Rank by</span>
1529
  ${[['ai','AI'],['ml','🤖 ML'],['blend','Blend']].map(([m,l]) =>
 
1533
  cardsEl.innerHTML = sortBar + bannerHtml + ordered.map((p, i) => renderPickCard(p, i, idPrefix)).join('');
1534
  ordered.forEach(p => {
1535
  const tvId = 'tv-' + idPrefix + '-' + p.ticker.replace(/[^a-zA-Z0-9]/g, '_');
1536
+ const stashed = chartStash[tvId];
1537
+ if (stashed) {
1538
+ const freshSlot = document.getElementById(tvId);
1539
+ if (freshSlot) freshSlot.replaceWith(stashed); // reuse the live chart — no remount, no blink
1540
+ } else {
1541
+ observeTvChart(tvId, p.ticker);
1542
+ }
1543
  _fetchAndFillMl(p.ticker); // instant ML row; AI row keeps its own loader
1544
  // Top picks have no per-card ↺ Retry button, so without this any TF that came back
1545
  // 'ai_unavailable'/'timeout' would sit on "AI loading… retrying automatically" forever.
 
2479
  // Cache hit — apply synchronously, no network round-trip needed.
2480
  const pick = cached.pick;
2481
  const tfs = pick.timeframes || {};
2482
+ // Use the RECOMMENDED timeframe (best_tf) so the modal's target / stop / logged timeframe
2483
+ // match the "Recommended: <TF>" the user clicked — not a hardcoded 3D preference.
2484
+ const tfKey = (pick.best_tf && tfs[pick.best_tf]) ? pick.best_tf
2485
+ : (tfs['3D'] ? '3D' : (tfs['1D'] ? '1D' : Object.keys(tfs)[0]));
2486
  const tf = tfs[tfKey];
2487
  if (tf && !_pendingTradeData.prediction_data) {
2488
  _pendingTradeData = {
 
2525
  if (!d || !d.pick) return;
2526
  const pick = d.pick;
2527
  const tfs = pick.timeframes || {};
2528
+ // Match the recommended timeframe (best_tf), not a hardcoded 3D preference.
2529
+ const tfKey = (pick.best_tf && tfs[pick.best_tf]) ? pick.best_tf
2530
+ : (tfs['3D'] ? '3D' : (tfs['1D'] ? '1D' : Object.keys(tfs)[0]));
2531
  const tf = tfs[tfKey];
2532
  if (!tf || _pendingTradeData.prediction_data) return; // already populated by the time this resolves
2533
  _pendingTradeData = {
 
2843
  };
2844
  const gradeInfo = h.hit_grade ? GRADE_BADGE[h.hit_grade] : null;
2845
  const midpoint = (tpLo && tpHi) ? (tpLo + tpHi) / 2 : null;
2846
+
2847
+ // Price the stock actually reached toward the target. Prefer the stored
2848
+ // point_reached; otherwise derive it from the realized window so the reached
2849
+ // price is always shown — including on misses. Bullish → window high (best
2850
+ // upward point), bearish → window low (best downward point), neutral → close.
2851
+ const _isBull = dirKey.includes('BULL');
2852
+ const _isBear = dirKey.includes('BEAR');
2853
+ let reachedPrice = h.point_reached;
2854
+ if (reachedPrice == null) {
2855
+ if (_isBull && winHi != null) reachedPrice = winHi;
2856
+ else if (_isBear && winLo != null) reachedPrice = winLo;
2857
+ else if (h.actual_price_at_validation != null) reachedPrice = h.actual_price_at_validation;
2858
+ else if (winHi != null) reachedPrice = winHi;
2859
+ }
2860
+ const reachedStr = (reachedPrice != null && entry > 0)
2861
+ ? (midpoint != null
2862
+ ? `Midpoint ₹${num(midpoint,2)} · reached ₹${num(reachedPrice,2)} <span class="vh-pct">(${pct(reachedPrice) >= 0 ? '+' : ''}${pct(reachedPrice)}%)</span>`
2863
+ : `Reached ₹${num(reachedPrice,2)} <span class="vh-pct">(${pct(reachedPrice) >= 0 ? '+' : ''}${pct(reachedPrice)}%)</span>`)
2864
  : null;
2865
 
2866
  const rangeStr = (tpLo && tpHi)
templates/index.html CHANGED
@@ -33,7 +33,7 @@
33
  @keyframes apl-spin { to { transform: rotate(360deg); } }
34
  .apl-hint { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif; font-size: 11px; color: #5a6278; }
35
  </style>
36
- <link rel="stylesheet" href="/static/style.css?v=20260723c">
37
  <script src="https://unpkg.com/lightweight-charts@4.2.0/dist/lightweight-charts.standalone.production.js"></script>
38
  </head>
39
  <body>
@@ -449,6 +449,6 @@
449
  </a>
450
  </nav>
451
 
452
- <script src="/static/app.js?v=20260723c"></script>
453
  </body>
454
  </html>
 
33
  @keyframes apl-spin { to { transform: rotate(360deg); } }
34
  .apl-hint { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif; font-size: 11px; color: #5a6278; }
35
  </style>
36
+ <link rel="stylesheet" href="/static/style.css?v=20260724a">
37
  <script src="https://unpkg.com/lightweight-charts@4.2.0/dist/lightweight-charts.standalone.production.js"></script>
38
  </head>
39
  <body>
 
449
  </a>
450
  </nav>
451
 
452
+ <script src="/static/app.js?v=20260724a"></script>
453
  </body>
454
  </html>