BaoKhuong commited on
Commit
7c5ccc9
Β·
verified Β·
1 Parent(s): f3e06cd

Upload 2 files

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