RajanStockrecom / app.py
GovindRoy's picture
Update app.py
5ca4128 verified
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
)