Prediction market risk management — Kelly sizing, EV calculation, drawdown protection
Project description
Nephyr Risk
Prediction market risk management for Python bots and AI agents.
Nephyr Risk packages CLM Studios' battle-tested risk math (used in live Polymarket and Kalshi trading bots) as a clean, stateless Python library — with an MCP server so Claude, ChatGPT, and any MCP-compatible agent can call it directly.
Zero dependencies for the core. No database, no API calls, no side effects.
Installation
# Core library only (no external dependencies)
pip install nephyr-risk
# With MCP server support
pip install "nephyr-risk[mcp]"
# With REST API support
pip install "nephyr-risk[api]"
Quick Start
from nephyr_risk import (
calculate_kelly_size,
calculate_ev,
check_risk_status,
validate_trade,
get_exposure,
simulate_drawdown,
)
# 1. Size a position: 60% model probability, 40¢ market price, $1000 bankroll
kelly = calculate_kelly_size(probability=0.60, entry_price=0.40, bankroll=1000)
print(kelly.size) # ~50.0 (capped at 5% of bankroll)
print(kelly.capped) # True
print(kelly.full_kelly) # ~0.3333 (full Kelly)
# 2. Check expected value
ev = calculate_ev(model_probability=0.65, market_price=0.50, platform="polymarket")
print(ev.ev) # ~0.13 (after fees)
print(ev.edge) # 0.15 (before fees)
print(ev.is_positive) # True
# 3. Check current risk status
status = check_risk_status(bankroll=950, daily_pnl=-50, peak_bankroll=1000)
print(status.status) # 'ACTIVE'
print(status.can_trade) # True
# 4. Pre-trade validation
check = validate_trade(proposed_size=50, bankroll=1000, daily_pnl=0, peak_bankroll=1000)
print(check.approved) # True
print(check.projected_daily_loss_pct) # 5.0
# 5. Portfolio exposure
exposure = get_exposure(
positions=[{"size": 80}, {"size": 60}, {"size": 40}],
bankroll=1000
)
print(exposure["total_exposure_pct"]) # 18.0
print(exposure["concentration_risk"]) # False
# 6. Simulate a drawdown scenario
scenario = simulate_drawdown(bankroll=1000, loss_sequence=[30, 40, 50, 60, 80])
print(scenario["max_drawdown_pct"]) # 26.0 → SHUTDOWN triggered
print(scenario["would_shutdown"]) # True
print(scenario["shutdown_after_n"]) # 5
API Reference
calculate_kelly_size(probability, entry_price, bankroll, kelly_fraction=0.25, max_position_pct=5.0, min_size=1.0) → KellyResult
Quarter-Kelly position sizing with a bankroll-percentage hard cap.
| Param | Type | Description |
|---|---|---|
probability |
float | Model probability of YES (0 < p < 1) |
entry_price |
float | Market price for YES contract (0 < price < 1) |
bankroll |
float | Available capital in USD |
kelly_fraction |
float | Fraction of full Kelly (default 0.25) |
max_position_pct |
float | Hard cap as % of bankroll (default 5%) |
min_size |
float | Minimum position size in USD (default $1) |
Returns KellyResult(size, full_kelly, quarter_kelly, capped, cap_reason).
Returns size=0 with a descriptive cap_reason for invalid inputs or no edge.
calculate_ev(model_probability, market_price, platform="polymarket") → EVResult
Expected value calculation after platform fees.
| Param | Type | Description |
|---|---|---|
model_probability |
float | Your model's YES probability |
market_price |
float | Current YES price on the exchange |
platform |
str | "polymarket" or "kalshi" |
Returns EVResult(ev, edge, fee_per_leg, round_trip_fee, is_positive).
Fee models:
- Polymarket:
fee = 0.02 × price(flat 2% per leg) - Kalshi:
fee = 0.07 × P × (1−P)(peaks at P=0.50: 1.75¢/contract)
calculate_fee(price, platform) → float
Fee per leg for a given price and platform. Use calculate_polymarket_fee(price) or calculate_kalshi_fee(price) for direct access.
check_risk_status(bankroll, daily_pnl, peak_bankroll, daily_loss_limit_pct=10.0, drawdown_limit_pct=25.0) → RiskStatus
Current risk status against configured limits.
Returns RiskStatus(status, can_trade, daily_loss_pct, drawdown_pct, warnings).
Status values:
"ACTIVE"— All clear, trading allowed"PAUSED"— Daily loss limit hit, resume tomorrow"SHUTDOWN"— Drawdown limit hit, manual review required
Warnings are raised (non-blocking) at 70% of each limit.
validate_trade(proposed_size, bankroll, daily_pnl, peak_bankroll, daily_loss_limit_pct=10.0, drawdown_limit_pct=25.0) → TradeValidation
Pre-trade risk check. Projects worst case (full loss on proposed trade) against limits.
Returns TradeValidation(approved, rejection_reason, projected_daily_loss_pct, projected_drawdown_pct).
get_exposure(positions, bankroll) → dict
Portfolio exposure summary.
positions is a list of dicts, each with at least {"size": float}.
Returns: total_exposure_usd, total_exposure_pct, largest_position_usd, largest_position_pct, num_positions, concentration_risk (bool, triggers at >10% of bankroll in one position), positions_above_5pct.
simulate_drawdown(bankroll, loss_sequence, daily_loss_limit_pct=10.0, drawdown_limit_pct=25.0) → dict
Simulate a hypothetical sequence of losses.
loss_sequence is a list of dollar amounts (positive = loss, negative = gain).
Returns: final_bankroll, max_drawdown_pct, max_daily_loss_pct, total_loss_usd, would_pause, would_shutdown, pause_after_n, shutdown_after_n, events (per-step detail).
MCP Server
Nephyr Risk ships as an MCP server so any MCP-compatible agent (Claude, ChatGPT, etc.) can call these functions as tools.
pip install "nephyr-risk[mcp]"
nephyr-risk-mcp
The server exposes all 7 core functions as MCP tools with full JSON schemas. Add it to your ~/.claude/claude_desktop_config.json:
{
"mcpServers": {
"nephyr-risk": {
"command": "nephyr-risk-mcp"
}
}
}
REST API
Nephyr Risk also ships as a FastAPI REST API for use from any language or tool.
Install and run
pip install "nephyr-risk[api]"
# Start the server (reload mode for development)
python -m api
# Or use uvicorn directly
uvicorn api.app:app --host 0.0.0.0 --port 8000 --reload
Interactive Swagger UI is available at http://localhost:8000/docs once the server is running.
Endpoints
| Method | Path | Function |
|---|---|---|
GET |
/v1/health |
Health check |
POST |
/v1/kelly |
calculate_kelly_size |
POST |
/v1/ev |
calculate_ev |
POST |
/v1/risk-status |
check_risk_status |
POST |
/v1/validate-trade |
validate_trade |
POST |
/v1/exposure |
get_exposure |
POST |
/v1/drawdown |
simulate_drawdown |
POST |
/v1/fee |
calculate_fee |
curl examples
# Health check
curl http://localhost:8000/v1/health
# Kelly position sizing
curl -s -X POST http://localhost:8000/v1/kelly \
-H "Content-Type: application/json" \
-d '{"probability": 0.6, "entry_price": 0.4, "bankroll": 1000}' | python -m json.tool
# Expected value (Kalshi)
curl -s -X POST http://localhost:8000/v1/ev \
-H "Content-Type: application/json" \
-d '{"model_probability": 0.65, "market_price": 0.50, "platform": "kalshi"}' | python -m json.tool
# Risk status check
curl -s -X POST http://localhost:8000/v1/risk-status \
-H "Content-Type: application/json" \
-d '{"bankroll": 950, "daily_pnl": -50, "peak_bankroll": 1000}' | python -m json.tool
# Pre-trade validation
curl -s -X POST http://localhost:8000/v1/validate-trade \
-H "Content-Type: application/json" \
-d '{"proposed_size": 50, "bankroll": 1000, "daily_pnl": 0, "peak_bankroll": 1000}' | python -m json.tool
# Portfolio exposure
curl -s -X POST http://localhost:8000/v1/exposure \
-H "Content-Type: application/json" \
-d '{"positions": [{"size": 80}, {"size": 60}, {"size": 40}], "bankroll": 1000}' | python -m json.tool
# Drawdown simulation
curl -s -X POST http://localhost:8000/v1/drawdown \
-H "Content-Type: application/json" \
-d '{"bankroll": 1000, "loss_sequence": [30, 40, 50, 60, 70]}' | python -m json.tool
# Fee calculation
curl -s -X POST http://localhost:8000/v1/fee \
-H "Content-Type: application/json" \
-d '{"price": 0.5, "platform": "polymarket"}' | python -m json.tool
API key authentication (optional)
Set the NEPHYR_API_KEYS environment variable to a comma-separated list of valid keys.
When set, requests with an X-API-Key header are validated against the list.
Requests without the header are allowed (free-tier placeholder).
export NEPHYR_API_KEYS="key-abc123,key-def456"
python -m api
curl -H "X-API-Key: key-abc123" -X POST http://localhost:8000/v1/kelly \
-H "Content-Type: application/json" \
-d '{"probability": 0.6, "entry_price": 0.4, "bankroll": 1000}'
Pricing
| Tier | Details |
|---|---|
| Free | 50 calls/month |
| Paid | $29/month — unlimited calls |
| Agent-to-agent | $0.005/call |
License
MIT — see LICENSE.
Built by CLM Studios.
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file nephyr_risk-0.1.1.tar.gz.
File metadata
- Download URL: nephyr_risk-0.1.1.tar.gz
- Upload date:
- Size: 41.3 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.14.3
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
dc74ffb0870d412a98e156ec6ae1f29455ea5dbbadd2e70567ecbdc21cc6d5cb
|
|
| MD5 |
c1a8f1b2f55477878a757969c9fcddaa
|
|
| BLAKE2b-256 |
5f994936416cb6ac661d74f3d097ded6eee31934d5dc51bd4879fbb5df8c53e0
|
File details
Details for the file nephyr_risk-0.1.1-py3-none-any.whl.
File metadata
- Download URL: nephyr_risk-0.1.1-py3-none-any.whl
- Upload date:
- Size: 30.0 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.14.3
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
f6fe2105b9d70244b6dd00fbceebb2cf632a5bfac930dee6242934c4a1adc21f
|
|
| MD5 |
681640a7e05587d64fb4f8fa52c30e40
|
|
| BLAKE2b-256 |
b7cdf5c18eed08e8f6735961798780ae2c6f39000d03674ea88ebd3a8994c31c
|