Metadata-Version: 2.4
Name: cpz-ai
Version: 4.37.0
Summary: CPZAI Python SDK — strategy framework, backtest engine, risk guards, execution algorithms, multi-broker trading, and SpiderRock MLink + Liberator data access
Author-email: CPZAI <contact@cpz-lab.com>
License: CPZAI SDK License Agreement
        
        Copyright (c) 2024-2026 CPZ Capital Ltd. All rights reserved.
        
        This software and accompanying documentation (the "SDK") are proprietary to
        CPZ Capital Ltd ("CPZ").
        
        1. License grant. Subject to your compliance with this agreement and the
           CPZAI Terms of Service (https://www.cpz-lab.com/terms), CPZ grants you a
           limited, non-exclusive, non-transferable, revocable license to install and
           use the SDK solely in connection with a valid CPZAI account and the CPZAI
           services.
        
        2. Restrictions. Except as expressly permitted above or by applicable law,
           you may not: (a) copy, modify, or create derivative works of the SDK;
           (b) distribute, sublicense, rent, lease, or otherwise make the SDK
           available to any third party; (c) reverse engineer or decompile the SDK
           except to the extent such restriction is prohibited by applicable law; or
           (d) use the SDK to build a competing product or service.
        
        3. Open-source components. Portions of the SDK depend on separately licensed
           open-source packages, including the cpz-quant package
           (https://github.com/CPZ-Lab/cpz-quant), which is licensed under the
           Apache License, Version 2.0. Nothing in this agreement limits your rights
           under those licenses to the components they cover.
        
        4. Ownership. The SDK is licensed, not sold. CPZ and its licensors retain
           all right, title, and interest in and to the SDK.
        
        5. Disclaimer. THE SDK IS PROVIDED "AS IS" WITHOUT WARRANTY OF ANY KIND,
           EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION WARRANTIES OF
           MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, AND NON-INFRINGEMENT.
           NOTHING IN THE SDK CONSTITUTES INVESTMENT ADVICE. TRADING INVOLVES RISK,
           INCLUDING LOSS OF CAPITAL.
        
        6. Limitation of liability. TO THE MAXIMUM EXTENT PERMITTED BY LAW, CPZ
           SHALL NOT BE LIABLE FOR ANY INDIRECT, INCIDENTAL, SPECIAL, CONSEQUENTIAL,
           OR PUNITIVE DAMAGES, OR ANY LOSS OF PROFITS, DATA, OR TRADING LOSSES,
           ARISING OUT OF OR RELATING TO THE SDK.
        
        7. Termination. This license terminates automatically if you breach this
           agreement or when your CPZAI account is closed. Sections 4-6 survive
           termination.
        
        8. Governing law. This agreement is governed by the laws of England and
           Wales.
        
        Questions: contact@cpz-lab.com
        
Project-URL: Homepage, https://www.cpz-lab.com/cpz-ai
Project-URL: Repository, https://github.com/CPZ-Lab/cpz-quant
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3 :: Only
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Typing :: Typed
Classifier: Development Status :: 4 - Beta
Classifier: Intended Audience :: Developers
Classifier: License :: Other/Proprietary License
Requires-Python: >=3.10
Description-Content-Type: text/markdown
License-File: LICENSE
Requires-Dist: cpz-quant>=1.1.0
Requires-Dist: pydantic<3,>=2.6
Requires-Dist: requests<3,>=2.31
Requires-Dist: idna>=3.4
Requires-Dist: charset-normalizer>=3.0
Requires-Dist: certifi>=2023.0
Requires-Dist: urllib3>=2.0
Requires-Dist: httpx<0.28,>=0.25
Requires-Dist: alpaca-py>=0.21
Requires-Dist: yfinance>=0.2
Requires-Dist: structlog<25,>=24
Requires-Dist: click<9,>=8.1
Requires-Dist: python-dotenv<2,>=1.0
Requires-Dist: pytz
Requires-Dist: numpy>=1.26
Requires-Dist: scipy>=1.14
Requires-Dist: polars<2,>=1.0
Provides-Extra: fix
Requires-Dist: websocket-client>=1.7; extra == "fix"
Provides-Extra: fix-async
Requires-Dist: aiohttp>=3.9; extra == "fix-async"
Provides-Extra: fix-all
Requires-Dist: websocket-client>=1.7; extra == "fix-all"
Requires-Dist: aiohttp>=3.9; extra == "fix-all"
Provides-Extra: spiderrock
Requires-Dist: websocket-client>=1.7; extra == "spiderrock"
Provides-Extra: spiderrock-async
Requires-Dist: aiohttp>=3.9; extra == "spiderrock-async"
Provides-Extra: spiderrock-all
Requires-Dist: websocket-client>=1.7; extra == "spiderrock-all"
Requires-Dist: aiohttp>=3.9; extra == "spiderrock-all"
Requires-Dist: pandas>=2.0; extra == "spiderrock-all"
Provides-Extra: sklearn
Requires-Dist: scikit-learn>=1.3; extra == "sklearn"
Provides-Extra: dev
Requires-Dist: pytest; extra == "dev"
Requires-Dist: pytest-cov; extra == "dev"
Requires-Dist: pytest-asyncio; extra == "dev"
Requires-Dist: responses; extra == "dev"
Requires-Dist: aioresponses; extra == "dev"
Requires-Dist: aiohttp<3.13; extra == "dev"
Requires-Dist: mypy; extra == "dev"
Requires-Dist: ruff; extra == "dev"
Requires-Dist: black; extra == "dev"
Requires-Dist: isort; extra == "dev"
Requires-Dist: types-requests; extra == "dev"
Requires-Dist: pre-commit; extra == "dev"
Provides-Extra: yfinance
Requires-Dist: yfinance>=0.2; extra == "yfinance"
Provides-Extra: databento
Requires-Dist: databento>=0.40; extra == "databento"
Provides-Extra: hft
Requires-Dist: grpcio>=1.60; extra == "hft"
Requires-Dist: protobuf>=4.25; extra == "hft"
Provides-Extra: risk
Requires-Dist: statsmodels>=0.14; extra == "risk"
Provides-Extra: quantum
Requires-Dist: dwave-neal>=0.6; extra == "quantum"
Requires-Dist: dimod>=0.12; extra == "quantum"
Provides-Extra: quantum-braket
Requires-Dist: amazon-braket-sdk>=1.80; extra == "quantum-braket"
Requires-Dist: dwave-neal>=0.6; extra == "quantum-braket"
Requires-Dist: dimod>=0.12; extra == "quantum-braket"
Provides-Extra: all
Requires-Dist: databento>=0.40; extra == "all"
Requires-Dist: yfinance>=0.2; extra == "all"
Requires-Dist: grpcio>=1.60; extra == "all"
Requires-Dist: protobuf>=4.25; extra == "all"
Requires-Dist: numpy>=1.26; extra == "all"
Requires-Dist: scipy>=1.14; extra == "all"
Requires-Dist: statsmodels>=0.14; extra == "all"
Requires-Dist: dwave-neal>=0.6; extra == "all"
Requires-Dist: dimod>=0.12; extra == "all"
Dynamic: license-file

<p align="center">
  <a href="https://www.cpz-lab.com/">
    <img src="https://drive.google.com/uc?id=1JY-PoPj9GHmpq3bZLC7WyJLbGuT1L3hN" alt="CPZ" width="180">
  </a>
</p>

<h1 align="center">CPZ Python SDK</h1>

<p align="center">
  <strong>Strategy Framework, Backtesting Engine, Risk Guards, Execution Algorithms, and Multi-Broker Trading</strong>
</p>

<p align="center">
  <a href="https://pypi.org/project/cpz-ai/"><img src="https://img.shields.io/pypi/v/cpz-ai.svg" alt="PyPI"></a>
  <a href="https://www.python.org/"><img src="https://img.shields.io/badge/python-3.10%2B-blue.svg" alt="Python 3.10 or newer"></a>
</p>

---

## What's New in v4.7.0 — Institutional Trust Suite

Five coordinated controls land together, each aligned to SEC Rule 15c3-5 (pre-trade) and SEC 17a-4 / FINRA 4511 (records) principles. This is a controls framework, not a claim of regulatory certification.

- **Bounded Autonomy** (`client.mandates`) — trading mandates that bound order flow per scope: symbol universe, per-order notional and quantity caps, gross exposure, leverage, daily-loss limit, and a mandatory expiry for live scopes. Enforced at every first-party order and deploy surface, backed by a hash-chained local audit ledger (`cpz.audit`) and `mandate` / `killswitch` / `audit` CLI subcommands.
- **Backtest Trust Layer** (`client.backtests`) — every backtest can emit a run card and validation artifact: metrics, benchmark context, data provenance, a deterministic config hash, and point-in-time / OHLC-sanity / lookahead-bias checks. The "SDK-attested" verdict is recomputed server-side, so a client can never self-certify.
- **Factor Library** (`client.factors`) — fetch the platform factor catalog and bench any factor over a universe and period (IC, rank IC, IR, turnover, with alive / reversed / dead status). Formulas run through an AST-whitelisted evaluator, never `eval`.
- **Strategy Health** (`client.health`) — a falsifiable-hypothesis registry plus a decay lifecycle (active / monitoring / decayed / disabled) computed from rolling Sharpe and IC. It records only; it never stops or modifies a deployment.
- **Shadow Account** (`client.shadow`) — parse broker execution history (Alpaca, IBKR Flex, generic CSV) into normalized fills, compute behavioral diagnostics (FIFO round trips, win rate, holding periods, disposition effect), and replay an honestly-bounded counterfactual.

**Behavioral changes — read before upgrading:**

- **Live orders now require CPZ platform connectivity.** The kill switch and mandate checks fail closed: if the control plane is unreachable, a live order raises rather than proceeding unchecked. Paper orders warn and proceed. Risk-reducing closes are exempt by design.
- **`validate=True` is the new backtest default.** Data-quality checks and the lookahead gate now run unless you pass `validate=False`.
- **Removed the unauthenticated hardcoded-IP engine REST fallback**, which cannot coexist with mandate enforcement.

### Recent releases

- **v4.6.6** — Bloomberg Data License market-data provider (`client.data.bloomberg`); order-fill durability.
- **v4.6.3** — real, vendor-agnostic options chain with greeks (`options()` / `get_option_chain()`), routed through the CPZ backend instead of hitting Alpaca directly.
- **v4.6.0** — implied-volatility history and IV rank (`client.data.get_iv_history` / `iv_rank`), vendor-agnostic and capability-based.
- **v4.2.0** — `client.data.scout(source, **params)`: one-call execution of any [Data Scout](https://www.cpz-lab.com) starter snippet, credentials resolved server-side from your stored API connections.
- **v4.1.0** — institutional FIX 4.4 client (`cpz.fix`) for direct broker connectivity, sync and async.
- **v4.25.0**: research Compute Fabric client and truthful local QAOA simulation; earlier real-QPU claims corrected.
- **v3.0.0** — Strategy Framework, Backtest Engine, Pre-Trade Risk Guard, TWAP/VWAP/Iceberg execution algos.

See [CHANGELOG.md](./CHANGELOG.md) for the full reconciled history (v2.4.0 → v4.7.0).

---

## Overview

The CPZ Python SDK is the unified interface for the CPZ quantitative trading platform. Write strategies once and run them in backtest or live mode with zero code changes.

| Module | Description |
|--------|-------------|
| **`Strategy`** | Professional-grade strategy framework with lifecycle hooks, backtest-live code parity |
| **`StrategyRunner`** | Run strategies live or backtest with historical replay and fill simulation |
| **`RiskGuard`** | Pre-trade risk validation — max order value, position limits, daily loss halts |
| **`cpz.portfolio`** | Portfolio optimization — MVO, Black-Litterman, HRP, CVaR, risk parity, QHRP, QUBO, and 10+ methods |
| **`cpz.portfolio.quantum_backends`** | Classical QUBO solvers and experimental Braket local QAOA simulation |
| **`client.compute`** | Private research formulation, advisory routing, explicit CPU solving and batch planning |
| **`client.risk`** | Portfolio risk analytics — VaR, Sharpe inference, Monte Carlo, stress testing |
| **`client.execution`** | One execution API for every broker (Alpaca, TradeStation, IBKR, Saxo Bank, tastytrade, SnapTrade brokers, Polymarket, HFT engine) with TWAP/VWAP/Iceberg algos |
| **`client.engine`** | Low-latency HFT engine deployment (Rust) for autonomous execution |
| **`client.data`** | Stocks, crypto, options, 31 US macro series from the agencies that publish them (FRED only with your own key), SEC filings, social sentiment, 100+ indicators, plus `client.data.scout()` for one-call execution of Data Scout starter snippets |
| **`client.simons`** | Simons — quantitative trading strategist for analysis, code generation, and strategy review |
| **`client.mandates`** | Bounded-autonomy trading mandates — notional/quantity/exposure/leverage caps, kill switch, hash-chained audit ledger |
| **`client.backtests`** | Backtest trust layer — run cards, validation artifacts, point-in-time / lookahead-bias checks, server-recomputed attestation |
| **`client.factors`** | Factor library — bench any factor over a universe (IC, rank IC, IR, turnover) via an AST-whitelisted evaluator |
| **`client.health`** | Strategy health — falsifiable-hypothesis registry and rolling Sharpe/IC decay lifecycle |
| **`client.shadow`** | Shadow account — parse broker fills into behavioral diagnostics and honestly-bounded counterfactual replay |
| **`client.middle_office`** | OTC book of record — book swaps, credit, total return, options, baskets and bonds; lifecycle, cash, ledger, NAV and share classes, marks and provenance, market-data policy, margin, confirmations, regulatory reporting, reports, administrator tie-out, as-of reproduction and operations health; exact `Decimal` money, pending items named never zeroed |

---

## Installation

```bash
pip install cpz-ai                    # core SDK (includes classical quantum-inspired optimizers)
pip install cpz-ai[risk]              # + statsmodels for advanced risk analytics
pip install cpz-ai[quantum]           # + dwave-neal for simulated annealing
pip install cpz-ai[quantum-braket]    # + Amazon Braket local QAOA simulation (CPU)
pip install cpz-ai[all]               # + all optional dependencies
```

---

## Quantum Portfolio Optimization

QUBO formulations support classical exact and annealing solvers, plus an
experimental Braket **local CPU simulator**. Remote managed simulators and
physical QPUs are disabled in cpz-quant 1.1.0. Earlier hardware and cost-tracking
claims were unsupported by the implementation and are superseded here.

```python
from cpz.portfolio import BraketSolver, qubo_portfolio_selection

# No AWS credentials or provider charges. Local CPU resources still have cost.
solver = BraketSolver(device="local", shots=256, max_iter=8, seed=7)
result = qubo_portfolio_selection(returns, target_k=2, solver=solver)
```

This does not demonstrate quantum advantage. QHRP permutation problems have N
squared binary variables; local QAOA is bounded to 20 variables.

## Compute Fabric research API (v4.25.0)

`client.compute` provides advisory recommendations, explicit small CPU hedge
solves, batch planning, and benchmark uploads. `client.quantum` is an alias for
the research interface, not physical QPU execution.

```python
# Existing authenticated CPZClient. All numbers below are synthetic.
proposal = client.compute.optimize_hedge(
    backend="edge_wasm", algorithm="brute_force", request_id="hedge-demo-1",
    book_greeks={"DEMO": {"delta": 1, "gamma": 0, "vega": 0}},
    candidates=[{
        "symbol": "DEMO-HEDGE", "underlying": "DEMO", "lot": 1,
        "greeks_per_lot": {"delta": -1, "gamma": 0, "vega": 0},
        "notional_per_lot": 100, "spread_bps": 1,
    }],
    cardinality=1, penalty=10,
    greek_weights={"delta": 1, "gamma": 1, "vega": 1},
)
assert proposal.production_eligible is False
```

The endpoint requires platform schema activation, entitlement and a spending
policy. It fails closed when these are unavailable. Keep the same request ID
after an uncertain response; use a new ID only for intentional new work.
Run uploads are caller-reported evidence, never billing authorization.
Reported provider costs are not verified invoices.

Proposals validate linear Greeks only and cannot emit orders. Live orders
declaring a research `compute_run_id` are blocked even in shadow mode.
Untagged user-created orders use existing controls; manually copied numbers
cannot be traced. GPU execution and physical-QPU dispatch remain unavailable.

---

## Strategy Framework

Write your strategy once as a `Strategy` subclass. The same code runs against the backtest engine or live broker with zero changes.

### Define a Strategy

```python
from cpz import Strategy, StrategyConfig, StrategyRunner, CPZClient

class MomentumConfig(StrategyConfig):
    fast_period: int = 10
    slow_period: int = 30
    trade_size: float = 100

class MomentumStrategy(Strategy):
    def on_start(self):
        self.subscribe_bars(self.config.instruments[0], "1D")

    def on_bar(self, bar):
        prices = self.cache.bars(bar.symbol)
        if len(prices) < self.config.slow_period:
            return
        fast_ma = sum(b.close for b in prices[-self.config.fast_period:]) / self.config.fast_period
        slow_ma = sum(b.close for b in prices[-self.config.slow_period:]) / self.config.slow_period

        if fast_ma > slow_ma and not self.portfolio.is_long(bar.symbol):
            self.buy(bar.symbol, qty=self.config.trade_size)
        elif fast_ma < slow_ma and self.portfolio.is_long(bar.symbol):
            self.flatten(bar.symbol)

    def on_order_filled(self, event):
        self.log.info(f"Filled {event.symbol} @ {event.fill_price}")

    def on_position_closed(self, event):
        self.log.info(f"Closed {event.symbol}, realized P&L: ${event.realized_pnl:,.2f}")
```

### Backtest

```python
client = CPZClient()
runner = StrategyRunner(client, mode="backtest")
runner.add_strategy(MomentumStrategy(MomentumConfig(instruments=["AAPL"])))

result = runner.backtest(
    start="2024-01-01",
    end="2024-12-31",
    initial_cash=100_000,
    commission_pct=0.0003,    # 3 bps
    slippage_pct=0.0001,      # 1 bp
)
print(result.summary())
```

Output:

```
============================================================
  BACKTEST RESULTS
============================================================
  Period:            2024-01-01 → 2024-12-31
  Initial Cash:      $100,000.00
  Final Equity:      $112,450.00
  Total Return:      +12.45%
------------------------------------------------------------
  Sharpe Ratio:      1.842
  Sortino Ratio:     2.315
  Max Drawdown:      -4.23%
------------------------------------------------------------
  Total Trades:      47
  Win Rate:          63.8%
  Profit Factor:     2.14
============================================================
```

### Backtest Realism (v4.29.0)

Every realism model is opt-in. With none set, a backtest behaves as before, and `result.modeling` says what that leaves out.

```python
from cpz.strategy.sim import CashAccount, MarginAccount, VolumeParticipation, ibkr_fixed

result = runner.backtest(
    start="2024-01-01",
    end="2024-12-31",
    order_model="intrabar",                # limit, stop, stop_limit, trailing_stop, MOO, MOC
    fee_model=ibkr_fixed(),                # or alpaca_zero_commission(), tradestation_equities(), PerShareFee(...)
    fill_model=VolumeParticipation(0.05),  # at most 5% of each bar's volume; remainders carry forward
    account=CashAccount(),                 # or MarginAccount(debit_interest_rate=...)
)
print(result.modeling.text())
```

**Orders** (`order_model="intrabar"`). Bars only give open, high, low and close, so each rule takes the reading least favourable to the strategy:

| Order | Fill rule |
|-------|-----------|
| market | next eligible bar's open |
| limit | gap through the limit fills at the open; otherwise only when the price trades through the limit (a touch does not fill); never fills through the limit after slippage |
| stop | gap through the stop fills at the open; otherwise at the stop when touched |
| stop_limit | triggers like a stop; triggered intrabar it fills at the stop only when the limit is beyond the stop, otherwise it rests as a limit |
| trailing_stop | `trail_percent` or `trail_amount`; each bar is tested against the stop as it stood before the bar, then the stop ratchets, so one bar never raises and hits the stop |
| market_on_open / moo | open of the next session's opening bar |
| market_on_close / moc | close of the session's closing bar; orders after 15:50 ET go to the next session |

An order placed while handling a bar can first fill on a bar that starts after that bar ends. Time in force: `DAY`, `GTC` (with optional `expire_at`), `IOC`, `FOK`. Expired, cancelled and rejected orders are listed on the result with their reasons.

**Fees.** `PercentFee` (the historic default), `PerShareFee(rate, min, max_pct)`, `SecSection31Fee` and `FinraTafFee` (dated rate tables for sells, sourced from sec.gov and finra.org), plus presets `ibkr_fixed()`, `alpaca_zero_commission()` and `tradestation_equities(tier)`. Rates carry their source URL and effective date in `cpz/strategy/sim/fees.py`. A fill dated before the first published rate raises; a fill after the last one uses the rate still in effect, and `result.modeling` records `rates_verified_as_of` (2026-09-17) with a note when any fill is later than that. Per-order minimums and caps are charged once per order across partial fills, as IBKR bills them. `result.fee_totals` breaks fees down by component.

**Accounts.** `CashAccount` buys only with settled cash (T+2 before 2024-05-28, T+1 after) and rejects short sales. `MarginAccount` applies Reg T 50% initial margin, FINRA Rule 4210 maintenance, a liquidation event on a maintenance breach, and daily debit interest at the rate you pass. An order over buying power is rejected with a reason, never resized.

**Scheduled events.** Register in `on_start`; `on_timer(event)` fires in backtests on the NYSE calendar (2010 to 2028, holidays and 13:00 early closes):

```python
self.schedule.every("tick", minutes=15)
self.schedule.at_market_open("open30", offset_minutes=30)
self.schedule.at_market_close("rebalance", offset_minutes=10, frequency="month_end")  # daily, weekly, month_start, month_end
```

**Consolidation.** `self.consolidate("AAPL", "15Min", handler)` rolls 1-minute bars into 15-minute bars and only ever hands the handler closed bars.

**Corporate actions.** `price_mode="raw", corporate_actions=corporate_action_source("massive")` replays unadjusted Massive bars and applies splits (position rescaled, open orders cancelled) and cash dividends (receivable on the ex-date, cash on the pay date). Providers without split and dividend data raise. The default replays bars as the data path returns them (the CPZ gateway requests split-adjusted bars from Alpaca and Polygon).

See `examples/backtest_realism.py`.

### Walk-Forward Optimization (v4.31.0)

The optimization is a call in your own script, so whoever reads the script sees what was searched and how:

```python
result = runner.walk_forward(
    MyStrategy,
    param_grid={"lookback": [10, 20, 40], "z_entry": [1.5, 2.0]},
    config=MyConfig(instruments=["SPY"]),
    start="2019-01-01",
    end="2025-12-31",
    folds=5, train_bars=504, test_bars=126, anchored=False,
    objective="sharpe",   # "sortino", "calmar", "total_return" or a callable(BacktestResult) -> float
    warmup_bars=40,       # optional indicator history before each test window
)
print(result.summary())
```

For each fold, every combination is backtested on the train window and the best in-sample objective is chosen (ties go to the earliest combination). Only then does that combination run on the next `test_bars` bars. Test windows follow one another without overlap and never influence a choice. The folds end at the last bar of the data. `anchored=True` keeps the first train bar fixed and grows the train window by `test_bars` each fold.

**Honesty rules.**

- **No lookahead.** Selection sees only the fold's train bars. Warm-up bars come from before the test window; the strategy sees them, but every order placed on them is refused (`result.warmup_refused_orders`) and they are not scored.
- **Every evaluated combination is a trial.** `result.trials` is combinations x folds, and it deflates the Sharpe of the stitched out-of-sample returns (`result.deflation`: deflated Sharpe, expected maximum Sharpe under the null, PSR, DSR probability). The formula is the platform's schema-2 rigor block, so SDK and Builder runs are comparable.
- **Everything is reported.** `result.folds` (train and test dates, chosen params, in-sample objective, out-of-sample metrics, and both backtests), `result.grid` / `grid_frame()` (every combination's in-sample score on every fold), `result.oos` (the stitched out-of-sample `BacktestResult`), `result.stability` (how often the choice changed, and the objective at the chosen point's neighbouring grid points) and `result.degradation` (in-sample vs out-of-sample objective).
- **Hard budget.** More than 256 combinations raises `WalkForwardBudgetError`; a grid is never sampled or truncated. Too few bars for `train_bars + folds * test_bars` raises `BacktestDataError` with the numbers.
- **Deterministic.** Runs are serial; the same inputs give the same result.

Every realism option (`order_model`, `fee_model`, `fill_model`, `account`, `calendar`, `price_mode`, `corporate_actions`) reaches every inner backtest, with stateful models copied per run, and `result.modeling.walk_forward` records the method, folds, train, test and warm-up bars, objective, grid and trials. Bars are fetched once for the whole window.

Each fold's test run starts flat with `initial_cash`; out-of-sample returns are compounded across folds, and a position still open at a test window's end is marked to market at its last close with no exit costs, as at the end of any backtest.

`upload=True` (or `client.backtests.upload(result)`) uploads the stitched out-of-sample run with a schema-2 `rigor` block: `deflatedSharpe.numTrials` is the full trial count, `walkForward.refit` is `true`, and `walkForward.anchored.oosSharpe` / `oosReturnPct` carry the stitched out-of-sample figures (the platform reads them under that key for rolling walk-forwards too; `walkForward.method` says which ran).

See `examples/walk_forward.py`.

### Live Trading (Same Code!)

```python
runner = StrategyRunner(client, mode="live")
runner.add_strategy(MomentumStrategy(MomentumConfig(instruments=["AAPL"])))
runner.run()  # Blocks until Ctrl+C
```

### Strategy Lifecycle Hooks

| Hook | When It Fires |
|------|---------------|
| `on_start()` | Strategy starts — subscribe to data here |
| `on_stop()` | Strategy stops — cleanup here |
| `on_bar(bar)` | New OHLCV bar received |
| `on_quote(quote)` | New bid/ask quote received |
| `on_trade(trade)` | New trade tick received |
| `on_order_submitted(event)` | Order sent to broker |
| `on_order_filled(event)` | Order fully filled |
| `on_order_canceled(event)` | Order canceled |
| `on_position_opened(event)` | New position opened |
| `on_position_changed(event)` | Position size changed |
| `on_position_closed(event)` | Position fully closed |
| `on_timer(event)` | Scheduled event fired (backtests) |
| `on_order_rejected(event)` | Order rejected (e.g. over buying power) |
| `on_order_expired(event)` | Order expired (time in force) |
| `on_margin_call(event)` | Backtest margin account liquidated |
| `on_signal(signal)` | Custom signal received |

### Strategy Actions

```python
self.buy("AAPL", qty=100)                    # Market buy
self.sell("AAPL", qty=50)                     # Market sell
self.buy("AAPL", qty=10, order_type="limit", limit_price=150.00)
self.sell("AAPL", qty=10, order_type="trailing_stop", trail_percent=5, time_in_force="GTC")
self.buy("AAPL", qty=10, order_type="market_on_close")  # backtest: order_model="intrabar"
self.flatten("AAPL")                          # Close position
self.flatten_all()                            # Close all positions
self.cancel_all_orders()                      # Cancel open orders
self.emit_signal("crossover", value=1.0)      # Emit custom signal
self.subscribe_bars("AAPL", "1Min")           # Subscribe to bars
self.subscribe_quotes("AAPL")                 # Subscribe to quotes
```

### Strategy Context

Every strategy has access to:

```python
self.cache.bars("AAPL")           # Cached bar history
self.cache.latest_bar("AAPL")     # Most recent bar
self.portfolio.equity()           # Current equity
self.portfolio.cash               # Available cash
self.portfolio.is_long("AAPL")    # Position check
self.portfolio.positions()        # All positions
self.clock.utc_now()              # Current time (real or simulated)
self.log.info("message")          # Structured logging
self.config.instruments           # Strategy config
```

---

## Pre-Trade Risk Guard

Intercepts every order before it reaches the broker. Any violation raises `RiskGuardViolation` and the order is rejected.

```python
from cpz import CPZClient

client = CPZClient()

# Attach risk guard to execution
client.execution.set_risk_guard(
    max_order_value=50_000,         # Reject orders > $50K
    max_position_pct=0.10,          # Max 10% of equity per position
    max_daily_loss=-5_000,          # Halt trading if daily P&L < -$5K
    max_open_orders=20,             # Max concurrent open orders
    blocked_symbols=["GME", "AMC"], # Blacklist
    cooldown_seconds=5.0,           # Min 5s between orders per symbol
    max_orders_per_minute=30,       # Rate limit
)

# Orders are now validated before submission
order = client.execution.order(symbol="AAPL", qty=10, side="buy", strategy_id="my-strat")
```

Or use directly in a strategy:

```python
from cpz import RiskGuard

class SafeStrategy(Strategy):
    def on_start(self):
        guard = RiskGuard(max_order_value=25_000, max_daily_loss=-2_000)
        self._router.set_risk_guard(guard)
        self.subscribe_bars("AAPL", "1D")
```

---

## Execution Algorithms

Split large orders into smaller slices for better execution.

```python
from cpz.execution.algos import TWAPAlgorithm, VWAPAlgorithm, IcebergAlgorithm

# TWAP — equal slices over time
algo = TWAPAlgorithm()
slices = algo.generate_slices(order_request, {
    "duration_minutes": 30,
    "num_slices": 10,
})

# VWAP — volume-weighted slicing with intraday profile
algo = VWAPAlgorithm()
slices = algo.generate_slices(order_request, {
    "duration_minutes": 60,
    "num_slices": 13,
})

# Iceberg — hidden quantity
algo = IcebergAlgorithm()
slices = algo.generate_slices(order_request, {
    "display_qty": 100,  # Only show 100 shares at a time
})
```

---

## Typed Domain Model

Precision-safe value types that reject NaN, Infinity, and invalid data at construction time.

```python
from cpz import Price, Quantity

# Validated, immutable
price = Price("150.25")                # From string (exact)
price = Price.from_float(150.25, precision=2)  # From float
qty = Quantity.from_int(100)

# Type-safe arithmetic
total = price * qty                    # Returns Money("15025.00", "USD")
spread = Price("150.50") - Price("150.25")  # Returns Price("0.25")

# Fail-fast on invalid data
Price(float("nan"))    # ValueError: Price must be finite
Quantity(-1)           # ValueError: Quantity cannot be negative
```

---

## Event Bus

Thread-safe pub/sub with wildcard topic matching.

```python
from cpz import EventBus

bus = EventBus()

# Subscribe with wildcards
bus.subscribe("bar.AAPL.*", handle_aapl_bars)     # Any timeframe for AAPL
bus.subscribe("order.**", handle_all_orders)       # All order events

# Publish
bus.publish("bar.AAPL.1Min", bar_event)
```

---

## Quick Start

```python
from cpz import CPZClient

client = CPZClient()

# ── Risk Analytics ──────────────────────────────────────
sharpe = client.risk.sharpe(daily_returns)
snapshot = client.risk.compute(daily_returns, spy_returns, weights)
mc = client.risk.monte_carlo(daily_returns, num_simulations=10000)

# ── Trading ─────────────────────────────────────────────
client.execution.use_broker("alpaca", environment="paper")
order = client.execution.order(symbol="AAPL", qty=10, side="buy", strategy_id="my-strat")

# ── Market Data ─────────────────────────────────────────
bars = client.data.bars("AAPL", timeframe="1D", limit=100)
quotes = client.data.quotes(["AAPL", "MSFT", "GOOGL"])
gdp = client.data.economic("GDP")

# ── Simons (Quant Strategist) ───────────────────────────
response = client.simons.chat("Analyze AAPL for momentum trading")
print(response.content)
```

---

## Risk Analytics

Comprehensive quantitative risk computation powered by numpy/scipy. Install with `pip install cpz-ai[risk]`.

On first use the risk-free rate is the newest 3-month Treasury yield (DGS3MO) from the CPZ `us_macro` warehouse. If that read fails, the SDK logs a `risk_free_rate_unavailable` warning and uses 0% until you call `client.risk.set_risk_free_rate(...)`. All methods accept daily returns as a list of floats.

### Core Metrics

```python
from cpz import CPZClient

client = CPZClient()

# Individual metrics
sharpe = client.risk.sharpe(daily_returns)               # Annualized Sharpe ratio
sortino = client.risk.sortino(daily_returns)              # Sortino (downside vol only)
vol = client.risk.volatility(daily_returns)               # Annualized volatility (%)
mdd = client.risk.max_drawdown(daily_returns)             # Maximum drawdown (%)
b = client.risk.beta(daily_returns, spy_returns)          # Beta vs benchmark
a = client.risk.alpha(daily_returns, spy_returns)         # Jensen's alpha (annualized)

# Full risk snapshot (all metrics at once)
snapshot = client.risk.compute(
    daily_returns=portfolio_returns,
    benchmark_returns=spy_returns,
    position_weights={"AAPL": 0.4, "NVDA": 0.35, "BTC/USD": 0.25},
    total_exposure=100000,
)
```

### Sharpe Inference (Lopez de Prado Framework)

Statistical significance testing for the Sharpe ratio. Implements all 5 corrections from Lopez de Prado (2012, 2018):

1. **Non-normality** — SE adjusted for skewness and kurtosis
2. **Serial correlation** — Lo (2002) autocorrelation adjustment
3. **Probabilistic Sharpe Ratio (PSR)** — P(true Sharpe > benchmark)
4. **Deflated Sharpe Ratio (DSR)** — Multiple-testing correction
5. **Minimum Track Record Length** — How long before the Sharpe is credible?

```python
inference = client.risk.sharpe_inference(daily_returns, num_trials=20)
print(f"PSR (prob true SR > 0): {inference.psr:.1%}")
print(f"DSR (adjusted for 20 trials): {inference.deflated_sharpe}")
print(f"Min track record needed: {inference.min_track_record_months} months")
```

### Value at Risk

```python
var_95 = client.risk.parametric_var(daily_returns, confidence=0.95)
hist_var = client.risk.historical_var(daily_returns, confidence=0.95)
mc = client.risk.monte_carlo(daily_returns, num_simulations=10000, horizon_days=5)
```

### Advanced Analytics

```python
dd = client.risk.drawdown_analysis(daily_returns)        # Drawdown decomposition
tail = client.risk.tail_risk(daily_returns)              # Skewness, kurtosis, Cornish-Fisher VaR
sizing = client.risk.position_size(100000, returns)      # Kelly criterion + vol targeting
exposure = client.risk.factor_exposure(returns, factors) # OLS factor decomposition
impacts = client.risk.stress_test_all(weights)           # 7 historical crisis scenarios
rolling = client.risk.rolling_metrics(returns, spy, 20)  # Rolling Sharpe, vol, beta
```

---

## Trading

### Broker Configuration

```python
from cpz import CPZClient

client = CPZClient()

client.execution.use_broker("alpaca", environment="paper")
client.execution.use_broker("alpaca", environment="live")
client.execution.use_broker("tradestation", environment="paper")
client.execution.use_broker("ibkr", environment="paper")
client.execution.use_broker("saxo", environment="live")
client.execution.use_broker("tastytrade", environment="live")
client.execution.use_broker("kalshi", environment="paper")  # Kalshi demo exchange
client.execution.use_broker("polymarket")
client.execution.use_broker("fidelity")  # SnapTrade brokers use the venue name
```

Supported broker names: `alpaca`, `tradestation`, `ibkr`, `saxo`, `tastytrade`,
`kalshi`, `polymarket`, `snaptrade` and the SnapTrade venues (`fidelity`, `schwab`,
`robinhood`, `questrade`, `wealthsimple`, `td`, `etrade`, `webull`, `coinbase`,
`vanguard`, `merrill`, `sofi`, `public`, `firstrade`), plus `hft_engine` and
`cpz_proxy`. TradeStation, Saxo and tastytrade keep their OAuth tokens
server-side, and Kalshi its encrypted API key, so they always trade through the
CPZAI execution gateway.

Kalshi symbols name one outcome of one market, `"<MARKET_TICKER>:YES"` or
`"<MARKET_TICKER>:NO"`, and `limit_price` is that outcome's price in dollars
(0 to 1). A sell only reduces a held outcome; buy the other outcome to take that
side. Kalshi has no market or stop orders: a `market` order needs `limit_price`
and runs immediate-or-cancel. `environment="paper"` is Kalshi's demo exchange
(demo API key), `"live"` is production.

```python
client.execution.use_broker("kalshi", environment="paper")
client.execution.submit_order(
    symbol="KXFED-26OCT-T4.25:YES", qty=10, side="buy",
    order_type="limit", limit_price=0.56, time_in_force="gtc",
)
```

### Order Placement

```python
# Simple order
order = client.execution.order(
    symbol="AAPL",
    qty=10,
    side="buy",
    strategy_id="my-strategy"
)

# Full control
from cpz import OrderSubmitRequest, OrderSide, OrderType, TimeInForce

request = OrderSubmitRequest(
    symbol="AAPL",
    side=OrderSide.BUY,
    qty=10,
    order_type=OrderType.LIMIT,
    time_in_force=TimeInForce.GTC,
    limit_price=150.00,
    strategy_id="my-strategy"
)
order = client.execution.submit_order(request)
```

### Account and Positions

```python
account = client.execution.get_account()
print(f"Buying Power: ${account.buying_power:,.2f}")

positions = client.execution.get_positions()
for pos in positions:
    print(f"{pos.symbol}: {pos.qty} shares @ ${pos.avg_entry_price}")
```

### Execution: one API for every broker

Every broker exposes the same methods, on `client.execution` (sync),
`AsyncCPZClient().execution` and `BrokerRouter`. A broker that cannot do
something raises `BrokerOperationUnsupported` (a `NotImplementedError`) before
it touches your account: never `None`, never invented data, never a cancel
followed by a failed replace. Ask first with `capabilities()`.

| Method | Returns | Notes |
|---|---|---|
| `capabilities()` | `BrokerCapabilities` | what the broker supports (table below) |
| `get_account()` | `Account` | |
| `get_positions()` | `list[Position]` | |
| `get_position(symbol)` | `Position` or `None` | `None` only when not held; errors propagate |
| `submit_order(req)` | `Order` | market, limit, stop, stop_limit, trailing_stop |
| `order(symbol=..., ...)` | `Order` | one-call builder over `submit_order` |
| `get_order(order_id)` | `Order` | |
| `get_order_by_client_id(client_order_id)` | `Order` or `None` | reconcile an unconfirmed submit |
| `list_orders(status, limit, after, until, symbols)` | `list[Order]` | |
| `cancel_order(order_id)` | `Order` | |
| `cancel_all_orders()` | `CancelAllResult` | `canceled`, `failed`, `remaining`, `complete` |
| `replace_order(order_id, OrderReplaceRequest)` | `Order` | qty, limit_price, stop_price, trail, time_in_force, client_order_id |
| `close_position(symbol, qty=None, percentage=None, strategy_id=...)` | `Order` | whole position unless qty or percentage is given |
| `close_all_positions(strategy_id=...)` | `CloseAllResult` | iterates like the old `list[Order]`; check `complete` |
| `get_portfolio_history(period, timeframe)` | `PortfolioHistory` | |
| `get_quotes(symbols)` | `list[Quote]` | real quotes or `QuoteUnavailableError`, never zeros |
| `get_historical_data(symbol, timeframe, limit, start, end)` | `list[Bar]` | |
| `stream_quotes(symbols)` | async iterator of `Quote` | |

```python
from cpz import BrokerOperationUnsupported

client.execution.use_broker("saxo", environment="live")
caps = client.execution.capabilities()
if caps.close_position:
    client.execution.close_position("AAPL", percentage=50, strategy_id="my-strategy")

order = client.execution.order(
    symbol="AAPL", qty=10, side="sell", order_type="stop", stop_price=180.0,
    strategy_id="my-strategy",
)

result = client.execution.cancel_all_orders()
if not result.complete:
    print(result.failed, result.remaining)

try:
    client.execution.replace_order(order.id, OrderReplaceRequest(stop_price=182.0))
except BrokerOperationUnsupported as exc:
    print(exc.broker, exc.operation, exc.reason)
```

`OrderSubmitRequest` also takes `trail_price` / `trail_percent` (trailing
stops, exactly one), `extended_hours`, `venue_symbol_id` (the broker's own
instrument id: IBKR conid, Saxo `Uic:AssetType`, tastytrade
`Instrument Type|symbol`, SnapTrade universal id, Polymarket token id, Kalshi
`TICKER:YES` / `TICKER:NO`),
`position_effect` (`open` / `close`) and `asset_class`. Stop orders need
`stop_price`, stop-limit orders need `stop_price` and `limit_price`.

Capabilities through the execution gateway (`GET /cpz/brokers` is the source of
truth; `capabilities()` reads it):

| Broker | client_order_id | replace | notional | fractional | streaming | close | close_all | cancel_all | portfolio_history | Asset classes |
|---|---|---|---|---|---|---|---|---|---|---|
| alpaca | Y | Y | Y | Y | Y | Y | Y | Y | Y | equity, option, crypto |
| tradestation | recovery | Y | N | N | Y | Y | Y | Y | Y | equity, option, future |
| ibkr | N | N | N | N | N | Y | Y | Y | N | equity, option, future, fx, bond |
| snaptrade | N | N | Y | Y | N | Y | Y | Y | N | equity, crypto |
| polymarket | N | N | N | Y | N | Y (limit) | Y | Y | N | prediction |
| kalshi | Y | Y (price/qty) | N | Y | N | Y (limit) | Y | Y | N | prediction |
| saxo | recovery | Y | N | N | N | Y | Y | Y | N | equity, future, option, fx, bond, cfd |
| tastytrade | Y | Y (price/type/tif) | N | N | N | Y | Y | Y | N | equity, option, future, future_option, crypto |
| hft_engine | Y | N | N | Y | Y | N | N | N | N | equity |

Native adapters (no `CPZ_PROXY_ENABLED`, or explicit credentials) describe
themselves with their own, narrower `capabilities()`: the native IBKR adapter
places market and limit orders only and has no order list, replace, closes or
portfolio history; the native SnapTrade adapter is read-only; the native
Polymarket adapter needs a `limit_price` on every order; the native Alpaca
adapter does not stream quotes.

### Execution Gateway Routing

With `CPZ_PROXY_ENABLED=1` in the environment (hosted strategy runs set it),
`use_broker(<name>)` for every gateway broker (`alpaca`, `tradestation`,
`ibkr`, `snaptrade` and its venues, `polymarket`, `saxo`, `tastytrade`, `kalshi`) and
the implicit Alpaca paper default behind `client.execution.order(...)` route
through the CPZAI execution gateway (`cpz/orders/execute`) instead of talking
to the broker from your process: broker credentials stay server-side, the
pre-trade guard runs on every submission, and each order carries a
`client_order_id` idempotency key. The broker name travels as the tag
(`broker=fidelity` for a SnapTrade venue). Passing `api_key_id`,
`api_secret_key`, `oauth_token` or `access_token` to `use_broker` keeps the
native adapter, and `hft_engine` always keeps its own. `router.routed_via`
tells you which path is active.

```python
import os

os.environ["CPZ_PROXY_ENABLED"] = "1"
client.execution.use_broker("ibkr", environment="paper")
print(client.execution.router.routed_via)  # "cpz_proxy"
```

Every `OrderSubmitRequest` gets `client_order_id = "cpz-<strategy8>-<uuid12>"`
unless you supply one (at most 64 characters from `[A-Za-z0-9_.:-]`). The
returned `Order` carries it, plus `idempotent_replay=True` when the gateway
answered from an earlier submission of the same id instead of placing a second
broker order. Gateway outcomes are typed:

```python
from cpz.execution.cpz_proxy import (
    CPZBrokerRejected,
    CPZOrderLaneBusy,
    CPZSubmitUnconfirmed,
)

try:
    order = client.execution.order(symbol="AAPL", qty=10, side="buy", strategy_id="my-strategy")
except CPZSubmitUnconfirmed as exc:
    # The order may be live under exc.client_order_id. Look it up with
    # get_order_by_client_id() or resend the identical request; never use a fresh id.
    ...
except CPZOrderLaneBusy as exc:
    # Another submission holds this symbol's lane and the SDK already retried
    # once. Retry the identical request after exc.retry_after_ms.
    ...
except CPZBrokerRejected as exc:
    # Definitive broker rejection: exc.code (for example wash_trade_rejected)
    # and exc.broker_code carry the reason. Not retryable as-is.
    ...
```

A timeout or connection error mid-submit is not treated as "not placed": the
SDK looks the order up by `client_order_id` (twice, 500 ms apart) and returns
it if the broker id is bound, and raises `CPZSubmitUnconfirmed` otherwise.

---

## Quantitative Libraries (v2.5.0+)

### Local Indicators

25+ indicators computed locally (no API calls):

```python
from cpz.indicators import sma, ema, rsi, macd, bollinger, atr, vwap

fast = ema(prices, period=10)
slow = ema(prices, period=30)
rsi_val = rsi(prices, period=14)
bb = bollinger(prices, period=20)   # .upper, .middle, .lower
macd_val = macd(prices)             # .macd, .signal, .histogram
```

### Portfolio Optimization

12 optimizers including HRP, Black-Litterman, and Mean-CVaR:

```python
from cpz.portfolio import mean_variance, risk_parity, hierarchical_risk_parity

weights = risk_parity(returns_df)
weights = hierarchical_risk_parity(returns_df)
```

### Signal Construction

```python
from cpz.signals import vol_target, kelly, regime_filter, max_positions

sized = vol_target(raw_signals, returns, target_vol=0.15)
filtered = regime_filter(signals, returns, window=60)
limited = max_positions(signals, max_n=10)
```

### Alpha Research

```python
from cpz.alpha import information_coefficient, signal_decay, quantile_returns

ic = information_coefficient(signals, forward_returns)
decay = signal_decay(signals, returns, max_lag=20)
```

---

## HFT Engine

Deploy strategies to the Rust HFT engine for autonomous microsecond-latency execution.

```python
status = client.engine.status()
client.engine.deploy(
    strategy_id="my-strategy",
    symbols=["AAPL", "NVDA"],
    broker="alpaca",
    environment="paper",
)
client.engine.stop("my-strategy")
```

---

## Data

```python
# Stock/crypto bars
bars = client.data.bars("AAPL", timeframe="1D", limit=100)

# Multi-symbol history for backtesting
df = client.data.history(["AAPL", "MSFT", "NVDA"], timeframe="1D", limit=252)

# Quotes, news, options, economic data, filings, sentiment
quotes = client.data.quotes(["AAPL", "MSFT"])
news = client.data.news("TSLA", limit=10)
gdp = client.data.economic("GDP")
filings = client.data.filings("AAPL", form="10-K")
sentiment = client.data.sentiment("GME")

# Massive (formerly Polygon): stocks, options, indices, futures, crypto, forex
bars  = client.data.massive.get_bars("AAPL", timeframe="1d")
chain = client.data.massive.get_options_chain("AAPL")
quote = client.data.massive.realtime.get_quote("AAPL")   # refuses DELAYED data

# 18 data providers: Massive, Alpaca, TwelveData, FRED, SEC EDGAR,
# Yahoo Finance, Databento, CoinGecko, Finnhub, Alpha Vantage, and more
```

**[Data Connections Guide](docs/data-connections.md)** — how to connect each
provider, then fetch, pull and stream from it (key resolution, entitlements,
streaming support, troubleshooting).

---

## Simons — Quantitative Trading Strategist

```python
response = client.simons.chat("Analyze AAPL for momentum trading")
print(response.content)

for chunk in client.simons.stream("Write a mean-reversion backtest"):
    if chunk.type == "text":
        print(chunk.content, end="", flush=True)
```

---

## Architecture

```
CPZClient
├── Strategy Framework (NEW in v3.0.0)
│   ├── Strategy          Base class with 20+ lifecycle hooks
│   ├── StrategyConfig    Serializable configuration
│   ├── StrategyRunner    Live and backtest orchestration
│   ├── BacktestEngine    Historical replay with fill simulation
│   ├── RiskGuard         Pre-trade order validation (10 rules)
│   └── TWAP/VWAP/Iceberg Execution algorithms
│
├── Typed Domain Model (NEW in v3.0.0)
│   ├── Price             Decimal-backed, fail-fast on NaN/Inf
│   ├── Quantity           Non-negative, precision-safe
│   ├── Money              Currency-tracked arithmetic
│   └── Events            BarEvent, FillEvent, PositionEvent, ...
│
├── risk               Portfolio risk analytics (numpy/scipy)
│   ├── compute()      Full risk snapshot
│   ├── monte_carlo()  Monte Carlo VaR (Rust-accelerated)
│   ├── sharpe_inference() Lopez de Prado framework
│   ├── stress_test_all()  7 historical crisis scenarios
│   └── [20+ more methods]
│
├── execution          Multi-broker trading
│   ├── use_broker()   Configure broker (Alpaca, IBKR, SnapTrade, Polymarket)
│   ├── order()        Place orders with risk guard validation
│   ├── set_risk_guard() Attach pre-trade risk rules
│   └── get_positions() Current positions
│
├── data               Market and reference data (18 providers)
│   ├── bars()         OHLCV price data
│   ├── history()      Multi-symbol DataFrames
│   ├── economic()     31 US macro series (us_macro); other FRED ids need your own key
│   └── [indicators]   100+ technical indicators via TwelveData
│
├── indicators         25+ local indicators (no API calls)
├── signals            Position sizing and signal filters
├── portfolio          12 portfolio optimizers
├── alpha              IC analysis, signal decay, quantile returns
│
├── simons             Quantitative trading strategist AI
└── engine             Rust HFT engine for autonomous execution
```

---

## Configuration

| Variable | Description | Required |
|----------|-------------|----------|
| `CPZ_AI_API_KEY` | CPZ API key | Yes |
| `CPZ_AI_SECRET_KEY` | CPZ API secret | Yes |
| `CPZ_AI_STRATEGY_ID` | Strategy ID for orders | For trading |

Get credentials at [ai.cpz-lab.com/settings](https://ai.cpz-lab.com/settings?tab=api-keys).

---

## Testing

```bash
pytest --cov=cpz --cov-report=term-missing
```

| Python | Status |
|--------|--------|
| 3.9 | Supported |
| 3.10 | Supported |
| 3.11 | Supported |
| 3.12 | Supported |

---

## Support

- **Platform**: [ai.cpz-lab.com](https://ai.cpz-lab.com/)
- **Repository**: [github.com/CPZ-Lab/cpz-py](https://github.com/CPZ-Lab/cpz-py)
- **Email**: contact@cpz-lab.com

---

<p align="center">
  <sub>Built by <a href="https://www.cpz-lab.com/">CPZ</a></sub>
</p>
