Spaces:
Runtime error
Runtime error
| import gradio as gr | |
| from huggingface_hub import InferenceClient | |
| import yfinance as yf | |
| import plotly.express as px | |
| import pandas as pd | |
| from datetime import datetime | |
| import feedparser | |
| # ---------- LLM Setup ---------- | |
| client = InferenceClient("microsoft/phi-4") | |
| def random_respond(message, history): | |
| messages = [{ | |
| "role": "system", | |
| "content": "You are an expert financial advisor and teacher from LSE and Oxford. You help young people understand finance by breaking down complex terms clearly." | |
| }] | |
| if history: | |
| for user_msg, bot_msg in history: | |
| messages.append({"role": "user", "content": user_msg}) | |
| messages.append({"role": "assistant", "content": bot_msg}) | |
| messages.append({"role": "user", "content": message}) | |
| response = client.chat_completion(messages, max_tokens=1000) | |
| return response['choices'][0]['message']['content'].strip() | |
| # ---------- Investment Simulator Setup ---------- | |
| class Investment_Simulator: | |
| def __init__(self, portfolio, history, portfolio_history): | |
| self.portfolio = {"cash": 500.0, "stocks": {}} | |
| self.history = [] | |
| self.portfolio_history = [] | |
| def get_price(self, ticker): | |
| try: | |
| data = yf.Ticker(ticker) | |
| todays_data = data.history(period='1d', interval='1m') | |
| latest_price = todays_data['Close'].dropna().iloc[-1] | |
| return float(latest_price) | |
| except: | |
| return None | |
| def compute_portfolio_value(self): | |
| total_stock_value = 0.0 | |
| for ticker, qty in self.portfolio["stocks"].items(): | |
| price = get_price(ticker) | |
| if price: | |
| total_stock_value += qty * price | |
| return self.portfolio["cash"] + total_stock_value | |
| def update_portfolio_history(self): | |
| now = datetime.now() | |
| total_value = self.compute_portfolio_value() | |
| self.portfolio_history.append((now, total_value)) | |
| def plot_portfolio_value(self): | |
| if not self.portfolio_history: | |
| return None | |
| df = pd.DataFrame(self.portfolio_history, columns=["Time", "Value"]) | |
| fig = px.line(df, x="Time", y="Value", title="Portfolio Value Over Time", labels={"Time": "Time", "Value": "Value (£)"}) | |
| fig.update_layout(xaxis_rangeslider_visible=True) | |
| return fig | |
| def process_input(self, user_input): | |
| user_input = user_input.lower() | |
| try: | |
| if "buy" in user_input: | |
| amount = int(user_input.split("£")[1].split()[0]) | |
| ticker = user_input.split("of")[1].strip().upper() | |
| price = self.get_price(ticker) | |
| if not price: | |
| return f"Couldn't fetch price for {ticker}." | |
| qty = round(amount / price, 4) | |
| if amount > self.portfolio["cash"]: | |
| return f"Insufficient funds (£{self.portfolio['cash']:.2f})." | |
| self.portfolio["cash"] -= amount | |
| self.portfolio["stocks"][ticker] = self.portfolio["stocks"].get(ticker, 0) + qty | |
| self.history.append(f"Bought £{amount} of {ticker} ({qty} shares at £{price:.2f})") | |
| self.update_portfolio_history() | |
| return f"Bought {qty} shares of {ticker} at £{price:.2f}" | |
| elif "sell" in user_input: | |
| amount = int(user_input.split("£")[1].split()[0]) | |
| ticker = user_input.split("of")[1].strip().upper() | |
| price = self.get_price(ticker) | |
| if not price: | |
| return f"Couldn't fetch price for {ticker}." | |
| qty_to_sell = round(amount / price, 4) | |
| current_qty = self.portfolio["stocks"].get(ticker, 0) | |
| if qty_to_sell > current_qty: | |
| return f"You don't own enough of {ticker}." | |
| self.portfolio["stocks"][ticker] -= qty_to_sell | |
| self.portfolio["cash"] += amount | |
| self.history.append(f"Sold £{amount} of {ticker} ({qty_to_sell} shares at £{price:.2f})") | |
| self.update_portfolio_history() | |
| return f"Sold {qty_to_sell} shares of {ticker} at £{price:.2f}" | |
| elif "portfolio" in user_input: | |
| cash = self.portfolio['cash'] | |
| total_stock_value = 0.0 | |
| summary = [f"Cash: £{cash:.2f}"] | |
| for ticker, qty in self.portfolio["stocks"].items(): | |
| live_price = self.get_price(ticker) | |
| if live_price: | |
| value = qty * live_price | |
| total_stock_value += value | |
| summary.append(f"{ticker}: {qty:.4f} shares (£{value:.2f})") | |
| else: | |
| summary.append(f"{ticker}: {qty:.4f} shares (price unavailable)") | |
| total_value = cash + total_stock_value | |
| summary.append(f"\nTotal Portfolio Value: £{total_value:.2f}") | |
| return "\n".join(summary) | |
| elif "history" in user_input: | |
| return "\n".join(self.history[-5:]) or "No trades yet." | |
| elif "reset" in user_input: | |
| self.portfolio["cash"] = 500.0 | |
| self.portfolio["stocks"].clear() | |
| self.history.clear() | |
| self.portfolio_history.clear() | |
| self.update_portfolio_history() | |
| return "Portfolio reset." | |
| else: | |
| return "Try commands like:\n• Buy £100 of AAPL\n• Sell £50 of TSLA\n• Show portfolio" | |
| except Exception as e: | |
| return f"Error: {e}" | |
| def run_all(self, text): | |
| response = self.process_input(text) | |
| fig = self.plot_portfolio_value() | |
| return response, fig | |
| self.update_portfolio_history() | |
| class News: | |
| def __init__(self): | |
| pass | |
| def get_news(self, tickers): | |
| ticker_list = [t.strip().upper() for t in tickers.split(",")] | |
| market_url = "https://finance.yahoo.com/rss/topstories" | |
| market_feed = feedparser.parse(market_url) | |
| result = "## General Market News\n" | |
| if market_feed.entries: | |
| for entry in market_feed.entries[:5]: | |
| result += f"**{entry.title}**\n{entry.link}\n{entry.published}\n\n" | |
| else: | |
| result += "_No general market news found_\n\n" | |
| for ticker in ticker_list: | |
| rss_url = f"https://feeds.finance.yahoo.com/rss/2.0/headline?s={ticker}®ion=US&lang=en-US" | |
| feed = feedparser.parse(rss_url) | |
| result += f"## News for {ticker}\n" | |
| if feed.entries: | |
| for entry in feed.entries[:5]: | |
| result += f"**{entry.title}**\n{entry.link}\n{entry.published}\n\n" | |
| else: | |
| result += "_No news found_\n\n" | |
| return result | |
| # ---------- UI Layout ---------- | |
| custom_css = """ | |
| #ZenoLogo { | |
| display: block; | |
| margin-left: auto; | |
| margin-right: auto; | |
| width: 200px; | |
| } | |
| #landing-content { | |
| text-align: center; | |
| margin-top: 100px; | |
| } | |
| #landing_page { | |
| background-color: #AEC3B0; | |
| padding: 50px; | |
| min-height: 100vh; | |
| overflow: hidden; | |
| } | |
| #sidebar-toggle { | |
| font-size: 5em; | |
| background: none; | |
| border: none; | |
| margin: 10px; | |
| cursor: pointer; | |
| } | |
| #sidebar { | |
| color: Black; | |
| background-color: #124559; | |
| border-right: 1px solid #ddd; | |
| padding: 10px; | |
| } | |
| """ | |
| news = News() | |
| with gr.Blocks(css=custom_css) as demo: | |
| active_section = gr.State("chat") | |
| sidebar_open = gr.State(True) | |
| landing_page = gr.Column(visible=True, elem_id="landing_page") | |
| with landing_page: | |
| gr.HTML(""" | |
| <div id="landing-content"> | |
| <img id="ZenoLogo" src="https://i.imgur.com/iCdIzOR.png" alt="ZENO" /> | |
| <h2>REDUCING YOUR MONEY PROBLEMS TO ZERO</h2> | |
| </div> | |
| """) | |
| landing_input = gr.Textbox( | |
| placeholder="Type your first finance question...", | |
| show_label=False, | |
| lines=1 | |
| ) | |
| app = gr.Row(visible=False) | |
| with app: | |
| with gr.Column(scale=1, min_width=200): | |
| sidebar_toggle = gr.Button("≡", elem_id="sidebar-toggle") | |
| with gr.Column(visible=True, elem_id="sidebar") as sidebar: | |
| gr.Markdown("📊 Zeno Tools") | |
| btn_chat = gr.Button("General Finance") | |
| btn_news = gr.Button("News/Updates") | |
| btn_mock = gr.Button("Mock Investment") | |
| btn_about = gr.Button("About Us") | |
| with gr.Column(scale=5): | |
| Zeno_Chat = gr.Column(visible=True) | |
| with Zeno_Chat: | |
| chatbot = gr.Chatbot() | |
| textbox = gr.Textbox( | |
| show_label=False, | |
| placeholder="Ask me anything about finance!", | |
| lines=1 | |
| ) | |
| Zeno_Investments = gr.Column(visible=False, elem_id="Investments") | |
| with Zeno_Investments: | |
| gr.Markdown("## Live Investment Simulator (Practice £500) + Portfolio Tracker") | |
| simulator = Investment_Simulator() | |
| invest_input = gr.Textbox(label="Enter a command", placeholder="Try: Buy £100 of AAPL") | |
| invest_output = gr.Textbox(label="Bot Response") | |
| invest_chart = gr.Plot(label="Portfolio Value Over Time") | |
| invest_input.submit( | |
| simulator.run_all, | |
| inputs=invest_input, | |
| outputs=[invest_output, invest_chart] | |
| ) | |
| Zeno_News = gr.Column(visible=False, elem_id="Stock and Market News") | |
| with Zeno_News: | |
| gr.Markdown("## 📰 Stock and Market News") | |
| ticker_input = gr.Textbox( | |
| label="Enter stock tickers (comma-separated, e.g., AAPL, TSLA, MSFT)" | |
| ) | |
| news_output = gr.Markdown() | |
| fetch_button = gr.Button("Get News") | |
| fetch_button.click( | |
| fn=news.get_news, | |
| inputs=ticker_input, | |
| outputs=news_output | |
| ) | |
| def handle_first_input(msg): | |
| return gr.update(visible=False), gr.update(visible=True), msg, [] | |
| landing_input.submit( | |
| handle_first_input, | |
| inputs=landing_input, | |
| outputs=[landing_page, app, textbox, chatbot] | |
| ) | |
| def respond_to_user(message, chat_history): | |
| bot_response = random_respond(message, chat_history) | |
| chat_history.append((message, bot_response)) | |
| return "", chat_history | |
| textbox.submit( | |
| respond_to_user, | |
| inputs=[textbox, chatbot], | |
| outputs=[textbox, chatbot] | |
| ) | |
| def switch_view(section): | |
| return ( | |
| gr.update(visible=section == "chat"), | |
| gr.update(visible=section == "investments"), | |
| section | |
| ) | |
| btn_chat.click(switch_view, inputs=[], outputs=[Zeno_Chat, Zeno_Investments, active_section], _js="() => 'chat'") | |
| btn_mock.click(switch_view, inputs=[], outputs=[Zeno_Chat, Zeno_Investments, active_section], _js="() => 'investments'") | |
| def toggle_sidebar(is_open): | |
| new_state = not is_open | |
| return gr.update(visible=new_state), new_state | |
| sidebar_toggle.click( | |
| toggle_sidebar, | |
| inputs=[sidebar_open], | |
| outputs=[sidebar, sidebar_open] | |
| ) | |
| demo.launch() |