> ## Documentation Index
> Fetch the complete documentation index at: https://mathematicalcompany.mintlify.site/llms.txt
> Use this file to discover all available pages before exploring further.

# Compliance

> Regulatory compliance module for prediction market trading - audit trails, surveillance, RBAC, approval workflows, and reporting.

As prediction markets become regulated financial products, Horizon provides a compliance module that gives hedge funds and institutional traders the infrastructure required for regulatory oversight.

## Quick Start

```python theme={null}
import horizon as hz
from horizon.compliance import (
    ComplianceManager,
    ComplianceConfig,
    RegulatoryLimitConfig,
    Role,
    compliance_gate,
)

# Configure compliance
config = ComplianceConfig(
    enabled=True,
    roles={
        "alice": Role.TRADER,
        "bob": Role.COMPLIANCE_OFFICER,
        "charlie": Role.RISK_MANAGER,
        "admin": Role.ADMIN,
    },
    approval_enabled=True,
    approval_threshold_notional=10_000,
    regulatory_limits=RegulatoryLimitConfig(
        max_total_notional=1_000_000,
        max_concentration_pct=25.0,
        restricted_markets={"BANNED-MARKET-123"},
        max_daily_volume=500_000,
        max_orders_per_minute=120,
    ),
)

compliance = ComplianceManager(config)

# Use in pipeline
hz.run(
    name="compliant_strategy",
    pipeline=[compliance_gate, my_signal, my_sizer],
    markets=["will-x-happen"],
    compliance=compliance,
)
```

## Architecture

The compliance module is Python-only (not on the hot path) and uses a separate SQLite database so compliance data survives strategy changes. All components are thread-safe.

```
ComplianceManager (orchestrator)
├── AuditTrail         - SHA-256 hash chain, tamper-evident
├── TradeSurveillance  - 5 real-time detectors
├── ApprovalGateway    - async human-in-the-loop
├── AccessControl      - RBAC with 5 roles
├── RegulatoryLimits   - firm-wide position/exposure limits
├── ReportGenerator    - daily, position, and compliance reports
├── ComplianceKillSwitch - RBAC-gated emergency halt
└── RetentionManager   - SEC 17a-4 data retention
```

## Audit Trail

Every compliance event is recorded with a SHA-256 hash chain. Each event links to its predecessor, creating a tamper-evident log that auditors can verify end-to-end.

```python theme={null}
# Record events (happens automatically when using ComplianceManager)
compliance.record_fill("will-btc-100k", "buy", 50.0, 0.62)

# Verify integrity
valid, events_checked = compliance.verify_audit_integrity()
assert valid  # True if no tampering detected

# Export for regulatory submission
compliance.export_audit("audit_2026_q1.csv", start=q1_start, end=q1_end)
```

### Tracked Event Types

| Event Type                | When Recorded                    |
| ------------------------- | -------------------------------- |
| `ORDER_SUBMITTED`         | Order passes compliance gate     |
| `ORDER_FILLED`            | Fill recorded                    |
| `ORDER_CANCELED`          | Cancellation recorded            |
| `ORDER_REJECTED`          | Compliance rejects order         |
| `ORDER_AMENDED`           | Order amendment                  |
| `POSITION_CHANGED`        | Position update                  |
| `CONFIG_CHANGED`          | Compliance config or role change |
| `KILL_SWITCH_ACTIVATED`   | Emergency halt activated         |
| `KILL_SWITCH_DEACTIVATED` | Emergency halt deactivated       |
| `APPROVAL_REQUESTED`      | Order held for review            |
| `APPROVAL_GRANTED`        | Reviewer approves                |
| `APPROVAL_DENIED`         | Reviewer denies                  |
| `APPROVAL_EXPIRED`        | Request auto-expired             |
| `SURVEILLANCE_ALERT`      | Surveillance detector fires      |
| `LIMIT_BREACH`            | Regulatory limit breached        |
| `REPORT_GENERATED`        | Compliance report created        |
| `DATA_PURGED`             | Retention purge executed         |

