2026-02-01 13:57:30 +00:00

183 lines
4.5 KiB
Python

# 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()