Skip to main content
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

MethodDescription
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:
  1. Static analysis (platform-side, instant): forbidden patterns, import whitelist, SDK usage checks.
  2. 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

LayerHow it works
API key validationSHA-256 hash lookup in sdk_keys table.
ExpirationKeys can have an expires_at date. Expired keys are rejected.
Rate limitingPer-user, per-action limits via Upstash Redis (in-memory fallback for dev).
Audit loggingEvery create/deploy/stop/credential action is logged. Credential ops are critical (failure throws).

Code Security

LayerWhat it blocks
Import whitelistOnly horizon, hz, datetime, collections, math, typing, enum, statistics, pydantic, abc
Forbidden builtinseval, exec, compile, open, __import__, globals, locals, getattr, setattr, breakpoint
Forbidden modulesos, 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 blockBackslash \ continuations blocked to prevent pattern bypass
Code sanitizationBOM stripping, line ending normalization, leading whitespace dedent
Worker sandboxAST-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

CheckWhen
Strategy must be validated/stopped/error/deployed statusBefore deploy
Circuit breaker required for live tradingBefore 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.pyAt runtime (worker)
Strategy runs in isolated subprocessAt runtime (worker)

Plan Limits

All limits are enforced server-side before any action proceeds.
LimitFreeProUltra
Max strategies110Unlimited
Concurrent deploys1510
Live tradingNoYesYes
Backtests / week110Unlimited
Priority executionNoNoYes
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
StatusMeaning
pendingCreated, worker being contacted
queuedWorker at capacity, waiting for a slot
startingWorker acknowledged, process launching
runningStrategy is actively trading
stoppedGracefully stopped (via cloud.stop() or platform UI)
errorCrashed or timed out

Rate Limits

ActionLimit
Read endpoints (strategies, metrics, logs, credentials)30 / minute
Create strategy10 / minute
Validate10 / minute
Deploy5 / minute
Stop10 / minute
Save credentials5 / minute
Exceeding the limit returns 429 Too Many Requests.

MCP Tools

When running the MCP server, cloud operations are available via the cloud compound tool with an action parameter:
ActionDescription
create_strategyCreate strategy from code
validateValidate strategy code
save_credentialsSave exchange credentials (encrypted)
list_credentialsList credential metadata
list_strategiesList strategies
get_strategyGet strategy details
deployDeploy a strategy
stopStop a deployment
statusGet deployment status
metricsGet performance metrics
logsGet deployment logs
accountGet 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']}")
StatusMeaning
400Bad request (invalid params, missing code, key format error)
401Invalid or missing API key
403Plan limit exceeded
404Strategy or credential not found
409Strategy already has an active deployment
422Code validation failed (includes validation_errors list)
429Rate limit exceeded
500Worker 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.