from reportlab.platypus import SimpleDocTemplate,Paragraph,Spacer,Table,TableStyle,KeepTogether
from reportlab.lib.styles import getSampleStyleSheet,ParagraphStyle
from reportlab.lib.colors import HexColor
from reportlab.lib.enums import TA_CENTER
from reportlab.lib import colors
from reportlab.lib.pagesizes import A4
from pathlib import Path
p=Path(__file__).parent
styles=getSampleStyleSheet();styles.add(ParagraphStyle(name='HeroX',parent=styles['Title'],fontSize=22,leading=28,textColor=HexColor('#123451'),spaceAfter=12));styles.add(ParagraphStyle(name='HeadX',parent=styles['Heading2'],fontSize=12,leading=16,textColor=HexColor('#126b81'),spaceBefore=15,spaceAfter=7));styles.add(ParagraphStyle(name='BodyX',parent=styles['BodyText'],fontSize=9.4,leading=14,spaceAfter=8));styles.add(ParagraphStyle(name='SmallX',parent=styles['BodyText'],fontSize=8,leading=11,textColor=HexColor('#52687b')))
doc=SimpleDocTemplate(str(p/'Informe_Metodologico.pdf'),pagesize=A4,leftMargin=49,rightMargin=49,topMargin=46,bottomMargin=48)
story=[]
def para(s,style='BodyX'):story.append(Paragraph(s,styles[style]))
def head(s):para(s,'HeadX')
para('FINANCIAL RISK LAB','HeroX');para('Proyecto 03 | Nahuel Iván López | Octubre de 2026','SmallX')
para('Análisis cuantitativo de carteras: riesgo, rendimiento, comparación con benchmark, sensibilidad de pesos y validación retrospectiva de Value at Risk.')
head('1. Objetivo y alcance')
para('El proyecto construye un backtest educativo de una cartera de cuatro ETF frente a SPY. El usuario puede modificar los pesos y el horizonte histórico. Los resultados son retrospectivos y no representan operaciones reales ni recomendaciones de inversión.')
head('2. Universo de inversión y asignación inicial')
data=[['ETF','Exposición aproximada','Peso inicial'],['SPY','Acciones estadounidenses (S&P 500)','40%'],['QQQ','Acciones Nasdaq-100','20%'],['IEF','Bonos del Tesoro EE. UU. 7–10 años','25%'],['GLD','Oro','15%']]
t=Table(data,colWidths=[62,310,92],repeatRows=1);t.setStyle(TableStyle([('BACKGROUND',(0,0),(-1,0),HexColor('#123451')),('TEXTCOLOR',(0,0),(-1,0),colors.white),('FONTNAME',(0,0),(-1,0),'Helvetica-Bold'),('ROWBACKGROUNDS',(0,1),(-1,-1),[colors.white,HexColor('#eef5f9')]),('BOTTOMPADDING',(0,0),(-1,-1),8),('TOPPADDING',(0,0),(-1,-1),8),('LINEBELOW',(0,-1),(-1,-1),.5,HexColor('#c9d6e1'))]));story.append(t)
head('3. Datos y trazabilidad')
para('El dashboard solicita un CSV público de precios ajustados desde github.com/manisahni/marketdata, descrito por su publicador como derivado de Yahoo Finance / yfinance. Se trata de una <b>fuente secundaria no auditada independientemente</b>. La fecha efectiva más reciente se informa en la interfaz. Se utilizan únicamente fechas con precios positivos y completos para los cuatro ETF; el intervalo arranca en 2019 y se limita al 8 de octubre de 2026. No se inventan datos si falla la descarga. La fuente remota puede modificarse o dejar de estar disponible.')
head('4. Reglas del backtest')
para('Se calculan rendimientos simples diarios a partir de precios ajustados. La cartera mantiene participaciones que varían con los precios y se rebalancea al final de cada mes a los pesos seleccionados, excepto al cierre final del período. Se descuenta un costo supuesto de 5 puntos básicos (0,05%) por unidad de volumen negociado, calculado como suma de diferencias absolutas de pesos. El benchmark SPY es buy-and-hold sin rebalanceo. Las series parten de USD 100 nominales. No se modelan impuestos, spreads adicionales, deslizamiento, liquidez ni restricciones de ejecución.')
head('5. Métricas financieras')
para('<b>Rentabilidad anualizada compuesta:</b> producto de (1+r) elevado a 252/N menos uno. <b>Volatilidad anual:</b> desvío estándar muestral de rendimientos diarios multiplicado por raíz de 252. <b>Drawdown máximo:</b> caída máxima del patrimonio respecto de su máximo histórico. <b>VaR histórico 95%:</b> negativo del percentil 5 de rendimientos diarios. <b>CVaR 95%:</b> negativo del promedio de rendimientos por debajo de ese percentil. <b>Sharpe:</b> rentabilidad media diaria anualizada dividida por volatilidad anualizada, con tasa libre de riesgo supuesta igual a cero (no observada).')
head('6. Comparación temporal y sensibilidad')
para('Los filtros permiten utilizar el historial completo o ventanas retrospectivas de 1, 3 y 5 años medidas desde la última cotización disponible. Cambiar el período reinicia el patrimonio en USD 100 y recalcula el backtest. Cambiar los pesos también recalcula la cartera. Las ponderaciones deben sumar exactamente 100%, sin posiciones cortas ni apalancamiento.')
head('7. Nube riesgo–rentabilidad y mínima varianza')
para('Se generan 1.500 combinaciones de pesos no negativos cuya suma es uno, usando un generador pseudoaleatorio con semilla fija. Cada punto utiliza la media aritmética anualizada de retornos diarios y la matriz de covarianzas histórica anualizada, sin costos de rebalanceo en la nube. La combinación de menor varianza dentro de la muestra simulada se identifica como <b>mínima varianza aproximada</b>. No se estima la frontera eficiente exacta ni se garantiza optimalidad global. El ejercicio es in-sample y puede sobreajustarse.')
head('8. Backtesting del VaR')
para('Para cada sesión a partir de la número 253 se calcula el VaR histórico del día siguiente con una ventana móvil de los 252 rendimientos anteriores, sin utilizar el rendimiento a evaluar. Se cuenta una excepción cuando el rendimiento observado es inferior al percentil 5 pronosticado. Se informa la tasa de excepciones y se compara descriptivamente con el 5% nominal. No se presenta una prueba estadística formal de cobertura condicional o independencia de excepciones.')
head('9. Riesgos de interpretación y límites')
para('La cartera contiene exposiciones accionarias correlacionadas entre SPY y QQQ. La optimización histórica es sensible a muestra, outliers y régimen de mercado. El VaR/CVaR histórico no asegura una pérdida máxima; los resultados no representan rentabilidad futura. El backtest del VaR se aplica a los retornos de la cartera simulada con los pesos elegidos y puede verse afectado por cambios de composición. Los datos son nominales en USD, sin ajuste inflacionario.')
head('10. Reproducibilidad y verificación')
para('El paquete incluye index.html, app.js, analisis.py, README.md y requirements.txt. La aplicación web realiza el cálculo en el navegador a partir del CSV público; el script Python proporciona una ruta independiente con yfinance y puede producir resultados distintos por diferencias de proveedor, fechas, dividendos o ajustes. Se verificó sintaxis del JavaScript, pero la disponibilidad futura de la fuente remota y la ejecución en cada navegador requieren una prueba posterior al despliegue.')
para('Material educativo de portfolio. No constituye asesoramiento financiero.','SmallX')
def foot(canvas,doc):canvas.setFont('Helvetica',8);canvas.setFillColor(HexColor('#61758a'));canvas.drawString(49,30,'Nahuel Iván López · Financial Risk Lab');canvas.drawRightString(A4[0]-49,30,f'Página {doc.page}')
doc.build(story,onFirstPage=foot,onLaterPages=foot)
