<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://github.com/CPZ-Lab/cpz-py"><img src="https://img.shields.io/badge/coverage-85%25%2B-brightgreen.svg" alt="Coverage"></a>
  <a href="https://www.python.org/"><img src="https://img.shields.io/badge/python-3.9%20%7C%203.10%20%7C%203.11%20%7C%203.12-blue.svg" alt="Python"></a>
  <a href="https://github.com/CPZ-Lab/cpz-py/actions"><img src="https://img.shields.io/badge/build-passing-brightgreen.svg" alt="Build"></a>
</p>

---

## What's New in v3.0.0

- **Strategy Framework** — NautilusTrader-class `Strategy` base class with lifecycle hooks and backtest-live code parity
- **Backtest Engine** — Historical replay with fill simulation, slippage/commission models, and full analytics
- **Pre-Trade Risk Guard** — Intercepts every order before broker submission with 10 configurable rules
- **Execution Algorithms** — TWAP, VWAP, and Iceberg order splitting
- **Typed Domain Model** — Precision-safe `Price`, `Quantity`, `Money` types that reject NaN/Infinity
- **Event Bus** — Thread-safe pub/sub with wildcard topic matching

---

## 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`** | NautilusTrader-class 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 |
| **`client.risk`** | Portfolio risk analytics — VaR, Sharpe inference, Monte Carlo, stress testing |
| **`client.execution`** | Multi-broker order management (Alpaca, IBKR, SnapTrade, Polymarket) with TWAP/VWAP/Iceberg algos |
| **`client.engine`** | Low-latency HFT engine deployment (Rust) for autonomous execution |
| **`client.data`** | Stocks, crypto, options, 800K+ FRED series, SEC filings, social sentiment, 100+ indicators |
| **`client.simons`** | Simons — quantitative trading strategist for analysis, code generation, and strategy review |

---

## Installation

```bash
pip install cpz-ai               # core SDK
pip install cpz-ai[risk]          # + statsmodels for advanced risk analytics
pip install cpz-ai[all]           # + all optional dependencies
```

---

## 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
============================================================
```

### 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)` | Timer alert fired |
| `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.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]`.

The risk-free rate is automatically fetched from FRED (3-Month Treasury) on first use. 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("ibkr", environment="paper")
```

### 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}")
```

---

## 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")

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

---

## 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()     FRED (800K+ series)
│   └── [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>
