Skip to main content

Prediction market risk management — Kelly sizing, EV calculation, drawdown protection

Project description

Nephyr Risk

PyPI version License: MIT

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


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

nephyr_risk-0.1.0.tar.gz (41.3 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

nephyr_risk-0.1.0-py3-none-any.whl (30.0 kB view details)

Uploaded Python 3

File details

Details for the file nephyr_risk-0.1.0.tar.gz.

File metadata

  • Download URL: nephyr_risk-0.1.0.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

Hashes for nephyr_risk-0.1.0.tar.gz
Algorithm Hash digest
SHA256 aae2fa6b48caccb99c4d22aadaaa8304e79e637ce7ad7a0a74eb4925f7881833
MD5 522627c63bfa0b3e77beda91a9b9571e
BLAKE2b-256 7fb261dee3902e87b1d66081c3a82f1b24c7b7074f6b37dfb1451eaa3e24fc1a

See more details on using hashes here.

File details

Details for the file nephyr_risk-0.1.0-py3-none-any.whl.

File metadata

  • Download URL: nephyr_risk-0.1.0-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

Hashes for nephyr_risk-0.1.0-py3-none-any.whl
Algorithm Hash digest
SHA256 1bb5f9c70616ea4e688169b16b6a3e78b9189662b7b2ba27b25929bbe620445c
MD5 87d03a612062cfaa07b4c242aae6f977
BLAKE2b-256 64b025fa8f166f317f6473ccd597d1670a8a12755fc470ded778798285f7b711

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page