trading-position-risk-mcp
Not financial advice. Use at your own risk. This is a guardrail that enforces rules you configure. It cannot see your broker, does not place orders, and trusts what the agent reports. It can be wrong, and trading can lose money. Test with paper trading first. See Security and trust model.
A deterministic position-sizing and pre-trade risk server for trading agents, served over MCP.
Agents never do sizing math themselves. They ask this server. It applies a rulebook that the human owns, and it answers APPROVED / RESIZED / REJECTED with the rules that fired and an approval token.
The core formula reproduces the E-Mini (ES) Position Sizing Calculator v4.5 spreadsheet exactly
(floor(equity × risk% ÷ (stop points × point value))). On top of that it adds costs, micro
fallback, portfolio heat, exposure buckets, a daily-loss lockout, drawdown tiers and options.
Quick start
pip install -e ".[dev]"
pytest # 190+ tests, including spreadsheet parity
position-risk-mcp # stdio MCP server
position-risk-mcp --http 8765 # streamable HTTP instead (loopback only; see Security)
position-risk-admin size ES long 6500 6498 --no-costs # same answer as the sheet: 5 contracts
Claude Desktop / Claude Code config:
{
"mcpServers": {
"position-risk": {
"command": "position-risk-mcp",
"env": {
"POSITION_RISK_RULES": "/path/to/config/rules.yaml",
"POSITION_RISK_STATE": "~/.position-risk-mcp/risk_state.json"
}
}
}
}
| Env var | Default |
|---|---|
POSITION_RISK_RULES |
./config/rules.yaml, then the repo's config/ |
POSITION_RISK_INSTRUMENTS |
./config/instruments.yaml, then the repo's config/ |
POSITION_RISK_STATE |
~/.position-risk-mcp/risk_state.json |
Order flow the agent must follow
check_trade ──► place order (≤ approved_contracts, before expiry) ──► record_fill ──► update_stop* ──► record_close
| Tool | Purpose |
|---|---|
check_trade |
The gate. Applies every limit; returns verdict, approved_contracts, approval_id, binding limits, and a micro suggestion when the full size rounds to 0. |
calculate_position_size |
Read-only sizing for one trade (ignores other open positions). include_costs=false reproduces the spreadsheet. |
record_fill |
Opens a tracked position against an approval. It refuses quantities above the approval or approvals that have expired. The fill is always recorded (it already happened at the broker), but limits are re-checked and any breaches are returned. If slippage raised the risk, it warns. |
cancel_approval |
Releases the risk reserved by an approval you won't use. |
update_stop |
Moves a stop closer or trails it into profit. Widening is rejected. |
record_close |
Realizes P&L and updates equity, peak, daily P&L, lockouts and drawdown tiers. Supports partial closes. |
get_risk_state |
Equity, drawdown, multiplier, daily budget, positions, bucket exposure, pending approvals, journal. |
get_instrument_spec / list_instruments / get_rulebook |
Reference data. |
By design, no tool can change equity, rules or lockouts. Those are owner-only, through the admin CLI:
position-risk-admin show
position-risk-admin set-equity 61250 --note "synced with broker"
position-risk-admin reset-peak --note "reviewed drawdown, resuming"
position-risk-admin unlock-day --note "..."
position-risk-admin remove-position pos_ab12cd34ef --note "closed manually"
What check_trade enforces (config/rules.yaml)
| Rule | Default | Effect |
|---|---|---|
| Per-trade risk | 1% default, 2% max | Larger requests are capped (RESIZED) |
| Costs | commission + 1 tick stop slippage | Added to risk per contract |
| Portfolio heat | 5% of equity | Total open risk across all positions |
| Bucket heat | 3% of equity | ES, MES, NQ, MNQ, RTY, YM, SPX, XSP, SPY and QQQ all count as one us_equity_index exposure |
| Daily loss | 3% of start-of-day equity | Hard lockout until the next trading day. A new trade's full stop-out must also fit in what's left of the day's budget |
| Drawdown tiers | −5% from peak → ½ size; −10% → halt | Halt lasts until the owner runs reset-peak |
| Open positions | 4 | |
| Margin | 50% utilization | Only for instruments where you set margin_per_contract |
| Contract cap | ES/NQ 20, others 100 | |
| Options | defined risk only | Sized on max loss; naked, short straddle, short strangle and ratio structures are rejected |
| Stops | snapped to tick, away from entry | Never widened |
Order of evaluation: lockout → drawdown halt → position count → order validity → min(per-trade,
heat, bucket, daily budget, margin, cap). The smallest limit wins and is reported in binding_limits.
Examples (with the default $55,000 account)
| Trade | Result |
|---|---|
| ES long 6500, stop 6498, costs off | 5 contracts (matches the spreadsheet) |
| ES long 6500, stop 6498, costs on | 4 contracts ($116.50 risk each) |
| ES long 6500, stop 6485 | REJECTED → suggestion: 7 MES |
| SPX credit spread, width 5, credit 1.20 | 1 spread ($382.60 max loss) |
| 3rd MES position, then an NQ trade | NQ REJECTED by bucket_heat → suggests MNQ |
Security and trust model
- Pending approvals reserve risk. Until filled, cancelled or expired, an approval counts against heat,
bucket, daily-budget, margin and position-count limits. A new
check_tradefor a symbol replaces that symbol's earlier pending approval. - The agent reports its own fills and closes. The server cannot see your broker. An agent that skips
record_closeor reports a falseexit_pricemakes equity, lockouts and drawdown tiers wrong. Treat the server as a guardrail for a cooperative agent, not as enforcement, and reconcile with your broker usingposition-risk-admin set-equity/remove-position. - HTTP transport. It binds to loopback by default. Any other
--hostis refused unlessPOSITION_RISK_TOKEN(24+ characters) is set; clients then sendAuthorization: Bearer <token>. There is no TLS: put a reverse proxy in front for anything beyond localhost. Every tool is available to any authenticated client. - Rulebook edits. State remembers which rulebook fingerprint created it. If
rules.yamlchanges,rulebook_changedis true in every response until the owner runsposition-risk-admin ack-rulebook.
Design notes
- Decimal math throughout, so
floor()never turns 11.0 into 10.999. - Equity is never an input. It starts at
account.starting_equity, moves with realized P&L and can be synced withset-equity. A hallucinated account size can't inflate a position. - Every response carries the rulebook version and fingerprint, and every state change is journaled, so you can audit which rules approved which trade.
- The state store is pluggable.
JsonFileStore(file lock + atomic write) is the default. For AWS, implement the sametransaction()/read()interface on DynamoDB with a conditional version write, and run the server behind Lambda/Fargate with--http. - Margins in
instruments.yamlare null on purpose. They change, and they differ by broker and session, so set them from your broker.
Roadmap ideas
- Broker adapters (equity and positions pulled from the broker instead of tracked locally)
- DynamoDB store + Lambda handler
- Futures options; per-symbol session calendars for the day rollover
- Volatility-regime multiplier (e.g. from a VIX regime feed)
License
MIT.
This software enforces rules you configure. It is not financial advice.
Metadata
Release files for trading-position-risk-mcp 0.1.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| trading_position_risk_mcp-0.1.0.tar.gz | 32.6 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| trading_position_risk_mcp-0.1.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 64.3 kB
Release files / trading_position_risk_mcp-0.1.0.tar.gz
| Download URL | trading_position_risk_mcp-0.1.0.tar.gz |
|---|---|
| Size | 32.6 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
9a59be3d2199e6684e60aa9d2febc78f52cfb7686bb996a89ae392393bd425d9
|
|
BLAKE2b-256 checksum How to use checksums |
d792fb9ba2eef0f52bbd1f6c57acbe667f44785b16846606afd35845a808f2c4
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Oct 11, 2026.
Transparency logRelease files / trading_position_risk_mcp-0.1.0-py3-none-any.whl
| Download URL | trading_position_risk_mcp-0.1.0-py3-none-any.whl |
|---|---|
| Size | 31.7 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
41ac590b9252dce6f52fb02c41141e379eb940ee0ffa5977c881b144248c6450
|
|
BLAKE2b-256 checksum How to use checksums |
09e31f87de6ece9f810972017710b317ad11415a54ad960a2babcf002096ee74
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Oct 11, 2026.
Transparency log