BaoKhuong commited on
Commit
6d207e1
·
verified ·
1 Parent(s): 6a9a68d

Upload 2 files

Browse files
Files changed (2) hide show
  1. app1 - claude-4.1-opus.py +705 -0
  2. app2 - gpt-5-high.py +692 -0
app1 - claude-4.1-opus.py ADDED
@@ -0,0 +1,705 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ App entry uses Fin-o1-14B GGUF via llama.cpp on CPU-only Spaces.
3
+ Removed heavy transformers/peft and Google Gemini imports.
4
+ """
5
+ import os
6
+ import json
7
+ import time
8
+ import random
9
+ from collections import defaultdict
10
+ from datetime import date, datetime, timedelta
11
+ import gradio as gr
12
+ import pandas as pd
13
+ import finnhub
14
+ # Removed Google Generative AI and transformer-based imports (not used)
15
+ from io import StringIO
16
+ import requests
17
+ from requests.adapters import HTTPAdapter
18
+ from urllib3.util.retry import Retry
19
+ from transformers import AutoModel # kept minimal placeholder to avoid breaking any implicit refs
20
+ from huggingface_hub import hf_hub_download
21
+ from llama_cpp import Llama
22
+
23
+ # Suppress Google Cloud warnings
24
+ os.environ['GRPC_VERBOSITY'] = 'ERROR'
25
+ os.environ['GRPC_TRACE'] = ''
26
+
27
+ # Suppress other warnings
28
+ import warnings
29
+ warnings.filterwarnings('ignore', category=UserWarning)
30
+ warnings.filterwarnings('ignore', category=FutureWarning)
31
+
32
+ # ---------- CẤU HÌNH ---------------------------------------------------------
33
+
34
+ GEMINI_MODEL = "gemini-2.5-pro"
35
+
36
+ # Fin-o1-14B GGUF via llama.cpp configuration (CPU-only)
37
+ FIN_O1_REPO = os.getenv("FIN_O1_REPO", "mradermacher/Fin-o1-14B-GGUF")
38
+ # Default to Q4_K_S (~8.7GB) for 16GB RAM Spaces; allow override via env
39
+ FIN_O1_QUANT = os.getenv("FIN_O1_QUANT", "Q6_K")
40
+ FIN_O1_FILENAME = os.getenv("FIN_O1_FILENAME", f"Fin-o1-14B.{FIN_O1_QUANT}.gguf")
41
+ LLAMA_CONTEXT_SIZE = int(os.getenv("LLAMA_CONTEXT_SIZE", "4096"))
42
+ LLAMA_THREADS = int(os.getenv("LLAMA_THREADS", str(os.cpu_count() or 2)))
43
+ LLAMA_N_GPU_LAYERS = int(os.getenv("LLAMA_N_GPU_LAYERS", "0")) # CPU-only on HF Spaces
44
+
45
+ # Singleton for llama.cpp model
46
+ _LLAMA_INSTANCE = None
47
+
48
+ def _resolve_fin_o1_gguf_path() -> str:
49
+ """Download (if needed) and return local path to the desired GGUF."""
50
+ try:
51
+ model_path = hf_hub_download(
52
+ repo_id=FIN_O1_REPO,
53
+ filename=FIN_O1_FILENAME,
54
+ repo_type="model",
55
+ )
56
+ return model_path
57
+ except Exception as e:
58
+ raise RuntimeError(f"Failed to download GGUF {FIN_O1_FILENAME} from {FIN_O1_REPO}: {e}")
59
+
60
+ def _get_llama_instance() -> Llama:
61
+ global _LLAMA_INSTANCE
62
+ if _LLAMA_INSTANCE is not None:
63
+ return _LLAMA_INSTANCE
64
+ model_path = _resolve_fin_o1_gguf_path()
65
+ _LLAMA_INSTANCE = Llama(
66
+ model_path=model_path,
67
+ n_ctx=LLAMA_CONTEXT_SIZE,
68
+ n_threads=LLAMA_THREADS,
69
+ n_gpu_layers=LLAMA_N_GPU_LAYERS,
70
+ verbose=False,
71
+ )
72
+ return _LLAMA_INSTANCE
73
+
74
+ # RapidAPI Configuration
75
+ RAPIDAPI_HOST = "alpha-vantage.p.rapidapi.com"
76
+
77
+ # Load Finnhub API keys from single secret (multiple keys separated by newlines)
78
+ FINNHUB_KEYS_RAW = os.getenv("FINNHUB_KEYS", "")
79
+ if FINNHUB_KEYS_RAW:
80
+ FINNHUB_KEYS = [key.strip() for key in FINNHUB_KEYS_RAW.split('\n') if key.strip()]
81
+ else:
82
+ FINNHUB_KEYS = []
83
+
84
+ # Load RapidAPI keys from single secret (multiple keys separated by newlines)
85
+ RAPIDAPI_KEYS_RAW = os.getenv("RAPIDAPI_KEYS", "")
86
+ if RAPIDAPI_KEYS_RAW:
87
+ RAPIDAPI_KEYS = [key.strip() for key in RAPIDAPI_KEYS_RAW.split('\n') if key.strip()]
88
+ else:
89
+ RAPIDAPI_KEYS = []
90
+
91
+ # Load Google API keys from single secret (multiple keys separated by newlines)
92
+ GOOGLE_API_KEYS_RAW = os.getenv("GOOGLE_API_KEYS", "")
93
+ if GOOGLE_API_KEYS_RAW:
94
+ GOOGLE_API_KEYS = [key.strip() for key in GOOGLE_API_KEYS_RAW.split('\n') if key.strip()]
95
+ else:
96
+ GOOGLE_API_KEYS = []
97
+
98
+ # Filter out empty keys
99
+ FINNHUB_KEYS = [key for key in FINNHUB_KEYS if key.strip()]
100
+ GOOGLE_API_KEYS = [key for key in GOOGLE_API_KEYS if key.strip()]
101
+
102
+ # Validate that we have at least one key for each service
103
+ if not FINNHUB_KEYS:
104
+ print("⚠️ Warning: No Finnhub API keys found in secrets")
105
+ if not RAPIDAPI_KEYS:
106
+ print("⚠️ Warning: No RapidAPI keys found in secrets")
107
+ if not GOOGLE_API_KEYS:
108
+ print("⚠️ Warning: No Google API keys found in secrets")
109
+
110
+ # Chọn ngẫu nhiên một khóa API để bắt đầu (if available)
111
+ GOOGLE_API_KEY = random.choice(GOOGLE_API_KEYS) if GOOGLE_API_KEYS else None
112
+
113
+ print("=" * 50)
114
+ print("🚀 FinRobot Forecaster Starting Up...")
115
+ print("=" * 50)
116
+ if FINNHUB_KEYS:
117
+ print(f"📊 Finnhub API: {len(FINNHUB_KEYS)} keys loaded")
118
+ else:
119
+ print("📊 Finnhub API: Not configured")
120
+ if RAPIDAPI_KEYS:
121
+ print(f"📈 RapidAPI Alpha Vantage: {RAPIDAPI_HOST} ({len(RAPIDAPI_KEYS)} keys loaded)")
122
+ else:
123
+ print("📈 RapidAPI Alpha Vantage: Not configured")
124
+ if GOOGLE_API_KEYS:
125
+ print(f"🤖 Google Gemini API: {len(GOOGLE_API_KEYS)} keys loaded")
126
+ else:
127
+ print("🤖 Google Gemini API: Not configured")
128
+ print("✅ Application started successfully!")
129
+ print("=" * 50)
130
+
131
+ print("ℹ️ Fin-o1 via llama.cpp will be used for inference (no Google calls).")
132
+
133
+ # Cấu hình Finnhub client (if keys available)
134
+ if FINNHUB_KEYS:
135
+ # Configure with first key for initial setup
136
+ finnhub_client = finnhub.Client(api_key=FINNHUB_KEYS[0])
137
+ print(f"✅ Finnhub configured with {len(FINNHUB_KEYS)} keys")
138
+ else:
139
+ finnhub_client = None
140
+ print("⚠️ Finnhub not configured - will use mock news data")
141
+
142
+ # Tạo session với retry strategy cho requests
143
+ def create_session():
144
+ session = requests.Session()
145
+ retry_strategy = Retry(
146
+ total=3,
147
+ backoff_factor=1,
148
+ status_forcelist=[429, 500, 502, 503, 504],
149
+ )
150
+ adapter = HTTPAdapter(max_retries=retry_strategy)
151
+ session.mount("http://", adapter)
152
+ session.mount("https://", adapter)
153
+ return session
154
+
155
+ # Tạo session global
156
+ requests_session = create_session()
157
+
158
+ SYSTEM_PROMPT = (
159
+ "You are a seasoned stock-market analyst. "
160
+ "Given recent company news and optional basic financials, "
161
+ "return:\n"
162
+ "[Positive Developments] – 2-4 bullets\n"
163
+ "[Potential Concerns] – 2-4 bullets\n"
164
+ "[Prediction & Analysis] – a one-week price outlook with rationale."
165
+ )
166
+
167
+ # ---------- UTILITY HELPERS ----------------------------------------
168
+
169
+ def today() -> str:
170
+ return date.today().strftime("%Y-%m-%d")
171
+
172
+ def n_weeks_before(date_string: str, n: int) -> str:
173
+ return (datetime.strptime(date_string, "%Y-%m-%d") -
174
+ timedelta(days=7 * n)).strftime("%Y-%m-%d")
175
+
176
+ # ---------- DATA FETCHING --------------------------------------------------
177
+
178
+ def get_stock_data(symbol: str, steps: list[str]) -> pd.DataFrame:
179
+ # Thử tất cả RapidAPI Alpha Vantage keys
180
+ for rapidapi_key in RAPIDAPI_KEYS:
181
+ try:
182
+ print(f"📈 Fetching stock data for {symbol} via RapidAPI (key: {rapidapi_key[:8]}...)")
183
+
184
+ # RapidAPI Alpha Vantage endpoint
185
+ url = f"https://{RAPIDAPI_HOST}/query"
186
+
187
+ headers = {
188
+ "X-RapidAPI-Host": RAPIDAPI_HOST,
189
+ "X-RapidAPI-Key": rapidapi_key
190
+ }
191
+
192
+ params = {
193
+ "function": "TIME_SERIES_DAILY",
194
+ "symbol": symbol,
195
+ "outputsize": "full",
196
+ "datatype": "csv"
197
+ }
198
+
199
+ # Thử lại 3 lần với RapidAPI key hiện tại
200
+ for attempt in range(3):
201
+ try:
202
+ resp = requests_session.get(url, headers=headers, params=params, timeout=30)
203
+ if not resp.ok:
204
+ print(f"RapidAPI HTTP error {resp.status_code} with key {rapidapi_key[:8]}..., attempt {attempt + 1}")
205
+ time.sleep(2 ** attempt)
206
+ continue
207
+
208
+ text = resp.text.strip()
209
+ if text.startswith("{"):
210
+ info = resp.json()
211
+ msg = info.get("Note") or info.get("Error Message") or info.get("Information") or str(info)
212
+ if "rate limit" in msg.lower() or "quota" in msg.lower():
213
+ print(f"RapidAPI rate limit hit with key {rapidapi_key[:8]}..., trying next key")
214
+ break # Thử key tiếp theo
215
+ raise RuntimeError(f"RapidAPI Alpha Vantage Error: {msg}")
216
+
217
+ # Parse CSV data
218
+ df = pd.read_csv(StringIO(text))
219
+ date_col = "timestamp" if "timestamp" in df.columns else df.columns[0]
220
+ df[date_col] = pd.to_datetime(df[date_col])
221
+ df = df.sort_values(date_col).set_index(date_col)
222
+
223
+ data = {"Start Date": [], "End Date": [], "Start Price": [], "End Price": []}
224
+ for i in range(len(steps) - 1):
225
+ s_date = pd.to_datetime(steps[i])
226
+ e_date = pd.to_datetime(steps[i+1])
227
+ seg = df.loc[s_date:e_date]
228
+ if seg.empty:
229
+ raise RuntimeError(
230
+ f"RapidAPI Alpha Vantage cannot get {symbol} data for {steps[i]} – {steps[i+1]}"
231
+ )
232
+ data["Start Date"].append(seg.index[0])
233
+ data["Start Price"].append(seg["close"].iloc[0])
234
+ data["End Date"].append(seg.index[-1])
235
+ data["End Price"].append(seg["close"].iloc[-1])
236
+ time.sleep(1) # RapidAPI has higher limits
237
+
238
+ print(f"✅ Successfully retrieved {symbol} data via RapidAPI (key: {rapidapi_key[:8]}...)")
239
+ return pd.DataFrame(data)
240
+
241
+ except requests.exceptions.Timeout:
242
+ print(f"RapidAPI timeout with key {rapidapi_key[:8]}..., attempt {attempt + 1}")
243
+ if attempt < 2:
244
+ time.sleep(5 * (attempt + 1))
245
+ continue
246
+ else:
247
+ break
248
+ except requests.exceptions.RequestException as e:
249
+ print(f"RapidAPI request error with key {rapidapi_key[:8]}..., attempt {attempt + 1}: {e}")
250
+ if attempt < 2:
251
+ time.sleep(3)
252
+ continue
253
+ else:
254
+ break
255
+
256
+ except Exception as e:
257
+ print(f"RapidAPI Alpha Vantage failed with key {rapidapi_key[:8]}...: {e}")
258
+ continue # Thử key tiếp theo
259
+
260
+ # Fallback: Tạo mock data nếu tất cả RapidAPI keys đều fail
261
+ print("⚠️ All RapidAPI keys failed, using mock data for demonstration...")
262
+ return create_mock_stock_data(symbol, steps)
263
+
264
+ def create_mock_stock_data(symbol: str, steps: list[str]) -> pd.DataFrame:
265
+ """Tạo mock data để demo khi API không hoạt động"""
266
+ import numpy as np
267
+
268
+ data = {"Start Date": [], "End Date": [], "Start Price": [], "End Price": []}
269
+
270
+ # Giá cơ bản khác nhau cho các symbol khác nhau
271
+ base_prices = {
272
+ "AAPL": 180.0, "MSFT": 350.0, "GOOGL": 140.0,
273
+ "TSLA": 200.0, "NVDA": 450.0, "AMZN": 150.0
274
+ }
275
+ base_price = base_prices.get(symbol.upper(), 150.0)
276
+
277
+ for i in range(len(steps) - 1):
278
+ s_date = pd.to_datetime(steps[i])
279
+ e_date = pd.to_datetime(steps[i+1])
280
+
281
+ # Tạo giá ngẫu nhiên với xu hướng tăng nhẹ
282
+ start_price = base_price + np.random.normal(0, 5)
283
+ end_price = start_price + np.random.normal(2, 8) # Xu hướng tăng nhẹ
284
+
285
+ data["Start Date"].append(s_date)
286
+ data["Start Price"].append(round(start_price, 2))
287
+ data["End Date"].append(e_date)
288
+ data["End Price"].append(round(end_price, 2))
289
+
290
+ base_price = end_price # Cập nhật giá cơ bản cho tuần tiếp theo
291
+
292
+ return pd.DataFrame(data)
293
+
294
+ def current_basics(symbol: str, curday: str) -> dict:
295
+ # Check if Finnhub is configured
296
+ if not FINNHUB_KEYS:
297
+ print(f"⚠️ Finnhub not configured, skipping financial basics for {symbol}")
298
+ return {}
299
+
300
+ # Thử với tất cả các Finnhub API keys
301
+ for api_key in FINNHUB_KEYS:
302
+ try:
303
+ client = finnhub.Client(api_key=api_key)
304
+ # Thêm timeout cho Finnhub client
305
+ raw = client.company_basic_financials(symbol, "all")
306
+ if not raw["series"]:
307
+ continue
308
+ merged = defaultdict(dict)
309
+ for metric, vals in raw["series"]["quarterly"].items():
310
+ for v in vals:
311
+ merged[v["period"]][metric] = v["v"]
312
+
313
+ latest = max((p for p in merged if p <= curday), default=None)
314
+ if latest is None:
315
+ continue
316
+ d = dict(merged[latest])
317
+ d["period"] = latest
318
+ return d
319
+ except Exception as e:
320
+ print(f"Error getting basics for {symbol} with key {api_key[:8]}...: {e}")
321
+ time.sleep(2) # Thêm delay trước khi thử key tiếp theo
322
+ continue
323
+ return {}
324
+
325
+ def attach_news(symbol: str, df: pd.DataFrame) -> pd.DataFrame:
326
+ news_col = []
327
+ for _, row in df.iterrows():
328
+ start = row["Start Date"].strftime("%Y-%m-%d")
329
+ end = row["End Date"].strftime("%Y-%m-%d")
330
+ time.sleep(2) # Tăng delay để tránh rate limit
331
+
332
+ # Check if Finnhub is configured
333
+ if not FINNHUB_KEYS:
334
+ print(f"⚠️ Finnhub not configured, using mock news for {symbol}")
335
+ news_data = create_mock_news(symbol, start, end)
336
+ news_col.append(json.dumps(news_data))
337
+ continue
338
+
339
+ # Thử với tất cả các Finnhub API keys
340
+ news_data = []
341
+ for api_key in FINNHUB_KEYS:
342
+ try:
343
+ client = finnhub.Client(api_key=api_key)
344
+ weekly = client.company_news(symbol, _from=start, to=end)
345
+ weekly_fmt = [
346
+ {
347
+ "date" : datetime.fromtimestamp(n["datetime"]).strftime("%Y%m%d%H%M%S"),
348
+ "headline": n["headline"],
349
+ "summary" : n["summary"],
350
+ }
351
+ for n in weekly
352
+ ]
353
+ weekly_fmt.sort(key=lambda x: x["date"])
354
+ news_data = weekly_fmt
355
+ break # Thành công, thoát khỏi loop
356
+ except Exception as e:
357
+ print(f"Error with Finnhub key {api_key[:8]}... for {symbol} from {start} to {end}: {e}")
358
+ time.sleep(3) # Thêm delay trước khi thử key tiếp theo
359
+ continue
360
+
361
+ # Nếu không có news data, tạo mock news
362
+ if not news_data:
363
+ news_data = create_mock_news(symbol, start, end)
364
+
365
+ news_col.append(json.dumps(news_data))
366
+ df["News"] = news_col
367
+ return df
368
+
369
+ def create_mock_news(symbol: str, start: str, end: str) -> list:
370
+ """Tạo mock news data khi API không hoạt động"""
371
+ mock_news = [
372
+ {
373
+ "date": f"{start}120000",
374
+ "headline": f"{symbol} Shows Strong Performance in Recent Trading",
375
+ "summary": f"Company {symbol} has demonstrated resilience in the current market conditions with positive investor sentiment."
376
+ },
377
+ {
378
+ "date": f"{end}090000",
379
+ "headline": f"Analysts Maintain Positive Outlook for {symbol}",
380
+ "summary": f"Financial analysts continue to recommend {symbol} based on strong fundamentals and growth prospects."
381
+ }
382
+ ]
383
+ return mock_news
384
+
385
+ # ---------- PROMPT CONSTRUCTION -------------------------------------------
386
+
387
+ def sample_news(news: list[str], k: int = 5) -> list[str]:
388
+ if len(news) <= k:
389
+ return news
390
+ return [news[i] for i in sorted(random.sample(range(len(news)), k))]
391
+
392
+ def make_prompt(symbol: str, df: pd.DataFrame, curday: str, use_basics=False) -> str:
393
+ # Thử với tất cả các Finnhub API keys để lấy company profile
394
+ company_blurb = f"[Company Introduction]:\n{symbol} is a publicly traded company.\n"
395
+
396
+ if FINNHUB_KEYS:
397
+ for api_key in FINNHUB_KEYS:
398
+ try:
399
+ client = finnhub.Client(api_key=api_key)
400
+ prof = client.company_profile2(symbol=symbol)
401
+ company_blurb = (
402
+ f"[Company Introduction]:\n{prof['name']} operates in the "
403
+ f"{prof['finnhubIndustry']} sector ({prof['country']}). "
404
+ f"Founded {prof['ipo']}, market cap {prof['marketCapitalization']:.1f} "
405
+ f"{prof['currency']}; ticker {symbol} on {prof['exchange']}.\n"
406
+ )
407
+ break # Thành công, thoát khỏi loop
408
+ except Exception as e:
409
+ print(f"Error getting company profile for {symbol} with key {api_key[:8]}...: {e}")
410
+ time.sleep(2) # Thêm delay trước khi thử key tiếp theo
411
+ continue
412
+ else:
413
+ print(f"⚠️ Finnhub not configured, using basic company info for {symbol}")
414
+
415
+ # Past weeks block
416
+ past_block = ""
417
+ for _, row in df.iterrows():
418
+ term = "increased" if row["End Price"] > row["Start Price"] else "decreased"
419
+ head = (f"From {row['Start Date']:%Y-%m-%d} to {row['End Date']:%Y-%m-%d}, "
420
+ f"{symbol}'s stock price {term} from "
421
+ f"{row['Start Price']:.2f} to {row['End Price']:.2f}.")
422
+ news_items = json.loads(row["News"])
423
+ summaries = [
424
+ f"[Headline] {n['headline']}\n[Summary] {n['summary']}\n"
425
+ for n in news_items
426
+ if not n["summary"].startswith("Looking for stock market analysis")
427
+ ]
428
+ past_block += "\n" + head + "\n" + "".join(sample_news(summaries, 5))
429
+
430
+ # Optional basic financials
431
+ if use_basics:
432
+ basics = current_basics(symbol, curday)
433
+ if basics:
434
+ basics_txt = "\n".join(f"{k}: {v}" for k, v in basics.items() if k != "period")
435
+ basics_block = (f"\n[Basic Financials] (reported {basics['period']}):\n{basics_txt}\n")
436
+ else:
437
+ basics_block = "\n[Basic Financials]: not available\n"
438
+ else:
439
+ basics_block = "\n[Basic Financials]: not requested\n"
440
+
441
+ horizon = f"{curday} to {n_weeks_before(curday, -1)}"
442
+ final_user_msg = (
443
+ company_blurb
444
+ + past_block
445
+ + basics_block
446
+ + f"\nBased on all information before {curday}, analyse positive "
447
+ "developments and potential concerns for {symbol}, then predict its "
448
+ f"price movement for next week ({horizon})."
449
+ )
450
+ return final_user_msg
451
+
452
+ # ---------- LLM CALL -------------------------------------------------------
453
+
454
+ def chat_completion(prompt: str,
455
+ model: str = "Fin-o1-14B-GGUF",
456
+ temperature: float = 0.2,
457
+ stream: bool = False,
458
+ symbol: str = "STOCK") -> str:
459
+ """Generate completion using Fin-o1-14B GGUF via llama.cpp.
460
+
461
+ Note: streaming is not implemented for llama.cpp in this app's UI path.
462
+ """
463
+ try:
464
+ llama = _get_llama_instance()
465
+ full_prompt = f"{SYSTEM_PROMPT}\n\n{prompt}"
466
+ # Conservative defaults for CPU-only inference on 16GB RAM
467
+ max_tokens = int(os.getenv("LLAMA_MAX_TOKENS", "1024"))
468
+ top_p = float(os.getenv("LLAMA_TOP_P", "0.9"))
469
+ top_k = int(os.getenv("LLAMA_TOP_K", "40"))
470
+ repeat_penalty = float(os.getenv("LLAMA_REPEAT_PENALTY", "1.1"))
471
+
472
+ if stream:
473
+ print("ℹ️ Streaming not enabled for llama.cpp path; generating non-streaming output.")
474
+
475
+ result = llama.create_completion(
476
+ prompt=full_prompt,
477
+ temperature=temperature,
478
+ max_tokens=max_tokens,
479
+ top_p=top_p,
480
+ top_k=top_k,
481
+ repeat_penalty=repeat_penalty,
482
+ )
483
+ return result.get("choices", [{}])[0].get("text", "")
484
+ except Exception as e:
485
+ print(f"⚠️ Fin-o1 llama.cpp inference failed for {symbol}: {e}")
486
+ return create_mock_ai_response(symbol)
487
+
488
+ def create_mock_ai_response(symbol: str) -> str:
489
+ """Tạo mock AI response khi Google API không hoạt động"""
490
+ return f"""
491
+ [Positive Developments]
492
+ • Strong market position and brand recognition for {symbol}
493
+ • Recent quarterly earnings showing growth potential
494
+ • Positive analyst sentiment and institutional investor interest
495
+ • Technological innovation and market expansion opportunities
496
+
497
+ [Potential Concerns]
498
+ • Market volatility and economic uncertainty
499
+ • Competitive pressures in the industry
500
+ • Regulatory changes that may impact operations
501
+ • Global economic factors affecting stock performance
502
+
503
+ [Prediction & Analysis]
504
+ Based on the current market conditions and company fundamentals, {symbol} is expected to show moderate growth over the next week. The stock may experience some volatility but should maintain an upward trend with a potential price increase of 2-5%. This prediction is based on current market sentiment and technical analysis patterns.
505
+
506
+ Note: This is a demonstration response using mock data. For real investment decisions, please consult with qualified financial professionals.
507
+ """
508
+
509
+ # ---------- MAIN PREDICTION FUNCTION -----------------------------------------
510
+
511
+ def predict(symbol: str = "AAPL",
512
+ curday: str = today(),
513
+ n_weeks: int = 3,
514
+ use_basics: bool = False,
515
+ stream: bool = False) -> tuple[str, str]:
516
+ try:
517
+ steps = [n_weeks_before(curday, n) for n in range(n_weeks + 1)][::-1]
518
+ df = get_stock_data(symbol, steps)
519
+ df = attach_news(symbol, df)
520
+
521
+ prompt_info = make_prompt(symbol, df, curday, use_basics)
522
+ answer = chat_completion(prompt_info, stream=stream, symbol=symbol)
523
+
524
+ return prompt_info, answer
525
+ except Exception as e:
526
+ error_msg = f"Error in prediction: {str(e)}"
527
+ print(f"Prediction error: {e}") # Log the error for debugging
528
+ return error_msg, error_msg
529
+
530
+ # ---------- HUGGINGFACE SPACES INTERFACE -----------------------------------------
531
+
532
+ def hf_predict(symbol, n_weeks, use_basics):
533
+ # 1. get curday
534
+ curday = date.today().strftime("%Y-%m-%d")
535
+ # 2. call predict
536
+ prompt, answer = predict(
537
+ symbol=symbol.upper(),
538
+ curday=curday,
539
+ n_weeks=int(n_weeks),
540
+ use_basics=bool(use_basics),
541
+ stream=False
542
+ )
543
+ return prompt, answer
544
+
545
+ # ---------- GRADIO INTERFACE -----------------------------------------
546
+
547
+ def create_interface():
548
+ with gr.Blocks(
549
+ title="FinRobot Forecaster",
550
+ theme=gr.themes.Soft(),
551
+ css="""
552
+ .gradio-container {
553
+ max-width: 1200px !important;
554
+ margin: auto !important;
555
+ }
556
+ #model_prompt_textbox textarea {
557
+ overflow-y: auto !important;
558
+ max-height: none !important;
559
+ min-height: 400px !important;
560
+ resize: vertical !important;
561
+ white-space: pre-wrap !important;
562
+ word-wrap: break-word !important;
563
+ height: auto !important;
564
+ }
565
+ #model_prompt_textbox {
566
+ height: auto !important;
567
+ }
568
+ #analysis_results_textbox textarea {
569
+ overflow-y: auto !important;
570
+ max-height: none !important;
571
+ min-height: 400px !important;
572
+ resize: vertical !important;
573
+ white-space: pre-wrap !important;
574
+ word-wrap: break-word !important;
575
+ height: auto !important;
576
+ }
577
+ #analysis_results_textbox {
578
+ height: auto !important;
579
+ }
580
+ .textarea textarea {
581
+ overflow-y: auto !important;
582
+ max-height: 500px !important;
583
+ resize: vertical !important;
584
+ }
585
+ .textarea {
586
+ height: auto !important;
587
+ min-height: 300px !important;
588
+ }
589
+ .gradio-textbox {
590
+ height: auto !important;
591
+ max-height: none !important;
592
+ }
593
+ .gradio-textbox textarea {
594
+ height: auto !important;
595
+ max-height: none !important;
596
+ overflow-y: auto !important;
597
+ }
598
+ """
599
+ ) as demo:
600
+ gr.Markdown("""
601
+ # 🤖 FinRobot Forecaster
602
+
603
+ **AI-powered stock market analysis and prediction using advanced language models**
604
+
605
+ This application analyzes stock market data, company news, and financial metrics to provide comprehensive market insights and predictions.
606
+
607
+ ⚠️ **Note**: Free API keys have daily rate limits. If you encounter errors, the app will use mock data for demonstration purposes.
608
+ """)
609
+
610
+ with gr.Row():
611
+ with gr.Column(scale=1):
612
+ symbol = gr.Textbox(
613
+ label="Stock Symbol",
614
+ value="AAPL",
615
+ placeholder="Enter stock symbol (e.g., AAPL, MSFT, GOOGL)",
616
+ info="Enter the ticker symbol of the stock you want to analyze"
617
+ )
618
+ n_weeks = gr.Slider(
619
+ 1, 6,
620
+ value=3,
621
+ step=1,
622
+ label="Historical Weeks to Analyze",
623
+ info="Number of weeks of historical data to include in analysis"
624
+ )
625
+ use_basics = gr.Checkbox(
626
+ label="Include Basic Financials",
627
+ value=True,
628
+ info="Include basic financial metrics in the analysis"
629
+ )
630
+ btn = gr.Button(
631
+ "🚀 Run Analysis",
632
+ variant="primary"
633
+ )
634
+
635
+ with gr.Column(scale=2):
636
+ with gr.Tabs():
637
+ with gr.Tab("📊 Analysis Results"):
638
+ gr.Markdown("**AI Analysis & Prediction**")
639
+ output_answer = gr.Textbox(
640
+ label="",
641
+ lines=40,
642
+ show_copy_button=True,
643
+ interactive=False,
644
+ placeholder="AI analysis and predictions will appear here...",
645
+ container=True,
646
+ scale=1,
647
+ elem_id="analysis_results_textbox"
648
+ )
649
+ with gr.Tab("🔍 Model Prompt"):
650
+ gr.Markdown("**Generated Prompt**")
651
+ output_prompt = gr.Textbox(
652
+ label="",
653
+ lines=40,
654
+ show_copy_button=True,
655
+ interactive=False,
656
+ placeholder="Generated prompt will appear here...",
657
+ container=True,
658
+ scale=1,
659
+ elem_id="model_prompt_textbox"
660
+ )
661
+
662
+ # Examples
663
+ gr.Examples(
664
+ examples=[
665
+ ["AAPL", 3, False],
666
+ ["MSFT", 4, True],
667
+ ["GOOGL", 2, False],
668
+ ["TSLA", 5, True],
669
+ ["NVDA", 3, True]
670
+ ],
671
+ inputs=[symbol, n_weeks, use_basics],
672
+ label="💡 Try these examples"
673
+ )
674
+
675
+ # Event handlers
676
+ btn.click(
677
+ fn=hf_predict,
678
+ inputs=[symbol, n_weeks, use_basics],
679
+ outputs=[output_prompt, output_answer],
680
+ show_progress=True
681
+ )
682
+
683
+
684
+ # Footer
685
+ gr.Markdown("""
686
+ ---
687
+ **Disclaimer**: This application is for educational and research purposes only.
688
+ The predictions and analysis provided should not be considered as financial advice.
689
+ Always consult with qualified financial professionals before making investment decisions.
690
+ """)
691
+
692
+ return demo
693
+
694
+ # ---------- MAIN EXECUTION -----------------------------------------
695
+
696
+ if __name__ == "__main__":
697
+ demo = create_interface()
698
+ demo.launch(
699
+ server_name="0.0.0.0",
700
+ server_port=7860,
701
+ share=False,
702
+ show_error=True,
703
+ debug=False,
704
+ quiet=True
705
+ )
app2 - gpt-5-high.py ADDED
@@ -0,0 +1,692 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ import json
3
+ import time
4
+ import random
5
+ from collections import defaultdict
6
+ from datetime import date, datetime, timedelta
7
+ import gradio as gr
8
+ import pandas as pd
9
+ import finnhub
10
+ from huggingface_hub import hf_hub_download
11
+ from llama_cpp import Llama
12
+ from io import StringIO
13
+ import requests
14
+ from requests.adapters import HTTPAdapter
15
+ from urllib3.util.retry import Retry
16
+
17
+ # Suppress Google Cloud warnings
18
+ os.environ['GRPC_VERBOSITY'] = 'ERROR'
19
+ os.environ['GRPC_TRACE'] = ''
20
+
21
+ # Suppress other warnings
22
+ import warnings
23
+ warnings.filterwarnings('ignore', category=UserWarning)
24
+ warnings.filterwarnings('ignore', category=FutureWarning)
25
+
26
+ # ---------- CẤU HÌNH ---------------------------------------------------------
27
+
28
+ # Local GGUF model config (CPU-only HF Spaces ~16GB RAM)
29
+ GGUF_REPO = os.getenv("GGUF_REPO", "mradermacher/Fin-o1-14B-GGUF")
30
+ # Default to Q6_K per user preference; override via env if needed
31
+ GGUF_FILENAME = os.getenv("GGUF_FILENAME", "fin-o1-14b.Q6_K.gguf")
32
+ N_CTX = int(os.getenv("LLAMA_N_CTX", "4096"))
33
+ N_THREADS = int(os.getenv("LLAMA_N_THREADS", str(os.cpu_count() or 4)))
34
+ N_BATCH = int(os.getenv("LLAMA_N_BATCH", "256"))
35
+ LLM_TEMPERATURE = float(os.getenv("LLAMA_TEMPERATURE", "0.2"))
36
+
37
+ # RapidAPI Configuration
38
+ RAPIDAPI_HOST = "alpha-vantage.p.rapidapi.com"
39
+
40
+ # Load Finnhub API keys from single secret (multiple keys separated by newlines)
41
+ FINNHUB_KEYS_RAW = os.getenv("FINNHUB_KEYS", "")
42
+ if FINNHUB_KEYS_RAW:
43
+ FINNHUB_KEYS = [key.strip() for key in FINNHUB_KEYS_RAW.split('\n') if key.strip()]
44
+ else:
45
+ FINNHUB_KEYS = []
46
+
47
+ # Load RapidAPI keys from single secret (multiple keys separated by newlines)
48
+ RAPIDAPI_KEYS_RAW = os.getenv("RAPIDAPI_KEYS", "")
49
+ if RAPIDAPI_KEYS_RAW:
50
+ RAPIDAPI_KEYS = [key.strip() for key in RAPIDAPI_KEYS_RAW.split('\n') if key.strip()]
51
+ else:
52
+ RAPIDAPI_KEYS = []
53
+
54
+ # Placeholder for compatibility; no Google keys needed with local model
55
+ GOOGLE_API_KEYS = []
56
+
57
+ # Filter out empty keys
58
+ FINNHUB_KEYS = [key for key in FINNHUB_KEYS if key.strip()]
59
+ GOOGLE_API_KEYS = [key for key in GOOGLE_API_KEYS if key.strip()]
60
+
61
+ # Validate that we have at least one key for each service
62
+ if not FINNHUB_KEYS:
63
+ print("⚠️ Warning: No Finnhub API keys found in secrets")
64
+ if not RAPIDAPI_KEYS:
65
+ print("⚠️ Warning: No RapidAPI keys found in secrets")
66
+ if not GOOGLE_API_KEYS:
67
+ print("⚠️ Warning: No Google API keys found in secrets")
68
+
69
+ # Chọn ngẫu nhiên một khóa API để bắt đầu (if available)
70
+ GOOGLE_API_KEY = random.choice(GOOGLE_API_KEYS) if GOOGLE_API_KEYS else None
71
+
72
+ print("=" * 50)
73
+ print("🚀 FinRobot Forecaster Starting Up...")
74
+ print("=" * 50)
75
+ if FINNHUB_KEYS:
76
+ print(f"📊 Finnhub API: {len(FINNHUB_KEYS)} keys loaded")
77
+ else:
78
+ print("📊 Finnhub API: Not configured")
79
+ if RAPIDAPI_KEYS:
80
+ print(f"📈 RapidAPI Alpha Vantage: {RAPIDAPI_HOST} ({len(RAPIDAPI_KEYS)} keys loaded)")
81
+ else:
82
+ print("📈 RapidAPI Alpha Vantage: Not configured")
83
+ print("🧠 Local LLM (llama.cpp) will be used: "+GGUF_REPO+"/"+GGUF_FILENAME)
84
+ print("✅ Application started successfully!")
85
+ print("=" * 50)
86
+
87
+ # Download GGUF model and initialize llama.cpp
88
+ _LLM = None
89
+ _TOKENS_PER_SECOND_INFO = None
90
+ try:
91
+ print("⬇️ Downloading GGUF model from Hugging Face Hub if not cached...")
92
+ gguf_path = hf_hub_download(repo_id=GGUF_REPO, filename=GGUF_FILENAME, local_dir="/home/user/.cache/hf")
93
+ print(f"✅ Model file ready: {gguf_path}")
94
+ print("🚀 Initializing llama.cpp (CPU)")
95
+ _LLM = Llama(
96
+ model_path=gguf_path,
97
+ n_ctx=N_CTX,
98
+ n_threads=N_THREADS,
99
+ n_batch=N_BATCH,
100
+ use_mlock=False,
101
+ use_mmap=True,
102
+ logits_all=False,
103
+ )
104
+ print("✅ Llama initialized")
105
+ except Exception as e:
106
+ print(f"❌ Failed to initialize local LLM: {e}")
107
+ _LLM = None
108
+
109
+ # Cấu hình Finnhub client (if keys available)
110
+ if FINNHUB_KEYS:
111
+ # Configure with first key for initial setup
112
+ finnhub_client = finnhub.Client(api_key=FINNHUB_KEYS[0])
113
+ print(f"✅ Finnhub configured with {len(FINNHUB_KEYS)} keys")
114
+ else:
115
+ finnhub_client = None
116
+ print("⚠️ Finnhub not configured - will use mock news data")
117
+
118
+ # Tạo session với retry strategy cho requests
119
+ def create_session():
120
+ session = requests.Session()
121
+ retry_strategy = Retry(
122
+ total=3,
123
+ backoff_factor=1,
124
+ status_forcelist=[429, 500, 502, 503, 504],
125
+ )
126
+ adapter = HTTPAdapter(max_retries=retry_strategy)
127
+ session.mount("http://", adapter)
128
+ session.mount("https://", adapter)
129
+ return session
130
+
131
+ # Tạo session global
132
+ requests_session = create_session()
133
+
134
+ SYSTEM_PROMPT = (
135
+ "You are a seasoned stock-market analyst. "
136
+ "Given recent company news and optional basic financials, "
137
+ "return:\n"
138
+ "[Positive Developments] – 2-4 bullets\n"
139
+ "[Potential Concerns] – 2-4 bullets\n"
140
+ "[Prediction & Analysis] – a one-week price outlook with rationale."
141
+ )
142
+
143
+ # ---------- UTILITY HELPERS ----------------------------------------
144
+
145
+ def today() -> str:
146
+ return date.today().strftime("%Y-%m-%d")
147
+
148
+ def n_weeks_before(date_string: str, n: int) -> str:
149
+ return (datetime.strptime(date_string, "%Y-%m-%d") -
150
+ timedelta(days=7 * n)).strftime("%Y-%m-%d")
151
+
152
+ # ---------- DATA FETCHING --------------------------------------------------
153
+
154
+ def get_stock_data(symbol: str, steps: list[str]) -> pd.DataFrame:
155
+ # Thử tất cả RapidAPI Alpha Vantage keys
156
+ for rapidapi_key in RAPIDAPI_KEYS:
157
+ try:
158
+ print(f"📈 Fetching stock data for {symbol} via RapidAPI (key: {rapidapi_key[:8]}...)")
159
+
160
+ # RapidAPI Alpha Vantage endpoint
161
+ url = f"https://{RAPIDAPI_HOST}/query"
162
+
163
+ headers = {
164
+ "X-RapidAPI-Host": RAPIDAPI_HOST,
165
+ "X-RapidAPI-Key": rapidapi_key
166
+ }
167
+
168
+ params = {
169
+ "function": "TIME_SERIES_DAILY",
170
+ "symbol": symbol,
171
+ "outputsize": "full",
172
+ "datatype": "csv"
173
+ }
174
+
175
+ # Thử lại 3 lần với RapidAPI key hiện tại
176
+ for attempt in range(3):
177
+ try:
178
+ resp = requests_session.get(url, headers=headers, params=params, timeout=30)
179
+ if not resp.ok:
180
+ print(f"RapidAPI HTTP error {resp.status_code} with key {rapidapi_key[:8]}..., attempt {attempt + 1}")
181
+ time.sleep(2 ** attempt)
182
+ continue
183
+
184
+ text = resp.text.strip()
185
+ if text.startswith("{"):
186
+ info = resp.json()
187
+ msg = info.get("Note") or info.get("Error Message") or info.get("Information") or str(info)
188
+ if "rate limit" in msg.lower() or "quota" in msg.lower():
189
+ print(f"RapidAPI rate limit hit with key {rapidapi_key[:8]}..., trying next key")
190
+ break # Thử key tiếp theo
191
+ raise RuntimeError(f"RapidAPI Alpha Vantage Error: {msg}")
192
+
193
+ # Parse CSV data
194
+ df = pd.read_csv(StringIO(text))
195
+ date_col = "timestamp" if "timestamp" in df.columns else df.columns[0]
196
+ df[date_col] = pd.to_datetime(df[date_col])
197
+ df = df.sort_values(date_col).set_index(date_col)
198
+
199
+ data = {"Start Date": [], "End Date": [], "Start Price": [], "End Price": []}
200
+ for i in range(len(steps) - 1):
201
+ s_date = pd.to_datetime(steps[i])
202
+ e_date = pd.to_datetime(steps[i+1])
203
+ seg = df.loc[s_date:e_date]
204
+ if seg.empty:
205
+ raise RuntimeError(
206
+ f"RapidAPI Alpha Vantage cannot get {symbol} data for {steps[i]} – {steps[i+1]}"
207
+ )
208
+ data["Start Date"].append(seg.index[0])
209
+ data["Start Price"].append(seg["close"].iloc[0])
210
+ data["End Date"].append(seg.index[-1])
211
+ data["End Price"].append(seg["close"].iloc[-1])
212
+ time.sleep(1) # RapidAPI has higher limits
213
+
214
+ print(f"✅ Successfully retrieved {symbol} data via RapidAPI (key: {rapidapi_key[:8]}...)")
215
+ return pd.DataFrame(data)
216
+
217
+ except requests.exceptions.Timeout:
218
+ print(f"RapidAPI timeout with key {rapidapi_key[:8]}..., attempt {attempt + 1}")
219
+ if attempt < 2:
220
+ time.sleep(5 * (attempt + 1))
221
+ continue
222
+ else:
223
+ break
224
+ except requests.exceptions.RequestException as e:
225
+ print(f"RapidAPI request error with key {rapidapi_key[:8]}..., attempt {attempt + 1}: {e}")
226
+ if attempt < 2:
227
+ time.sleep(3)
228
+ continue
229
+ else:
230
+ break
231
+
232
+ except Exception as e:
233
+ print(f"RapidAPI Alpha Vantage failed with key {rapidapi_key[:8]}...: {e}")
234
+ continue # Thử key tiếp theo
235
+
236
+ # Fallback: Tạo mock data nếu tất cả RapidAPI keys đều fail
237
+ print("⚠️ All RapidAPI keys failed, using mock data for demonstration...")
238
+ return create_mock_stock_data(symbol, steps)
239
+
240
+ def create_mock_stock_data(symbol: str, steps: list[str]) -> pd.DataFrame:
241
+ """Tạo mock data để demo khi API không hoạt động"""
242
+ import numpy as np
243
+
244
+ data = {"Start Date": [], "End Date": [], "Start Price": [], "End Price": []}
245
+
246
+ # Giá cơ bản khác nhau cho các symbol khác nhau
247
+ base_prices = {
248
+ "AAPL": 180.0, "MSFT": 350.0, "GOOGL": 140.0,
249
+ "TSLA": 200.0, "NVDA": 450.0, "AMZN": 150.0
250
+ }
251
+ base_price = base_prices.get(symbol.upper(), 150.0)
252
+
253
+ for i in range(len(steps) - 1):
254
+ s_date = pd.to_datetime(steps[i])
255
+ e_date = pd.to_datetime(steps[i+1])
256
+
257
+ # Tạo giá ngẫu nhiên với xu hướng tăng nhẹ
258
+ start_price = base_price + np.random.normal(0, 5)
259
+ end_price = start_price + np.random.normal(2, 8) # Xu hướng tăng nhẹ
260
+
261
+ data["Start Date"].append(s_date)
262
+ data["Start Price"].append(round(start_price, 2))
263
+ data["End Date"].append(e_date)
264
+ data["End Price"].append(round(end_price, 2))
265
+
266
+ base_price = end_price # Cập nhật giá cơ bản cho tuần tiếp theo
267
+
268
+ return pd.DataFrame(data)
269
+
270
+ def current_basics(symbol: str, curday: str) -> dict:
271
+ # Check if Finnhub is configured
272
+ if not FINNHUB_KEYS:
273
+ print(f"⚠️ Finnhub not configured, skipping financial basics for {symbol}")
274
+ return {}
275
+
276
+ # Thử với tất cả các Finnhub API keys
277
+ for api_key in FINNHUB_KEYS:
278
+ try:
279
+ client = finnhub.Client(api_key=api_key)
280
+ # Thêm timeout cho Finnhub client
281
+ raw = client.company_basic_financials(symbol, "all")
282
+ if not raw["series"]:
283
+ continue
284
+ merged = defaultdict(dict)
285
+ for metric, vals in raw["series"]["quarterly"].items():
286
+ for v in vals:
287
+ merged[v["period"]][metric] = v["v"]
288
+
289
+ latest = max((p for p in merged if p <= curday), default=None)
290
+ if latest is None:
291
+ continue
292
+ d = dict(merged[latest])
293
+ d["period"] = latest
294
+ return d
295
+ except Exception as e:
296
+ print(f"Error getting basics for {symbol} with key {api_key[:8]}...: {e}")
297
+ time.sleep(2) # Thêm delay trước khi thử key tiếp theo
298
+ continue
299
+ return {}
300
+
301
+ def attach_news(symbol: str, df: pd.DataFrame) -> pd.DataFrame:
302
+ news_col = []
303
+ for _, row in df.iterrows():
304
+ start = row["Start Date"].strftime("%Y-%m-%d")
305
+ end = row["End Date"].strftime("%Y-%m-%d")
306
+ time.sleep(2) # Tăng delay để tránh rate limit
307
+
308
+ # Check if Finnhub is configured
309
+ if not FINNHUB_KEYS:
310
+ print(f"⚠️ Finnhub not configured, using mock news for {symbol}")
311
+ news_data = create_mock_news(symbol, start, end)
312
+ news_col.append(json.dumps(news_data))
313
+ continue
314
+
315
+ # Thử với tất cả các Finnhub API keys
316
+ news_data = []
317
+ for api_key in FINNHUB_KEYS:
318
+ try:
319
+ client = finnhub.Client(api_key=api_key)
320
+ weekly = client.company_news(symbol, _from=start, to=end)
321
+ weekly_fmt = [
322
+ {
323
+ "date" : datetime.fromtimestamp(n["datetime"]).strftime("%Y%m%d%H%M%S"),
324
+ "headline": n["headline"],
325
+ "summary" : n["summary"],
326
+ }
327
+ for n in weekly
328
+ ]
329
+ weekly_fmt.sort(key=lambda x: x["date"])
330
+ news_data = weekly_fmt
331
+ break # Thành công, thoát khỏi loop
332
+ except Exception as e:
333
+ print(f"Error with Finnhub key {api_key[:8]}... for {symbol} from {start} to {end}: {e}")
334
+ time.sleep(3) # Thêm delay trước khi thử key tiếp theo
335
+ continue
336
+
337
+ # Nếu không có news data, tạo mock news
338
+ if not news_data:
339
+ news_data = create_mock_news(symbol, start, end)
340
+
341
+ news_col.append(json.dumps(news_data))
342
+ df["News"] = news_col
343
+ return df
344
+
345
+ def create_mock_news(symbol: str, start: str, end: str) -> list:
346
+ """Tạo mock news data khi API không hoạt động"""
347
+ mock_news = [
348
+ {
349
+ "date": f"{start}120000",
350
+ "headline": f"{symbol} Shows Strong Performance in Recent Trading",
351
+ "summary": f"Company {symbol} has demonstrated resilience in the current market conditions with positive investor sentiment."
352
+ },
353
+ {
354
+ "date": f"{end}090000",
355
+ "headline": f"Analysts Maintain Positive Outlook for {symbol}",
356
+ "summary": f"Financial analysts continue to recommend {symbol} based on strong fundamentals and growth prospects."
357
+ }
358
+ ]
359
+ return mock_news
360
+
361
+ # ---------- PROMPT CONSTRUCTION -------------------------------------------
362
+
363
+ def sample_news(news: list[str], k: int = 5) -> list[str]:
364
+ if len(news) <= k:
365
+ return news
366
+ return [news[i] for i in sorted(random.sample(range(len(news)), k))]
367
+
368
+ def make_prompt(symbol: str, df: pd.DataFrame, curday: str, use_basics=False) -> str:
369
+ # Thử với tất cả các Finnhub API keys để lấy company profile
370
+ company_blurb = f"[Company Introduction]:\n{symbol} is a publicly traded company.\n"
371
+
372
+ if FINNHUB_KEYS:
373
+ for api_key in FINNHUB_KEYS:
374
+ try:
375
+ client = finnhub.Client(api_key=api_key)
376
+ prof = client.company_profile2(symbol=symbol)
377
+ company_blurb = (
378
+ f"[Company Introduction]:\n{prof['name']} operates in the "
379
+ f"{prof['finnhubIndustry']} sector ({prof['country']}). "
380
+ f"Founded {prof['ipo']}, market cap {prof['marketCapitalization']:.1f} "
381
+ f"{prof['currency']}; ticker {symbol} on {prof['exchange']}.\n"
382
+ )
383
+ break # Thành công, thoát khỏi loop
384
+ except Exception as e:
385
+ print(f"Error getting company profile for {symbol} with key {api_key[:8]}...: {e}")
386
+ time.sleep(2) # Thêm delay trước khi thử key tiếp theo
387
+ continue
388
+ else:
389
+ print(f"⚠️ Finnhub not configured, using basic company info for {symbol}")
390
+
391
+ # Past weeks block
392
+ past_block = ""
393
+ for _, row in df.iterrows():
394
+ term = "increased" if row["End Price"] > row["Start Price"] else "decreased"
395
+ head = (f"From {row['Start Date']:%Y-%m-%d} to {row['End Date']:%Y-%m-%d}, "
396
+ f"{symbol}'s stock price {term} from "
397
+ f"{row['Start Price']:.2f} to {row['End Price']:.2f}.")
398
+ news_items = json.loads(row["News"])
399
+ summaries = [
400
+ f"[Headline] {n['headline']}\n[Summary] {n['summary']}\n"
401
+ for n in news_items
402
+ if not n["summary"].startswith("Looking for stock market analysis")
403
+ ]
404
+ past_block += "\n" + head + "\n" + "".join(sample_news(summaries, 5))
405
+
406
+ # Optional basic financials
407
+ if use_basics:
408
+ basics = current_basics(symbol, curday)
409
+ if basics:
410
+ basics_txt = "\n".join(f"{k}: {v}" for k, v in basics.items() if k != "period")
411
+ basics_block = (f"\n[Basic Financials] (reported {basics['period']}):\n{basics_txt}\n")
412
+ else:
413
+ basics_block = "\n[Basic Financials]: not available\n"
414
+ else:
415
+ basics_block = "\n[Basic Financials]: not requested\n"
416
+
417
+ horizon = f"{curday} to {n_weeks_before(curday, -1)}"
418
+ final_user_msg = (
419
+ company_blurb
420
+ + past_block
421
+ + basics_block
422
+ + f"\nBased on all information before {curday}, analyse positive "
423
+ "developments and potential concerns for {symbol}, then predict its "
424
+ f"price movement for next week ({horizon})."
425
+ )
426
+ return final_user_msg
427
+
428
+ # ---------- LLM CALL -------------------------------------------------------
429
+
430
+ def chat_completion(prompt: str,
431
+ model: str = "local-llama-cpp",
432
+ temperature: float = LLM_TEMPERATURE,
433
+ stream: bool = False,
434
+ symbol: str = "STOCK") -> str:
435
+ if _LLM is None:
436
+ print(f"⚠️ Local LLM not available, using mock response for {symbol}")
437
+ return create_mock_ai_response(symbol)
438
+
439
+ # Build a simple chat-style prompt for Qwen-based SFT
440
+ # Qwen-style chat can work with a plain system + user concatenation for inference
441
+ full_prompt = f"<|im_start|>system\n{SYSTEM_PROMPT}<|im_end|>\n<|im_start|>user\n{prompt}<|im_end|>\n<|im_start|>assistant\n"
442
+
443
+ try:
444
+ if stream:
445
+ out_text = []
446
+ for tok in _LLM(
447
+ full_prompt,
448
+ max_tokens=1024,
449
+ temperature=temperature,
450
+ top_p=0.9,
451
+ repeat_penalty=1.1,
452
+ stop=["<|im_end|>", "</s>", "<|endoftext|>"],
453
+ stream=True,
454
+ ):
455
+ delta = tok.get("choices", [{}])[0].get("text", "")
456
+ if delta:
457
+ print(delta, end="", flush=True)
458
+ out_text.append(delta)
459
+ print()
460
+ return "".join(out_text)
461
+ else:
462
+ res = _LLM(
463
+ full_prompt,
464
+ max_tokens=1024,
465
+ temperature=temperature,
466
+ top_p=0.9,
467
+ repeat_penalty=1.1,
468
+ stop=["<|im_end|>", "</s>", "<|endoftext|>"]
469
+ )
470
+ return res["choices"][0]["text"].strip()
471
+ except Exception as e:
472
+ print(f"❌ LLM inference error: {e}")
473
+ return create_mock_ai_response(symbol)
474
+
475
+ def create_mock_ai_response(symbol: str) -> str:
476
+ """Tạo mock AI response khi Google API không hoạt động"""
477
+ return f"""
478
+ [Positive Developments]
479
+ • Strong market position and brand recognition for {symbol}
480
+ • Recent quarterly earnings showing growth potential
481
+ • Positive analyst sentiment and institutional investor interest
482
+ • Technological innovation and market expansion opportunities
483
+
484
+ [Potential Concerns]
485
+ • Market volatility and economic uncertainty
486
+ • Competitive pressures in the industry
487
+ • Regulatory changes that may impact operations
488
+ • Global economic factors affecting stock performance
489
+
490
+ [Prediction & Analysis]
491
+ Based on the current market conditions and company fundamentals, {symbol} is expected to show moderate growth over the next week. The stock may experience some volatility but should maintain an upward trend with a potential price increase of 2-5%. This prediction is based on current market sentiment and technical analysis patterns.
492
+
493
+ Note: This is a demonstration response using mock data. For real investment decisions, please consult with qualified financial professionals.
494
+ """
495
+
496
+ # ---------- MAIN PREDICTION FUNCTION -----------------------------------------
497
+
498
+ def predict(symbol: str = "AAPL",
499
+ curday: str = today(),
500
+ n_weeks: int = 3,
501
+ use_basics: bool = False,
502
+ stream: bool = False) -> tuple[str, str]:
503
+ try:
504
+ steps = [n_weeks_before(curday, n) for n in range(n_weeks + 1)][::-1]
505
+ df = get_stock_data(symbol, steps)
506
+ df = attach_news(symbol, df)
507
+
508
+ prompt_info = make_prompt(symbol, df, curday, use_basics)
509
+ answer = chat_completion(prompt_info, stream=stream, symbol=symbol)
510
+
511
+ return prompt_info, answer
512
+ except Exception as e:
513
+ error_msg = f"Error in prediction: {str(e)}"
514
+ print(f"Prediction error: {e}") # Log the error for debugging
515
+ return error_msg, error_msg
516
+
517
+ # ---------- HUGGINGFACE SPACES INTERFACE -----------------------------------------
518
+
519
+ def hf_predict(symbol, n_weeks, use_basics):
520
+ # 1. get curday
521
+ curday = date.today().strftime("%Y-%m-%d")
522
+ # 2. call predict
523
+ prompt, answer = predict(
524
+ symbol=symbol.upper(),
525
+ curday=curday,
526
+ n_weeks=int(n_weeks),
527
+ use_basics=bool(use_basics),
528
+ stream=False
529
+ )
530
+ return prompt, answer
531
+
532
+ # ---------- GRADIO INTERFACE -----------------------------------------
533
+
534
+ def create_interface():
535
+ with gr.Blocks(
536
+ title="FinRobot Forecaster",
537
+ theme=gr.themes.Soft(),
538
+ css="""
539
+ .gradio-container {
540
+ max-width: 1200px !important;
541
+ margin: auto !important;
542
+ }
543
+ #model_prompt_textbox textarea {
544
+ overflow-y: auto !important;
545
+ max-height: none !important;
546
+ min-height: 400px !important;
547
+ resize: vertical !important;
548
+ white-space: pre-wrap !important;
549
+ word-wrap: break-word !important;
550
+ height: auto !important;
551
+ }
552
+ #model_prompt_textbox {
553
+ height: auto !important;
554
+ }
555
+ #analysis_results_textbox textarea {
556
+ overflow-y: auto !important;
557
+ max-height: none !important;
558
+ min-height: 400px !important;
559
+ resize: vertical !important;
560
+ white-space: pre-wrap !important;
561
+ word-wrap: break-word !important;
562
+ height: auto !important;
563
+ }
564
+ #analysis_results_textbox {
565
+ height: auto !important;
566
+ }
567
+ .textarea textarea {
568
+ overflow-y: auto !important;
569
+ max-height: 500px !important;
570
+ resize: vertical !important;
571
+ }
572
+ .textarea {
573
+ height: auto !important;
574
+ min-height: 300px !important;
575
+ }
576
+ .gradio-textbox {
577
+ height: auto !important;
578
+ max-height: none !important;
579
+ }
580
+ .gradio-textbox textarea {
581
+ height: auto !important;
582
+ max-height: none !important;
583
+ overflow-y: auto !important;
584
+ }
585
+ """
586
+ ) as demo:
587
+ gr.Markdown("""
588
+ # 🤖 FinRobot Forecaster
589
+
590
+ **AI-powered stock market analysis and prediction using advanced language models**
591
+
592
+ This application analyzes stock market data, company news, and financial metrics to provide comprehensive market insights and predictions.
593
+
594
+ ⚠️ **Note**: Free API keys have daily rate limits. If you encounter errors, the app will use mock data for demonstration purposes.
595
+ """)
596
+
597
+ with gr.Row():
598
+ with gr.Column(scale=1):
599
+ symbol = gr.Textbox(
600
+ label="Stock Symbol",
601
+ value="AAPL",
602
+ placeholder="Enter stock symbol (e.g., AAPL, MSFT, GOOGL)",
603
+ info="Enter the ticker symbol of the stock you want to analyze"
604
+ )
605
+ n_weeks = gr.Slider(
606
+ 1, 6,
607
+ value=3,
608
+ step=1,
609
+ label="Historical Weeks to Analyze",
610
+ info="Number of weeks of historical data to include in analysis"
611
+ )
612
+ use_basics = gr.Checkbox(
613
+ label="Include Basic Financials",
614
+ value=True,
615
+ info="Include basic financial metrics in the analysis"
616
+ )
617
+ btn = gr.Button(
618
+ "🚀 Run Analysis",
619
+ variant="primary"
620
+ )
621
+
622
+ with gr.Column(scale=2):
623
+ with gr.Tabs():
624
+ with gr.Tab("📊 Analysis Results"):
625
+ gr.Markdown("**AI Analysis & Prediction**")
626
+ output_answer = gr.Textbox(
627
+ label="",
628
+ lines=40,
629
+ show_copy_button=True,
630
+ interactive=False,
631
+ placeholder="AI analysis and predictions will appear here...",
632
+ container=True,
633
+ scale=1,
634
+ elem_id="analysis_results_textbox"
635
+ )
636
+ with gr.Tab("🔍 Model Prompt"):
637
+ gr.Markdown("**Generated Prompt**")
638
+ output_prompt = gr.Textbox(
639
+ label="",
640
+ lines=40,
641
+ show_copy_button=True,
642
+ interactive=False,
643
+ placeholder="Generated prompt will appear here...",
644
+ container=True,
645
+ scale=1,
646
+ elem_id="model_prompt_textbox"
647
+ )
648
+
649
+ # Examples
650
+ gr.Examples(
651
+ examples=[
652
+ ["AAPL", 3, False],
653
+ ["MSFT", 4, True],
654
+ ["GOOGL", 2, False],
655
+ ["TSLA", 5, True],
656
+ ["NVDA", 3, True]
657
+ ],
658
+ inputs=[symbol, n_weeks, use_basics],
659
+ label="💡 Try these examples"
660
+ )
661
+
662
+ # Event handlers
663
+ btn.click(
664
+ fn=hf_predict,
665
+ inputs=[symbol, n_weeks, use_basics],
666
+ outputs=[output_prompt, output_answer],
667
+ show_progress=True
668
+ )
669
+
670
+
671
+ # Footer
672
+ gr.Markdown("""
673
+ ---
674
+ **Disclaimer**: This application is for educational and research purposes only.
675
+ The predictions and analysis provided should not be considered as financial advice.
676
+ Always consult with qualified financial professionals before making investment decisions.
677
+ """)
678
+
679
+ return demo
680
+
681
+ # ---------- MAIN EXECUTION -----------------------------------------
682
+
683
+ if __name__ == "__main__":
684
+ demo = create_interface()
685
+ demo.launch(
686
+ server_name="0.0.0.0",
687
+ server_port=7860,
688
+ share=False,
689
+ show_error=True,
690
+ debug=False,
691
+ quiet=True
692
+ )