## Trade Surveillance

Real-time detection of manipulative trading patterns using sliding-window analysis. Runs once per strategy cycle via `on_cycle()`.

### Detectors

| Detector          | What It Catches                         | Severity |
| ----------------- | --------------------------------------- | -------- |
| **Wash Trading**  | Buy + sell same market within threshold | Critical |
| **Spoofing**      | Order placed and canceled before fill   | Warning  |
| **Layering**      | N stacked orders on one side            | Warning  |
| **Rapid-Fire**    | Order rate exceeding limit/minute       | Warning  |
| **Concentration** | Single market exceeding portfolio share | Warning  |

```python theme={null}
config = ComplianceConfig(
    surveillance_enabled=True,
    surveillance_window_secs=300,       # 5-minute window
    wash_trade_threshold_secs=5.0,      # buy+sell within 5s
    spoof_cancel_threshold_secs=2.0,    # order+cancel within 2s
    layering_depth=3,                   # 3+ stacked orders
    regulatory_limits=RegulatoryLimitConfig(
        max_orders_per_minute=120,
        max_concentration_pct=25.0,
    ),
)
```

### Accessing Alerts

```python theme={null}
# Latest alerts from last cycle
alerts = compliance.surveillance.recent_alerts

# Query historical alerts
from horizon.compliance import ComplianceStore
alerts = compliance.store.query_alerts(
    start=yesterday_ts,
    severity="critical",
    resolved=False,
    limit=100,
)

# Resolve an alert
compliance.store.resolve_alert(alert_id, resolved_by="bob", notes="False positive")
```

## Human-in-the-Loop Approval

Orders exceeding a notional threshold are held for manual review. The strategy loop is **never blocked** - pending orders are queued and submitted asynchronously when approved.

```python theme={null}
config = ComplianceConfig(
    approval_enabled=True,
    approval_threshold_notional=10_000,
    approval_timeout_secs=300,          # 5-minute expiry
    approval_callback=my_notification_fn,  # optional webhook
)
```

### Approval Workflow

```python theme={null}
# 1. Order above threshold → held for review
result = compliance.check_order("market-x", "buy", 0.65, 20_000, actor="alice")
# result == ComplianceAction.PENDING_APPROVAL

# 2. Reviewer sees pending orders
pending = compliance.approval.pending()

# 3. Approve or deny
compliance.approval.approve(pending[0].request_id, "bob", notes="within risk budget")
# or
compliance.approval.deny(pending[0].request_id, "bob", notes="too concentrated")

# 4. Approved orders are submitted on next cycle via drain_approved()
```

### Notification Callback

```python theme={null}
def notify_slack(request: ApprovalRequest):
    """Send approval request to Slack (or any webhook)."""
    requests.post(SLACK_WEBHOOK, json={
        "text": f"Order pending approval: {request.market_id} "
                f"notional=${request.order_data['price'] * request.order_data['size']:,.0f} "
                f"(expires in {request.expires_at - request.timestamp:.0f}s)",
    })

config = ComplianceConfig(
    approval_enabled=True,
    approval_callback=notify_slack,
)
```

## Role-Based Access Control (RBAC)

Five hierarchical roles control who can perform compliance-sensitive operations.

| Role                   | Permissions                                                                                                  |
| ---------------------- | ------------------------------------------------------------------------------------------------------------ |
| **Viewer**             | View positions, feeds, orders, audit trail, alerts, reports                                                  |
| **Trader**             | Submit/cancel orders, view positions/feeds/orders/alerts                                                     |
| **Risk Manager**       | Trader + kill switch, modify risk config                                                                     |
| **Compliance Officer** | Risk Manager + approve/deny orders, resolve alerts, generate reports, modify compliance config, export audit |
| **Admin**              | Compliance Officer + manage roles, purge data                                                                |

