Jitendra12421 commited on
Commit
40408cc
·
verified ·
1 Parent(s): e879d65

Upload 13 files

Browse files
app.py CHANGED
@@ -7,9 +7,13 @@ from zoneinfo import ZoneInfo
7
  from data_updater import update_daily_data, is_trading_day
8
  from forecaster_engine import generate_predictions
9
 
 
 
10
  IST = ZoneInfo("Asia/Kolkata")
11
  MARKET_CLOSE_BUFFER = time(15, 45) # Update runs after 3:45 PM
 
12
  PREDICTIONS_FILE = os.path.join(os.path.dirname(__file__), "predictions.json")
 
13
 
14
  app = FastAPI(title="HF NIFTY Forecaster Backend")
15
 
@@ -20,16 +24,20 @@ app.add_middleware(
20
  allow_headers=["*"],
21
  )
22
 
23
- def run_update_pipeline():
24
  try:
25
- # Step 1: Update data
26
- res = update_daily_data()
27
- if res.get("status") == "error":
28
- print(f"Update failed: {res.get('reason')}")
29
- return
30
-
31
- # Step 2: Generate predictions
32
- generate_predictions()
 
 
 
 
33
  except Exception as e:
34
  print(f"Pipeline error: {e}")
35
 
@@ -43,6 +51,16 @@ def get_predictions():
43
 
44
  return data
45
 
 
 
 
 
 
 
 
 
 
 
46
  @app.post("/cron/update")
47
  def cron_trigger(background_tasks: BackgroundTasks):
48
  now = datetime.now(IST)
@@ -53,14 +71,17 @@ def cron_trigger(background_tasks: BackgroundTasks):
53
  if not is_trading_day(today):
54
  return {"status": "skipped", "reason": f"{today} is a holiday or weekend"}
55
 
56
- # 2. Check if it's past 3:45 PM
57
- if current_time < MARKET_CLOSE_BUFFER:
58
- return {"status": "skipped", "reason": "Market is still open or buffer not reached. Runs after 3:45 PM IST."}
59
-
60
- # Trigger the full pipeline in the background so Netlify doesn't timeout
61
- background_tasks.add_task(run_update_pipeline)
62
-
63
- return {"status": "triggered", "message": "Update and forecast pipeline started in the background."}
 
 
 
64
 
65
  @app.get("/health")
66
  def health_check():
 
7
  from data_updater import update_daily_data, is_trading_day
8
  from forecaster_engine import generate_predictions
9
 
10
+ from t5_engine import generate_t5_predictions
11
+
12
  IST = ZoneInfo("Asia/Kolkata")
13
  MARKET_CLOSE_BUFFER = time(15, 45) # Update runs after 3:45 PM
14
+ T5_RUN_BUFFER = time(9, 21) # T+5 runs after 9:21 AM
15
  PREDICTIONS_FILE = os.path.join(os.path.dirname(__file__), "predictions.json")
16
+ PREDICTIONS_FILE_T5 = os.path.join(os.path.dirname(__file__), "predictions_t5.json")
17
 
18
  app = FastAPI(title="HF NIFTY Forecaster Backend")
19
 
 
24
  allow_headers=["*"],
25
  )
26
 
27
+ def run_update_pipeline(is_t5=False):
28
  try:
29
+ if is_t5:
30
+ # Step 1: Generate T+5 predictions
31
+ generate_t5_predictions()
32
+ else:
33
+ # Step 1: Update daily data
34
+ res = update_daily_data()
35
+ if res.get("status") == "error":
36
+ print(f"Update failed: {res.get('reason')}")
37
+ return
38
+
39
+ # Step 2: Generate T+1 predictions
40
+ generate_predictions()
41
  except Exception as e:
42
  print(f"Pipeline error: {e}")
43
 
 
51
 
52
  return data
53
 
54
+ @app.get("/t5-predictions")
55
+ def get_t5_predictions():
56
+ if not os.path.exists(PREDICTIONS_FILE_T5):
57
+ raise HTTPException(status_code=404, detail="T+5 Predictions not yet generated")
58
+
59
+ with open(PREDICTIONS_FILE_T5, "r") as f:
60
+ data = json.load(f)
61
+
62
+ return data
63
+
64
  @app.post("/cron/update")
