---
name: canon-risk-engine
description: Integrate Canon's pre-trade risk intelligence API into agentic trading flows on Hyperliquid. Use when building or updating trade validation, sizing, execution gating, monitoring, and outcome logging with Canon endpoints.
---

# Canon Risk Engine

## Use This Skill When

- You are wiring a trading agent to Canon before opening, increasing, reducing, or closing positions.
- You need deterministic trade gating from `risk_score`, `recommendation`, and `flags`.
- You are implementing monitoring, alerting, or post-trade calibration feedback loops.

## Canon API Snapshot

- Base URL: `https://api.canonprotocol.org`
- Exchange coverage: Hyperliquid (live)
- Auth: Bearer token on all endpoints except `GET /health` and `GET /v1/alerts/ws`
- Early Access rate limit: 100 requests/minute per API key

Core endpoints:

- `POST /v1/validate`: Pre-trade risk analysis for one trade
- `POST /v1/validate/batch`: Sequential risk analysis for 1-50 planned trades
- `GET /v1/assets`: Tradable assets and constraints (max leverage, isolated-only)
- `GET /v1/model-info`: Engine/calibration metadata and recommendation bands
- `GET /v1/monitor/:wallet`: Live portfolio and per-position risk
- `GET /v1/alerts/ws`: Push alerts on threshold breaches
- `POST /v1/outcome`: Record realized trade outcomes
- `GET /v1/outcomes/stats`: Aggregate outcome calibration and band performance stats
- `GET /v1/accuracy`: Calibration metrics from recorded outcomes
- `GET /v1/calibration/health`: Cross-asset VaR health diagnostics
- `GET /v1/backtest/:asset`: Per-asset VaR backtest diagnostics

## Non-Negotiable Rules

- Call `POST /v1/validate` before every open or size increase.
- Do not block exits: when `action="close"`, Canon caps risk score at 30 and returns `proceed`.
- Validate asset constraints from `GET /v1/assets` before sending orders.
- Never use `mode="cross"` for assets with `only_isolated=true`.
- Persist `meta.request_id` and `model_version.calibration_hash` for traceability.

## Local Doc Map

These paths are local to this repository. Use them only when the agent has this repo mounted.

- Core setup: `docs/introduction.md`, `docs/quickstart.md`, `docs/api/authentication.md`
- Trade evaluation: `docs/api/validate.md`, `docs/api/batch-validate.md`
- Risk interpretation: `docs/risk/risk-score.md`, `docs/risk/flags.md`, `docs/risk/risk-layers.md`
- Operations: `docs/api/monitor.md`, `docs/api/alerts-ws.md`, `docs/api/errors.md`, `docs/api/rate-limiting.md`
- Model quality: `docs/api/outcome.md`, `docs/api/accuracy.md`, `docs/api/calibration-health.md`, `docs/api/backtest.md`, `docs/api/model-info.md`

## Integration Workflow

### 1. Startup Preflight

1. Fetch `GET /v1/assets` and build an in-memory map:
   - `name`, `max_leverage`, `only_isolated`, `mark_price`, `funding_rate_8h`
2. Fetch `GET /v1/model-info` and cache:
   - `engine_version`, `calibration_hash`, `recommendation_bands`
3. Refresh both every 300 seconds (5 minutes), and immediately on validation errors like `invalid asset`.

### 2. Pre-Trade Validation

For each candidate trade call `POST /v1/validate` with:

- `asset`, `action`, `size`, `wallet`
- Optional but usually required: `leverage`, `mode`
- Optional: `monte_carlo=true` when you need deeper tail-risk insight

Request template:

```json
{
  "asset": "ETH",
  "action": "long",
  "size": 50000,
  "leverage": 10,
  "wallet": "0x7a3b1234567890abcdef1234567890abcdef1234",
  "mode": "cross",
  "monte_carlo": false
}
```

Hard prechecks before the API call:

- Wallet must be 42 chars with `0x` prefix.
- `size > 0` and `size <= 100000000`.
- `leverage <= asset.max_leverage`.
- Asset exists in current `assets` list.