```python theme={null}
# Check permission
can_approve = compliance.access.check_permission("alice", "approve_order")

# Require permission (raises PermissionError if denied)
compliance.access.require_permission("bob", "approve_order")

# Assign roles (requires Admin)
compliance.access.assign_role("new_trader", Role.TRADER, assigned_by="admin")

# List current roles
roles = compliance.access.list_roles()
# {"alice": "trader", "bob": "compliance_officer", ...}
```

## Regulatory Limits

Firm-wide limits enforced **before** order submission, separate from strategy-level `RiskConfig`.

| Limit                     | Description                                          |
| ------------------------- | ---------------------------------------------------- |
| `restricted_markets`      | Blacklisted market IDs - orders rejected immediately |
| `max_position_per_market` | Per-market position size cap                         |
| `max_total_notional`      | Portfolio-wide notional cap                          |
| `max_concentration_pct`   | Max % of portfolio in one market (default 25%)       |
| `max_daily_volume`        | Daily traded volume cap                              |
| `max_orders_per_minute`   | Order rate limit (default 120)                       |
| `max_cancel_ratio`        | Max cancel-to-order ratio (default 95%)              |

```python theme={null}
limits = RegulatoryLimitConfig(
    restricted_markets={"ILLEGAL-MARKET"},
    max_position_per_market={"BTC-ABOVE-100K": 1000.0},
    max_total_notional=5_000_000,
    max_concentration_pct=20.0,
    max_daily_volume=2_000_000,
    max_orders_per_minute=60,
)

# Check utilization
util = compliance.limits.current_utilization(engine)
# {"total_notional": 45.2, "daily_volume": 12.8, "position_BTC-ABOVE-100K": 30.0}
```

## Kill Switch

Emergency halt with RBAC enforcement. Requires `RISK_MANAGER` role or higher. Snapshots all open positions at activation time for post-incident review.

```python theme={null}
# Activate (cancels all orders, prevents new ones)
compliance.kill_switch.activate(
    reason="Flash crash detected",
    actor="charlie",  # must be risk_manager+
)

# Check status
status = compliance.kill_switch.status()
# {"active": True, "activated_by": "charlie", "reason": "Flash crash detected", ...}

# Deactivate with justification
compliance.kill_switch.deactivate(
    actor="charlie",
    justification="Market stabilized, positions reviewed",
)
```

## Reporting

Generate regulatory-ready reports for internal review and filing.

### Daily Trade Report

```python theme={null}
report = compliance.daily_report("2026-03-12")
print(f"Orders: {report.total_orders}")
print(f"Fills: {report.total_fills}")
print(f"Volume: ${report.total_volume:,.2f}")
print(f"Alerts: {report.surveillance_alerts}")
for entry in report.by_market:
    print(f"  {entry['market_id']}: {entry['orders']} orders, ${entry['volume']:,.2f}")
```

### Position Report

```python theme={null}
report = compliance.position_report()
print(f"Total notional: ${report.total_notional:,.2f}")
for market, pct in report.concentration.items():
    print(f"  {market}: {pct:.1f}%")
```

### Full Compliance Report

```python theme={null}
import time
report = compliance.compliance_report(
    period_start=time.time() - 86400,
    period_end=time.time(),
)
print(f"Chain integrity: {report.chain_integrity}")
print(f"Kill switch events: {report.kill_switch_events}")
print(f"Surveillance alerts: {report.surveillance_summary}")
```

## Data Retention

SEC Rule 17a-4 requires 6+ years of record retention. Default is 2,555 days (\~7 years).

```python theme={null}
# Check retention status
status = compliance.retention.check_retention_status()
print(f"Total events: {status['total_events']}")

# Archive old records to CSV, then purge
result = compliance.retention.archive_and_purge(
    archive_dir="/secure/compliance-archive/",
    actor="admin",
)
print(f"Archived: {result['events_archived']}, Purged: {result['events_purged']}")
```

