Spaces:
Build error
Build error
| import os | |
| import re | |
| import openai | |
| import pandas as pd | |
| import traceback | |
| from collections import Counter, defaultdict | |
| from apscheduler.schedulers.background import BackgroundScheduler | |
| import gradio as gr | |
| from utils import ( | |
| SECTOR, DYN, save_dynamic, | |
| fetch_news, keyword_hit, | |
| validate_ticker, send_tg, ist_now | |
| ) | |
| # โโ initialize LLM client โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ | |
| openai.api_base = "https://api.groq.com/openai/v1" | |
| openai.api_key = os.getenv("GROQ_API_KEY") | |
| # โโ helper: ask LLM for rationale โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ | |
| def llm_reason(theme, tickers, headlines): | |
| prompt = f""" | |
| Theme: {theme} | |
| Sample news: {' | '.join(headlines[:4])} | |
| Write TWO crisp bullets (<60 words total) explaining why {', '.join(tickers)} benefit now. | |
| """ | |
| rsp = openai.ChatCompletion.create( | |
| model="llama3-70b-8192", | |
| messages=[{"role":"user","content":prompt}], | |
| temperature=0.35, | |
| max_tokens=130 | |
| ) | |
| return rsp.choices[0].message.content.strip() | |
| # โโ helper: ask LLM for new tickers โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ | |
| def suggest_tickers(theme): | |
| ask = openai.ChatCompletion.create( | |
| model="llama3-8b-8192", | |
| messages=[{"role":"user","content": | |
| f"List up to 5 NSE tickers that will benefit from '{theme}'. Return comma-separated tickers only." | |
| }], | |
| temperature=0.4, | |
| max_tokens=20 | |
| ) | |
| raw = re.split(r"[,\s]+", ask.choices[0].message.content.strip()) | |
| return [validate_ticker(t.upper()) for t in raw] | |
| # โโ core logic (may raise) โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ | |
| def _daily_job(): | |
| news = fetch_news() | |
| freq = Counter() | |
| hits = defaultdict(list) | |
| # count keyword hits per theme | |
| for art in news: | |
| text = art["title"] + " " + art.get("description", "") | |
| for theme, cfg in SECTOR.items(): | |
| if keyword_hit(text, cfg["keywords"]): | |
| freq[theme] += 1 | |
| hits[theme].append(art["title"]) | |
| rows, cards = [], [] | |
| if not freq: | |
| return pd.DataFrame(rows, columns=["theme","hits","stocks","reason"]) | |
| for theme, count in freq.most_common(): | |
| base = SECTOR[theme]["tickers"] | |
| extra = DYN.get(theme, []) | |
| new = [t for t in suggest_tickers(theme) if t and t not in base+extra][:3] | |
| if new: | |
| DYN.setdefault(theme, []).extend(new) | |
| save_dynamic(DYN) | |
| tickers = list(dict.fromkeys(base + extra + new))[:6] | |
| reason = llm_reason(theme, tickers, hits[theme]) | |
| cards.append( | |
| f"*{theme}* ({count} hits)\n" | |
| f"Stocks: {', '.join('`'+t[:-3]+'`' for t in tickers)}\n" | |
| f"{reason}" | |
| ) | |
| rows.append({ | |
| "theme": theme, | |
| "hits": count, | |
| "stocks": ', '.join(tickers), | |
| "reason": reason | |
| }) | |
| header = f"๐ *Macro Theme Brief* {ist_now().strftime('%dย %bย %Y %H:%M')}" | |
| send_tg(header + "\n\n" + "\n\n".join(cards)) | |
| return pd.DataFrame(rows) | |
| # โโ safe wrapper (catches & displays errors) โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ | |
| def daily_job_safe(): | |
| try: | |
| return _daily_job() | |
| except Exception: | |
| tb = traceback.format_exc() | |
| print("Error in daily_job:", tb) | |
| return "```python\n" + tb + "\n```" | |
| # โโ schedule to run at 08:00 IST (02:30 UTC) โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ | |
| sched = BackgroundScheduler(timezone="UTC") | |
| sched.add_job(daily_job_safe, trigger="cron", hour=2, minute=30) | |
| sched.start() | |
| # โโ Gradio UI โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ | |
| if __name__ == "__main__": | |
| port = int(os.environ.get("PORT", 7860)) | |
| with gr.Blocks() as demo: | |
| gr.Markdown("### ๐ฎ๐ณ MacroโTheme Bot (Elecย MFG | Semicon | Hโ | Ethanol | RE)") | |
| out = gr.Textbox(lines=20, label="Output / Error") | |
| gr.Button("Run now").click(fn=daily_job_safe, outputs=out) | |
| demo.launch( | |
| server_name="0.0.0.0", | |
| server_port=port, | |
| ssr_mode=False | |
| ) | |