"""Financial Risk Lab: real ETF market data, no simulated observations.
Run: pip install -r requirements.txt && python analisis.py
"""
from pathlib import Path
import json
import numpy as np
import pandas as pd
import yfinance as yf

ROOT=Path(__file__).resolve().parent
START='2019-01-01'; END='2026-10-09'
TICKERS=['SPY','QQQ','IEF','GLD']
WEIGHTS=np.array([.4,.2,.25,.15])
FEE_BPS=5

def download():
    raw=yf.download(TICKERS,start=START,end=END,auto_adjust=True,progress=False,threads=False)
    if raw.empty: raise RuntimeError('Sin cotizaciones. No se generarán resultados ficticios.')
    close=raw['Close'] if isinstance(raw.columns,pd.MultiIndex) else raw
    close=close[TICKERS].dropna()
    if len(close)<252:raise RuntimeError('Datos insuficientes para el análisis.')
    close.to_csv(ROOT/'precios_ajustados.csv',float_format='%.6f')
    return close

def drawdown(series):return series/series.cummax()-1

def metrics(r):
    r=pd.Series(r).dropna()
    wealth=(1+r).cumprod()
    vol=float(r.std(ddof=1)*np.sqrt(252))
    var=float(-r.quantile(.05))
    tail=r[r<=r.quantile(.05)]
    return {'rendimiento_anualizado':float(wealth.iloc[-1]**(252/len(r))-1),
      'volatilidad_anualizada':vol,'sharpe_sin_tasa_libre':float(r.mean()*252/vol) if vol else None,
      'max_drawdown':float(drawdown(wealth).min()),'var_95_diario':var,
      'cvar_95_diario':float(-tail.mean()),'dias':len(r)}

def backtest(prices):
    rets=prices.pct_change().dropna()
    # Portfolio weights drift each day; rebalance to target at month-end,
    # using only prices available at the close of that date.
    holdings=WEIGHTS.copy(); port=[]; turnovers=[]
    for i,(date,row) in enumerate(rets.iterrows()):
        rr=row.to_numpy(float)
        gross=float(holdings@rr)
        post=holdings*(1+rr)/(1+gross)
        nxt=rets.index[i+1] if i+1<len(rets) else None
        rebalance=nxt is not None and nxt.month!=date.month
        turnover=float(np.abs(WEIGHTS-post).sum()) if rebalance else 0.
        cost=turnover*FEE_BPS/10000
        port.append(gross-cost)
        turnovers.append(turnover)
        holdings=WEIGHTS.copy() if rebalance else post
    p=pd.Series(port,index=rets.index,name='Cartera')
    b=rets['SPY'].rename('SPY')
    return p,b,pd.Series(turnovers,index=rets.index)

def main():
    prices=download(); p,b,turnover=backtest(prices)
    comparison=pd.DataFrame({'Cartera':(1+p).cumprod()*100,'SPY':(1+b).cumprod()*100})
    dd=pd.DataFrame({'Cartera':drawdown(comparison['Cartera'])*100,'SPY':drawdown(comparison['SPY'])*100})
    comparison.to_csv(ROOT/'evolucion.csv',float_format='%.6f')
    dd.to_csv(ROOT/'drawdown.csv',float_format='%.6f')
    result={'fuente':'Yahoo Finance mediante yfinance (auto_adjust=True)',
      'periodo_inicio':str(comparison.index.min().date()),'periodo_fin':str(comparison.index.max().date()),
      'tickers':TICKERS,'pesos':WEIGHTS.tolist(),'costo_bps':FEE_BPS,
      'costos_totales_relativos':float((turnover*FEE_BPS/10000).sum()),
      'cartera':metrics(p),'benchmark_spy':metrics(b),
      'correlaciones':prices.pct_change().dropna().corr().round(4).to_dict()}
    (ROOT/'resultados.json').write_text(json.dumps(result,ensure_ascii=False,indent=2),encoding='utf8')
    print('OK:',result['periodo_inicio'],result['periodo_fin'],len(p),'retornos')
if __name__=='__main__':main()
