import pandas as pd
import numpy as np
import yfinance as yf

# =========================================================
# CONFIG
# =========================================================

START_DATE = "2010-01-01"
END_DATE   = "2025-12-31"

SIP_START_DATE = "2018-04-01"
SIP_END_DATE   = "2025-10-31"   # set None for "till last data"

MONTHLY_SIP = 100

# Order frequency parameter (N trading days)
ORDER_EVERY_N = 5   # 5 => every 5 trading days, 30 => every 30 trading days

# Whether to keep MONTHLY_SIP constant (recommended)
# True  => scales cash per order so approx monthly investment stays ~MONTHLY_SIP
# False => invests MONTHLY_SIP every order (be careful: for N=5 this is much larger than monthly SIP)
KEEP_MONTHLY_BUDGET_CONSTANT = True

NIFTY = "NIFTYBEES.NS"
GOLD  = "GOLDBEES.NS"

SIP_START_DATE = pd.to_datetime(SIP_START_DATE)
SIP_END_DATE   = pd.to_datetime(SIP_END_DATE) if SIP_END_DATE else None

# =========================================================
# CENTRAL SIP DATE WINDOW
# =========================================================

def in_sip_window(date):
    if date < SIP_START_DATE:
        return False
    if SIP_END_DATE and date > SIP_END_DATE:
        return False
    return True

# =========================================================
# VALUATION (SMA MEAN REVERSION)
# =========================================================

SMA_MONTHS  = 36
TILT_MULT   = 1.5
MAX_TILT    = 0.25
BASE_EQUITY = 0.60

MIN_EQUITY = 0.20
MAX_EQUITY = 0.90

# For daily data
TRADING_DAYS_PER_MONTH = 21
SMA_DAYS = SMA_MONTHS * TRADING_DAYS_PER_MONTH  # ~36 months on daily series

# =========================================================
# DATA LOAD
# =========================================================

def load_price(ticker):
    df = yf.download(
        ticker,
        start=START_DATE,
        end=END_DATE,
        auto_adjust=True,
        progress=False
    )
    if isinstance(df.columns, pd.MultiIndex):
        df.columns = df.columns.get_level_values(0)
    return df["Close"]

prices = pd.DataFrame({
    "NIFTY": load_price(NIFTY),
    "GOLD":  load_price(GOLD)
}).dropna()

# Use business-day frequency (daily for trading)
prices = prices.resample("B").last().dropna()

# =========================================================
# ORDER SCHEDULE: EVERY N TRADING DAYS
# =========================================================

def get_order_dates(index: pd.DatetimeIndex, n: int) -> pd.DatetimeIndex:
    window = index[(index >= SIP_START_DATE) & ((index <= SIP_END_DATE) if SIP_END_DATE else True)]
    return window[::n]

ORDER_DATES = get_order_dates(prices.index, ORDER_EVERY_N)

# Cash per order
if KEEP_MONTHLY_BUDGET_CONSTANT:
    # Approx monthly orders = 21 / N
    orders_per_month = TRADING_DAYS_PER_MONTH / ORDER_EVERY_N
    SIP_AMOUNT_PER_ORDER = MONTHLY_SIP / orders_per_month
else:
    SIP_AMOUNT_PER_ORDER = MONTHLY_SIP

# =========================================================
# (OPTIONAL) CHECK PRICE ON A GIVEN DATE (UNCHANGED)
# =========================================================

def check_price_on_date(ticker, date_str):
    date = pd.to_datetime(date_str)

    df = yf.download(
        ticker,
        start=date - pd.Timedelta(days=5),
        end=date + pd.Timedelta(days=5),
        auto_adjust=False,
        progress=False
    )

    if df.empty:
        print(f"No data returned for {ticker}")
        return

    if isinstance(df.columns, pd.MultiIndex):
        df.columns = df.columns.get_level_values(0)

    if date not in df.index:
        print(f"{ticker} | {date.date()} → No trading data (holiday / no volume)")
        return

    row = df.loc[date]

    print(f"\n{ticker} — {date.date()}")
    print(f"Open : ₹{row['Open']:.2f}")
    print(f"High : ₹{row['High']:.2f}")
    print(f"Low  : ₹{row['Low']:.2f}")
    print(f"Close: ₹{row['Close']:.2f}")
    print(f"Volume: {int(row['Volume'])}")

check_price_on_date("NIFTYBEES.NS", "2025-11-10")

