Compare commits
3 commits
16dba11b21
...
d19f970d48
| Author | SHA1 | Date | |
|---|---|---|---|
| d19f970d48 | |||
| 81b79a225f | |||
| efc4c54972 |
10 changed files with 1290 additions and 4 deletions
31
CLAUDE.md
31
CLAUDE.md
|
|
@ -10,9 +10,12 @@ Personal homepage built with Zola.
|
||||||
- `content/` — Markdown content
|
- `content/` — Markdown content
|
||||||
- `blog/` — Blog posts
|
- `blog/` — Blog posts
|
||||||
- `lib/` — Library/reference articles
|
- `lib/` — Library/reference articles
|
||||||
- `triforce_strategies/` — Triforce Strategies LLC content
|
- `triforce-strategies/` — Triforce Strategies LLC content
|
||||||
|
- `tools/` — Interactive calculators/tools
|
||||||
|
- `templates/shortcodes/` — Custom shortcodes (override theme)
|
||||||
|
- `static/js/` — JavaScript files
|
||||||
|
- `static/css/` — CSS files
|
||||||
- `themes/tabi-lean/` — Theme (git subtree, do not edit directly)
|
- `themes/tabi-lean/` — Theme (git subtree, do not edit directly)
|
||||||
- `static/` — Static assets
|
|
||||||
- `config.toml` — Site configuration
|
- `config.toml` — Site configuration
|
||||||
|
|
||||||
## Content Conventions
|
## Content Conventions
|
||||||
|
|
@ -20,5 +23,29 @@ Personal homepage built with Zola.
|
||||||
- Section indices: `_index.md`
|
- Section indices: `_index.md`
|
||||||
- Internal links: `@/path/to/file.md`
|
- Internal links: `@/path/to/file.md`
|
||||||
|
|
||||||
|
## Adding Interactive Tools
|
||||||
|
|
||||||
|
To add a new tool under `triforce-strategies/tools/`:
|
||||||
|
|
||||||
|
1. **Create shortcode**: `templates/shortcodes/tool_name.html`
|
||||||
|
- HTML structure (form, canvas, results container)
|
||||||
|
- Load JS via `{{ get_url(path='js/toolName.js') | safe }}`
|
||||||
|
- Include `<noscript>` fallback
|
||||||
|
|
||||||
|
2. **Create JS**: `static/js/toolName.js`
|
||||||
|
- Wrap in IIFE: `(function() { 'use strict'; ... })();`
|
||||||
|
- Use theme CSS variables for colors (--text-color, --primary-color, etc.)
|
||||||
|
- Scale canvas for retina: `window.devicePixelRatio`
|
||||||
|
|
||||||
|
3. **Create CSS**: `static/css/tool-name.css`
|
||||||
|
- Use theme CSS variables from `themes/tabi-lean/sass/main.scss`
|
||||||
|
- Responsive layout with media queries
|
||||||
|
|
||||||
|
4. **Create page**: `content/triforce-strategies/tools/tool_name.md`
|
||||||
|
- Frontmatter: `[extra] stylesheets = ["css/tool-name.css"]`
|
||||||
|
- Invoke shortcode: `{{ tool_name() }}`
|
||||||
|
|
||||||
|
Example: `snp500_vs_anything` (fund comparison calculator with Monte Carlo simulation)
|
||||||
|
|
||||||
## Commit Style
|
## Commit Style
|
||||||
`type: description` (e.g., `blog: add post title`)
|
`type: description` (e.g., `blog: add post title`)
|
||||||
|
|
|
||||||
|
|
@ -10,6 +10,10 @@ author = "Fabian Montero"
|
||||||
build_search_index = true
|
build_search_index = true
|
||||||
generate_sitemap = true
|
generate_sitemap = true
|
||||||
|
|
||||||
|
taxonomies = [
|
||||||
|
{ name = "tags" },
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
[markdown]
|
[markdown]
|
||||||
highlight_code = true
|
highlight_code = true
|
||||||
|
|
@ -65,9 +69,9 @@ feed_icon = true
|
||||||
menu = [
|
menu = [
|
||||||
{name = "lib", url = "lib"},
|
{name = "lib", url = "lib"},
|
||||||
{name = "blog", url = "blog"},
|
{name = "blog", url = "blog"},
|
||||||
{name = "leaderboard", url = "leaderboard" },
|
|
||||||
{name = "git", url = "https://git.posixlycorrect.com"},
|
{name = "git", url = "https://git.posixlycorrect.com"},
|
||||||
{name = "recipes", url = "https://food.posixlycorrect.com"},
|
{name = "triforce strategies", url = "triforce-strategies"},
|
||||||
|
{name = "leaderboard", url = "leaderboard" },
|
||||||
]
|
]
|
||||||
|
|
||||||
footer_menu = [
|
footer_menu = [
|
||||||
|
|
@ -76,6 +80,7 @@ footer_menu = [
|
||||||
{name = "wiki", url = "https://wiki.posixlycorrect.com"},
|
{name = "wiki", url = "https://wiki.posixlycorrect.com"},
|
||||||
{name = "notes", url = "https://notes.posixlycorrect.com"},
|
{name = "notes", url = "https://notes.posixlycorrect.com"},
|
||||||
{name = "ebooks", url = "https://calibre.posixlycorrect.com"},
|
{name = "ebooks", url = "https://calibre.posixlycorrect.com"},
|
||||||
|
{name = "recipes", url = "https://food.posixlycorrect.com"},
|
||||||
{name = "news", url = "https://rss.posixlycorrect.com"},
|
{name = "news", url = "https://rss.posixlycorrect.com"},
|
||||||
{name = "photos", url = "https://photos.posixlycorrect.com"},
|
{name = "photos", url = "https://photos.posixlycorrect.com"},
|
||||||
{name = "sitemap", url = "sitemap.xml", trailing_slash = false},
|
{name = "sitemap", url = "sitemap.xml", trailing_slash = false},
|
||||||
|
|
|
||||||
5
content/triforce-strategies/_index.md
Normal file
5
content/triforce-strategies/_index.md
Normal file
|
|
@ -0,0 +1,5 @@
|
||||||
|
+++
|
||||||
|
title = "triforce strategies archive"
|
||||||
|
sort_by = "date"
|
||||||
|
paginate_by = 10
|
||||||
|
+++
|
||||||
160
content/triforce-strategies/fondos_inversion_bn_bac.md
Normal file
160
content/triforce-strategies/fondos_inversion_bn_bac.md
Normal file
|
|
@ -0,0 +1,160 @@
|
||||||
|
+++
|
||||||
|
title = "Fondos de Inversión en Costa Rica: BN ETF 500 vs Millennium BAC"
|
||||||
|
date = 2026-03-25
|
||||||
|
description = "Comparación entre dos fondos de inversión disponibles en Costa Rica para acceder al S&P 500 y mercados internacionales."
|
||||||
|
|
||||||
|
[taxonomies]
|
||||||
|
tags = ["inversiones", "etfs", "costa-rica", "sp500", "bac", "bn"]
|
||||||
|
|
||||||
|
[extra]
|
||||||
|
author = "fabian"
|
||||||
|
isso = true
|
||||||
|
+++
|
||||||
|
|
||||||
|
## ¿Qué es un fondo de inversión?
|
||||||
|
|
||||||
|
Uno le da la plata al banco y ellos la invierten en algo que hopefully genera ganancias. En este caso, es SPY.
|
||||||
|
|
||||||
|
- **Fondo abierto:** Puede entrar y salir cuando quiera. El número de participaciones es variable.
|
||||||
|
- **Valor de participación:** Se calcula diariamente con base en el valor neto del fondo (NAV). Tu ganancia o pérdida es la diferencia entre el valor al entrar y al salir.
|
||||||
|
- **Crecimiento:** Las utilidades no se distribuyen; se reinvierten, aumentando el valor de la participación.
|
||||||
|
- **Plazo de liquidación:** Al solicitar un retiro, el reembolso tarda T+5.
|
||||||
|
- **SAFI:** Sociedad Administradora de Fondos de Inversión. La entidad que gestiona el fondo, regulada por SUGEVAL.
|
||||||
|
|
||||||
|
## Marco regulatorio e impuestos
|
||||||
|
|
||||||
|
### Regulación
|
||||||
|
|
||||||
|
SUGEVAL supervisa todas las SAFIs y fondos de inversión en Costa Rica. Los fondos deben estar inscritos en el Registro Nacional de Valores e Intermediarios.
|
||||||
|
|
||||||
|
**Ojo:** Autorización de oferta pública NO implica opinión sobre el fondo ni sobre la SAFI. La gestión financiera del fondo es independiente de la entidad bancaria del grupo.
|
||||||
|
|
||||||
|
### Impuestos
|
||||||
|
|
||||||
|
La Ley 9635 (Fortalecimiento de las Finanzas Públicas) reformó el régimen fiscal desde julio 2019. Los fondos pagan impuesto sobre rentas de capital y ganancias/pérdidas de capital. Para activos extranjeros aplica el régimen fiscal del país donde se invierte.
|
||||||
|
|
||||||
|
Es su responsabilidad verificar tu tratamiento tributario particular.
|
||||||
|
|
||||||
|
## Gestión pasiva vs. gestión activa
|
||||||
|
|
||||||
|
| | Gestión pasiva | Gestión activa |
|
||||||
|
|---|---|---|
|
||||||
|
| Estrategia | Replica un índice (ej. S&P 500) | Gestor toma decisiones de compra/venta |
|
||||||
|
| Objetivo | No intenta superar al mercado | Busca retornos con base en análisis de mercado |
|
||||||
|
| Comisiones | Más bajas | Más altas (pagan al gestor) |
|
||||||
|
| Transparencia | Total: sabés exactamente qué comprás | Diversificación geográfica gestionada |
|
||||||
|
| En caídas | El fondo cae igual | Puede proteger la cartera (o no) |
|
||||||
|
| **Ejemplo** | **BN ETF 500** | **Millennium BAC** |
|
||||||
|
|
||||||
|
## BN ETF 500
|
||||||
|
|
||||||
|
#### Prospecto: [Prospecto BN ETF500 nd.pdf](https://www.bncr.fi.cr/hubfs/archives/Prospecto%20BN%20ETF500%20nd.pdf).
|
||||||
|
|
||||||
|
*Banco Nacional · BN Fondos*
|
||||||
|
|
||||||
|
| | |
|
||||||
|
|---|---|
|
||||||
|
| Inversión mínima | $100 |
|
||||||
|
| Comisión anual | 1% |
|
||||||
|
| Estrategia de gestión | Pasiva |
|
||||||
|
| Perfil de riesgo | Agresivo |
|
||||||
|
| Autorizado | 2025-01 |
|
||||||
|
|
||||||
|
### Objetivo
|
||||||
|
|
||||||
|
Replicar el comportamiento del Índice S&P 500 por medio de ETFs (VOO, SPY). No busca generar retornos más allá del seguimiento del índice.
|
||||||
|
|
||||||
|
**Composición:** Hasta 98% de la cartera es pasiva; un 10% puede invertirse en valores de deuda para manejo de liquidez.
|
||||||
|
|
||||||
|
### Composición de cartera
|
||||||
|
La solicité y me la dieron, pero el correo es confidencial. Si la quiere ver, acérquese a una ventanilla del BN y pregunte. Básicamente son inversiones enormes en fondos que trackean el S&P500.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## Millennium BAC
|
||||||
|
|
||||||
|
#### Prospecto: [cr_prospecto_millennium_ene_2026.pdf](https://www.baccredomatic.com/sites/default/files/2026-01/cr_prospecto_millennium_ene_2026.pdf).
|
||||||
|
|
||||||
|
*BAC Credomatic · BAC San José SFI*
|
||||||
|
|
||||||
|
| | |
|
||||||
|
|---|---|
|
||||||
|
| Inversión mínima | $250 |
|
||||||
|
| Comisión anual | 1.25% |
|
||||||
|
| Estrategia de gestión | Activa |
|
||||||
|
| Perfil de riesgo | Moderado |
|
||||||
|
| Operando desde | 2006-01 |
|
||||||
|
|
||||||
|
### Modelo de negocio
|
||||||
|
|
||||||
|
Fondo de crecimiento con gestión activa. Invierte en participaciones de otros fondos de inversión y ETFs internacionales (fund-of-funds).
|
||||||
|
|
||||||
|
- **Gestor:** Ronald Rojas — toma decisiones de compra y venta buscando retornos
|
||||||
|
- **Calificación:** AArv.cr por Moody's
|
||||||
|
- **Activo neto:** ~$35.98M USD
|
||||||
|
|
||||||
|
### Distribución geográfica
|
||||||
|
|
||||||
|
| Región | % |
|
||||||
|
|---|---:|
|
||||||
|
| Estados Unidos | 62% |
|
||||||
|
| Europa | 14% |
|
||||||
|
| Efectivo | 9% |
|
||||||
|
| Asia | 8% |
|
||||||
|
| Mercados Emergentes | 7% |
|
||||||
|
|
||||||
|
**Rendimiento 12 meses: 15.35%** (2026-01)
|
||||||
|
|
||||||
|
### Rendimientos históricos
|
||||||
|
|
||||||
|
*Rendimientos netos anuales (%). Datos del prospecto y ficha comercial.*
|
||||||
|
|
||||||
|
| Mes | 2024 | 2025 |
|
||||||
|
|---|---:|---:|
|
||||||
|
| Ene | 10.03 | 13.67 |
|
||||||
|
| Feb | 16.63 | 9.84 |
|
||||||
|
| Mar | 17.41 | 4.51 |
|
||||||
|
| Abr | 12.63 | 7.09 |
|
||||||
|
| May | 16.20 | 9.30 |
|
||||||
|
| Jun | 14.34 | 11.88 |
|
||||||
|
| Jul | 13.08 | 9.89 |
|
||||||
|
| Ago | 17.46 | 10.54 |
|
||||||
|
| Sep | 24.51 | 11.65 |
|
||||||
|
| Oct | 25.02 | 15.41 |
|
||||||
|
| Nov | 20.83 | 12.45 |
|
||||||
|
| Dic | 11.54 | 17.05 |
|
||||||
|
|
||||||
|
## Comparación directa
|
||||||
|
|
||||||
|
| Dimensión | BN ETF 500 | Millennium BAC |
|
||||||
|
|---|---|---|
|
||||||
|
| Gestor | BN Fondos (Banco Nacional) | BAC San José SFI |
|
||||||
|
| Estrategia | Pasiva (replica S&P 500) | Activa (fund-of-funds) |
|
||||||
|
| Comisión anual | **1.00%** | 1.25% |
|
||||||
|
| Inversión mínima | **$100** | $250 |
|
||||||
|
| Diversificación geográfica | Solo EE.UU. (S&P 500) | **EE.UU., Europa, Asia, EM** |
|
||||||
|
| Track record | ~1 año (desde 2025-01) | **20 años (desde 2006)** |
|
||||||
|
| Activo neto | ~$12.5M | **~$36M** |
|
||||||
|
| Calificación | Pendiente (< 12 meses) | **AArv.cr (Moody's)** |
|
||||||
|
| Transparencia de cartera | **100% visible (3 ETFs + cash)** | Holdings detallados no públicos |
|
||||||
|
|
||||||
|
## Riesgos y consideraciones
|
||||||
|
|
||||||
|
- **No diversificados:** Ambos fondos están clasificados como 'no diversificados' por SUGEVAL. Una situación adversa concentrada puede afectar más el fondo.
|
||||||
|
- **Riesgo de gestión pasiva (BN ETF 500):** En una caída generalizada del S&P 500, el fondo cae igual. No se toman acciones defensivas.
|
||||||
|
- **Riesgo cambiario:** Los fondos operan en USD. Movimientos en el tipo de cambio CRC/USD afectan su rendimiento real.
|
||||||
|
- **Liquidación del fondo:** Si el número de inversionistas o el activo neto caen bajo el mínimo por más de 6 meses, el fondo podría liquidarse. [BN ETF 500 Prospecto (página 22 de 43), Millennium BAC Prospecto (página 11 de 24)]
|
||||||
|
- **Comisiones sobre comisiones (Millennium):** Al ser fund-of-funds, se paga la comisión del fondo local (1.25%) más las comisiones internas de los fondos subyacentes.
|
||||||
|
|
||||||
|
## Preguntas abiertas
|
||||||
|
|
||||||
|
1. **Holdings exactos de Millennium BAC:** Solicité la composición detallada a BAC; respuesta pendiente.
|
||||||
|
2. **Overlap entre fondos:** Si Millennium tiene alta concentración en S&P 500, ¿vale la pena pagar 0.25% más?
|
||||||
|
|
||||||
|
### Es altamente probable que sea mejor simplemente invertir en el S&P500 a través de un broker. Use esta calculadora para comparar: [S&P500 vs lo que sea](https://posixlycorrect.com/triforce-strategies/fondos-inversion-bn-bac/#bn-etf-500)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
> **Disclaimer:** This content is for informational and educational purposes only. The author is not a financial advisor, and none of this constitutes financial advice.
|
||||||
|
>
|
||||||
|
> **Descargo de responsabilidad:** Este contenido es únicamente con fines informativos y educativos. El autor no es asesor financiero, y nada de esto constituiste asesoría financiera.
|
||||||
149
content/triforce-strategies/ley_proteccion_al_trabajador.md
Normal file
149
content/triforce-strategies/ley_proteccion_al_trabajador.md
Normal file
|
|
@ -0,0 +1,149 @@
|
||||||
|
+++
|
||||||
|
title = "Ley de Protección al Trabajador: Apuntes básicos"
|
||||||
|
date = 2026-03-25
|
||||||
|
description = "Apuntes sobre el FCL, ROP, pensiones voluntarias y cómo funciona el sistema de ahorro laboral en Costa Rica."
|
||||||
|
|
||||||
|
[taxonomies]
|
||||||
|
tags = ["costa-rica", "ley-7983", "rop", "fcl"]
|
||||||
|
|
||||||
|
[extra]
|
||||||
|
author = "chema"
|
||||||
|
isso = true
|
||||||
|
+++
|
||||||
|
|
||||||
|
Costa Rica tiene un sistema de ahorro laboral obligatorio que la mayoría de trabajadores no entiende completamente. Esta ley cubre tres pilares: el Fondo de Capitalización Laboral (FCL), el sistema de pensiones, y la gestión de esos recursos.
|
||||||
|
|
||||||
|
## Fondo de Capitalización Laboral (FCL)
|
||||||
|
|
||||||
|
El FCL es ahorro laboral que viene de contribuciones del patrono más los rendimientos de inversión — que dependen de la operadora que elijás.
|
||||||
|
|
||||||
|
**¿Cuánto?** 1.5% de tu salario mensual, durante toda la relación laboral.
|
||||||
|
|
||||||
|
### Consideraciones importantes
|
||||||
|
|
||||||
|
- **No se toca:** No puede cederse ni ser embargado (excepto 1/2 por pensiones alimenticias lmao)
|
||||||
|
- **No se interrumpe** por enfermedad, vacaciones u otras situaciones de este tipo
|
||||||
|
- **No taxes** o cargas sociales — es solo para el worker
|
||||||
|
- **Solo los puede gestionar** ciertos entes autorizados
|
||||||
|
- **Empresas públicas** aportan 15% de utilidades netas para ayudar (COMMUNISM)
|
||||||
|
- **Ente regulador:** La Superintendencia de Pensiones. Establece reglamentos, distribución de competencias e impone medidas/sanciones
|
||||||
|
|
||||||
|
### Cuándo podés retirar
|
||||||
|
|
||||||
|
- **Fin de relación laboral:** Se otorga todo el dinero acumulado
|
||||||
|
- **Death:** Se reclama por los parientes con interés
|
||||||
|
- **Cada 5 años:** Get that bread
|
||||||
|
- **Suspensión temporal** o reducción de jornada
|
||||||
|
|
||||||
|
Los fondos se entregan 15 días luego si no hay irregularidades, por transferencia.
|
||||||
|
|
||||||
|
## Régimen Obligatorio de Pensión Complementaria (ROP)
|
||||||
|
|
||||||
|
La base de cálculo es la misma base salarial para el Régimen de Invalidez Vejez y Muerte — los mejores 300 salarios luego de ajustar por inflación.
|
||||||
|
|
||||||
|
El usuario puede escoger operadora y cambiar sin costo, pero solo una uWu.
|
||||||
|
|
||||||
|
### Financiamiento
|
||||||
|
|
||||||
|
| Fuente | Porcentaje |
|
||||||
|
|---|---|
|
||||||
|
| Trabajador | 1% mensual |
|
||||||
|
| Patrono (después de 18 meses, cuz primero va al Banco Popular) | 0.25% mensual |
|
||||||
|
| Patrono | 3% mensual |
|
||||||
|
| Aportes de convenios | Variable |
|
||||||
|
| Aportes extraordinarios | Variable |
|
||||||
|
|
||||||
|
## Pensión Voluntaria
|
||||||
|
|
||||||
|
Aportes periódicos adicionales a las cuentas de pensión complementaria. Por escrito y con copia a la Superintendencia. Se registran a nombre del trabajador, por separado a los aportes obligatorios.
|
||||||
|
|
||||||
|
### Afiliación
|
||||||
|
|
||||||
|
Te podés afiliar aunque no estés en el Régimen Obligatorio (why tho). Los planes son de contribución definida, autorizados por el Superintendente de Pensiones.
|
||||||
|
|
||||||
|
### Ahorro Voluntario
|
||||||
|
|
||||||
|
Aportes administrados por las operadoras en un "megafondo" — basically para inversiones. Los afiliados pueden retirar según el contrato. Aplica el 5% de la ley sobre el Mercado de Valores.
|
||||||
|
|
||||||
|
### Retiro anticipado
|
||||||
|
|
||||||
|
Está permitido aunque no cumplas lo de 57 años or dying. **Requisito:** Haber cotizado al menos 66 meses + cancelar al Estado los beneficios fiscales (pay the taxes).
|
||||||
|
|
||||||
|
**Escala de devolución:** Entre más lejos de los 57, más % de taxes tenés que devolver. Menor de 48 devuelve el 100%, cada año adicional es 10% menos que devuelve.
|
||||||
|
|
||||||
|
## Beneficios
|
||||||
|
|
||||||
|
Accedés cuando cumplís con los requisitos del Régimen de Invalidez, Vejez y Muerte o régimen público equivalente. Si expirás, alguien más puede acceder a los $$$.
|
||||||
|
|
||||||
|
Los operadores tienen **60 días** para entregar el dinero. La edad mínima depende del contrato, pero mínimo 57 years (o invalidez/death).
|
||||||
|
|
||||||
|
### Formas de prestación
|
||||||
|
|
||||||
|
- **Renta vitalicia:** Se "compra" con el operador un derecho a recibir cierta cantidad de $$$ hasta morirse
|
||||||
|
- **Renta permanente:** Se entrega producto de los rendimientos de la inversión, el saldo es para sus beneficiarios when die
|
||||||
|
- **Retiro programado:** Renta periódica calculada anualmente según tu saldo y el valor de la unidad de pensión
|
||||||
|
- **Expectativa de vida condicionada:** Se divide el capital para la pensión entre los años que te quedan como expectativa
|
||||||
|
|
||||||
|
**Excepción:** Podés sacar todo de un solo si tenés enfermedad terminal o condición grave de salud (death likely).
|
||||||
|
|
||||||
|
Si los beneficios son menores a 20%, se te otorga el 20% hasta que tu saldo se agote **(COMMUNISM)**.
|
||||||
|
|
||||||
|
**Importante:** Dejá beneficiarios para evitar problemas tbh.
|
||||||
|
|
||||||
|
## Operadoras
|
||||||
|
|
||||||
|
Básicamente gestionan el fondo de pensión. Son cringe pero tocan.
|
||||||
|
|
||||||
|
- **Junta directiva:** Tienen restricciones woke en la composición
|
||||||
|
- **Elección:** Escogés una, no pueden negarse si cumplís requisitos
|
||||||
|
- **Responsabilidad:** Responden por pérdidas en las aportaciones o rendimientos. Liquidan para pagar si la cosa se pone fea lmao
|
||||||
|
- **Portabilidad:** TIENEN que pasar la $$$ a otras operadoras si el trabajador lo quiere hacer
|
||||||
|
|
||||||
|
### Fondos
|
||||||
|
|
||||||
|
Los fondos son patrimonios autónomos, propiedad de los afiliados, administrados por la operadora. Los rendimientos se acreditan en cuentas individualizadas. Se pagan y depositan los aportes al mismo tiempo que el ROP.
|
||||||
|
|
||||||
|
### Inversiones
|
||||||
|
|
||||||
|
- **No sujetas** a regulación del BCCR
|
||||||
|
- **Objetivo:** Para el provecho de los afiliados
|
||||||
|
- **Restricción local:** Solo se invierten en valores inscritos en el Registro Nacional de Valores e Intermediarios, o valores emitidos por empresas supervisadas por SUGEF (o sea cringe, solo varas de CR)
|
||||||
|
- **Límites:** Pueden ser limitados por la Superintendencia según el riesgo
|
||||||
|
- **Mínimo ROP:** Las operadoras deben invertir **al menos** 15% de los fondos depositados por concepto del ROP
|
||||||
|
- **Inversión extranjera:** Hasta 25%. Si el rendimiento nacional <= que el extranjero, se puede autorizar hasta 50%
|
||||||
|
- **Conflicto de interés:** NO se pueden invertir en valores emitidos/garantizados por gente de la junta o parientes si tienen más de 5% de participación accionaria o control efectivo
|
||||||
|
|
||||||
|
### Taxes
|
||||||
|
|
||||||
|
- **Voluntario:** Los aportes al régimen voluntario no pagan impuestos hasta un 10% del ingreso bruto. Se aplica cuando el patrono deduce lo correspondiente al trabajador **ANTES** de hacer la planilla de pago
|
||||||
|
- **ROP y RVP:** Los beneficios no pagan taxes
|
||||||
|
- **Valores de operadoras:** Los beneficios originados no pagan ciertos impuestos
|
||||||
|
|
||||||
|
### Sanciones
|
||||||
|
|
||||||
|
Categorías: muy graves, graves y leves. Sancionan diferente a los operadores. Para administrar y comercializar planes de pensiones y fondos de capitalización necesitás autorización de la Superintendencia.
|
||||||
|
|
||||||
|
## La Caja
|
||||||
|
|
||||||
|
La Caja otorga la pensión correspondiente en caso de irregularidades de pago y tiene que arreglarse con el patrono. Cierra temporalmente centros si existe mora de más de 2 meses y no hay arreglo de pago.
|
||||||
|
|
||||||
|
### El patrono cannot
|
||||||
|
|
||||||
|
- **Empadronamiento:** Iniciar luego de 8 días hábiles del inicio de la actividad el proceso con la Caja
|
||||||
|
- **Rebajos:** Cubrir con rebajos de salario o remuneración la cuota del patrono
|
||||||
|
- **Cuotas:** Evitar deducir la cuota obrera y no pagar la cuota patronal
|
||||||
|
- **Falsificación:** Falsificar salarios o cantidad de trabajadores
|
||||||
|
- **Represalias:** Tomar represalias contra trabajadores que hagan snitch con autoridades
|
||||||
|
|
||||||
|
### Info que nos pueden dar
|
||||||
|
|
||||||
|
- Info general de su situación económica, programa de inversiones y proyecciones
|
||||||
|
- Evolución probable de la situación financiera de la Caja
|
||||||
|
- Medidas implementadas para su saneamiento y mejoramiento económico
|
||||||
|
- Info estadística para fundamentar lo anterior
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
> **Disclaimer:** This content is for informational and educational purposes only. The author is not a financial advisor, and none of this constitutes financial advice.
|
||||||
|
>
|
||||||
|
> **Descargo de responsabilidad:** Este contenido es únicamente con fines informativos y educativos. El autor no es asesor financiero, y nada de esto constituye asesoría financiera.
|
||||||
5
content/triforce-strategies/tools/_index.md
Normal file
5
content/triforce-strategies/tools/_index.md
Normal file
|
|
@ -0,0 +1,5 @@
|
||||||
|
+++
|
||||||
|
title = "tools"
|
||||||
|
sort_by = "title"
|
||||||
|
paginate_by = 10
|
||||||
|
+++
|
||||||
41
content/triforce-strategies/tools/snp500_vs_anything.md
Normal file
41
content/triforce-strategies/tools/snp500_vs_anything.md
Normal file
|
|
@ -0,0 +1,41 @@
|
||||||
|
+++
|
||||||
|
title = "S&P 500 vs lo que sea"
|
||||||
|
date = 2026-03-25
|
||||||
|
description = "Compara el crecimiento del S&P500 contra cualquier otro fondo hipotético usando una simulación Monte Carlo sencilla."
|
||||||
|
|
||||||
|
[extra]
|
||||||
|
stylesheets = ["css/fund-comparison.css"]
|
||||||
|
+++
|
||||||
|
|
||||||
|
Esta calculadora compara el crecimiento proyectado del S&P500 contra cualquier otro fondo hipotético. Usa una simulación Monte Carlo sencilla para modelar la volatilidad del mercado.
|
||||||
|
|
||||||
|
Este tipo de simulación es muy sencilla y solo sirve para ilustrar la diferencia entre el rendimiento del S&P500 en comparación a otros fondos disponibles en Costa Rica.
|
||||||
|
|
||||||
|
## Cómo usar la calculadora
|
||||||
|
|
||||||
|
0. **Idioma**: Perdón, todo está en spanglish, pero es que es el restulado de una investigación que es en inglés y español.
|
||||||
|
1. **Fondo S&P 500**: Valores por defecto representan un índice de bajo costo. Puede buscar el rendimiento real del S&P 500 y poner los valores aquí.
|
||||||
|
2. **Fondo a comparar**: Introduzca los parámetros del fondo que quiere comprarar.
|
||||||
|
3. **Parámetros de inversión**: Seleccione el tiempo de la inversión y la contribución mensual.
|
||||||
|
4. **Monte Carlo**: Active la simulación para un modelado rudimentario de volatilidad.
|
||||||
|
|
||||||
|
## Cómo leer el gráfico generado
|
||||||
|
|
||||||
|
- **Medianas**: Verde (S&P 500) y azul (fondo comparado) muestran los resultados esperados
|
||||||
|
- **Bandas sombreadas**: Rango del percentil 10 al 90 cuando la simulación Monte Carlo está activada
|
||||||
|
- **Δ (Delta)**: Diferencia entre los valores finales de ambos fondos
|
||||||
|
- **Tax loss**: (pérdida fiscal) Segmento rojo que muestra el impuesto sobre ganancias de capital deducido al retirar (cuando aplica)
|
||||||
|
|
||||||
|
### Nota acerca de impuestos
|
||||||
|
- Costarricenses no tienen que pagar impuestos a ganancias de capital obtenidas a través de inversinoes directas en USA. Más info: [taxation of NRAs](https://www.irs.gov/businesses/taxation-of-nonresident-aliens-international-tax-gap-series).
|
||||||
|
- En Costa Rica no hay renta global (aún lol)
|
||||||
|
- Por eso la calculadora no calcula una pérdida fiscal al invertir directamente en el S&P500 pero sí al invertir en un fondo de CR.
|
||||||
|
|
||||||
|
{{ fund_comparison() }}
|
||||||
|
|
||||||
|
## Notas generales
|
||||||
|
- Los rendimientos se modelan usando distribución log-normal
|
||||||
|
- Los gastos de administración y comisiones se deducen mensualmente
|
||||||
|
- El impuesto sobre ganancias de capital se aplica únicamente sobre las ganancias al momento del retiro final y se muestra como una caída roja en el gráfico
|
||||||
|
- Los rendimientos históricos del S&P 500 promedian ~10% anual con ~15% de desviación estándar
|
||||||
|
- Rendimientos pasados no garantizan resultados futuros
|
||||||
181
static/css/fund-comparison.css
Normal file
181
static/css/fund-comparison.css
Normal file
|
|
@ -0,0 +1,181 @@
|
||||||
|
.fund-comparison-container {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.fund-comparison-form {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: 1fr 1fr;
|
||||||
|
gap: 1.5rem;
|
||||||
|
margin-bottom: 2rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-section {
|
||||||
|
background: var(--bg-0);
|
||||||
|
padding: 1rem;
|
||||||
|
border-radius: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-section h3 {
|
||||||
|
margin-top: 0;
|
||||||
|
margin-bottom: 1rem;
|
||||||
|
color: var(--text-color-high-contrast);
|
||||||
|
font-size: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-group {
|
||||||
|
margin-bottom: 0.75rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-group label {
|
||||||
|
display: block;
|
||||||
|
margin-bottom: 0.25rem;
|
||||||
|
font-size: 0.85rem;
|
||||||
|
color: var(--meta-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-group input {
|
||||||
|
width: 100%;
|
||||||
|
padding: 0.5rem;
|
||||||
|
background: var(--bg-2);
|
||||||
|
border: 1px solid var(--divider-color);
|
||||||
|
border-radius: 3px;
|
||||||
|
color: var(--text-color);
|
||||||
|
font-family: var(--code-font);
|
||||||
|
font-size: 0.9rem;
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-group input:focus {
|
||||||
|
outline: none;
|
||||||
|
border-color: var(--primary-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-section-full {
|
||||||
|
grid-column: 1 / -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-row {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: 1fr 1fr;
|
||||||
|
gap: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.checkbox-group {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 0.5rem;
|
||||||
|
margin-top: 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.checkbox-group input[type="checkbox"] {
|
||||||
|
width: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.checkbox-group label {
|
||||||
|
margin-bottom: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.calculate-btn {
|
||||||
|
display: block;
|
||||||
|
width: 100%;
|
||||||
|
padding: 0.75rem;
|
||||||
|
margin-top: 1rem;
|
||||||
|
background: var(--primary-color);
|
||||||
|
color: var(--background-color);
|
||||||
|
border: none;
|
||||||
|
border-radius: 4px;
|
||||||
|
font-family: var(--sans-serif-font);
|
||||||
|
font-size: 1rem;
|
||||||
|
font-weight: 550;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: opacity 0.2s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.calculate-btn:hover {
|
||||||
|
opacity: 0.9;
|
||||||
|
}
|
||||||
|
|
||||||
|
.chart-container {
|
||||||
|
margin-bottom: 2rem;
|
||||||
|
background: var(--bg-2);
|
||||||
|
border-radius: 4px;
|
||||||
|
padding: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.chart-container canvas {
|
||||||
|
width: 100%;
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
.summary-section {
|
||||||
|
margin-bottom: 1.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.summary-section h3 {
|
||||||
|
margin-top: 0;
|
||||||
|
margin-bottom: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.summary-table {
|
||||||
|
width: 100%;
|
||||||
|
border-collapse: collapse;
|
||||||
|
font-size: 0.9rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.summary-table th,
|
||||||
|
.summary-table td {
|
||||||
|
padding: 0.5rem 0.75rem;
|
||||||
|
text-align: left;
|
||||||
|
border-bottom: 1px solid var(--divider-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
.summary-table th {
|
||||||
|
background: var(--bg-0);
|
||||||
|
font-weight: 550;
|
||||||
|
}
|
||||||
|
|
||||||
|
.summary-table td:not(:first-child) {
|
||||||
|
text-align: right;
|
||||||
|
font-family: var(--code-font);
|
||||||
|
}
|
||||||
|
|
||||||
|
.percentiles-details {
|
||||||
|
margin-top: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.percentiles-details summary {
|
||||||
|
cursor: pointer;
|
||||||
|
color: var(--primary-color);
|
||||||
|
font-weight: 550;
|
||||||
|
}
|
||||||
|
|
||||||
|
.percentiles-table {
|
||||||
|
margin-top: 0.75rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.noscript-warning {
|
||||||
|
background: #ff9800;
|
||||||
|
color: #000;
|
||||||
|
padding: 1rem;
|
||||||
|
border-radius: 4px;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 600px) {
|
||||||
|
.fund-comparison-form {
|
||||||
|
grid-template-columns: 1fr;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-row {
|
||||||
|
grid-template-columns: 1fr;
|
||||||
|
}
|
||||||
|
|
||||||
|
.summary-table {
|
||||||
|
font-size: 0.8rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.summary-table th,
|
||||||
|
.summary-table td {
|
||||||
|
padding: 0.4rem 0.5rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
567
static/js/fundComparison.js
Normal file
567
static/js/fundComparison.js
Normal file
|
|
@ -0,0 +1,567 @@
|
||||||
|
(function() {
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
// Box-Muller transform for normal distribution
|
||||||
|
function boxMuller() {
|
||||||
|
let u1, u2;
|
||||||
|
do {
|
||||||
|
u1 = Math.random();
|
||||||
|
u2 = Math.random();
|
||||||
|
} while (u1 === 0);
|
||||||
|
return Math.sqrt(-2.0 * Math.log(u1)) * Math.cos(2.0 * Math.PI * u2);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Convert annual rate to monthly
|
||||||
|
function annualToMonthly(annualRate) {
|
||||||
|
return Math.pow(1 + annualRate, 1/12) - 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Run a single simulation
|
||||||
|
function runSimulation(params, months) {
|
||||||
|
const monthlyReturn = annualToMonthly(params.annualReturn);
|
||||||
|
const monthlyVol = params.annualVol / Math.sqrt(12);
|
||||||
|
const monthlyExpense = params.expenseRatio / 12;
|
||||||
|
|
||||||
|
let value = 0;
|
||||||
|
const values = [0];
|
||||||
|
|
||||||
|
for (let m = 1; m <= months; m++) {
|
||||||
|
// Add monthly deposit at start of month
|
||||||
|
value += params.monthlyDeposit;
|
||||||
|
|
||||||
|
// Apply log-normal return
|
||||||
|
const randomReturn = monthlyReturn + monthlyVol * boxMuller();
|
||||||
|
value *= Math.exp(randomReturn);
|
||||||
|
|
||||||
|
// Deduct expense ratio
|
||||||
|
value *= (1 - monthlyExpense);
|
||||||
|
|
||||||
|
values.push(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Track pre-tax value
|
||||||
|
const preTaxValue = value;
|
||||||
|
|
||||||
|
// Apply capital gains tax at withdrawal if specified
|
||||||
|
const totalDeposits = params.monthlyDeposit * months;
|
||||||
|
const gains = value - totalDeposits;
|
||||||
|
let taxPaid = 0;
|
||||||
|
if (params.capitalGainsTax > 0 && gains > 0) {
|
||||||
|
taxPaid = gains * params.capitalGainsTax;
|
||||||
|
value -= taxPaid;
|
||||||
|
}
|
||||||
|
|
||||||
|
return { values, finalValue: value, preTaxValue, taxPaid };
|
||||||
|
}
|
||||||
|
|
||||||
|
// Run Monte Carlo simulation
|
||||||
|
function runMonteCarlo(params, months, numSimulations) {
|
||||||
|
const allFinalValues = [];
|
||||||
|
const allPreTaxValues = [];
|
||||||
|
const allTaxPaid = [];
|
||||||
|
const allValuesByMonth = [];
|
||||||
|
|
||||||
|
for (let i = 0; i <= months; i++) {
|
||||||
|
allValuesByMonth.push([]);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (let sim = 0; sim < numSimulations; sim++) {
|
||||||
|
const result = runSimulation(params, months);
|
||||||
|
allFinalValues.push(result.finalValue);
|
||||||
|
allPreTaxValues.push(result.preTaxValue);
|
||||||
|
allTaxPaid.push(result.taxPaid);
|
||||||
|
for (let m = 0; m <= months; m++) {
|
||||||
|
allValuesByMonth[m].push(result.values[m]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sort values at each month for percentile calculation
|
||||||
|
for (let m = 0; m <= months; m++) {
|
||||||
|
allValuesByMonth[m].sort((a, b) => a - b);
|
||||||
|
}
|
||||||
|
allFinalValues.sort((a, b) => a - b);
|
||||||
|
allPreTaxValues.sort((a, b) => a - b);
|
||||||
|
allTaxPaid.sort((a, b) => a - b);
|
||||||
|
|
||||||
|
// Calculate percentiles
|
||||||
|
function getPercentile(sortedArr, p) {
|
||||||
|
const idx = Math.floor(sortedArr.length * p);
|
||||||
|
return sortedArr[Math.min(idx, sortedArr.length - 1)];
|
||||||
|
}
|
||||||
|
|
||||||
|
const percentiles = {
|
||||||
|
p10: getPercentile(allFinalValues, 0.10),
|
||||||
|
p25: getPercentile(allFinalValues, 0.25),
|
||||||
|
p50: getPercentile(allFinalValues, 0.50),
|
||||||
|
p75: getPercentile(allFinalValues, 0.75),
|
||||||
|
p90: getPercentile(allFinalValues, 0.90)
|
||||||
|
};
|
||||||
|
|
||||||
|
const preTaxPercentiles = {
|
||||||
|
p50: getPercentile(allPreTaxValues, 0.50)
|
||||||
|
};
|
||||||
|
|
||||||
|
const taxPaidPercentiles = {
|
||||||
|
p50: getPercentile(allTaxPaid, 0.50)
|
||||||
|
};
|
||||||
|
|
||||||
|
// Get percentile curves for chart
|
||||||
|
const curves = {
|
||||||
|
p10: [],
|
||||||
|
p50: [],
|
||||||
|
p90: []
|
||||||
|
};
|
||||||
|
|
||||||
|
for (let m = 0; m <= months; m++) {
|
||||||
|
curves.p10.push(getPercentile(allValuesByMonth[m], 0.10));
|
||||||
|
curves.p50.push(getPercentile(allValuesByMonth[m], 0.50));
|
||||||
|
curves.p90.push(getPercentile(allValuesByMonth[m], 0.90));
|
||||||
|
}
|
||||||
|
|
||||||
|
return { percentiles, preTaxPercentiles, taxPaidPercentiles, curves };
|
||||||
|
}
|
||||||
|
|
||||||
|
// Run deterministic simulation (no volatility)
|
||||||
|
function runDeterministic(params, months) {
|
||||||
|
const monthlyReturn = annualToMonthly(params.annualReturn);
|
||||||
|
const monthlyExpense = params.expenseRatio / 12;
|
||||||
|
|
||||||
|
let value = 0;
|
||||||
|
const values = [0];
|
||||||
|
|
||||||
|
for (let m = 1; m <= months; m++) {
|
||||||
|
value += params.monthlyDeposit;
|
||||||
|
value *= (1 + monthlyReturn);
|
||||||
|
value *= (1 - monthlyExpense);
|
||||||
|
values.push(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
const preTaxValue = value;
|
||||||
|
const totalDeposits = params.monthlyDeposit * months;
|
||||||
|
const gains = value - totalDeposits;
|
||||||
|
let taxPaid = 0;
|
||||||
|
if (params.capitalGainsTax > 0 && gains > 0) {
|
||||||
|
taxPaid = gains * params.capitalGainsTax;
|
||||||
|
value -= taxPaid;
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
percentiles: { p10: value, p25: value, p50: value, p75: value, p90: value },
|
||||||
|
preTaxPercentiles: { p50: preTaxValue },
|
||||||
|
taxPaidPercentiles: { p50: taxPaid },
|
||||||
|
curves: { p10: values, p50: values, p90: values }
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// Format currency
|
||||||
|
function formatCurrency(value) {
|
||||||
|
if (value >= 1000000) {
|
||||||
|
return '$' + (value / 1000000).toFixed(2) + 'M';
|
||||||
|
} else if (value >= 1000) {
|
||||||
|
return '$' + (value / 1000).toFixed(1) + 'K';
|
||||||
|
}
|
||||||
|
return '$' + value.toFixed(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Chart renderer
|
||||||
|
const ChartRenderer = {
|
||||||
|
canvas: null,
|
||||||
|
ctx: null,
|
||||||
|
|
||||||
|
init: function(canvasId) {
|
||||||
|
this.canvas = document.getElementById(canvasId);
|
||||||
|
this.ctx = this.canvas.getContext('2d');
|
||||||
|
this.resize();
|
||||||
|
},
|
||||||
|
|
||||||
|
resize: function() {
|
||||||
|
const container = this.canvas.parentElement;
|
||||||
|
const dpr = window.devicePixelRatio || 1;
|
||||||
|
const rect = container.getBoundingClientRect();
|
||||||
|
|
||||||
|
this.canvas.width = rect.width * dpr;
|
||||||
|
this.canvas.height = 400 * dpr;
|
||||||
|
this.canvas.style.width = rect.width + 'px';
|
||||||
|
this.canvas.style.height = '400px';
|
||||||
|
|
||||||
|
this.ctx.scale(dpr, dpr);
|
||||||
|
},
|
||||||
|
|
||||||
|
clear: function() {
|
||||||
|
const width = this.canvas.width / (window.devicePixelRatio || 1);
|
||||||
|
const height = this.canvas.height / (window.devicePixelRatio || 1);
|
||||||
|
this.ctx.clearRect(0, 0, width, height);
|
||||||
|
},
|
||||||
|
|
||||||
|
draw: function(sp500Result, comparedResult, years, monthlyDeposit, useMonteCarlo) {
|
||||||
|
this.clear();
|
||||||
|
|
||||||
|
const width = this.canvas.width / (window.devicePixelRatio || 1);
|
||||||
|
const height = this.canvas.height / (window.devicePixelRatio || 1);
|
||||||
|
const padding = { top: 30, right: 100, bottom: 50, left: 80 };
|
||||||
|
const chartWidth = width - padding.left - padding.right;
|
||||||
|
const chartHeight = height - padding.top - padding.bottom;
|
||||||
|
|
||||||
|
const months = years * 12;
|
||||||
|
const totalDeposits = [];
|
||||||
|
for (let m = 0; m <= months; m++) {
|
||||||
|
totalDeposits.push(monthlyDeposit * m);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Find max value for Y axis
|
||||||
|
let maxVal = 0;
|
||||||
|
const allCurves = [
|
||||||
|
sp500Result.curves.p90,
|
||||||
|
comparedResult.curves.p90,
|
||||||
|
totalDeposits
|
||||||
|
];
|
||||||
|
for (const curve of allCurves) {
|
||||||
|
for (const val of curve) {
|
||||||
|
if (val > maxVal) maxVal = val;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
maxVal *= 1.1; // Add 10% padding
|
||||||
|
|
||||||
|
// Get computed styles for colors
|
||||||
|
const style = getComputedStyle(document.documentElement);
|
||||||
|
const textColor = style.getPropertyValue('--text-color').trim() || '#D4D4D4';
|
||||||
|
const dividerColor = style.getPropertyValue('--divider-color').trim() || '#4a4a4a';
|
||||||
|
const primaryColor = style.getPropertyValue('--primary-color').trim() || '#91e0ee';
|
||||||
|
|
||||||
|
// Draw grid
|
||||||
|
this.ctx.strokeStyle = dividerColor;
|
||||||
|
this.ctx.lineWidth = 0.5;
|
||||||
|
this.ctx.setLineDash([5, 5]);
|
||||||
|
|
||||||
|
// Y-axis grid lines
|
||||||
|
const numYLines = 5;
|
||||||
|
for (let i = 0; i <= numYLines; i++) {
|
||||||
|
const y = padding.top + (chartHeight / numYLines) * i;
|
||||||
|
this.ctx.beginPath();
|
||||||
|
this.ctx.moveTo(padding.left, y);
|
||||||
|
this.ctx.lineTo(width - padding.right, y);
|
||||||
|
this.ctx.stroke();
|
||||||
|
}
|
||||||
|
|
||||||
|
// X-axis grid lines
|
||||||
|
for (let yr = 0; yr <= years; yr++) {
|
||||||
|
const x = padding.left + (chartWidth / years) * yr;
|
||||||
|
this.ctx.beginPath();
|
||||||
|
this.ctx.moveTo(x, padding.top);
|
||||||
|
this.ctx.lineTo(x, height - padding.bottom);
|
||||||
|
this.ctx.stroke();
|
||||||
|
}
|
||||||
|
|
||||||
|
this.ctx.setLineDash([]);
|
||||||
|
|
||||||
|
// Helper to map data to canvas coordinates
|
||||||
|
const mapX = (month) => padding.left + (month / months) * chartWidth;
|
||||||
|
const mapY = (value) => padding.top + chartHeight - (value / maxVal) * chartHeight;
|
||||||
|
|
||||||
|
// Draw confidence bands if Monte Carlo
|
||||||
|
if (useMonteCarlo) {
|
||||||
|
// S&P 500 band (green)
|
||||||
|
this.ctx.fillStyle = 'rgba(76, 175, 80, 0.15)';
|
||||||
|
this.ctx.beginPath();
|
||||||
|
this.ctx.moveTo(mapX(0), mapY(sp500Result.curves.p10[0]));
|
||||||
|
for (let m = 0; m <= months; m++) {
|
||||||
|
this.ctx.lineTo(mapX(m), mapY(sp500Result.curves.p90[m]));
|
||||||
|
}
|
||||||
|
for (let m = months; m >= 0; m--) {
|
||||||
|
this.ctx.lineTo(mapX(m), mapY(sp500Result.curves.p10[m]));
|
||||||
|
}
|
||||||
|
this.ctx.closePath();
|
||||||
|
this.ctx.fill();
|
||||||
|
|
||||||
|
// Compared fund band (blue)
|
||||||
|
this.ctx.fillStyle = 'rgba(33, 150, 243, 0.15)';
|
||||||
|
this.ctx.beginPath();
|
||||||
|
this.ctx.moveTo(mapX(0), mapY(comparedResult.curves.p10[0]));
|
||||||
|
for (let m = 0; m <= months; m++) {
|
||||||
|
this.ctx.lineTo(mapX(m), mapY(comparedResult.curves.p90[m]));
|
||||||
|
}
|
||||||
|
for (let m = months; m >= 0; m--) {
|
||||||
|
this.ctx.lineTo(mapX(m), mapY(comparedResult.curves.p10[m]));
|
||||||
|
}
|
||||||
|
this.ctx.closePath();
|
||||||
|
this.ctx.fill();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Draw total deposits line (dashed gray)
|
||||||
|
this.ctx.strokeStyle = '#888888';
|
||||||
|
this.ctx.lineWidth = 2;
|
||||||
|
this.ctx.setLineDash([8, 4]);
|
||||||
|
this.ctx.beginPath();
|
||||||
|
this.ctx.moveTo(mapX(0), mapY(totalDeposits[0]));
|
||||||
|
for (let m = 1; m <= months; m++) {
|
||||||
|
this.ctx.lineTo(mapX(m), mapY(totalDeposits[m]));
|
||||||
|
}
|
||||||
|
this.ctx.stroke();
|
||||||
|
this.ctx.setLineDash([]);
|
||||||
|
|
||||||
|
// Draw S&P 500 median line (green)
|
||||||
|
this.ctx.strokeStyle = '#4CAF50';
|
||||||
|
this.ctx.lineWidth = 2.5;
|
||||||
|
this.ctx.beginPath();
|
||||||
|
this.ctx.moveTo(mapX(0), mapY(sp500Result.curves.p50[0]));
|
||||||
|
for (let m = 1; m <= months; m++) {
|
||||||
|
this.ctx.lineTo(mapX(m), mapY(sp500Result.curves.p50[m]));
|
||||||
|
}
|
||||||
|
this.ctx.stroke();
|
||||||
|
|
||||||
|
// Draw compared fund median line (blue)
|
||||||
|
this.ctx.strokeStyle = '#2196F3';
|
||||||
|
this.ctx.lineWidth = 2.5;
|
||||||
|
this.ctx.beginPath();
|
||||||
|
this.ctx.moveTo(mapX(0), mapY(comparedResult.curves.p50[0]));
|
||||||
|
for (let m = 1; m <= months; m++) {
|
||||||
|
this.ctx.lineTo(mapX(m), mapY(comparedResult.curves.p50[m]));
|
||||||
|
}
|
||||||
|
this.ctx.stroke();
|
||||||
|
|
||||||
|
// Draw capital gains tax loss on compared fund (red segment)
|
||||||
|
const taxPaid = comparedResult.taxPaidPercentiles.p50;
|
||||||
|
const preTaxValue = comparedResult.preTaxPercentiles.p50;
|
||||||
|
const postTaxValue = comparedResult.percentiles.p50;
|
||||||
|
const endX = mapX(months);
|
||||||
|
|
||||||
|
if (taxPaid > 0) {
|
||||||
|
const preTaxY = mapY(preTaxValue);
|
||||||
|
const postTaxY = mapY(postTaxValue);
|
||||||
|
|
||||||
|
// Draw red line from pre-tax to post-tax
|
||||||
|
this.ctx.strokeStyle = '#F44336';
|
||||||
|
this.ctx.lineWidth = 3;
|
||||||
|
this.ctx.beginPath();
|
||||||
|
this.ctx.moveTo(endX, preTaxY);
|
||||||
|
this.ctx.lineTo(endX, postTaxY);
|
||||||
|
this.ctx.stroke();
|
||||||
|
|
||||||
|
// Draw red circle at post-tax point
|
||||||
|
this.ctx.fillStyle = '#F44336';
|
||||||
|
this.ctx.beginPath();
|
||||||
|
this.ctx.arc(endX, postTaxY, 4, 0, Math.PI * 2);
|
||||||
|
this.ctx.fill();
|
||||||
|
|
||||||
|
// Label the tax loss
|
||||||
|
this.ctx.fillStyle = '#F44336';
|
||||||
|
this.ctx.font = '10px monospace';
|
||||||
|
this.ctx.textAlign = 'left';
|
||||||
|
this.ctx.textBaseline = 'middle';
|
||||||
|
this.ctx.fillText('-' + formatCurrency(taxPaid) + ' tax', endX + 8, (preTaxY + postTaxY) / 2);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Draw delta annotation between funds
|
||||||
|
const sp500Final = sp500Result.percentiles.p50;
|
||||||
|
const comparedFinal = comparedResult.percentiles.p50;
|
||||||
|
const delta = sp500Final - comparedFinal;
|
||||||
|
|
||||||
|
// Draw delta bracket/line
|
||||||
|
const sp500Y = mapY(sp500Final);
|
||||||
|
const comparedY = mapY(comparedFinal);
|
||||||
|
const bracketX = endX + 6;
|
||||||
|
|
||||||
|
this.ctx.strokeStyle = textColor;
|
||||||
|
this.ctx.lineWidth = 1;
|
||||||
|
this.ctx.setLineDash([]);
|
||||||
|
this.ctx.beginPath();
|
||||||
|
// Horizontal ticks
|
||||||
|
this.ctx.moveTo(endX + 2, sp500Y);
|
||||||
|
this.ctx.lineTo(bracketX, sp500Y);
|
||||||
|
this.ctx.moveTo(endX + 2, comparedY);
|
||||||
|
this.ctx.lineTo(bracketX, comparedY);
|
||||||
|
// Vertical line
|
||||||
|
this.ctx.moveTo(bracketX, sp500Y);
|
||||||
|
this.ctx.lineTo(bracketX, comparedY);
|
||||||
|
this.ctx.stroke();
|
||||||
|
|
||||||
|
// Delta label
|
||||||
|
this.ctx.fillStyle = delta >= 0 ? '#4CAF50' : '#F44336';
|
||||||
|
this.ctx.font = '11px monospace';
|
||||||
|
this.ctx.textAlign = 'left';
|
||||||
|
this.ctx.textBaseline = 'middle';
|
||||||
|
const deltaLabel = 'Δ ' + (delta >= 0 ? '+' : '') + formatCurrency(delta);
|
||||||
|
this.ctx.fillText(deltaLabel, bracketX + 4, (sp500Y + comparedY) / 2);
|
||||||
|
|
||||||
|
// Draw axes
|
||||||
|
this.ctx.strokeStyle = textColor;
|
||||||
|
this.ctx.lineWidth = 1;
|
||||||
|
this.ctx.beginPath();
|
||||||
|
this.ctx.moveTo(padding.left, padding.top);
|
||||||
|
this.ctx.lineTo(padding.left, height - padding.bottom);
|
||||||
|
this.ctx.lineTo(width - padding.right, height - padding.bottom);
|
||||||
|
this.ctx.stroke();
|
||||||
|
|
||||||
|
// Y-axis labels
|
||||||
|
this.ctx.fillStyle = textColor;
|
||||||
|
this.ctx.font = '12px monospace';
|
||||||
|
this.ctx.textAlign = 'right';
|
||||||
|
this.ctx.textBaseline = 'middle';
|
||||||
|
for (let i = 0; i <= numYLines; i++) {
|
||||||
|
const value = maxVal * (1 - i / numYLines);
|
||||||
|
const y = padding.top + (chartHeight / numYLines) * i;
|
||||||
|
this.ctx.fillText(formatCurrency(value), padding.left - 10, y);
|
||||||
|
}
|
||||||
|
|
||||||
|
// X-axis labels
|
||||||
|
this.ctx.textAlign = 'center';
|
||||||
|
this.ctx.textBaseline = 'top';
|
||||||
|
for (let yr = 0; yr <= years; yr++) {
|
||||||
|
const x = padding.left + (chartWidth / years) * yr;
|
||||||
|
this.ctx.fillText(yr + 'y', x, height - padding.bottom + 10);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Legend
|
||||||
|
const legendY = padding.top - 15;
|
||||||
|
const legendItems = [
|
||||||
|
{ color: '#4CAF50', label: 'S&P 500' },
|
||||||
|
{ color: '#2196F3', label: 'Compared' },
|
||||||
|
{ color: '#888888', label: 'Deposits', dashed: true },
|
||||||
|
{ color: '#F44336', label: 'Tax Loss' }
|
||||||
|
];
|
||||||
|
|
||||||
|
let legendX = padding.left;
|
||||||
|
this.ctx.font = '11px monospace';
|
||||||
|
this.ctx.textAlign = 'left';
|
||||||
|
this.ctx.textBaseline = 'middle';
|
||||||
|
|
||||||
|
for (const item of legendItems) {
|
||||||
|
// Draw line sample
|
||||||
|
this.ctx.strokeStyle = item.color;
|
||||||
|
this.ctx.lineWidth = 2;
|
||||||
|
if (item.dashed) {
|
||||||
|
this.ctx.setLineDash([4, 2]);
|
||||||
|
}
|
||||||
|
this.ctx.beginPath();
|
||||||
|
this.ctx.moveTo(legendX, legendY);
|
||||||
|
this.ctx.lineTo(legendX + 20, legendY);
|
||||||
|
this.ctx.stroke();
|
||||||
|
this.ctx.setLineDash([]);
|
||||||
|
|
||||||
|
// Draw label
|
||||||
|
this.ctx.fillStyle = textColor;
|
||||||
|
this.ctx.fillText(item.label, legendX + 25, legendY);
|
||||||
|
|
||||||
|
legendX += this.ctx.measureText(item.label).width + 50;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Main calculator
|
||||||
|
const Calculator = {
|
||||||
|
form: null,
|
||||||
|
chartRenderer: null,
|
||||||
|
|
||||||
|
init: function() {
|
||||||
|
this.form = document.getElementById('fund-comparison-form');
|
||||||
|
this.chartRenderer = Object.create(ChartRenderer);
|
||||||
|
this.chartRenderer.init('fund-comparison-chart');
|
||||||
|
|
||||||
|
this.form.addEventListener('submit', (e) => {
|
||||||
|
e.preventDefault();
|
||||||
|
this.calculate();
|
||||||
|
});
|
||||||
|
|
||||||
|
window.addEventListener('resize', () => {
|
||||||
|
this.chartRenderer.resize();
|
||||||
|
this.calculate();
|
||||||
|
});
|
||||||
|
|
||||||
|
// Initial calculation
|
||||||
|
this.calculate();
|
||||||
|
},
|
||||||
|
|
||||||
|
getInputs: function() {
|
||||||
|
return {
|
||||||
|
sp500: {
|
||||||
|
annualReturn: parseFloat(document.getElementById('sp500-return').value) / 100,
|
||||||
|
annualVol: parseFloat(document.getElementById('sp500-volatility').value) / 100,
|
||||||
|
expenseRatio: parseFloat(document.getElementById('sp500-expense').value) / 100,
|
||||||
|
capitalGainsTax: 0,
|
||||||
|
monthlyDeposit: parseFloat(document.getElementById('monthly-deposit').value)
|
||||||
|
},
|
||||||
|
compared: {
|
||||||
|
annualReturn: parseFloat(document.getElementById('compared-return').value) / 100,
|
||||||
|
annualVol: parseFloat(document.getElementById('compared-volatility').value) / 100,
|
||||||
|
expenseRatio: parseFloat(document.getElementById('compared-admin-fee').value) / 100,
|
||||||
|
capitalGainsTax: parseFloat(document.getElementById('compared-capital-gains').value) / 100,
|
||||||
|
monthlyDeposit: parseFloat(document.getElementById('monthly-deposit').value)
|
||||||
|
},
|
||||||
|
years: parseInt(document.getElementById('time-frame').value),
|
||||||
|
useMonteCarlo: document.getElementById('use-monte-carlo').checked
|
||||||
|
};
|
||||||
|
},
|
||||||
|
|
||||||
|
calculate: function() {
|
||||||
|
const inputs = this.getInputs();
|
||||||
|
const months = inputs.years * 12;
|
||||||
|
const numSimulations = 1000;
|
||||||
|
|
||||||
|
let sp500Result, comparedResult;
|
||||||
|
|
||||||
|
if (inputs.useMonteCarlo) {
|
||||||
|
sp500Result = runMonteCarlo(inputs.sp500, months, numSimulations);
|
||||||
|
comparedResult = runMonteCarlo(inputs.compared, months, numSimulations);
|
||||||
|
} else {
|
||||||
|
sp500Result = runDeterministic(inputs.sp500, months);
|
||||||
|
comparedResult = runDeterministic(inputs.compared, months);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Draw chart
|
||||||
|
this.chartRenderer.draw(
|
||||||
|
sp500Result,
|
||||||
|
comparedResult,
|
||||||
|
inputs.years,
|
||||||
|
inputs.sp500.monthlyDeposit,
|
||||||
|
inputs.useMonteCarlo
|
||||||
|
);
|
||||||
|
|
||||||
|
// Update summary
|
||||||
|
this.updateSummary(sp500Result, comparedResult, inputs);
|
||||||
|
},
|
||||||
|
|
||||||
|
updateSummary: function(sp500Result, comparedResult, inputs) {
|
||||||
|
const totalDeposits = inputs.sp500.monthlyDeposit * inputs.years * 12;
|
||||||
|
|
||||||
|
// Summary table
|
||||||
|
document.getElementById('total-deposits').textContent = formatCurrency(totalDeposits);
|
||||||
|
|
||||||
|
document.getElementById('sp500-final').textContent = formatCurrency(sp500Result.percentiles.p50);
|
||||||
|
const sp500Gain = sp500Result.percentiles.p50 - totalDeposits;
|
||||||
|
document.getElementById('sp500-gain').textContent = formatCurrency(sp500Gain);
|
||||||
|
document.getElementById('sp500-multiple').textContent = (sp500Result.percentiles.p50 / totalDeposits).toFixed(2) + 'x';
|
||||||
|
|
||||||
|
document.getElementById('compared-final').textContent = formatCurrency(comparedResult.percentiles.p50);
|
||||||
|
const comparedGain = comparedResult.percentiles.p50 - totalDeposits;
|
||||||
|
document.getElementById('compared-gain').textContent = formatCurrency(comparedGain);
|
||||||
|
document.getElementById('compared-multiple').textContent = (comparedResult.percentiles.p50 / totalDeposits).toFixed(2) + 'x';
|
||||||
|
|
||||||
|
// Percentiles section
|
||||||
|
const percentilesSection = document.getElementById('percentiles-section');
|
||||||
|
if (inputs.useMonteCarlo) {
|
||||||
|
percentilesSection.style.display = 'block';
|
||||||
|
|
||||||
|
document.getElementById('sp500-p10').textContent = formatCurrency(sp500Result.percentiles.p10);
|
||||||
|
document.getElementById('sp500-p25').textContent = formatCurrency(sp500Result.percentiles.p25);
|
||||||
|
document.getElementById('sp500-p50').textContent = formatCurrency(sp500Result.percentiles.p50);
|
||||||
|
document.getElementById('sp500-p75').textContent = formatCurrency(sp500Result.percentiles.p75);
|
||||||
|
document.getElementById('sp500-p90').textContent = formatCurrency(sp500Result.percentiles.p90);
|
||||||
|
|
||||||
|
document.getElementById('compared-p10').textContent = formatCurrency(comparedResult.percentiles.p10);
|
||||||
|
document.getElementById('compared-p25').textContent = formatCurrency(comparedResult.percentiles.p25);
|
||||||
|
document.getElementById('compared-p50').textContent = formatCurrency(comparedResult.percentiles.p50);
|
||||||
|
document.getElementById('compared-p75').textContent = formatCurrency(comparedResult.percentiles.p75);
|
||||||
|
document.getElementById('compared-p90').textContent = formatCurrency(comparedResult.percentiles.p90);
|
||||||
|
} else {
|
||||||
|
percentilesSection.style.display = 'none';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Initialize on DOM ready
|
||||||
|
if (document.readyState === 'loading') {
|
||||||
|
document.addEventListener('DOMContentLoaded', () => Calculator.init());
|
||||||
|
} else {
|
||||||
|
Calculator.init();
|
||||||
|
}
|
||||||
|
})();
|
||||||
146
templates/shortcodes/fund_comparison.html
Normal file
146
templates/shortcodes/fund_comparison.html
Normal file
|
|
@ -0,0 +1,146 @@
|
||||||
|
<div class="fund-comparison-container">
|
||||||
|
<noscript>
|
||||||
|
<div class="noscript-warning">
|
||||||
|
This calculator requires JavaScript to function.
|
||||||
|
</div>
|
||||||
|
</noscript>
|
||||||
|
|
||||||
|
<form id="fund-comparison-form" class="fund-comparison-form">
|
||||||
|
<div class="form-section">
|
||||||
|
<h3>S&P 500 Index Fund</h3>
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="sp500-return">Expected yearly return (%)</label>
|
||||||
|
<input type="number" id="sp500-return" value="10" step="0.1" required>
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="sp500-volatility">Volatility / std dev (%)</label>
|
||||||
|
<input type="number" id="sp500-volatility" value="15" step="0.1" required>
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="sp500-expense">Expense ratio (%)</label>
|
||||||
|
<input type="number" id="sp500-expense" value="0.03" step="0.01" required>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="form-section">
|
||||||
|
<h3>Compared Fund</h3>
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="compared-return">Expected yearly return (%)</label>
|
||||||
|
<input type="number" id="compared-return" value="7" step="0.1" required>
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="compared-volatility">Volatility / std dev (%)</label>
|
||||||
|
<input type="number" id="compared-volatility" value="10" step="0.1" required>
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="compared-admin-fee">Admin fee (% yearly)</label>
|
||||||
|
<input type="number" id="compared-admin-fee" value="1" step="0.01">
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="compared-capital-gains">Capital gains tax at withdrawal (%)</label>
|
||||||
|
<input type="number" id="compared-capital-gains" value="0" step="0.1">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="form-section form-section-full">
|
||||||
|
<h3>Investment Parameters</h3>
|
||||||
|
<div class="form-row">
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="time-frame">Time frame (years)</label>
|
||||||
|
<input type="number" id="time-frame" value="20" min="1" max="50" required>
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="monthly-deposit">Monthly deposit ($)</label>
|
||||||
|
<input type="number" id="monthly-deposit" value="500" min="1" required>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="checkbox-group">
|
||||||
|
<input type="checkbox" id="use-monte-carlo" checked>
|
||||||
|
<label for="use-monte-carlo">Use Monte Carlo simulation (1000 runs)</label>
|
||||||
|
</div>
|
||||||
|
<button type="submit" class="calculate-btn">Calculate</button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
|
||||||
|
<div class="chart-container">
|
||||||
|
<canvas id="fund-comparison-chart"></canvas>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="summary-section">
|
||||||
|
<h3>Summary</h3>
|
||||||
|
<table class="summary-table">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th></th>
|
||||||
|
<th>S&P 500 Fund</th>
|
||||||
|
<th>Compared Fund</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<tr>
|
||||||
|
<td>Total Deposits</td>
|
||||||
|
<td colspan="2" id="total-deposits">-</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>Final Value (median)</td>
|
||||||
|
<td id="sp500-final">-</td>
|
||||||
|
<td id="compared-final">-</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>Total Gain</td>
|
||||||
|
<td id="sp500-gain">-</td>
|
||||||
|
<td id="compared-gain">-</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>Return Multiple</td>
|
||||||
|
<td id="sp500-multiple">-</td>
|
||||||
|
<td id="compared-multiple">-</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div id="percentiles-section" class="summary-section">
|
||||||
|
<details class="percentiles-details">
|
||||||
|
<summary>Percentile Breakdown</summary>
|
||||||
|
<table class="summary-table percentiles-table">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>Percentile</th>
|
||||||
|
<th>S&P 500 Fund</th>
|
||||||
|
<th>Compared Fund</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<tr>
|
||||||
|
<td>10th (pessimistic)</td>
|
||||||
|
<td id="sp500-p10">-</td>
|
||||||
|
<td id="compared-p10">-</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>25th</td>
|
||||||
|
<td id="sp500-p25">-</td>
|
||||||
|
<td id="compared-p25">-</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>50th (median)</td>
|
||||||
|
<td id="sp500-p50">-</td>
|
||||||
|
<td id="compared-p50">-</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>75th</td>
|
||||||
|
<td id="sp500-p75">-</td>
|
||||||
|
<td id="compared-p75">-</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>90th (optimistic)</td>
|
||||||
|
<td id="sp500-p90">-</td>
|
||||||
|
<td id="compared-p90">-</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</details>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<script src="{{ get_url(path='js/fundComparison.js') | safe }}"></script>
|
||||||
Loading…
Add table
Add a link
Reference in a new issue