65
  def cron_trigger(background_tasks: BackgroundTasks):
66
  now = datetime.now(IST)
 
71
  if not is_trading_day(today):
72
  return {"status": "skipped", "reason": f"{today} is a holiday or weekend"}
73
 
74
+ # Determine which pipeline to run
75
+ if current_time >= MARKET_CLOSE_BUFFER:
76
+ # Run standard T+1 end-of-day pipeline
77
+ background_tasks.add_task(run_update_pipeline, is_t5=False)
78
+ return {"status": "triggered", "message": "End-of-day T+1 update and forecast pipeline started in the background."}
79
+ elif current_time >= T5_RUN_BUFFER and current_time < time(10, 0):
80
+ # Run T+5 pipeline at 09:21
81
+ background_tasks.add_task(run_update_pipeline, is_t5=True)
82
+ return {"status": "triggered", "message": "Morning T+5 forecast pipeline started in the background."}
83
+ else:
84
+ return {"status": "skipped", "reason": f"Current time {current_time} is not in the execution windows (09:21-10:00 for T+5, after 15:45 for T+1)."}
85
 
86
  @app.get("/health")
87
  def health_check():
features_t5.py ADDED
@@ -0,0 +1,167 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ Feature extraction for T+5 model from 1-min OHLCV DataFrames.
3
+ Uses the 09:15-09:20 candle window to predict the day's close.
4
+ """
5
+
6
+ import numpy as np
7
+ import pandas as pd
8
+
9
+ def _safe_fill(arr):
10
+ return pd.Series(arr).ffill().bfill().values
11
+
12
+ def extract_semantic_features_t5(df):
13
+ df = df.copy()
14
+ df["time"] = df.index.time
15
+ df["date_only"] = df.index.date
16
+ required_times = (
17
+ pd.date_range("09:15", "09:20", freq="min").time.tolist()
18
+ + [pd.to_datetime("15:10").time()]
19
+ )
20
+ df = df[~df.index.duplicated(keep="first")]
21
+
22
+ daily_close = df.groupby("date_only")["close"].last()
23
+ prev_daily_close = daily_close.shift(1)
24
+
25
+ time_0921 = pd.to_datetime("09:21").time()
26
+ time_1200 = pd.to_datetime("12:00").time()
27
+ df_morning = df[(df["time"] >= time_0921) & (df["time"] <= time_1200)]
28
+ morning_low_per_date = df_morning.groupby("date_only")["low"].min()
29
+ morning_high_per_date = df_morning.groupby("date_only")["high"].max()
30
+
31
+ df_filtered = df[df["time"].isin(required_times)].copy()
32
+ pivot_close = df_filtered.pivot(index="date_only", columns="time", values="close")
33
+ pivot_open = df_filtered.pivot(index="date_only", columns="time", values="open")
34
+ pivot_high = df_filtered.pivot(index="date_only", columns="time", values="high")
35
+ pivot_low = df_filtered.pivot(index="date_only", columns="time", values="low")
36
+ pivot_vol = df_filtered.pivot(index="date_only", columns="time", values="volume")
37
+
38
+ time_0920 = pd.to_datetime("09:20").time()
39
+ time_1510 = pd.to_datetime("15:10").time()
40
+
41
+ if time_0920 not in pivot_close.columns:
42
+ return None, None, None
43
+
44
+ pivot_close = pivot_close.dropna(subset=[time_0920])
45
+ valid_dates = pivot_close.index
46
+ times_6m = pd.date_range("09:15", "09:20", freq="min").time
47
+
48
+ feature_dicts = []
49
+ metadata = {}
50
+
51
+ for date in valid_dates:
52
+ f = {}
53
+ c_series = _safe_fill(pivot_close.loc[date, times_6m].values.astype(float))
54
+ o_series = _safe_fill(pivot_open.loc[date, times_6m].values.astype(float))
55
+ h_series = _safe_fill(pivot_high.loc[date, times_6m].values.astype(float))
56
+ l_series = _safe_fill(pivot_low.loc[date, times_6m].values.astype(float))
57
+ v_series = pd.Series(pivot_vol.loc[date, times_6m].values.astype(float)).fillna(0).values
58
+
59
+ o_915 = o_series[0]
60
+ c_920 = c_series[-1]
61
+
62
+ pdc = prev_daily_close.get(date, np.nan)
63
+ f["gap"] = 0 if (pd.isna(pdc) or pdc == 0) else (o_915 / pdc) - 1.0
64
+ f["return_6m"] = (c_920 / o_915) - 1.0 if o_915 != 0 else 0
65
+ f["std_dev"] = np.std(c_series / (o_915 + 1e-8))
66
+
67
+ max_h = np.max(h_series)
68
+ min_l = np.min(l_series)
69
+ f["range_pct"] = (max_h - min_l) / (o_915 + 1e-8)
70
+ f["upper_shadow"] = (max_h - max(o_915, c_920)) / (o_915 + 1e-8)
71
+ f["lower_shadow"] = (min(o_915, c_920) - min_l) / (o_915 + 1e-8)
72
+ f["total_vol"] = np.sum(v_series)
73
+
74
+ vwap = np.sum(((h_series + l_series + c_series) / 3.0) * v_series) / (np.sum(v_series) + 1e-8)
75
+ f["vwap_dev"] = (c_920 / vwap) - 1.0 if vwap != 0 else 0
76
+ f["price_momentum"] = (c_series[-1] - c_series[0]) / (c_series[0] + 1e-8)
77
+ f["morning_trend"] = np.polyfit(np.arange(len(c_series)), c_series, 1)[0]
78
+
79
+ feature_dicts.append(f)
80
+
81
+ metadata[date] = {
82
+ "c_0920": c_920,
83
+ "h_0920": float(pivot_high.loc[date, time_0920]) if time_0920 in pivot_high.columns else c_920,
84
+ "l_0920": float(pivot_low.loc[date, time_0920]) if time_0920 in pivot_low.columns else c_920,
85
+ "v_0920": float(pivot_vol.loc[date, time_0920]) if time_0920 in pivot_vol.columns else 0,
86
+ "c_1510": float(pivot_close.loc[date, time_1510]) if time_1510 in pivot_close.columns else c_920,
87
+ "h_1510": float(pivot_high.loc[date, time_1510]) if time_1510 in pivot_high.columns else c_920,
88
+ "l_1510": float(pivot_low.loc[date, time_1510]) if time_1510 in pivot_low.columns else c_920,
89
+ "dip_low": float(morning_low_per_date.get(date, c_920)),
90
+ "peak_high": float(morning_high_per_date.get(date, c_920)),
91
+ }
92
+
93
+ X = pd.DataFrame(feature_dicts, index=valid_dates).fillna(0)
94
+
95
+ if time_1510 in pivot_close.columns:
96
+ target = (pivot_close[time_1510] > pivot_close[time_0920]).astype(int)
97
+ else:
98
+ target = pd.Series(0, index=valid_dates)
99
+
100
+ return X, target, metadata
101
+
102
+ def extract_sequential_features_t5(df):
103
+ df = df.copy()
104
+ df["time"] = df.index.time
105
+ df["date_only"] = df.index.date
106
+ required_times = (
107
+ pd.date_range("09:15", "09:20", freq="min").time.tolist()
108
+ + [pd.to_datetime("15:10").time()]
109
+ )
110
+ df = df[~df.index.duplicated(keep="first")]
111
+
112
+ time_0921 = pd.to_datetime("09:21").time()
113
+ time_1200 = pd.to_datetime("12:00").time()
114
+ df_morning = df[(df["time"] >= time_0921) & (df["time"] <= time_1200)]
115
+ morning_low_per_date = df_morning.groupby("date_only")["low"].min()
116
+ morning_high_per_date = df_morning.groupby("date_only")["high"].max()
117
+
118
+ df_filtered = df[df["time"].isin(required_times)].copy()
119
+ pivot_close = df_filtered.pivot(index="date_only", columns="time", values="close")
120
+ pivot_high = df_filtered.pivot(index="date_only", columns="time", values="high")
121
+ pivot_low = df_filtered.pivot(index="date_only", columns="time", values="low")
122
+ pivot_vol = df_filtered.pivot(index="date_only", columns="time", values="volume")
123
+
124
+ time_0920 = pd.to_datetime("09:20").time()
125
+ time_1510 = pd.to_datetime("15:10").time()
126
+
127
+ if time_0920 not in pivot_close.columns:
128
+ return None, None, None
129
+
130
+ pivot_close = pivot_close.dropna(subset=[time_0920])
131
+ valid_dates = pivot_close.index
132
+ times_6m = pd.date_range("09:15", "09:20", freq="min").time
133
+
134
+ feature_dicts = []
135
+ metadata = {}
136
+
137
+ for date in valid_dates:
138
+ f = {}
139
+ c_series = _safe_fill(pivot_close.loc[date, times_6m].values.astype(float))
140
+ v_series = pd.Series(pivot_vol.loc[date, times_6m].values.astype(float)).fillna(0).values
141
+ c_ref = c_series[-1]
142
+
143
+ for i, t in enumerate(times_6m):
144
+ f[f"ret_c_{i}"] = (c_series[i] / (c_ref + 1e-8)) - 1.0
145
+ f[f"raw_vol_{i}"] = v_series[i]
146
+ feature_dicts.append(f)
147
+
148
+ metadata[date] = {
149
+ "c_0920": c_ref,
150
+ "h_0920": float(pivot_high.loc[date, time_0920]) if time_0920 in pivot_high.columns else c_ref,
151
+ "l_0920": float(pivot_low.loc[date, time_0920]) if time_0920 in pivot_low.columns else c_ref,
152
+ "v_0920": float(pivot_vol.loc[date, time_0920]) if time_0920 in pivot_vol.columns else 0,
153
+ "c_1510": float(pivot_close.loc[date, time_1510]) if time_1510 in pivot_close.columns else c_ref,
154
+ "h_1510": float(pivot_high.loc[date, time_1510]) if time_1510 in pivot_high.columns else c_ref,
155
+ "l_1510": float(pivot_low.loc[date, time_1510]) if time_1510 in pivot_low.columns else c_ref,
156
+ "dip_low": float(morning_low_per_date.get(date, c_ref)),
157
+ "peak_high": float(morning_high_per_date.get(date, c_ref)),
158
+ }
159
+
160
+ X = pd.DataFrame(feature_dicts, index=valid_dates).fillna(0)
161
+
162
+ if time_1510 in pivot_close.columns:
163
+ target = (pivot_close[time_1510] > pivot_close[time_0920]).astype(int)
164
+ else:
165
+ target = pd.Series(0, index=valid_dates)
166
+
167
+ return X, target, metadata
models/ASIANPAINT_t5.joblib ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:06b6c926a9012ebbba27d892b221b4591f1f21818142acdb7bf14da832ff111a
3
+ size 261524
models/INFY_t5.joblib ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:bea768648f602a109a2044009bc8baaf6d8879bb6d163a12722456f7894590b0
3
+ size 75331
models/ONGC_t5.joblib ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:593f0d615caa5de3d1657048b4dc3db317741eabe27c2256651c4c503787105a
3
+ size 139795
models/POWERGRID_t5.joblib ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:82dcdd852eb2eadffb46c8df64061027e5a338dcd3cee13033b7d69e48c653a2
3
+ size 73524
models/TECHM_t5.joblib ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:f0e709841fbb944548a9ce70f71fad0d5b89d6ae777cbf698ab453deef22a391
3
+ size 279604
requirements.txt CHANGED
@@ -5,3 +5,5 @@ requests
5
  pandas_market_calendars
6
  pyarrow
7
  fastparquet
 
 
 
5
  pandas_market_calendars
6
  pyarrow
7
  fastparquet
8
+ scikit-learn
9
+ joblib
t5_engine.py ADDED
@@ -0,0 +1,167 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ import json
3
+ import time
4
+ import requests
5
+ import joblib
6
+ import pandas as pd
7
+ import numpy as np
8
+ from datetime import datetime, date
9
+ from zoneinfo import ZoneInfo
10
+ from features_t5 import extract_semantic_features_t5, extract_sequential_features_t5
11
+
12
+ IST = ZoneInfo("Asia/Kolkata")
13
+ DATA_DIR = os.path.dirname(__file__)
14
+ MODELS_DIR = os.path.join(DATA_DIR, "models")
15
+ PREDICTIONS_FILE_T5 = os.path.join(DATA_DIR, "predictions_t5.json")
16
+ DAILY_DATA_FILE = os.path.join(DATA_DIR, "data", "nifty50_daily.parquet")
17
+
18
+ TICKERS = [
19
+ 'ADANIENT', 'ADANIPORTS', 'APOLLOHOSP', 'ASIANPAINT', 'AXISBANK', 'BAJAJ-AUTO', 'BAJAJFINSV', 'BAJFINANCE',
20
+ 'BHARTIARTL', 'BPCL', 'BRITANNIA', 'CIPLA', 'COALINDIA', 'DIVISLAB', 'DRREDDY', 'EICHERMOT', 'GRASIM',
21
+ 'HCLTECH', 'HDFCBANK', 'HDFCLIFE', 'HEROMOTOCO', 'HINDALCO', 'HINDUNILVR', 'ICICIBANK', 'INDUSINDBK',
22
+ 'INFY', 'ITC', 'JSWSTEEL', 'KOTAKBANK', 'LT', 'M&M', 'MARUTI', 'NESTLEIND', 'NTPC', 'ONGC', 'POWERGRID',
23
+ 'RELIANCE', 'SBILIFE', 'SBIN', 'SUNPHARMA', 'TATACONSUM', 'TATAMOTORS', 'TATASTEEL', 'TCS', 'TECHM',
24
+ 'TITAN', 'ULTRACEMCO', 'UPL', 'WIPRO'
25
+ ]
26
+
27
+ def fetch_groww_t5_data(ticker: str, start_ts: int, end_ts: int):
28
+ url = f"https://groww.in/v1/api/charting_service/v2/chart/exchange/NSE/segment/CASH/{ticker}?endTimeInMillis={end_ts}&intervalInMinutes=1&startTimeInMillis={start_ts}"
29
+ headers = {
30
+ "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64)",
31
+ "Accept": "application/json"
32
+ }
33
+
34
+ try:
35
+ response = requests.get(url, headers=headers, timeout=10)
36
+ if response.status_code == 200:
37
+ data = response.json()
38
+ if data and 'candles' in data and len(data['candles']) > 0:
39
+ rows = []
40
+ for c in data['candles']:
41
+ rows.append({
42
+ "date": datetime.fromtimestamp(c[0], IST).replace(tzinfo=None),
43
+ "open": float(c[1]),
44
+ "high": float(c[2]),
45
+ "low": float(c[3]),
46
+ "close": float(c[4]),
47
+ "volume": float(c[5]),
48
+ "ticker": ticker
49
+ })
50
+ return pd.DataFrame(rows)
51
+ return None
52
+ except Exception as e:
53
+ print(f"Error fetching T+5 for {ticker}: {e}")
54
+ return None
55
+
56
+ def fetch_all_t5_data(today: date):
57
+ # Market open 09:15 to 09:20
58
+ start_dt = datetime.combine(today, datetime.strptime("09:15", "%H:%M").time()).replace(tzinfo=IST)
59
+ end_dt = datetime.combine(today, datetime.strptime("09:25", "%H:%M").time()).replace(tzinfo=IST) # fetch slightly wider just in case
60
+
61
+ start_ts = int(start_dt.timestamp() * 1000)
62
+ end_ts = int(end_dt.timestamp() * 1000)
63
+
64
+ dfs = []
65
+ for ticker in TICKERS:
66
+ df_tick = fetch_groww_t5_data(ticker, start_ts, end_ts)
67
+ if df_tick is not None and not df_tick.empty:
68
+ dfs.append(df_tick)
69
+ time.sleep(0.1)
70
+
71
+ if dfs:
72
+ return pd.concat(dfs, ignore_index=True)
73
+ return pd.DataFrame()
74
+
75
+ def generate_t5_predictions():
76
+ now = datetime.now(IST)
77
+ today = now.date()
78
+
79
+ df_live = fetch_all_t5_data(today)
80
+ if df_live.empty:
81
+ print("No live data fetched for T+5.")
82
+ return None
83
+
84
+ df_live.set_index("date", inplace=True)
85
+
86
+ # Load previous day's close for the gap feature
87
+ prev_closes = {}
88
+ if os.path.exists(DAILY_DATA_FILE):
89
+ df_daily = pd.read_parquet(DAILY_DATA_FILE)
90
+ df_daily = df_daily[df_daily['date'].dt.date < today]
91
+ if not df_daily.empty:
92
+ for ticker in TICKERS:
93
+ t_data = df_daily[df_daily['ticker'] == ticker]
94
+ if not t_data.empty:
95
+ # Last row is yesterday's close
96
+ t_data = t_data.sort_values("date")
97
+ prev_closes[ticker] = t_data.iloc[-1]['close']
98
+
99
+ predictions = {}
100
+
101
+ for ticker in TICKERS:
102
+ model_path = os.path.join(MODELS_DIR, f"{ticker}_t5.joblib")
103
+ if not os.path.exists(model_path):
104
+ continue
105
+
106
+ feat_type, clf = joblib.load(model_path)
107
+
108
+ # Build dummy dataframe for extraction
109
+ t_data = df_live[df_live['ticker'] == ticker].copy()
110
+ if t_data.empty:
111
+ continue
112
+
113
+ # Insert yesterday's dummy row at 15:30 to populate prev_daily_close correctly
114
+ if ticker in prev_closes:
115
+ yday = datetime.combine(today - pd.Timedelta(days=1), datetime.strptime("15:30", "%H:%M").time())
116
+ t_data.loc[yday] = {"open": prev_closes[ticker], "high": prev_closes[ticker], "low": prev_closes[ticker], "close": prev_closes[ticker], "volume": 0, "ticker": ticker}
117
+
118
+ t_data.sort_index(inplace=True)
119
+
120
+ if feat_type == "semantic":
121
+ X, _, _ = extract_semantic_features_t5(t_data)
122
+ else:
123
+ X, _, _ = extract_sequential_features_t5(t_data)
124
+
125
+ if X is None or X.empty:
126
+ continue
127
+
128
+ # Get today's prediction
129
+ if today in X.index:
130
+ X_today = X.loc[[today]]
131
+ prob_up = clf.predict_proba(X_today)[0][1]
132
+ prob_dn = 1.0 - prob_up
133
+
134
+ # Confidence threshold from tuning (0.55)
135
+ if prob_up > prob_dn and prob_up >= 0.55:
136
+ predictions[ticker] = {
137
+ "prediction": "UP",
138
+ "probability": round(prob_up * 100, 2),
139
+ "confidence": "HIGH"
140
+ }
141
+ elif prob_dn > prob_up and prob_dn >= 0.55:
142
+ predictions[ticker] = {
143
+ "prediction": "DOWN",
144
+ "probability": round(prob_dn * 100, 2),
145
+ "confidence": "HIGH"
146
+ }
147
+
148
+ probs = [info["probability"] for info in predictions.values()]
149
+ mean_accuracy = round(np.mean(probs), 2) if probs else 0.0
150
+ median_accuracy = round(np.median(probs), 2) if probs else 0.0
151
+
152
+ output = {
153
+ "generated_at": datetime.now().isoformat(),
154
+ "forecast_date": today.strftime('%Y-%m-%d'),
155
+ "mean_accuracy": mean_accuracy,
156
+ "median_accuracy": median_accuracy,
157
+ "predictions": predictions
158
+ }
159
+
160
+ with open(PREDICTIONS_FILE_T5, "w") as f:
161
+ json.dump(output, f, indent=4)
162
+
163
+ print(f"Generated T+5 predictions. Saved to {PREDICTIONS_FILE_T5}")
164
+ return output
165
+
166
+ if __name__ == "__main__":
167
+ generate_t5_predictions()