Documentation Index
Fetch the complete documentation index at: https://mathematicalcompany.mintlify.app/llms.txt
Use this file to discover all available pages before exploring further.
Deploy strategies to the Horizon Cloud and monitor them in real time, all from Python or the MCP server. The full lifecycle (create, validate, deploy, monitor, stop) works entirely from the SDK with no browser required.
Setup
Generate an SDK key from horizon.mathematicalcompany.com under Settings > SDK Keys.
export HORIZON_API_KEY="hz_sdk_abc123..."
Quick Start - Full Lifecycle
from horizon import HorizonCloud
cloud = HorizonCloud() # reads HORIZON_API_KEY from env
# 1. Create a strategy from code
strategy = cloud.create_strategy(
name="Simple MM",
code="""
import horizon as hz
def pipeline(ctx):
mid = ctx.feeds.get("mid")
if not mid:
return None
return hz.quotes(fair=mid.price, spread=0.04, size=10)
hz.run(
pipeline=[pipeline],
markets=["will-x-happen"],
feeds=[hz.PolymarketBook("will-x-happen")],
)
""",
)
print(f"Created: {strategy['id']} ({strategy['status']})")
# 2. Use existing credentials or save new ones
creds = cloud.list_credentials()
if creds:
cred_id = creds[0]["id"] # use the first saved credential
print(f"Using existing credential: {creds[0]['label']}")
else:
cred = cloud.save_credentials(
private_key="0xabc123...",
label="Trading Key",
exchange="polymarket",
)
cred_id = cred["id"]
# 3. Validate (static analysis + worker sandbox)
result = cloud.validate(strategy["id"])
assert result["valid"], result["errors"]
# 4. Deploy in paper mode
dep = cloud.deploy(
strategy["id"],
credential_id=cred_id,
mode="paper",
)
print(f"Deployment: {dep['id']} - {dep['status']}")
# 5. Wait until running
dep = cloud.wait_for_running(strategy["id"], timeout=120)
# 6. Monitor
metrics = cloud.metrics(strategy["id"])
print(f"PnL: {metrics['latest']['total_pnl']}")
for log in cloud.logs(strategy["id"], limit=20):
print(f"[{log['level']}] {log['message']}")
# 7. Stop
cloud.stop(strategy["id"])
API Reference
Constructor
HorizonCloud(
api_key: str = None, # falls back to HORIZON_API_KEY env var
base_url: str = None, # falls back to HORIZON_PLATFORM_URL or default
timeout: int = 30, # request timeout in seconds
)
Methods
| Method | Description |
|---|
create_strategy(name, code, ...) | Create a strategy from Python code |
validate(strategy_id) | Validate code (static + sandbox) |
list_strategies() | List all strategies |
get_strategy(id) | Get strategy details |
save_credentials(private_key, ...) | Save exchange credentials (encrypted) |
list_credentials() | List credential metadata (no keys) |
deploy(id, credential_id, mode, markets) | Deploy a strategy |
stop(id) | Stop active deployments |
status(id) | Current deployment status |
deployments(id) | List deployment history |
metrics(id, limit=50) | Performance metrics |
logs(id, limit=100, level, deployment_id) | Deployment logs |
account() | Plan, limits, usage |
wait_for_running(id, timeout=120) | Block until running |
Create Strategy
cloud.create_strategy(
name="My Strategy", # required, max 200 chars
code="def pipeline(ctx):...", # required, max 500KB
description="...", # optional, max 2000 chars
params={"threshold": 0.6}, # optional strategy parameters
risk_config={...}, # optional risk configuration
auto_fix=True, # auto-fix common AI mistakes (default)
)
Code validation runs automatically on creation:
- Forbidden imports blocked:
os, subprocess, socket, requests, pickle, ctypes, etc.
- Forbidden builtins blocked:
eval(), exec(), compile(), open(), __import__(), etc.
- Required SDK patterns: at least one
def ...(ctx) pipeline function and hz.quotes()/hz.run() usage.
- Code is sanitized: BOM stripped, line endings normalized, common whitespace dedented.
If validation fails, returns 422 with detailed errors:
try:
cloud.create_strategy(name="Bad", code="import subprocess\nsubprocess.run('ls')")
except HorizonCloudError as e:
print(e.body["validation_errors"])
# [{"line": 1, "message": "Import \"subprocess\" is not allowed..."}]
Validate
Two-phase validation:
- Static analysis (platform-side, instant): forbidden patterns, import whitelist, SDK usage checks.
- Sandbox validation (worker-side): AST parsing, import resolution, forbidden attribute access.
result = cloud.validate("strategy-id")
if result["valid"]:
print("Ready to deploy!")
else:
for err in result["errors"]:
print(f" Line {err['line']}: {err['message']}")
Save Credentials
cloud.save_credentials(
private_key="0xabc...", # 64 hex chars, optional 0x prefix
label="My Trading Key", # max 100 chars
exchange="polymarket", # "polymarket" or "kalshi"
wallet_address="0x123...", # optional, 0x + 40 hex chars
)
Security guarantees:
- Private key is transmitted over HTTPS only.
- Encrypted at rest with AES-256-GCM (platform-side encryption key, not stored in DB).
- Never returned in any API response - not in
list_credentials, not in save_credentials response.
- Decrypted only in-memory at deploy time, then sent to the worker over HMAC-signed HTTPS.
- Max 10 credentials per user.
- All credential operations are critically audited (audit log insert failure throws, preventing silent loss).
Deploy
cloud.deploy(
strategy_id="abc-123",
credential_id="cred-456", # from save_credentials()
mode="paper", # "paper" (default) or "live"
markets=["slug-a"], # optional market slugs
)
mode="paper" - dry run, no real orders.
mode="live" - requires Pro/Ultra plan + circuit breaker enabled.
markets - patches hz.run(markets=[...]) in the strategy code.
Logs
cloud.logs(
strategy_id="abc-123",
limit=100, # max entries (up to 500)
level="error", # optional: "info", "error", "warning"
deployment_id="dep-789", # optional (defaults to latest)
)
Returns log entries in chronological order (oldest first).
Security Architecture
Every request goes through multiple security layers - all enforced server-side, never in the SDK client:
Authentication & Authorization
| Layer | How it works |
|---|
| API key validation | SHA-256 hash lookup in sdk_keys table. |
| Expiration | Keys can have an expires_at date. Expired keys are rejected. |
| Rate limiting | Per-user, per-action limits via Upstash Redis (in-memory fallback for dev). |
| Audit logging | Every create/deploy/stop/credential action is logged. Credential ops are critical (failure throws). |
Code Security
| Layer | What it blocks |
|---|
| Import whitelist | Only horizon, hz, datetime, collections, math, typing, enum, statistics, pydantic, abc |
| Forbidden builtins | eval, exec, compile, open, __import__, globals, locals, getattr, setattr, breakpoint |
| Forbidden modules | os, sys, subprocess, socket, http, urllib, requests, pickle, ctypes, threading, multiprocessing, importlib, shutil, tempfile |
| Forbidden attributes | __builtins__, __subclasses__, __bases__, __globals__, __code__, __closure__, __mro__, __reduce__ |
| Line continuation block | Backslash \ continuations blocked to prevent pattern bypass |
| Code sanitization | BOM stripping, line ending normalization, leading whitespace dedent |
| Worker sandbox | AST-level validation, import resolution check, separate process isolation |
Credential Security
SDK (HTTPS) → Platform (AES-256-GCM encrypt) → Database (ciphertext + IV + auth tag)
↓ (at deploy time only)
Platform (decrypt in-memory)
↓
Worker (HMAC-signed HTTPS) → Strategy subprocess
- Encryption key is a 256-bit hex string stored in platform env (
ENCRYPTION_KEY), never in the database.
- Worker communication uses Bearer token + HMAC-SHA256 signature + HTTPS-only enforcement.
- Worker URL must be
https:// in production (localhost exempted for dev).
Deployment Security
| Check | When |
|---|
Strategy must be validated/stopped/error/deployed status | Before deploy |
| Circuit breaker required for live trading | Before deploy |
| Double-deploy guard (409 if already active) | Before deploy |
| Worker capacity check (queue if full) | Before deploy |
| Plan limits (concurrent deploys, live trading) | Before deploy |
Risk overrides injected by platform_runner.py | At runtime (worker) |
| Strategy runs in isolated subprocess | At runtime (worker) |
Plan Limits
All limits are enforced server-side before any action proceeds.
| Limit | Free | Pro | Ultra |
|---|
| Max strategies | 1 | 10 | Unlimited |
| Concurrent deploys | 1 | 5 | 10 |
| Live trading | No | Yes | Yes |
| Backtests / week | 1 | 10 | Unlimited |
| Priority execution | No | No | Yes |
try:
cloud.deploy("abc-123", credential_id="cred-456", mode="live")
except HorizonCloudError as e:
if e.status_code == 403:
print(f"Plan limit: {e.body.get('plan')}")
Check your current usage:
info = cloud.account()
print(f"Plan: {info['plan']}")
print(f"Limits: {info['limits']}")
print(f"Usage: {info['usage']}")
Deployment Lifecycle
pending → starting → running → stopped
↓ ↓
queued error
| Status | Meaning |
|---|
pending | Created, worker being contacted |
queued | Worker at capacity, waiting for a slot |
starting | Worker acknowledged, process launching |
running | Strategy is actively trading |
stopped | Gracefully stopped (via cloud.stop() or platform UI) |
error | Crashed or timed out |
Rate Limits
| Action | Limit |
|---|
| Read endpoints (strategies, metrics, logs, credentials) | 30 / minute |
| Create strategy | 10 / minute |
| Validate | 10 / minute |
| Deploy | 5 / minute |
| Stop | 10 / minute |
| Save credentials | 5 / minute |
Exceeding the limit returns 429 Too Many Requests.
When running the MCP server, cloud operations are available via the cloud compound tool with an action parameter:
| Action | Description |
|---|
create_strategy | Create strategy from code |
validate | Validate strategy code |
save_credentials | Save exchange credentials (encrypted) |
list_credentials | List credential metadata |
list_strategies | List strategies |
get_strategy | Get strategy details |
deploy | Deploy a strategy |
stop | Stop a deployment |
status | Get deployment status |
metrics | Get performance metrics |
logs | Get deployment logs |
account | Get account info |
Example MCP usage (Claude Desktop / Claude Code):
“Create a market making strategy, save my Polymarket key, and deploy it in paper mode”
// Example: deploy a strategy
{"action": "deploy", "params": "{\"strategy_id\": \"uuid\", \"credential_id\": \"uuid\", \"mode\": \"paper\"}"}
All cloud actions use HORIZON_API_KEY from the environment for authentication.
Error Handling
All API errors raise HorizonCloudError with status_code and body:
from horizon import HorizonCloud, HorizonCloudError
cloud = HorizonCloud()
try:
cloud.create_strategy(name="Bad", code="import os; os.system('rm -rf /')")
except HorizonCloudError as e:
print(f"Status: {e.status_code}") # 422
print(f"Errors: {e.body['validation_errors']}")
| Status | Meaning |
|---|
| 400 | Bad request (invalid params, missing code, key format error) |
| 401 | Invalid or missing API key |
| 403 | Plan limit exceeded |
| 404 | Strategy or credential not found |
| 409 | Strategy already has an active deployment |
| 422 | Code validation failed (includes validation_errors list) |
| 429 | Rate limit exceeded |
| 500 | Worker or platform error |
Architecture
SDK (HorizonCloud)
│
▼ HTTPS (Bearer hz_sdk_...)
Platform API (v1)
│ validates key → enforces plan limits → rate limits
│ encrypts credentials (AES-256-GCM) → validates code (static + sandbox)
▼
Worker (FastAPI, HMAC-signed HTTPS)
│ AST validation → subprocess isolation → risk overrides
▼
Trading Engine (Rust/PyO3)
│ 8-point risk pipeline → paper/live exchange
▼
Polymarket / Kalshi / Alpaca
The SDK client (HorizonCloud) calls the Platform’s v1 REST API over HTTPS using your SDK key. The Platform validates the key (SHA-256 hash lookup), enforces plan limits and rate limits, then forwards deploy requests to the Worker over HMAC-signed HTTPS. The Worker validates the code in a sandbox, spawns an isolated subprocess running your strategy with risk overrides injected, and reports metrics back via webhooks.