DeskPricer
Microservicio local de precios HTTP para opciones sobre acciones europeas y americanas estándar.
Documentación
DeskPricer v3.5.0
Microservicio HTTP local de precios para opciones europeas y americanas estándar sobre acciones. Diseñado para integración con Excel WEBSERVICE + FILTERXML — sin VBA, sin llamadas a terminal Bloomberg dentro del servicio.
Intención de diseño: DeskPricer es una herramienta exclusivamente local para precios de escritorio personal y análisis de opciones. No está pensado para ejecutarse o servirse como un servicio público/tipo servidor. Todas las decisiones de diseño — vinculación a localhost, sin autenticación, sin TLS, sin limitación de tasa, XML por defecto — reflejan esto.
Uso con Agentes de IA (MCP)
DeskPricer está disponible como servidor MCP. Añádelo a Cursor, Claude Desktop, o cualquier agente compatible con MCP:
pip install deskpricer
Publicado en PyPI: https://pypi.org/project/deskpricer/
Cursor — añade a ~/.cursor/mcp.json:
{
"mcpServers": {
"deskpricer": {
"command": "deskpricer-mcp",
"args": []
}
}
}
Claude Desktop — añade a claude_desktop_config.json:
{
"mcpServers": {
"deskpricer": {
"command": "deskpricer-mcp"
}
}
}
Si deskpricer-mcp no está en tu PATH, usa la ruta completa al ejecutable en tu entorno virtual.
Herramientas: price_option, implied_volatility, pnl_attribution, portfolio_greeks — mismo motor de precios que la API HTTP.
Consulta docs/mcp_quickstart.md para la configuración completa, convenciones y ejemplos de prompts.
Inicio rápido
Ve de un clon limpio a una llamada de precios funcional en menos de 5 minutos:
# 1. Install
python -m venv .venv
.venv\Scripts\activate
pip install -e ".[dev]"
# 2. Run
python -m deskpricer.main
# 3. Test with curl
curl "http://127.0.0.1:8765/v1/greeks?s=100&k=105&t=0.25&r=0.05&q=0.02&b=0.0&v=0.20&type=call&style=european"
# 4. Test with Excel (copy into a cell)
# =FILTERXML(WEBSERVICE("http://127.0.0.1:8765/v1/greeks?s=100&k=105&t=0.25&r=0.05&q=0.02&b=0.0&v=0.20&type=call&style=european"),"//outputs/price")
Salida esperada para la llamada curl (XML):
<?xml version="1.0" encoding="UTF-8"?>
<greeks>
<meta>
<service_version>3.5.0</service_version>
<quantlib_version>1.42.1</quantlib_version>
<engine>analytic</engine>
<valuation_date>2026-06-10</valuation_date>
</meta>
<inputs>
<s>100.0</s>
<k>105.0</k>
<t>0.25</t>
<r>0.05</r>
<q>0.02</q>
<b>0.0</b>
<v>0.2</v>
<type>call</type>
<style>european</style>
</inputs>
<outputs>
<price>2.288743</price>
<delta>0.356244</delta>
<gamma>0.037206</gamma>
<vega>0.185519</vega>
<theta>-0.033315</theta>
<rho>0.083111</rho>
<charm>-0.001241</charm>
</outputs>
</greeks>
Para JSON, envía Accept: application/json o añade ?format=json.
Prueba el Libro de Trabajo de Demostración
Abre sample/DeskPricer_Bitcoin_Demo.xlsx para un ejemplo listo para ejecutar. Contiene 3 hojas:
| Hoja | Qué muestra |
|---|---|
| Griegas | Opción de compra europea sobre Bitcoin — spot $75K, strike $100K, vencimiento 3M, volatilidad 50% |
| VolImplícita | Recupera ~68.3% de volatilidad implícita de un precio de mercado de $3,398.71 |
| Atribución de PnL | Descompone el PnL cuando el spot sube de $75K → $80K y la volatilidad se amplía de 50% → 55% |
Cada hoja tiene las fórmulas reales de WEBSERVICE y FILTERXML precargadas. Solo inicia DeskPricer y las celdas se poblarán automáticamente.
Qué hace
- Precio + Griegas para opciones individuales o carteras multi-pierna
- Solucionador de volatilidad implícita (método de Brent vía QuantLib)
- Atribución de PnL — descompone el PnL de opciones en delta, gamma, vega, theta, rho, vanna, volga y residual
- XML por defecto — Excel
WEBSERVICE+FILTERXMLfuncionan de inmediato; JSON disponible víaAccept: application/json - Solo localhost — se vincula a
127.0.0.1; sin exposición a la red
Convenciones
| Griega | Unidad / Convención |
|---|---|
delta | Absoluta (∂V/∂S) |
gamma | Absoluta (∂²V/∂S²) |
vega | Por punto de volatilidad del 1% |
theta | Por día calendario (ACT/365). Negativa para opciones largas típicas (decaimiento temporal). El signo es opuesto al de Bloomberg DM, que reporta theta como decaimiento positivo. |
rho | Por punto de tasa del 1% (solo tasa libre de riesgo; sin rho de rendimiento de dividendos ni costo de préstamo) |
charm | Por día calendario (∂delta/∂t) |
Costo de préstamo (b): Costo anualizado opcional de préstamo de acciones (decimal). El costo de acarreo efectivo es r − q − b. Omitido o 0.0 coincide con el comportamiento anterior a 3.4.0.
Tiempo hasta vencimiento (t): Se proporciona en años bajo ACT/365. Internamente se convierte a días calendario (round(t * 365)) con un mínimo absoluto de 1 día, luego se traslada al siguiente día hábil usando el calendario elegido (hong_kong por defecto). Theta y charm se calculan por día calendario, no por día hábil.
Atribución de PnL: calendar_days representa el período real de tenencia transcurrido en días calendario. theta_pnl = theta × calendar_days_elapsed. Si se omiten ambas fechas de valoración, calendar_days se establece por defecto en 1. Proporciona fechas explícitas para precisión en tenencias de varios días.
Opciones de Instalación
Ejecutable Independiente (Recomendado)
Descarga DeskPricer_v3.exe de la página de Releases y ejecuta:
.\DeskPricer_v3.exe
El servicio se inicia en el puerto 8765. Para usar un puerto diferente:
.\DeskPricer_v3.exe --port 9000
Desde el Código Fuente
python -m venv .venv
.venv\Scripts\activate
pip install -e ".[dev]"
python -m deskpricer.main
Guía de Usuario para Excel
Estado del Servicio
Verifica que el servicio esté ejecutándose antes de obtener precios:
| Celda | Fórmula |
|---|---|
| Estado | =IFERROR(FILTERXML(WEBSERVICE("http://127.0.0.1:8765/v1/health"),"//status"),"DOWN") |
Salida esperada: UP
Ejemplo 1: Precio de una Opción Individual + Griegas
Supón que tu hoja tiene:
| Columna | Etiqueta | Valor de Ejemplo |
|---|---|---|
| C | Spot | 100 |
| K | Strike | 105 |
| T | Tiempo hasta vencimiento (años) | 0.25 |
| R | Tasa libre de riesgo | 0.05 |
| Q | Rendimiento de dividendos | 0.02 |
| B | Costo de préstamo (opcional, por defecto 0.0) | 0.0 |
| V | Volatilidad | 0.20 |
| TYPE | Tipo de opción | call |
| STYLE | Estilo | european |
Paso 1 — Construye la URL en una celda auxiliar (p. ej. H2):
="http://127.0.0.1:8765/v1/greeks?s="&C2&"&k="&K2&"&t="&T2&"&r="&R2&"&q="&Q2&"&b="&B2&"&v="&V2&"&type="&TYPE2&"&style="&STYLE2
Paso 2 — Obtén el XML crudo (p. ej. I2):
=WEBSERVICE(H2)
Paso 3 — Extrae valores en celdas individuales:
| Salida | Fórmula |
|---|---|
| Precio | =VALUE(FILTERXML(I2,"//outputs/price")) |
| Delta | =VALUE(FILTERXML(I2,"//outputs/delta")) |
| Gamma | =VALUE(FILTERXML(I2,"//outputs/gamma")) |
| Vega | =VALUE(FILTERXML(I2,"//outputs/vega")) |
| Theta | =VALUE(FILTERXML(I2,"//outputs/theta")) |
| Rho | =VALUE(FILTERXML(I2,"//outputs/rho")) |
| Charm | =VALUE(FILTERXML(I2,"//outputs/charm")) |
Salida esperada para el ejemplo anterior:
| Griega | Valor |
|---|---|
| Precio | 2.288743 |
| Delta | 0.356244 |
| Gamma | 0.037206 |
| Vega | 0.185519 |
| Theta | -0.033315 |
| Rho | 0.083111 |
| Charm | -0.001241 |
Consejo: Envuelve cada
FILTERXMLenIFERROR(...,"ERR")para que una fila con error no rompa toda la hoja.
Ejemplo 2: Recuperar la Volatilidad Implícita del Precio de Mercado
Observas un precio medio de mercado de 6.50 para la misma opción y quieres la volatilidad implícita.
Paso 1 — Construye la URL (p. ej. H2):
="http://127.0.0.1:8765/v1/impliedvol?s="&C2&"&k="&K2&"&t="&T2&"&r="&R2&"&q="&Q2&"&b="&B2&"&price=6.50&type="&TYPE2&"&style="&STYLE2
Paso 2 — Extrae la volatilidad implícita:
=VALUE(FILTERXML(WEBSERVICE(H2),"//outputs/implied_vol"))
Salida esperada: 0.417484 (≈ 41.7 % de volatilidad)
Ejemplo 3: Atribución de PnL
Tenías una posición ayer (t-1) y quieres explicar el PnL de hoy.
Supón:
| Campo | Valor t-1 | Valor t |
|---|---|---|
| Spot | 100 | 102 |
| Tiempo | 0.25 | 0.2466 |
| Vol | 0.20 | 0.22 |
| Tasa | 0.05 | 0.05 |
| Div | 0.02 | 0.02 |
| Préstamo | 0.0 | 0.0 |
| Cantidad | 10 | — |
Paso 1 — Construye la URL:
="http://127.0.0.1:8765/v1/pnl_attribution?s_t_minus_1=100&s_t=102&k=105&t_t_minus_1=0.25&t_t=0.2466&r_t_minus_1=0.05&r_t=0.05&q_t_minus_1=0.02&q_t=0.02&b_t_minus_1=0.0&b_t=0.0&v_t_minus_1=0.2&v_t=0.22&type=call&style=european&qty=10&cross_greeks=true"
Paso 2 — Extrae los componentes de atribución:
| Componente | Fórmula |
|---|---|
| PnL Real | =VALUE(FILTERXML(WEBSERVICE(H2),"//outputs/actual_pnl")) |
| PnL Delta | =VALUE(FILTERXML(WEBSERVICE(H2),"//outputs/delta_pnl")) |
| PnL Gamma | =VALUE(FILTERXML(WEBSERVICE(H2),"//outputs/gamma_pnl")) |
| PnL Vega | =VALUE(FILTERXML(WEBSERVICE(H2),"//outputs/vega_pnl")) |
| PnL Theta | =VALUE(FILTERXML(WEBSERVICE(H2),"//outputs/theta_pnl")) |
| PnL Rho | =VALUE(FILTERXML(WEBSERVICE(H2),"//outputs/rho_pnl")) |
| PnL Vanna | =VALUE(FILTERXML(WEBSERVICE(H2),"//outputs/vanna_pnl")) |
| PnL Volga | =VALUE(FILTERXML(WEBSERVICE(H2),"//outputs/volga_pnl")) |
| Residual | =VALUE(FILTERXML(WEBSERVICE(H2),"//outputs/residual_pnl")) |
Salida esperada:
| Componente | Valor |
|---|---|
| price_t_minus_1 | 2.288743 |
| price_t | 3.448862 |
| PnL Real | 11.601 |
| PnL Delta | 7.125 |
| PnL Gamma | 0.744 |
| PnL Vega | 3.710 |
| PnL Theta | -0.333 |
| PnL Rho | 0.0 |
| PnL Vanna | 0.343 |
| PnL Volga | 0.031 |
| PnL Explicado | 11.621 |
| Residual | -0.020 |
El
residual_pnlcaptura efectos de orden superior y diferencias de modelo entre t-1 y t. Habilitacross_greeks=truepara incluir contribuciones de vanna y volga.
Ejemplo 4: Cartera / Griegas en Lote
Para agregación a nivel de libro, usa el endpoint POST /v1/portfolio/greeks vía Power Query o un pequeño helper de VBA. El endpoint acepta un cuerpo JSON con múltiples piernas y devuelve griegas por pierna y agregadas.
Consulta docs/api.md para el esquema completo de solicitud/respuesta.
Convenciones de Griegas
| Griega | Unidad | Notas |
|---|---|---|
| Delta | absoluta | Por movimiento de $1 en el spot |
| Gamma | absoluta | Por movimiento de $1 en el spot |
| Vega | por 1 punto de volatilidad | es decir, volatilidad decimal × 100 |
| Theta | por día calendario | P&L forward de un día calendario que pasa (revalorización a 1 día calendario − precio de hoy). Negativa para una opción larga en decaimiento. |
| Rho | por punto de tasa del 1% | es decir, tasa decimal × 100 |
| Charm | por día calendario | ∂delta/∂t (delta en t − 1/365 − delta hoy). Negativa para una opción de compra larga — el delta decae hacia el vencimiento |
Ubicación del Registro y Registro Estructurado
- Ruta del registro: la variable de entorno
DESKPRICER_LOG_DIRanula el valor por defecto (C:\ProgramData\DeskPricer\logsen Windows,~/.local/share/deskpricer/logsen otros sistemas). - Formato: Usa el módulo estándar de Python
loggingcon un formateador JSON personalizado yRotatingFileHandler(rotación de 10 MB, 5 copias de respaldo). Esto reemplaza el enfoque anterior hecho a mano conopen(). - Cambiar la ruta:
$env:DESKPRICER_LOG_DIR = "C:\MyLogs" python -m deskpricer.main
Solución de Problemas
Fallos de instalación de QuantLib
Si pip install falla en QuantLib, asegúrate de tener un compilador de C++ y CMake, o usa una rueda precompilada. Consulta docs/operator_guide.md para pasos detallados.
Sorpresas con cero días hasta vencimiento
t=0 tiene un mínimo de 1 día calendario para evitar el colapso de QuantLib. Obtendrás una pequeña prima de valor temporal en lugar de intrínseco puro. Esto es intencional.
Desajustes de motor/estilo
style=european→ soloengine=analyticstyle=american→ soloengine=binomial_crrobinomial_jr
XML vs JSON
Excel recibe XML por defecto. Para JSON, envía Accept: application/json o ?format=json.
Códigos de error
| Código | Significado |
|---|---|
INVALID_INPUT | Falló la validación de reglas de negocio o esquema |
UNSUPPORTED_COMBINATION | Desajuste de motor/estilo |
PRICING_FAILURE | Error interno inesperado (sin rastreo de pila filtrado) |
Limitaciones
- Sin motor FD — BSM de forma cerrada para europeas y americanas equivalentes; binomial CRR/JR para otras americanas.
- QuantLib concurrente vía grupo de procesos — por defecto
min(4, cpu_count())trabajadores (DESKPRICER_WORKERS). Las piernas de cartera se procesan en una sola llamada de trabajador por solicitud. - Rangos de perturbación acotados —
bump_spot_rel≤ 0.1,bump_vol_abs≤ 0.01,bump_rate_abs≤ 0.01. - Sin base de datos ni persistencia — todo el estado está en memoria por solicitud.
- Sin autenticación, TLS ni limitación de tasa — solo local por diseño.
Ejecución de Pruebas
pytest tests -v
Decisiones de Diseño
Solo local por diseño
DeskPricer está construido como una herramienta de escritorio personal, no una API pública. Esto explica cada omisión intencional:
- Sin autenticación / autorización — solo
127.0.0.1puede alcanzar el servicio. - Sin TLS / HTTPS — el tráfico de bucle local no está cifrado por diseño.
- Sin limitación de tasa — sin estrangulamiento; las solicitudes concurrentes se manejan con un grupo de procesos en lugar de serializarse en un único estado global de QuantLib.
- Sin Swagger / Redoc — los documentos OpenAPI están ocultos en las compilaciones de producción para reducir la superficie de ataque.
- XML por defecto — la función
WEBSERVICEde Excel no envíaAccept: application/json.
Si necesitas cualquiera de estas funciones, DeskPricer es la herramienta equivocada. Usa una pasarela API adecuada o una plataforma de precios con todas las funciones.
Por qué QuantLib se ejecuta en un grupo de procesos
Los enlaces de Python de QuantLib dependen de un único objeto Settings.instance() global del proceso. En lugar de serializar todas las solicitudes con un asyncio.Lock, DeskPricer envía el trabajo de QuantLib a un ProcessPoolExecutor. Cada proceso trabajador tiene su propia configuración aislada, por lo que las llamadas concurrentes de agentes o Excel pueden calcular precios en paralelo sin corromper las fechas de valoración. El tamaño del grupo se establece por defecto en min(4, cpu_count()) y es configurable vía DESKPRICER_WORKERS.
Las europeas y las americanas económicamente equivalentes omiten QuantLib por completo y usan una implementación BSM en Python puro (bsm_fast), validada contra QuantLib hasta seis decimales.
Por qué la atribución de PnL usa GET con muchos parámetros de consulta
La función WEBSERVICE de Excel solo admite HTTP GET. Dado que el principal usuario de este servicio es Excel, el endpoint GET /v1/pnl_attribution está diseñado específicamente para la compatibilidad con WEBSERVICE. Los clientes programáticos que necesiten un cuerpo JSON más limpio pueden usar POST /v1/portfolio/greeks hoy; una alternativa POST para la atribución de PnL podría añadirse en una futura versión.
Estructura del Proyecto
DeskPricer/
├── pyproject.toml
├── README.md
├── src/deskpricer/ # FastAPI app + pricing core
│ ├── app.py # Thin composition root
│ ├── routers/ # APIRouter modules
│ ├── services/ # Pricing orchestration + QL lock
│ ├── pricing/ # QuantLib pricing engines
│ ├── schemas.py # Pydantic models
│ ├── responses.py # XML/JSON serializers
│ ├── errors.py # Custom exceptions
│ ├── logging_config.py # Structured JSON logging
│ └── main.py # Uvicorn entrypoint
├── tests/ # pytest + hypothesis
├── tests/fixtures/ # Regression baseline JSONs
├── scripts/ # Build + fixture generation
├── sample/ # Demo Excel workbook
└── docs/ # API ref + operator guide
Licencia
MIT