Khanna, Videh Rakesh Rakesh Claude Sonnet 4.6 commited on
Commit
edd5dd2
·
1 Parent(s): 89f4faa

feat: bearish neutralization guard, T7 trigger, WhatsApp alerts, equity curve + portfolio review

Browse files

ai_forecast: add T7 (slow positive drift) trigger to 1D/3D/5D; loosen T4/guard RSI+BB thresholds
to 50/45% (from 46/40%); add bearish neutralization guard (BEARISH → NEUTRAL when momentum > -3%
and BB < 65% and RSI < 60 and no bull trigger fired); Nifty-EMA200 context in bearish guard.

app.py: WhatsApp HIGH-confidence alerts on watchlist refresh (no-op if env not set);
/api/equity-curve endpoint; /api/portfolio-review batch AI trade coach endpoint.

risk_engine: include_curve=True support for equity curve data.

top5_picker: refactored for reliability.

research: backtest + loop_backtest + validate_on_trades updates; confidence calibration tuned.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

ai_forecast.py CHANGED
@@ -974,6 +974,9 @@ def _build_synthesis_prompt(
974
  " [B1] Price below VWAP AND price broke the opening-range (ORB) low AND RSI(5) falling\n"
975
  " [B2] Gap down (<-0.3%) failing to reclaim the opening price [failed gap]\n"
976
  "BEARISH GUARD: If RSI < 44 AND BB < 35% → call BULLISH not BEARISH (oversold bounce).\n"
 
 
 
977
  "NEUTRAL: no trigger fires AND price is hovering around VWAP with flat RSI(5).\n"
978
  "Confidence: HIGH = price + VWAP + ORB all aligned. Conflicting VWAP/ORB → MEDIUM. "
979
  "Mixed/at-VWAP → LOW → output NEUTRAL.\n"
@@ -984,15 +987,20 @@ def _build_synthesis_prompt(
984
  " [T1] Price above EMA50 AND MACD > 0\n"
985
  " [T2] Price above EMA50 AND 10D momentum > +3% AND BB position < 85%\n"
986
  " [T3] Price above EMA50 AND 3+ consecutive up days AND 20D momentum > 0%\n"
987
- " [T4] RSI < 46 AND BB position < 38% AND 10D momentum > -2% [mild oversold + flat momentum]\n"
988
  " [T5] 10D momentum > +7% AND BB position < 80% [strong momentum breakout]\n"
989
  " [T6] RSI < 44 AND BB position < 35% [deeply oversold — expect intraday bounce]\n"
 
990
  "BEARISH trigger — ALL conditions required (confirmed bear market, not just correction):\n"
991
  " [B2] Below EMA50 AND below EMA200 AND MACD < 0 AND 10D momentum < -5%\n"
992
  " AND RSI > 50 AND BB > 40% [established bear trend, not oversold]\n"
993
  "OVERBOUGHT stocks (BB > 90%, RSI > 63): call NEUTRAL — NSE stocks in strong uptrends\n"
994
  " continue rallying; overbought alone is NOT a reversal signal.\n"
995
- "BEARISH GUARD: If RSI < 46 AND BB < 40% → call BULLISH (oversold stocks bounce intraday)\n"
 
 
 
 
996
  "POST-SELLOFF GUARD: If Streak shows 3+ consecutive DOWN days → call NEUTRAL, not BEARISH.\n"
997
  " After a multi-day selloff, NSE stocks mean-revert strongly. B2 cannot fire in this state.\n"
998
  " Exception: B1 (BB>95% AND RSI>64 — stock JUST peaked before falling) can still be BEARISH.\n"
@@ -1007,14 +1015,19 @@ def _build_synthesis_prompt(
1007
  " [T1] Price above EMA50 AND MACD > 0\n"
1008
  " [T2] Price above EMA50 AND (10D momentum > +3% OR 20D momentum > +2%)\n"
1009
  " [T3] Price above EMA50 AND 3+ consecutive up days AND 20D momentum > 0%\n"
1010
- " [T4] RSI < 46 AND BB position < 38% AND 10D momentum > -2% [mild oversold bounce]\n"
1011
  " [T5] 10D momentum > +6% AND BB position < 75% [strong breakout]\n"
1012
  " [T6] RSI < 44 AND BB position < 35% [deeply oversold — high bounce probability over 3D]\n"
 
1013
  "BEARISH trigger — ALL conditions required:\n"
1014
  " [B2] Below EMA50 AND below EMA200 AND MACD < 0 AND 20D momentum < -5%\n"
1015
  " AND RSI > 50 AND BB > 40% [confirmed bear market downtrend, not correction]\n"
1016
  "OVERBOUGHT stocks (BB > 90%, RSI > 62): call NEUTRAL — momentum stocks continue higher.\n"
1017
- "BEARISH GUARD: If RSI < 46 AND BB < 40%: call BULLISH not BEARISH (oversold reversal)\n"
 
 
 
 
1018
  "POST-SELLOFF GUARD: If Streak shows 3+ consecutive DOWN days → call NEUTRAL, not BEARISH.\n"
1019
  " After a multi-day selloff, NSE stocks mean-revert. 3D BEARISH after 3+ down days fails ~97%.\n"
1020
  " Exception: B1 (stock JUST reversed from an extreme peak — BB>95%, RSI>64) can be BEARISH.\n"
@@ -1029,14 +1042,19 @@ def _build_synthesis_prompt(
1029
  " [T1] Price above EMA50 AND 20D momentum > 0%\n"
1030
  " [T2] Price above EMA200 AND MACD > 0 [medium-term trend intact]\n"
1031
  " [T3] 10D momentum > +5% AND BB position < 70% [trend with room to run]\n"
1032
- " [T4] RSI < 46 AND BB position < 35% AND 10D momentum > -3%\n"
1033
  " [T5] RSI < 44 AND BB position < 30% [deeply oversold — strong 5D bounce likely]\n"
 
1034
  "BEARISH trigger — ALL conditions required:\n"
1035
  " [B2] Below EMA50 AND below EMA200 AND 20D momentum < -6%\n"
1036
  " AND MACD < 0 AND RSI > 52 AND BB > 40% [genuine bear trend, not oversold dip]\n"
1037
  "OVERBOUGHT stocks (BB > 90%, RSI > 60): call NEUTRAL — high-momentum NSE stocks\n"
1038
  " overshoot and keep running; overbought is not a timing signal over 5 days.\n"
1039
- "BEARISH GUARD: If RSI < 46 AND BB < 40%: call BULLISH not BEARISH\n"
 
 
 
 
1040
  "POST-SELLOFF GUARD: If Streak shows 3+ consecutive DOWN days → call NEUTRAL, not BEARISH.\n"
1041
  " Over 5D horizon, stocks recovering from multi-day selloffs outperform BEARISH predictions.\n"
1042
  " Exception: B1 (extreme overbought before the drop — BB>95%, RSI>64) can still be BEARISH.\n"
 
974
  " [B1] Price below VWAP AND price broke the opening-range (ORB) low AND RSI(5) falling\n"
975
  " [B2] Gap down (<-0.3%) failing to reclaim the opening price [failed gap]\n"
976
  "BEARISH GUARD: If RSI < 44 AND BB < 35% → call BULLISH not BEARISH (oversold bounce).\n"
977
+ "BEARISH NEUTRALIZATION GUARD (prevents wrong BEARISH only — does NOT override BULLISH):\n"
978
+ " If NO bullish trigger fired AND you are about to call BEARISH AND 10D momentum > -3%\n"
979
+ " AND BB < 65% AND RSI < 60 → call NEUTRAL instead (insufficient bearish evidence for intraday)\n"
980
  "NEUTRAL: no trigger fires AND price is hovering around VWAP with flat RSI(5).\n"
981
  "Confidence: HIGH = price + VWAP + ORB all aligned. Conflicting VWAP/ORB → MEDIUM. "
982
  "Mixed/at-VWAP → LOW → output NEUTRAL.\n"
 
987
  " [T1] Price above EMA50 AND MACD > 0\n"
988
  " [T2] Price above EMA50 AND 10D momentum > +3% AND BB position < 85%\n"
989
  " [T3] Price above EMA50 AND 3+ consecutive up days AND 20D momentum > 0%\n"
990
+ " [T4] RSI < 50 AND BB position < 45% AND 10D momentum > -2% [mild oversold + flat momentum]\n"
991
  " [T5] 10D momentum > +7% AND BB position < 80% [strong momentum breakout]\n"
992
  " [T6] RSI < 44 AND BB position < 35% [deeply oversold — expect intraday bounce]\n"
993
+ " [T7] Price above EMA50 AND 20D momentum between +1% and +5% AND RSI < 62 [slow positive drift]\n"
994
  "BEARISH trigger — ALL conditions required (confirmed bear market, not just correction):\n"
995
  " [B2] Below EMA50 AND below EMA200 AND MACD < 0 AND 10D momentum < -5%\n"
996
  " AND RSI > 50 AND BB > 40% [established bear trend, not oversold]\n"
997
  "OVERBOUGHT stocks (BB > 90%, RSI > 63): call NEUTRAL — NSE stocks in strong uptrends\n"
998
  " continue rallying; overbought alone is NOT a reversal signal.\n"
999
+ "BEARISH GUARD: If RSI < 50 AND BB < 45% AND Nifty ABOVE EMA200 → call BULLISH not BEARISH\n"
1000
+ " (If Nifty BELOW EMA200, oversold stocks may continue falling — output NEUTRAL instead)\n"
1001
+ "BEARISH NEUTRALIZATION GUARD (prevents wrong BEARISH calls only — does NOT override BULLISH):\n"
1002
+ " If NO bullish trigger fired AND you are about to call BEARISH AND 10D momentum > -3%\n"
1003
+ " AND BB < 65% AND RSI < 60 → call NEUTRAL instead (insufficient bearish evidence)\n"
1004
  "POST-SELLOFF GUARD: If Streak shows 3+ consecutive DOWN days → call NEUTRAL, not BEARISH.\n"
1005
  " After a multi-day selloff, NSE stocks mean-revert strongly. B2 cannot fire in this state.\n"
1006
  " Exception: B1 (BB>95% AND RSI>64 — stock JUST peaked before falling) can still be BEARISH.\n"
 
1015
  " [T1] Price above EMA50 AND MACD > 0\n"
1016
  " [T2] Price above EMA50 AND (10D momentum > +3% OR 20D momentum > +2%)\n"
1017
  " [T3] Price above EMA50 AND 3+ consecutive up days AND 20D momentum > 0%\n"
1018
+ " [T4] RSI < 50 AND BB position < 45% AND 10D momentum > -2% [mild oversold bounce]\n"
1019
  " [T5] 10D momentum > +6% AND BB position < 75% [strong breakout]\n"
1020
  " [T6] RSI < 44 AND BB position < 35% [deeply oversold — high bounce probability over 3D]\n"
1021
+ " [T7] Price above EMA50 AND 20D momentum between +1% and +5% AND RSI < 62 [slow positive drift]\n"
1022
  "BEARISH trigger — ALL conditions required:\n"
1023
  " [B2] Below EMA50 AND below EMA200 AND MACD < 0 AND 20D momentum < -5%\n"
1024
  " AND RSI > 50 AND BB > 40% [confirmed bear market downtrend, not correction]\n"
1025
  "OVERBOUGHT stocks (BB > 90%, RSI > 62): call NEUTRAL — momentum stocks continue higher.\n"
1026
+ "BEARISH GUARD: If RSI < 50 AND BB < 45% AND Nifty ABOVE EMA200 → call BULLISH not BEARISH\n"
1027
+ " (If Nifty BELOW EMA200, oversold stocks may continue falling — output NEUTRAL instead)\n"
1028
+ "BEARISH NEUTRALIZATION GUARD (prevents wrong BEARISH calls only — does NOT override BULLISH):\n"
1029
+ " If NO bullish trigger fired AND you are about to call BEARISH AND 10D momentum > -3%\n"
1030
+ " AND BB < 65% AND RSI < 60 → call NEUTRAL instead (insufficient bearish evidence)\n"
1031
  "POST-SELLOFF GUARD: If Streak shows 3+ consecutive DOWN days → call NEUTRAL, not BEARISH.\n"
1032
  " After a multi-day selloff, NSE stocks mean-revert. 3D BEARISH after 3+ down days fails ~97%.\n"
1033
  " Exception: B1 (stock JUST reversed from an extreme peak — BB>95%, RSI>64) can be BEARISH.\n"
 
1042
  " [T1] Price above EMA50 AND 20D momentum > 0%\n"
1043
  " [T2] Price above EMA200 AND MACD > 0 [medium-term trend intact]\n"
1044
  " [T3] 10D momentum > +5% AND BB position < 70% [trend with room to run]\n"
1045
+ " [T4] RSI < 50 AND BB position < 45% AND 10D momentum > -3%\n"
1046
  " [T5] RSI < 44 AND BB position < 30% [deeply oversold — strong 5D bounce likely]\n"
1047
+ " [T6] Price above EMA50 AND 20D momentum between +1% and +5% AND RSI < 62 [slow positive drift]\n"
1048
  "BEARISH trigger — ALL conditions required:\n"
1049
  " [B2] Below EMA50 AND below EMA200 AND 20D momentum < -6%\n"
1050
  " AND MACD < 0 AND RSI > 52 AND BB > 40% [genuine bear trend, not oversold dip]\n"
1051
  "OVERBOUGHT stocks (BB > 90%, RSI > 60): call NEUTRAL — high-momentum NSE stocks\n"
1052
  " overshoot and keep running; overbought is not a timing signal over 5 days.\n"
1053
+ "BEARISH GUARD: If RSI < 50 AND BB < 45% AND Nifty ABOVE EMA200 → call BULLISH not BEARISH\n"
1054
+ " (If Nifty BELOW EMA200, oversold stocks may continue falling — output NEUTRAL instead)\n"
1055
+ "BEARISH NEUTRALIZATION GUARD (prevents wrong BEARISH calls only — does NOT override BULLISH):\n"
1056
+ " If NO bullish trigger fired AND you are about to call BEARISH AND 10D momentum > -3%\n"
1057
+ " AND BB < 65% AND RSI < 60 → call NEUTRAL instead (insufficient bearish evidence)\n"
1058
  "POST-SELLOFF GUARD: If Streak shows 3+ consecutive DOWN days → call NEUTRAL, not BEARISH.\n"
1059
  " Over 5D horizon, stocks recovering from multi-day selloffs outperform BEARISH predictions.\n"
1060
  " Exception: B1 (extreme overbought before the drop — BB>95%, RSI>64) can still be BEARISH.\n"
app.py CHANGED
@@ -1403,6 +1403,14 @@ def watchlist_picks():
1403
  if not market and pick_market:
1404
  market = pick_market
1405
 
 
 
 
 
 
 
 
 
1406
  mkt = nse_market_status()
1407
  resp: dict = {"picks": picks, "market": market,
1408
  "generated_at": datetime.now().strftime("%Y-%m-%d %H:%M")}
@@ -1803,6 +1811,77 @@ def portfolio():
1803
  return _json_no_store(summary)
1804
 
1805
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1806
  # ── SIGNAL ACCURACY ───────────────────────────────────────────────────────────
1807
 
1808
  @app.route("/api/signal-accuracy")
 
1403
  if not market and pick_market:
1404
  market = pick_market
1405
 
1406
+ # Send WhatsApp alerts for HIGH-confidence predictions (no-op if env vars not set)
1407
+ try:
1408
+ from whatsapp_alerts import send_bulk_alerts
1409
+ all_tf_preds = [tf_pred for pick in picks for tf_pred in pick.get("timeframes", {}).values() if isinstance(tf_pred, dict)]
1410
+ send_bulk_alerts(all_tf_preds)
1411
+ except Exception:
1412
+ pass
1413
+
1414
  mkt = nse_market_status()
1415
  resp: dict = {"picks": picks, "market": market,
1416
  "generated_at": datetime.now().strftime("%Y-%m-%d %H:%M")}
 
1811
  return _json_no_store(summary)
1812
 
1813
 
1814
+ @app.route("/api/equity-curve")
1815
+ def equity_curve():
1816
+ """Return the equity curve (running portfolio value starting at 100) from closed trades."""
1817
+ try:
1818
+ from risk_engine import get_portfolio_risk
1819
+ data = get_portfolio_risk(include_curve=True)
1820
+ curve = data.get("equity_curve") or []
1821
+ return _json_no_store({"equity_curve": curve, "trade_count": data.get("trade_count", 0)})
1822
+ except Exception as e:
1823
+ return _json_no_store({"equity_curve": [], "error": str(e)}), 500
1824
+
1825
+
1826
+ @app.route("/api/portfolio-review")
1827
+ def portfolio_review():
1828
+ """Batch AI review of last N closed trades — surfaces systematic patterns and biases."""
1829
+ from datetime import datetime as _dt
1830
+ n = request.args.get("n", 20, type=int)
1831
+ n = max(5, min(n, 100))
1832
+
1833
+ trades = db.get_trade_history()
1834
+ closed = [t for t in trades if t.get("pnl_pct") is not None][-n:]
1835
+ if len(closed) < 3:
1836
+ return _json_no_store({"error": "Need at least 3 closed trades for a meaningful review", "trade_count": len(closed)})
1837
+
1838
+ lines = []
1839
+ for t in closed:
1840
+ outcome = "WIN" if _safe_float(t.get("pnl_pct"), 0) >= 0 else "LOSS"
1841
+ lines.append(
1842
+ f"- {t.get('ticker','?')} | {t.get('direction','?')} | "
1843
+ f"Entry ₹{_safe_float(t.get('entry_price'),0):.0f} → Exit ₹{_safe_float(t.get('exit_price') or t.get('current_price'),0):.0f} | "
1844
+ f"P&L {_safe_float(t.get('pnl_pct'),0):+.2f}% | {outcome}"
1845
+ )
1846
+ trade_list = "\n".join(lines)
1847
+
1848
+ prompt = (
1849
+ f"You are a senior trading coach reviewing {len(closed)} recent paper trades on NSE Indian equities.\n\n"
1850
+ f"TRADES:\n{trade_list}\n\n"
1851
+ "Analyze these trades holistically and provide:\n"
1852
+ "1. SYSTEMATIC BIASES: any patterns in what types of trades consistently win or lose\n"
1853
+ "2. SECTOR / TIMING PATTERNS: any sector or time-based tendencies\n"
1854
+ "3. SIZING / RISK MISTAKES: any position sizing or stop-loss issues visible\n"
1855
+ "4. ONE CONCRETE PROCESS FIX: the single most impactful change to improve outcomes\n\n"
1856
+ "Be specific — name tickers and P&L figures. Keep total response under 300 words."
1857
+ )
1858
+
1859
+ review_text = None
1860
+ try:
1861
+ from ai_forecast import _make_chat_call
1862
+ content, provider, model = _make_chat_call(
1863
+ messages=[{"role": "user", "content": prompt}],
1864
+ max_tokens=512,
1865
+ temperature=0.4,
1866
+ )
1867
+ review_text = content.strip()
1868
+ except Exception as e:
1869
+ review_text = f"AI review unavailable: {e}"
1870
+
1871
+ return _json_no_store({
1872
+ "review_text": review_text,
1873
+ "trade_count": len(closed),
1874
+ "generated_at": _dt.now().strftime("%Y-%m-%d %H:%M"),
1875
+ })
1876
+
1877
+
1878
+ def _safe_float(v, default=0.0):
1879
+ try:
1880
+ return float(v) if v is not None else default
1881
+ except Exception:
1882
+ return default
1883
+
1884
+
1885
  # ── SIGNAL ACCURACY ───────────────────────────────────────────────────────────
1886
 
1887
  @app.route("/api/signal-accuracy")
research/ai_prompt_accuracy.csv CHANGED
The diff for this file is too large to render. See raw diff
 
research/ai_prompt_accuracy_trades.csv CHANGED
@@ -1,73 +1,73 @@
1
- date,ticker,timeframe,confidence,direction,matched_strategy,ml_prob,vix,nifty_ok,source,source_provider,source_model,entry_price,target_price_lo,target_price_hi,ret_intraday,ret_1d,ret_3d,ret_5d,ret_for_tf,max_up_0d,min_down_0d,max_up_1d,min_down_1d,max_up_3d,min_down_3d,max_up_5d,min_down_5d,max_up_for_tf,min_down_for_tf,intraday_hit_for_tf,target_hit_for_tf
2
- 2026-06-16,HINDALCO.NS,INTRADAY,MEDIUM,BULLISH,,0.78,13.4,False,github:gpt-4o-mini,github,gpt-4o-mini,982.4,982.69,984.46,0.0,2.596,2.809,0.448,0.0,0.774,-1.262,2.972,-1.14,3.563,-1.14,3.634,-1.14,0.774,-1.262,1,1
3
- 2026-06-16,HINDALCO.NS,1D,MEDIUM,NEUTRAL,,0.78,13.4,False,github:gpt-4o-mini,github,gpt-4o-mini,982.4,932.3,1032.5,0.0,2.596,2.809,0.448,2.596,0.774,-1.262,2.972,-1.14,3.563,-1.14,3.634,-1.14,2.972,-1.14,1,1
4
- 2026-06-16,HINDALCO.NS,3D,MEDIUM,NEUTRAL,,0.78,13.4,False,github:gpt-4o-mini,github,gpt-4o-mini,982.4,932.3,1032.5,0.0,2.596,2.809,0.448,2.809,0.774,-1.262,2.972,-1.14,3.563,-1.14,3.634,-1.14,3.563,-1.14,1,1
5
- 2026-06-16,HINDALCO.NS,5D,MEDIUM,BULLISH,,0.78,13.4,False,github:gpt-4o-mini,github,gpt-4o-mini,982.4,982.5,983.68,0.0,2.596,2.809,0.448,0.448,0.774,-1.262,2.972,-1.14,3.563,-1.14,3.634,-1.14,3.634,-1.14,1,1
6
- 2026-06-16,IPCALAB.NS,INTRADAY,LOW,NEUTRAL,,0.64,13.4,False,github:gpt-4o-mini,github,gpt-4o-mini,1550.2,1536.25,1564.15,0.0,-0.742,2.296,4.825,0.0,2.174,-0.335,0.548,-1.135,3.8,-1.303,5.883,-1.303,2.174,-0.335,1,1
7
- 2026-06-16,IPCALAB.NS,1D,MEDIUM,NEUTRAL,,0.64,13.4,False,github:gpt-4o-mini,github,gpt-4o-mini,1550.2,1471.14,1629.26,0.0,-0.742,2.296,4.825,-0.742,2.174,-0.335,0.548,-1.135,3.8,-1.303,5.883,-1.303,0.548,-1.135,1,1
8
- 2026-06-16,IPCALAB.NS,3D,MEDIUM,NEUTRAL,,0.64,13.4,False,github:gpt-4o-mini,github,gpt-4o-mini,1550.2,1471.14,1629.26,0.0,-0.742,2.296,4.825,2.296,2.174,-0.335,0.548,-1.135,3.8,-1.303,5.883,-1.303,3.8,-1.303,1,1
9
- 2026-06-16,IPCALAB.NS,5D,MEDIUM,NEUTRAL,,0.64,13.4,False,github:gpt-4o-mini,github,gpt-4o-mini,1550.2,1452.54,1647.86,0.0,-0.742,2.296,4.825,4.825,2.174,-0.335,0.548,-1.135,3.8,-1.303,5.883,-1.303,5.883,-1.303,1,1
10
- 2026-06-16,POLYCAB.NS,INTRADAY,MEDIUM,BULLISH,,0.75,13.4,False,github:gpt-4o-mini,github,gpt-4o-mini,9546.205,9549.07,9566.25,0.0,3.493,5.623,3.921,0.0,0.829,-1.215,4.129,0.089,6.011,0.089,6.074,0.089,0.829,-1.215,1,1
11
- 2026-06-16,POLYCAB.NS,1D,MEDIUM,BULLISH,,0.75,13.4,False,github:gpt-4o-mini,github,gpt-4o-mini,9546.205,9547.16,9558.62,0.0,3.493,5.623,3.921,3.493,0.829,-1.215,4.129,0.089,6.011,0.089,6.074,0.089,4.129,0.089,1,1
12
- 2026-06-16,POLYCAB.NS,3D,MEDIUM,BULLISH,,0.75,13.4,False,github:gpt-4o-mini,github,gpt-4o-mini,9546.205,9547.16,9558.62,0.0,3.493,5.623,3.921,5.623,0.829,-1.215,4.129,0.089,6.011,0.089,6.074,0.089,6.011,0.089,1,1
13
- 2026-06-16,POLYCAB.NS,5D,MEDIUM,BULLISH,,0.75,13.4,False,github:gpt-4o-mini,github,gpt-4o-mini,9546.205,9547.16,9558.62,0.0,3.493,5.623,3.921,3.921,0.829,-1.215,4.129,0.089,6.011,0.089,6.074,0.089,6.074,0.089,1,1
14
- 2026-06-16,DLF.NS,INTRADAY,MEDIUM,BULLISH,,0.65,13.4,False,github:gpt-4o-mini,github,gpt-4o-mini,629.3,629.49,630.62,0.0,-0.914,-0.763,-2.693,0.0,0.429,-2.797,0.707,-1.764,2.336,-1.764,2.336,-3.059,0.429,-2.797,1,1
15
- 2026-06-16,DLF.NS,1D,MEDIUM,BULLISH,,0.65,13.4,False,github:gpt-4o-mini,github,gpt-4o-mini,629.3,629.36,630.12,0.0,-0.914,-0.763,-2.693,-0.914,0.429,-2.797,0.707,-1.764,2.336,-1.764,2.336,-3.059,0.707,-1.764,1,1
16
- 2026-06-16,DLF.NS,3D,MEDIUM,BULLISH,,0.65,13.4,False,github:gpt-4o-mini,github,gpt-4o-mini,629.3,629.36,630.12,0.0,-0.914,-0.763,-2.693,-0.763,0.429,-2.797,0.707,-1.764,2.336,-1.764,2.336,-3.059,2.336,-1.764,1,1
17
- 2026-06-16,DLF.NS,5D,MEDIUM,BULLISH,,0.65,13.4,False,github:gpt-4o-mini,github,gpt-4o-mini,629.3,629.36,630.12,0.0,-0.914,-0.763,-2.693,-2.693,0.429,-2.797,0.707,-1.764,2.336,-1.764,2.336,-3.059,2.336,-3.059,1,1
18
- 2026-06-16,SHRIRAMFIN.NS,INTRADAY,MEDIUM,BULLISH,,0.71,13.4,False,github:gpt-4o-mini,github,gpt-4o-mini,1000.093,1000.39,1002.19,0.0,0.174,-0.383,-1.233,0.0,0.428,-1.362,0.994,-0.378,1.148,-1.337,1.148,-2.182,0.428,-1.362,1,1
19
- 2026-06-16,SHRIRAMFIN.NS,1D,MEDIUM,BULLISH,,0.71,13.4,False,github:gpt-4o-mini,github,gpt-4o-mini,1000.093,1000.19,1001.39,0.0,0.174,-0.383,-1.233,0.174,0.428,-1.362,0.994,-0.378,1.148,-1.337,1.148,-2.182,0.994,-0.378,1,1
20
- 2026-06-16,SHRIRAMFIN.NS,3D,MEDIUM,BULLISH,,0.71,13.4,False,github:gpt-4o-mini,github,gpt-4o-mini,1000.093,1000.19,1001.39,0.0,0.174,-0.383,-1.233,-0.383,0.428,-1.362,0.994,-0.378,1.148,-1.337,1.148,-2.182,1.148,-1.337,1,1
21
- 2026-06-16,SHRIRAMFIN.NS,5D,MEDIUM,BULLISH,,0.71,13.4,False,github:gpt-4o-mini,github,gpt-4o-mini,1000.093,1000.19,1001.39,0.0,0.174,-0.383,-1.233,-1.233,0.428,-1.362,0.994,-0.378,1.148,-1.337,1.148,-2.182,1.148,-2.182,1,1
22
- 2026-06-16,AXISCADES.NS,INTRADAY,MEDIUM,BULLISH,,0.71,13.4,False,github:gpt-4o-mini,github,gpt-4o-mini,1825.3,1825.85,1829.13,0.0,4.996,6.459,0.904,0.0,5.057,-0.493,4.996,0.038,9.023,0.038,9.023,0.038,5.057,-0.493,1,1
23
- 2026-06-16,AXISCADES.NS,1D,MEDIUM,BULLISH,,0.71,13.4,False,github:gpt-4o-mini,github,gpt-4o-mini,1825.3,1825.48,1827.67,0.0,4.996,6.459,0.904,4.996,5.057,-0.493,4.996,0.038,9.023,0.038,9.023,0.038,4.996,0.038,1,1
24
- 2026-06-16,AXISCADES.NS,3D,MEDIUM,BULLISH,,0.71,13.4,False,github:gpt-4o-mini,github,gpt-4o-mini,1825.3,1825.48,1827.67,0.0,4.996,6.459,0.904,6.459,5.057,-0.493,4.996,0.038,9.023,0.038,9.023,0.038,9.023,0.038,1,1
25
- 2026-06-16,AXISCADES.NS,5D,MEDIUM,BULLISH,,0.71,13.4,False,github:gpt-4o-mini,github,gpt-4o-mini,1825.3,1825.48,1827.67,0.0,4.996,6.459,0.904,0.904,5.057,-0.493,4.996,0.038,9.023,0.038,9.023,0.038,9.023,0.038,1,1
26
- 2026-06-19,HINDALCO.NS,INTRADAY,MEDIUM,BULLISH,,0.74,13.0,False,github:gpt-4o-mini,github,gpt-4o-mini,1010.0,1010.3,1012.12,0.0,0.416,-3.307,-5.624,0.0,0.673,-2.455,0.802,-0.634,0.802,-3.931,0.802,-5.921,0.673,-2.455,1,1
27
- 2026-06-19,HINDALCO.NS,1D,MEDIUM,NEUTRAL,,0.74,13.0,False,github:gpt-4o-mini,github,gpt-4o-mini,1010.0,958.49,1061.51,0.0,0.416,-3.307,-5.624,0.416,0.673,-2.455,0.802,-0.634,0.802,-3.931,0.802,-5.921,0.802,-0.634,1,1
28
- 2026-06-19,HINDALCO.NS,3D,MEDIUM,NEUTRAL,,0.74,13.0,False,github:gpt-4o-mini,github,gpt-4o-mini,1010.0,958.49,1061.51,0.0,0.416,-3.307,-5.624,-3.307,0.673,-2.455,0.802,-0.634,0.802,-3.931,0.802,-5.921,0.802,-3.931,1,1
29
- 2026-06-19,HINDALCO.NS,5D,MEDIUM,NEUTRAL,,0.74,13.0,False,github:gpt-4o-mini,github,gpt-4o-mini,1010.0,946.37,1073.63,0.0,0.416,-3.307,-5.624,-5.624,0.673,-2.455,0.802,-0.634,0.802,-3.931,0.802,-5.921,0.802,-5.921,1,1
30
- 2026-06-19,IPCALAB.NS,INTRADAY,MEDIUM,BULLISH,,0.71,13.0,False,github:gpt-4o-mini,github,gpt-4o-mini,1585.8,1586.28,1589.13,0.0,0.725,2.232,2.049,0.0,1.469,-2.73,1.204,-0.214,3.506,-0.214,4.364,-0.214,1.469,-2.73,1,1
31
- 2026-06-19,IPCALAB.NS,1D,MEDIUM,BULLISH,,0.71,13.0,False,github:gpt-4o-mini,github,gpt-4o-mini,1585.8,1585.96,1587.86,0.0,0.725,2.232,2.049,0.725,1.469,-2.73,1.204,-0.214,3.506,-0.214,4.364,-0.214,1.204,-0.214,1,1
32
- 2026-06-19,IPCALAB.NS,3D,MEDIUM,BULLISH,,0.71,13.0,False,github:gpt-4o-mini,github,gpt-4o-mini,1585.8,1585.96,1587.86,0.0,0.725,2.232,2.049,2.232,1.469,-2.73,1.204,-0.214,3.506,-0.214,4.364,-0.214,3.506,-0.214,1,1
33
- 2026-06-19,IPCALAB.NS,5D,MEDIUM,BULLISH,,0.71,13.0,False,github:gpt-4o-mini,github,gpt-4o-mini,1585.8,1585.96,1587.86,0.0,0.725,2.232,2.049,2.049,1.469,-2.73,1.204,-0.214,3.506,-0.214,4.364,-0.214,4.364,-0.214,1,1
34
- 2026-06-19,POLYCAB.NS,INTRADAY,MEDIUM,BULLISH,,0.72,13.0,False,github:gpt-4o-mini,github,gpt-4o-mini,10083.0,10086.02,10104.17,0.0,-0.6,-3.873,-5.475,0.0,0.367,-2.499,0.426,-0.903,0.426,-4.046,0.426,-5.618,0.367,-2.499,1,1
35
- 2026-06-19,POLYCAB.NS,1D,MEDIUM,BULLISH,,0.72,13.0,False,github:gpt-4o-mini,github,gpt-4o-mini,10083.0,10084.01,10096.11,0.0,-0.6,-3.873,-5.475,-0.6,0.367,-2.499,0.426,-0.903,0.426,-4.046,0.426,-5.618,0.426,-0.903,1,1
36
- 2026-06-19,POLYCAB.NS,3D,MEDIUM,BULLISH,,0.72,13.0,False,github:gpt-4o-mini,github,gpt-4o-mini,10083.0,10084.01,10096.11,0.0,-0.6,-3.873,-5.475,-3.873,0.367,-2.499,0.426,-0.903,0.426,-4.046,0.426,-5.618,0.426,-4.046,1,1
37
- 2026-06-19,POLYCAB.NS,5D,MEDIUM,BULLISH,,0.72,13.0,False,github:gpt-4o-mini,github,gpt-4o-mini,10083.0,10084.01,10096.11,0.0,-0.6,-3.873,-5.475,-5.475,0.367,-2.499,0.426,-0.903,0.426,-4.046,0.426,-5.618,0.426,-5.618,1,1
38
- 2026-06-19,DLF.NS,INTRADAY,MEDIUM,BULLISH,,0.71,13.0,False,github:gpt-4o-mini,github,gpt-4o-mini,624.5,624.69,625.81,0.0,0.496,-1.017,-0.504,0.0,2.322,-0.777,1.169,0.096,1.906,-2.418,1.906,-2.418,2.322,-0.777,1,1
39
- 2026-06-19,DLF.NS,1D,MEDIUM,BULLISH,,0.71,13.0,False,github:gpt-4o-mini,github,gpt-4o-mini,624.5,624.56,625.31,0.0,0.496,-1.017,-0.504,0.496,2.322,-0.777,1.169,0.096,1.906,-2.418,1.906,-2.418,1.169,0.096,1,1
40
- 2026-06-19,DLF.NS,3D,MEDIUM,BULLISH,,0.71,13.0,False,github:gpt-4o-mini,github,gpt-4o-mini,624.5,624.56,625.31,0.0,0.496,-1.017,-0.504,-1.017,2.322,-0.777,1.169,0.096,1.906,-2.418,1.906,-2.418,1.906,-2.418,1,1
41
- 2026-06-19,DLF.NS,5D,MEDIUM,BULLISH,,0.71,13.0,False,github:gpt-4o-mini,github,gpt-4o-mini,624.5,624.56,625.31,0.0,0.496,-1.017,-0.504,-0.504,2.322,-0.777,1.169,0.096,1.906,-2.418,1.906,-2.418,1.906,-2.418,1,1
42
- 2026-06-19,SHRIRAMFIN.NS,INTRADAY,MEDIUM,BULLISH,,0.71,13.0,False,github:gpt-4o-mini,github,gpt-4o-mini,996.265,996.56,998.36,0.0,-0.903,1.707,2.984,0.0,0.579,-0.958,0.384,-1.807,2.74,-2.131,5.0,-2.131,0.579,-0.958,1,1
43
- 2026-06-19,SHRIRAMFIN.NS,1D,MEDIUM,BULLISH,,0.71,13.0,False,github:gpt-4o-mini,github,gpt-4o-mini,996.265,996.36,997.56,0.0,-0.903,1.707,2.984,-0.903,0.579,-0.958,0.384,-1.807,2.74,-2.131,5.0,-2.131,0.384,-1.807,1,1
44
- 2026-06-19,SHRIRAMFIN.NS,3D,MEDIUM,BULLISH,,0.71,13.0,False,github:gpt-4o-mini,github,gpt-4o-mini,996.265,996.36,997.56,0.0,-0.903,1.707,2.984,1.707,0.579,-0.958,0.384,-1.807,2.74,-2.131,5.0,-2.131,2.74,-2.131,1,1
45
- 2026-06-19,SHRIRAMFIN.NS,5D,MEDIUM,BULLISH,,0.71,13.0,False,github:gpt-4o-mini,github,gpt-4o-mini,996.265,996.36,997.56,0.0,-0.903,1.707,2.984,2.984,0.579,-0.958,0.384,-1.807,2.74,-2.131,5.0,-2.131,5.0,-2.131,1,1
46
- 2026-06-19,AXISCADES.NS,INTRADAY,MEDIUM,BULLISH,,0.78,13.0,False,github:gpt-4o-mini,github,gpt-4o-mini,1943.2,1943.78,1947.28,0.0,-3.355,-9.536,-12.979,0.0,1.379,-1.662,0.098,-3.767,0.098,-9.953,0.098,-13.601,1.379,-1.662,1,1
47
- 2026-06-19,AXISCADES.NS,1D,MEDIUM,BULLISH,,0.78,13.0,False,github:gpt-4o-mini,github,gpt-4o-mini,1943.2,1943.39,1945.73,0.0,-3.355,-9.536,-12.979,-3.355,1.379,-1.662,0.098,-3.767,0.098,-9.953,0.098,-13.601,0.098,-3.767,1,1
48
- 2026-06-19,AXISCADES.NS,3D,MEDIUM,BULLISH,,0.78,13.0,False,github:gpt-4o-mini,github,gpt-4o-mini,1943.2,1943.39,1945.73,0.0,-3.355,-9.536,-12.979,-9.536,1.379,-1.662,0.098,-3.767,0.098,-9.953,0.098,-13.601,0.098,-9.953,1,1
49
- 2026-06-19,AXISCADES.NS,5D,MEDIUM,BULLISH,,0.78,13.0,False,github:gpt-4o-mini,github,gpt-4o-mini,1943.2,1943.39,1945.73,0.0,-3.355,-9.536,-12.979,-12.979,1.379,-1.662,0.098,-3.767,0.098,-9.953,0.098,-13.601,0.098,-13.601,1,1
50
- 2026-06-22,HINDALCO.NS,INTRADAY,LOW,NEUTRAL,,0.64,12.8,False,github:gpt-4o-mini,github,gpt-4o-mini,1014.2,1005.07,1023.33,0.0,-2.702,-6.015,-4.969,0.0,0.385,-1.045,-1.499,-3.569,-1.499,-6.31,-1.499,-6.31,0.385,-1.045,1,1
51
- 2026-06-22,HINDALCO.NS,1D,MEDIUM,NEUTRAL,,0.64,12.8,False,github:gpt-4o-mini,github,gpt-4o-mini,1014.2,962.48,1065.92,0.0,-2.702,-6.015,-4.969,-2.702,0.385,-1.045,-1.499,-3.569,-1.499,-6.31,-1.499,-6.31,-1.499,-3.569,1,0
52
- 2026-06-22,HINDALCO.NS,3D,MEDIUM,BULLISH,,0.64,12.8,False,github:gpt-4o-mini,github,gpt-4o-mini,1014.2,1014.3,1015.52,0.0,-2.702,-6.015,-4.969,-6.015,0.385,-1.045,-1.499,-3.569,-1.499,-6.31,-1.499,-6.31,-1.499,-6.31,0,0
53
- 2026-06-22,HINDALCO.NS,5D,MEDIUM,NEUTRAL,,0.64,12.8,False,github:gpt-4o-mini,github,gpt-4o-mini,1014.2,950.31,1078.09,0.0,-2.702,-6.015,-4.969,-4.969,0.385,-1.045,-1.499,-3.569,-1.499,-6.31,-1.499,-6.31,-1.499,-6.31,1,0
54
- 2026-06-22,IPCALAB.NS,INTRADAY,LOW,NEUTRAL,,0.71,12.8,False,github:gpt-4o-mini,github,gpt-4o-mini,1597.3,1582.92,1611.68,0.0,1.734,1.315,3.124,0.0,0.476,-0.933,2.761,0.163,3.612,0.163,4.232,0.163,0.476,-0.933,1,1
55
- 2026-06-22,IPCALAB.NS,1D,MEDIUM,BULLISH,,0.71,12.8,False,github:gpt-4o-mini,github,gpt-4o-mini,1597.3,1597.46,1599.38,0.0,1.734,1.315,3.124,1.734,0.476,-0.933,2.761,0.163,3.612,0.163,4.232,0.163,2.761,0.163,1,1
56
- 2026-06-22,IPCALAB.NS,3D,MEDIUM,BULLISH,,0.71,12.8,False,github:gpt-4o-mini,github,gpt-4o-mini,1597.3,1597.46,1599.38,0.0,1.734,1.315,3.124,1.315,0.476,-0.933,2.761,0.163,3.612,0.163,4.232,0.163,3.612,0.163,1,1
57
- 2026-06-22,IPCALAB.NS,5D,MEDIUM,BULLISH,,0.71,12.8,False,github:gpt-4o-mini,github,gpt-4o-mini,1597.3,1597.46,1599.38,0.0,1.734,1.315,3.124,3.124,0.476,-0.933,2.761,0.163,3.612,0.163,4.232,0.163,4.232,0.163,1,1
58
- 2026-06-22,POLYCAB.NS,INTRADAY,LOW,NEUTRAL,,0.68,12.8,False,github:gpt-4o-mini,github,gpt-4o-mini,10022.5,9932.3,10112.7,0.0,-1.018,-4.904,-2.43,0.0,1.033,-0.304,0.524,-1.307,0.524,-5.049,0.524,-6.006,1.033,-0.304,1,1
59
- 2026-06-22,POLYCAB.NS,1D,MEDIUM,BULLISH,,0.68,12.8,False,github:gpt-4o-mini,github,gpt-4o-mini,10022.5,10023.5,10035.53,0.0,-1.018,-4.904,-2.43,-1.018,1.033,-0.304,0.524,-1.307,0.524,-5.049,0.524,-6.006,0.524,-1.307,1,1
60
- 2026-06-22,POLYCAB.NS,3D,MEDIUM,BULLISH,,0.68,12.8,False,github:gpt-4o-mini,github,gpt-4o-mini,10022.5,10023.5,10035.53,0.0,-1.018,-4.904,-2.43,-4.904,1.033,-0.304,0.524,-1.307,0.524,-5.049,0.524,-6.006,0.524,-5.049,1,1
61
- 2026-06-22,POLYCAB.NS,5D,MEDIUM,BULLISH,,0.68,12.8,False,github:gpt-4o-mini,github,gpt-4o-mini,10022.5,10023.5,10035.53,0.0,-1.018,-4.904,-2.43,-2.43,1.033,-0.304,0.524,-1.307,0.524,-5.049,0.524,-6.006,0.524,-6.006,1,1
62
- 2026-06-22,DLF.NS,INTRADAY,MEDIUM,BULLISH,,0.71,12.8,False,github:gpt-4o-mini,github,gpt-4o-mini,627.6,627.79,628.92,0.0,-2.43,-0.996,-2.047,0.0,0.669,-0.398,1.402,-2.796,1.402,-2.9,1.402,-2.9,0.669,-0.398,1,1
63
- 2026-06-22,DLF.NS,1D,MEDIUM,BULLISH,,0.71,12.8,False,github:gpt-4o-mini,github,gpt-4o-mini,627.6,627.66,628.42,0.0,-2.43,-0.996,-2.047,-2.43,0.669,-0.398,1.402,-2.796,1.402,-2.9,1.402,-2.9,1.402,-2.796,1,1
64
- 2026-06-22,DLF.NS,3D,MEDIUM,BULLISH,,0.71,12.8,False,github:gpt-4o-mini,github,gpt-4o-mini,627.6,627.66,628.42,0.0,-2.43,-0.996,-2.047,-0.996,0.669,-0.398,1.402,-2.796,1.402,-2.9,1.402,-2.9,1.402,-2.9,1,1
65
- 2026-06-22,DLF.NS,5D,MEDIUM,BULLISH,,0.71,12.8,False,github:gpt-4o-mini,github,gpt-4o-mini,627.6,627.66,628.42,0.0,-2.43,-0.996,-2.047,-2.047,0.669,-0.398,1.402,-2.796,1.402,-2.9,1.402,-2.9,1.402,-2.9,1,1
66
- 2026-06-22,SHRIRAMFIN.NS,INTRADAY,MEDIUM,BULLISH,,0.78,12.8,False,github:gpt-4o-mini,github,gpt-4o-mini,987.265,987.56,989.34,0.0,0.05,3.923,4.009,0.0,1.299,-0.912,1.496,-0.841,5.958,-1.239,6.3,-1.239,1.299,-0.912,1,1
67
- 2026-06-22,SHRIRAMFIN.NS,1D,MEDIUM,BULLISH,,0.78,12.8,False,github:gpt-4o-mini,github,gpt-4o-mini,987.265,987.36,988.55,0.0,0.05,3.923,4.009,0.05,1.299,-0.912,1.496,-0.841,5.958,-1.239,6.3,-1.239,1.496,-0.841,1,1
68
- 2026-06-22,SHRIRAMFIN.NS,3D,MEDIUM,BULLISH,,0.78,12.8,False,github:gpt-4o-mini,github,gpt-4o-mini,987.265,987.36,988.55,0.0,0.05,3.923,4.009,3.923,1.299,-0.912,1.496,-0.841,5.958,-1.239,6.3,-1.239,5.958,-1.239,1,1
69
- 2026-06-22,SHRIRAMFIN.NS,5D,MEDIUM,BULLISH,,0.78,12.8,False,github:gpt-4o-mini,github,gpt-4o-mini,987.265,987.36,988.55,0.0,0.05,3.923,4.009,4.009,1.299,-0.912,1.496,-0.841,5.958,-1.239,6.3,-1.239,6.3,-1.239,1,1
70
- 2026-06-22,AXISCADES.NS,INTRADAY,MEDIUM,BULLISH,,0.71,12.8,False,github:gpt-4o-mini,github,gpt-4o-mini,1878.0,1878.56,1881.94,0.0,-1.928,-9.957,-9.526,0.0,3.573,-0.426,1.587,-2.391,1.587,-10.602,1.587,-12.875,3.573,-0.426,1,1
71
- 2026-06-22,AXISCADES.NS,1D,MEDIUM,BULLISH,,0.71,12.8,False,github:gpt-4o-mini,github,gpt-4o-mini,1878.0,1878.19,1880.44,0.0,-1.928,-9.957,-9.526,-1.928,3.573,-0.426,1.587,-2.391,1.587,-10.602,1.587,-12.875,1.587,-2.391,1,1
72
- 2026-06-22,AXISCADES.NS,3D,MEDIUM,BULLISH,,0.71,12.8,False,github:gpt-4o-mini,github,gpt-4o-mini,1878.0,1878.19,1880.44,0.0,-1.928,-9.957,-9.526,-9.957,3.573,-0.426,1.587,-2.391,1.587,-10.602,1.587,-12.875,1.587,-10.602,1,1
73
- 2026-06-22,AXISCADES.NS,5D,MEDIUM,BULLISH,,0.71,12.8,False,github:gpt-4o-mini,github,gpt-4o-mini,1878.0,1878.19,1880.44,0.0,-1.928,-9.957,-9.526,-9.526,3.573,-0.426,1.587,-2.391,1.587,-10.602,1.587,-12.875,1.587,-12.875,1,1
 
1
+ date,ticker,timeframe,confidence,direction,matched_strategy,ml_prob,vix,nifty_ok,source,source_provider,source_model,entry_price,target_price_lo,target_price_hi,ret_intraday,ret_1d,ret_3d,ret_5d,ret_for_tf,max_up_0d,min_down_0d,max_up_1d,min_down_1d,max_up_3d,min_down_3d,max_up_5d,min_down_5d,max_up_for_tf,min_down_for_tf,intraday_hit_for_tf,target_hit_for_tf,trigger_T1,trigger_T2,trigger_T3,trigger_T4,trigger_T5,trigger_T6,trigger_B1,trigger_B2
2
+ 2026-06-16,HINDALCO.NS,INTRADAY,MEDIUM,BULLISH,,0.78,13.4,False,groq:llama-3.1-8b-instant,groq,llama-3.1-8b-instant,982.4,982.69,984.46,0.0,2.596,2.809,0.448,0.0,0.774,-1.262,2.972,-1.14,3.563,-1.14,3.634,-1.14,0.774,-1.262,1,1,0,0,0,0,0,1,0,0
3
+ 2026-06-16,HINDALCO.NS,1D,MEDIUM,BULLISH,,0.78,13.4,False,groq:llama-3.1-8b-instant,groq,llama-3.1-8b-instant,982.4,982.5,983.68,0.0,2.596,2.809,0.448,2.596,0.774,-1.262,2.972,-1.14,3.563,-1.14,3.634,-1.14,2.972,-1.14,1,1,0,0,0,0,0,1,0,0
4
+ 2026-06-16,HINDALCO.NS,3D,MEDIUM,BULLISH,,0.78,13.4,False,groq:llama-3.1-8b-instant,groq,llama-3.1-8b-instant,982.4,982.5,983.68,0.0,2.596,2.809,0.448,2.809,0.774,-1.262,2.972,-1.14,3.563,-1.14,3.634,-1.14,3.563,-1.14,1,1,0,0,0,0,0,1,0,0
5
+ 2026-06-16,HINDALCO.NS,5D,MEDIUM,BULLISH,,0.78,13.4,False,groq:llama-3.1-8b-instant,groq,llama-3.1-8b-instant,982.4,982.5,983.68,0.0,2.596,2.809,0.448,0.448,0.774,-1.262,2.972,-1.14,3.563,-1.14,3.634,-1.14,3.634,-1.14,1,1,0,0,0,0,0,1,0,0
6
+ 2026-06-16,IPCALAB.NS,INTRADAY,LOW,NEUTRAL,,0.64,13.4,False,groq:llama-3.1-8b-instant,groq,llama-3.1-8b-instant,1550.2,1536.25,1564.15,0.0,-0.742,2.296,4.825,0.0,2.174,-0.335,0.548,-1.135,3.8,-1.303,5.883,-1.303,2.174,-0.335,1,1,0,0,0,1,0,0,0,0
7
+ 2026-06-16,IPCALAB.NS,1D,MEDIUM,NEUTRAL,,0.64,13.4,False,huggingface:meta-llama/llama-3.1-8b-instruct,huggingface,meta-llama/llama-3.1-8b-instruct,1550.2,1471.14,1629.26,0.0,-0.742,2.296,4.825,-0.742,2.174,-0.335,0.548,-1.135,3.8,-1.303,5.883,-1.303,0.548,-1.135,1,1,0,0,0,1,0,0,0,0
8
+ 2026-06-16,IPCALAB.NS,3D,MEDIUM,NEUTRAL,,0.64,13.4,False,huggingface:meta-llama/llama-3.1-8b-instruct,huggingface,meta-llama/llama-3.1-8b-instruct,1550.2,1471.14,1629.26,0.0,-0.742,2.296,4.825,2.296,2.174,-0.335,0.548,-1.135,3.8,-1.303,5.883,-1.303,3.8,-1.303,1,1,0,0,0,1,0,0,0,0
9
+ 2026-06-16,IPCALAB.NS,5D,MEDIUM,NEUTRAL,,0.64,13.4,False,huggingface:meta-llama/llama-3.1-8b-instruct,huggingface,meta-llama/llama-3.1-8b-instruct,1550.2,1452.54,1647.86,0.0,-0.742,2.296,4.825,4.825,2.174,-0.335,0.548,-1.135,3.8,-1.303,5.883,-1.303,5.883,-1.303,1,1,0,0,0,1,0,0,0,0
10
+ 2026-06-16,POLYCAB.NS,INTRADAY,LOW,NEUTRAL,,0.75,13.4,False,huggingface:meta-llama/llama-3.1-8b-instruct,huggingface,meta-llama/llama-3.1-8b-instruct,9546.205,9460.29,9632.12,0.0,3.493,5.623,3.921,0.0,0.829,-1.215,4.129,0.089,6.011,0.089,6.074,0.089,0.829,-1.215,1,1,0,0,1,0,0,0,0,0
11
+ 2026-06-16,POLYCAB.NS,1D,MEDIUM,NEUTRAL,,0.75,13.4,False,huggingface:meta-llama/llama-3.1-8b-instruct,huggingface,meta-llama/llama-3.1-8b-instruct,9546.205,9059.35,10033.06,0.0,3.493,5.623,3.921,3.493,0.829,-1.215,4.129,0.089,6.011,0.089,6.074,0.089,4.129,0.089,1,0,0,0,1,0,0,0,0,0
12
+ 2026-06-16,POLYCAB.NS,3D,LOW,NEUTRAL,,0.75,13.4,False,huggingface:meta-llama/llama-3.1-8b-instruct,huggingface,meta-llama/llama-3.1-8b-instruct,9546.205,9059.35,10033.06,0.0,3.493,5.623,3.921,5.623,0.829,-1.215,4.129,0.089,6.011,0.089,6.074,0.089,6.011,0.089,0,0,0,0,1,0,0,0,0,0
13
+ 2026-06-16,POLYCAB.NS,5D,LOW,NEUTRAL,,0.75,13.4,False,groq:llama-3.1-8b-instant,groq,llama-3.1-8b-instant,9546.205,8944.79,10147.62,0.0,3.493,5.623,3.921,3.921,0.829,-1.215,4.129,0.089,6.011,0.089,6.074,0.089,6.074,0.089,1,0,0,0,1,0,0,0,0,0
14
+ 2026-06-16,DLF.NS,INTRADAY,LOW,NEUTRAL,,0.65,13.4,False,groq:llama-3.1-8b-instant,groq,llama-3.1-8b-instant,629.3,623.64,634.96,0.0,-0.914,-0.763,-2.693,0.0,0.429,-2.797,0.707,-1.764,2.336,-1.764,2.336,-3.059,0.429,-2.797,1,1,1,0,1,0,0,0,0,0
15
+ 2026-06-16,DLF.NS,1D,LOW,NEUTRAL,,0.65,13.4,False,groq:llama-3.1-8b-instant,groq,llama-3.1-8b-instant,629.3,597.21,661.39,0.0,-0.914,-0.763,-2.693,-0.914,0.429,-2.797,0.707,-1.764,2.336,-1.764,2.336,-3.059,0.707,-1.764,1,1,1,0,1,0,0,0,0,0
16
+ 2026-06-16,DLF.NS,3D,MEDIUM,NEUTRAL,,0.65,13.4,False,groq:llama-3.1-8b-instant,groq,llama-3.1-8b-instant,629.3,597.21,661.39,0.0,-0.914,-0.763,-2.693,-0.763,0.429,-2.797,0.707,-1.764,2.336,-1.764,2.336,-3.059,2.336,-1.764,1,1,1,0,1,0,0,0,0,0
17
+ 2026-06-16,DLF.NS,5D,LOW,NEUTRAL,,0.65,13.4,False,groq:llama-3.1-8b-instant,groq,llama-3.1-8b-instant,629.3,589.65,668.95,0.0,-0.914,-0.763,-2.693,-2.693,0.429,-2.797,0.707,-1.764,2.336,-1.764,2.336,-3.059,2.336,-3.059,1,1,1,0,1,0,0,0,0,0
18
+ 2026-06-16,SHRIRAMFIN.NS,INTRADAY,LOW,NEUTRAL,,0.71,13.4,False,huggingface:meta-llama/llama-3.1-8b-instruct,huggingface,meta-llama/llama-3.1-8b-instruct,1000.093,991.09,1009.09,0.0,0.174,-0.383,-1.233,0.0,0.428,-1.362,0.994,-0.378,1.148,-1.337,1.148,-2.182,0.428,-1.362,1,1,1,0,1,0,0,0,0,0
19
+ 2026-06-16,SHRIRAMFIN.NS,1D,MEDIUM,BULLISH,,0.71,13.4,False,huggingface:meta-llama/llama-3.1-8b-instruct,huggingface,meta-llama/llama-3.1-8b-instruct,1000.093,1000.19,1001.39,0.0,0.174,-0.383,-1.233,0.174,0.428,-1.362,0.994,-0.378,1.148,-1.337,1.148,-2.182,0.994,-0.378,1,1,1,0,1,0,0,0,0,0
20
+ 2026-06-16,SHRIRAMFIN.NS,3D,MEDIUM,BULLISH,,0.71,13.4,False,huggingface:meta-llama/llama-3.1-8b-instruct,huggingface,meta-llama/llama-3.1-8b-instruct,1000.093,1000.19,1001.39,0.0,0.174,-0.383,-1.233,-0.383,0.428,-1.362,0.994,-0.378,1.148,-1.337,1.148,-2.182,1.148,-1.337,1,1,1,0,1,0,0,0,0,0
21
+ 2026-06-16,SHRIRAMFIN.NS,5D,MEDIUM,BULLISH,,0.71,13.4,False,huggingface:meta-llama/llama-3.1-8b-instruct,huggingface,meta-llama/llama-3.1-8b-instruct,1000.093,1000.19,1001.39,0.0,0.174,-0.383,-1.233,-1.233,0.428,-1.362,0.994,-0.378,1.148,-1.337,1.148,-2.182,1.148,-2.182,1,1,1,0,1,0,0,0,0,0
22
+ 2026-06-16,AXISCADES.NS,INTRADAY,MEDIUM,BULLISH,,0.71,13.4,False,huggingface:meta-llama/llama-3.1-8b-instruct,huggingface,meta-llama/llama-3.1-8b-instruct,1825.3,1825.85,1829.13,0.0,4.996,6.459,0.904,0.0,5.057,-0.493,4.996,0.038,9.023,0.038,9.023,0.038,5.057,-0.493,1,1,0,0,0,1,1,0,0,0
23
+ 2026-06-16,AXISCADES.NS,1D,MEDIUM,BULLISH,,0.71,13.4,False,groq:llama-3.1-8b-instant,groq,llama-3.1-8b-instant,1825.3,1825.48,1827.67,0.0,4.996,6.459,0.904,4.996,5.057,-0.493,4.996,0.038,9.023,0.038,9.023,0.038,4.996,0.038,1,1,0,0,0,1,1,0,0,0
24
+ 2026-06-16,AXISCADES.NS,3D,MEDIUM,BULLISH,,0.71,13.4,False,groq:llama-3.1-8b-instant,groq,llama-3.1-8b-instant,1825.3,1825.48,1827.67,0.0,4.996,6.459,0.904,6.459,5.057,-0.493,4.996,0.038,9.023,0.038,9.023,0.038,9.023,0.038,1,1,0,0,0,1,1,0,0,0
25
+ 2026-06-16,AXISCADES.NS,5D,MEDIUM,BULLISH,,0.71,13.4,False,groq:llama-3.1-8b-instant,groq,llama-3.1-8b-instant,1825.3,1825.48,1827.67,0.0,4.996,6.459,0.904,0.904,5.057,-0.493,4.996,0.038,9.023,0.038,9.023,0.038,9.023,0.038,1,1,0,0,0,1,1,0,0,0
26
+ 2026-06-19,HINDALCO.NS,INTRADAY,LOW,NEUTRAL,,0.74,13.0,False,groq:llama-3.1-8b-instant,groq,llama-3.1-8b-instant,1010.0,1000.91,1019.09,0.0,0.416,-3.307,-5.624,0.0,0.673,-2.455,0.802,-0.634,0.802,-3.931,0.802,-5.921,0.673,-2.455,1,1,0,0,0,0,0,1,0,0
27
+ 2026-06-19,HINDALCO.NS,1D,MEDIUM,NEUTRAL,,0.74,13.0,False,groq:llama-3.1-8b-instant,groq,llama-3.1-8b-instant,1010.0,958.49,1061.51,0.0,0.416,-3.307,-5.624,0.416,0.673,-2.455,0.802,-0.634,0.802,-3.931,0.802,-5.921,0.802,-0.634,1,1,0,0,0,0,0,1,0,0
28
+ 2026-06-19,HINDALCO.NS,3D,MEDIUM,NEUTRAL,,0.74,13.0,False,huggingface:meta-llama/llama-3.1-8b-instruct,huggingface,meta-llama/llama-3.1-8b-instruct,1010.0,958.49,1061.51,0.0,0.416,-3.307,-5.624,-3.307,0.673,-2.455,0.802,-0.634,0.802,-3.931,0.802,-5.921,0.802,-3.931,1,1,0,0,0,0,0,1,0,0
29
+ 2026-06-19,HINDALCO.NS,5D,MEDIUM,NEUTRAL,,0.74,13.0,False,huggingface:meta-llama/llama-3.1-8b-instruct,huggingface,meta-llama/llama-3.1-8b-instruct,1010.0,946.37,1073.63,0.0,0.416,-3.307,-5.624,-5.624,0.673,-2.455,0.802,-0.634,0.802,-3.931,0.802,-5.921,0.802,-5.921,1,1,0,0,0,0,0,1,0,0
30
+ 2026-06-19,IPCALAB.NS,INTRADAY,LOW,NEUTRAL,,0.71,13.0,False,huggingface:meta-llama/llama-3.1-8b-instruct,huggingface,meta-llama/llama-3.1-8b-instruct,1585.8,1571.53,1600.07,0.0,0.725,2.232,2.049,0.0,1.469,-2.73,1.204,-0.214,3.506,-0.214,4.364,-0.214,1.469,-2.73,1,1,0,0,0,0,0,0,0,0
31
+ 2026-06-19,IPCALAB.NS,1D,MEDIUM,BULLISH,,0.71,13.0,False,huggingface:meta-llama/llama-3.1-8b-instruct,huggingface,meta-llama/llama-3.1-8b-instruct,1585.8,1585.96,1587.86,0.0,0.725,2.232,2.049,0.725,1.469,-2.73,1.204,-0.214,3.506,-0.214,4.364,-0.214,1.204,-0.214,1,1,0,0,0,0,0,0,0,0
32
+ 2026-06-19,IPCALAB.NS,3D,MEDIUM,BULLISH,,0.71,13.0,False,huggingface:meta-llama/llama-3.1-8b-instruct,huggingface,meta-llama/llama-3.1-8b-instruct,1585.8,1585.96,1587.86,0.0,0.725,2.232,2.049,2.232,1.469,-2.73,1.204,-0.214,3.506,-0.214,4.364,-0.214,3.506,-0.214,1,1,0,0,0,0,0,0,0,0
33
+ 2026-06-19,IPCALAB.NS,5D,MEDIUM,NEUTRAL,,0.71,13.0,False,huggingface:meta-llama/llama-3.1-8b-instruct,huggingface,meta-llama/llama-3.1-8b-instruct,1585.8,1485.89,1685.71,0.0,0.725,2.232,2.049,2.049,1.469,-2.73,1.204,-0.214,3.506,-0.214,4.364,-0.214,4.364,-0.214,1,1,0,0,0,0,0,0,0,0
34
+ 2026-06-19,POLYCAB.NS,INTRADAY,MEDIUM,BULLISH,,0.72,13.0,False,groq:llama-3.1-8b-instant,groq,llama-3.1-8b-instant,10083.0,10086.02,10104.17,0.0,-0.6,-3.873,-5.475,0.0,0.367,-2.499,0.426,-0.903,0.426,-4.046,0.426,-5.618,0.367,-2.499,1,1,1,0,1,0,0,0,0,0
35
+ 2026-06-19,POLYCAB.NS,1D,LOW,NEUTRAL,,0.72,13.0,False,groq:llama-3.1-8b-instant,groq,llama-3.1-8b-instant,10083.0,9568.77,10597.23,0.0,-0.6,-3.873,-5.475,-0.6,0.367,-2.499,0.426,-0.903,0.426,-4.046,0.426,-5.618,0.426,-0.903,1,1,1,0,1,0,0,0,0,0
36
+ 2026-06-19,POLYCAB.NS,3D,MEDIUM,BULLISH,,0.72,13.0,False,groq:llama-3.1-8b-instant,groq,llama-3.1-8b-instant,10083.0,10084.01,10096.11,0.0,-0.6,-3.873,-5.475,-3.873,0.367,-2.499,0.426,-0.903,0.426,-4.046,0.426,-5.618,0.426,-4.046,1,1,1,0,1,0,0,0,0,0
37
+ 2026-06-19,POLYCAB.NS,5D,LOW,NEUTRAL,,0.72,13.0,False,groq:llama-3.1-8b-instant,groq,llama-3.1-8b-instant,10083.0,9447.77,10718.23,0.0,-0.6,-3.873,-5.475,-5.475,0.367,-2.499,0.426,-0.903,0.426,-4.046,0.426,-5.618,0.426,-5.618,1,1,1,0,1,0,0,0,0,0
38
+ 2026-06-19,DLF.NS,INTRADAY,MEDIUM,BULLISH,,0.71,13.0,False,groq:llama-3.1-8b-instant,groq,llama-3.1-8b-instant,624.5,624.69,625.81,0.0,0.496,-1.017,-0.504,0.0,2.322,-0.777,1.169,0.096,1.906,-2.418,1.906,-2.418,2.322,-0.777,1,1,1,1,0,0,0,0,0,0
39
+ 2026-06-19,DLF.NS,1D,MEDIUM,BULLISH,,0.71,13.0,False,huggingface:meta-llama/llama-3.1-8b-instruct,huggingface,meta-llama/llama-3.1-8b-instruct,624.5,624.56,625.31,0.0,0.496,-1.017,-0.504,0.496,2.322,-0.777,1.169,0.096,1.906,-2.418,1.906,-2.418,1.169,0.096,1,1,1,1,0,0,0,0,0,0
40
+ 2026-06-19,DLF.NS,3D,MEDIUM,BULLISH,,0.71,13.0,False,huggingface:meta-llama/llama-3.1-8b-instruct,huggingface,meta-llama/llama-3.1-8b-instruct,624.5,624.56,625.31,0.0,0.496,-1.017,-0.504,-1.017,2.322,-0.777,1.169,0.096,1.906,-2.418,1.906,-2.418,1.906,-2.418,1,1,1,1,0,0,0,0,0,0
41
+ 2026-06-19,DLF.NS,5D,MEDIUM,BULLISH,,0.71,13.0,False,huggingface:meta-llama/llama-3.1-8b-instruct,huggingface,meta-llama/llama-3.1-8b-instruct,624.5,624.56,625.31,0.0,0.496,-1.017,-0.504,-0.504,2.322,-0.777,1.169,0.096,1.906,-2.418,1.906,-2.418,1.906,-2.418,1,1,1,1,0,0,0,0,0,0
42
+ 2026-06-19,SHRIRAMFIN.NS,INTRADAY,MEDIUM,BULLISH,,0.71,13.0,False,huggingface:meta-llama/llama-3.1-8b-instruct,huggingface,meta-llama/llama-3.1-8b-instruct,996.265,996.56,998.36,0.0,-0.903,1.707,2.984,0.0,0.579,-0.958,0.384,-1.807,2.74,-2.131,5.0,-2.131,0.579,-0.958,1,1,1,1,0,0,0,0,0,0
43
+ 2026-06-19,SHRIRAMFIN.NS,1D,MEDIUM,BULLISH,,0.71,13.0,False,huggingface:meta-llama/llama-3.1-8b-instruct,huggingface,meta-llama/llama-3.1-8b-instruct,996.265,996.36,997.56,0.0,-0.903,1.707,2.984,-0.903,0.579,-0.958,0.384,-1.807,2.74,-2.131,5.0,-2.131,0.384,-1.807,1,1,1,1,0,0,0,0,0,0
44
+ 2026-06-19,SHRIRAMFIN.NS,3D,MEDIUM,BULLISH,,0.71,13.0,False,huggingface:meta-llama/llama-3.1-8b-instruct,huggingface,meta-llama/llama-3.1-8b-instruct,996.265,996.36,997.56,0.0,-0.903,1.707,2.984,1.707,0.579,-0.958,0.384,-1.807,2.74,-2.131,5.0,-2.131,2.74,-2.131,1,1,1,1,0,0,0,0,0,0
45
+ 2026-06-19,SHRIRAMFIN.NS,5D,MEDIUM,BULLISH,,0.71,13.0,False,groq:llama-3.1-8b-instant,groq,llama-3.1-8b-instant,996.265,996.36,997.56,0.0,-0.903,1.707,2.984,2.984,0.579,-0.958,0.384,-1.807,2.74,-2.131,5.0,-2.131,5.0,-2.131,1,1,1,1,0,0,0,0,0,0
46
+ 2026-06-19,AXISCADES.NS,INTRADAY,MEDIUM,BULLISH,,0.78,13.0,False,groq:llama-3.1-8b-instant,groq,llama-3.1-8b-instant,1943.2,1943.78,1947.28,0.0,-3.355,-9.536,-12.979,0.0,1.379,-1.662,0.098,-3.767,0.098,-9.953,0.098,-13.601,1.379,-1.662,1,1,1,1,0,0,0,0,0,0
47
+ 2026-06-19,AXISCADES.NS,1D,MEDIUM,BULLISH,,0.78,13.0,False,groq:llama-3.1-8b-instant,groq,llama-3.1-8b-instant,1943.2,1943.39,1945.73,0.0,-3.355,-9.536,-12.979,-3.355,1.379,-1.662,0.098,-3.767,0.098,-9.953,0.098,-13.601,0.098,-3.767,1,1,1,1,0,0,0,0,0,0
48
+ 2026-06-19,AXISCADES.NS,3D,MEDIUM,BULLISH,,0.78,13.0,False,groq:llama-3.1-8b-instant,groq,llama-3.1-8b-instant,1943.2,1943.39,1945.73,0.0,-3.355,-9.536,-12.979,-9.536,1.379,-1.662,0.098,-3.767,0.098,-9.953,0.098,-13.601,0.098,-9.953,1,1,1,1,0,0,0,0,0,0
49
+ 2026-06-19,AXISCADES.NS,5D,MEDIUM,BULLISH,,0.78,13.0,False,groq:llama-3.1-8b-instant,groq,llama-3.1-8b-instant,1943.2,1943.39,1945.73,0.0,-3.355,-9.536,-12.979,-12.979,1.379,-1.662,0.098,-3.767,0.098,-9.953,0.098,-13.601,0.098,-13.601,1,1,1,1,0,0,0,0,0,0
50
+ 2026-06-22,HINDALCO.NS,INTRADAY,MEDIUM,BEARISH,,0.64,12.8,False,groq:llama-3.1-8b-instant,groq,llama-3.1-8b-instant,1014.2,1012.98,1013.79,0.0,-2.702,-6.015,-4.969,0.0,0.385,-1.045,-1.499,-3.569,-1.499,-6.31,-1.499,-6.31,0.385,-1.045,1,1,0,0,0,0,0,1,0,0
51
+ 2026-06-22,HINDALCO.NS,1D,LOW,NEUTRAL,,0.64,12.8,False,groq:llama-3.1-8b-instant,groq,llama-3.1-8b-instant,1014.2,962.48,1065.92,0.0,-2.702,-6.015,-4.969,-2.702,0.385,-1.045,-1.499,-3.569,-1.499,-6.31,-1.499,-6.31,-1.499,-3.569,1,0,0,0,0,0,0,1,0,0
52
+ 2026-06-22,HINDALCO.NS,3D,MEDIUM,NEUTRAL,,0.64,12.8,False,groq:llama-3.1-8b-instant,groq,llama-3.1-8b-instant,1014.2,962.48,1065.92,0.0,-2.702,-6.015,-4.969,-6.015,0.385,-1.045,-1.499,-3.569,-1.499,-6.31,-1.499,-6.31,-1.499,-6.31,0,0,0,0,0,0,0,1,0,0
53
+ 2026-06-22,HINDALCO.NS,5D,MEDIUM,NEUTRAL,,0.64,12.8,False,groq:llama-3.1-8b-instant,groq,llama-3.1-8b-instant,1014.2,950.31,1078.09,0.0,-2.702,-6.015,-4.969,-4.969,0.385,-1.045,-1.499,-3.569,-1.499,-6.31,-1.499,-6.31,-1.499,-6.31,1,0,0,0,0,0,0,1,0,0
54
+ 2026-06-22,IPCALAB.NS,INTRADAY,LOW,NEUTRAL,,0.71,12.8,False,groq:llama-3.1-8b-instant,groq,llama-3.1-8b-instant,1597.3,1582.92,1611.68,0.0,1.734,1.315,3.124,0.0,0.476,-0.933,2.761,0.163,3.612,0.163,4.232,0.163,0.476,-0.933,1,1,0,0,1,0,0,0,0,0
55
+ 2026-06-22,IPCALAB.NS,1D,LOW,NEUTRAL,,0.71,12.8,False,groq:llama-3.1-8b-instant,groq,llama-3.1-8b-instant,1597.3,1515.84,1678.76,0.0,1.734,1.315,3.124,1.734,0.476,-0.933,2.761,0.163,3.612,0.163,4.232,0.163,2.761,0.163,1,0,0,0,1,0,0,0,0,0
56
+ 2026-06-22,IPCALAB.NS,3D,LOW,NEUTRAL,,0.71,12.8,False,groq:llama-3.1-8b-instant,groq,llama-3.1-8b-instant,1597.3,1515.84,1678.76,0.0,1.734,1.315,3.124,1.315,0.476,-0.933,2.761,0.163,3.612,0.163,4.232,0.163,3.612,0.163,1,0,0,0,1,0,0,0,0,0
57
+ 2026-06-22,IPCALAB.NS,5D,LOW,NEUTRAL,,0.71,12.8,False,groq:llama-3.1-8b-instant,groq,llama-3.1-8b-instant,1597.3,1496.67,1697.93,0.0,1.734,1.315,3.124,3.124,0.476,-0.933,2.761,0.163,3.612,0.163,4.232,0.163,4.232,0.163,1,0,0,0,1,0,0,0,0,0
58
+ 2026-06-22,POLYCAB.NS,INTRADAY,MEDIUM,BEARISH,,0.68,12.8,False,groq:llama-3.1-8b-instant,groq,llama-3.1-8b-instant,10022.5,10010.47,10018.49,0.0,-1.018,-4.904,-2.43,0.0,1.033,-0.304,0.524,-1.307,0.524,-5.049,0.524,-6.006,1.033,-0.304,1,1,1,0,0,0,0,0,0,0
59
+ 2026-06-22,POLYCAB.NS,1D,LOW,NEUTRAL,,0.68,12.8,False,groq:llama-3.1-8b-instant,groq,llama-3.1-8b-instant,10022.5,9511.35,10533.65,0.0,-1.018,-4.904,-2.43,-1.018,1.033,-0.304,0.524,-1.307,0.524,-5.049,0.524,-6.006,0.524,-1.307,1,1,1,0,0,0,0,0,0,0
60
+ 2026-06-22,POLYCAB.NS,3D,MEDIUM,NEUTRAL,,0.68,12.8,False,groq:llama-3.1-8b-instant,groq,llama-3.1-8b-instant,10022.5,9511.35,10533.65,0.0,-1.018,-4.904,-2.43,-4.904,1.033,-0.304,0.524,-1.307,0.524,-5.049,0.524,-6.006,0.524,-5.049,1,1,1,0,0,0,0,0,0,0
61
+ 2026-06-22,POLYCAB.NS,5D,LOW,NEUTRAL,,0.68,12.8,False,groq:llama-3.1-8b-instant,groq,llama-3.1-8b-instant,10022.5,9391.08,10653.92,0.0,-1.018,-4.904,-2.43,-2.43,1.033,-0.304,0.524,-1.307,0.524,-5.049,0.524,-6.006,0.524,-6.006,1,1,1,0,0,0,0,0,0,0
62
+ 2026-06-22,DLF.NS,INTRADAY,MEDIUM,BULLISH,,0.71,12.8,False,groq:llama-3.1-8b-instant,groq,llama-3.1-8b-instant,627.6,627.79,628.92,0.0,-2.43,-0.996,-2.047,0.0,0.669,-0.398,1.402,-2.796,1.402,-2.9,1.402,-2.9,0.669,-0.398,1,1,1,1,0,0,0,0,0,0
63
+ 2026-06-22,DLF.NS,1D,MEDIUM,BULLISH,,0.71,12.8,False,groq:llama-3.1-8b-instant,groq,llama-3.1-8b-instant,627.6,627.66,628.42,0.0,-2.43,-0.996,-2.047,-2.43,0.669,-0.398,1.402,-2.796,1.402,-2.9,1.402,-2.9,1.402,-2.796,1,1,1,1,0,0,0,0,0,0
64
+ 2026-06-22,DLF.NS,3D,MEDIUM,BULLISH,,0.71,12.8,False,groq:llama-3.1-8b-instant,groq,llama-3.1-8b-instant,627.6,627.66,628.42,0.0,-2.43,-0.996,-2.047,-0.996,0.669,-0.398,1.402,-2.796,1.402,-2.9,1.402,-2.9,1.402,-2.9,1,1,1,1,0,0,0,0,0,0
65
+ 2026-06-22,DLF.NS,5D,MEDIUM,BULLISH,,0.71,12.8,False,groq:llama-3.1-8b-instant,groq,llama-3.1-8b-instant,627.6,627.66,628.42,0.0,-2.43,-0.996,-2.047,-2.047,0.669,-0.398,1.402,-2.796,1.402,-2.9,1.402,-2.9,1.402,-2.9,1,1,1,1,0,0,0,0,0,0
66
+ 2026-06-22,SHRIRAMFIN.NS,INTRADAY,MEDIUM,BULLISH,,0.78,12.8,False,groq:llama-3.1-8b-instant,groq,llama-3.1-8b-instant,987.265,987.56,989.34,0.0,0.05,3.923,4.009,0.0,1.299,-0.912,1.496,-0.841,5.958,-1.239,6.3,-1.239,1.299,-0.912,1,1,1,1,0,0,1,0,0,0
67
+ 2026-06-22,SHRIRAMFIN.NS,1D,MEDIUM,BULLISH,,0.78,12.8,False,groq:llama-3.1-8b-instant,groq,llama-3.1-8b-instant,987.265,987.36,988.55,0.0,0.05,3.923,4.009,0.05,1.299,-0.912,1.496,-0.841,5.958,-1.239,6.3,-1.239,1.496,-0.841,1,1,1,1,0,0,1,0,0,0
68
+ 2026-06-22,SHRIRAMFIN.NS,3D,MEDIUM,BULLISH,,0.78,12.8,False,groq:llama-3.1-8b-instant,groq,llama-3.1-8b-instant,987.265,987.36,988.55,0.0,0.05,3.923,4.009,3.923,1.299,-0.912,1.496,-0.841,5.958,-1.239,6.3,-1.239,5.958,-1.239,1,1,1,1,0,0,1,0,0,0
69
+ 2026-06-22,SHRIRAMFIN.NS,5D,MEDIUM,BULLISH,,0.78,12.8,False,groq:llama-3.1-8b-instant,groq,llama-3.1-8b-instant,987.265,987.36,988.55,0.0,0.05,3.923,4.009,4.009,1.299,-0.912,1.496,-0.841,5.958,-1.239,6.3,-1.239,6.3,-1.239,1,1,1,1,0,0,1,0,0,0
70
+ 2026-06-22,AXISCADES.NS,INTRADAY,MEDIUM,BULLISH,,0.71,12.8,False,groq:llama-3.3-70b-versatile,groq,llama-3.3-70b-versatile,1878.0,1878.56,1881.94,0.0,-1.928,-9.957,-9.526,0.0,3.573,-0.426,1.587,-2.391,1.587,-10.602,1.587,-12.875,3.573,-0.426,1,1,1,0,0,0,0,0,0,0
71
+ 2026-06-22,AXISCADES.NS,1D,MEDIUM,BULLISH,,0.71,12.8,False,groq:llama-3.1-8b-instant,groq,llama-3.1-8b-instant,1878.0,1878.19,1880.44,0.0,-1.928,-9.957,-9.526,-1.928,3.573,-0.426,1.587,-2.391,1.587,-10.602,1.587,-12.875,1.587,-2.391,1,1,1,0,0,0,0,0,0,0
72
+ 2026-06-22,AXISCADES.NS,3D,MEDIUM,BULLISH,,0.71,12.8,False,groq:llama-3.1-8b-instant,groq,llama-3.1-8b-instant,1878.0,1878.19,1880.44,0.0,-1.928,-9.957,-9.526,-9.957,3.573,-0.426,1.587,-2.391,1.587,-10.602,1.587,-12.875,1.587,-10.602,1,1,1,0,0,0,0,0,0,0
73
+ 2026-06-22,AXISCADES.NS,5D,MEDIUM,BULLISH,,0.71,12.8,False,groq:llama-3.1-8b-instant,groq,llama-3.1-8b-instant,1878.0,1878.19,1880.44,0.0,-1.928,-9.957,-9.526,-9.526,3.573,-0.426,1.587,-2.391,1.587,-10.602,1.587,-12.875,1.587,-12.875,1,1,1,0,0,0,0,0,0,0
research/backtest.py CHANGED
@@ -49,6 +49,13 @@ VIX_TK = "^INDIAVIX"
49
  STEP = 60 # every 60 trading days → ~22 dates × 15 tickers × 3 TFs = ~990 work items
50
  WORKERS = 1 # single worker — avoids 429 burst; rate limiter still controls pace
51
 
 
 
 
 
 
 
 
52
  TIMEFRAMES = ["INTRADAY", "1D", "3D", "5D"]
53
  _TF_COL = {"INTRADAY": "ret_intraday", "1D": "ret_1d", "3D": "ret_3d", "5D": "ret_5d"}
54
 
@@ -312,6 +319,63 @@ def _compute_indicators(sc_tk, sh_tk, sl_tk, sv_tk, date):
312
  return inds
313
 
314
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
315
  # ── MAIN BACKTEST ──────────────────────────────────────────────────────────────
316
 
317
  def _build_indicator_snapshots(sc, sh, sl, sv, nc, vc):
@@ -618,6 +682,7 @@ def run_backtest(work_items: list[dict], csv_path: str | None = None, limit_work
618
  w["tf"],
619
  ret_tf,
620
  )
 
621
  return {
622
  "date": str(w["date"].date()),
623
  "ticker": w["ticker"],
@@ -651,6 +716,7 @@ def run_backtest(work_items: list[dict], csv_path: str | None = None, limit_work
651
  "min_down_for_tf": round(min_down_tf, 3),
652
  "intraday_hit_for_tf": int(direction_hit),
653
  "target_hit_for_tf": int(target_hit),
 
654
  }
655
  except Exception as e:
656
  print(f" SKIP {w['ticker']} @ {w['date']} [{w['tf']}]: {e}")
@@ -770,6 +836,54 @@ def print_results(df: pd.DataFrame) -> dict:
770
  for k, v in provider_counts.items():
771
  print(f" - {k}: {v}")
772
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
773
  _write_calibration_artifact(df)
774
  _sep()
775
  return acc
@@ -829,10 +943,26 @@ if __name__ == "__main__":
829
  p.add_argument("--timeframes", nargs="+", choices=TIMEFRAMES, help="Run only selected timeframe(s)")
830
  p.add_argument("--csv-out", default=os.path.join(os.path.dirname(__file__), "ai_prompt_accuracy.csv"), help="Output CSV path")
831
  p.add_argument("--limit-work-items", type=int, default=0, help="Run only first N work items (quick smoke runs)")
 
 
 
 
832
  args = p.parse_args()
833
 
834
  csv_path = args.csv_out
835
 
 
 
 
 
 
 
 
 
 
 
 
 
836
  if args.print_only:
837
  if not os.path.exists(csv_path):
838
  print("No CSV found."); sys.exit(1)
@@ -842,11 +972,23 @@ if __name__ == "__main__":
842
  print_results(df)
843
  else:
844
  _sep()
845
- print("LLM Backtest 1D/3D/5D | fast mode (1 call/prediction) | actual NSE data 2024–2025")
 
846
  _sep()
847
- sc, sh, sl, sv, nc, vc = fetch_data(LLM_UNIVERSE, DATA_START, END)
 
 
 
 
 
 
 
848
  indicator_cache = _build_indicator_snapshots(sc, sh, sl, sv, nc, vc)
849
  work_items = build_work_items(sc, sh, sl, sv, nc, vc, feature_cache=indicator_cache)
 
 
 
 
850
  if args.timeframes:
851
  selected = set(args.timeframes)
852
  work_items = [item for item in work_items if item["tf"] in selected]
 
49
  STEP = 60 # every 60 trading days → ~22 dates × 15 tickers × 3 TFs = ~990 work items
50
  WORKERS = 1 # single worker — avoids 429 burst; rate limiter still controls pace
51
 
52
+ # ── HOLD-OUT SPLIT ────────────────────────────────────────────────────────────
53
+ # Training window: START → TRAIN_END (loop_backtest.py optimizes here)
54
+ # Hold-out window: HOLDOUT_START → HOLDOUT_END (--eval flag; never used for optimization)
55
+ TRAIN_END = "2024-12-31"
56
+ HOLDOUT_START = "2025-01-01"
57
+ HOLDOUT_END = "2025-06-01"
58
+
59
  TIMEFRAMES = ["INTRADAY", "1D", "3D", "5D"]
60
  _TF_COL = {"INTRADAY": "ret_intraday", "1D": "ret_1d", "3D": "ret_3d", "5D": "ret_5d"}
61
 
 
319
  return inds
320
 
321
 
322
+ # ── TRIGGER FLAG EVALUATION ───────────────────────────────────────────────────
323
+
324
+ def _compute_trigger_flags(inds: dict, price: float) -> dict:
325
+ """Evaluate which synthesis-prompt triggers fired for a given indicator snapshot.
326
+
327
+ Uses the 1D trigger conditions as the canonical set (they represent all TFs).
328
+ Adds 9 boolean columns: trigger_T1 … trigger_T7, trigger_B1, trigger_B2.
329
+ """
330
+ rsi = inds.get("rsi14", 50.0)
331
+ bb = inds.get("bb_pct", 50.0)
332
+ r10 = inds.get("return_10d", 0.0)
333
+ r20 = inds.get("return_20d", 0.0)
334
+ macd = inds.get("macd_signal", 0.0)
335
+ ema50 = inds.get("ema50")
336
+ ema200 = inds.get("ema200")
337
+
338
+ above_ema50 = (ema50 is not None) and (price > ema50)
339
+ above_ema200 = (ema200 is not None) and (price > ema200)
340
+
341
+ streak = inds.get("consec_days", "")
342
+ consec_up = 0
343
+ if isinstance(streak, str) and streak.startswith("+"):
344
+ try: consec_up = int(streak.split()[0].lstrip("+"))
345
+ except Exception: pass
346
+
347
+ T1 = bool(above_ema50 and macd > 0)
348
+ T2 = bool(above_ema50 and r10 > 3.0 and bb < 85.0)
349
+ T3 = bool(above_ema50 and consec_up >= 3 and r20 > 0.0)
350
+ T4 = bool(rsi < 50 and bb < 45.0 and r10 > -2.0) # widened from rsi<46 bb<38
351
+ T5 = bool(r10 > 7.0 and bb < 80.0)
352
+ T6 = bool(rsi < 44 and bb < 35.0)
353
+ T7 = bool(above_ema50 and 1.0 <= r20 <= 5.0 and rsi < 62.0) # slow positive drift
354
+
355
+ # B1 removed from production (overbought reversal); kept as placeholder False for CSV schema stability
356
+ B1 = False
357
+ B2 = bool(
358
+ (not above_ema50)
359
+ and (not above_ema200)
360
+ and macd < 0
361
+ and r10 < -5.0
362
+ and rsi > 50
363
+ and bb > 40.0
364
+ )
365
+
366
+ return {
367
+ "trigger_T1": int(T1),
368
+ "trigger_T2": int(T2),
369
+ "trigger_T3": int(T3),
370
+ "trigger_T4": int(T4),
371
+ "trigger_T5": int(T5),
372
+ "trigger_T6": int(T6),
373
+ "trigger_T7": int(T7),
374
+ "trigger_B1": int(B1),
375
+ "trigger_B2": int(B2),
376
+ }
377
+
378
+
379
  # ── MAIN BACKTEST ──────────────────────────────────────────────────────────────
380
 
381
  def _build_indicator_snapshots(sc, sh, sl, sv, nc, vc):
 
682
  w["tf"],
683
  ret_tf,
684
  )
685
+ trigger_flags = _compute_trigger_flags(w.get("inds", {}), w["price"])
686
  return {
687
  "date": str(w["date"].date()),
688
  "ticker": w["ticker"],
 
716
  "min_down_for_tf": round(min_down_tf, 3),
717
  "intraday_hit_for_tf": int(direction_hit),
718
  "target_hit_for_tf": int(target_hit),
719
+ **trigger_flags,
720
  }
721
  except Exception as e:
722
  print(f" SKIP {w['ticker']} @ {w['date']} [{w['tf']}]: {e}")
 
836
  for k, v in provider_counts.items():
837
  print(f" - {k}: {v}")
838
 
839
+ # TABLE 4 — Per-trigger accuracy breakdown (only if columns present in CSV)
840
+ _trigger_cols = [c for c in ["trigger_T1","trigger_T2","trigger_T3","trigger_T4","trigger_T5","trigger_T6","trigger_T7","trigger_B2"] if c in df.columns]
841
+ if _trigger_cols:
842
+ _sep()
843
+ print("TABLE 4 — Per-Trigger Accuracy Breakdown (how often predictions that fired each trigger were correct)")
844
+ _sep()
845
+ print(f" {'Trigger':<12} {'Fired':>7} {'Correct':>9} {'HitRate':>9} Direction")
846
+ _sep("-")
847
+ _dir_df = df[df["direction"].isin(["BULLISH", "BEARISH"])].copy()
848
+ _bull_triggers = ["trigger_T1","trigger_T2","trigger_T3","trigger_T4","trigger_T5","trigger_T6","trigger_T7"]
849
+ _bear_triggers = ["trigger_B2"]
850
+ for col in _trigger_cols:
851
+ fired = _dir_df[_dir_df[col] == 1]
852
+ if len(fired) < 3:
853
+ continue
854
+ n_fired = len(fired)
855
+ n_correct = int(fired["intraday_hit_for_tf"].sum())
856
+ rate = n_correct / n_fired * 100
857
+ dirn = "BULLISH" if col in _bull_triggers else "BEARISH"
858
+ print(f" {col:<12} {n_fired:>7} {n_correct:>9} {rate:>8.1f}% {dirn}")
859
+
860
+ # TABLE 5 — Per-regime accuracy slice (requires nifty_ok and vix columns)
861
+ if "nifty_ok" in df.columns and "vix" in df.columns:
862
+ _sep()
863
+ print("TABLE 5 — Per-Regime Accuracy Slice (directional predictions only)")
864
+ _sep()
865
+ print(f" {'Regime':<22} {'N':>6} {'DirHit':>9} {'TgtHit':>9}")
866
+ _sep("-")
867
+ _dir_df2 = df[df["direction"].isin(["BULLISH","BEARISH"])].copy()
868
+ _dir_df2["vix_band"] = pd.cut(_dir_df2["vix"], bins=[0, 15, 20, 100], labels=["VIX<15","VIX 15-20","VIX>20"])
869
+ for regime_label, mask in [
870
+ ("Nifty Bull (above EMA200)", _dir_df2["nifty_ok"] == True),
871
+ ("Nifty Bear (below EMA200)", _dir_df2["nifty_ok"] == False),
872
+ ]:
873
+ sub = _dir_df2[mask]
874
+ if len(sub) < 3:
875
+ continue
876
+ dh = sub["intraday_hit_for_tf"].mean() * 100
877
+ th = sub["target_hit_for_tf"].mean() * 100
878
+ print(f" {regime_label:<22} {len(sub):>6} {dh:>8.1f}% {th:>8.1f}%")
879
+ for band in ["VIX<15","VIX 15-20","VIX>20"]:
880
+ sub = _dir_df2[_dir_df2["vix_band"] == band]
881
+ if len(sub) < 3:
882
+ continue
883
+ dh = sub["intraday_hit_for_tf"].mean() * 100
884
+ th = sub["target_hit_for_tf"].mean() * 100
885
+ print(f" {band:<22} {len(sub):>6} {dh:>8.1f}% {th:>8.1f}%")
886
+
887
  _write_calibration_artifact(df)
888
  _sep()
889
  return acc
 
943
  p.add_argument("--timeframes", nargs="+", choices=TIMEFRAMES, help="Run only selected timeframe(s)")
944
  p.add_argument("--csv-out", default=os.path.join(os.path.dirname(__file__), "ai_prompt_accuracy.csv"), help="Output CSV path")
945
  p.add_argument("--limit-work-items", type=int, default=0, help="Run only first N work items (quick smoke runs)")
946
+ p.add_argument("--eval", action="store_true",
947
+ help=f"Run on hold-out dates only ({HOLDOUT_START} → {HOLDOUT_END}) — never used for prompt optimization")
948
+ p.add_argument("--start", default=None, help="Override start date (YYYY-MM-DD)")
949
+ p.add_argument("--end", default=None, help="Override end date (YYYY-MM-DD)")
950
  args = p.parse_args()
951
 
952
  csv_path = args.csv_out
953
 
954
+ # Resolve date range
955
+ if args.eval:
956
+ _run_start = HOLDOUT_START
957
+ _run_end = HOLDOUT_END
958
+ _eval_csv = csv_path.replace(".csv", "_holdout_eval.csv")
959
+ csv_path = _eval_csv
960
+ print(f" *** HOLD-OUT EVAL MODE: {_run_start} → {_run_end} ***")
961
+ print(f" Output: {csv_path}")
962
+ else:
963
+ _run_start = args.start or START
964
+ _run_end = args.end or END
965
+
966
  if args.print_only:
967
  if not os.path.exists(csv_path):
968
  print("No CSV found."); sys.exit(1)
 
972
  print_results(df)
973
  else:
974
  _sep()
975
+ mode_label = "HOLD-OUT EVAL" if args.eval else "TRAINING"
976
+ print(f"LLM Backtest [{mode_label}] — {_run_start} → {_run_end} | fast mode | actual NSE data")
977
  _sep()
978
+
979
+ # Temporarily override module-level START/END so build_work_items uses the right range
980
+ _g = globals()
981
+ _orig_start, _orig_end = _g["START"], _g["END"]
982
+ _g["START"] = _run_start
983
+ _g["END"] = _run_end
984
+
985
+ sc, sh, sl, sv, nc, vc = fetch_data(LLM_UNIVERSE, DATA_START, _run_end)
986
  indicator_cache = _build_indicator_snapshots(sc, sh, sl, sv, nc, vc)
987
  work_items = build_work_items(sc, sh, sl, sv, nc, vc, feature_cache=indicator_cache)
988
+
989
+ _g["START"] = _orig_start
990
+ _g["END"] = _orig_end
991
+
992
  if args.timeframes:
993
  selected = set(args.timeframes)
994
  work_items = [item for item in work_items if item["tf"] in selected]
research/confidence_calibration.json CHANGED
@@ -1,26 +1,33 @@
1
  {
2
- "generated_rows": 672,
3
  "timeframes": {
 
 
 
 
 
 
 
4
  "1D": {
5
- "n_total": 232,
6
- "high_rate_pct": 7.8,
7
- "high_hit_pct": 83.3,
8
- "medium_hit_pct": 76.2,
9
- "recommendation": "hold"
10
  },
11
  "3D": {
12
- "n_total": 232,
13
- "high_rate_pct": 9.5,
14
- "high_hit_pct": 100.0,
15
- "medium_hit_pct": 82.6,
16
- "recommendation": "hold"
17
  },
18
  "5D": {
19
- "n_total": 208,
20
- "high_rate_pct": 13.9,
21
- "high_hit_pct": 96.6,
22
- "medium_hit_pct": 78.4,
23
- "recommendation": "hold"
24
  }
25
  }
26
  }
 
1
  {
2
+ "generated_rows": 72,
3
  "timeframes": {
4
+ "INTRADAY": {
5
+ "n_total": 18,
6
+ "high_rate_pct": 0.0,
7
+ "high_hit_pct": null,
8
+ "medium_hit_pct": 100.0,
9
+ "recommendation": "promote_medium_to_high"
10
+ },
11
  "1D": {
12
+ "n_total": 18,
13
+ "high_rate_pct": 0.0,
14
+ "high_hit_pct": null,
15
+ "medium_hit_pct": 100.0,
16
+ "recommendation": "promote_medium_to_high"
17
  },
18
  "3D": {
19
+ "n_total": 18,
20
+ "high_rate_pct": 0.0,
21
+ "high_hit_pct": null,
22
+ "medium_hit_pct": 93.8,
23
+ "recommendation": "promote_medium_to_high"
24
  },
25
  "5D": {
26
+ "n_total": 18,
27
+ "high_rate_pct": 0.0,
28
+ "high_hit_pct": null,
29
+ "medium_hit_pct": 100.0,
30
+ "recommendation": "promote_medium_to_high"
31
  }
32
  }
33
  }
research/loop_backtest.py CHANGED
@@ -29,6 +29,10 @@ TARGET = 90.0
29
  BACKTEST_CACHE_DIR = os.path.join(os.path.dirname(__file__), "cache")
30
  ITERATION_BACKUP_RE = re.compile(r"ai_prompt_accuracy_iter(\d+)\.csv$")
31
 
 
 
 
 
32
 
33
  # ── ANALYSIS ──────────────────────────────────────────────────────────────────
34
 
@@ -430,6 +434,17 @@ def main():
430
  print(f" Backed up previous results -> {os.path.basename(backup)}")
431
  backup_iteration += 1
432
 
 
 
 
 
 
 
 
 
 
 
 
433
  # Run backtest — explicit file handles so subprocess doesn't inherit
434
  # nohup's broken fds (avoids "Bad file descriptor" crash on macOS)
435
  print(f"\n Running backtest (fresh historical data + prompt-only calibration)...")
@@ -441,6 +456,8 @@ def main():
441
  [
442
  sys.executable,
443
  os.path.join(os.path.dirname(__file__), "backtest.py"),
 
 
444
  ],
445
  cwd=os.path.dirname(__file__) + "/..",
446
  stdin=subprocess.DEVNULL,
 
29
  BACKTEST_CACHE_DIR = os.path.join(os.path.dirname(__file__), "cache")
30
  ITERATION_BACKUP_RE = re.compile(r"ai_prompt_accuracy_iter(\d+)\.csv$")
31
 
32
+ # Hold-out boundary — must match backtest.py HOLDOUT_START.
33
+ # Training window for loop optimization = rolling 18 months ending the day before this.
34
+ HOLDOUT_START = "2025-01-01"
35
+
36
 
37
  # ── ANALYSIS ──────────────────────────────────────────────────────────────────
38
 
 
434
  print(f" Backed up previous results -> {os.path.basename(backup)}")
435
  backup_iteration += 1
436
 
437
+ # Rolling 18-month training window: optimize on recent data only, never touch hold-out.
438
+ # holdout_dt is 2025-01-01; rolling window ends 2024-12-31, starts 18 months earlier.
439
+ from datetime import datetime as _dt, timedelta as _td
440
+ _holdout_dt = _dt.strptime(HOLDOUT_START, "%Y-%m-%d")
441
+ _train_end_dt = _holdout_dt - _td(days=1) # 2024-12-31
442
+ _rolling_days = 18 * 30 # ~18 months
443
+ _rolling_start_dt = _train_end_dt - _td(days=_rolling_days)
444
+ _rolling_start = _rolling_start_dt.strftime("%Y-%m-%d")
445
+ _rolling_end = _train_end_dt.strftime("%Y-%m-%d")
446
+ print(f" Rolling window: {_rolling_start} → {_rolling_end} (18-month training set, hold-out locked)")
447
+
448
  # Run backtest — explicit file handles so subprocess doesn't inherit
449
  # nohup's broken fds (avoids "Bad file descriptor" crash on macOS)
450
  print(f"\n Running backtest (fresh historical data + prompt-only calibration)...")
 
456
  [
457
  sys.executable,
458
  os.path.join(os.path.dirname(__file__), "backtest.py"),
459
+ "--start", _rolling_start,
460
+ "--end", _rolling_end,
461
  ],
462
  cwd=os.path.dirname(__file__) + "/..",
463
  stdin=subprocess.DEVNULL,
research/validate_on_trades.py CHANGED
@@ -22,7 +22,7 @@ warnings.filterwarnings("ignore")
22
 
23
  from backtest import (
24
  fetch_data, _compute_indicators, _fwd_intraday_moves, _fwd_returns,
25
- _vix_nifty_series, _simple_ml_prob, run_backtest, TIMEFRAMES,
26
  )
27
 
28
  # ── TRADED TICKERS ─────────────────────────────────────────────────────────────
@@ -238,6 +238,11 @@ def main():
238
  _print_summary(df)
239
  print(f"\nSaved → {csv_out}")
240
 
 
 
 
 
 
241
 
242
  if __name__ == "__main__":
243
  main()
 
22
 
23
  from backtest import (
24
  fetch_data, _compute_indicators, _fwd_intraday_moves, _fwd_returns,
25
+ _vix_nifty_series, _simple_ml_prob, run_backtest, print_results, TIMEFRAMES,
26
  )
27
 
28
  # ── TRADED TICKERS ─────────────────────────────────────────────────────────────
 
238
  _print_summary(df)
239
  print(f"\nSaved → {csv_out}")
240
 
241
+ # Full breakdown — Tables 1-5 (trigger accuracy, per-regime slice, etc.)
242
+ print("\n" + "=" * 70)
243
+ print("FULL BREAKDOWN (shared with backtest.py output)")
244
+ print_results(df)
245
+
246
 
247
  if __name__ == "__main__":
248
  main()
risk_engine.py CHANGED
@@ -58,6 +58,23 @@ def _annualized_vol(std_per_trade: float, avg_holding_days: float) -> float:
58
 
59
  # ── EQUITY CURVE & DRAWDOWN ───────────────────────────────────────────────────
60
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
61
  def _max_drawdown(pnl_pct_list: list[float]) -> float:
62
  """Max drawdown % from an ordered series of per-trade P&L %."""
63
  if not pnl_pct_list:
@@ -176,7 +193,7 @@ def _avg_holding_days(trades: list[dict]) -> float:
176
 
177
  # ── PUBLIC API ────────────────────────────────────────────────────────────────
178
 
179
- def get_portfolio_risk() -> dict:
180
  """
181
  Compute risk metrics from the paper trading database.
182
 
@@ -237,6 +254,9 @@ def get_portfolio_risk() -> dict:
237
  gross_loss = abs(sum(losers))
238
  profit_factor = round(gross_profit / gross_loss, 3) if gross_loss > 0 else float("inf")
239
 
 
 
 
240
  # Holding period & volatility
241
  avg_hold = _avg_holding_days(closed)
242
 
@@ -265,6 +285,9 @@ def get_portfolio_risk() -> dict:
265
  ordered_pnl = [_safe(t["pnl_pct"]) for t in sorted_trades]
266
  max_dd = _max_drawdown(ordered_pnl)
267
 
 
 
 
268
  # Beta (best-effort; may return None)
269
  beta = _compute_beta(sorted_trades)
270
 
@@ -279,6 +302,7 @@ def get_portfolio_risk() -> dict:
279
  "beta_vs_nifty": beta,
280
  "portfolio_volatility_ann": round(ann_vol * 100, 2) if ann_vol else None,
281
  "profit_factor": profit_factor,
 
282
  "kelly_fraction": round(kf, 4),
283
  "suggested_position_size_pct": suggested_position,
284
  "trade_count": n,
@@ -287,6 +311,7 @@ def get_portfolio_risk() -> dict:
287
  "avg_win_pct": round(avg_win, 2),
288
  "avg_loss_pct": round(avg_loss, 2),
289
  "computed_at": datetime.now().isoformat(),
 
290
  }
291
 
292
 
 
58
 
59
  # ── EQUITY CURVE & DRAWDOWN ───────────────────────────────────────────────────
60
 
61
+ def _build_equity_series(sorted_trades: list[dict]) -> list[dict]:
62
+ """Build equity curve as [{date, equity, trade_id}] starting at 100."""
63
+ series = []
64
+ equity = 100.0
65
+ for t in sorted_trades:
66
+ pnl = _safe(t.get("pnl_pct"), 0.0)
67
+ equity *= (1 + pnl / 100)
68
+ series.append({
69
+ "date": (t.get("closed_at") or "")[:10],
70
+ "equity": round(equity, 4),
71
+ "trade_id": t.get("id"),
72
+ "ticker": t.get("ticker"),
73
+ "pnl_pct": round(pnl, 3),
74
+ })
75
+ return series
76
+
77
+
78
  def _max_drawdown(pnl_pct_list: list[float]) -> float:
79
  """Max drawdown % from an ordered series of per-trade P&L %."""
80
  if not pnl_pct_list:
 
193
 
194
  # ── PUBLIC API ────────────────────────────────────────────────────────────────
195
 
196
+ def get_portfolio_risk(include_curve: bool = False) -> dict:
197
  """
198
  Compute risk metrics from the paper trading database.
199
 
 
254
  gross_loss = abs(sum(losers))
255
  profit_factor = round(gross_profit / gross_loss, 3) if gross_loss > 0 else float("inf")
256
 
257
+ loss_rate = 1 - win_rate
258
+ expectancy = round(win_rate * avg_win - loss_rate * avg_loss, 3)
259
+
260
  # Holding period & volatility
261
  avg_hold = _avg_holding_days(closed)
262
 
 
285
  ordered_pnl = [_safe(t["pnl_pct"]) for t in sorted_trades]
286
  max_dd = _max_drawdown(ordered_pnl)
287
 
288
+ # Equity curve (exposed when include_curve=True)
289
+ equity_series = _build_equity_series(sorted_trades) if include_curve else None
290
+
291
  # Beta (best-effort; may return None)
292
  beta = _compute_beta(sorted_trades)
293
 
 
302
  "beta_vs_nifty": beta,
303
  "portfolio_volatility_ann": round(ann_vol * 100, 2) if ann_vol else None,
304
  "profit_factor": profit_factor,
305
+ "expectancy": expectancy,
306
  "kelly_fraction": round(kf, 4),
307
  "suggested_position_size_pct": suggested_position,
308
  "trade_count": n,
 
311
  "avg_win_pct": round(avg_win, 2),
312
  "avg_loss_pct": round(avg_loss, 2),
313
  "computed_at": datetime.now().isoformat(),
314
+ **({"equity_curve": equity_series} if include_curve else {}),
315
  }
316
 
317
 
static/app.js CHANGED
@@ -549,6 +549,10 @@ function renderPickCard(pick, idx, idPrefix = 'pick', mode = 'top5') {
549
  const pickPrice = pick.price || 0;
550
  const sl3d = (tfs['3D'] || {}).stop_loss || 0;
551
  const tgt3d = (tfs['3D'] || {}).expected_target_price || (tfs['3D'] || {}).min_target || 0;
 
 
 
 
552
  const NO_TRADE_LABELS = {
553
  'no_signal': '— No signal',
554
  'wrong_timeframe': '— Signal ≠ horizon',
@@ -677,7 +681,9 @@ function renderPickCard(pick, idx, idPrefix = 'pick', mode = 'top5') {
677
  ${hasSl ? `<div class="rr-bar"><div class="rr-bar-fill" style="width:${rrPct}%"></div></div>` : ''}
678
  </div>`;
679
 
680
- return `<div class="tf-cell">
 
 
681
  <div class="tf-label">${tf === 'INTRADAY' ? 'Today' : tf}</div>
682
  <div class="tf-return" style="color:${isNoTrade ? 'var(--text-muted)' : retColor(d.midpoint||0)}">${retLabel}</div>
683
  ${noTradeDetail}
@@ -698,7 +704,7 @@ function renderPickCard(pick, idx, idPrefix = 'pick', mode = 'top5') {
698
  ? `<button class="btn-danger btn-sm" onclick="removeFromWatchlist('${pick.ticker}')">✕ Remove</button>
699
  <button class="btn-primary btn-sm" onclick='openTradeModal(${JSON.stringify(pick.ticker)},${JSON.stringify(safeCompany)},${pick.price||0},${sl3d},${tgt3d},${JSON.stringify(tfs["3D"] || {})})'>Trade</button>`
700
  : `<button class="btn-ghost btn-sm" onclick="addToWatchlist('${pick.ticker}','${safeCompany}')">+ Watch</button>
701
- <button class="btn-primary btn-sm" onclick='openTradeModal(${JSON.stringify(pick.ticker)},${JSON.stringify(safeCompany)},${pick.price||0},${sl3d},${tgt3d},${JSON.stringify(tfs["3D"] || {})})'>Trade</button>`;
702
 
703
  const bareSym = pick.ticker.replace(/\.(NS|BO)$/i, '');
704
  const exchange = pick.ticker.endsWith('.BO') ? 'BSE' : 'NSE';
 
549
  const pickPrice = pick.price || 0;
550
  const sl3d = (tfs['3D'] || {}).stop_loss || 0;
551
  const tgt3d = (tfs['3D'] || {}).expected_target_price || (tfs['3D'] || {}).min_target || 0;
552
+ const bestTf = pick.best_tf || '3D';
553
+ const slBest = (tfs[bestTf] || {}).stop_loss || 0;
554
+ const tgtBest = (tfs[bestTf] || {}).expected_target_price || (tfs[bestTf] || {}).min_target || 0;
555
+ const planDataBestJSON = JSON.stringify(Object.assign({}, tfs[bestTf] || {}, { timeframe: bestTf }));
556
  const NO_TRADE_LABELS = {
557
  'no_signal': '— No signal',
558
  'wrong_timeframe': '— Signal ≠ horizon',
 
681
  ${hasSl ? `<div class="rr-bar"><div class="rr-bar-fill" style="width:${rrPct}%"></div></div>` : ''}
682
  </div>`;
683
 
684
+ const isBestTf = (mode !== 'watchlist') && (tf === bestTf);
685
+ return `<div class="tf-cell${isBestTf ? ' tf-cell--best' : ''}">
686
+ ${isBestTf ? '<span class="best-tf-badge">Best Bet</span>' : ''}
687
  <div class="tf-label">${tf === 'INTRADAY' ? 'Today' : tf}</div>
688
  <div class="tf-return" style="color:${isNoTrade ? 'var(--text-muted)' : retColor(d.midpoint||0)}">${retLabel}</div>
689
  ${noTradeDetail}
 
704
  ? `<button class="btn-danger btn-sm" onclick="removeFromWatchlist('${pick.ticker}')">✕ Remove</button>
705
  <button class="btn-primary btn-sm" onclick='openTradeModal(${JSON.stringify(pick.ticker)},${JSON.stringify(safeCompany)},${pick.price||0},${sl3d},${tgt3d},${JSON.stringify(tfs["3D"] || {})})'>Trade</button>`
706
  : `<button class="btn-ghost btn-sm" onclick="addToWatchlist('${pick.ticker}','${safeCompany}')">+ Watch</button>
707
+ <button class="btn-primary btn-sm" onclick='openTradeModal(${JSON.stringify(pick.ticker)},${JSON.stringify(safeCompany)},${pick.price||0},${slBest},${tgtBest},${planDataBestJSON})'>Trade</button>`;
708
 
709
  const bareSym = pick.ticker.replace(/\.(NS|BO)$/i, '');
710
  const exchange = pick.ticker.endsWith('.BO') ? 'BSE' : 'NSE';
static/style.css CHANGED
@@ -396,6 +396,17 @@ input::placeholder { color: var(--text-dim); }
396
  display: flex; flex-direction: column; align-items: center; justify-content: flex-start;
397
  gap: 4px;
398
  }
 
 
 
 
 
 
 
 
 
 
 
399
  .tf-label { font-size: 11px; color: var(--text-muted); text-transform: uppercase; letter-spacing: .6px; font-weight: 600; }
400
  .tf-return { font-size: 16px; font-weight: 700; font-variant-numeric: tabular-nums; line-height: 1.1; }
401
  .no-trade-label { font-size: 13px; font-weight: 500; color: var(--text-muted); font-style: italic; }
 
396
  display: flex; flex-direction: column; align-items: center; justify-content: flex-start;
397
  gap: 4px;
398
  }
399
+ .tf-cell--best {
400
+ border-color: var(--green);
401
+ background: rgba(34, 197, 94, 0.06);
402
+ }
403
+ .best-tf-badge {
404
+ display: inline-flex; align-items: center;
405
+ font-size: 9px; font-weight: 700; letter-spacing: 0.5px; text-transform: uppercase;
406
+ color: var(--green);
407
+ background: rgba(34, 197, 94, 0.15); border: 1px solid rgba(34, 197, 94, 0.3);
408
+ border-radius: 3px; padding: 1px 5px; margin-bottom: 2px;
409
+ }
410
  .tf-label { font-size: 11px; color: var(--text-muted); text-transform: uppercase; letter-spacing: .6px; font-weight: 600; }
411
  .tf-return { font-size: 16px; font-weight: 700; font-variant-numeric: tabular-nums; line-height: 1.1; }
412
  .no-trade-label { font-size: 13px; font-weight: 500; color: var(--text-muted); font-style: italic; }
top5_picker.py CHANGED
@@ -18,7 +18,7 @@ from typing import Optional
18
 
19
  from predictor_core import predict_stock_v2, DEFAULT_UNIVERSE, timeframe_to_dates
20
 
21
- TIMEFRAMES = ["1D", "3D", "5D"]
22
 
23
  # Curated liquid universe for top5 — top 75 stocks by market cap from cache.
24
  # Using the full 491-stock universe × 3 timeframes is too slow for interactive use.
@@ -131,8 +131,8 @@ def get_top5_picks(
131
  _CONF_MULT = {"HIGH": 1.0, "MEDIUM": 0.80}
132
  _ACCEPTED_DIRECTIONS = {"BULLISH", "SLIGHTLY BULLISH"}
133
 
134
- def _score_5d(p: dict) -> float:
135
- """Composite profit score for a 5D scan result. Higher is better."""
136
  base_ret = float(p.get("ret_hi") or 0.0)
137
 
138
  conf = p.get("confidence", "LOW")
@@ -166,7 +166,7 @@ def get_top5_picks(
166
 
167
  # Filter: BULLISH direction, MEDIUM+ confidence, positive return magnitude.
168
  # Magnitude-based thresholds (1%, R:R ≥ 1.2) are removed: incompatible with
169
- # AI calibrated ranges (~0.18%); _score_5d handles ranking instead.
170
  bullish = []
171
  for p in scan_preds.values():
172
  if not p:
@@ -179,9 +179,9 @@ def get_top5_picks(
179
  continue
180
  bullish.append(p)
181
 
182
- # Sort by composite 5D profit score descending.
183
  # Expand candidate pool to 2× top_n so the full 1D/3D rerun has room to re-rank.
184
- bullish.sort(key=_score_5d, reverse=True)
185
  candidate_pool_size = top_n * 2 # fetch 10 full predictions, trim to top_n at end
186
  candidates = bullish[:candidate_pool_size]
187
 
@@ -204,12 +204,12 @@ def get_top5_picks(
204
  }
205
 
206
  # ATR multipliers and R:R targets per timeframe (must match predictor_core.py)
207
- _ATR_MULT = {"1D": 0.7, "3D": 1.1, "5D": 1.5}
208
- _RR_MULT = {"1D": 1.5, "3D": 1.7, "5D": 2.0}
209
 
210
  def _derive_risk(price, atr14, tf):
211
  """Derive SL/target from ATR when AI response risk fields are missing."""
212
- if not price or not atr14:
213
  return None, None, None
214
  sl_risk = _ATR_MULT[tf] * atr14
215
  sl_price = round(price - sl_risk, 2)
@@ -263,9 +263,26 @@ def get_top5_picks(
263
  "risk": {},
264
  }
265
 
266
- # Step 3: Assemble picks for full candidate pool, then re-rank by 5D profit score
267
- # and trim to top_n. This ensures the final list maximises 5D return even after
268
- # 1D/3D full predictions might have changed direction/confidence for some stocks.
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
269
  picks = []
270
  for stock in candidates:
271
  ticker = stock["ticker"]
@@ -313,10 +330,11 @@ def get_top5_picks(
313
  "actual_rr": actual_rr,
314
  }
315
 
316
- # Keep card metadata, but expose AI-led primary call and avoid
317
- # strategy-derived chips/details in top5 cards.
318
  pick = dict(stock)
319
- ai_anchor = ai_preds.get((ticker, "5D"), {})
 
 
320
  if ai_anchor and not ai_anchor.get("error"):
321
  pick["direction"] = ai_anchor.get("direction", pick.get("direction"))
322
  pick["confidence"] = ai_anchor.get("confidence", pick.get("confidence"))
@@ -326,18 +344,18 @@ def get_top5_picks(
326
  pick["signal_count"] = 0
327
  pick["timeframes"] = timeframe_data
328
 
329
- # Attach composite 5D score using the full (debate) prediction as anchor
330
- pick["_score_5d"] = _score_5d(ai_anchor if (ai_anchor and not ai_anchor.get("error")) else stock)
331
  picks.append(pick)
332
 
333
- # Re-rank by 5D composite profit score (uses full debate predictions where available)
334
- picks.sort(key=lambda x: x.get("_score_5d", 0.0), reverse=True)
335
  picks = picks[:top_n]
336
 
337
  # Assign final ranks and remove internal score field
338
  for i, p in enumerate(picks):
339
  p["rank"] = i + 1
340
- p.pop("_score_5d", None)
341
 
342
  return {
343
  "picks": picks,
 
18
 
19
  from predictor_core import predict_stock_v2, DEFAULT_UNIVERSE, timeframe_to_dates
20
 
21
+ TIMEFRAMES = ["INTRADAY", "1D", "3D", "5D"]
22
 
23
  # Curated liquid universe for top5 — top 75 stocks by market cap from cache.
24
  # Using the full 491-stock universe × 3 timeframes is too slow for interactive use.
 
131
  _CONF_MULT = {"HIGH": 1.0, "MEDIUM": 0.80}
132
  _ACCEPTED_DIRECTIONS = {"BULLISH", "SLIGHTLY BULLISH"}
133
 
134
+ def _score_tf(p: dict) -> float:
135
+ """Composite profit score for any TF prediction. Higher is better."""
136
  base_ret = float(p.get("ret_hi") or 0.0)
137
 
138
  conf = p.get("confidence", "LOW")
 
166
 
167
  # Filter: BULLISH direction, MEDIUM+ confidence, positive return magnitude.
168
  # Magnitude-based thresholds (1%, R:R ≥ 1.2) are removed: incompatible with
169
+ # AI calibrated ranges (~0.18%); _score_tf handles ranking instead.
170
  bullish = []
171
  for p in scan_preds.values():
172
  if not p:
 
179
  continue
180
  bullish.append(p)
181
 
182
+ # Sort by composite profit score descending.
183
  # Expand candidate pool to 2× top_n so the full 1D/3D rerun has room to re-rank.
184
+ bullish.sort(key=_score_tf, reverse=True)
185
  candidate_pool_size = top_n * 2 # fetch 10 full predictions, trim to top_n at end
186
  candidates = bullish[:candidate_pool_size]
187
 
 
204
  }
205
 
206
  # ATR multipliers and R:R targets per timeframe (must match predictor_core.py)
207
+ _ATR_MULT = {"INTRADAY": 0.4, "1D": 0.7, "3D": 1.1, "5D": 1.5}
208
+ _RR_MULT = {"INTRADAY": 1.2, "1D": 1.5, "3D": 1.7, "5D": 2.0}
209
 
210
  def _derive_risk(price, atr14, tf):
211
  """Derive SL/target from ATR when AI response risk fields are missing."""
212
+ if not price or not atr14 or tf not in _ATR_MULT:
213
  return None, None, None
214
  sl_risk = _ATR_MULT[tf] * atr14
215
  sl_price = round(price - sl_risk, 2)
 
263
  "risk": {},
264
  }
265
 
266
+ def _pick_best_tf(ticker: str) -> tuple[str, float]:
267
+ """Return the TF (1D/3D/5D) with the highest profit score for this ticker."""
268
+ best, best_score = "3D", 0.0
269
+ for tf in ["INTRADAY", "1D", "3D", "5D"]:
270
+ pred = ai_preds.get((ticker, tf), {})
271
+ if not pred or pred.get("error"):
272
+ continue
273
+ if pred.get("direction") not in _ACCEPTED_DIRECTIONS:
274
+ continue
275
+ if pred.get("confidence") not in _CONF_MULT:
276
+ continue
277
+ if float(pred.get("ret_hi") or 0.0) <= 0:
278
+ continue
279
+ s = _score_tf(pred)
280
+ if s > best_score:
281
+ best, best_score = tf, s
282
+ return best, best_score
283
+
284
+ # Step 3: Assemble picks for full candidate pool, then re-rank by best-TF profit score
285
+ # and trim to top_n. Stocks that shine in any TF (not just 5D) can now win.
286
  picks = []
287
  for stock in candidates:
288
  ticker = stock["ticker"]
 
330
  "actual_rr": actual_rr,
331
  }
332
 
333
+ # Keep card metadata, but expose AI-led primary call from the best TF.
 
334
  pick = dict(stock)
335
+ best_tf, best_score = _pick_best_tf(ticker)
336
+ pick["best_tf"] = best_tf
337
+ ai_anchor = ai_preds.get((ticker, best_tf), {})
338
  if ai_anchor and not ai_anchor.get("error"):
339
  pick["direction"] = ai_anchor.get("direction", pick.get("direction"))
340
  pick["confidence"] = ai_anchor.get("confidence", pick.get("confidence"))
 
344
  pick["signal_count"] = 0
345
  pick["timeframes"] = timeframe_data
346
 
347
+ # Rank by best-TF score so stocks strong in any TF surface to the top
348
+ pick["_score_best"] = best_score
349
  picks.append(pick)
350
 
351
+ # Re-rank by best-TF composite profit score
352
+ picks.sort(key=lambda x: x.get("_score_best", 0.0), reverse=True)
353
  picks = picks[:top_n]
354
 
355
  # Assign final ranks and remove internal score field
356
  for i, p in enumerate(picks):
357
  p["rank"] = i + 1
358
+ p.pop("_score_best", None)
359
 
360
  return {
361
  "picks": picks,
whatsapp_alerts.py ADDED
@@ -0,0 +1,80 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ whatsapp_alerts.py — Send WhatsApp alerts for HIGH-confidence predictions via CallMeBot.
3
+
4
+ One-time self-setup (done once by you in WhatsApp):
5
+ 1. Save +34 644 60 49 11 as a contact named "CallMeBot"
6
+ 2. Send "I allow callmebot to send me messages" to that number
7
+ 3. You receive an API key back (e.g. 123456)
8
+ 4. Set env vars: WHATSAPP_PHONE=91XXXXXXXXXX WHATSAPP_APIKEY=123456
9
+
10
+ No extra packages needed — uses requests (already a dependency).
11
+ """
12
+ from __future__ import annotations
13
+ import logging
14
+ import os
15
+ import urllib.parse
16
+
17
+ logger = logging.getLogger(__name__)
18
+
19
+ _CALLMEBOT_URL = "https://api.callmebot.com/whatsapp.php"
20
+
21
+
22
+ def _build_message(pred: dict) -> str:
23
+ ticker = pred.get("ticker", "?").replace(".NS", "")
24
+ direction = pred.get("direction", "?")
25
+ confidence = pred.get("confidence", "?")
26
+ tf = pred.get("timeframe", "?")
27
+ entry = pred.get("entry_price") or pred.get("current_price")
28
+ lo = pred.get("target_price_lo")
29
+ hi = pred.get("target_price_hi")
30
+ stop = pred.get("stop_loss")
31
+
32
+ parts = [f"{ticker} | {direction} {confidence} | {tf}"]
33
+ if entry:
34
+ parts.append(f"Entry ₹{entry:.0f}")
35
+ if lo and hi:
36
+ parts.append(f"Target ₹{lo:.0f}–{hi:.0f}")
37
+ if stop:
38
+ parts.append(f"Stop ₹{stop:.0f}")
39
+ return " | ".join(parts)
40
+
41
+
42
+ def send_prediction_alert(pred: dict) -> bool:
43
+ """
44
+ Send a WhatsApp message for a HIGH-confidence prediction.
45
+ No-ops silently if WHATSAPP_PHONE or WHATSAPP_APIKEY are not set.
46
+ Returns True if the message was sent successfully.
47
+ """
48
+ phone = os.environ.get("WHATSAPP_PHONE", "").strip()
49
+ apikey = os.environ.get("WHATSAPP_APIKEY", "").strip()
50
+ if not phone or not apikey:
51
+ return False
52
+
53
+ if (pred.get("confidence") or "").upper() != "HIGH":
54
+ return False
55
+
56
+ text = _build_message(pred)
57
+ try:
58
+ import requests
59
+ resp = requests.get(
60
+ _CALLMEBOT_URL,
61
+ params={"phone": phone, "text": text, "apikey": apikey},
62
+ timeout=10,
63
+ )
64
+ if resp.status_code == 200:
65
+ logger.info("WhatsApp alert sent: %s", text)
66
+ return True
67
+ logger.warning("WhatsApp alert HTTP %d: %s", resp.status_code, resp.text[:200])
68
+ return False
69
+ except Exception as e:
70
+ logger.warning("WhatsApp alert failed: %s", e)
71
+ return False
72
+
73
+
74
+ def send_bulk_alerts(predictions: list[dict]) -> int:
75
+ """Send alerts for all HIGH-confidence predictions. Returns count sent."""
76
+ sent = 0
77
+ for pred in predictions:
78
+ if send_prediction_alert(pred):
79
+ sent += 1
80
+ return sent