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:

HojaQué muestra
GriegasOpción de compra europea sobre Bitcoin — spot $75K, strike $100K, vencimiento 3M, volatilidad 50%
VolImplícitaRecupera ~68.3% de volatilidad implícita de un precio de mercado de $3,398.71
Atribución de PnLDescompone 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 + FILTERXML funcionan de inmediato; JSON disponible vía Accept: application/json
  • Solo localhost — se vincula a 127.0.0.1; sin exposición a la red

Convenciones

GriegaUnidad / Convención
deltaAbsoluta (∂V/∂S)
gammaAbsoluta (∂²V/∂S²)
vegaPor punto de volatilidad del 1%
thetaPor 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.
rhoPor punto de tasa del 1% (solo tasa libre de riesgo; sin rho de rendimiento de dividendos ni costo de préstamo)
charmPor 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:

CeldaFó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:

ColumnaEtiquetaValor de Ejemplo
CSpot100
KStrike105
TTiempo hasta vencimiento (años)0.25
RTasa libre de riesgo0.05
QRendimiento de dividendos0.02
BCosto de préstamo (opcional, por defecto 0.0)0.0
VVolatilidad0.20
TYPETipo de opcióncall
STYLEEstiloeuropean

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:

SalidaFó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:

GriegaValor
Precio2.288743
Delta0.356244
Gamma0.037206
Vega0.185519
Theta-0.033315
Rho0.083111
Charm-0.001241

Consejo: Envuelve cada FILTERXML en IFERROR(...,"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:

CampoValor t-1Valor t
Spot100102
Tiempo0.250.2466
Vol0.200.22
Tasa0.050.05
Div0.020.02
Préstamo0.00.0
Cantidad10—

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:

ComponenteFó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:

ComponenteValor
price_t_minus_12.288743
price_t3.448862
PnL Real11.601
PnL Delta7.125
PnL Gamma0.744
PnL Vega3.710
PnL Theta-0.333
PnL Rho0.0
PnL Vanna0.343
PnL Volga0.031
PnL Explicado11.621
Residual-0.020

El residual_pnl captura efectos de orden superior y diferencias de modelo entre t-1 y t. Habilita cross_greeks=true para 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

GriegaUnidadNotas
DeltaabsolutaPor movimiento de $1 en el spot
GammaabsolutaPor movimiento de $1 en el spot
Vegapor 1 punto de volatilidades decir, volatilidad decimal × 100
Thetapor día calendarioP&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.
Rhopor punto de tasa del 1%es decir, tasa decimal × 100
Charmpor 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_DIR anula el valor por defecto (C:\ProgramData\DeskPricer\logs en Windows, ~/.local/share/deskpricer/logs en otros sistemas).
  • Formato: Usa el módulo estándar de Python logging con un formateador JSON personalizado y RotatingFileHandler (rotación de 10 MB, 5 copias de respaldo). Esto reemplaza el enfoque anterior hecho a mano con open().
  • 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 → solo engine=analytic
  • style=american → solo engine=binomial_crr o binomial_jr

XML vs JSON

Excel recibe XML por defecto. Para JSON, envía Accept: application/json o ?format=json.

Códigos de error

CódigoSignificado
INVALID_INPUTFalló la validación de reglas de negocio o esquema
UNSUPPORTED_COMBINATIONDesajuste de motor/estilo
PRICING_FAILUREError 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.1 puede 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 WEBSERVICE de Excel no envía Accept: 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