# app/streamlit_app.py import streamlit as st import time from datetime import datetime from pathlib import Path import pandas as pd from history import load_monthly_close from market import us_market_status from data import fetch_live_price from strategy import allocation from execution import try_execute_sip from state import load_state, save_state from mtm import log_mtm, should_log_mtm if "engine_active" not in st.session_state: st.session_state.engine_active = False SP500 = "SPY" GOLD = "GLD" GOLD_FALLBACK = "IAU" SMA_MONTHS = 36 def get_prices(): try: sp = fetch_live_price("SPY") except Exception as e: st.error(f"SPY price error: {e}") return None, None try: gd = fetch_live_price("GLD") except Exception: try: gd = fetch_live_price("IAU") except Exception as e: st.error(f"Gold price error: {e}") return None, None return sp, gd SIP_AMOUNT = st.number_input("SIP Amount ($)", 100, 5000, 1000) SIP_INTERVAL_SEC = st.number_input("SIP Interval (sec) [TEST]", 30, 3600, 120) st.title("SIPXAR — Phase-1 Safe Engine") market_open, market_time = us_market_status() st.info(f"Market {'OPEN' if market_open else 'CLOSED'} | ET {market_time}") col1, col2 = st.columns(2) with col1: if st.button("START ENGINE"): st.session_state.engine_active = True # HARD RESET for f in [ Path("storage/state.json"), Path("storage/mtm_ledger.csv"), Path("storage/ledger.csv"), ]: if f.exists(): f.unlink() fresh_state = { "total_invested": 0.0, "sp_units": 0.0, "gd_units": 0.0, "last_sip_ts": datetime.utcnow().isoformat(), } save_state(fresh_state) st.success("Engine started") with col2: if st.button("KILL ENGINE"): st.session_state.engine_active = False # HARD RESET from pathlib import Path for f in [ Path("storage/state.json"), Path("storage/mtm_ledger.csv"), Path("storage/ledger.csv"), ]: if f.exists(): f.unlink() st.session_state.clear() st.warning("Engine killed and state wiped") st.stop() if not st.session_state.engine_active: st.info("Engine is stopped. Click START to begin.") st.stop() state = load_state() sp_price, gd_price = get_prices() if sp_price is None or gd_price is None: st.stop() st.write("Engine Prices") st.write("SP Price:", sp_price) st.write("Gold Price:", gd_price) sp_hist = load_monthly_close(SP500) gd_hist = load_monthly_close(GOLD_FALLBACK) sp_sma = sp_hist.rolling(SMA_MONTHS).mean().iloc[-1] gd_sma = gd_hist.rolling(SMA_MONTHS).mean().iloc[-1] eq_w, gd_w = allocation( sp_price=sp_price, gd_price=gd_price, sp_sma=sp_sma, gd_sma=gd_sma ) state, executed = try_execute_sip( now=datetime.utcnow(), market_open=market_open, sip_interval=SIP_INTERVAL_SEC, sip_amount=SIP_AMOUNT, sp_price=sp_price, gd_price=gd_price, eq_w=eq_w, gd_w=gd_w ) MTM_FILE = Path("storage/mtm_ledger.csv") if MTM_FILE.exists(): mtm_df = pd.read_csv(MTM_FILE) else: mtm_df = pd.DataFrame() now = datetime.utcnow() if market_open and should_log_mtm(mtm_df, now): portfolio_value, pnl = log_mtm( sp_units=state["sp_units"], gd_units=state["gd_units"], sp_price=sp_price, gd_price=gd_price, total_invested=state["total_invested"], ) else: # Do NOT log MTM when market is closed portfolio_value = ( state["sp_units"] * sp_price + state["gd_units"] * gd_price ) pnl = portfolio_value - state["total_invested"] st.metric("Total Invested", f"${state['total_invested']:,.2f}") st.metric("SP Units", round(state["sp_units"], 4)) st.metric("Gold Units", round(state["gd_units"], 4)) st.metric("Portfolio Value", f"${portfolio_value:,.2f}") st.metric("Unrealized PnL", f"${pnl:,.2f}", delta=f"{pnl:,.2f}") st.subheader("Equity Curve (Unrealized PnL)") if not mtm_df.empty and len(mtm_df) > 1: mtm_df["timestamp"] = pd.to_datetime(mtm_df["timestamp"]) mtm_df = mtm_df.sort_values("timestamp") mtm_df.set_index("timestamp", inplace=True) st.line_chart( mtm_df["pnl"], height=350, ) else: st.info("Equity curve will appear after sufficient MTM data.") if executed: st.success("SIP Executed") time.sleep(5) st.rerun()