# =========================================================
# SMA DEVIATION (DAILY)
# =========================================================

sma_nifty = prices["NIFTY"].rolling(SMA_DAYS).mean()
sma_gold  = prices["GOLD"].rolling(SMA_DAYS).mean()

dev_nifty = (prices["NIFTY"] / sma_nifty) - 1
dev_gold  = (prices["GOLD"] / sma_gold) - 1

rel_dev = dev_nifty - dev_gold

# =========================================================
# SIPXAR ENGINE (FLOW ONLY, NO REBALANCE) — EXECUTES ON ORDER_DATES
# =========================================================

def run_sipxar(prices, rel_dev, order_dates, sip_amount_per_order):

    nifty_units = 0.0
    gold_units  = 0.0

    total_invested = 0.0
    prev_value = None

    rows = []

    for date in order_dates:

        nifty_price = prices.loc[date, "NIFTY"]
        gold_price  = prices.loc[date, "GOLD"]

        cash = float(sip_amount_per_order)
        total_invested += cash

        rd = rel_dev.loc[date]

        tilt = 0.0
        if not pd.isna(rd):
            tilt = np.clip(-rd * TILT_MULT, -MAX_TILT, MAX_TILT)

        equity_w = BASE_EQUITY * (1 + tilt)
        equity_w = min(max(equity_w, MIN_EQUITY), MAX_EQUITY)
        gold_w   = 1 - equity_w

        nifty_buy = cash * equity_w
        gold_buy  = cash * gold_w

        nifty_units += nifty_buy / nifty_price
        gold_units  += gold_buy  / gold_price

        nifty_val = nifty_units * nifty_price
        gold_val  = gold_units  * gold_price
        port_val  = nifty_val + gold_val

        unrealized = port_val - total_invested

        if prev_value is None:
            period_pnl = 0.0
        else:
            period_pnl = port_val - prev_value - cash

        prev_value = port_val

        rows.append({
            "Date": date,
            "Cash_Added": round(cash, 4),
            "Total_Invested": round(total_invested, 4),

            "Equity_Weight": round(equity_w, 3),
            "Gold_Weight": round(gold_w, 3),

            "Rel_Deviation": round(float(rd) if not pd.isna(rd) else np.nan, 4),

            "NIFTY_Units": round(nifty_units, 6),
            "GOLD_Units": round(gold_units, 6),

            "NIFTY_Value": round(nifty_val, 4),
            "GOLD_Value": round(gold_val, 4),

            "Portfolio_Value": round(port_val, 4),
            "Period_PnL": round(period_pnl, 4),
            "Unrealized_PnL": round(unrealized, 4)
        })

    return pd.DataFrame(rows).set_index("Date")

# =========================================================
# RUN SIPXAR
# =========================================================

sipxar_ledger = run_sipxar(
    prices=prices,
    rel_dev=rel_dev,
    order_dates=ORDER_DATES,
    sip_amount_per_order=SIP_AMOUNT_PER_ORDER
)

start_dt = sipxar_ledger.index.min()
end_dt   = sipxar_ledger.index.max()

# =========================================================
# XIRR
# =========================================================

def xirr(cashflows):
    dates = np.array([cf[0] for cf in cashflows], dtype="datetime64[D]")
    amounts = np.array([cf[1] for cf in cashflows], dtype=float)

    def npv(rate):
        years = (dates - dates[0]).astype(int) / 365.25
        return np.sum(amounts / ((1 + rate) ** years))

    low, high = -0.99, 5.0
    for _ in range(200):
        mid = (low + high) / 2
        val = npv(mid)
        if abs(val) < 1e-6:
            return mid
        if val > 0:
            low = mid
        else:
            high = mid

    return mid

cashflows_sipxar = []
for date, row in sipxar_ledger.iterrows():
    cashflows_sipxar.append((date, -row["Cash_Added"]))

final_date = sipxar_ledger.index[-1]
final_value = sipxar_ledger["Portfolio_Value"].iloc[-1]
cashflows_sipxar.append((final_date, final_value))

sipxar_xirr = xirr(cashflows_sipxar)

# =========================================================
# COMPARISONS: NIFTY-ONLY + STATIC 60/40 — EXECUTES ON ORDER_DATES
# =========================================================

def run_nifty_sip(prices, order_dates, sip_amount):
    units = 0.0
    rows = []
    for date in order_dates:
        price = prices.loc[date, "NIFTY"]
        units += sip_amount / price
        rows.append((date, units * price))
    return pd.DataFrame(rows, columns=["Date", "Value"]).set_index("Date")