### 3. Decision Engine (Execution Gating)

Default recommendation bands:

- `0-40`: `proceed`
- `41-70`: `proceed_with_caution`
- `71-100`: `abort`

Apply this decision order:

1. If `recommendation == "abort"`: reject trade.
2. If any critical flags are present: reject trade.
3. If caution/warning conditions exist: resize or adjust leverage.
4. If clean: execute.

Treat these flags as immediate block conditions:

- `extreme_slippage`
- `near_liquidation`
- `near_backstop`
- `oi_at_cap`

Suggested adaptive actions for warning flags:

- `thin_book` or `high_slippage`: cap size to <= `0.5 * slippage.depth_50bps`
- `funding_expensive` or `cex_funding_divergence`: require expected alpha to exceed projected funding drag
- `correlated_exposure` or `concentration_risk`: reduce new size allocation (for example 25-50%)
- `high_leverage`: lower leverage and re-validate

These are default policy baselines. Tune thresholds and resizing factors to your strategy's turnover, alpha half-life, and risk budget.

### 4. Portfolio Construction (Multi-Leg)

Use `POST /v1/validate/batch` for planned multi-trade sequences.

- Canon evaluates each trade against the wallet state resulting from all previous trades in the same batch.
- Use this when opening correlated legs or rebalancing multiple positions.
- Batch size limit: 50 trades.

Batch template:

```json
{
  "wallet": "0x7a3b1234567890abcdef1234567890abcdef1234",
  "trades": [
    {
      "asset": "BTC",
      "action": "long",
      "size": 50000,
      "leverage": 10,
      "mode": "cross"
    },
    {
      "asset": "ETH",
      "action": "long",
      "size": 30000,
      "leverage": 5,
      "mode": "cross"
    }
  ]
}
```

Deterministic batch policy:

1. Run `POST /v1/validate/batch` before placing any orders.
2. If any `results[i]` has `recommendation == "abort"` or a critical block flag (`extreme_slippage`, `near_liquidation`, `near_backstop`, `oi_at_cap`), abort the whole sequence and place zero trades.
3. If there are no aborts but at least one `proceed_with_caution`, reduce only caution trades by 50% and run one final batch re-check.
4. Execute only when the final batch has no `abort` and no critical block flags. If still not satisfied, place zero trades.

### 5. Runtime Risk Monitoring

1. Poll `GET /v1/monitor/:wallet` every 30 seconds.
2. Open WebSocket `wss://api.canonprotocol.org/v1/alerts/ws` for push alerts.
3. Send `AlertConfig` after connect.
4. On severe alerts, trigger protective actions (reduce, hedge, or close).

AlertConfig template:

```json
{
  "wallet": "0x1234567890abcdef1234567890abcdef12345678",
  "thresholds": {
    "max_risk_score": 70,
    "min_liquidation_distance_pct": 8.0,
    "max_portfolio_var_pct": 50.0,
    "max_funding_drag_daily_pct": 1.0
  }
}
```

Interpretation:

- `max_risk_score` is the warning threshold trigger for subscriptions.
- Canon labels alerts as `critical` at risk score >= 85, liquidation distance < 2%, or regime shift to `crisis`.

### 6. Post-Trade Feedback Loop

After trade close, record result using `POST /v1/outcome`.
Required fields include:

- `trade_id`, `asset`, `action`, `entry_price`, `exit_price`, `size`, `leverage`
- `risk_score_at_entry`
- `calibration_hash` (from `validate.model_version.calibration_hash`)
- `pnl_usd`, `pnl_pct`, `holding_period_hours`

Extraction rule:

```python
calibration_hash = validate_response["model_version"]["calibration_hash"]
```

Outcome template:

```json
{
  "trade_id": "trade_abc123",
  "asset": "BTC",
  "action": "long",
  "entry_price": 67500.0,
  "exit_price": 68200.0,
  "size": 50000,
  "leverage": 10,
  "risk_score_at_entry": 35,
  "calibration_hash": "a1b2c3d4...",
  "pnl_usd": 518.5,
  "pnl_pct": 10.37,
  "holding_period_hours": 4.5,
  "fees_paid_usd": 35.0,
  "funding_paid_usd": 12.3
}
```

