167 lines
4.6 KiB
Python
167 lines
4.6 KiB
Python
# =========================================================
|
|
# SIPXAR — PAPER TRADING (SESSION STATE ONLY)
|
|
# =========================================================
|
|
|
|
import math
|
|
import numpy as np
|
|
import pandas as pd
|
|
import yfinance as yf
|
|
import streamlit as st
|
|
from datetime import datetime
|
|
|
|
# =========================================================
|
|
# CONFIG
|
|
# =========================================================
|
|
|
|
MONTHLY_SIP = 10000
|
|
|
|
NIFTY = "NIFTYBEES.NS"
|
|
GOLD = "GOLDBEES.NS"
|
|
|
|
SMA_MONTHS = 36
|
|
|
|
BASE_EQUITY = 0.60
|
|
TILT_MULT = 1.5
|
|
MAX_TILT = 0.25
|
|
MIN_EQUITY = 0.20
|
|
MAX_EQUITY = 0.90
|
|
|
|
# =========================================================
|
|
# DATA
|
|
# =========================================================
|
|
|
|
@st.cache_data(ttl=300)
|
|
def fetch_price(ticker):
|
|
df = yf.download(
|
|
ticker,
|
|
period="5d",
|
|
interval="1d",
|
|
auto_adjust=True,
|
|
progress=False
|
|
)
|
|
return float(df["Close"].iloc[-1].item())
|
|
|
|
@st.cache_data(ttl=3600)
|
|
def fetch_sma(ticker):
|
|
df = yf.download(
|
|
ticker,
|
|
period=f"{SMA_MONTHS+2}mo",
|
|
interval="1mo",
|
|
auto_adjust=True,
|
|
progress=False
|
|
).dropna()
|
|
return float(df["Close"].tail(SMA_MONTHS).mean())
|
|
|
|
# =========================================================
|
|
# STRATEGY
|
|
# =========================================================
|
|
|
|
def compute_weights(n_price, g_price, n_sma, g_sma):
|
|
dev_n = (n_price / n_sma) - 1
|
|
dev_g = (g_price / g_sma) - 1
|
|
rel = dev_n - dev_g
|
|
|
|
tilt = np.clip(-rel * TILT_MULT, -MAX_TILT, MAX_TILT)
|
|
eq_w = BASE_EQUITY * (1 + tilt)
|
|
eq_w = min(max(eq_w, MIN_EQUITY), MAX_EQUITY)
|
|
|
|
return eq_w, 1 - eq_w
|
|
|
|
# =========================================================
|
|
# SESSION STATE INIT
|
|
# =========================================================
|
|
|
|
if "nifty_units" not in st.session_state:
|
|
st.session_state.nifty_units = 0
|
|
st.session_state.gold_units = 0
|
|
st.session_state.invested = 0
|
|
st.session_state.ledger = []
|
|
|
|
# =========================================================
|
|
# STREAMLIT UI
|
|
# =========================================================
|
|
|
|
st.set_page_config(page_title="SIPXAR Paper Trading", layout="centered")
|
|
st.title("📊 SIPXAR — Paper Trading (Session Only)")
|
|
|
|
# Fetch market data
|
|
n_price = fetch_price(NIFTY)
|
|
g_price = fetch_price(GOLD)
|
|
n_sma = fetch_sma(NIFTY)
|
|
g_sma = fetch_sma(GOLD)
|
|
|
|
eq_w, g_w = compute_weights(n_price, g_price, n_sma, g_sma)
|
|
|
|
# Market snapshot
|
|
st.subheader("Market Snapshot")
|
|
st.write(f"**NIFTY:** ₹{n_price:.2f}")
|
|
st.write(f"**GOLD:** ₹{g_price:.2f}")
|
|
|
|
# Allocation
|
|
st.subheader("Allocation Weights")
|
|
st.progress(eq_w)
|
|
st.write(f"Equity: **{eq_w:.2%}** | Gold: **{g_w:.2%}**")
|
|
|
|
# =========================================================
|
|
# SIP ACTION
|
|
# =========================================================
|
|
|
|
if st.button("Run Paper SIP (Once)"):
|
|
n_amt = MONTHLY_SIP * eq_w
|
|
g_amt = MONTHLY_SIP * g_w
|
|
|
|
n_qty = math.floor(n_amt / n_price)
|
|
g_qty = math.floor(g_amt / g_price)
|
|
|
|
if n_qty == 0 and g_qty == 0:
|
|
st.warning(
|
|
"SIP amount too small to buy ETF units at current prices."
|
|
)
|
|
else:
|
|
st.session_state.nifty_units += n_qty
|
|
st.session_state.gold_units += g_qty
|
|
st.session_state.invested += MONTHLY_SIP
|
|
|
|
port_value = (
|
|
st.session_state.nifty_units * n_price +
|
|
st.session_state.gold_units * g_price
|
|
)
|
|
|
|
st.session_state.ledger.append({
|
|
"timestamp": datetime.now().strftime("%Y-%m-%d %H:%M:%S"),
|
|
"nifty_units": st.session_state.nifty_units,
|
|
"gold_units": st.session_state.gold_units,
|
|
"invested": st.session_state.invested,
|
|
"portfolio_value": port_value,
|
|
"pnl": port_value - st.session_state.invested
|
|
})
|
|
|
|
st.success("Paper SIP executed")
|
|
|
|
# =========================================================
|
|
# PORTFOLIO VIEW
|
|
# =========================================================
|
|
|
|
if st.session_state.invested > 0:
|
|
port_value = (
|
|
st.session_state.nifty_units * n_price +
|
|
st.session_state.gold_units * g_price
|
|
)
|
|
|
|
pnl = port_value - st.session_state.invested
|
|
|
|
st.subheader("Portfolio Summary")
|
|
st.metric("Total Invested", f"₹{st.session_state.invested:,.0f}")
|
|
st.metric("Portfolio Value", f"₹{port_value:,.0f}")
|
|
st.metric("PnL", f"₹{pnl:,.0f}")
|
|
|
|
df = pd.DataFrame(st.session_state.ledger)
|
|
|
|
st.subheader("Equity Curve")
|
|
st.line_chart(df[["portfolio_value", "invested"]])
|
|
|
|
st.subheader("Ledger")
|
|
st.dataframe(df, use_container_width=True)
|
|
else:
|
|
st.info("No SIPs yet. Click the button to start.")
|