def run_static_sip(prices, order_dates, sip_amount, eq_w=0.6):
    n_units = 0.0
    g_units = 0.0
    rows = []
    for date in order_dates:
        n_price = prices.loc[date, "NIFTY"]
        g_price = prices.loc[date, "GOLD"]

        n_units += (sip_amount * eq_w) / n_price
        g_units += (sip_amount * (1 - eq_w)) / g_price

        rows.append((date, n_units * n_price + g_units * g_price))
    return pd.DataFrame(rows, columns=["Date", "Value"]).set_index("Date")

def build_sip_ledger(value_df, sip_amount):
    total = 0.0
    rows = []
    prev_value = None

    for date, row in value_df.iterrows():
        total += sip_amount
        value = float(row.iloc[0])

        if prev_value is None:
            period_pnl = 0.0
        else:
            period_pnl = value - prev_value - sip_amount

        prev_value = value

        rows.append({
            "Date": date,
            "Cash_Added": round(sip_amount, 4),
            "Total_Invested": round(total, 4),
            "Portfolio_Value": round(value, 4),
            "Period_PnL": round(period_pnl, 4)
        })

    return pd.DataFrame(rows).set_index("Date")

nifty_sip = run_nifty_sip(prices, ORDER_DATES, SIP_AMOUNT_PER_ORDER)
static_sip = run_static_sip(prices, ORDER_DATES, SIP_AMOUNT_PER_ORDER, 0.6)

nifty_sip  = nifty_sip.loc[start_dt:end_dt]
static_sip = static_sip.loc[start_dt:end_dt]

nifty_ledger  = build_sip_ledger(nifty_sip, SIP_AMOUNT_PER_ORDER)
static_ledger = build_sip_ledger(static_sip, SIP_AMOUNT_PER_ORDER)

cashflows_nifty = [(d, -SIP_AMOUNT_PER_ORDER) for d in nifty_sip.index]
cashflows_nifty.append((nifty_sip.index[-1], float(nifty_sip["Value"].iloc[-1])))
nifty_xirr = xirr(cashflows_nifty)

cashflows_static = [(d, -SIP_AMOUNT_PER_ORDER) for d in static_sip.index]
cashflows_static.append((static_sip.index[-1], float(static_sip["Value"].iloc[-1])))
static_xirr = xirr(cashflows_static)

# =========================================================
# PRINTS
# =========================================================

print("\n=== CONFIG SUMMARY ===")
print(f"Order every N trading days: {ORDER_EVERY_N}")
print(f"Keep monthly budget constant: {KEEP_MONTHLY_BUDGET_CONSTANT}")
print(f"MONTHLY_SIP (target): ₹{MONTHLY_SIP}")
print(f"SIP_AMOUNT_PER_ORDER: ₹{SIP_AMOUNT_PER_ORDER:.4f}")
print(f"Orders executed: {len(ORDER_DATES)}")
print(f"Period: {start_dt.date()} → {end_dt.date()}")

print("\n=== SIPXAR LEDGER (LAST 12 ROWS) ===")
print(sipxar_ledger.tail(12))

print("\n=== EQUITY WEIGHT DISTRIBUTION ===")
print(sipxar_ledger["Equity_Weight"].describe())

print("\n=== STEP 1: XIRR COMPARISON ===")
print(f"SIPXAR XIRR     : {sipxar_xirr*100:.2f}%")
print(f"NIFTY SIP XIRR  : {nifty_xirr*100:.2f}%")
print(f"60/40 SIP XIRR  : {static_xirr*100:.2f}%")

# =========================================================
# EXPORT
# =========================================================

output_file = "SIPXAR_Momentum_SIP.xlsx"

with pd.ExcelWriter(output_file, engine="xlsxwriter") as writer:
    sipxar_ledger.to_excel(writer, sheet_name="Ledger")

    yearly = sipxar_ledger.copy()
    yearly["Year"] = yearly.index.year

    yearly_summary = yearly.groupby("Year").agg({
        "Cash_Added": "sum",
        "Total_Invested": "last",
        "Portfolio_Value": "last",
        "Unrealized_PnL": "last"
    })
    yearly_summary.to_excel(writer, sheet_name="Yearly_Summary")

print(f"\nExcel exported successfully: {output_file}")

# =========================================================
# PHASE 2: CRASH & SIDEWAYS REGIME BACKTEST (ADAPTED TO DAILY INDEX)
# =========================================================

