portall_ws
Portall Webservice
Documentation
DeskPricer v3.5.0
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.
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):
3.5.0
1.42.1
analytic
2026-06-10
100.0
105.0
0.25
0.05
0.02
0.0
0.2
call
european
2.288743
0.356244
0.037206
0.185519
-0.033315
0.083111
-0.001241For JSON, send `Accept: application/json` or append `?format=json`.
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` + `FILTERXML` work out of the box; JSON available via `Accept: 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.
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.mainExcel 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 `FILTERXML` in `IFERROR(...,"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_pnl` captures higher-order effects and model differences between t-1 and t. Enable `cross_greeks=true` to 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_DIR` env var overrides the default (`C:\ProgramData\DeskPricer\logs` on Windows, `~/.local/share/deskpricer/logs` elsewhere).
- Format: Uses Python's stdlib `logging` module with a custom JSON formatter and `RotatingFileHandler` (10 MB rotation, 5 backups). This replaces the earlier hand-rolled `open()` approach.
- Change the path:
$env:DESKPRICER_LOG_DIR = "C:\MyLogs"
python -m deskpricer.mainTroubleshooting
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` → only `engine=analytic`
- `style=american` → only `engine=binomial_crr` or `binomial_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.
Running Tests
pytest tests -vDesign 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.1` can 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 `WEBSERVICE` function does not send `Accept: 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 guideLicense
MIT
Frequently asked questions
What is portall_ws?
portall_ws is Portall Webservice
How do I install portall_ws?
Open the GitHub repository and follow its README. Most MCP servers are added to your client's MCP config, then called by your agent.
Is portall_ws open source?
Yes — it is hosted on GitHub at https://github.com/JohnJohnJohnJohn/portall_ws.
Related MCP tools
Transparent and Efficient Financial Analysis
Fast and Accurate Code Search for Agents. Uses 99% fewer tokens than grep+read
TradingView MCP server — real-time market data, technical analysis, screeners & backtesting for Claude, ChatGPT, Cursor & any MCP client. Stocks, crypto, forex & futures across global exchanges. Hosted or self-host.
Open source AI clip generator: turns long videos into viral 9:16 shorts with AI moment detection, face tracking, subtitles and dubbing. Self-host free with Docker (MIT), or use the cloud with GPU speed from $12/mo. MCP server and API for AI agents.
Control Gmail, Google Calendar, Docs, Sheets, Slides, Chat, Forms, Tasks, Search & Drive with AI - Comprehensive Google Workspace MCP Server & CLI Tool
Cut AI token costs 95%+ on code exploration. The leading MCP server for precise, symbol-level GitHub code retrieval via tree-sitter AST. Works with Claude Code, Cursor & any MCP client. 313B+ tokens saved.
Run your own MCP server? See who uses it and what to fix.
Measure it with TrackMCP