zainulabedin949's picture
Update app.py
cd8413e verified
import gradio as gr
import pandas as pd
import numpy as np
import yfinance as yf
import plotly.graph_objects as go
import tempfile
import os
# --- Helpers ---
def parse_tickers(tickers_text, uploaded_csv):
if uploaded_csv is not None:
try:
df = pd.read_csv(uploaded_csv.name if hasattr(uploaded_csv, "name") else uploaded_csv)
except Exception as e:
raise ValueError(f"Failed to read CSV: {e}")
if "ticker" in df.columns:
tickers = [str(t).strip().upper() for t in df["ticker"].dropna().unique()]
else:
# try first column
tickers = [str(t).strip().upper() for t in df.iloc[:, 0].dropna().unique()]
return tickers
else:
tickers = [t.strip().upper() for t in (tickers_text or "").split(",") if t.strip()]
return tickers
def fetch_price_dfs(tickers, period, interval):
if not tickers:
return {}
data = yf.download(tickers, period=period, interval=interval, group_by="ticker", progress=False, threads=True)
result = {}
# multi-index case
if isinstance(data.columns, pd.MultiIndex):
for t in tickers:
if t in data.columns.levels[0]:
df = data[t].dropna(how="all").reset_index()
if not df.empty:
result[t] = df
else:
df = data.dropna(how="all").reset_index()
if not df.empty:
if len(tickers) == 1:
result[tickers[0]] = df
else:
# fallback: assign same df to first ticker
result[tickers[0]] = df
return result
def compute_universe_metrics(price_dfs):
rows = []
for t, df in price_dfs.items():
close = df["Close"].astype(float)
if close.shape[0] < 2:
recent_return = 0.0
vol = 0.0
else:
recent_return = (close.iloc[-1] / close.iloc[0]) - 1
daily_rets = close.pct_change().dropna()
vol = float(daily_rets.std(ddof=0) * np.sqrt(252)) if not daily_rets.empty else 0.0
rows.append({"ticker": t, "last_price": float(close.iloc[-1]) if not close.empty else 0.0,
"recent_return": float(recent_return), "vol_annual": float(vol)})
dfm = pd.DataFrame(rows).set_index("ticker")
n = max(len(dfm), 1)
# normalize by ranks to 0..1
if len(dfm) > 1:
dfm["ret_rank"] = dfm["recent_return"].rank(ascending=False, method="average")
dfm["vol_rank"] = dfm["vol_annual"].rank(ascending=True, method="average")
dfm["ret_norm"] = (dfm["ret_rank"] - 1) / (n - 1)
dfm["vol_norm"] = (dfm["vol_rank"] - 1) / (n - 1)
# score: favor higher return and lower vol
dfm["score"] = 0.6 * dfm["ret_norm"] + 0.4 * (1 - dfm["vol_norm"])
else:
dfm["score"] = 0.5
return dfm.fillna(0)
def heuristic_trade_idea(ticker, last_price, score, vol, risk_tolerance):
"""
risk_tolerance: 1 (low risk) .. 3 (high risk)
score: 0..1
"""
s = float(score)
lp = float(last_price) if last_price else 0.0
vol = float(vol) if vol else 0.0
# action
if s >= 0.66:
action = "Long"
target_mult = 1.12 # +12%
stop_mult = 1 - 0.05 / (risk_tolerance) # tighter stop for low risk tolerance
elif s >= 0.4:
action = "Conditional Long (watch for confirmation)"
target_mult = 1.08
stop_mult = 1 - 0.07 / (risk_tolerance)
else:
action = "Avoid / Monitor (not recommended for new long positions)"
target_mult = 1 - 0.08
stop_mult = 1 + 0.04
target = round(lp * target_mult, 4)
stop = round(lp * stop_mult, 4)
confidence = f"{int(s*100)}%"
rationale = f"{ticker} score={s:.2f}. Recent return and volatility imply " \
f"{'favorable' if s>=0.4 else 'weak'} risk-reward. Annual vol ~ {vol:.2%}."
key_risks = [
"Earnings / news event could invalidate the idea",
"Macro moves / liquidity shocks increase short-term risk"
]
idea_text = (
f"{action} {ticker} at ~{lp}. Target: {target}. Stop-loss: {stop}.\n\n"
f"Rationale: {rationale}\n"
f"Key Risks: {', '.join(key_risks)}\n"
f"Confidence: {confidence}\n"
f"Generated deterministically (no external LLM)."
)
structured = {
"ticker": ticker, "action": action, "entry": lp, "target": target,
"stop": stop, "rationale": rationale, "risks": key_risks, "confidence": confidence
}
return idea_text, structured
def make_price_figure(price_dfs, tickers):
fig = go.Figure()
for t in tickers:
if t in price_dfs:
df = price_dfs[t]
fig.add_trace(go.Scatter(x=df["Date"], y=df["Close"], name=t))
fig.update_layout(title="Price Chart", margin=dict(t=40))
return fig
def save_text_file(text):
tmp = tempfile.NamedTemporaryFile(delete=False, suffix=".txt", prefix="trade_idea_")
tmp.write(text.encode("utf-8"))
tmp.close()
return tmp.name
# --- Main app function ---
def run_tool(tickers_text, uploaded_csv, period, interval, risk_tolerance, selected_ticker_for_idea):
try:
tickers = parse_tickers(tickers_text, uploaded_csv)
except Exception as e:
return f"Input error: {e}", None, None, None
if not tickers:
return "No tickers provided.", None, None, None
try:
price_dfs = fetch_price_dfs(tickers, period, interval)
except Exception as e:
return f"Fetch error: {e}", None, None, None
if not price_dfs:
return "No price data returned for given tickers/period.", None, None, None
metrics = compute_universe_metrics(price_dfs)
# HTML table for display
html_table = metrics[["last_price", "recent_return", "vol_annual", "score"]].round(6).to_html(classes="table table-striped", border=0)
# prepare price figure for the tickers
fig = make_price_figure(price_dfs, tickers)
# choose ticker for idea
t_for_idea = selected_ticker_for_idea.strip().upper() if selected_ticker_for_idea else tickers[0]
if t_for_idea not in metrics.index:
# fallback to first ticker
t_for_idea = metrics.index[0]
last_price = metrics.loc[t_for_idea, "last_price"]
score = metrics.loc[t_for_idea, "score"]
vol = metrics.loc[t_for_idea, "vol_annual"]
idea_text, structured = heuristic_trade_idea(t_for_idea, last_price, score, vol, risk_tolerance)
# make downloadable file
file_path = save_text_file(idea_text)
# return: idea text, html table, plot, file path
return idea_text, html_table, fig, file_path
# --- Gradio UI ---
with gr.Blocks(title="Trade Idea Generator") as demo:
gr.Markdown("## 🔍 Trade Idea Generator \nUpload tickers or type comma-separated symbols. Tool generates simple, rule-based trade ideas and metrics.")
with gr.Row():
with gr.Column(scale=2):
tickers_in = gr.Textbox(label="Tickers (comma-separated)", value="AAPL, MSFT, NVDA")
csv_up = gr.File(label="Or upload CSV (first column = tickers or column named 'ticker')", file_types=[".csv"])
period = gr.Dropdown(choices=["1mo", "3mo", "6mo", "1y", "2y", "5y"], value="6mo", label="Price period")
interval = gr.Dropdown(choices=["1d", "1wk", "1mo"], value="1d", label="Price interval")
risk_tolerance = gr.Slider(minimum=1, maximum=3, step=1, value=2, label="Risk tolerance (1 low — 3 high)")
selected_ticker = gr.Textbox(label="Ticker to generate idea for (optional)", placeholder="e.g., AAPL")
run_btn = gr.Button("Generate Trade Idea")
with gr.Column(scale=3):
idea_out = gr.Textbox(label="Trade Idea (deterministic)", interactive=False)
table_out = gr.HTML()
plot_out = gr.Plot()
download_file = gr.File(label="Download idea as .txt")
run_btn.click(fn=run_tool, inputs=[tickers_in, csv_up, period, interval, risk_tolerance, selected_ticker], outputs=[idea_out, table_out, plot_out, download_file])
if __name__ == "__main__":
demo.launch()