
DeskPricer
from JohnJohnJohnJohn
Local HTTP pricing microservice for vanilla European and American equity options.
β labsco summary β our analysis, not the vendor's
What it is β A local HTTP microservice (also an MCP server) that prices vanilla European and American equity options and computes Greeks, built for Excel and AI-agent use.
What you get β
- MCP tools
price_option,implied_volatility,pnl_attribution,portfolio_greeksβ same engine as the HTTP API - QuantLib-backed analytics returning price, delta, gamma, vega, theta, rho, charm
- Excel integration via
WEBSERVICE+FILTERXML(no VBA), plus a Bitcoin demo workbook
Requirements β nothing external: env is empty and no keys are needed; Python and pip install deskpricer.
Cost snapshot β MIT-licensed and free; no paid service behind it.
Setup effort β pip install deskpricer and add deskpricer-mcp to your client, or run the local server; light config.
Our take β A handy self-contained pricing desk for quants and Excel users, but the author is explicit that it's local-only by design β no auth, no TLS, no rate limiting β so never expose it as a public service.
Source: the project README β summarized 2026-07-08.
β readme from github β mirrored (latest on GitHub β)
DeskPricer v3.4.5
Local HTTP pricing microservice for vanilla European and American equity options. Designed for Excel WEBSERVICE + FILTERXML integration β no VBA, no Bloomberg terminal calls inside the service.
Design intent: DeskPricer is a local-only tool for personal desk pricing and option analytics. It is not intended to be run or served as a public/server-style service. All design choices β localhost binding, no auth, no TLS, no rate limiting, XML-by-default β reflect this.
Use with AI Agents (MCP)
DeskPricer is available as an MCP server. Add it to Cursor, Claude Desktop, or any MCP-compatible agent:
pip install deskpricerPublished on PyPI: https://pypi.org/project/deskpricer/
Cursor β add to ~/.cursor/mcp.json:
{
"mcpServers": {
"deskpricer": {
"command": "deskpricer-mcp",
"args": []
}
}
}Claude Desktop β add to claude_desktop_config.json:
{
"mcpServers": {
"deskpricer": {
"command": "deskpricer-mcp"
}
}
}If deskpricer-mcp is not on your PATH, use the full path to the executable in your virtualenv.
Tools: price_option, implied_volatility, pnl_attribution, portfolio_greeks β same pricing engine as the HTTP API.
See docs/mcp_quickstart.md for full setup, conventions, and example prompts.
Try the Demo Workbook
Open sample/DeskPricer_Bitcoin_Demo.xlsx for a ready-to-run example. It contains 3 sheets:
| Sheet | What it shows |
|---|---|
| Greeks | Bitcoin European Call β $75K spot, $100K strike, 3M expiry, 50% vol |
| ImpliedVol | Back out ~68.3% implied vol from a $3,398.71 market price |
| PnL Attribution | Decompose PnL when spot rallies $75K β $80K and vol widens 50% β 55% |
Each sheet has the actual WEBSERVICE and FILTERXML formulas pre-loaded. Just start DeskPricer and the cells will populate automatically.
What it does
- Price + Greeks for single options or multi-leg portfolios
- Implied volatility solver (Brent method via QuantLib)
- PnL attribution β decompose option PnL into delta, gamma, vega, theta, rho, vanna, volga, and residual
- XML by default β Excel
WEBSERVICE+FILTERXMLwork out of the box; JSON available viaAccept: application/json - Localhost-only β binds to
127.0.0.1; no network exposure
Conventions
| Greek | Unit / Convention |
|---|---|
delta | Absolute (βV/βS) |
gamma | Absolute (βΒ²V/βSΒ²) |
vega | Per 1% vol point |
theta | Per calendar day (ACT/365). Negative for typical long options (time decay). Sign is opposite of Bloomberg DM, which reports theta as positive decay. |
rho | Per 1% rate point (risk-free rate only; no dividend-yield or borrow-cost rho) |
charm | Per calendar day (βdelta/βt) |
Borrow cost (b): Optional annualized stock borrow cost (decimal). Effective cost-of-carry is r β q β b. Omitted or 0.0 matches pre-3.4.0 behavior.
Time to expiry (t): Supplied in years under ACT/365. Internally converted to calendar days (round(t * 365)) with a hard floor of 1 day, then rolled to the next business day using the chosen calendar (hong_kong by default). Theta and charm are computed per calendar day, not per business day.
PnL attribution: calendar_days represents the actual elapsed calendar-day hold period. theta_pnl = theta Γ calendar_days_elapsed. If both valuation dates are omitted, calendar_days defaults to 1. Provide explicit dates for multi-day hold accuracy.
Excel User Guide
Service Status
Check that the service is running before pulling prices:
| Cell | Formula |
|---|---|
| Status | =IFERROR(FILTERXML(WEBSERVICE("http://127.0.0.1:8765/v1/health"),"//status"),"DOWN") |
Expected output: UP
Example 1: Price a Single Option + Greeks
Assume your sheet has:
| Column | Label | Example Value |
|---|---|---|
| C | Spot | 100 |
| K | Strike | 105 |
| T | Time to expiry (years) | 0.25 |
| R | Risk-free rate | 0.05 |
| Q | Dividend yield | 0.02 |
| B | Borrow cost (optional, default 0.0) | 0.0 |
| V | Volatility | 0.20 |
| TYPE | Option type | call |
| STYLE | Style | european |
Step 1 β Build the URL in a helper cell (e.g. 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="&STYLE2Step 2 β Fetch the raw XML (e.g. I2):
=WEBSERVICE(H2)Step 3 β Extract values into individual cells:
| Output | Formula |
|---|---|
| Price | =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")) |
Expected output for the example above:
| Greek | Value |
|---|---|
| Price | 2.288743 |
| Delta | 0.356244 |
| Gamma | 0.037206 |
| Vega | 0.185519 |
| Theta | -0.033315 |
| Rho | 0.083111 |
| Charm | -0.001241 |
Tip: Wrap each
FILTERXMLinIFERROR(...,"ERR")so one bad row doesn't break the whole sheet.
Example 2: Back Out Implied Volatility from Market Price
You observe a mid-market price of 6.50 for the same option and want the implied vol.
Step 1 β Build the URL (e.g. 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="&STYLE2Step 2 β Extract implied vol:
=VALUE(FILTERXML(WEBSERVICE(H2),"//outputs/implied_vol"))Expected output: 0.417484 (β 41.7 % vol)
Example 3: PnL Attribution
You had a position yesterday (t-1) and want to explain today's PnL.
Assume:
| Field | t-1 Value | t Value |
|---|---|---|
| Spot | 100 | 102 |
| Time | 0.25 | 0.2466 |
| Vol | 0.20 | 0.22 |
| Rate | 0.05 | 0.05 |
| Div | 0.02 | 0.02 |
| Borrow | 0.0 | 0.0 |
| Qty | 10 | β |
Step 1 β Build the 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"Step 2 β Extract attribution buckets:
| Bucket | Formula |
|---|---|
| Actual PnL | =VALUE(FILTERXML(WEBSERVICE(H2),"//outputs/actual_pnl")) |
| Delta PnL | =VALUE(FILTERXML(WEBSERVICE(H2),"//outputs/delta_pnl")) |
| Gamma PnL | =VALUE(FILTERXML(WEBSERVICE(H2),"//outputs/gamma_pnl")) |
| Vega PnL | =VALUE(FILTERXML(WEBSERVICE(H2),"//outputs/vega_pnl")) |
| Theta PnL | =VALUE(FILTERXML(WEBSERVICE(H2),"//outputs/theta_pnl")) |
| Rho PnL | =VALUE(FILTERXML(WEBSERVICE(H2),"//outputs/rho_pnl")) |
| Vanna PnL | =VALUE(FILTERXML(WEBSERVICE(H2),"//outputs/vanna_pnl")) |
| Volga PnL | =VALUE(FILTERXML(WEBSERVICE(H2),"//outputs/volga_pnl")) |
| Residual | =VALUE(FILTERXML(WEBSERVICE(H2),"//outputs/residual_pnl")) |
Expected output:
| Bucket | Value |
|---|---|
| price_t_minus_1 | 2.288743 |
| price_t | 3.448862 |
| Actual PnL | 11.601 |
| Delta PnL | 7.125 |
| Gamma PnL | 0.744 |
| Vega PnL | 3.710 |
| Theta PnL | -0.333 |
| Rho PnL | 0.0 |
| Vanna PnL | 0.343 |
| Volga PnL | 0.031 |
| Explained PnL | 11.621 |
| Residual | -0.020 |
The
residual_pnlcaptures higher-order effects and model differences between t-1 and t. Enablecross_greeks=trueto include vanna and volga contributions.
Example 4: Portfolio / Bulk Greeks
For book-level aggregation, use the POST /v1/portfolio/greeks endpoint via Power Query or a small VBA helper. The endpoint accepts a JSON body with multiple legs and returns per-leg and aggregate Greeks.
See docs/api.md for the full request/response schema.
Greek Conventions
| Greek | Unit | Notes |
|---|---|---|
| Delta | absolute | Per $1 spot move |
| Gamma | absolute | Per $1 spot move |
| Vega | per 1 vol point | i.e. decimal vol Γ 100 |
| Theta | per calendar day | Forward P&L of one calendar day passing (1-calendar-day revalue β today's price). Negative for a decaying long option. |
| Rho | per 1% rate point | i.e. decimal rate Γ 100 |
| Charm | per calendar day | βdelta/βt (delta at t β 1/365 β delta today). Negative for a long call β delta decays toward expiry |
Log Location and Structured Logging
- Log path:
DESKPRICER_LOG_DIRenv var overrides the default (C:\ProgramData\DeskPricer\logson Windows,~/.local/share/deskpricer/logselsewhere). - Format: Uses Python's stdlib
loggingmodule with a custom JSON formatter andRotatingFileHandler(10 MB rotation, 5 backups). This replaces the earlier hand-rolledopen()approach. - Change the path:
$env:DESKPRICER_LOG_DIR = "C:\MyLogs" python -m deskpricer.main
Design Decisions
Local-only by design
DeskPricer is built as a personal desktop tool, not a public API. This explains every intentional omission:
- No authentication / authorization β only
127.0.0.1can reach the service. - No TLS / HTTPS β local loopback traffic is unencrypted by design.
- No rate limiting β no throttling; concurrent requests are handled by a process pool rather than serialized on a single QuantLib global state.
- No Swagger / Redoc β OpenAPI docs are hidden in production builds to reduce attack surface.
- XML by default β Excel's
WEBSERVICEfunction does not sendAccept: application/json.
If you need any of these features, DeskPricer is the wrong tool. Use a proper API gateway or a full-featured pricing platform.
Why QuantLib runs in a process pool
QuantLib's Python bindings rely on a single process-global Settings.instance() object. Rather than serializing all requests with an asyncio.Lock, DeskPricer dispatches QuantLib work to a ProcessPoolExecutor. Each worker process has its own isolated settings, so concurrent agent or Excel calls can price in parallel without corrupting evaluation dates. Pool size defaults to min(4, cpu_count()) and is configurable via DESKPRICER_WORKERS.
Europeans and economically equivalent Americans bypass QuantLib entirely and use a pure-Python BSM implementation (bsm_fast), validated against QuantLib to six decimal places.
Why PnL attribution uses GET with many query params
Excel's WEBSERVICE function only supports HTTP GET. Since the primary user of this service is Excel, the GET /v1/pnl_attribution endpoint is designed specifically for WEBSERVICE compatibility. Programmatic clients that need a cleaner JSON body can use POST /v1/portfolio/greeks today; a POST alternative for PnL attribution may be added in a future release.
Project Structure
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{
"mcpServers": {
"deskpricer": {
"command": "deskpricer-mcp",
"args": []
}
}
}Quickstart
Go from clean clone to a working pricing call in under 5 minutes:
# 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")Expected output for the curl call (XML):
<?xml version="1.0" encoding="UTF-8"?>
<greeks>
<meta>
<service_version>3.4.5</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>For JSON, send Accept: application/json or append ?format=json.
Installation Options
Standalone Executable (Recommended)
Download DeskPricer_v3.exe from the Releases page and run:
.\DeskPricer_v3.exeThe service starts on port 8765. To use a different port:
.\DeskPricer_v3.exe --port 9000From Source
python -m venv .venv
.venv\Scripts\activate
pip install -e ".[dev]"
python -m deskpricer.mainRunning Tests
pytest tests -vTroubleshooting
QuantLib install failures
If pip install fails on QuantLib, ensure you have a C++ compiler and CMake, or use a pre-built wheel. See docs/operator_guide.md for detailed steps.
Zero-DTE surprises
t=0 is floored to 1 calendar day to prevent QuantLib collapse. You will get a small time-value premium rather than pure intrinsic. This is intentional.
Engine/style mismatches
style=europeanβ onlyengine=analyticstyle=americanβ onlyengine=binomial_crrorbinomial_jr
XML vs JSON
Excel receives XML by default. For JSON, send Accept: application/json or ?format=json.
Error codes
| Code | Meaning |
|---|---|
INVALID_INPUT | Business-rule or schema validation failed |
UNSUPPORTED_COMBINATION | Engine/style mismatch |
PRICING_FAILURE | Unexpected internal error (no traceback leaked) |
Limitations
- No FD engine β closed-form BSM for Europeans and equivalent Americans; binomial CRR/JR for other Americans.
- Concurrent QuantLib via process pool β default
min(4, cpu_count())workers (DESKPRICER_WORKERS). Portfolio legs batch in a single worker call per request. - Bounded bump ranges β
bump_spot_relβ€ 0.1,bump_vol_absβ€ 0.01,bump_rate_absβ€ 0.01. - No database or persistence β all state is in-memory per-request.
- No auth, TLS, or rate limiting β local-only by design.
Licensed under MITβ you can use, modify, and redistribute it under that license's terms.
License
MIT