# ========================================================= # SIPXAR LIVE — SINGLE FILE # ========================================================= import os import yfinance as yf import numpy as np import math import sqlite3 import logging import uuid import datetime as dt from datetime import datetime, date from kiteconnect import KiteConnect # ========================================================= # CONFIG # ========================================================= MONTHLY_SIP = 100 ALLOW_AFTER_MARKET_TEST = True # ONLY for testing NIFTY_YF = "NIFTYBEES.NS" GOLD_YF = "GOLDBEES.NS" NIFTY_KITE = "NIFTYBEES" GOLD_KITE = "GOLDBEES" SMA_MONTHS = 36 BASE_EQUITY = 0.60 TILT_MULT = 1.5 MAX_TILT = 0.25 MIN_EQUITY = 0.20 MAX_EQUITY = 0.90 STRATEGY_VERSION = "SIPXAR_v1.0" KITE_API_KEY = "YOUR_API_KEY" KITE_ACCESS_TOKEN = "YOUR_ACCESS_TOKEN" KILL_SWITCH_FILE = "KILL_SWITCH" DB_FILE = "sipxar_live.db" LOG_FILE = f"sipxar_{datetime.now().strftime('%Y_%m')}.log" # ========================================================= # LOGGER # ========================================================= logging.basicConfig( level=logging.INFO, format="%(asctime)s | %(levelname)s | %(message)s", handlers=[ logging.FileHandler(LOG_FILE), logging.StreamHandler() ] ) log = logging.getLogger("SIPXAR") # ========================================================= # LEDGER (SQLite) # ========================================================= def db(): return sqlite3.connect(DB_FILE) def init_db(): with db() as conn: conn.execute(""" CREATE TABLE IF NOT EXISTS runs ( run_date TEXT PRIMARY KEY, run_id TEXT ) """) conn.execute(""" CREATE TABLE IF NOT EXISTS trades ( id INTEGER PRIMARY KEY AUTOINCREMENT, run_id TEXT, run_date TEXT, symbol TEXT, strategy_version TEXT, equity_weight REAL, allocated_amount REAL, price_used REAL, quantity INTEGER, order_id TEXT, order_status TEXT, timestamp TEXT, notes TEXT ) """) def already_ran_today(): today = date.today().isoformat() with db() as conn: cur = conn.execute("SELECT 1 FROM runs WHERE run_date=?", (today,)) return cur.fetchone() is not None def record_run(run_id): with db() as conn: conn.execute( "INSERT INTO runs (run_date, run_id) VALUES (?, ?)", (date.today().isoformat(), run_id) ) def record_trade(**r): with db() as conn: conn.execute(""" INSERT INTO trades ( run_id, run_date, symbol, strategy_version, equity_weight, allocated_amount, price_used, quantity, order_id, order_status, timestamp, notes ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) """, ( r["run_id"], r["run_date"], r["symbol"], r["strategy_version"], r["equity_weight"], r["allocated_amount"], r["price_used"], r["quantity"], r["order_id"], r["order_status"], datetime.now().isoformat(), r.get("notes") )) # ========================================================= # DATA (YFINANCE) # ========================================================= 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()) def fetch_price(ticker): df = yf.download( ticker, period="5d", interval="1d", auto_adjust=True, progress=False ) return float(df["Close"].iloc[-1].item()) # ========================================================= # STRATEGY # ========================================================= def kill_switch_active(): return os.path.exists(KILL_SWITCH_FILE) def market_open(): now = dt.datetime.now().time() return dt.time(9, 15) <= now <= dt.time(15, 30) def sanity_check(n_qty, g_qty): if n_qty <= 0 and g_qty <= 0: raise RuntimeError("Sanity check failed: both quantities zero") def compute_weights(n_price, g_price, n_sma, g_sma): assert isinstance(n_price, float) assert isinstance(g_price, float) assert isinstance(n_sma, float) assert isinstance(g_sma, float) 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 # ========================================================= # ZERODHA EXECUTION # ========================================================= kite = KiteConnect(api_key=KITE_API_KEY) kite.set_access_token(KITE_ACCESS_TOKEN) def place_buy(symbol, qty): if qty <= 0: return None return kite.place_order( variety=kite.VARIETY_REGULAR, exchange=kite.EXCHANGE_NSE, tradingsymbol=symbol, transaction_type=kite.TRANSACTION_TYPE_BUY, quantity=qty, order_type=kite.ORDER_TYPE_MARKET, product=kite.PRODUCT_CNC ) # ========================================================= # MAIN RUN # ========================================================= def main(): init_db() if kill_switch_active(): log.critical("KILL SWITCH ACTIVE — ABORTING EXECUTION") return if not market_open() and not ALLOW_AFTER_MARKET_TEST: log.error("Market closed — aborting") return if not market_open() and ALLOW_AFTER_MARKET_TEST: log.warning("Market closed — TEST MODE override enabled") if already_ran_today(): log.error("ABORT: Strategy already executed today") return run_id = str(uuid.uuid4()) record_run(run_id) log.info(f"Starting SIPXAR LIVE | run_id={run_id}") n_price = fetch_price(NIFTY_YF) g_price = fetch_price(GOLD_YF) n_sma = fetch_sma(NIFTY_YF) g_sma = fetch_sma(GOLD_YF) eq_w, g_w = compute_weights(n_price, g_price, n_sma, g_sma) 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) sanity_check(n_qty, g_qty) log.info(f"Weights → Equity={eq_w:.2f}, Gold={g_w:.2f}") log.info(f"Qty → NIFTY={n_qty}, GOLD={g_qty}") for sym, qty, price, amt in [ (NIFTY_KITE, n_qty, n_price, n_amt), (GOLD_KITE, g_qty, g_price, g_amt) ]: if qty <= 0: log.warning(f"Skipping {sym}, qty=0") continue try: oid = place_buy(sym, qty) log.info(f"Order placed {sym} | order_id={oid}") record_trade( run_id=run_id, run_date=date.today().isoformat(), symbol=sym, strategy_version=STRATEGY_VERSION, equity_weight=eq_w, allocated_amount=amt, price_used=price, quantity=qty, order_id=oid, order_status="PLACED", notes="LIVE order" ) except Exception as e: log.error(f"Order failed {sym}: {e}") record_trade( run_id=run_id, run_date=date.today().isoformat(), symbol=sym, strategy_version=STRATEGY_VERSION, equity_weight=eq_w, allocated_amount=amt, price_used=price, quantity=qty, order_id=None, order_status="FAILED", notes=str(e) ) log.info("SIPXAR LIVE run completed") # ========================================================= if __name__ == "__main__": main()