def rolling_cagr(series: pd.Series, periods: int, years: float):
    r = series / series.shift(periods)
    return (r ** (1 / years)) - 1

def window_xirr_from_value(value_df, start, end, sip_amount):
    df = value_df.loc[start:end]
    if len(df) < 6:
        return np.nan
    cashflows = [(d, -sip_amount) for d in df.index]
    cashflows.append((df.index[-1], float(df.iloc[-1, 0])))
    return xirr(cashflows)

def sip_max_drawdown(ledger):
    value = ledger["Portfolio_Value"]
    peak = value.cummax()
    dd = value / peak - 1
    trough = dd.idxmin()
    peak_date = value.loc[:trough].idxmax()
    return {"Peak": peak_date, "Trough": trough, "Max_Drawdown": float(dd.min())}

def worst_rolling_xirr(ledger, periods: int):
    dates = ledger.index
    worst = None

    for i in range(len(dates) - periods):
        start = dates[i]
        end = dates[i + periods]
        window = ledger.loc[start:end]
        if len(window) < max(6, periods // 2):
            continue

        cashflows = [(d, -float(row["Cash_Added"])) for d, row in window.iterrows()]
        cashflows.append((end, float(window["Portfolio_Value"].iloc[-1])))

        try:
            rx = xirr(cashflows)
            if worst is None or rx < worst.get("XIRR", np.inf):
                worst = {"Start": start, "End": end, "XIRR": rx}
        except Exception:
            pass

    return worst

# 1) Identify crash windows from NIFTY drawdowns (daily)
nifty_price = prices["NIFTY"].loc[
    (prices.index >= SIP_START_DATE) &
    ((prices.index <= SIP_END_DATE) if SIP_END_DATE else True)
]

peak = nifty_price.cummax()
drawdown = nifty_price / peak - 1.0

CRASH_THRESHOLD = -0.15
in_crash = drawdown <= CRASH_THRESHOLD

crash_windows = []
groups = (in_crash != in_crash.shift()).cumsum()

for _, block in in_crash.groupby(groups):
    if block.iloc[0]:
        crash_windows.append((block.index[0], block.index[-1]))

print("\n=== CRASH WINDOWS (NIFTY DD <= -15%) ===")
for s, e in crash_windows:
    print(s.date(), "->", e.date())

# 2) Sideways windows using ~36M rolling CAGR on daily data
ROLL_MONTHS = 36
SIDEWAYS_CAGR = 0.06

ROLL_DAYS = ROLL_MONTHS * TRADING_DAYS_PER_MONTH
cagr_36 = rolling_cagr(nifty_price, periods=ROLL_DAYS, years=ROLL_MONTHS / 12.0)

in_sideways = cagr_36 <= SIDEWAYS_CAGR

sideways_windows = []
groups = (in_sideways != in_sideways.shift()).cumsum()

for _, block in in_sideways.groupby(groups):
    if block.iloc[0]:
        sideways_windows.append((block.index[0], block.index[-1]))

print("\n=== SIDEWAYS WINDOWS (~36M CAGR <= 6%) ===")
for s, e in sideways_windows:
    print(s.date(), "->", e.date())

# 3) Score each regime window using order-based ledgers
rows = []
for label, windows in [("CRASH", crash_windows), ("SIDEWAYS", sideways_windows)]:
    for s, e in windows:
        # align to nearest available ledger dates
        s2 = sipxar_ledger.index[sipxar_ledger.index.get_indexer([s], method="nearest")[0]]
        e2 = sipxar_ledger.index[sipxar_ledger.index.get_indexer([e], method="nearest")[0]]

        months_like = (e2.year - s2.year) * 12 + (e2.month - s2.month) + 1

        rows.append({
            "Regime": label,
            "Start": s2.date(),
            "End": e2.date(),
            "MonthsLike": months_like,
            "SIPXAR_XIRR": window_xirr_from_value(sipxar_ledger[["Portfolio_Value"]], s2, e2, SIP_AMOUNT_PER_ORDER),
            "NIFTY_SIP_XIRR": window_xirr_from_value(nifty_sip, s2, e2, SIP_AMOUNT_PER_ORDER),
            "STATIC_60_40_XIRR": window_xirr_from_value(static_sip, s2, e2, SIP_AMOUNT_PER_ORDER)
        })

regime_results = pd.DataFrame(rows)

print("\n=== REGIME PERFORMANCE SUMMARY ===")
if len(regime_results) == 0:
    print("No regime windows detected (check thresholds / data range).")
else:
    print(regime_results.to_string(index=False))

# =========================================================
# METRIC 1: TIME UNDERWATER
# =========================================================

sipxar_ledger["Underwater"] = (sipxar_ledger["Portfolio_Value"] < sipxar_ledger["Total_Invested"])
periods_underwater = int(sipxar_ledger["Underwater"].sum())

print("\n=== TIME UNDERWATER ===")
print(f"Periods underwater: {periods_underwater} / {len(sipxar_ledger)}")
print(f"% Time underwater : {periods_underwater / len(sipxar_ledger) * 100:.1f}%")

# =========================================================
# METRIC 2: SIP-AWARE MAX DRAWDOWN
# =========================================================

dd_sipxar = sip_max_drawdown(sipxar_ledger)
dd_nifty  = sip_max_drawdown(nifty_ledger)
dd_static = sip_max_drawdown(static_ledger)

print("\n=== SIP-AWARE MAX DRAWDOWN ===")
for name, dd in [("SIPXAR", dd_sipxar), ("NIFTY SIP", dd_nifty), ("60/40 SIP", dd_static)]:
    print(
        f"{name:10s} | "
        f"Peak: {dd['Peak'].date()} | "
        f"Trough: {dd['Trough'].date()} | "
        f"DD: {dd['Max_Drawdown']*100:.2f}%"
    )

# =========================================================
# METRIC 3: WORST ROLLING 24M SIP XIRR (ORDER-BASED)
# =========================================================

# Convert 24 months to "order periods" approximately
ORDERS_PER_MONTH = TRADING_DAYS_PER_MONTH / ORDER_EVERY_N
ROLL_24M_PERIODS = int(round(24 * ORDERS_PER_MONTH))
ROLL_24M_PERIODS = max(6, ROLL_24M_PERIODS)

worst_24_sipxar = worst_rolling_xirr(sipxar_ledger, ROLL_24M_PERIODS)
worst_24_nifty  = worst_rolling_xirr(nifty_ledger, ROLL_24M_PERIODS)
worst_24_static = worst_rolling_xirr(static_ledger, ROLL_24M_PERIODS)

print("\n=== WORST ROLLING ~24M XIRR (by order periods) ===")
print(f"Using {ROLL_24M_PERIODS} periods (~24 months) given N={ORDER_EVERY_N}")
for name, w in [("SIPXAR", worst_24_sipxar), ("NIFTY SIP", worst_24_nifty), ("60/40 SIP", worst_24_static)]:
    if not w or pd.isna(w.get("XIRR", np.nan)):
        print(f"{name:10s} | insufficient data")
        continue
    print(f"{name:10s} | {w['Start'].date()} → {w['End'].date()} | {w['XIRR']*100:.2f}%")

# =========================================================
# METRIC 4: PnL VOLATILITY (PER-ORDER)
# =========================================================

period_pnl = sipxar_ledger["Period_PnL"]
pnl_std = float(period_pnl.std())
pnl_mean = float(period_pnl.mean())

print("\n=== PnL VOLATILITY (PER ORDER PERIOD) ===")
print(f"Avg Period PnL : ₹{pnl_mean:,.2f}")
print(f"PnL Std Dev    : ₹{pnl_std:,.2f}")
print(f"Volatility %   : {pnl_std / SIP_AMOUNT_PER_ORDER * 100:.1f}% of per-order SIP")

# =========================================================
# SIP GRAPH: INVESTED vs PORTFOLIO VALUE (HEADLESS SAFE)
# =========================================================

import matplotlib
matplotlib.use("Agg")
import matplotlib.pyplot as plt

sipxar_ledger["Invested_Capital"] = sipxar_ledger["Total_Invested"]

plt.figure(figsize=(10, 5))
plt.plot(sipxar_ledger.index, sipxar_ledger["Portfolio_Value"], label="Portfolio Value")
plt.plot(sipxar_ledger.index, sipxar_ledger["Invested_Capital"], label="Total Invested")

plt.xlabel("Date")
plt.ylabel("Value (₹)")
plt.title(f"SIPXAR – SIP Performance (Every {ORDER_EVERY_N} Trading Days)")
plt.legend()
plt.tight_layout()

plt.savefig("sipxar_performance.png", dpi=150)
plt.close()

print("Plot saved: sipxar_performance.png")
