Spaces:
Sleeping
Sleeping
| 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 | |
| # ---------- 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 ---------- | |
| portfolio = {"cash": 500.0, "stocks": {}} | |
| history = [] | |
| portfolio_history = [] | |
| def get_price(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(): | |
| total_stock_value = 0.0 | |
| for ticker, qty in portfolio["stocks"].items(): | |
| price = get_price(ticker) | |
| if price: | |
| total_stock_value += qty * price | |
| return portfolio["cash"] + total_stock_value | |
| def update_portfolio_history(): | |
| now = datetime.now() | |
| total_value = compute_portfolio_value() | |
| portfolio_history.append((now, total_value)) | |
| def plot_portfolio_value(): | |
| if not portfolio_history: | |
| return None | |
| df = pd.DataFrame(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(user_input): | |
| global portfolio, history | |
| 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 = get_price(ticker) | |
| if not price: | |
| return f"❌ Couldn't fetch price for {ticker}." | |
| qty = round(amount / price, 4) | |
| if amount > portfolio["cash"]: | |
| return f"❌ Insufficient funds (£{portfolio['cash']:.2f})." | |
| portfolio["cash"] -= amount | |
| portfolio["stocks"][ticker] = portfolio["stocks"].get(ticker, 0) + qty | |
| history.append(f"🟢 Bought £{amount} of {ticker} ({qty} shares at £{price:.2f})") | |
| 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 = get_price(ticker) | |
| if not price: | |
| return f"❌ Couldn't fetch price for {ticker}." | |
| qty_to_sell = round(amount / price, 4) | |
| current_qty = portfolio["stocks"].get(ticker, 0) | |
| if qty_to_sell > current_qty: | |
| return f"❌ You don't own enough of {ticker}." | |
| portfolio["stocks"][ticker] -= qty_to_sell | |
| portfolio["cash"] += amount | |
| history.append(f"🔴 Sold £{amount} of {ticker} ({qty_to_sell} shares at £{price:.2f})") | |
| update_portfolio_history() | |
| return f"✅ Sold {qty_to_sell} shares of {ticker} at £{price:.2f}" | |
| elif "portfolio" in user_input: | |
| cash = portfolio['cash'] | |
| total_stock_value = 0.0 | |
| summary = [f"💰 Cash: £{cash:.2f}"] | |
| for ticker, qty in portfolio["stocks"].items(): | |
| live_price = 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"\n💼 Total Portfolio Value: £{total_value:.2f}") | |
| return "\n".join(summary) | |
| elif "history" in user_input: | |
| return "\n".join(history[-5:]) or "No trades yet." | |
| elif "reset" in user_input: | |
| portfolio["cash"] = 500.0 | |
| portfolio["stocks"].clear() | |
| history.clear() | |
| portfolio_history.clear() | |
| 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(text): | |
| response = process_input(text) | |
| fig = plot_portfolio_value() | |
| return response, fig | |
| update_portfolio_history() | |
| # ---------- 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; | |
| } | |
| """ | |
| 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") | |
| 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(run_all, inputs=invest_input, outputs=[invest_output, invest_chart]) | |
| 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(fn=lambda: switch_view("chat"), inputs=[], outputs=[Zeno_Chat, Zeno_Investments, active_section]) | |
| btn_mock.click(fn=lambda: switch_view("investments"), inputs=[], outputs=[Zeno_Chat, Zeno_Investments, active_section]) | |
| 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() | |