### 7. Model Health and Drift Checks

Schedule regular diagnostics:

- `GET /v1/accuracy` (updated every 5 minutes)
- `GET /v1/calibration/health`
- `GET /v1/backtest/:asset` for actively traded assets

Default safety reactions:

- If `accuracy.brier_score > 0.25`, tighten sizing and raise caution thresholding.
- If `calibration/health.aggregate_status == "degraded"`, log warning mode and tighten caution thresholding only for assets in `per_asset` where `status != "pass"` (for example lower max leverage and reduce size 25-40% on those assets).
- If `calibration/health.aggregate_status == "miscalibrated"`, switch to defensive mode or pause new risk.
- If backtest status is `too_many_breaches` on traded asset, reduce leverage and position size until resolved.

## Error Handling Contract

Map statuses to actions:

- `400 validation_error`: fail fast, fix payload, do not retry unchanged request.
- `401`: refresh credentials or rotate key; do not retry blindly.
- `429`: obey `Retry-After`, exponential backoff with jitter.
- `502/503`: transient upstream/service issue; retry with capped backoff.
- `504 timeout`: retry once with reduced optional load (for example disable `monte_carlo`) and tighter order sizing.
- `500`: retry with jitter and alert operator if repeated.

Always parse error JSON fields (`error`, optional `detail`) and log them with request context.

## Minimal Python Skeleton

Python example below is illustrative. If your stack is TypeScript, keep the same control flow and error contract.

```python
import time
import random
import requests

BASE = "https://api.canonprotocol.org"

class CanonClient:
    def __init__(self, api_key: str, timeout: float = 12.0):
        self.session = requests.Session()
        self.session.headers.update({
            "Authorization": f"Bearer {api_key}",
            "Content-Type": "application/json",
        })
        self.timeout = timeout

    def _request(self, method: str, path: str, json_body=None, retries: int = 3):
        url = f"{BASE}{path}"
        for attempt in range(retries):
            resp = self.session.request(method, url, json=json_body, timeout=self.timeout)
            try:
                data = resp.json()
            except ValueError:
                data = {"error": "non_json_response", "detail": resp.text[:500]}

            if resp.status_code == 429:
                wait = int(resp.headers.get("Retry-After", data.get("retry_after_secs", 1)))
                time.sleep(wait + random.uniform(0, 0.5))
                continue
            if resp.status_code in (502, 503, 504, 500) and attempt < retries - 1:
                time.sleep((2 ** attempt) + random.uniform(0, 0.5))
                continue
            if 200 <= resp.status_code < 300:
                return data

            error = data.get("error", "http_error")
            detail = data.get("detail", "")
            raise RuntimeError(f"Canon API {resp.status_code} {error}: {detail}")

        raise RuntimeError("Canon request failed after retries")

    def validate(self, trade: dict) -> dict:
        return self._request("POST", "/v1/validate", json_body=trade)


def should_execute(result: dict) -> bool:
    if result["recommendation"] == "abort":
        return False

    # Canon docs define `flags` as string[]; this handles both string and object forms defensively.
    raw_flags = result.get("flags", [])
    normalized_flags = {
        f.get("type") if isinstance(f, dict) else f
        for f in raw_flags
        if (isinstance(f, str) and f) or (isinstance(f, dict) and f.get("type"))
    }

    blocked = {"extreme_slippage", "near_liquidation", "near_backstop", "oi_at_cap"}
    return not any(flag in blocked for flag in normalized_flags)
```

## Runtime Checklist

- Canon pre-check is in the critical path before every entry/increase.
- Trade logs store `request_id`, `risk_score`, `recommendation`, `flags`, `calibration_hash`.
- Monitoring loop (`/v1/monitor`) and alert stream (`/v1/alerts/ws`) are active.
- Outcome reporting is wired for every completed trade.
- Rate limit and retry policy is implemented and tested.
- Strategy has a documented defensive mode for calibration degradation.
