SIP_GoldBees_Backend/strategy_code/zerodha_live_monthly.py
2026-02-01 13:57:30 +00:00

291 lines
7.8 KiB
Python

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