## Pipeline Integration

Use `compliance_gate` as the first function in your pipeline to automatically enforce compliance on every cycle.

```python theme={null}
hz.run(
    name="institutional_mm",
    exchange=hz.Polymarket(...),
    markets=["will-x-happen"],
    pipeline=[
        compliance_gate,   # compliance checks first
        my_signal,
        my_sizer,
        hz.market_maker,
    ],
    compliance=compliance,  # binds to engine automatically
)
```

When the kill switch is active, `compliance_gate` returns `None`, which halts the pipeline for that cycle. Surveillance runs every cycle. Stale approvals are auto-expired.

## Standalone Usage

The compliance module works independently from `hz.run()`:

```python theme={null}
from horizon.compliance import ComplianceManager, ComplianceConfig, Role

config = ComplianceConfig(
    roles={"alice": Role.TRADER, "bob": Role.COMPLIANCE_OFFICER},
    regulatory_limits=RegulatoryLimitConfig(
        max_total_notional=1_000_000,
    ),
)

compliance = ComplianceManager(config)
compliance.bind_engine(engine)

# Manual pre-trade check
action = compliance.check_order("market-1", "buy", 0.55, 100, actor="alice")
if action == ComplianceAction.APPROVED:
    engine.submit_quotes(...)

# Always close when done
compliance.close()
```

## Configuration Reference

### ComplianceConfig

| Field                         | Type                    | Default        | Description                             |
| ----------------------------- | ----------------------- | -------------- | --------------------------------------- |
| `enabled`                     | `bool`                  | `True`         | Master switch for all compliance checks |
| `audit_enabled`               | `bool`                  | `True`         | Enable audit trail recording            |
| `surveillance_enabled`        | `bool`                  | `True`         | Enable trade surveillance               |
| `approval_enabled`            | `bool`                  | `False`        | Enable human-in-the-loop approval       |
| `regulatory_limits`           | `RegulatoryLimitConfig` | (defaults)     | Firm-wide limits                        |
| `approval_threshold_notional` | `float`                 | `5000`         | Notional threshold for approval         |
| `approval_timeout_secs`       | `float`                 | `300`          | Approval request TTL                    |
| `retention_days`              | `int`                   | `2555`         | Data retention period (\~7 years)       |
| `db_path`                     | `str \| None`           | `None`         | SQLite database path                    |
| `surveillance_window_secs`    | `float`                 | `300`          | Sliding window for surveillance         |
| `wash_trade_threshold_secs`   | `float`                 | `5.0`          | Buy+sell same market threshold          |
| `spoof_cancel_threshold_secs` | `float`                 | `2.0`          | Order+cancel threshold                  |
| `layering_depth`              | `int`                   | `3`            | Stacked orders threshold                |
| `roles`                       | `dict[str, Role]`       | `{}`           | Actor-to-role mapping                   |
| `kill_switch_requires_role`   | `Role`                  | `RISK_MANAGER` | Minimum role for kill switch            |
| `approval_callback`           | `Callable \| None`      | `None`         | Notification callback                   |
| `webhook_url`                 | `str \| None`           | `None`         | Webhook URL for alerts                  |

### RegulatoryLimitConfig

| Field                     | Type               | Default | Description                     |
| ------------------------- | ------------------ | ------- | ------------------------------- |
| `max_position_per_market` | `dict[str, float]` | `{}`    | Per-market position limits      |
| `max_total_notional`      | `float`            | `inf`   | Total portfolio notional cap    |
| `max_concentration_pct`   | `float`            | `25.0`  | Max single-market concentration |
| `restricted_markets`      | `set[str]`         | `set()` | Blacklisted market IDs          |
| `max_daily_volume`        | `float`            | `inf`   | Daily volume cap                |
| `max_orders_per_minute`   | `int`              | `120`   | Order rate limit                |
| `max_cancel_ratio`        | `float`            | `0.95`  | Max cancel-to-order ratio       |
