# CPZAI Full Documentation
This file contains the full content of CPZAI documentation for ingestion by LLMs, crawlers, and knowledge bases. It is auto-generated from src/lib/docs-content.ts on every build — do not edit by hand.
- Generated: 2026-11-14
- Source of truth: src/lib/docs-content.ts
- Website: https://ai.cpz-lab.com
- Docs landing: https://ai.cpz-lab.com/docs
# What is CPZAI?
CPZAI is an AI-native systematic trading operating system covering research, strategy development, derivatives valuation, execution, portfolio risk and middle-office record keeping.
The components share one compiled core. A valuation returned by the API, a mark applied to a position and the amount posted to the ledger are produced by the same compiled code rather than by separate implementations.
## Components
| Component | What it does |
|---|---|
| Strategy Lab | Create and manage strategies from templates or custom code |
| Builder | A Python IDE for strategy development, with integrated backtesting |
| Native lanes | Rust and C++ strategies compiled and run per user |
| Simons | An AI quantitative strategist available across the modules |
| Backtesting | Historical simulation, out-of-sample checks and sealed holdouts, cost and slippage settings, a record of every run |
| Derivatives | Rates, credit, volatility, delta one, barriers, digitals, multi-asset and path-dependent products, with greeks |
| Middle Office | OTC trade capture, lifecycle events, cash flows, a double-entry ledger, NAV and PnL attribution |
| Risk Lab | VaR, stress testing, correlation and concentration, portfolio construction, mandates and a trading halt |
| Execution | Broker connectivity, order management, drop copy, FIX sessions, paper and live |
| Data Lab | Your own files, data provider and warehouse connections, a screener, macro and SEC data, and a catalogue of datasets |
| Portfolio | Aggregated performance across accounts and strategies |
## Architecture
**One compiled core.** The pricing engine and the middle-office engine are dependency-free Rust crates. Each is compiled once and used from Rust strategy code, the C and C++ lane, the Python SDK, the edge services and the browser, through a C ABI and a WebAssembly build of the same source.
**Deterministic.** The engines read no wall clock and use no ambient randomness. Monte Carlo seeds are explicit inputs. The same inputs produce the same outputs on re-run.
**Errors instead of sentinel values.** A computation that cannot be performed returns an error naming the input that prevented it. There is no sentinel value, and no NaN or zero substituted for a result. A floating coupon whose fixing has not been observed is reported as pending, with the fixing it requires named.
**Integer amounts in the ledger.** Amounts posted to the general ledger and the NAV are integer counts of minor units rather than floating point, so that entries balance exactly and sum identically regardless of traversal order. Valuation is performed in double precision; the conversion between the two happens once, at a single documented point, using round-half-to-even.
**Validation against published references.** Day count conventions are checked against the ISDA 2006 tables, barrier and American option prices against Haug's published values, hashes against the NIST vectors, and closed forms against independent Monte Carlo and quadrature.
## Data
Some data comes with every account: the screener's quotes and fundamentals, a default price history for backtests, US macro series from the agencies that publish them, and SEC filings. Other providers, from market data vendors to your own data warehouse, connect with your own credentials, and their data stays under your licence. See [Connecting Data Providers](/docs/data-integrations/connecting-data-providers).
## Interfaces
Every capability is available over HTTP, through the Python SDK and through the CLI. The web interface calls the same API.
Strategy code is Python, Rust or C++. Every strategy is versioned in a Git repository: one CPZAI keeps for you, and your own on GitHub if you link one.
# How Simons works
Simons is the AI quantitative strategist built into CPZAI. It is not a chatbot bolted onto a broker, and it is not a small language model wearing a large-model name.
## The split
| Job | Model class | Why |
|---|---|---|
| Strategy conversation, tool use, code edits | Large language model: CPZAI routing by default, or the model you pick from Anthropic, OpenAI, Google, DeepSeek, Moonshot, Perplexity or CPZAI's self-hosted model ([which provider sees what](/docs/security-privacy/ai-model-routing-privacy)) | Tool calling, long context, and a diff you can review |
| Sentiment classification | Smaller specialised model | A label, not a paragraph |
| Retrieval (Simons RAG) | Embedding model | Semantic search over your strategies |
| Voice | Whisper / Deepgram | Transcription, not reasoning |
| Self-hosted replacements | Model registry + fine-tune jobs | When a hosted model is the wrong fit |
The industry shorthand "SLM" means a small language model. We run small models where a small model is the right tool. We do not describe Simons, the strategist, as one.
## How a turn runs
1. You send a message in a mode (Ask / Agent / Debug), together with the strategy you are working on.
2. Simons loads your connections, files, and the strategy as it actually is before answering.
3. It looks up what it needs (your data, your strategy files, the SDK reference) instead of guessing.
4. Tool results are shown in the chat. A number that is not in those results is not invented.
5. In Agent mode, a code change is a proposal. You accept it or you do not.
## What this is not
- Not a claim that a small model is "better" than a large one for writing strategies. It is not, today, for this job.
- Not a claim that we train a foundation model. Fine-tune jobs exist; they are for specialised replacements, not for a new Simons.
- Not a substitute for reading the diff.
# Differences from Incumbent Platforms
This page records factual differences between CPZAI and the established institutional platforms, in both directions. It is intended for an evaluation, not as a recommendation.
## Architectural differences
**Single core rather than integrated components.** Valuation, lifecycle and accounting are implemented in two Rust crates rather than as separate systems reconciled to one another. A mark and the corresponding ledger posting are produced by the same compiled code.
**Reproducibility.** Valuations are deterministic and the journal is append-only and hash-chained, with corrections recorded as reversing entries rather than as edits. The state of the book at any past date can be reconstructed from the journal.
**Interface.** All functionality is exposed over HTTP, the SDK and the CLI. There is no proprietary strategy language; strategies are Python, Rust or C++ held in your own repository.
**Data.** Vendor data is accessed under the client's own entitlements rather than resold by the platform.
## Commercial differences
Pricing is a fixed fee rather than a charge on assets under management.
Implementation is scoped as a pilot against the client's own environment rather than as a multi-quarter programme.
## Where the incumbent platforms cover more
- **Instrument coverage** beyond what is documented here, particularly structured credit, securitised products, insurance-linked instruments and physical commodities.
- **Regulatory reporting** packs for a wider set of jurisdictions.
- **Counterparty integrations** across a longer list of custodians, administrators and prime brokers.
- **Support organisation** of a size a smaller vendor does not have.
## Scope of the claims in this documentation
Everything described in these docs is implemented and covered by tests. Where a capability exists in the engine but is not yet surfaced in the web interface, the relevant page says so. Feature parity with platforms developed over two decades is not claimed.
# System Overview
Every page is one click from the sidebar on the left.
## The sidebar
| Page | What it is for |
|---|---|
| **Home** | Your accounts at a glance, quick actions such as connecting a broker or deploying a strategy, and the **Getting started** checklist |
| **Strategy Lab** | Your strategies and the built-in templates. Two pages are listed under it: the **Builder**, the editor where a strategy is written, run and backtested, and **Agents**, your agents and the agent templates, once agents are switched on for your account |
| **Simons** | A full-page conversation with Simons, the AI quantitative strategist built into CPZAI |
| **Data Lab** | Research projects, your own files, data and warehouse connections, charts, the screener, macro and SEC data, a factor library and the Data Scout catalogue |
| **Risk Lab** | Risk metrics, stress scenarios, reconciliation against your broker, mandates and the trading halt |
| **Portfolio** | Performance, accounts, strategy attribution, trades and daily P&L |
| **Execution** | Broker connections, order entry, positions and recent orders |
**Docs** and **Help** are at the bottom of the sidebar.
## The top bar
From left to right: the sidebar toggle, **Search**, the light and dark theme toggle, **Create new strategy**, the **Help** menu, notifications and your account menu.
## Simons beside any page
Press Cmd+/ (Ctrl+/ on Windows and Linux) to open Simons next to the page you are working on, and again to close it.
## Where to start
[Getting Started](/docs/getting-started/account-setup) covers your account, connecting a broker, and the path from a first idea to a strategy trading on paper.
# Account Setup
## Creating your account
1. Go to [ai.cpz-lab.com](https://ai.cpz-lab.com/auth?tab=signup) and open **Sign up**.
2. Enter your name and email address, read and accept the Terms and Conditions, and click **Continue**.
3. Add a payment method on the secure checkout page. The Research Seat starts with a 14-day free trial, and you are charged only if you keep it after the trial.
4. Open the setup email and follow its link within 24 hours. Set a password of at least 12 characters and click **Complete Setup**.
5. Set up two-factor authentication with an authenticator app. Every account needs it before any page opens.
The sign-in page also offers Google, GitHub, Apple, and a one-time code sent to your email.
## During the trial
- Live trading is off until the trial ends. Paper trading works as usual.
- Simons and compute share a $5 usage allowance.
## Plans
| Plan | |
|---|---|
| **Research Seat** | $249 a month. Includes $40 of usage each month, one allowance shared by Simons and compute. See [Pricing Plans](/docs/billing/pricing-plans) |
| **Institutional** | For firms, priced per engagement: [contact@cpz-lab.com](mailto:contact@cpz-lab.com) |
Evaluating CPZAI for a firm? Write to [contact@cpz-lab.com](mailto:contact@cpz-lab.com) and we will set up accounts for the people who will use it.
## More than one person
Each person signs in to their own account, with their own password and authenticator. Research projects can be shared with colleagues who have accounts, as **Viewer**, **Commenter** or **Editor**: open the project in **Data Lab → Research** and click **Share**.
## After you sign in
- A welcome dialog offers **Take the Tour**, a guided walk through the main pages. **Help** starts it again at any time.
- **Home** shows a **Getting started** checklist: Broker, Repo (optional), Data, Strategy, Backtest, Deploy. Each step ticks itself once it is done.
- **Help → Getting Started → Risk Assessment** is a short conversation with Simons about your goals and your tolerance for risk. Simons uses the answers in its research and suggestions. It sets no limits; limits are [mandates](/docs/risk-management/mandates-and-halts).
## Next
1. [Connect your broker](/docs/getting-started/connecting-your-broker).
2. Follow [From an Idea to a Paper Deployment](/docs/getting-started/first-paper-deployment).
# Connecting Your Broker
CPZAI is not a broker. Orders are placed in your own account at your broker, which holds your cash and positions. CPZ Capital Ltd is not affiliated with, or a partner of, any broker named here.
## Where
- **Execution → Connect Broker** when no account is connected yet, or **API Keys → Manage Accounts → Add Account** on the same page once one is.
- **Settings → Integrations**, which lists each broker with its own connect button.
Both open **Connect Broker**. Pick your broker and follow its steps.
## Brokers
| Broker | How you connect | Environments |
|---|---|---|
| **Alpaca** | **Connect with Alpaca OAuth**, which connects paper and live in one sign-in, or an API key and secret key | Paper and Live |
| **TradeStation®** | Sign in with TradeStation. One sign-in connects both | SIM and Live |
| **Saxo Bank** | Register an application with Saxo, enter its App Key and App Secret, then sign in with Saxo | SIM or Live |
| **tastytrade** | Sign in with tastytrade | Sandbox or Live |
| **Interactive Brokers** | Your IBKR username and password, then approve the sign-in IBKR sends to your phone. Switched on for your account on request | Paper or Live |
| **Other brokers**: Fidelity, Schwab, Robinhood and more | **Open Connection Portal** and choose your broker | Live |
| **Kalshi** | A Demo API key ID and its RSA private key, checked before they are saved | Internal Demo testing only; customer access awaits approval |
| **Polymarket** | A Polygon wallet private key | Live |
What you can trade through each depends on the broker and on your account there.
## Paper and live
Each connection belongs to one environment, and the Execution page follows the account you select. A paper account trades simulated money at the broker.
Live trading is switched on for each account by CPZAI, after the trial. Before your first live order you sign a short live-trading acknowledgement. See [Paper vs. Live Trading](/docs/execution/paper-vs-live).
## More than one account
Connect as many accounts as you need. They are listed under **Paper Accounts** and **Live Accounts**. Most brokers take one connection per environment; Alpaca API keys, the connection portal and Polymarket take several.
- The account dropdown at the top of **Execution** chooses where a manual order goes. The star marks your default.
- A strategy chooses its account in its own code, for example `client.execution.use_broker("alpaca", environment="paper")`. See the [Execution API](/docs/sdk/execution).
## If a connection fails
- **Alpaca or Kalshi keys** belong to one environment: a paper key does not connect a live account.
- **Saxo Bank** needs your own application's App Key and App Secret, from Saxo's developer portal.
- **Interactive Brokers** waits for you to approve the request IBKR sends to your phone.
# From an Idea to a Paper Deployment
The path a strategy takes from a first idea to trading on paper. Each step links to the detail.
1. **Connect an Alpaca paper account.** One-click deployment trades in it. See [Connecting Your Broker](/docs/getting-started/connecting-your-broker).
2. **Create the strategy.** In **Strategy Lab**, click **New Strategy**, or start from a card on **Templates**. See [Strategy Lab Overview](/docs/strategy-lab/overview).
3. **Read and refine it in the Builder.** `strategy.py` holds the logic and its `SYMBOLS`. Simons can draft, explain and change it with you. See [Using the Builder](/docs/strategy-lab/using-the-builder).
4. **Backtest it.** On `backtest.py`, open **Config**, set the period, capital, data source, costs and slippage, and click **Run**. See [Running Backtests](/docs/backtesting/running-backtests).
5. **Test it on data it has not seen.** Seal a holdout in **Config**, keep working on the earlier data, and run the sealed test once, with your final code.
6. **Put limits around it.** In **Risk Lab → Mandates**, create a paper mandate for the strategy or the account: order size, gross exposure, daily loss, orders per day, symbols. See [Mandates and the Trading Halt](/docs/risk-management/mandates-and-halts).
7. **Deploy it to paper.** Click the rocket on the strategy in **Strategy Lab**, or **Home → Deploy → Deploy to Paper (1-click)**. CPZAI checks the code for look-ahead, backtests it over the last year if it has not been backtested, checks the result, and schedules it from its code. See [Running a Strategy on a Schedule](/docs/execution/running-strategies).
8. **Watch it.** The deployment's monitor page shows equity, exposure, fills and its log. **Portfolio** shows the account, and **Strategy Attribution** the strategy's share of it. See [Performance Dashboards](/docs/portfolio/performance-dashboards).
9. **Go live, when you are ready.** CPZAI switches live trading on for your account. Then connect a live account, give it a live mandate (every limit, and an expiry date), and point the strategy's code at it. See [Paper vs. Live Trading](/docs/execution/paper-vs-live).
At any point, **Halt trading** in **Risk Lab → Mandates** stops every new order and every strategy run at once.
# Navigating CPZAI
## Sidebar
The sidebar lists **Home**, **Strategy Lab**, **Builder**, **Agents**, **Simons**, **Data Lab**, **Risk Lab**, **Portfolio** and **Execution**, with **Docs** and **Help** at the bottom. **Builder** and **Agents** are indented under **Strategy Lab**, and **Agents** is listed once it is switched on for your account. The toggle at the top left collapses the sidebar to icons.
## Search
Click **Search** in the top bar, or press `Cmd+K` (`Ctrl+K` on Windows and Linux), to:
- jump to any page or setting,
- open one of your strategies or data files,
- look up a symbol,
- start an action, such as creating a strategy.
## Keyboard shortcuts
Shortcuts work when you are not typing in a field. Press `?` to see them.
| Shortcut | Action |
|---|---|
| `Cmd+K` | Search |
| `Cmd+/` | Open or close Simons, even while typing |
| `G` then `H` | Home |
| `G` then `S` | Strategy Lab |
| `G` then `B` | Builder |
| `G` then `I` | Simons |
| `G` then `D` | Data Lab |
| `G` then `R` | Risk Lab |
| `G` then `P` | Portfolio |
| `G` then `E` | Execution |
| `N` | New strategy |
[Keyboard Shortcuts](/docs/shortcuts/quick-reference) has the full list, including the Builder's own.
## Top bar
From left to right: the sidebar toggle, **Search**, the theme toggle, **Create new strategy**, the **Help** menu, notifications and your account menu.
The account menu holds **Settings**; links to the documentation, the Python SDK, the CLI and the open-source cpz-quant library; **Report Bug**, **Request Feature** and **Contact Support**; and **Sign Out**.
# Strategy Lab Overview
The Strategy Lab holds your strategies and the built-in templates, and is where a new strategy starts.
## Starting a strategy
- **New Strategy** opens **Create New Strategy**. Only **Strategy Name** is required. You can also load a template, import a repository, write a description, attach research papers or context files, and have Simons draft the code with **Sketch with Simons**. **Create strategy** saves it as a draft and opens its details.
- The **Templates** tab lists the built-in templates and your own. Click one to start from it.
- The [screener](/docs/data-integrations/screening-the-universe)'s **Export to Strategy** starts one from the names you selected.
- Anywhere, press `N`.
A strategy's symbols and parameters live in its code, for example `SYMBOLS = ["SPY", "TLT"]`, so what you read in the Builder is what runs.
## Where the code lives
Every strategy is versioned in a Git repository that CPZAI keeps for you. To mirror it to GitHub, choose **Create new** or **Link existing** under **Version control** when you create the strategy, or connect GitHub later in **Settings → Integrations**. When CPZAI creates the GitHub repository for you, choose **Private** unless you mean to publish the code.
**Export as ZIP**, in the Builder or in the strategy's details, downloads the code at any time.
## How many strategies
An account holds five strategies by default; demo strategies do not count. Strategy Lab's Library shows your count against your account's limit, and support can raise the limit.
## Next
- [Using the Builder](/docs/strategy-lab/using-the-builder)
- [Managing Strategies](/docs/strategy-lab/managing-strategies)
- [Running Backtests](/docs/backtesting/running-backtests)
# Using the Builder
The Builder is the full-screen editor where a strategy is written, run and backtested. Open it from **Strategy Lab** in the sidebar, from a strategy's details, or with `G` then `B`.
## Layout
- **Strategy picker and file tabs.** `strategy.py` is the strategy. `backtest.py` is the script that backtests it: the Builder writes it the first time you open the tab, and it is plain Python you can read and change. Other files in the strategy's repository open from **+** then **Repository Files**.
- **Top bar.** The strategy's status, **Strategy language** (Python, Rust or C++), Simons, **Code History**, the GitHub controls (**Sync**, **Pull latest**, **Commit & Push**) when a repository is linked, and **Export as ZIP**.
- **Beside Code History.** **Packages**, **Load Data** and **Snippets**.
- **Output.** **Results** and **Console**. On `backtest.py`, **Config** holds the backtest settings; see [Running Backtests](/docs/backtesting/running-backtests).
## Running code
**Run**, `Shift+Enter` or `Cmd+Enter` runs the open file, and `Esc` stops it.
Running `strategy.py` executes it for real: orders it places go to the broker account its code selects. Backtests run from `backtest.py`, with orders switched off.
## Packages
**Packages** searches PyPI or takes a Git URL, and installs, upgrades and removes packages. You can also upload a wheel (`.whl`) of up to 50 MB. When a run fails on a missing module, the output offers to install it.
## Data
**Load Data** inserts the code that reads one of your uploaded files into the strategy. Uploading files is covered in [Importing Data](/docs/data-integrations/importing-data).
## Saving and versions
`Cmd+S` saves. A dot on a file tab marks unsaved changes, and the Builder warns you before you leave with any. Backtest results are saved automatically. **Code History** shows earlier versions of the file.
## Simons in the Builder
`Cmd+/` opens Simons beside your code. It has three modes:
- **Ask** answers questions and changes nothing.
- **Agent** proposes code changes as a diff, which you accept or reject.
- **Debug** works through an error with you.
`Cmd+Shift+A` switches between Ask and Agent, and `Cmd+Shift+D` switches to Debug.
## Rust and C++
Choose the language under **Strategy language**. Rust and C++ strategies are compiled and run on CPZAI's compute service. The `backtest.py` tab is for Python strategies.
# Managing Strategies
## The library
- **Search**, and the presets **All**, **Top**, **Recent** and **Review**.
- Filters for **Status**, **Type** and **Scheduler**, and **Advanced Filters** for return, Sharpe ratio and creation date.
- Cards or a table. Double-click a strategy's name to rename it.
## Draft, Testing, Active and Paused
| Status | Meaning |
|---|---|
| **Draft** | In development |
| **Testing** | Paper trading |
| **Active** | Running on its schedule |
| **Paused** | Stopped |
Change it from the strategy's details. Set the schedule in the **Scheduler** tab first: **Active** then switches it on, and **Paused** switches it off. See [Running a Strategy on a Schedule](/docs/execution/running-strategies).
## In a strategy's details
- **Implementation → Backtest History** lists every saved run, to view it, load the code it ran, or download it.
- **Clone Strategy** copies the strategy as a new draft.
- **Save as Template** adds it to your templates.
- **Export as ZIP** downloads its code.
- **Certify & share** grades it.
- **Delete Strategy** removes it permanently.
# Running Backtests
A backtest runs `backtest.py`, a Backtrader script the Builder writes from your strategy. Everything the backtest does is in that file: the script you read is the backtest that ran.
## Running one
1. Open the strategy in the **Builder** and select the `backtest.py` tab.
2. Open **Config** and set:
- **Period**: the start and end dates.
- **Symbols**: filled in from `SYMBOLS` in your strategy.
- **Capital**.
- **Source**: where prices come from (see below).
- **Benchmark**.
- **Execution**: **Slip** (5 or 10 basis points), **Cost** (3, 5 or 0 basis points) and **Size**: the strategy's own weights, equal weight, risk parity or Kelly. Risk parity and Kelly need 20 bars of returns for a symbol: until then that symbol is held at zero and the others are sized. On daily returns Kelly usually keeps a few percent of the account invested, and the run's flags say how much.
3. Click **Run**, or press `Shift+Enter`.
Orders are switched off in a backtest. Bars are daily unless the script sets another interval.
## Price sources
| Source | What it needs |
|---|---|
| **Alpaca** (the default) | Nothing: without an Alpaca account of your own, daily bars come from Alpaca's consolidated SIP feed and recent intraday bars from its IEX feed |
| **Massive**, **Databento** | Your own key, connected in the Data Lab. See [Connecting Data Providers](/docs/data-integrations/connecting-data-providers) |
| **Interactive Brokers** | Your IBKR connection |
| **Kalshi** | Internal Demo testing only: other accounts are refused when the run starts. See below |
## Kalshi contracts
**Kalshi** backtests named contracts from the Kalshi Demo exchange, which is simulated trading. Customer access awaits Kalshi's approval, and production data is not available. Powered by Kalshi Research.
- **Symbols** are **TICKER:YES** or **TICKER:NO**. Bars are **1m**, **1h** or **1d**.
- **Bars** are trade prices. A period with no trades has no bar, so nothing is carried forward. NO is the mirror of YES: open 1-o, high 1-l, low 1-h, close 1-c. Volume is contracts.
- **Settlement.** When a market settled inside the period, the series ends with one bar at what the held outcome paid, 1.00 or 0.00, from the market's real result. A position still held is paid out at that value with no fee and counts as closed. No order fills on that bar. A void or unknown result stops the run.
- **Not settled.** The series ends at its last trade, and the position stays open and marked to that price. The results say so.
- **Costs** are the slippage and commission you set, not Kalshi's fee schedule, and fills assume the trade price, not the bid or ask. **Broker** must be Flat bps and **Account** Not modeled.
- **Long only.** A sell cannot open the other side on Kalshi, so a negative weight stops the run: buy the other outcome instead. A buy fills what the cash on hand pays for.
- **No benchmark.** There is no benchmark return, alpha, beta or factor attribution. Sharpe and volatility are scaled on the run's own bars per year, and a run under a year shows its total return as Annualized Return.
- **Several contracts** are scored from the first bar on which all of them have traded.
## Results
- **Backtest Results**, marked **Verified** or **Unverified**, with **Save** and exports of the summary, the chart and the trades.
- The headline metrics: see [Understanding Backtest Metrics](/docs/backtesting/understanding-metrics).
- The run card: its verification checks, where the data came from, and the **Rigor** section.
- **Equity Curve**, **Risk & Performance**, and **Trade History**, which shows the first 50 trades.
## A test the strategy has not seen
**Holdout**, in **Config**, keeps a stretch of recent data out of your research:
1. Click **Seal recent data**, choose a length of 3, 6 or 12 months, check the dates, and confirm with **Seal this window**. From then on, research runs end before the sealed window. If recent data has already been tested, the button reads **Seal a forward holdout** instead, and the window can be tested once it has passed.
2. Work on the strategy against the earlier data. Until a backtest reads the window, **Undo seal** releases it. Nothing has seen that data yet, so undoing costs no evidence.
3. When the code is final, click **Run sealed test**. That run goes through the sealed window, once, and is graded. From then on the seal is permanent, and later runs that cover it are counted as re-reads.
## History
Every saved run is in the strategy's details under **Implementation → Backtest History**, where you can view it, load the code it ran, or download it.
# Understanding Backtest Metrics
## The tiles
| Metric | What it measures |
|---|---|
| **Strategy Return** | The total gain or loss over the period |
| **Benchmark** | The benchmark's return over the same period, SPY unless you choose another. On Alpaca and Massive bars it is a price return with dividends left out, and positions receive none either. A benchmark that started trading after the start date shows its own dates instead of Outperformed or Underperformed, the run is flagged, and alpha is not reported |
| **Sharpe Ratio** | Return above the risk-free rate per unit of volatility, annualised. The rate is 5% unless you change it under Config, and idle cash earns 0% |
| **Max Drawdown** | The largest fall from a peak to a later low |
| **Win Rate** | The share of closed positions that made money. A position runs from flat to flat and counts once, however many times it was added to or trimmed. Positions still open on the last bar are not scored |
| **Volatility** | The annualised standard deviation of returns |
| **Alpha** and **Beta** | Return not explained by the benchmark, and sensitivity to it |
## Risk & Performance
The **Risk & Performance** tab adds the drawdown chart, annualised return, profit factor (gross profit divided by gross loss), average profit and loss per trade, **What this backtest modeled**, and factor attribution. A position's profit or loss per trade is its dollar result divided by the most money it held at once, so a holding rebalanced every day scores like the same holding bought once.
## How far to trust a result
The run card's **Rigor** section is there because a single good backtest proves little:
- **Deflated Sharpe** corrects the Sharpe ratio for the number of variants tried. CPZAI counts the runs recorded for the strategy, so testing many variations raises the bar each one has to clear.
- **Probabilistic Sharpe** is the probability that the true Sharpe ratio is above zero, given the length and shape of the return series.
- **Out-of-sample** Sharpe and return come from a final stretch of data the run holds back. It is one fixed split, not a re-fitted walk-forward.
- **Turnover** says how much the strategy trades, as a multiple of its average equity per year.
- **Capacity** and **Parameter stability** are not estimated yet, so the run card shows n/a for both: the engine has no volume participation model to size capacity, and it does not re-run a strategy across a grid of its parameters.
For a test the strategy has never seen, seal a holdout: see [Running Backtests](/docs/backtesting/running-backtests).
## Reading the results
- Look at the equity curve, not just the return. A result carried by a few trades is fragile.
- Compare the strategy with its benchmark on risk as well as return.
- Check the trade count. A handful of trades says little about the next hundred.
# What a Backtest Models
The backtest script is the backtest. Nothing runs that you cannot read in the editor, and the script says, near the top, what it simulates and what it does not.
## The MODELING block
Every generated `backtest.py` has a short `MODELING` block. Each row names an area (fees, fills, account, settlement, borrow, dividends and splits), says whether the script models it, and how. The results show the same rows under **What this backtest modeled**. Rows that were not modeled are shown muted, not hidden.
## Costs and fills
- A strategy returns target weights, for example `{'SPY': 0.6, 'TLT': 0.4}`. The backtest turns them into market orders, filled at the next bar's open.
- **Cost** charges a commission, in basis points of the value traded, on every fill.
- **Slip** moves every fill against you by that many basis points.
- No account rules are applied: target weights are scaled to at most 1x gross, and shorts are allowed.
- Stocks and ETFs are bought in whole shares, rounded down. Crypto pairs (`BTC/USD`, `CRYPTO:BTC`, `X:BTCUSD`) are bought in fractions of a coin.
- Shorts are charged `BORROW_COST_BPS` a year to borrow them. It is 0 unless you set it in `backtest.py`: Config has no borrow setting. A run that held shorts says on Results what it charged.
## More realism: the CPZ SDK
For order types beyond market orders (limit, stop, trailing stop, market-on-open and market-on-close), broker fee schedules, cash and margin account rules, volume-limited fills, scheduled rebalancing and corporate actions, backtest with the CPZ SDK. The `cpz.strategy.sim` module in `cpz-ai` 4.29.0 and later provides them through `StrategyRunner.backtest`.
# Importing Data
## Uploading a file
1. In **Data Lab**, click **+** at the top right and choose **Upload Files**.
2. Drop a file in, or click to choose one. One file is uploaded at a time.
| File | How it is kept |
|---|---|
| CSV, JSON, Excel (.xlsx, .xls) | Read as a table of data |
| PDF, Word, text, Markdown, RTF, Python, images | Stored as a document, for research projects and for Simons |
A file can be up to 2 GB, and an account holds 2 GB to begin with. **Settings → Usage** shows what you are using and has **Request More Storage**.
Your files are listed in **Data Lab → My Data → My Data Files**.
## Using a file in a strategy
In the Builder, **Load Data** inserts the code that reads one of your files into a DataFrame. The strategy then uses it like any other data.
# Connecting Data Providers
## Data that comes with every account
- The [screener](/docs/data-integrations/screening-the-universe)'s quotes and fundamentals.
- Price history for backtests when you have no Alpaca account of your own: Alpaca's consolidated SIP feed for daily bars, its IEX feed for recent intraday bars.
- US macro series from the agencies that publish them, and SEC filings, in **Data Lab → Macro & Economic**.
- Charts, in **Data Lab → Charts**.
## Providers you connect with your own credentials
The data comes under your licence with the provider.
| Provider | What you enter |
|---|---|
| Massive, Alpha Vantage, Finnhub, Twelve Data, Financial Modeling Prep, Tiingo, EODHD, Nasdaq Data Link, Bigdata, CoinGecko, Databento | An API key |
| SpiderRock | A key, its environment and your data tiers |
| Snowflake | Your account and an access token, or sign-in, which your Snowflake ACCOUNTADMIN enables |
| Databricks | Your workspace URL and a token, or sign-in |
| Amazon Redshift | The cluster, region, database and user, with an AWS access key and secret |
| Google Drive, Dropbox, Notion, Airtable | Sign in |
## Connecting one
- **API keys:** **Data Lab → My Data → API Connections → Add**, choose the provider, enter its credentials and click **Add Connection**.
- **Warehouses and sign-in providers:** **Data Lab → Integrations**, then the provider's card.
Credentials are stored encrypted. A key is checked the first time data is fetched with it, so a mistyped key shows up as an error on that first fetch.
## Using connected data
- The CPZ SDK uses your connected keys when your code asks a provider for data.
- In the Builder, a backtest's **Source** of **Massive** or **Databento** uses your key.
- **Data Lab → Data Scout** is a catalogue of external datasets, each marked with how it is accessed: free, with your own key, or under a licence.
# Screening the Universe
**Data Lab → Screener** screens US-listed stocks and ETFs: every name with a quote in the last 14 days. The count is on screen.
## Filters
- **Presets:** S&P 500, NASDAQ 100, Dow 30, Value, Growth, Momentum, High Dividend, Small Cap Value and Mega Cap.
- **Search** by symbol or name, **Index**, **Cap**, and **Sectors** (the 11 GICS sectors).
- **Advanced:** P/E Ratio, Price ($), Div Yield (%), Avg Volume, P/B Ratio, and **Technical**: above the 50-day or the 200-day moving average.
## Columns
Symbol, Name, Price, Chg %, Mkt Cap, P/E, Yield, 52-Week Range and Volume. Click a column to sort by it. Results show 50 to a page.
## How fresh it is
| Data | Refreshed |
|---|---|
| Quotes | Every 2 minutes in the regular session, every 10 minutes before and after it |
| Fundamentals | Twice a day, rescaled to the latest price |
| 52-week range and moving averages | Nightly |
| Index membership | Daily |
## From a screen to a strategy
Select the rows you want. A bar appears at the bottom:
- **Copy** copies the symbols.
- **Research first** asks Simons to analyse them with real data before you build anything.
- **Export to Strategy** opens **Create New Strategy** with your selection as the strategy's `SYMBOLS`.
## From code
The SDK screens the same universe, with more filters, including return on equity, exchange, and stock or ETF:
```python
from cpz import CPZClient
client = CPZClient()
results = client.data.screen(index="sp500", sort="divYield", sort_dir="desc", limit=20)
```
# Risk Lab Overview
The Risk Lab measures the risk in your connected accounts and holds the controls that limit it. The account selector at the top chooses one account or **All Accounts**.
## The summary strip
Sharpe, Sortino, VaR 95% (parametric, one day), Max DD (90 days), Volatility, Beta (against SPY), Alpha and HHI (concentration), with long and short exposure and **Active Alerts**.
## Tabs
| Tab | What it shows |
|---|---|
| **Overview** | Risk metrics over time, with CSV and PDF export, and risk per strategy |
| **Real-Time** | Live VaR, drawdown, slippage and Sharpe, intraday risk, and signal decay |
| **Stress Scenarios** | Your portfolio under historical and hypothetical shocks, scenarios you build, and a pre-trade what-if. See [Stress Testing](/docs/risk-management/stress-testing) |
| **Fragility** | A market-wide fragility score from crowding, regime, liquidity, the volatility surface and cross-asset correlation |
| **Factors** | Your exposure to market, size, value, momentum and low-volatility factors |
| **Reconciliation** | Your positions in CPZAI against the broker's, and the breaks between them |
| **Portfolio** | Risk budgeting, execution quality, capacity, and minimum-variance, risk-parity and HRP weights across strategies |
| **Overlays** | Hedge ratios and VaR for overlay strategies, when you run any |
| **External** | US macro data, asset-class exposure and contagion |
| **Governance** | The strategy registry, attribution, the audit trail and a PDF risk report |
| **Mandates** | Limits on orders, enforced before every order, and the trading halt. See [Mandates and the Trading Halt](/docs/risk-management/mandates-and-halts) |
| **Strategy Health** | Signs that a strategy's edge is decaying, and a register of the hypotheses behind each strategy |
| **Network** | Correlation between your strategies' returns |
| **Pairs Analysis** | Cointegration tests (Engle-Granger and Johansen) on two symbols you choose |
Most tabs need positions in a connected account. **Fragility**, **External** macro data and **Pairs Analysis** work without any. The portfolio weights need a strategy with at least 20 days of history.
## Reconciliation
CPZAI compares its record of your positions with your broker's for Alpaca and for accounts connected through the connection portal: every 15 minutes during US trading hours on weekdays, nightly, and whenever you click **Run reconciliation**. A break is a position missing on either side, or a quantity or cost basis that differs. A position missing in CPZAI, or a quantity that differs, also sends you an email.
# Stress Testing
**Risk Lab → Stress Scenarios** revalues every position in your connected accounts under a set of shocks and shows the profit or loss each would cause.
## Historical scenarios
Each is a fixed set of shocks sized to a real episode, not a replay of its prices:
- **Global Financial Crisis (2008):** equities -38%, implied volatility +30 points, rates -200bp.
- **COVID Crash (Mar 2020):** equities -34%, implied volatility +50 points, rates -100bp.
- **Black Monday (1987):** equities -22%, implied volatility +25 points.
- **Rate Shock (2022):** rates +300bp, equities -25%.
- **Taper Tantrum (2013):** rates +100bp, equities -6%.
## Hypothetical scenarios
Single shocks that isolate one exposure: equities +10%, -10% and -20%; rates +100bp, -100bp and +200bp; the US dollar +10%; crypto -50%; volatility +20 points. **Crowd de-risks 10%** and **25%** sell every crowded name in proportion to how crowded it is.
## Running a test
1. Open **Risk Lab → Stress Scenarios**.
2. Click **Run all scenarios**, or tick the ones you want and click **Run selected**.
3. Read the results, scenario by scenario or side by side.
**Build a scenario** combines up to eleven shocks, with an optional knock-on effect and a probability, and saves it to your library.
## Results
- Portfolio profit or loss, in dollars and percent, and by strategy.
- Every position with its market value, profit or loss and the pricing method used.
- The share of the portfolio that could be priced, and each excluded position with the reason.
A position that cannot be priced is excluded and listed, never valued at zero. An equity is priced from its factor beta, so run the **Factors** tab first if positions are being excluded for want of one.
## What-if
The same tab has a pre-trade what-if: enter an order (symbol, side, quantity and an optional limit price) to see how it would change your risk score, VaR, largest concentration and gross exposure. Nothing is sent to a broker.
# Mandates and the Trading Halt
Both live in **Risk Lab → Mandates**. CPZAI enforces them before an order is sent to your broker, whether you place it on the Execution page or a strategy, the API or the SDK places it. They cover orders for Alpaca, TradeStation®, Saxo Bank, tastytrade, Kalshi, Polymarket and the brokers on the connection portal.
## Mandates
A mandate is a set of limits for one broker account, or for one strategy, in either the paper or the live environment. Click **New trading mandate** and set:
| Limit | What it stops |
|---|---|
| **Max order notional (USD)** | Any single order worth more |
| **Max order quantity** | Any single order for more units |
| **Max gross exposure (USD)** | An order that would take gross exposure above it |
| **Daily loss limit (USD)** | Further trading, once the day's realised loss reaches it |
| **Max orders per day** | Orders beyond the count |
| **Expires** | Everything, after the date: an expired mandate refuses its orders and its strategy's runs |
| **Symbol universe** | Any symbol not on the list |
| **Blocked symbols** | The symbols on the list |
A live mandate must set every limit and an expiry date.
**Commit and freeze terms** fixes the terms. A committed mandate cannot be edited; it can be suspended, resumed or revoked, and a change of terms is a new mandate. Each mandate's terms are hashed, and every decision is recorded in the mandate audit trail on the same tab.
An order that breaks a mandate is refused before it is sent, and the refusal appears under **Active Alerts**. When an order falls under both a strategy mandate and an account mandate, the strategy mandate applies.
The daily loss limit counts the day's realised profit and loss, and is checked when the next order arrives. When it is reached, CPZAI engages the trading halt for your account.
## The trading halt
**Halt trading** stops all new orders, on every account, and refuses strategy runs, scheduled or manual. It takes effect at once and is recorded in the audit trail.
The halt does not cancel orders already at your broker or close positions. Cancel or liquidate those from **Execution**: see [Order Management](/docs/execution/order-management).
**Release halt** asks you to confirm. A halt that CPZAI engaged for a daily loss breach is released only once the loss check passes.
# Alerts and Notifications
## What you are told about
| Event | Where you hear about it |
|---|---|
| An order refused by a mandate | **Active Alerts** in the Risk Lab |
| A reconciliation break: a position missing in CPZAI, or a quantity that differs from your broker's | Risk Lab, notifications and email |
| A strategy run that fails | Notifications, and email for errors |
| A scheduled strategy paused after three identical failures | Notifications |
| A fill in one of your accounts | A push notification in the iPhone and iPad app |
The bell in the top bar opens your notifications. **Settings → Notifications** also offers a **Daily Email Digest**, which can include a risk alerts section.
## Webhooks
To send events to your own systems, register a webhook through the [REST API](/docs/api/introduction) or the [agent tools](/docs/agent-integration/webhooks). The events are `order.placed`, `order.filled`, `strategy.executed`, `backtest.completed`, `risk.alert` and `news.breaking`.
## Limits are not alerts
An alert tells you after the fact. To stop an order before it is sent, set a [mandate](/docs/risk-management/mandates-and-halts).
# Prediction Markets and Mixed-Instrument Strategies
Open **Data Lab → Alternative → Prediction Markets** to browse Polymarket. Kalshi data is available only on the Demo exchange to approved internal accounts for integration testing; customer Demo access and production data remain unavailable pending approval. Demo prices are simulated, and Demo and production list different contracts. Powered by Kalshi Research.
Use **Event-driven strategy**, **Portfolio hedge**, or **Compare venues** to start a research brief with Simons. A prediction-market probability can be a signal for another instrument, or a separately sized contract alongside it. Research does not enable trading.
## Connect and identify each leg
Approved internal accounts can connect Kalshi Demo in **Settings → Connected Apps**, using the Demo API key ID and RSA private key. All Kalshi Live connections and order submission remain paused pending approval; customer Demo access is unavailable. Connect Polymarket using a Polygon EOA wallet; this connection does not support proxy/deposit wallets. Complete funding and approvals at Polymarket. Polymarket execution is live only.
Keep a separate CPZClient instance for each broker, and specify its environment explicitly. Kalshi uses **TICKER:YES** or **TICKER:NO**, with the price of that outcome in dollars. Polymarket uses the outcome's decimal trading ID, never the question or condition ID. Obtain that ID from current market metadata: positionIds for version v2, clobTokenIds for version v1, aligned with the requested outcome label.
## Strategy market data
The general CPZ prediction-data routes reject Kalshi raw-data redistribution with HTTP 403, including Demo. The dedicated **POST /functions/v1/cpz/kalshi/backtest-data** route serves only the Builder backtest source for approved internal accounts, reads the Demo exchange only, and cannot select production. It does not open general SDK/API/MCP Kalshi data access.
For Polymarket, use **data/events?provider=polymarket**, **data/markets?provider=polymarket**, or **data/bars?provider=polymarket&symbol=CONDITION_ID&outcome=YES&timeframe=1H**. Discovery does not require a stock symbol. Price history is sampled probability, not traded OHLCV: volume is unavailable. Do not treat a missing price or failed provider call as a zero signal.
Authenticate these requests using your CPZ API key with data scope. Orders require orders scope and a connected account. Data access alone grants no trading permission.
## Cross-venue execution
Use the execution gateway with an explicit broker, environment and account for each leg. Use limit orders and a persistent, unique client_order_id per intended leg. Reuse the ID after an uncertain response; inspect the gateway/order status before issuing another order.
Orders across Kalshi, Polymarket and a securities broker are **not atomic**. Confirm fills rather than counting an accepted order as a fill. Define how much unmatched exposure is allowed, when to cancel unfilled quantities, and what to do if only one leg fills. Stop on stale data, missing credentials, a trading halt or an unknown order outcome.
Before comparing contracts, check their resolution source, exact condition, expiry, fees and settlement currency. A probability difference alone does not establish arbitrage. Backtests require point-in-time market histories and resolution rules; today's market list cannot reconstruct expired contracts or remove survivorship bias. The **Kalshi** source in [Running Backtests](/docs/backtesting/running-backtests) tests contracts you name, and does not choose them for you.
Start with research and a broker's paper environment. Kalshi Demo integration testing is restricted to approved internal accounts; it does not grant production-data or customer-analysis rights. Treat Polymarket as a read-only signal until you deliberately enable and verify live execution.
# Paper vs. Live Trading
## Paper
A paper account is a simulated account at your broker: real market prices, simulated money. Alpaca (Paper), TradeStation® (SIM), Saxo Bank (SIM), tastytrade (Sandbox), Interactive Brokers (Paper) and Kalshi (Demo) offer one. Orders, positions and P&L in a paper account appear in CPZAI as a live account's do.
## Live
Live trading is switched on for each account by CPZAI, after the trial. Then:
1. Connect a live account. See [Connecting Your Broker](/docs/getting-started/connecting-your-broker).
2. Sign the live-trading acknowledgement. CPZAI asks for it before your first live order.
3. Set a live [mandate](/docs/risk-management/mandates-and-halts) for the account or the strategy. A live mandate must set every limit and an expiry date.
## Which account an order uses
There is no global paper or live switch: each connection belongs to one environment.
- On the **Execution** page, the order goes to the account selected at the top. Each account is marked **PAPER** or **LIVE**.
- A strategy trades in the account its code selects, for example `client.execution.use_broker("alpaca", environment="live")`.
- A one-click deployment always trades paper.
## Before going live
- Compare the strategy's paper results with its backtest over the same weeks.
- Start smaller than the backtest, with mandate limits to match.
- Know where **Halt trading** is: **Risk Lab → Mandates**.
# Running a Strategy on a Schedule
A strategy trades on its own once it has a schedule. There are two ways to give it one.
## One-click paper deployment
Click the rocket on a strategy in **Strategy Lab** ("Deploy to paper"), or **Home → Deploy → Deploy to Paper (1-click)**. It runs in the background:
1. checks the code for look-ahead,
2. picks your Alpaca paper account,
3. backtests the strategy over the last year if it has not been backtested, and checks the result,
4. works out a schedule from the code, for example once a day for daily bars, and starts it.
A one-click deployment always trades paper. **View monitor** opens its page: equity, exposure, unrealised P&L, 30-day Sharpe and drawdown, win rate, trades, its cadence, the latest fills and its log, with every run. To end it, choose **Stop** on its card.
## The Scheduler
In a strategy's details, the **Scheduler** tab runs it on a schedule you set:
- **Interval**: every so many minutes or hours.
- **Specific Times**: up to ten times a day, in the timezone you choose.
- **Cron**: a cron expression, in the timezone you choose.
Turn on **Enabled** and click **Save**. **Test** runs the strategy once, now, and **Logs** lists every run. In the strategy's details, **Paused** stops the schedule and **Active** starts it again.
## What every run passes
CPZAI refuses a run if trading is halted, if the strategy's mandate has expired, or if the month's spending cap has been reached. Every order the run places then passes the [pre-trade checks](/docs/execution/order-management). A run that fails is reported in your notifications, and by email when it is an error. Three identical failures in a row pause the strategy's schedule.
# Order Management
The **Execution** page places orders in the account selected at the top, and shows that account's positions and orders. Each account carries a **PAPER** or **LIVE** badge; the star marks your default.
## Placing an order
The **Buy/Sell** card on the **Stocks** tab:
- **Size** in shares or in dollars. A dollar amount becomes a fractional share quantity.
- **Order type:** Market, Limit, Stop, Stop Limit or Trailing Stop. Stop orders are not offered for crypto.
- **Time in force:** DAY, GTC, FOK, IOC, OPG or CLS.
- **Attach Orders:** a stop loss and a take profit, as a percentage or a price.
**Review Order** opens **Review your order** with the details and your buying power; confirm with **Buy** or **Sell**.
How attached orders behave depends on the broker. At Alpaca the stop loss and take profit are linked to the order, and need a whole number of shares. At other brokers they are placed as separate orders. Saxo Bank, tastytrade and Kalshi do not take trailing stops.
## Other markets
- **Options** (once options trading is approved for your account): single-leg orders, Market or Limit, DAY.
- **Futures** and **Forex**, through a connected TradeStation® account: Market, Limit, Stop and Stop Limit.
- **Polymarket**: Limit or Market.
- **Kalshi** (Demo accounts only, while Kalshi Live awaits approval): pick a market, Yes or No, Buy or Sell, a number of contracts and a limit price on that market's own price steps, as Kalshi lists them: most markets take whole cents from $0.01 to $0.99, and some take tenths of a cent near $0 and $1. A sell only reduces contracts you hold. On a Kalshi position, **Sell** opens this ticket on that market for the contracts held; Kalshi has no market orders or stops, so its positions have no Liquidate or exit strategy.
## Positions and orders
- **Top Positions** lists what the account holds. Each row can **Set exit strategy (stop loss / take profit)** or **Liquidate position**; select several rows to liquidate them together.
- **Recent Orders** lists orders with their status. Select open orders to cancel them, refresh from the broker, or open **View All**.
To change an order, cancel it and place a new one.
## Before an order is sent
CPZAI checks each order before it is sent: the trading halt, your [mandates](/docs/risk-management/mandates-and-halts), the limits of the deployment that placed it, and that the order itself makes sense (a symbol, a positive quantity, a valid limit price). A refused order is not sent, and the reason is shown. [Mandates and the Trading Halt](/docs/risk-management/mandates-and-halts) lists the brokers these checks cover.
## Stopping everything
**Halt trading** in **Risk Lab → Mandates** stops all new orders at once. It does not cancel open orders or close positions: cancel and liquidate those here.
# Performance Dashboards
The **Portfolio** page shows one connected account, or all of them, chosen at the top right. Each account is marked **LIVE** or **PAPER**.
## Summary
Portfolio Value, Cash, Buying Power, Daily Return, MTD, YTD, realised and unrealised P&L, and the Sharpe, Sortino and Calmar ratios, Max Drawdown, Skewness and Kurtosis of the account's returns. The refresh button pulls positions and P&L from your broker again.
The **Performance** chart plots a single account's value with no benchmark. **All Live**, **All Paper** and **All Accounts** (live and paper as two lines) draw each line rebased to 100, and on 1M, 1Y and All add the S&P 500 ETF (SPY), price return without dividends. A chart with one line has no benchmark.
## Accounts
Long and short exposure, sector and asset-class allocation, and each account's positions.
## Strategy Attribution
Profit, loss and metrics by strategy, searchable by strategy, account or symbol.
## Trades
The trade history: every trade in the account, with its side, quantity and price. Filter by symbol, side and date range, to see what traded yesterday or over any period, and sort as you choose.
## Daily P&L
Profit and loss day by day and cumulatively, per account or for all of them, exported as CSV.
# Simons AI Overview
Simons is your AI-native quantitative strategist, built into every module of CPZAI. Named to reflect its role as your strategic partner, Simons helps you research ideas, develop strategies, debug issues, and analyze performance - all through natural language conversation.
## What Simons Can Do
- **Research** - Ask about trading concepts, market dynamics, indicator logic, and strategy approaches.
- **Strategy development** - Describe what you want to build and Simons helps you create it.
- **Debugging** - Share an error or unexpected behavior and Simons walks you through the fix.
- **Analysis** - Ask Simons to explain your backtest results, identify weaknesses, or suggest improvements.
- **Education** - Learn about quantitative finance concepts, risk management frameworks, and best practices.
## Accessing Simons
Simons is available in multiple ways:
- **Dedicated page** - Navigate to **Simons** from the sidebar for a full-screen chat experience with conversation history.
- **Floating AI partner** - Press `Cmd+/` on any page to open the floating Simons chat without leaving your current workflow.
- **Builder integration** - Simons is embedded directly in the Builder IDE for inline coding assistance.
## Models and Intelligent Routing
Simons runs on the frontier model of your choice, or picks one for you:
- **CPZAI (Intelligent Routing)** - the default. Simple questions run on fast, low-cost models; standard work runs on Claude Opus 5.5, and the hardest research and strategy work is routed to Claude Fable 5.1, the most capable model available. You pay the rate of the model that answered, the provider's price plus 20%, never a flat premium.
- **Claude Fable 5.1** - the most capable model available, for the deepest research and strategy work (premium pricing).
- **Claude Opus 5.5** - the default single-model choice: frontier reasoning for complex agentic and enterprise work at standard premium pricing.
- **Claude Sonnet 5.5 and Haiku 5.5** - the current Sonnet and Haiku, for everyday work at lower cost and for the fastest answers.
- **Additional models** - the previous Claude generation (Fable 5, Opus 5, Sonnet 5 and earlier), OpenAI GPT-6 and GPT-5 families, Google Gemini, DeepSeek, Moonshot Kimi, and Perplexity Sonar for live web research. New provider releases appear in the picker automatically once the daily model sync finds them.
Change models anytime from the model selector in the chat header or in **Settings > Simons > AI Model**. Per-model token pricing for the models you can pick is on the [pricing page](https://ai.cpz-lab.com/#pricing); **Settings → Usage → Pricing Reference** lists every billed model.
## Slash Commands
Type `/` in the chat input to access quick commands. These shortcuts pre-fill your prompt for common workflows.
**Analysis & Research**
| Command | Description |
|---------|-------------|
| `/equity-research` | Generate a comprehensive equity research report on any public company |
| `/market-scan` | Scan sectors or themes with real-time quotes |
| `/factor-screen` | Screen stocks by quant factors - value, momentum, quality, low-vol |
| `/earnings-preview` | Preview upcoming earnings - expectations, historical patterns, positioning |
| `/options-analysis` | Analyze options strategies, Greeks, vol surface, and positioning |
| `/trade-idea` | Generate a structured trade idea with entry, exit, sizing, and risk management |
**Portfolio & Risk**
| Command | Description |
|---------|-------------|
| `/portfolio-review` | Full portfolio health check - positions, performance, diversification |
| `/risk-report` | Generate a risk report with VaR, Sharpe, drawdown |
| `/optimize` | Optimize portfolio allocation - mean-variance, Black-Litterman, risk parity, HRP |
**Strategy & Backtesting**
| Command | Description |
|---------|-------------|
| `/strategy-builder` | Guided strategy creation from a natural language idea |
| `/strategies` | List, view, and manage your trading strategies |
| `/backtest` | Run a backtest and analyze results |
**Trading & Data**
| Command | Description |
|---------|-------------|
| `/price` | Get real-time quotes - price, change, volume, bid/ask |
| `/trade` | Place a trade through your connected broker |
| `/positions` | View current portfolio positions with P&L and weights |
| `/orders` | View recent orders - open, filled, cancelled |
| `/sync` | Sync portfolio from all connected brokers |
## Conversation History
All your conversations with Simons are saved and searchable. Access your history from the Simons page sidebar to revisit past research, pick up where you left off, or reference earlier discussions.
# Ask, Agent, and Debug Modes
Simons operates in three distinct modes, each designed for a different type of interaction. You can switch modes at any time.
## Ask Mode (Default)
Ask mode provides **read-only assistance**. Simons can answer questions, explain concepts, analyze data, and provide recommendations - but it won't modify any of your code or configurations.
**Best for:**
- Learning about trading concepts and strategies.
- Getting explanations of your backtest results.
- Researching market dynamics and indicator logic.
- General questions about CPZAI.
## Agent Mode
Agent mode gives Simons the ability to **propose and apply code changes** directly to your strategies. When you enable Agent mode, Simons can write, modify, and refactor your code based on your instructions.
**Best for:**
- Building new strategies from natural language descriptions.
- Refactoring or optimizing existing code.
- Adding features like stop losses, position sizing, or new indicators.
- Making complex code changes that would take time to do manually.
**Important:** Always review Simons' proposed changes before accepting them. Agent mode shows you exactly what will change so you can approve or reject each modification.
## Debug Mode
Debug mode is a specialized troubleshooting flow. Share an error message, unexpected behavior, or failing backtest, and Simons guides you through **step-by-step diagnosis and resolution**.
**Best for:**
- Fixing errors in your strategy code.
- Understanding why a backtest produced unexpected results.
- Diagnosing connection or execution issues.
- Resolving operating system-related problems.
# Tips for Effective Prompts
The quality of Simons' responses depends heavily on the clarity and specificity of your prompts. Here are best practices for getting the most out of your AI strategist.
## Be Specific
Instead of vague requests, provide concrete details about what you want.
- **Vague:** "Help me with my strategy."
- **Specific:** "Add a trailing stop loss that activates after 3% profit and trails by 1.5%."
## Provide Context
The more context you give Simons, the better its response will be:
- What you're trying to achieve.
- What you've already tried.
- Specific problems or errors you're encountering.
- The type of strategy you're working on.
## Iterate Step by Step
Start simple and build up complexity:
1. Start with a clear, focused request.
2. Review the response and refine.
3. Add requirements incrementally rather than all at once.
## Effective Prompt Examples
- "Explain the difference between Sharpe ratio and Sortino ratio."
- "Why might a momentum strategy underperform in a choppy, range-bound market?"
- "What risk management rules should I consider for a mean reversion strategy?"
- "Review my backtest results - the Sharpe is 1.2 but max drawdown is 18%. Is that concerning?"
## When to Use Each Mode
- Use **Ask mode** when you want guidance, explanations, or analysis.
- Use **Agent mode** when you want Simons to write or modify code.
- Use **Debug mode** when something isn't working and you need to diagnose the issue.
# API-First Design
CPZAI is built on an API-first architecture. Every capability - from strategy management to execution to data access - is available through a well-defined API layer. The UI itself is a client of these same APIs.
## What API-First Means
**Every feature is an API.** The visual interface you use in the browser is built on top of the exact same APIs available to you programmatically. There is no hidden functionality reserved for the UI - everything you can do by clicking, you can do through code.
**Automation by default.** Because everything is API-accessible, you can automate any workflow: deploying strategies on a schedule, pulling performance data into your own dashboards, triggering alerts through external systems, or integrating CPZAI into your existing infrastructure.
**Extensible.** Build on top of CPZAI with your own tools, scripts, and integrations. Connect it to your existing stack - data pipelines, monitoring systems, communication tools, or custom dashboards.
## Python SDK
CPZAI provides an official Python SDK that wraps the CPZAI APIs into a clean, Pythonic interface. Install it from PyPI and interact with your strategies, data, and execution programmatically.
The SDK covers:
- **Strategy management** - Create, update, list, and deploy strategies.
- **Data access** - Pull market data, import datasets, and manage connections.
- **Execution** - Place orders, check positions, and monitor account status.
- **Backtesting** - Launch backtests and retrieve results programmatically.
- **Simons AI** - Send prompts and receive AI-generated analysis or code.
## No Black Boxes
We believe trading infrastructure should be transparent and controllable. You should be able to inspect, modify, and extend any part of your workflow. API-first design ensures you're never locked into a GUI-only workflow with no escape hatch.
# No Vendor Lock-In
CPZAI is designed with a core principle: **your work belongs to you**. Your strategies, data, configurations, and research are always exportable, portable, and under your control.
## Your Code, Your Way
Strategies in CPZAI are written in standard Python. There's no proprietary language, no custom DSL, no framework you can't use elsewhere. Your strategy code runs on standard Python with standard libraries - pandas, numpy, scipy, scikit-learn, and anything else you install.
If you ever leave CPZAI, your strategy code works anywhere Python runs. No translation, no rewriting, no migration headaches.
## Data Portability
- **Export anytime** - Download your strategies, backtest results, trade history, and performance data at any time.
- **Standard formats** - All exports use standard formats: Python files, CSV, JSON, Excel. No proprietary file formats.
- **Your imported data stays yours** - Data you upload is never used for anything other than your own strategies and analysis.
## Broker Independence
CPZAI's broker-agnostic architecture means you're not locked into a single brokerage. Connect multiple brokers, switch between them, or move to a different broker entirely - your strategies and system configuration remain unchanged.
## GitHub Sync
Connect your GitHub account to sync strategy code bidirectionally. Your code lives in your own repositories, under your own version control. CPZAI becomes a development environment for code that you own and host independently.
## Open Standards
We use open protocols and standard formats wherever possible:
- **Python** for strategy code.
- **Rust** for the HFT execution engine - sub-500ns latency, zero GC.
- **REST APIs** for programmatic access.
- **OAuth** for authentication integrations.
- **CSV/JSON/Parquet** for data import and export.
- **Git** for version control.
- **Nanosecond-precision time-series databases** for tick-level market data with full historical depth.
## Why This Matters
Vendor lock-in is a hidden cost. It starts small - a proprietary format here, a custom language there - and compounds until switching vendors means rebuilding everything from scratch. We've designed CPZAI to make that impossible. Your investment in strategies, research, and data is portable from day one.
# GitHub Sync
CPZAI integrates with GitHub to give you full version control over your strategy code. Connect your GitHub account, link repositories, and sync your strategies bidirectionally.
## Why GitHub Sync?
- **Version history** - Track every change to your strategies with full Git history. Roll back to any previous version at any time.
- **Collaboration** - Work with team members using standard Git workflows - branches, pull requests, and code review.
- **Backup** - Your code lives in your own GitHub repositories, giving you an independent backup outside of CPZAI.
- **CI/CD** - Integrate with GitHub Actions or other CI tools to automate testing, validation, or deployment workflows.
- **Portability** - Your strategies are always in your own repos, under your own account.
## Setting Up GitHub Sync
1. Go to **Settings** > **Connected Apps**.
2. Click **Connect GitHub**.
3. Authorize the CPZAI GitHub App.
4. Select which repositories to link (or create a new one).
5. Choose sync direction - push from CPZAI, pull from GitHub, or bidirectional.
## How It Works
Once connected, changes you make in the CPZAI Builder are automatically committed and pushed to your linked GitHub repository. Conversely, changes pushed to your GitHub repo are pulled into CPZAI and reflected in your strategy library.
## Conflict Resolution
When changes are made in both CPZAI and GitHub simultaneously:
- CPZAI detects conflicts automatically.
- You're presented with a diff view showing both versions.
- Choose which version to keep, or merge changes manually.
## Best Practices
- **Use branches** for experimental strategy changes, then merge to main when validated.
- **Tag releases** when a strategy passes backtesting and is promoted to paper or live trading.
- **Keep repositories focused** - one repository per strategy or strategy family.
# Data Control & Connections
CPZAI gives you complete control over your data - what comes in, how it's processed, where it goes, and who has access. Every data connection is explicit, auditable, and revocable.
## Data Sovereignty
**Your data stays yours.** Data you import into CPZAI is used solely for your strategies and analysis. It's never shared, aggregated, sold, or used to train models for other users.
**You control access.** Every data connection, API credential, and dataset access is under your explicit control. Revoke access or delete data at any time.
## Managing Data Connections
The Data page gives you a unified view of all data connections:
- **Active connections** - See every connected data provider, its status, and last sync time.
- **Credentials** - Manage API keys and tokens. Rotate or revoke credentials from one place.
- **Usage** - Monitor how much data you're consuming from each provider.
- **Health checks** - Automatic connection health monitoring with alerts when a connection fails.
## Connection Types
CPZAI supports multiple types of data connections:
- **Market data providers** - Real-time and historical price data for equities, futures, options, crypto, and forex.
- **Fundamental data** - Company financials, earnings, SEC filings, and economic indicators.
- **Alternative data** - Sentiment, news feeds, social media, satellite data, and other non-traditional sources.
- **Custom APIs** - Connect to any REST API endpoint and pull data into your strategies.
- **File imports** - Upload CSV, Excel, and JSON files for one-time or recurring data loads.
## Data Pipeline Control
Every step in your data pipeline is visible and configurable:
1. **Source** - Where the data comes from (API, file upload, manual entry).
2. **Ingestion** - How often data is refreshed (real-time, daily, on-demand).
3. **Storage** - Where your data lives and how long it's retained.
4. **Access** - Which strategies and notebooks can use each dataset.
5. **Export** - Download any dataset at any time in standard formats.
## Auditing
Every data access is logged. You can see exactly when a dataset was accessed, by which strategy, and what operations were performed. This is essential for compliance, debugging, and understanding your strategy's data dependencies.
# Performance & Infrastructure
CPZAI is engineered for professional-grade performance. From sub-microsecond execution to nanosecond-resolution data, CPZAI is built to meet the demands of serious systematic traders.
## HFT-Grade Execution Engine
CPZAI's execution engine is written in **Rust** - the same language used by leading high-frequency trading firms for its zero-cost abstractions, memory safety, and bare-metal performance.
- **Sub-500 nanosecond execution latency** - Orders are processed and routed at HFT-grade speeds.
- **Zero garbage collection pauses** - Rust's ownership model eliminates GC-related latency spikes that plague JVM and Python-based systems.
- **Deterministic performance** - Consistent, predictable latency under load. No surprises during volatile markets.
Whether you're running a strategy that trades once a day or one that needs to react in microseconds, the execution layer won't be your bottleneck.
## Nanosecond Data Resolution
CPZAI's data infrastructure supports **1-nanosecond resolution** - high-fidelity databases that capture every tick, every quote, every trade with precise timestamps.
- **Tick-level market depth** - Full order book data with nanosecond timestamps.
- **No data aggregation artifacts** - When you need raw, unaggregated data, it's there.
- **Time-series optimized** - Purpose-built storage for financial time-series data with fast range queries and point-in-time lookups.
## 20+ Years of Historical Data
Backtest your strategies against more than two decades of historical market data:
- **Tick-level depth** - Not just OHLCV bars, but full tick data where available.
- **Multiple asset classes** - Equities, futures, options, crypto, and forex.
- **Split-adjusted prices** - Bars are adjusted for stock splits. They are not adjusted for dividends or spin-offs, so a spin-off shows as a one-day drop in the parent's price.
- **Point-in-time accuracy** - Data reflects what was actually known at each historical moment, avoiding look-ahead bias.
## Performance at a Glance
| Metric | Value |
|--------|-------|
| Execution latency | <500 nanoseconds |
| Data resolution | 1 nanosecond |
| Historical data depth | 20+ years |
| Time to first backtest | ~5 minutes |
| Deployment speed | 90% faster vs. traditional setup |
## 90% Faster Deployment
The traditional workflow for deploying a systematic trading or investing strategy involves stitching together multiple tools - a research environment, a backtesting framework, a risk system, an execution layer, and monitoring. CPZAI unifies all of these, cutting deployment time by 90% compared to building and maintaining a custom stack.
## Always-On AI
Simons, your AI quantitative strategist, is available **24/7** across every module. It doesn't sleep, doesn't take breaks, and has instant recall of your entire strategy library and conversation history.
## 5-Minute First Backtest
New users can go from account creation to their first backtest results in approximately 5 minutes. Choose a template, configure parameters, and run - CPZAI handles the rest.
# Client Data Privacy Architecture
CPZAI is built on the principle that your data belongs to you - and only you.
## Privacy Stack
| Layer | What It Does | Status |
|---|---|---|
| **Encryption at Rest** | Strategy code encrypted in your browser with AES-256-GCM before storage. The encryption key is generated in your browser; a copy is held by our servers so scheduled runs can decrypt your code when your browser is offline. | **Live** |
| **Confidential Compute Enclave** | A confidential enclave isolates execution in hardware-attested memory. The host cannot inspect enclave contents. Only the verified enclave image can unwrap keys to decrypt strategy code. | **Available on Request** |
| **Tamper-Evident Record** | Every fill, daily snapshot and reconciliation result is hash-chained as it is recorded, each day is signed, and the archive is write-once. CPZ attests to the record, not the strategy. | **Live** |
| **Zero-Knowledge Proofs** | Proving a claim about a record without revealing the record. | **Not offered** |
## What We Protect
- **Strategy source code** - encrypted in your browser before it reaches our database. A copy of your encryption key is held on our servers so scheduled runs and Simons tools can decrypt your code when your browser is offline. See [Confidential Compute](#confidential-compute-on-request) for a hardware-isolated alternative.
- **Trades and positions** - isolated per user at the database level
- **Broker credentials** - encrypted at rest, decrypted only at execution time
- **Personal data** - never exposed in cross-user analytics
## How Encryption Works
Your encryption key is generated once per user via the Web Crypto API and stored only in your browser. When you save a strategy, the code is encrypted with AES-256-GCM before leaving the browser. The database stores only ciphertext. When you load a strategy, it is decrypted locally in your browser.
CPZAI (the service operator) holds a copy of your encryption key and can decrypt your strategy code from the database when needed to run scheduled executions or operate Simons tools. We do not read your code for any other purpose. To remove this server-side copy entirely, request a confidential-compute enclave deployment - see [Confidential Compute](#confidential-compute-on-request).
## Confidential Compute (On Request)
For clients who require execution-time privacy, CPZAI offers confidential compute enclave deployment. The enclave is an isolated environment colocated with the compute host - the host cannot inspect enclave memory, attach debuggers, or read ciphertext-only traffic in place of execution. Strategy material is encrypted such that only the attested enclave can decrypt. Contact us to enable this for your account.
## What This Means for You
- **All users**: Your strategies are encrypted at rest - we cannot read them from our database.
- **Enterprise / institutional**: Confidential enclave execution available on request for hardware-isolated execution with cryptographic attestation.
- **Compliance teams**: The architecture is auditable and meets regulatory requirements for data isolation.
For security inquiries or audit requests, contact [security@cpz-lab.com](mailto:security@cpz-lab.com).
# Client-Side Encryption at Rest
Your strategy code is the most sensitive data in CPZAI. We encrypt it in your browser before it ever reaches our servers.
## How It Works
- Strategy code is encrypted using AES-256-GCM authenticated encryption - entirely in your browser.
- The database stores only ciphertext for your strategy code fields.
- When you load a strategy, it is decrypted locally in your browser using your key.
- A unique encryption key is auto-generated per user. No passphrase, no friction.
- Your key is generated in your browser. We keep a server-side copy so scheduled runs work when your browser is offline. To avoid the server-side copy, request a confidential-compute enclave deployment.
- You can export your key for backup or cross-device transfer from Settings.
## What Stays Unencrypted
Metadata required for operating system operations remains readable: strategy name, description, scheduling, and performance metrics. Your proprietary logic - the actual Python code - is always encrypted.
## Performance
Negligible. AES-256-GCM is hardware-accelerated on modern processors. Encrypt/decrypt takes under 1 millisecond.
# AI Providers & Your Strategy Code
Simons is served by multiple AI providers so an outage or a rate limit never becomes your outage. Transparency about what each provider can see:
## The provider set
Chat turns may be served by: Anthropic, OpenAI, Google, DeepSeek, Moonshot, Perplexity, or CPZAI's own self-hosted model running on CPZ-controlled infrastructure. Your model picker controls the preference; automatic failover uses this set. Requests are sent under CPZAI's API agreements; we do not use providers' consumer products.
## The strategy-code allowlist
Turns that carry your strategy code in context - Agent mode, Debug mode, and any turn that ships your editor contents - are restricted to an allowlist: **Anthropic, OpenAI, and CPZAI's self-hosted model only**. If your selected model is outside that list on a code-bearing turn, the turn is served by the platform default instead (and this is logged, never silent). Failover on code-bearing turns never leaves the allowlist either.
## One caveat, stated plainly
In Ask mode on a model outside the allowlist, if you explicitly ask Simons to read one of your strategy files mid-conversation, that content flows to the model you chose - the provider cannot be switched mid-turn. Choose your Ask-mode model with that in mind, or work on code in Agent mode where the allowlist always applies.
# Platform Improvement Data
CPZAI improves its models and defaults using operational metrics - never your content.
## What is used
- Backtest outcome **metrics** (Sharpe, deflation statistics, certification grades) from runs on the platform, keyed for de-duplication.
- Aggregate, strategy-blind platform statistics (median performance by category, fill quality percentiles).
## What is never used
- Your strategy **code** is never exported into any training corpus. Model training on code uses only CPZAI's own platform-authored demo strategies. This exclusion is structural and enforced by automated tests in our build, not by policy alone.
- Your datasets, positions, conversations, and research artifacts are never used for training.
# Confidential Compute Enclave
Encryption at rest protects your code in the database. For execution-time privacy, CPZAI offers confidential compute enclave deployment.
**Status: Available on Request** - contact us to enable for your account.
## What It Means
A confidential compute enclave is an isolated execution environment paired with the compute server. The host cannot access it like a normal workload: it cannot read enclave memory, inject debuggers, or trace processes inside the boundary. Only code running inside the attested enclave image can decrypt strategy material; keys are cryptographically bound to that verified image.
This is enforced with hardware isolation and attestation - not operational policy alone.
## What You Get
- **Hardware-isolated execution** - strategy code is decrypted and executed inside the enclave only. The host sees only ciphertext.
- **Cryptographic attestation** - encryption keys unwrap only inside a verified enclave image. Even the infrastructure operator cannot decrypt outside that boundary.
- **Per-user isolation** - each strategy runs in its own subprocess with enforced resource limits, separate filesystem, and memory zeroing after execution.
- **Outputs only** - order signals, P&L, and risk metrics are returned. Source code never leaves the enclave.
## Without the Enclave
All users benefit from client-side AES-256-GCM encryption at rest. A copy of the encryption key is held on our servers so scheduled runs can decrypt your code when your browser is offline; the operator can decrypt your stored strategy code for that purpose. During standard execution, code is decrypted in your browser and sent to the compute server over HTTPS. Confidential-compute enclave deployment removes the server-side key copy entirely.
## Performance
The enclave adds less than 30ms overhead per execution (local IPC + attestation). Negligible compared to typical strategy runtimes of seconds to minutes.
# Zero-Knowledge Proofs (ZKPs)
**Status: not offered.** CPZAI does not run zero-knowledge proofs. What CPZAI does attest is a hash-chained, signed, write-once record of what a strategy tested and what it traded; a published record is re-checked in the reader's browser from the stored bytes. The rest of this article describes a design, not a live feature.
A zero-knowledge proof lets you prove a statement is true without revealing the data behind it. The design below would use ZKPs for specific, bounded claims - verifiable compliance and performance reporting without exposing proprietary strategy details.
## Where ZKPs Apply
ZKPs are not suited for general-purpose computation (running arbitrary Python strategies would be millions of times slower). However, they excel at proving specific, narrow claims:
| Proof Type | What It Proves | What Stays Hidden |
|---|---|---|
| **Risk compliance** | Portfolio VaR is below threshold X | Actual positions and allocations |
| **Performance attestation** | Strategy achieved Sharpe > 2 over period Y | Strategy logic and trade history |
| **Order validity** | Order is within pre-set risk limits | The actual risk limits |
| **Execution quality** | All fills were at or better than signal price | Signal generation logic |
## How It Works
1. The compute server calculates the relevant metric (VaR, Sharpe, etc.) during or after strategy execution.
2. A ZK circuit generates a compact cryptographic proof of the claim.
3. The proof is published alongside the claim.
4. Anyone can verify the proof in milliseconds - without access to the underlying data.
## Performance
- **Proof generation**: 1-10 seconds per proof (runs asynchronously, never in the trading hot path).
- **Proof verification**: Milliseconds.
- **Trading latency impact**: None - proofs are generated post-hoc.
## Implementation
CPZAI uses industry-standard ZK proof systems for browser-verifiable proofs alongside server-side proof generation for more complex claims.
## Use Cases
**For institutional compliance**: Generate provable risk reports for regulators without disclosing proprietary positions.
**For fund marketing**: Prove track record claims (Sharpe ratio, max drawdown, returns) to prospective investors with cryptographic certainty.
**For operational trust**: Users can independently verify that CPZAI executed their strategy correctly without trusting the operator.
## Limitations
ZKPs are designed for bounded, deterministic computations. They cannot prove arbitrary Python execution. For general strategy confidentiality, CPZAI relies on hardware confidential computing (see [Confidential Computing](/docs/security-privacy/confidential-computing)).
# Privacy Architecture Overview
CPZAI's data privacy architecture is built in three layers, each reinforcing the others.
## Layer 1: Client-Side Encryption at Rest
**Status: Live**
- Strategy code encrypted in the browser using AES-256-GCM authenticated encryption.
- Database stores only ciphertext - the operator cannot read your strategies.
- Per-user encryption key auto-generated. No passphrase required.
- Metadata (name, schedule, type) remains readable for operating system operations.
**Impact**: High trust signal. Immediately addresses the concern of "you can see our strategies in your database."
## Layer 2: Confidential Compute Enclave
**Status: Available on Request**
- Confidential compute enclaves isolate execution in hardware-attested memory the host cannot inspect.
- Attestation-bound keys ensure only the verified enclave image can decrypt strategy code.
- Per-user isolation ensures no cross-user data access.
- Only execution outputs (signals, metrics) are returned.
**Impact**: Full IP protection with hardware guarantees. Available for enterprise and institutional accounts.
## Layer 3: Selective Zero-Knowledge Proofs
**Status: Live**
- Risk compliance proofs: "portfolio VaR is below threshold."
- Performance attestation: "Sharpe ratio > X over period Y."
- Execution quality proofs: "all fills at or better than signal price."
- Proofs generated post-hoc - no impact on trading latency.
**Impact**: Verifiable claims without data disclosure. Regulatory and investor reporting without IP exposure.
## Performance Summary
| Approach | Overhead | Trading Latency Impact | Status |
|---|---|---|---|
| Client-side encryption at rest | Negligible | None | Live |
| Confidential compute enclave | <30ms | Sub-ms (typical) | On Request |
| Selective ZK proofs | Seconds per proof | None (async) | Live |
## Competitive Position
CPZAI is one of the only quant SaaS systems that encrypts strategy code client-side with the key never touching the server. For institutional clients requiring execution-time privacy, confidential enclave execution provides hardware-attested isolation available on request.
# Pricing Plans
## Research Seat: $249 a month
- Simons, with Ask, Agent and Debug.
- The Strategy Lab, the Builder and backtesting.
- The Data Lab, including the screener and the [data that comes with every account](/docs/data-integrations/connecting-data-providers).
- The Risk Lab, Portfolio and Execution, with broker connections for paper and live trading.
- The Python SDK, the CLI and the REST API.
- 2 GB of storage and five strategies (support can raise the strategy limit).
- $40 of usage each month: one allowance shared by Simons and compute.
Simons is billed at the rate of the model that answered: the provider's price plus a 20% platform margin. Every model's rate is listed in **Settings → Usage → Pricing Reference**. In each calendar month (UTC), only the part of Simons and compute usage together that goes above $40 is billed, on your next invoice.
**Settings → Usage** shows your usage as it happens:
- **AI Spending** shows Simons against your monthly AI limit, $20 unless support raises it. Simons stops at that limit.
- **Overage Protection** stops Simons at $20 and compute at $20, so together they stay within the $40 included and nothing is billed beyond your subscription.
- **Monthly Spending Cap** sets a ceiling on Simons and compute together.
## Institutional
For firms, priced per engagement. Write to [contact@cpz-lab.com](mailto:contact@cpz-lab.com).
## Free trial
The Research Seat starts with a 14-day free trial when you sign up with your email address. A payment method is required, and you are charged only if you keep the subscription after the trial; a trial without a valid payment method ends by itself. During the trial, live trading is off and Simons and compute share a $5 allowance.
## Managing your subscription
**[Settings → Subscription](https://ai.cpz-lab.com/settings?tab=subscription)** shows your plan and your billing. **Manage billing** opens the billing portal, for your payment method, invoices and receipts.
## Cancelling
1. Open **[Settings → Subscription](https://ai.cpz-lab.com/settings?tab=subscription)**.
2. Click **Cancel** and confirm.
Your subscription stays active until the end of the period you have paid for. After that the account is closed and your data is kept. To reopen it, write to [support@cpz-lab.com](mailto:support@cpz-lab.com). To take a copy of your data first, use **Settings → Security & Privacy → Export Data**.
# Account Settings
Open **Settings** from the account menu at the top right.
| Tab | What it holds |
|---|---|
| **Profile** | Your name and profile |
| **Simons** | How Simons works with you |
| **Security & Privacy** | Two-factor authentication, your password, and **Export Data** |
| **API Keys** | Keys for the REST API and the Python SDK. See [Authentication](/docs/sdk/authentication) |
| **OAuth Apps** | Applications you have authorised to act for you |
| **Integrations** | Brokers and GitHub |
| **Connectors** | Services Simons can use in a conversation |
| **Notifications** | What CPZAI emails you, including the daily digest |
| **Subscription** | Your plan, billing and payment method |
| **Usage** | AI and compute usage, the **Monthly Spending Cap** and **Overage Protection** |
| **Referrals** | Your referral code, once your subscription is paid |
| **Advanced** | Account deletion |
Data provider connections are in **Data Lab**, not in Settings: see [Connecting Data Providers](/docs/data-integrations/connecting-data-providers).
# Open Source: cpz-quant
CPZAI runs an open-core model. The **research layer** is open source and independently auditable; the **execution layer** (broker routing, FIX connectivity, market data entitlements, the hosted operating system) stays proprietary.
- Package: [cpz-quant on PyPI](https://pypi.org/project/cpz-quant/)
- Source: [github.com/CPZ-Lab/cpz-quant](https://github.com/CPZ-Lab/cpz-quant) (Apache-2.0)
- Library docs: [cpz-lab.github.io/cpz-quant](https://cpz-lab.github.io/cpz-quant/)
```bash
pip install cpz-quant
```
## Why it is open
A certification standard nobody can inspect is just marketing. Every grade CPZAI issues is computed by math you can read, reproduce, and challenge: Probability of Backtest Overfitting via combinatorially symmetric cross-validation, and the Deflated Sharpe Ratio, which discounts a Sharpe ratio for the number of trials that produced it.
## What is in it
| Area | Contents |
|---|---|
| Optimization | Mean-variance, min-variance, max-Sharpe, risk parity, HRP, HERC, NCO, Schur complementary, Black-Litterman, entropy pooling, mean-CVaR, robust MVO, max diversification, min tracking error, turnover-penalized, alpha-risk-cost |
| Risk measures | 17 measures including CVaR, EVaR, CDaR, EDaR, drawdown-at-risk, Ulcer index, Gini mean difference |
| Covariance | Ledoit-Wolf, Oracle Approximating Shrinkage, EWMA, factor model, Marchenko-Pastur denoising, detoning, Gerber |
| Model selection | Walk-forward and combinatorial purged cross-validation, grid search, optional scikit-learn estimators |
| Certification | PBO (CSCV), Deflated Sharpe gates, regime-conditional performance, factor attribution |
| Convex backend | Exact cvxpy programs: cardinality constraints (mixed-integer), robust box and ellipsoidal uncertainty sets, CVaR linear programming |
| Acceleration | Rust crate (PyO3 + rayon) with parity-tested NumPy fallbacks |
Every allocator accepts a Polars DataFrame, a pandas DataFrame, or a plain `{asset: [returns]}` dict.
## Example
```python
import polars as pl
from cpz_quant.portfolio import hierarchical_risk_parity, WalkForward, cross_validate
from cpz_quant.certification import probability_of_backtest_overfitting
returns = pl.DataFrame({"AAPL": [...], "MSFT": [...], "TLT": [...]})
hrp = hierarchical_risk_parity(returns)
print(hrp.weights, hrp.sharpe_ratio)
# Validate out of sample before trusting anything
cv = cross_validate(
lambda train: hierarchical_risk_parity(train).weights,
returns,
cv=WalkForward(n_splits=4, test_size=63),
)
print(cv.oos_sharpe, cv.stability())
```
## How it relates to cpz-ai
The proprietary `cpz-ai` SDK depends on `cpz-quant` and re-exports it, so `cpz.portfolio`, `cpz.certification` and `cpz.indicators` continue to work unchanged. Research locally with the open library; add `cpz-ai` when you want live multi-broker execution and platform data.
## AI transparency
Parts of cpz-quant are developed with Simons, the AI research partner of the CPZAI operating system, under CPZ Lab review. AI-authored commits carry the git identity `Simons ` so provenance is auditable commit by commit, in line with the transparency expectations of the EU AI Act, the NIST AI Risk Management Framework, and ISO/IEC 42001.
# Python SDK Quickstart
The CPZAI Python SDK wraps the REST API in a clean, Pythonic interface. Available on [PyPI](https://pypi.org/project/cpz-ai/).
## 1. Install
```bash
pip install cpz-ai
```
Optional extras:
```bash
pip install cpz-ai[risk] # Risk analytics (numpy, scipy)
pip install cpz-ai[all] # All optional dependencies
```
**Requires Python 3.10 or later.** On Python 3.9, pip quietly installs cpz-ai 4.24.1, an old release that lacks most of what these docs describe.
## 2. Set Your Credentials
```bash
export CPZ_AI_API_KEY="cpz_key_..."
export CPZ_AI_API_SECRET="cpz_secret_..."
```
## 3. Make Your First Call
```python
from cpz import CPZClient
client = CPZClient()
# Get market data
bars = client.data.bars("AAPL", timeframe="1D", limit=10)
# Ask Simons AI
response = client.simons.chat("What's the current trend on AAPL?")
print(response.content)
```
## What's Next
- [Authentication](/docs/sdk/authentication) - Credential management and auth patterns.
- [Data](/docs/sdk/data) - Market data, economic data, news, options, and indicators.
- [Execution](/docs/sdk/execution) - Place orders, manage positions, multi-broker routing.
- [Risk](/docs/sdk/risk) - VaR, Monte Carlo, stress testing, portfolio optimization.
- [Simons](/docs/sdk/simons) - AI Partner for analysis and code generation.
- [HFT Engine](/docs/sdk/engine) - Deploy to the Rust execution engine.
- [CLI](/docs/sdk/cli) - Command-line interface.
## Links
- **PyPI**: [pypi.org/project/cpz-ai](https://pypi.org/project/cpz-ai/)
- **API Reference**: [REST API docs](/docs/api/introduction)
# Authentication
Every request to the CPZAI API requires authentication via an API key and secret.
## Getting Your Credentials
1. Log in to CPZAI at [ai.cpz-lab.com](https://ai.cpz-lab.com).
2. Go to **Settings** > **API Keys**.
3. Click **Create API Key**.
4. Copy your **API Key** and **Secret Key** - the secret is only shown once.
## Credential Format
| Credential | Format | Example |
|-----------|--------|---------|
| API Key | `cpz_key_` + 24 hex characters | `cpz_key_a1b2c3d4e5f6...` |
| Secret Key | `cpz_secret_` + 48 alphanumeric characters | `cpz_secret_x9y8z7...` |
## Environment Variables
The SDK reads credentials from environment variables by default:
```bash
export CPZ_AI_API_KEY="cpz_key_..."
export CPZ_AI_API_SECRET="cpz_secret_..."
```
| Variable | Required | Description |
|----------|----------|-------------|
| `CPZ_AI_API_KEY` | Yes | Your API key |
| `CPZ_AI_API_SECRET` | Yes | Your API secret |
| `CPZ_AI_STRATEGY_ID` | For orders | Default strategy ID for order attribution |
| `CPZ_AI_BASE_URL` | No | Override API base URL |
## Initializing the Client
```python
from cpz import CPZClient
# From environment variables (recommended)
client = CPZClient()
# From explicit keys
client = CPZClient.from_keys(
api_key="cpz_key_...",
secret_key="cpz_secret_..."
)
```
## Async Client
For async/await workflows:
```python
from cpz import AsyncCPZClient
client = AsyncCPZClient()
bars = await client.data.bars("AAPL", timeframe="1D", limit=10)
```
## Security Best Practices
- **Never hardcode credentials** in source code. Always use environment variables or a secrets manager.
- **Rotate keys regularly** from Settings > API Keys.
- **Use separate keys** for development and production.
- **Revoke compromised keys** immediately from the dashboard.
# Data API
The Data API provides access to market data, economic indicators, news, options, SEC filings, sentiment, and technical indicators across multiple asset classes and providers.
**Built-in market data**: every CPZAI account can fetch US equity bars, quotes, trades, and news out of the box through CPZAI's built-in Alpaca feed: IEX for recent data, and the consolidated SIP feed for daily backtest windows and for anything before 2020-07-27. No broker connection or data subscription is required to start backtesting. Trades and news need cpz-ai 4.41.0 or later. To read another vendor's feed, connect it and pass `provider=`.
## Market Data
```python
# Price bars (OHLCV)
bars = client.data.bars("AAPL", timeframe="1D", limit=100)
# The same bars for a date range, for one symbol or several
history = client.data.history(
symbols=["AAPL", "MSFT", "GOOGL"],
start="2024-01-01",
end="2024-12-31",
timeframe="1D"
)
# Real-time quotes
quotes = client.data.quotes(["AAPL", "MSFT", "GOOGL"])
# Tick-level trades
trades = client.data.trades("AAPL", limit=500)
```
`start` and `end` take a datetime or an ISO date such as `"2024-12-31"`. An end written as a date includes that whole day: the example above ends on the December 31 bar. A datetime is exact, so `datetime(2024, 12, 31)` stops before that day's bar.
**Which data you get**: `bars` and `history` return the same bars, split-adjusted and not adjusted for dividends or spin-offs. When a daily, weekly or monthly request gives an end date, as backtests do, the bars come from the consolidated SIP feed (full-market volume) and hold completed periods only: a session counts as complete from 20:30 New York time, a week once its Friday is, a month once its last weekday is. A window with an end date that starts after the last completed period is refused with an error rather than answered empty. Without an end date, and for every intraday request, a window starting on or after 2020-07-27 comes from the IEX exchange feed, so its volume is IEX's share of the market and the session in progress is included; an earlier window comes from SIP. The response says which feed answered and the end it served (`feed`, `bars_end`, `completed_sessions_only`). `bars.attrs["cpz_source"]` says which feed answered (cpz-ai 4.41.0 or later).
## Timeframes
| Value | Description |
|-------|-------------|
| `1Min`, `5Min`, `15Min`, `30Min` | Intraday bars |
| `1H`, `4H` | Hourly bars |
| `1D` | Daily bars |
| `1W` | Weekly bars |
| `1Month` | Monthly bars |
The SDK reads a timeframe without regard to case, so `1M` means one minute, not one month. Ask for monthly bars with `1Month`.
## Economic Data
```python
# US macro series, newest first
ten_year = client.data.cpz("us_macro", symbol="DGS10")
unemployment = client.data.cpz("us_macro", symbol="UNRATE", limit=24)
cpi = client.data.cpz("us_macro", symbol="CPIAUCSL", start="2020-01-01")
# Every series CPZ carries, with its coverage
series = client.data.cpz("us_macro")
```
US Treasury yields, the fed funds rate, the Fed balance sheet, money stock, industrial production, jobless claims, unemployment, payrolls and CPI, stored from the agencies that publish them: the Treasury, the Federal Reserve Board, BLS and the Department of Labor. `client.data.cpz("us_macro")` lists every series with its coverage and whether it is available. Every result carries its source and the notices the agency requires in `df.attrs["cpz_warehouse"]["attribution"]`. Needs cpz-ai 4.17.0 or later.
## Treasury Bills
```python
# Every bill maturing in a month, on a day, or sold at a tenor
april = client.data.cpz("treasury_bills", query="April 2027")
one_day = client.data.cpz("treasury_bills", query="April 15 2027")
outstanding_6m = client.data.cpz("treasury_bills", term="26-Week", status="outstanding")
# One bill by CUSIP: a row per auction
auctions = client.data.cpz("treasury_bills", symbol="912797UE5")
# The bill rate table on a day, one row per tenor
rates = client.data.cpz("treasury_bills", on_date="2026-04-15")
```
Every individual US Treasury bill since 1979 by CUSIP and maturity date, with its auction results (discount rate, investment rate, price, bid-to-cover, amounts), and Treasury's Daily Treasury Bill Rates by tenor since 2002. You can also find any bill in the platform search bar by typing its CUSIP or a date such as "T-bill April 15 2027", in Data Lab under Macro & Economic > Treasury Bills, or by asking Simons.
Rates are auction results and Treasury's closing rate for the on-the-run bill of each tenor, not a secondary-market quote for a CUSIP. Bills cannot be ordered through CPZ.
## News
```python
news = client.data.news(symbols=["AAPL"], limit=20)
```
## Options
`client.data.options` reads chains from SpiderRock, so it needs a SpiderRock connection (**Data Lab → My Data → API Connections**). Without one it raises an error that says so (cpz-ai 4.41.0 or later), rather than returning an empty list.
```python
chains = client.data.options(
underlying="AAPL",
expiration="2025-01-17",
option_type="call"
)
```
## SEC Filings
```python
filings = client.data.filings("AAPL", form="10-K", limit=5)
```
## My Data
```python
# Every dataset and file in Data Lab, with the same keys on both kinds of row
items = client.data.datasets()
# One of them, by name or id, as a DataFrame
df = client.data.load("my_prices")
```
`load` reads CSV and JSON files and saved datasets. Use `format="text"` for a text or Markdown file. An image or PDF raises an error; read those with `client.download_file("user-data", storage_path)`. When two items share a name, the error lists their ids so you can load one by id.
## Technical Indicators
Indicators are computed by Twelve Data, so they need a Twelve Data connection (**Data Lab → My Data → API Connections**). Without one, each call raises an error that says so.
```python
sma = client.data.sma("AAPL", period=20)
rsi = client.data.rsi("AAPL", period=14)
macd = client.data.macd("AAPL")
bbands = client.data.bbands("AAPL", period=20)
```
Available indicators: SMA, EMA, RSI, MACD, Bollinger Bands, Stochastic, ADX, ATR, OBV, VWAP, Ichimoku, and more.
## Data Providers
The SDK supports multiple data providers. Specify a provider or let the SDK choose the best one automatically:
- **Alpaca** - US equities, crypto, news
- **SpiderRock** - Option chains, greeks, volatility surfaces (`client.data.options`)
- **Twelve Data** - Global equities, forex, crypto, technical indicators
- **US agencies** (Treasury, Federal Reserve Board, BLS, Department of Labor) - US macro series
- **SEC EDGAR** - Corporate filings
- **Massive** (formerly Polygon) - US equities, crypto, option reference data (`client.data.massive`)
- **Alpha Vantage** - Global equities, forex
- **Databento** - Institutional-grade tick data
- **Polymarket** - Prediction markets
# Execution API
The Execution API lets you place orders, manage positions, and route trades to multiple brokers - all with strategy attribution for tracking and analysis.
## Connect a Broker
```python
client.execution.use_broker("alpaca", environment="paper")
```
Supported brokers: Alpaca (`alpaca`), Interactive Brokers (`ibkr`), TradeStation, tastytrade, Saxo, Kalshi, Polymarket, and the brokers reached through SnapTrade, such as `schwab` and `fidelity`. Paper trading uses a broker's own paper environment, as in the example above. The SDK has no built-in CPZAI paper broker: `use_broker("cpz_paper")` raises `BrokerNotRegistered`.
## Place an Order
```python
order = client.execution.order(
symbol="AAPL",
qty=10,
side="buy",
order_type="market",
strategy_id="my-strategy"
)
```
## Order Types
| Type | Description |
|------|-------------|
| `market` | Execute immediately at best available price |
| `limit` | Execute at specified price or better |
| `stop` | Trigger when market reaches stop price |
| `stop_limit` | Stop trigger with limit execution |
## Order Parameters
| Parameter | Type | Description |
|-----------|------|-------------|
| `symbol` | string | Ticker symbol |
| `qty` | number | Number of shares |
| `notional` | number | Dollar amount (alternative to qty) |
| `side` | "buy" or "sell" | Order direction |
| `order_type` | string | market, limit, stop, stop_limit |
| `time_in_force` | string | DAY, GTC, IOC, FOK |
| `limit_price` | number | Limit price (for limit orders) |
| `strategy_id` | string | Strategy attribution |
## Manage Positions
```python
# Get account info
account = client.execution.get_account()
# List open positions
positions = client.execution.get_positions()
# Check a specific order
order = client.execution.get_order(order_id)
# Cancel an order
client.execution.cancel_order(order_id)
```
## Strategy Attribution
Every order can be tagged with a `strategy_id`. This links the order to a specific strategy in your CPZAI portfolio, enabling:
- Per-strategy P&L tracking
- Strategy-level risk analytics
- Performance attribution across your portfolio
# Risk API
The Risk API provides institutional-grade risk analytics - Value at Risk, Monte Carlo simulation, stress testing, factor analysis, and portfolio optimization.
## Quick Risk Snapshot
```python
snapshot = client.risk.compute(
daily_returns=returns_df,
benchmark_returns=spy_returns,
position_weights={"AAPL": 0.3, "MSFT": 0.3, "GOOGL": 0.4}
)
```
Returns a comprehensive `RiskSnapshot` with VaR, Sharpe, drawdown, beta, alpha, and more.
## Individual Metrics
```python
sharpe = client.risk.sharpe(daily_returns)
sortino = client.risk.sortino(daily_returns)
vol = client.risk.volatility(daily_returns)
mdd = client.risk.max_drawdown(daily_returns)
beta = client.risk.beta(daily_returns, benchmark_returns)
alpha = client.risk.alpha(daily_returns, benchmark_returns)
```
## Value at Risk
```python
# Parametric VaR (95% confidence)
var_p = client.risk.parametric_var(daily_returns, confidence=0.95)
# Historical VaR
var_h = client.risk.historical_var(daily_returns, confidence=0.99)
```
## Monte Carlo Simulation
```python
mc = client.risk.monte_carlo(
daily_returns=returns_df,
num_simulations=10000,
horizon_days=252
)
```
Rust-accelerated for performance - 10,000 simulations run in milliseconds.
## Stress Testing
```python
# Every built-in scenario, by name: {"COVID-19 Crisis": ..., "2008 Financial Crisis": ..., ...}
results = client.risk.stress_test_all(weights)
print(results["2008 Financial Crisis"]) # portfolio impact in %
# A scenario of your own: shocks are fractional moves by asset class
from cpz import StressScenario
rates_up = StressScenario(name="Rates up", equity_shock=-0.10, bond_shock=-0.08)
impact = client.risk.stress_test(weights, rates_up)
```
`stress_test` takes a `StressScenario`, not a name. The built-in scenarios are COVID-19 Crisis, 2008 Financial Crisis, Dot-com Bubble, Geopolitical Tensions 2022, Black Monday 1987, Euro Debt Crisis and Flash Crash 2010. In cpz-ai 4.41.1 and earlier, each shock is applied to the size of a position whether it is long or short, so a short counts as a loss when the market falls. Read the results for a book with shorts with that in mind.
## Portfolio Analytics
```python
# Drawdown analysis
dd = client.risk.drawdown_analysis(daily_returns)
# Rolling metrics
rolling = client.risk.rolling_metrics(daily_returns, window=60)
# Tail risk
tail = client.risk.tail_risk(daily_returns)
# Factor exposure
factors = client.risk.factor_exposure(daily_returns, factor_returns)
# Position sizing
size = client.risk.position_size(portfolio_value=100000, daily_returns=returns_df)
# Correlation risk
corr_risk = client.risk.correlation_risk(return_series)
```
## Portfolio Optimization
```python
from cpz import optimize_portfolio, euler_decomposition, mctr
# returns_df: daily returns, one column per strategy or asset
optimal = optimize_portfolio(returns_df, method="min_variance")
weights = optimal.weights # {name: weight}
# Both take the weights first and a covariance matrix second, as {name: {name: covariance}}
cov = (returns_df.cov() * 252).to_dict()
decomp = euler_decomposition(weights, cov)
print(decomp.portfolio_volatility, decomp.contributions)
marginal = mctr(weights, cov)
print(marginal.mctr)
```
## Quantum-Inspired Portfolio Optimization
```python
from cpz.portfolio import quantum_inspired_hrp, qubo_portfolio_selection
# Quantum-Inspired HRP (1QBit, 2016), classical execution
# Benchmark on your own data; no quantum advantage is claimed.
result = quantum_inspired_hrp(returns, backend="heuristic")
print(result.weights) # {asset: weight}
# QUBO cardinality-constrained asset selection
result = qubo_portfolio_selection(returns, target_k=5)
print(result.selected_assets)
# For local gate-model QAOA simulation:
# pip install cpz-ai[quantum-braket]
# from cpz.portfolio import BraketSolver
# solver = BraketSolver(device="local", seed=7)
# result = quantum_inspired_hrp(returns, solver=solver)
```
# Quantum Portfolio Optimization
CPZAI provides quantum-inspired and quantum-ready portfolio optimization through the CPZ SDK. Two algorithms are available:
- **Quantum-Inspired HRP (QHRP)** - optimises asset permutation via QUBO to minimise information loss during hierarchical bisection
- **QUBO Portfolio Selection** - cardinality-constrained asset selection via binary optimisation
Both run classically by default. The Braket adapter provides experimental local QAOA simulation on CPU. Paid managed simulators and physical QPUs are disabled pending budget enforcement and cost reconciliation. No quantum advantage is claimed.
## Quantum-Inspired HRP
Based on the 1QBit white paper (Alipour, Adolphs, Zaribafiyan, Rounds, 2016). Published research results are not a CPZAI benchmark or a guarantee of future performance.
**Algorithm:**
1. Build similarity matrix A_ij = |C_ij| from the covariance matrix
2. Find the permutation that makes A as quasi-block-diagonal as possible (QUBO with N² binary variables)
3. Recursively split the reordered assets at the point minimising off-diagonal coupling
4. Allocate weights inversely proportional to cluster variance
```python
from cpz.portfolio import quantum_inspired_hrp
# Classical heuristic (default - no extra dependencies)
result = quantum_inspired_hrp(returns, backend="heuristic")
# Exact solution only for very small N: permutation QUBO has N² variables
result = quantum_inspired_hrp(returns, backend="brute_force")
# Simulated annealing (pip install cpz-ai[quantum])
result = quantum_inspired_hrp(returns, backend="simulated_annealing")
# Local QAOA simulation (pip install cpz-ai[quantum-braket])
from cpz.portfolio import BraketSolver
solver = BraketSolver(
device="local", seed=7, shots=1000, max_iter=50,
)
result = quantum_inspired_hrp(returns, solver=solver)
```
## QUBO Portfolio Selection
Selects the best K assets from a larger universe by solving a combinatorial optimisation problem.
```python
from cpz.portfolio import qubo_portfolio_selection
result = qubo_portfolio_selection(
returns,
target_k=5, # Select 5 assets
risk_aversion=0.5, # Balance risk vs return
budget_penalty=10.0, # Penalty for violating cardinality
)
print(result.selected_assets) # ['AAPL', 'MSFT', ...]
print(result.weights) # Equal-weight among selected
```
## Building Custom QUBOs
Export the raw QUBO matrix for custom workflows or direct submission to quantum hardware:
```python
from cpz.portfolio import build_portfolio_qubo, build_permutation_qubo
# Binary portfolio selection QUBO
Q = build_portfolio_qubo(mu, cov, target_k=3)
# Permutation QUBO for QHRP
Q = build_permutation_qubo(similarity_matrix)
```
## Backend Tiers
| Backend | Install | Hardware | Best for |
|---------|---------|----------|----------|
| heuristic | `pip install cpz-ai` | Classical CPU | Production default |
| brute_force | `pip install cpz-ai` | Classical CPU | Small universes (N ≤ 20) |
| simulated_annealing | `pip install cpz-ai[quantum]` | Classical CPU | Testing QUBO formulations |
| braket | `pip install cpz-ai[quantum-braket]` | Braket LocalSimulator on CPU | Research only; ≤20 binary variables |
## Experimental boundary
The local simulator needs no AWS account and incurs no Braket provider fee; it still consumes local CPU resources. CPZAI does not currently operate a physical-QPU execution service. Uploaded benchmark costs are caller-reported evidence and cannot charge a CPZ account.
AWS Tracker amounts are estimates, not settled invoices. Any future paid backend must reserve budget before submission and reconcile failed, cancelled and completed tasks. See [AWS cost tracking](https://docs.aws.amazon.com/braket/latest/developerguide/braket-pricing.html).
# Simons API
Interact with Simons AI programmatically. Ask questions, analyze strategies, generate code, and get market insights - all from your Python scripts and notebooks.
## Chat
```python
response = client.simons.chat("What factors drive tech stock volatility?")
print(response.content)
```
## Modes
| Mode | Description |
|------|-------------|
| `ask` | Read-only analysis and guidance (default) |
| `agent` | Simons can propose code changes |
| `debug` | Step-by-step troubleshooting |
```python
response = client.simons.chat(
message="Add a trailing stop loss to my momentum strategy",
mode="agent",
strategy_id="my-strategy"
)
```
## Streaming
For real-time responses:
```python
for chunk in client.simons.stream("Analyze my portfolio risk exposure"):
print(chunk.content, end="", flush=True)
```
## Memory
Simons remembers your preferences and context:
```python
# Get current memory
memory = client.simons.get_memory()
# Update preferences
client.simons.update_memory(
preferences={"risk_tolerance": "moderate", "preferred_assets": "US equities"}
)
```
## Response Object
Every chat response includes:
| Field | Description |
|-------|-------------|
| `content` | The response text |
| `token_usage` | Tokens consumed (prompt + completion) |
| `code_proposals` | Proposed code changes (agent mode) |
| `model` | AI model used |
# HFT Engine API
Deploy your strategies to CPZAI's Rust-powered HFT execution engine for sub-microsecond order processing and low-latency live trading.
## Check Engine Status
```python
status = client.engine.status()
```
## Deploy a Strategy
```python
client.engine.deploy(
strategy_id="my-strategy",
symbols=["AAPL", "MSFT"],
broker="alpaca",
environment="paper",
strategy_type="momentum",
params={"lookback": 20, "threshold": 0.02},
risk={"max_position_pct": 0.1, "stop_loss_pct": 0.02}
)
```
## Stop a Strategy
```python
client.engine.stop(strategy_id="my-strategy")
```
## Monitor Running Strategies
```python
# List all running executors
executors = client.engine.list_executors()
# Get a specific executor's status
status = client.engine.get_executor()
```
## Engine Capabilities
- **Sub-500ns order processing** - Rust-powered execution with zero garbage collection.
- **Real-time signal processing** - Continuously evaluates market data against your strategy logic.
- **Built-in risk controls** - Position limits, stop losses, and exposure caps enforced at the engine level.
- **Multi-symbol** - Run strategies across multiple symbols simultaneously.
- **Paper and live modes** - Test on paper before deploying to live markets.
# CLI Reference
The CPZAI CLI provides command-line access to broker management, order execution, and operating system operations.
## Installation
The CLI is included with the Python SDK:
```bash
pip install cpz-ai
```
## Broker Management
```bash
# List available brokers
cpz-ai broker list
# Select a broker
cpz-ai broker use alpaca --env paper --account-id YOUR_ID
```
## Order Execution
```bash
# Submit an order
cpz-ai order submit --symbol AAPL --side buy --qty 10 --type market --strategy-id my-strat
# Get order status
cpz-ai order get --id ORDER_ID
# Cancel an order
cpz-ai order cancel --id ORDER_ID
# Replace an order
cpz-ai order replace --id ORDER_ID --qty 20
```
## Positions
```bash
# View open positions
cpz-ai positions
```
## Market Data Streaming
```bash
# Stream real-time quotes
cpz-ai stream quotes --symbols AAPL,MSFT --broker alpaca --env paper
```
## Platform Health
```bash
# Check platform status
cpz-ai platform health
# Echo test
cpz-ai platform echo
```
# Install the CLI
The CLI brings Simons, your portfolio, your strategies and your brokers into the terminal. It also works against the files on your own machine, which the web app cannot do.
## macOS and Linux
```
curl -fsSL https://cli.cpz-lab.com/install.sh | bash
```
Installs a self-contained binary to `~/.cpzai/bin`. No Node runtime required, no sudo. Apple silicon, Intel, x86_64 and arm64 are all covered; the installer detects your platform and verifies the download against a published checksum before installing.
## Windows
There is no native Windows build yet. Open [WSL](https://learn.microsoft.com/windows/wsl/install) (Windows Subsystem for Linux) and run the macOS and Linux command above: it installs the Linux build. The npm package this page used to name, `@cpzai/cli`, was never published, so `npm install -g @cpzai/cli` fails.
## Homebrew (macOS)
```
brew install --cask cpz-lab/tap/cpzai
```
The cask can trail the latest release. Check what you got with `cpzai --version`; the install script always installs the latest release.
## Sign in
Just run it:
```
cpzai
```
On first run it asks you to sign in, opens your browser, and carries straight on into the session. The token goes into your system keychain and renews itself, so you are not asked again.
Signing in with an API key is an option in that same prompt. Generate a pair under Settings, API Keys.
# Update the CLI
```
cpzai upgrade
```
That is all you need. It works out how the CLI was installed and takes the matching path: an npm install runs npm, a standalone binary re-downloads and verifies its checksum, and a linked development build is told to pull rather than being overwritten.
## You are told when an update is out
The CLI checks for a newer version and shows one line on the banner when there is one:
```
▲ Update available: v0.1.2 run cpzai upgrade
```
The check is bounded to a short timeout and cached for six hours, so it never delays startup and never fails a session. If you are offline it stays quiet.
## Restart running sessions
A running session keeps the version it launched with, because it loads its code at startup. After upgrading, exit and start `cpzai` again, or you will keep seeing the old behaviour.
## Checking what you have
```
cpzai --version
```
## Reinstalling
Re-running the installer is always safe. It replaces the binary in place and verifies the checksum first:
```
curl -fsSL https://cli.cpz-lab.com/install.sh | bash
```
# CLI commands
Anything that is not a command is a message to Simons.
## From your shell
| Command | What it does |
|---------|--------------|
| `cpzai` | Start an interactive session |
| `cpzai -p "..."` | Ask one question and exit |
| `cpzai upgrade` | Update to the latest version |
| `cpzai broker status` | Every broker and whether it is healthy |
| `cpzai connect` | Add a broker, data provider or repository |
| `cpzai connect ibkr` | Use Trader Workstation running on this machine |
| `cpzai --add-dir ~/research` | Let it work on another directory |
## Inside a session
| Command | What it does |
|---------|--------------|
| `/effort` | Fast, Balanced, Thorough or Max |
| `/model` | Pick a model, or leave it on the router |
| `/env` | Switch paper and live. Live asks first |
| `/portfolio`, `/positions` | Holdings, P&L and weights |
| `/orders` | Recent orders with status and fills |
| `/strategies` | Your strategies and their status |
| `/broker` | Brokers and accounts |
| `/connections` | Everything connected |
| `/dir` | Directories the agent may touch |
| `/help` | The full list |
Ctrl-C cancels a running turn. Ctrl-D exits.
## Local files
Simons can read, search, edit and run code in the directory you started in. Paths cannot escape the directories you granted, credential stores are refused outright, and writes and shell commands ask before running.
# API Introduction
CPZAI has two HTTP APIs. Both take the same key and secret and apply its scopes; all requests use JSON.
| API | Base URL | Serves |
|---|---|---|
| CPZ gateway | `https://api-ai.cpz-lab.com/cpz` | The endpoint pages in this section; the Python SDK's default |
| REST adapter | `https://api.cpz-lab.com/functions/v1/rest-api/v1` | The [Middle Office API](/docs/middle-office/middle-office-api), positions, webhooks and the rest of the [OpenAPI document](https://api-ai.cpz-lab.com/cpz/openapi.json); the CPZAI plugin calls it |
`api.cpz-lab.com/rest/v1` (the database) and `api.cpz-lab.com/functions/v1/middle-office` (the app's own path, which needs a signed-in session) do not take an API key.
## Authentication
Both APIs take two headers:
| Header | Value |
|--------|-------|
| `X-CPZ-Key` | Your API key (`cpz_key_...`) |
| `X-CPZ-Secret` | Your API secret (`cpz_secret_...`) |
The REST adapter also takes `Authorization: Bearer cpz_key_....cpz_secret_...` (key, dot, secret) or an OAuth access token.
Get your credentials from **Settings** > **API Keys** in the CPZAI app.
## Request Format
All request bodies use JSON with `Content-Type: application/json`.
## Response Format
Successful responses return JSON with the relevant data. Error responses include an `error` field with a message and error code.
| HTTP Code | Meaning |
|-----------|---------|
| `200` | Success |
| `201` | Created |
| `400` | Bad request - check parameters |
| `401` | Unauthorized - invalid or missing credentials |
| `403` | Forbidden - insufficient permissions |
| `404` | Not found |
| `429` | Rate limit exceeded |
| `500` | Server error |
## API Key Scopes
Each API key can be scoped to specific resources. Configure scopes when creating a key in **Settings** > **API Keys**:
| Scope | Access |
|-------|--------|
| `strategies` | Create, read, update, delete strategies |
| `data` | Fetch market data, economic data, news |
| `storage` | Upload, download, manage files |
| `orders` | Submit, cancel, replace orders |
| `trading credentials` | Access broker connections |
| `data connections` | Manage data provider connections |
| `simons` | Chat with Simons AI |
Use the **principle of least privilege** - only grant the scopes each key actually needs.
## Rate Limits
API requests are rate-limited per account. If you exceed the limit, you'll receive a `429` response. Implement exponential backoff for retries.
## Prefer the SDK?
The [CPZ Python SDK](/docs/sdk/quickstart) wraps all these endpoints in a clean Pythonic interface. Install with `pip install cpz-ai`.
# Error Codes
All API errors return a JSON body with an `error` field.
| Error | HTTP Code | Description |
|-------|-----------|-------------|
| `authentication_error` | 401 | Invalid or missing API key/secret |
| `authorization_error` | 403 | Insufficient permissions for this action |
| `not_found` | 404 | Resource does not exist |
| `validation_error` | 400 | Invalid request parameters |
| `rate_limit_error` | 429 | Too many requests - retry with backoff |
| `broker_error` | 502 | Broker connection or execution failure |
| `server_error` | 500 | Internal server error |
## Retry Strategy
For `429` and `500` errors, use exponential backoff (1s, 2s, 4s). For `401`/`403`, check your credentials.
# List Strategies
`GET /strategies`
Returns all strategies for the authenticated user, including the full Python source code in the `code` field. Requires the `strategies` scope.
| Parameter | Type | Description |
|-----------|------|-------------|
| `limit` | integer | Max results (default 100) |
| `offset` | integer | Pagination offset |
## Response Fields
| Field | Type | Description |
|-------|------|-------------|
| `id` | string | Strategy UUID |
| `name` | string | Strategy name |
| `description` | string | Strategy description |
| `code` | string | Full Python source code |
| `universe` | array | Trading symbols |
| `template_id` | string | Base template ID (if created from template) |
| `status` | string | Current status (draft, backtested, paper, live) |
| `created_at` | string | ISO 8601 timestamp |
| `updated_at` | string | ISO 8601 timestamp |
# Retrieve Strategy
`GET /strategies?id=eq.{strategy_id}`
Returns a single strategy by ID, including the full Python source code in the `code` field. Requires the `strategies` scope.
## Path Parameters
| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| `strategy_id` | string (UUID) | **Yes** | The strategy ID to retrieve |
## Response
Returns the full strategy object including `id`, `name`, `description`, `code`, `universe`, `status`, `created_at`, and `updated_at`.
# Create Strategy
`POST /strategies`
Creates a new strategy. The `user_id` is automatically set from your API key. Requires the `strategies` scope.
## Request Body
| Field | Type | Required | Description |
|-------|------|----------|-------------|
| `name` | string | **Yes** | Strategy name |
| `description` | string | No | Strategy description |
| `code` | string | No | Full Python source code |
| `universe` | array | No | Trading symbols (e.g. `["AAPL", "MSFT"]`) |
| `template_id` | string | No | Base template ID to clone from |
## Response
Returns the created strategy object with all fields, including the generated `id`.
# Update Strategy
`PATCH /strategies?id=eq.{strategy_id}`
Updates an existing strategy. Send only the fields you want to change. Ownership is enforced - you can only update your own strategies.
## Path Parameters
| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| `strategy_id` | string (UUID) | **Yes** | The strategy ID to update |
## Request Body
Send only the fields you want to update:
| Field | Type | Description |
|-------|------|-------------|
| `name` | string | New strategy name |
| `description` | string | New description |
| `code` | string | Updated Python source code |
| `universe` | array | Updated trading symbols |
## Response
Returns the updated strategy object.
# Delete Strategy
`DELETE /strategies?id=eq.{strategy_id}`
Permanently removes the strategy. This action cannot be undone. Ownership is enforced.
## Path Parameters
| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| `strategy_id` | string (UUID) | **Yes** | The strategy ID to delete |
## Response
Returns `{"success": true}` on success.
# Execute Order
`POST /orders/execute`
Places a real order through your connected broker (Alpaca). The order is executed immediately and recorded in your order history.
Requires both the `orders` AND `trading credentials` scopes.
## Required Flow
1. Call `GET /trading_credentials_private` to list your connected broker accounts
2. Pick an `account_id` from the response
3. Call `POST /orders/execute` with that `account_id` + order details
| Field | Type | Required | Description |
|-------|------|----------|-------------|
| `account_id` | string | Yes | Broker account ID (from trading credentials) |
| `symbol` | string | Yes | Ticker symbol (e.g. "AAPL") |
| `qty` | number | Yes* | Number of shares |
| `notional` | number | Yes* | Dollar amount (alternative to qty) |
| `side` | string | Yes | `buy` or `sell` |
| `type` | string | No | `market` (default), `limit`, `stop`, `stop_limit` |
| `time_in_force` | string | No | `day` (default), `gtc`, `ioc`, `fok` |
| `limit_price` | number | For limit | Limit price |
| `stop_price` | number | For stop | Stop trigger price |
| `strategy_id` | string | No | Strategy attribution |
*Provide either `qty` or `notional`, not both.
## Response
| Field | Type | Description |
|-------|------|-------------|
| `success` | boolean | Whether the order was accepted |
| `order.id` | string | Broker order ID |
| `order.symbol` | string | Symbol |
| `order.side` | string | buy or sell |
| `order.status` | string | Order status (accepted, filled, etc.) |
| `order.filled_avg_price` | number | Fill price (if filled) |
## OAuth Accounts
For Alpaca OAuth accounts, use `oauth-paper` or `oauth-live` as the `account_id`.
## Idempotency
Pass an `Idempotency-Key` header (any unique string, e.g. a UUID) to make order submission safe to retry. If the same key is submitted twice, the original order is returned instead of a duplicate being placed. The CPZAI web app and SDK set this automatically.
# Record Order
`POST /orders`
Records an order in the database for tracking and attribution. This does **not** execute through a broker - use [Execute Order](/docs/api-orders/execute-order) for real execution.
Useful for recording orders placed through external systems or the SDK.
| Field | Type | Required | Description |
|-------|------|----------|-------------|
| `symbol` | string | Yes | Ticker symbol |
| `qty` | number | Yes* | Number of shares |
| `notional` | number | Yes* | Dollar amount |
| `side` | string | Yes | `buy` or `sell` |
| `type` | string | Yes | `market`, `limit`, `stop`, `stop_limit` |
| `strategy_id` | string | No | Strategy attribution |
| `account_id` | string | No | Broker account ID |
*Provide either `qty` or `notional`, not both.
# List Orders
`GET /orders`
Returns all orders for the authenticated user.
| Parameter | Type | Description |
|-----------|------|-------------|
| `status` | string | Filter: `open`, `closed`, `all` |
| `limit` | integer | Max results |
# Retrieve Order
`GET /orders?id=eq.{order_id}`
Returns a single order by ID.
## Path Parameters
| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| `order_id` | string (UUID) | **Yes** | The order ID to retrieve |
## Response Fields
| Field | Type | Description |
|-------|------|-------------|
| `id` | string | Order UUID |
| `order_id` | string | Broker-assigned order ID |
| `symbol` | string | Ticker symbol |
| `side` | string | buy or sell |
| `order_type` | string | market, limit, stop, stop_limit |
| `quantity` | number | Ordered quantity |
| `filled_quantity` | number | Filled quantity |
| `average_fill_price` | number | Average fill price |
| `status` | string | pending, submitted, filled, cancelled, failed |
| `strategy_id` | string | Attributed strategy |
| `account_id` | string | Broker account used |
| `environment` | string | paper or live |
| `submitted_at` | string | ISO 8601 timestamp |
# Update Order
`PATCH /orders`
Update an existing order record.
## Request Body
| Field | Type | Required | Description |
|-------|------|----------|-------------|
| `id` | string (UUID) | **Yes** | Order ID to update |
| `status` | string | No | New status |
| `filled_qty` | number | No | Filled quantity |
| `filled_avg_price` | number | No | Average fill price |
**Note:** Order cancellation is handled through your broker's API via the SDK's `client.execution.cancel_order()` method.
# List Datasets
`GET /data_files`
Returns all dataset records for the authenticated user. Requires the `data` scope.
# Create Dataset
`POST /data_files`
| Field | Type | Required | Description |
|-------|------|----------|-------------|
| `name` | string | Yes | Dataset name |
| `type` | string | No | File type (csv, json, parquet) |
| `source` | string | No | Data source or provider |
| `description` | string | No | Dataset description |
| `symbols` | array | No | Associated ticker symbols |
# Update Dataset
`PATCH /data_files?id=eq.{id}`
Send only the fields you want to update.
# Delete Dataset
`DELETE /data_files?id=eq.{id}`
Removes the dataset record. Use the [Storage API](/docs/api-storage/list-files) for file management.
# Fetch Bars
`POST /data/bars`
| Field | Type | Required | Description |
|-------|------|----------|-------------|
| `symbol` | string | Yes | Ticker symbol |
| `timeframe` | string | Yes | `1Min`, `5Min`, `15Min`, `1H`, `1D`, `1W`, `1M` (one month here; `1Month` also works) |
| `start` | string | No | Start date (ISO 8601) |
| `end` | string | No | End date (ISO 8601) |
| `limit` | integer | No | Max bars (default 100) |
Returns an array of OHLCV bars with timestamps.
# Fetch Quotes
`POST /data/quotes`
| Field | Type | Description |
|-------|------|-------------|
| `symbols` | array | Array of ticker symbols |
Returns latest bid/ask quotes for each symbol.
# Fetch News
`POST /data/news`
| Field | Type | Description |
|-------|------|-------------|
| `symbols` | array | Ticker symbols to filter |
| `limit` | integer | Max articles (default 20) |
# Chat
`POST /simons/chat`
Requires the `simons` scope.
| Field | Type | Required | Description |
|-------|------|----------|-------------|
| `message` | string | Yes | Your message |
| `mode` | string | No | `ask` (default), `agent`, `debug` |
| `strategy_id` | string | No | Strategy context |
| `model` | string | No | AI model preference |
**Response:**
| Field | Type | Description |
|-------|------|-------------|
| `content` | string | Simons' response |
| `token_usage` | object | Tokens consumed |
| `code_proposals` | array | Proposed code changes (agent mode) |
# Stream
`POST /simons/stream`
Same parameters as Chat. Returns a server-sent events (SSE) stream.
Each chunk:
| Field | Type | Description |
|-------|------|-------------|
| `content` | string | Partial response text |
| `done` | boolean | Whether streaming is complete |
# Get Memory
`GET /simons/memory`
Returns Simons' stored preferences and context for the authenticated user.
# Update Memory
`POST /simons/memory`
| Field | Type | Description |
|-------|------|-------------|
| `preferences` | object | Key-value preferences |
| `facts` | object | Key-value facts about the user |
# List Files
`GET /storage/object/list/{bucket}`
| Parameter | Type | Description |
|-----------|------|-------------|
| `prefix` | string | Filter by path prefix |
| `limit` | integer | Max results (default 100) |
# Download File
`GET /storage/object/{bucket}/{path}`
Returns the file contents. Supports CSV, JSON, Parquet, and other formats.
# Upload File
`POST /storage/object/{bucket}/{path}`
Upload a file using multipart form data or raw body.
| Header | Value |
|--------|-------|
| `Content-Type` | File MIME type |
# Delete File
`DELETE /storage/object/{bucket}/{path}`
Permanently removes the file from storage.
# Health Check
`GET https://api-ai.cpz-lab.com/health`
Returns API status. No authentication required. This route sits outside the `/cpz` base the other endpoints use; `/cpz/health` asks for API keys like every `/cpz` route.
Response: `{"status": "ok"}`
# Trading Credentials
`GET /trading_credentials_private`
Returns stored broker credentials for the authenticated user. Read-only. Requires the `trading credentials` scope.
Automatically merges Alpaca OAuth-connected accounts alongside manually configured broker credentials.
# Data Credentials
`GET /data_credentials`
Returns stored data provider API keys (Alpaca data, Twelve Data, etc.). Requires the `data connections` scope.
Broker credentials (`alpaca_api_key`, `alpaca_api_secret`, `alpaca_oauth_token`) are only returned to keys that also carry the `trading credentials` scope. Alpaca issues one key pair for both market data and trading, so those values can place live orders.
# Data Connections
`GET /data_connections`
Returns active data provider connections and their status.
# Metadata
`GET /metadata`
Returns the tables your API key has access to (based on scopes) along with column names for each table.
# Agent Integration
Connect CPZAI to an AI agent for strategy management, market data, portfolio and risk review, trading, and middle-office reporting. MCP, the REST API, the Python SDK, and the Claude plugin expose different interfaces; their tool sets are not identical.
## MCP connection
**Endpoint:** `https://mcp.cpz-lab.com/mcp`
**Transport:** stateless Streamable HTTP
**Documented release:** 1.5.0: 39 tools, two guide resources, and two workflow prompts. This catalog describes the updated server source. Use your connected client's `tools/list`, `resources/list`, and `prompts/list` to confirm what its deployment currently exposes.
In a client that supports remote MCP connections with custom headers, use:
```json
{
"mcpServers": {
"cpzai": {
"url": "https://mcp.cpz-lab.com/mcp",
"headers": {
"X-CPZ-Key": "YOUR_KEY",
"X-CPZ-Secret": "YOUR_SECRET"
}
}
}
}
```
Create credentials in **Settings → API Keys**. The example is a remote MCP configuration shape; use your client's equivalent connector settings if it does not accept this file format.
OAuth-capable clients can connect to the same endpoint and complete sign-in using OAuth with PKCE (S256). Discovery is at `https://mcp.cpz-lab.com/.well-known/oauth-authorization-server`; protected-resource metadata is at `https://mcp.cpz-lab.com/.well-known/oauth-protected-resource`. Opaque MCP OAuth tokens are accepted by the MCP server; do not forward them directly to the platform REST endpoint. Passing a raw key and secret in a Bearer token is disabled by default.
## Available tools
| Domain | Tool | Purpose |
|---|---|---|
| Strategies | `list_strategies` | status, strategy_type, title |
| Strategies | `get_strategy` | Read one strategy |
| Strategies | `create_strategy` | Create a strategy |
| Strategies | `update_strategy` | Update selected strategy fields |
| Overlays | `list_overlays` | Every overlay strategy with its hedge policy and targets |
| Overlays | `get_overlay_exposure` | What an overlay hedges, its hedge ratio, and a suggested rebalance |
| Overlays | `configure_overlay` | Set or remove an overlay's policy and targets |
| Backtests | `get_backtest_results` | List saved backtest runs |
| Backtests | `get_backtest_result` | Read one saved backtest run |
| Orders | `list_orders` | status, symbol, side, strategy_id |
| Orders | `get_order` | Read one CPZ order record |
| Orders | `place_order` | Submit an order to the selected connected account |
| Portfolio | `list_positions` | account_id, symbol |
| Portfolio | `sync_portfolio` | Synchronize connected broker portfolios |
| Portfolio | `export_transactions` | Export an account's transactions for a date range |
| Accounts | `list_accounts` | broker, environment, tradable |
| Accounts | `cash_summary` | Cash balances across your connected accounts |
| Watchlist | `list_watchlist` | Read your watchlist |
| Watchlist | `add_to_watchlist` | Add a symbol to your watchlist |
| Watchlist | `remove_from_watchlist` | Remove a symbol from your watchlist |
| Connections | `list_connections` | connection_type |
| Connections | `create_connection` | Store an encrypted provider credential |
| Data files | `list_data_files` | name, status, file_type |
| Data files | `get_data_file` | Read uploaded file metadata |
| Market data | `get_market_data` | Fetch current quotes for symbols |
| Market data | `get_bars` | Fetch historical OHLCV bars |
| Risk | `compute_risk` | Compute and store a fresh risk snapshot |
| Risk | `list_risk_snapshots` | account_id |
| Risk | `get_risk_snapshot` | Read one saved risk snapshot |
| Execution | `execute_strategy` | Execute strategy code; may place orders |
| Middle office | `list_deals` | entity_id, fund, book, deal_id, product_kind, status |
| Middle office | `list_lifecycle_events` | entity_id, deal_id, kind, status, from, to |
| Middle office | `list_cash_flows` | entity_id, deal_id, fund, book, from, to, unsettled |
| Middle office | `list_journal_entries` | entity_id, deal_id, from, to |
| Middle office | `list_fund_periods` | entity_id, fund, status |
| Webhooks | `list_webhooks` | active |
| Webhooks | `create_webhook` | Subscribe an HTTPS endpoint to events |
| Webhooks | `delete_webhook` | Remove a webhook subscription |
| Profile | `get_profile` | Read your authenticated profile |
List filters above are optional. Single-record tools require a CPZ record UUID returned by a corresponding list tool. `get_data_file` reads stored metadata; it does not download or sample a dataset. `get_backtest_results` and `get_backtest_result` read saved runs; neither launches a backtest.
## Pagination and history
All list tools accept `limit` (integer 1–100) and `offset` (nonnegative integer, default 0). This includes accounts, strategies, orders, positions, connections, data files, backtest results, risk snapshots, webhooks, and middle-office lists. The default limit is 50, except `list_accounts`, which defaults to 100. Use an explicit limit when paging and the sorting fields advertised by the connected tool schema.
For a portfolio with more than 100 positions, call `list_positions` with `{"limit":100,"offset":0}`, then `{"limit":100,"offset":100}`, continuing until a page has fewer than the requested limit. REST `count` is the number in that page, not a total portfolio count. Do not calculate portfolio-wide totals from one full page.
Example historical data arguments for `get_bars`:
```json
{
"symbols": ["AAPL", "MSFT"],
"timeframe": "1Day",
"start": "2026-08-01",
"end": "2026-08-31",
"limit": 1000,
"feed": "iex"
}
```
Supported timeframes: `1Min`, `5Min`, `15Min`, `30Min`, `1Hour`, `2Hour`, `4Hour`, `1Day`, `1Week`, `1Month`. Historical `limit` is 1–10,000 bars per symbol. `feed` selects `iex` or `sip` for Alpaca; availability depends on connected providers and their entitlements.
## Permissions and execution
The REST API validates the underlying API key, user scope, subscription access, and resource permissions on calls. Strategy and backtest tools require `strategies`; data files, quotes, bars, and connections require `data`; accounts require `trading_credentials`; orders and strategy execution require `orders`. Positions, risk, portfolio sync, and middle-office reads accept either `trading_credentials` or `orders`. Webhooks accept a supported platform resource scope. Legacy `read`, `write`, and `trade` keys are mapped by the REST API to its resource scopes. Tool annotations describe side effects; they are not an authorization boundary.
Use `list_accounts` to select the account and inspect its `environment` and `tradable` fields. Account discovery covers more providers than order routing, so a listed connection does not imply that `place_order` can trade through it. Live or paper execution follows the selected account and platform controls.
`place_order` requires a positive quantity. Limit and stop-limit orders require a positive `price`; stop and stop-limit orders require a positive `stop_price`. Only `time_in_force: "day"` is exposed consistently across broker routes; order-type support remains broker-dependent. `execute_strategy` runs code and can submit orders; it is not a read-only signal preview. Use either operation only for trading the user has authorized, and never follow strategy execution with manual orders merely to repeat the same trades.
Middle-office lists use your personal book by default. Supply `entity_id` to read an entity book where you are an active member. `from` and `to` filter event dates, payment dates, or journal entry dates according to the tool. Keep minor-unit amounts returned as decimal strings exact when exporting or aggregating; do not convert large values to JavaScript numbers.
## Results and failures
Tool results include readable JSON text and structured content. Check `isError` before using the data. An authentication failure, malformed response, or unavailable provider is an error, never an empty portfolio or a fabricated quote.
A timeout or lost response after a write leaves the operation's outcome unconfirmed. Check `list_orders` / `get_order`, the broker, or the affected resource before submitting it again. POST trading and other writes are not automatically retried. Read-only GET calls can retry transient upstream failures within the client's timeout budget.
## Guides and workflow prompts
Read `cpzai://guides/tool-usage` for tool selection and pagination, and `cpzai://guides/permissions` for permissions and execution boundaries. Request `review_portfolio` with an optional `account_id`, or `analyze_strategy` with a required `strategy_id`, to get a suggested review workflow. Prompts supply instructions; requesting a prompt does not place a trade or run a strategy.
## Claude Plugin (Cowork / Claude Code)
The **[CPZAI plugin](https://github.com/CPZ-Lab/cpzai-plugin)** turns Claude into a systematic trading specialist. It bundles domain skills, slash commands, and an MCP connector so Claude produces expert-level results for quant trading tasks.
The plugin is free and open-source (Apache 2.0), but a CPZAI subscription is required to connect to Simons and the CPZAI tools.
### Installation
**Cowork:** Install from the plugin marketplace at [claude.com/plugins](https://claude.com/plugins/).
**Claude Code:**
```bash
claude plugin marketplace add cpz-lab/cpzai-plugin
claude plugin install CPZAI@cpzai-plugin
```
### Commands (17)
**Analysis & Research**
| Command | Description |
|---------|-------------|
| `/equity-research` | Generate a comprehensive equity research report on any public company |
| `/market-scan` | Scan sectors or themes with real-time quotes |
| `/factor-screen` | Screen stocks by quant factors - value, momentum, quality, low-vol |
| `/earnings-preview` | Preview upcoming earnings - expectations, historical patterns, positioning |
| `/options-analysis` | Analyze options strategies, Greeks, vol surface, and positioning |
| `/trade-idea` | Generate a structured trade idea with entry, exit, sizing, and risk management |
**Portfolio & Risk**
| Command | Description |
|---------|-------------|
| `/portfolio-review` | Full portfolio health check - positions, performance, diversification |
| `/risk-report` | Generate a risk report with VaR, Sharpe, drawdown |
| `/optimize` | Optimize portfolio allocation - mean-variance, Black-Litterman, risk parity, HRP |
**Strategy & Backtesting**
| Command | Description |
|---------|-------------|
| `/strategy-builder` | Guided strategy creation from a natural language idea |
| `/strategies` | List, view, and manage your trading strategies |
| `/backtest` | Run a backtest and analyze results |
**Trading & Data**
| Command | Description |
|---------|-------------|
| `/price` | Get real-time quotes - price, change, volume, bid/ask |
| `/trade` | Place a trade through your connected broker |
| `/positions` | View current portfolio positions with P&L and weights |
| `/orders` | View recent orders - open, filled, cancelled |
| `/sync` | Sync portfolio from all connected brokers |
### Skills (10)
Skills activate automatically when Claude detects relevant context:
- **strategy-development** - Signal generation, factor models, archetypes
- **risk-management** - VaR, factor risk models, risk budgeting, liquidity risk
- **backtesting** - Walk-forward, overfitting, production monitoring
- **order-execution** - Market impact models, execution algorithms, TCA
- **market-data** - Data quality, technical indicators, sourcing
- **portfolio-analytics** - Attribution, benchmarking, rebalancing
- **portfolio-optimization** - Mean-variance, Black-Litterman, HRP
- **volatility-and-derivatives** - Greeks, GARCH, vol surface, options strategies
- **alpha-research** - IC/IR, feature engineering, ML cross-validation, signal decay
- **compliance-and-recordkeeping** - Books and records, audit trails, retention, supervisory review
Each skill includes MCP tool chaining workflows - specific sequences of tool calls for common tasks.
# Webhooks
Subscribe to real-time system events. Events are delivered as HMAC-signed HTTP POST requests to your endpoint.
## Available Events
| Event | Trigger |
|-------|---------|
| `order.placed` | A new order is submitted to a broker |
| `order.filled` | An order is filled (partially or fully) |
| `strategy.executed` | A strategy execution completes |
| `backtest.completed` | A backtest run finishes |
| `risk.alert` | Portfolio risk score exceeds threshold |
| `news.breaking` | Critical breaking news is detected |
## Creating a Webhook
```bash
curl -X POST https://api.cpz-lab.com/functions/v1/rest-api/v1/webhooks \
-H "X-CPZ-Key: YOUR_KEY" \
-H "X-CPZ-Secret: YOUR_SECRET" \
-H "Content-Type: application/json" \
-d '{"url":"https://your-server.com/webhook","events":["order.filled","risk.alert"]}'
```
The response includes a `secret` for verifying payloads - save it, it's shown once.
## Payload Format
```json
{
"event": "order.filled",
"payload": {
"order_id": "...",
"symbol": "AAPL",
"side": "buy",
"quantity": 10,
"fill_price": 185.42
},
"timestamp": "2026-03-02T15:00:00.000Z"
}
```
## Signature Verification
Every delivery includes an `X-Webhook-Signature` header - the HMAC-SHA256 of the raw JSON body using your webhook secret.
```python
import hmac, hashlib
def verify_webhook(body: bytes, signature: str, secret: str) -> bool:
expected = hmac.new(secret.encode(), body, hashlib.sha256).hexdigest()
return hmac.compare_digest(expected, signature)
```
# Autonomous Workflows
Combine tools into a review workflow, then execute only the changes the user has authorized. Retrieve every needed page and retain errors as unavailable data.
## Portfolio review
1. `list_accounts`: identify account IDs, environments, and supported order routing.
2. `list_positions`: retrieve all position pages, optionally filtered by account.
3. `get_market_data`: obtain current quotes; report missing or stale data explicitly.
4. `list_risk_snapshots` and `get_risk_snapshot`: inspect saved risk and its timestamp. Use `compute_risk` when a fresh stored snapshot is requested.
5. `list_orders` and `get_order`: inspect existing orders before proposing a change.
6. Present findings and any proposed trades with the selected account, quantity, and prices.
The `review_portfolio` prompt provides a starting point for this workflow.
## Strategy investigation
1. `list_strategies` and `get_strategy`: identify the strategy and inspect its saved definition.
2. `get_backtest_results` and `get_backtest_result`: inspect saved runs, outcomes, and diagnostics.
3. `list_data_files`, `get_data_file`, and `list_connections`: identify available data and metadata.
4. `get_bars`: inspect actual historical prices within the connected provider's entitlements.
5. Propose a change; use `update_strategy` when the requested edit is authorized.
The `analyze_strategy` prompt accepts `strategy_id`. These tools do not provide a sandbox or launch a new backtest. `execute_strategy` can trade; it is not a substitute for a backtest or preview.
## Middle-office reporting
1. `list_deals`: filter the personal or authorized entity book by fund, book, product, or status.
2. `list_lifecycle_events`: inspect scheduled events and any required inputs.
3. `list_cash_flows`: inspect dated cash flows, optionally only unsettled entries.
4. `list_journal_entries` and `list_fund_periods`: review recorded accounting and fund periods.
5. Export all pages while preserving exact minor-unit strings and reporting unresolved inputs.
These MCP tools read existing records. They do not book deals, settle events, or post ledger entries.
## Scheduled and overnight trading
To let a script trade overnight, run it in your own scheduler or an authorized platform strategy schedule. MCP handles individual requests; it does not keep an autonomous agent alive between calls. Define the permitted account, strategy, trading window, risk limits, and recovery behavior before enabling a scheduled run. Review orders and failures after each run.
## Authorized execution and monitoring
Inspect the selected account and existing orders first. Use `place_order` for an explicitly specified manual trade, or `execute_strategy` for an authorized strategy run. Strategy execution may already submit its own orders. After a timeout, establish the existing outcome before any resubmission.
Use `create_webhook` to subscribe an HTTPS endpoint to relevant events, save its signing secret, and verify delivery signatures. Review subscriptions with paginated `list_webhooks`; remove a subscription with `delete_webhook` when requested. A webhook subscription alone does not create a continuously running agent.
## Direct REST access
The public REST adapter uses `https://api.cpz-lab.com/functions/v1/rest-api/v1` and a CPZ key/secret header pair. For example, this reads the first page of positions:
```bash
curl --max-time 20 \
-H "X-CPZ-Key: YOUR_KEY" \
-H "X-CPZ-Secret: YOUR_SECRET" \
'https://api.cpz-lab.com/functions/v1/rest-api/v1/positions?limit=100&offset=0'
```
# Derivatives Engine
The pricing and risk engine is a single dependency-free Rust core. The same compiled artifact serves research, backtesting, the live path and the browser, so a price on a screen, a mark in a backtest and a valuation in the live risk system are produced by the same machine code and cannot disagree.
The engine reads no wall clock and uses no ambient randomness; simulation seeds are explicit inputs. The same inputs produce the same outputs on re-run.
## Coverage
| Family | Instruments |
|---|---|
| Vanilla | European and American options on any underlying through the cost-of-carry parameterisation (equity, index with yield, futures via Black-76, FX via Garman-Kohlhagen), greeks to third order, implied volatility, discrete dividends |
| Barrier | All eight single-barrier types with rebates paid at hit or at expiry, double knock-outs, discrete-monitoring correction |
| Digital | Cash-or-nothing, asset-or-nothing, gap options, one-touch and no-touch, call-spread replication |
| Dual digital | All four corner conditions across two underlyings, with correlation risk reported |
| Multi-asset | Basket options (closed-form geometric, Levy moment matching, Monte Carlo with an exact control variate), spread options (Kirk, Margrabe), best-of and worst-of rainbows |
| Path-dependent | Asian (geometric closed form, Turnbull-Wakeman, Monte Carlo, seasoned deals), lookbacks, forward-start, cliquets with local caps, choosers, compound options |
| Volatility | Variance swaps by static replication across the smile, volatility swaps with the convexity adjustment, corridor variance, seasoned mark-to-market, capped payoffs |
| Correlation | Correlation swaps on the seasoned decomposition: the correlation realised so far blended with the forward fair figure, which is either inverted exactly from a quoted index volatility through the basket variance identity or averaged from the quoted pairs. The screen proxy is offered but never substituted, and an index volatility its constituents cannot support is named rather than truncated |
| Smile | SABR (lognormal and normal), SVI with the Gatheral-Jacquier no-arbitrage conditions checked |
| Rates | Interest rate swaps (dual curve), caps and floors, swaptions in lognormal, normal or SABR terms, physical or cash settled, Hull-White bond options |
| Credit | CDS with accrued premium on default, bootstrapped hazard curves, par spread, standard-coupon upfront, CS01, IR01, jump-to-default, CVA |
| Delta one | Forwards, futures with the convexity difference, total return swaps, equity swaps, CFDs with full financing economics |
## Behaviour on invalid or incomplete input
**It does not return a number it cannot defend.** Every failure is an error carrying the offending input. There is no sentinel value, no zero standing in for "could not price", and no NaN leaking into a position line.
**It does not round or truncate.** Values are IEEE 754 doubles from input to output. Formatting for display is the caller's business.
**It does not repair bad inputs.** A correlation matrix that is not positive definite is rejected with the offending pivot named, not quietly patched. A swap tenor that is not a whole number of periods is an error, not a rounded schedule.
**It does not hide an approximation.** Where a closed form is exact it says so. Where it approximates, the measured error and its direction are documented, and the result carries a reliability flag when the inputs sit outside the regime the approximation holds in.
**It has no hidden state.** No wall clock, no ambient randomness. Monte Carlo seeds are explicit inputs.
## Negative rates
Swaptions quoted in normal (Bachelier) or SABR terms price correctly at negative strikes and negative forwards rather than being refused, which matters for a CHF or EUR book.
# How the Engine Is Validated
Correctness is checked against published reference values and against independent numerical methods, rather than against previously recorded output.
## Published references
- **Reiner-Rubinstein barrier table** (Haug, 2nd edition, table 4-13) reproduced in full: 30 values across all eight barrier types with rebates.
- **Bjerksund-Stensland (2002)** American approximations reproduced against Haug's published values to 5e-5.
- **Genz bivariate normal probabilities** cross-checked against a 400,000-point Simpson integration.
## Independent numerical methods
- Barrier prices cross-checked against a **Brownian-bridge Monte Carlo**, which corrects the discrete-monitoring bias present in a plain simulation.
- Lookback closed forms cross-checked against an **exact continuous-extremum simulation**. A discretely monitored simulation would attribute a sign error in the reflection term to monitoring bias; this check does not.
- Variance-swap replication verified to converge to sigma-squared on a flat smile, with second-order convergence demonstrated as the grid refines.
- Every closed-form greek checked against central differences.
## Structural invariants
Asserted throughout rather than spot-checked: put-call parity, in-out parity, digital parity, cap-floor parity, the rainbow decomposition, touch parity, and the four dual-digital corners summing to one.
## Model risk documentation
Where a method approximates, the measured error and its direction are documented, and the result carries a reliability flag when the inputs fall outside the regime the approximation holds in. Levy moment matching on a basket reports the regime it is accurate in and flags inputs outside it.
# Pricing API
`POST /functions/v1/derivatives-pricing`
Prices any supported derivative through the compiled engine. A `GET` on the same path is a capability probe that lists the supported instruments and proves the engine loaded.
## Contract
```
POST { instrument: string, params: object }
200 { instrument, result, engine: { abiVersion, deterministic } }
400 { error } the inputs cannot support a valuation, with the reason
401 { error } not authenticated
```
A failed valuation returns 400 with the engine's own message, which names the offending input. It never returns a zero, a null or a NaN dressed as a price.
## Instruments
`vanilla`, `implied_vol`, `barrier`, `digital`, `dual_digital`, `interest_rate_swap`, `swaption`, `credit_default_swap`, `total_return_swap`, `cfd`, `variance_swap`, `basket`, `quantum_vanilla`.
Always pass the cost of carry explicitly rather than letting it default.
# Middle Office Overview
A listed order is a symbol, a side, a quantity and a price. An OTC deal has no symbol at all: it is a set of economic terms agreed with a named counterparty under a master agreement.
The middle office is the model that difference demands, and everything that follows from it.
```
deal terms -> schedule -> lifecycle -> cash flows -> journal -> NAV and PnL
\-> marks -> stress, VaR, reconciliation, collateral
```
Each stage is derived from the one before it. The schedule comes from the terms, the events from the schedule, the cash from the events and the postings from the cash. An amendment to the terms regenerates the calendar; there is no separately maintained list.
It runs on the same one-artifact design as the derivatives engine: a dependency-free Rust core compiled once and served to Rust, C and C++, Python, the edge functions and the browser. A mark and a posting are produced by identical code.
## What it covers
| Area | |
|---|---|
| Products | Interest rate swaps (fixed and floating legs in any combination, cross-currency), credit default swaps, total return swaps, CFDs, FX forwards, swaptions, variance and volatility swaps (with corridors), bilateral options (vanilla, barrier, double-barrier, digital, one-touch, no-touch, dual-digital), basket options, bonds and notes (fixed or floating coupon, amortising, ISIN and CUSIP verified), listed holdings (equities, ETFs and fund units held outright, dividends net of withholding) |
| Conventions | ACT/360, ACT/365F, ACT/ACT ISDA, ACT/ACT ICMA, 30/360, 30E/360, 30E/360 ISDA, 1/1 |
| Calendars | TARGET, London, New York, Zurich, computed from published rules rather than tabulated, plus joint calendars and dated ad-hoc closures |
| Schedules | Forward and backward rolls, short and long stubs at either end, payment lags, separate termination convention, unadjusted and adjusted dates both retained |
| Lifecycle | Fifteen event kinds derived from the terms, each naming the observation it is waiting on |
| Fixings | SOFR and €STR fetched on schedule from their administrators and recorded with source and fetch time; SARON and EURIBOR keyed by hand, and the queue says which |
| Valuation | Every product marked through the same engine that generates its cash; a mark the market cannot support is pending and names what it needs |
| Risk | Full-repricing stress over the platform's reference scenario library (translated, translation shown) and the desk's own shifts; historical-simulation VaR over the book's own recorded closes and fixings |
| Accounting | Double-entry journal balanced exactly per currency, SHA-256 hash chain, reversal-only corrections, trial balance per book and per fund |
| Fund | NAV roll-forward, unit dealing, management fees on a stated day count, performance fees against a high water mark |
| PnL | Attribution into interest, financing, dividend, credit, realised, unrealised, FX and fees, at deal, book and fund level |
## Behaviour on invalid or incomplete input
**A missing observation does not produce a number.** A coupon whose fixing has not been observed is reported as pending, naming the fixing required. It is not reported as zero, and the previous fixing is not carried forward.
**It will not hold an unbalanced entry.** An entry is refused unless its lines sum to exactly zero in every currency it touches.
**It will not let an amount round twice.** Economics are computed in floating point; postings are exact integers; the quantisation between them happens once, at a named call.
**It will not mix currencies.** An FX translation is a separate operation that records the rate it used.
**It will not edit history.** A posted entry cannot be modified or deleted: row-level security denies both, so the only correction is a reversal.
**It will not round a schedule to fit.** A term that is not a whole number of periods needs a declared stub convention, or it is an error naming the remainder.
**An unimplemented calendar is an error.** Requesting a financial centre that is not implemented returns an error listing those that are, rather than falling back to a weekends-only calendar, which would generate payment dates on market holidays.
## Relationship to a fund administrator
The NAV produced here is the manager's own, derived from the same events and ledger that produce the marks. It is not a replacement for an administrator's official NAV. Its intended use is reconciliation against that NAV, line by line.
## Four ways in, one engine
| Surface | |
|---|---|
| Screens | Blotter, Lifecycle, Valuation, Risk, Reconciliation, Cash, Journal, Fund and Entity tabs under Middle Office |
| REST | Five read-only resources and one action endpoint; see Middle Office API |
| Python | `client.middle_office` in the cpz-ai SDK: one typed method per action and per read table, money as exact `Decimal` |
| Simons | Read-only tools over the same function: validate and project a deal, the fixings schedule's status, the reference scenarios in the book's terms, the VaR history |
A number shown on any of them is the number the others show: the engine is one compiled artifact, and every surface calls it.
# OTC Deal Capture
A deal record carries what an OTC trade actually is, not a symbol and a quantity.
## Who
The counterparty by **legal name and LEI**, not a broker code, plus the **netting set** it trades within. Exposure, netting and collateral are all computed per counterparty, and the same legal entity can sit in more than one netting set under different agreements.
LEIs are validated against the ISO 17442 check digits on capture and rejected if they do not verify.
## Under what
The **master agreement**, and where one exists the **credit support annex**: threshold, minimum transfer amount, independent amount, collateral currency, margin frequency and rounding. An absent CSA is recorded as uncollateralised rather than as a missing value, since the two have different valuation and close-out treatment.
The collateral call calculation applies the threshold, then the independent amount, then the minimum transfer amount, then rounding. Calls round up and returns round down.
## What
The full economic terms, typed per product, from which the schedule and cash flows are **derived** rather than entered separately. A swap carries two legs, each fixed or floating, each with its own notional, currency, day count, schedule and, for a floating leg, its index, spread, fixing lag and fixing convention.
## One example per product
Every product is a `product` object with a `kind`. The examples below are the demo book's own terms, each of which validates and generates today. A `schedule` is `{effective_date, termination_date, frequency, calendar, business_day_convention, termination_convention?, stub?, payment_lag_business_days?, end_of_month?}`.
```json
{ "kind": "INTEREST_RATE_SWAP",
"receive": { "type": "FIXED", "notional": 40000000, "currency": "CHF", "rate": 0.009, "day_count": "30/360",
"schedule": { "effective_date": "2026-04-10", "termination_date": "2031-04-10", "frequency": "1Y", "calendar": ["ZURICH"], "business_day_convention": "MODIFIEDFOLLOWING" } },
"pay": { "type": "FLOATING", "notional": 40000000, "currency": "CHF", "index": "SARON", "spread": 0, "day_count": "ACT/360",
"fixing_lag_business_days": 2, "fixing_convention": "IN_ADVANCE",
"schedule": { "effective_date": "2026-04-10", "termination_date": "2031-04-10", "frequency": "3M", "calendar": ["ZURICH"], "business_day_convention": "MODIFIEDFOLLOWING" } } }
```
```json
{ "kind": "CREDIT_DEFAULT_SWAP", "reference_entity": "Deutsche Telekom AG", "seniority": "SENIOR_UNSECURED",
"coupon": 0.01, "recovery_rate": 0.4, "notional": 10000000, "currency": "EUR", "day_count": "ACT/360", "buy_protection": false,
"schedule": { "effective_date": "2026-03-20", "termination_date": "2031-06-20", "frequency": "3M", "calendar": ["TARGET"],
"business_day_convention": "FOLLOWING", "termination_convention": "UNADJUSTED", "stub": "SHORTFRONT" } }
```
```json
{ "kind": "TOTAL_RETURN_SWAP", "underlying": "SMI Index", "notional": 30000000, "currency": "CHF", "initial_price": 12500,
"funding_index": "SARON", "funding_spread": 0.0035, "funding_day_count": "ACT/360", "dividend_pass_through": 0.85, "receive_total_return": true,
"reset_schedule": { "effective_date": "2026-03-16", "termination_date": "2029-03-16", "frequency": "3M", "calendar": ["ZURICH"], "business_day_convention": "MODIFIEDFOLLOWING" } }
```
```json
{ "kind": "CFD", "underlying": "NESN SW", "quantity": 50000, "entry_price": 95.4, "currency": "CHF",
"financing_index": "SARON", "financing_spread": 0.015, "financing_day_count": "ACT/360", "financing_basis": 360, "initial_margin_rate": 0.2, "is_long": true }
```
```json
{ "kind": "FX_FORWARD", "buy_currency": "CHF", "buy_amount": 9400000, "sell_currency": "EUR", "sell_amount": 10000000, "delivery_date": "2026-12-03" }
```
```json
{ "kind": "SWAPTION", "expiry_date": "2027-06-15", "strike": 0.011, "notional": 50000000, "currency": "CHF", "underlying_tenor": "5Y", "is_payer": true, "settlement": "CASH" }
```
```json
{ "kind": "VARIANCE_SWAP", "underlying": "SMI Index", "volatility_strike": 18, "vega_notional": 150000, "currency": "CHF",
"observation_start": "2026-06-12", "observation_end": "2027-06-11", "is_volatility_swap": false, "corridor": null }
```
```json
{ "kind": "CORRELATION_SWAP", "underlyings": [{ "underlying": "NESN SW", "weight": 0.5 }, { "underlying": "ROG SW", "weight": 0.3 }, { "underlying": "NOVN SW", "weight": 0.2 }],
"index_underlying": "SMI Index", "correlation_strike": 45, "notional_per_point": 20000, "currency": "CHF",
"observation_start": "2026-06-12", "observation_end": "2027-06-11", "cap": null, "floor": null }
```
```json
{ "kind": "OTC_OPTION", "underlying": "SMI Index", "strike": 12800, "expiry_date": "2027-03-19", "quantity": 200, "currency": "CHF",
"is_call": true, "is_american": false, "settlement": "CASH",
"payoff": { "type": "BARRIER", "kind": "DOWN_OUT", "level": 11000, "rebate": 0, "rebate_paid": "AT_EXPIRY", "monitoring": { "type": "DISCRETE", "observations_per_year": 252 } } }
```
```json
{ "kind": "BASKET_OPTION", "underlyings": [ { "underlying": "NESN SW", "weight": 1 }, { "underlying": "ROG SW", "weight": 0.5 }, { "underlying": "NOVN SW", "weight": 1.2 } ],
"strike": 340, "expiry_date": "2027-06-17", "quantity": 500, "currency": "CHF", "is_call": true, "settlement": "CASH" }
```
The bond and the listed holding are shown in full below. Around the product sit the deal's own fields: `deal_id`, `book`, `fund`, `direction` (PAY or RECEIVE), `status`, `trade_date`, `effective_date`, `termination_date`, `counterparty {legal_name, lei, netting_set}`, optional `master_agreement` with its `csa`, optional `identifiers` and `notes`.
## Bonds and notes
A bond is booked as a deal shape, not as a listed position, because its economics are a swap leg's: the coupons, the accrual and the fixings are derived from the terms. The record carries the issuer, an optional ISIN and CUSIP (both check digits verified on capture), the face amount and currency, the coupon as `{type: "FIXED", rate}` or `{type: "FLOATING", index, spread, fixing_lag_business_days, fixing_convention}`, the day count, the coupon schedule from the dated date to maturity (which may start before the deal's effective date, for paper bought in the secondary market), the redemption price as a fraction of par, an amortisation list of `{date, fraction}` repaid at par strictly before maturity, whether the book is long or short, and an optional clean purchase price.
```json
{
"kind": "BOND",
"issuer": "Swiss Confederation",
"isin": "CH000CPZ0005",
"notional": 5000000,
"currency": "CHF",
"coupon": { "type": "FIXED", "rate": 0.015 },
"day_count": "30/360",
"schedule": {
"effective_date": "2026-03-16", "termination_date": "2031-03-16", "frequency": "1Y",
"calendar": ["ZURICH"], "business_day_convention": "MODIFIEDFOLLOWING",
"termination_convention": "UNADJUSTED"
},
"redemption_price": 1.0,
"amortisation": [],
"is_long": true,
"clean_purchase_price": 0.9925
}
```
What follows from those terms: a coupon event per period (with a fixing event first on a floating note, waiting on the index), a principal event on every amortisation date and at maturity, and, where a purchase price was recorded, the purchase consideration (clean plus accrued) as a cash flow on the settlement date. Coupons that paid on or before the purchase belong to the previous holder and are not raised. Coupons accrue on the face outstanding, split exactly at a paydown inside a period; ACT/ACT ICMA is measured against the coupon period and frequency, as the convention requires. The mark discounts every remaining flow on the currency curve, applies an issuer credit spread where one is quoted (and says on the method whether it did), and reports clean price, dirty price and accrued per 100 of the outstanding face together with the ICMA yield to maturity, Macaulay and modified duration, convexity and DV01. A bond's principal posts to the debt-securities line of the balance sheet, never the derivatives line.
Not yet modelled, and refused rather than approximated: ex-coupon periods, callable and puttable features, step-up coupons and inflation linkage.
## Listed holdings
An equity, an ETF or a fund unit held outright is booked as a deal too, so the same lifecycle, cash, ledger, NAV and attribution hold it beside the OTC book. The record carries the `underlying` the closes are recorded under (a ticker, or a fund's ISIN), the `holding_kind` (EQUITY, ETF, FUND_UNIT), the `quantity`, the `cost_price` per unit, the currency, the side and the `dividend_net_fraction` the holder receives (1 gross; 0.65 after a 35% withholding).
```json
{ "kind": "LISTED_HOLDING", "underlying": "NESN SW", "holding_kind": "EQUITY", "quantity": 20000, "cost_price": 94.8,
"currency": "CHF", "is_long": true, "dividend_net_fraction": 0.65 }
```
What follows: the acquisition is cash at the cost price on the effective date and needs nothing observed; the disposal on the termination date waits on that day's close; a dividend or distribution is not on the terms at all, because it is declared later, so it becomes an event only once recorded on the book (`record_observation` of kind DIVIDEND with its ex-date and pay date) and is then cash on its pay date, net of what is withheld. The mark is the quantity at the close recorded for the valuation date, never a price carried from an earlier day, and its detail carries the price, the cost and the unrealised difference. The consideration posts to the listed-securities line of the balance sheet and attributes as realised; the mark's movement is unrealised, so purchase plus movement is the position's P&L to the minor unit. A fund of funds holds its external funds this way, with the fund's ISIN as the price key and its published NAV recorded as the close.
## Exotic payoffs, baskets and corridors
An OTC option carries a `payoff` beyond the vanilla; absent means vanilla, so every option booked before payoffs existed reads back unchanged. The shapes, each routed to its own pricer in the derivatives engine:
| Payoff | Terms | Priced by |
|---|---|---|
| `BARRIER` | `kind` (UP_IN, UP_OUT, DOWN_IN, DOWN_OUT), `level`, `rebate`, `rebate_paid` (AT_HIT, AT_EXPIRY), `monitoring` (CONTINUOUS, or DISCRETE with `observations_per_year`) | Reiner-Rubinstein; a discretely monitored level is shifted by Broadie-Glasserman-Kou and the mark reports the effective level |
| `DOUBLE_BARRIER` | `lower`, `upper`; a rebate is refused, not approximated | Ikeda-Kunitomo double knock-out |
| `DIGITAL` | `payout_type` CASH_OR_NOTHING with `payout`, or ASSET_OR_NOTHING | Reiner-Rubinstein digital |
| `ONE_TOUCH` | `level`, `payout`, `at_hit` | Reiner-Rubinstein, paid at the hit or the discounted touch probability at expiry |
| `NO_TOUCH` | `level`, `payout` | The complement of the one-touch at expiry |
| `DUAL_DIGITAL` | `second_underlying`, `second_strike`, `condition` (BOTH_ABOVE, BOTH_BELOW, FIRST_ABOVE_SECOND_BELOW, FIRST_BELOW_SECOND_ABOVE), `payout` | Bivariate normal; the pair's correlation is a required mark input |
A barrier or touch has a knock state. When the level trades, operations calls `record_knock { deal_id, date, price, source }`: the function refuses a close on the wrong side of a barrier, stores the close as an observation, re-validates the terms through the engine, cancels the stored events the knock made moot and regenerates the calendar from the knock date. A knocked-out option raises no further cash (only a rebate still owed); a knocked-in one is the vanilla it referenced. A discretely monitored barrier raises a `BARRIER_OBSERVATION` event per monitoring date on the currency's business calendar, each waiting on that day's close. An option's settlement is projected as cash at expiry from the close (a settlement that comes to nothing moves no cash); a physically settled option waits on the exercise decision, because delivery moves an asset, not only cash.
A `BASKET_OPTION` carries `underlyings` as `[{underlying, weight}]` (every weight positive), `strike` on the basket level, `expiry_date`, `quantity`, `currency`, `is_call` and `settlement`. It is marked by Levy's two-moment match where the pricer states the match is accurate, and otherwise by a seeded Monte Carlo with the geometric basket as control variate; the mark says which ran and, for the simulation, its standard error. Every pair needs a correlation, supplied as `correlations: [{a, b, rho}]` on the mark inputs and named if missing.
A `CORRELATION_SWAP` names the basket it measures, the weights the pairs are averaged with, a strike in correlation points (45 for 0.45) and a notional per point. Where it names an `index_underlying`, the fair correlation for the remaining window is inverted from that index's quoted volatility through the basket variance identity, exactly rather than through the ratio proxy the screens show; where it does not, the quoted pairwise correlations are averaged with the same weights, and an absent pair is a named missing input. Both routes are checked against each other in the engine's tests, and the mark says which one it took. A quoted index volatility that its constituents cannot support -- one implying an average correlation outside -1 to 1 -- comes back as a pending mark asking for a consistent quote, because truncating it to the boundary would bury the inconsistency and refusing outright would take the rest of the book's run down with it. A cap or a floor is applied at settlement and is not modelled in the seasoned value; the mark says so on its own record.
A `VARIANCE_SWAP` may carry `corridor: {lower, upper}`: variance accrues only on closes inside the corridor, and the fair strike is the option replication truncated to it, run over the quoted SABR set or smile where one exists and the flat vol otherwise. The realised variance supplied to the mark must be the corridor-accrued figure.
## Instrument identifiers
Every deal may carry `identifiers`: a list of `{scheme, value}` where the scheme is `ISIN`, `CUSIP`, `FIGI`, `SEDOL`, `BLOOMBERG_TICKER`, `RIC` or `INTERNAL`. The four with a check digit are verified on capture and normalised to upper case; a value that fails is refused with the scheme named, because a mistyped identifier attaches an exposure to the wrong security. One scheme may not appear twice with different values. `list_deals` accepts `identifier: {scheme, value}` and finds every deal carrying it, so a TRS, a CFD and a bond on the same paper are found as one exposure.
## The book's own security master
A book of record declares the instruments its deals reference in its own master (`upsert_security`): the price key the book records closes under (a ticker, an index name, a fund's ISIN), the name, the instrument type (equity, ETF, fund unit, bond, index, FX pair, rate index, future, other), the currency where the instrument has one, the identifiers the engine verifies exactly as on a booking, and the contract terms a listed line needs (multiplier, tick size, lot size, calendar, day count, price decimals), with an optional link to the global FIGI-keyed master. One ISIN is one security: an identifier already carried by another key in the book is refused by name.
Every booking is checked against it. A deal referencing a key the master knows must agree with the master on every identifier they share, and the disagreement is refused naming both values; a deal that carries no identifiers is given the master's, and the terms hash covers what was attached. `resolve_security` answers by key or by identifier from the book's master first, the global `security_master` second and the OpenFIGI symbology service third, saying which of the three answered, and never with the nearest match. A service that is unreachable raises rather than answering "not found": a vendor being down and a security not existing are different facts, and the second told wrongly gets an instrument re-keyed. The Securities screen and the booking form's identifiers card look an instrument up the same way.
**Nine schemes, because a code is national.** A CUSIP is the United States and Canada, and nothing else: a Zurich line is keyed by its Valorennummer, a Frankfurt line by its WKN, a London line by its SEDOL, and the ISIN is the one code that spans all of them. ISIN, CUSIP, FIGI and SEDOL carry check digits and are verified on capture. A Valor and a WKN carry none, so the engine verifies them a second way: an ISIN is built from its national number, so a Swiss ISIN beside a Valor, or a German ISIN beside a WKN, must agree, and a pair that does not is refused naming both. That check is the one that earns its keep, because each code passes its own shape while naming a different security. A country whose national number the engine cannot name is left alone rather than guessed at. CINS, the international extension of CUSIP, is a CUSIP with a country letter in front and verifies identically.
## When
Trade, effective and termination dates are stored separately, since they can differ and each governs different behaviour.
## Frozen on confirmation
Confirmation stamps a hash over the economic terms, so a later amendment is a visible dated event rather than an edit no one can see.
## Validation on capture
Terms are validated before the deal is stored. A deal that starts accruing before it was agreed, a master agreement dated after the deal it governs, a CDS with a recovery rate of 1 (protection that pays nothing on default), a dividend pass-through above 100 percent, a swap whose legs are both fixed in the same currency: each is refused with the offending term named.
## Actions and reads
| | |
|---|---|
| `book_deal { deal }` | Validate, schedule, hash and store. Under four-eyes the deal lands as a DRAFT. Where the scope has declared its funds, the deal's fund and book must be declared, and a booking into an undeclared one is refused by name |
| `approve_deal { deal_id }`, `reject_deal { deal_id, note }` | The second pair of eyes; self-approval is refused at the row |
| `list_deals { fund?, book?, status?, product_kind?, identifier? }` | The book; `identifier: {scheme, value}` finds every deal carrying it |
| `get_deal { deal_id }` | The stored terms with their events and flows |
| `record_knock { deal_id, date, price, source }` | A barrier or touch level traded |
| `GET /rest-api/v1/deals` | The same rows over the REST adapter, read only |
| `client.middle_office.book_deal(terms)` | The Python SDK; the reply's `summary` is the engine's read of the terms |
# Lifecycle Events
Given a deal and a window, the engine produces every event the contract will raise in that window. This is the calendar the operations desk works from, and the input to cash flow projection.
## Fifteen event kinds
Trade booking, initial exchange, fixing, reset, coupon payment, financing accrual, dividend pass-through, credit event, exercise, expiry, observation start, observation end, final exchange, maturity, barrier observation.
## Events are derived, never entered
Every event is a function of the deal's terms, so the calendar cannot drift from the deal it belongs to. A deal amendment regenerates it.
## An event says what it still needs
A projected coupon on a floating leg is not yet an amount, because the fixing has not happened. The event carries the observation it is waiting on and the date it is observed on.
Events are therefore groupable by the observation they require: **"eleven events are waiting on the SARON fixing for 1 December"**. No value is produced for them until that fixing is recorded.
Observations an event can be waiting on: an index fixing, a closing price, a declared dividend, an auction recovery, an exercise decision, or realised variance over a window.
## Fixings on a schedule
Twice on every weekday the platform fills the fixings a book's events are waiting on from the administrators' own publications: SOFR from the Federal Reserve Bank of New York and €STR from the European Central Bank, at the precision they publish, recorded under the exact index name the deal uses with a source that names the publisher, the series, the fetch time and the notice the publisher's terms require. A fixing the desk recorded by hand is never overwritten; a date the administrator has not published stays pending and is never carried forward; an administrator's revision of a value already on the book is reported, not applied. The affected deals' calendars are regenerated so the events read as ready and the cash projects.
SARON and EURIBOR are not fetched: SIX publishes SARON to six decimals under licence and the free republication is rounded to two, which is not the fixing; EMMI licenses EURIBOR. They are keyed by hand, or fed under the client's own entitlement. Each waiting group in the Lifecycle tab says which applies, and `fixing_coverage` reports it per index. A book switches the feed off with `set_valuation_policy { fixing_feed: false }`. The sources, their terms and the reasoning are in `docs/middle-office/fixing-sources.md`.
## Worked examples
**TRS reset.** The reset calendar derives itself from the deal. Each reset carries the last reset price, the funding rate and spread, dividends since the reset and the pass-through rate net of withholding, and it says it needs the closing price on the reset date before it becomes an amount.
**CDS coupon.** Standard quarterly schedule on the 20th of March, June, September and December, which is why a five-year CDS traded today does not mature in five years to the day. Premium leg, accrued premium on default, and the auction recovery driving the payout rather than the recovery assumed at trade date.
**CFD financing.** Accrues on business days only, marked at the current price rather than the entry price, split out from price PnL.
## Actions and reads
| | |
|---|---|
| `generate_events { deal_id, from, to }` | Derive and store the calendar over a window; the reply's `outstanding` names what each waiting event needs, as `{event_key, required_input}` in the engine's own shape (`INDEX_FIXING` with `index` and `fixing_date`, `UNDERLYING_PRICE` with `underlying` and `price_date`, `DIVIDEND` with `ex_date`, `RECOVERY_RATE`, `EXERCISE_DECISION`, `REALISED_VARIANCE` and `REALISED_CORRELATION` with their windows, each with a `description`) |
| `record_observation { kind, key, date, value, source, pay_date?, window_start? }` | A FIXING (a decimal: 0.0142 for 1.42%), PRICE, DIVIDEND (with its pay date), RECOVERY, REALISED_VARIANCE or REALISED_CORRELATION. A realised figure is observed over a window, so it carries `window_start` and `date` is where the window ends; the same basket over a different window is a different observation. The source is required; an unsourced observation cannot be audited |
| `fixing_coverage {}` | For every index the pending events name: fed on schedule and by whom, or keyed by hand and why; the schedule and the last run |
| `record_knock` | See deal capture |
| `GET /rest-api/v1/lifecycle-events` | The stored calendar, filterable by deal, kind, status and event date |
| `client.middle_office.generate_events(...)` | The Python SDK; `item.required_input.describe()` reads "SARON fixing for 2026-06-16" |
## The window is mandatory
Generation is always bounded by an explicit date range. A five-year CFD raises a financing accrual every business day; a caller that asks for the whole life of the book gets it, and a caller that asks for tomorrow gets tomorrow.
# Cash Flows and Accruals
## Sign convention, stated once
**A positive amount is cash into the fund.** Every flow is signed from the book's own perspective, so a coupon paid away is negative and summing a set of flows gives net cash without anyone having to remember which leg was which.
## A missing observation is not a zero
An event whose fixing has not arrived is reported as **pending**, naming what is missing. It does not produce a flow of zero, and it does not reach for the last known rate.
Determined flows and pending events are returned as two separate lists rather than one list with a status flag, so a pending event cannot be summed as though it were zero.
## Amounts are computed in floating point and booked as exact integers
The valuation is performed in double precision. The booked amount is an exact count of minor units, quantised once when the flow is created, using round-half-to-even.
Round-half-to-even is used rather than half-away-from-zero. Over many postings, half-away-from-zero accumulates a directional bias in the rounding residual; round-half-to-even does not. This is an accounting policy and is stated here so that it can be reconciled against.
Both values are kept on the flow: the booked amount is what the ledger posts, the economic amount is what the calculation produced, and the difference between them is the rounding, visible rather than lost.
## Accruals
Interest accrued but not yet paid, as of a date. This is the number that makes a NAV struck mid-period correct rather than lumpy.
A leg whose fixing has not been observed returns **an error naming the missing fixing** rather than an accrual computed from the previous fixing.
## Cash ladder
Net cash by settlement date and currency. Pending events are excluded and reported separately, so a cash ladder never silently omits an unfixed coupon.
## Treasury: accounts, statements and projected balances
A fund's cash lives in accounts: a settlement account per currency at the custodian, a broker account, a collateral account under a CSA. The book declares them (`create_cash_account`: code, name, fund, currency, kind, institution, reference; one settlement default per fund and currency, so every flow has somewhere to land) and records what the custodian stated (`record_cash_balance`: account, date, the balance signed as stated, and its source, which is required because an unsourced balance cannot be reconciled against).
`treasury_ladder` puts the custodian's number beside the book's, per account: the ladder opens from the latest statement on or before the window start rolled forward by the movements settled since (and says so; without a statement it opens at zero and says that), runs the projected balance day by day from settled and projected deal cash and the fund's own subscriptions, redemptions, expenses and income, and, for the latest statement inside the window, states the book's balance on that date and the difference. A difference is named, never smoothed; a movement in a currency the fund has no account for is named too; deal cash still waiting on an observation is counted, never placed in a balance. The Cash tab shows all of it and takes the declarations.
## Actions and reads
| | |
|---|---|
| `project_cashflows { deal_id, from, to }` | Determined flows, each with `amount: {minor, currency}` exact and `economic_amount` unrounded, stored on the event key; `pending` events with `missing` named; `no_cash` markers (a booking, a fixing, a maturity) |
| `cash_ladder { from, to, fund?, book? }` | Net cash by settlement date and currency from the stored flows |
| `accrue { as_of, fund?, book?, deal_id? }` | Interest accrued and unpaid per leg; a leg without its fixing is named, not estimated |
| `create_cash_account`, `list_cash_accounts { fund? }` | The fund's cash accounts, with each account's latest stated balance |
| `record_cash_balance { account, balance_date, balance, source }` | What the custodian stated, signed as stated, with its source |
| `treasury_ladder { fund, from, to }` | Per account: the statement against the book and the difference, then the projected balance day by day |
| `GET /rest-api/v1/cash-flows` | The stored flows, with `amount_minor` as a decimal string and `unsettled=true` for what has not settled |
| `client.middle_office.project_cashflows(...)` | `flow.amount.amount` is a `Decimal`; a pending flow has no amount attribute at all |
# The General Ledger
## Double entry is enforced, not assumed
An entry is rejected unless its lines sum to zero **in every currency it touches**. Not approximately zero, not zero after rounding: exactly zero, which is possible because amounts are exact integer counts of minor units.
The check runs at the point of posting rather than in a batch report, so an unbalanced entry is rejected rather than recorded and detected later.
**The check is per currency.** An entry whose lines net to zero only after an FX conversion would carry an unrecorded FX position. A translation between currencies is a separate entry that records the rate applied.
## Why amounts are integers and not floats
A ledger must balance exactly and sum to the same total regardless of the order in which rows are added. Binary floating point provides neither property: 0.1 plus 0.2 does not equal 0.3, and addition is not associative, so a trial balance can reconcile in one traversal order and not in another.
Amounts are therefore held as an exact integer count of minor units (cents, rappen) in a stated currency, wide enough that a position in yen or Chilean pesos cannot overflow at any size a fund will hold.
## The chain
Each entry commits to the SHA-256 of the entry before it, over a canonical rendering of its own contents. Altering a posted entry breaks the chain from that point: its contents no longer hash to its recorded hash.
Repairing the altered entry's own hash does not help: the next entry still commits to the old one, so the break simply moves forward.
SHA-256 is used rather than a non-cryptographic digest, so that a modified entry cannot be given a colliding hash to repair the chain.
## Corrections are entries, not edits
A correction is a new entry posting the reversing amounts, linked to the entry it reverses; the API offers no other way to change a posted entry, and the same entry cannot be reversed twice. The state of the book at any past date is therefore reconstructable.
The journal is append-only at the database and not merely at the API: there is no UPDATE or DELETE policy on the entries or their lines, so row-level security refuses both to every caller. The middle-office service is the one exception: it posts and reverses, and `rechain_journal` rewrites the hash columns (see Rechaining). It never changes amounts, accounts, dates or descriptions.
On top of that it is **tamper-evident**. Every entry carries a hash over its own lines and its predecessor, so a change made with database-owner rights still breaks the chain from that point forward. The Journal tab and the REST read show each entry's `prev_hash` and `entry_hash`, so a deleted, inserted or reordered entry shows as a broken link. No action yet recomputes each stored entry's hash from its lines.
`reverse_entry { entry_id, reason, reversal_date? }` is that correction. The original stands; the reversal negates it line for line, is chained onto the same journal, and carries the reason, which is required: a reversal without one is a deletion wearing its clothes. The engine refuses to reverse the same entry twice, because that would restate the book by the amount again.
## Rechaining
`rechain_journal { dry_run? }` repairs books posted into before 2026-09-03, whose entries were each hashed against an empty journal. Only the book's OWNER or ADMIN can run it. It replays the entries as stored now and rewrites only `prev_hash`, `entry_hash` and `sequence`; nothing is written if the replay does not balance, every run is on the audit log, and `dry_run` writes nothing. Run after an entry was altered, it makes that entry link again: the audit row, and any copy of the hashes taken before, are what show it.
## The trial balance is the postings, not a summary beside them
`trial_balance { as_of?, book? }` replays every stored entry through the engine and returns each account's balance with its name and type from the chart, the same balances summed by account type (which is the fund's balance sheet), and whether the book balances. It is computed from the postings rather than kept alongside them: a total that could disagree with the chain is the one nobody would notice.
## Unrealised movements are postings too
A mark is not only a number on a valuation record. `post_valuations { as_of, fund?, book? }` posts each deal's movement in fair value since its own previous mark, so the balance sheet the ledger produces is the fund's. Which line it moves depends on the product: a derivative crosses between an asset and a liability as its value changes sign, while a bond or a listed holding stays an asset that is worth more or less, and posts to the debt-securities or listed-securities line with the securities gain and loss accounts. A deal marked for the first time posts its whole value; a mark that did not move posts nothing, because a zero entry says nothing; and an entry already posted for a deal and date is not posted twice.
## Corporate actions
A split does not change what a holding is worth and changes every number that describes it. A dividend is cash the fund is owed on a date it did not choose. A merger is a decision somebody has to make. `record_corporate_action` records the announcement with its ex-date, its terms and its source, and `apply_corporate_action` does what the issuer did.
**A ratio action preserves the cost exactly.** A split rewrites each holding's quantity and cost price so that quantity times cost is the same number before and after, computed on exact decimals; a ratio that cannot do that on the holding it is applied to is refused rather than rounded into a small permanent error in the fund's cost basis. The amended deal is revalidated through the engine, its terms hash recomputed, and its calendar rebuilt.
**A dividend pays through the path every dividend takes.** Applying a cash dividend records the dividend observation the lifecycle already turns into a pass-through event and cash, rather than inventing a second route to the same money.
**An action that needs a decision is not applied.** A merger, a spin-off and a rights issue carry elections. They are recorded, reported against the holdings they touch, and left for a person, because guessing an election is worse than not making one.
**Applying twice is refused.** What an action did to each holding is recorded with the numbers before and after, and those records are what make a second application impossible: a split applied twice would double the units.
Announcements arrive the same way statements do. The statement kinds include `CORPORATE_ACTION`, so the layouts, the feeds, the inbox and the file ledger built for custodian files carry an issuer's announcements with no new channel to run.
## Share classes: one portfolio, several prices
A fund's investors do not all pay the same fee. A retail class at 75 basis points and an institutional class at 45 hold the same portfolio at two different NAVs per unit, and the difference between them is the fee, allocated. `create_share_class` declares a class with its currency, whether it hedges, its own management and performance fee rates, its minimum and its launch date; a fee rate is a decimal, so 0.0075 is 75 basis points and a rate at or above one is refused as the unit error it is.
`roll_class_nav { fund, period_start, period_end }` divides a period the fund has already rolled. It reads the fund's own roll rather than recomputing it, so the classes cannot disagree with the fund about what there was to share: the investment return is the fund's movement with the fees the classes carry themselves added back, each class takes its share, and each then pays its own fee. **The classes sum to the fund exactly.** The minor unit that cannot be split is handed to a class by the largest-remainder rule and reported on that class's row, so nothing is lost or counted twice, and the reply says whether the sum ties and by how much it does not.
The share is struck on the weights the caller states, or pro rata on opening net assets where none are stated. The second is exact only where no class dealt inside the period, and the reply says so in words when it is not: an approximation that names itself can be checked, and one that does not cannot. A fund with classes carries its management fee on the classes rather than on a fund-level expense schedule, so it is charged once, to the class that owes it.
Dealing names its class. Where a fund issues more than one, a subscription that names none is refused, because a register that cannot be split by class cannot price either of them. `class_register` reads the register per class and checks each class's units against the class period that priced it.
## The register says whose units these are
A subscription was a fund flow with a number of units on it: the fund knew how many units it had issued and nothing about who held them. The register is the other half. `create_investor` records a holder with the identifier and domicile a transfer agent needs, and `record_investor_transaction` records their dealing: subscriptions, redemptions and transfers, each with the units, the amount, the NAV per unit they were dealt at, and the dealing and settlement dates.
Three rules make it a register rather than a list. **The three numbers must agree**: units times the NAV per unit equals the amount to the currency's own minor unit, checked before anything is written, because a unit count that does not match the cash is a register that will never tie again and nothing later will say so. **Nobody redeems what they do not hold**: a redemption is checked against the holding on its dealing date and refused naming what is actually held. **The register and the fund cannot drift apart**: a dealing either creates the fund-level flow the NAV roll reads, or links to an existing one and is checked against it, so the same subscription is never recorded twice with two different numbers.
`investor_register { fund, as_of? }` is the register at a date: every holder with their units, what they have put in net of what they have taken out, and when they last dealt, together with the units the fund itself has issued in the period covering that date and the difference between the two. A register that does not tie is the finding, so the reply says so in a sentence rather than leaving two numbers to be compared. Where the fund has struck no unit count, the tie is reported as unknown rather than as true. Dealing agreed on or before the date but settling after it is reported per currency as what the fund is owed and what it owes.
## Expenses are scheduled, not remembered
A fee stated once is accrued by schedule. `create_expense_schedule` records what the fund carries -- a rate on net assets or a fixed amount a period, with its frequency, day count, calendar and expense account -- and `accrue_expenses { fund, to }` turns it into dated amounts: one EXPENSE fund flow a period, so the NAV roll carries it, and one journal entry debiting the expense account against accrued expenses. Each period is accrued exactly once, whoever runs it and however often, because the accrual is recorded with a unique key per schedule and period end. A fee on net assets needs the net assets it is charged on: it takes them from the last closed NAV, or from what the caller states, and refuses to run without them rather than charging a fee on nothing. What it was charged on is stored with the accrual, so an auditor can recompute it.
## Attribution lives on the line
A ledger line carries the book and the deal that caused it. A per-book figure is therefore a filter over the ledger rather than a replay of orders that may not tie out.
## Chart of accounts
`install_chart_of_accounts` writes the engine's own chart into the book, and `chart_of_accounts` reads back what the book holds together with what the engine will accept, naming any account the book is missing. The engine validates every posting against its own chart whether or not a book has installed it; installing it gives the book a copy of the same list rather than a second version of it that could drift.
A standard fund chart is provided: cash at bank and at broker, collateral posted and received, interest and dividend receivable, derivative assets and liabilities at fair value, debt securities at fair value, subscriptions receivable, redemptions payable, contributed capital, retained earnings, interest and dividend income, realised and unrealised gains and losses, credit protection income, and the expense side including management, performance, administration, custody and audit fees.
## Actions and reads
| | |
|---|---|
| `entries_for_flows { from, to, fund?, book?, deal_id? }` | The balanced entries the stored flows imply, derived and not posted, each line with `debit` and `credit` and its `book` |
| `trial_balance { as_of?, book? }` | Every account's balance, the balance sheet by account type, and whether the book balances |
| `reverse_entry { entry_id, reason, reversal_date? }` * | The reversing entry, chained. The reason is required and the same entry cannot be reversed twice |
| `post_valuations { as_of, fund?, book? }` * | Each deal's movement in fair value since its previous mark, posted to the line its product belongs on |
| `install_chart_of_accounts` *, `chart_of_accounts` | The engine's chart into the book; what the book holds against what the engine accepts |
| `record_corporate_action` *, `list_corporate_actions { status?, identifier?, from?, to? }` | An issuer announcement, and what is on the book's securities |
| `apply_corporate_action { id }` *, `corporate_action_effects { deal_id?, action_id? }` | Do what the issuer did; what it did to each holding, before and after |
| `create_share_class` *, `list_share_classes { fund? }` | A class with its currency, fee rates, minimum and launch date |
| `roll_class_nav { fund, period_start, period_end, classes? }` * | The fund's period divided between its classes, with the tie to the fund reported |
| `class_register { fund, as_of? }` | The register per class, against the class period that priced it |
| `create_investor` *, `list_investors` | A holder on the register, with the identifier and domicile a transfer agent needs |
| `record_investor_transaction` * | One holder's dealing, with its fund flow created or linked and checked |
| `investor_register { fund, as_of? }`, `investor_statement { investor, fund?, from?, to? }` | Who holds the units and whether the register ties; one holder's dealing and holding |
| `create_expense_schedule` *, `list_expense_schedules { fund? }` | A fee stated once: its basis, frequency, day count, calendar and expense account |
| `accrue_expenses { fund, to, from?, net_assets?, post? }` *, `expense_accruals { fund? }` | The schedules turned into dated amounts, once per period; what each run produced |
| `performance_fee { nav_per_unit, units, high_water_mark, rate, as_of, currency }` | What crystallises above the mark, and the mark it sets |
| `post_journal { entry, deal_id?, event_key?, source_reference?, as_of? }` * | Post one entry: `{entry_id, entry_date, description, lines: [{account_code, side, amount, book?}]}`. The engine balances it per currency and chains it to the book's head; an unbalanced entry is refused with the difference named |
| `rechain_journal { dry_run? }` * | OWNER or ADMIN only; see Rechaining |
| `GET /rest-api/v1/journal-entries` | Entries with `prev_hash` and `entry_hash`, so an auditor can check the links |
| `client.middle_office.entry_for_posting(entry)` | The Python SDK turns a derived entry into the posting shape |
# NAV and PnL Attribution
## The NAV identity is enforced, not reported
```
closing NAV = opening NAV
+ subscriptions - redemptions
+ income - expenses
+ realised PnL + unrealised PnL
```
The closing NAV is derived from that identity. Where an independently computed closing NAV is supplied, the two are checked against each other **exactly**. Because every amount is an integer count of minor units the comparison is equality rather than a tolerance, and a difference of one minor unit is reported.
## Returns are measured per unit
A subscription increases total NAV without a change in value per unit. Return is therefore reported per unit rather than on total NAV.
## Deal cash is realised PnL, not income or expense
A coupon received on a swap is realised PnL on the position; a coupon paid away is negative realised PnL. Neither is income or expense in the fund accounting sense. Recording a payment out as an expense would overstate the fund's expense line.
Income and expense are reserved for items that are not position PnL: interest on the cash balance, a rebate, the management fee, audit and custody.
## Fees
Management fees accrue on the day count the offering document states, which is why the convention is a parameter rather than assumed. Performance fees are charged against a **high water mark**: below the mark the fee is zero and the mark does not move, and when a fee crystallises the mark advances net of the fee rather than to the gross level, so the same gain is not charged twice.
## Attribution ties out
PnL is decomposed into interest, financing, dividend, credit, realised, unrealised, FX and fees, at **deal, book and fund level**, and the levels sum exactly.
There is one attribution routine. The book view is the deal-level result grouped by book, and the fund view is the same result grouped again, so the three levels are not computed independently.
## A period includes both of its dates
A period runs from `period_start` to `period_end` and contains both. A coupon paid on the first of the month belongs to that month, so the NAV roll and the attribution for one period are struck over the same cash. Periods are stated as the spans they name -- 1 to 30 September, then 1 to 31 October -- and they tile without a gap.
## Interest is not counted twice
```
interest PnL over a period = coupons paid or received in the period
+ closing accrual
- opening accrual
```
A coupon that accrued last period and settles this one contributes nothing to this period beyond the days it actually accrued.
## Unrealised means the change, not the balance
Unrealised PnL for a period is the movement in fair value, not the closing fair value. Adding the balance would restate every prior period's gain into this one.
## Actions and reads
| | |
|---|---|
| `record_fund_flow { fund, flow_date, kind, amount, units?, description? }` | A SUBSCRIPTION or REDEMPTION (with units), an EXPENSE, a FEE or non-position INCOME. `amount` is a positive magnitude; the kind carries the direction, so a redemption cannot be typed as a negative subscription |
| `roll_nav { fund, period_start, period_end, opening_nav?, currency?, opening_units?, realised_pnl_adjustment?, unrealised_pnl_change?, independent_closing_nav? }` | The roll from the recorded flows and the deal cash of the period. A first period needs `opening_nav` and `currency`; later periods open on the prior close. `realised_pnl` itself cannot be passed: it is derived from the cash |
| `sign_off_nav`, `reopen_nav_period { note }` | The second signature, and the administrator's reopening with a reason |
| `attribute_pnl { from, to, currency, fund? / book? / deal_id?, opening_valuations?, closing_valuations?, opening_accruals?, closing_accruals?, fees? }` | The decomposition at deal, book and fund level; `ties_out` is the engine's own check |
| `GET /rest-api/v1/fund-periods` | Every rolled period with its lines as `_minor` strings |
## Translation is an operation, not a default
A fund whose deals settle cash in more than one currency is rolled in its base currency, and every foreign amount crosses into it through one explicit step. The roll takes `fx: [{from, to, rate}]`; absent, it uses the FX quotes on the valuation policy, the desk's own stated market; absent both, it refuses naming the pair. Each foreign currency's deal cash is translated at that rate, quantised once at the fund currency's scale with banker's rounding, and the period row records `fx_translations`: the pair, the rate, where the rate came from, how many flows and the amounts before and after. Nothing is chained through a third currency: a EUR/USD rate is never derived from EUR/CHF and USD/CHF.
Attribution takes the same `fx` for the period and `fx_opening` for the period start. Flows, fees and closing positions translate at the period rate; an opening position translates at the opening rate, and its move from the opening rate to the period rate is the FX component. A EUR position unchanged in EUR while EUR/CHF moves from 0.94 to 0.96 shows CHF 2,000 per EUR 100,000 of FX and nothing unrealised; a EUR 1,000 gain on top shows as EUR 1,000 at the closing rate. The two always sum to the closing value at the closing rate less the opening value at the opening rate, to the minor unit, and the levels still tie out. Where a foreign opening position exists and no opening rate was stated, the attribution is refused rather than folding the FX effect into the unrealised line.
## Funds and books
A book of record declares its funds (`create_fund { code, name, base_currency, inception_date? }`) and the books under them (`create_book { fund, code, name, currency, strategy? }`), and reads them with `list_funds`. Once any fund is declared, a deal is booked only into a declared fund and a declared book under it, and the booking is refused naming what is missing; a scope that has declared nothing keeps free-text funds and books. A book's currency is the one its deals are attributed in; the fund's base currency is the one its NAV is struck in.
# Middle Office API
Two surfaces on the REST adapter, `https://api.cpz-lab.com/functions/v1/rest-api/v1`, not the CPZ gateway (see [API Introduction](/docs/api/introduction)). Authenticate with an API key from Settings.
## Reading the book
Standard REST resources under that base URL, supporting the same filtering,
sorting and pagination parameters as the rest of the API.
| Resource | Returns | Filters |
|---|---|---|
| `deals` | Booked OTC deals | fund, book, deal_id, product_kind, status (LIVE for draft and confirmed) |
| `lifecycle-events` | The event calendar | deal_id, kind, status, from, to |
| `cash-flows` | Determined cash flows | deal_id, fund, book, from, to, unsettled |
| `journal-entries` | Ledger entries with their chain hashes | deal_id, from, to |
| `fund-periods` | NAV roll-forwards | fund, status |
| `securities` | The book's own security master | key, instrument_type, status |
| `cash-accounts`, `cash-balances` | The funds' cash accounts, and the balances custodians stated (`balance_minor` as a string) | fund, currency; account, from, to |
| `statement-mappings` | Statement layouts, every version | name, kind |
| `statement-feeds`, `statement-files` | The feeds; the file ledger (every file received and its outcome) | status; status, feed, from, to |
| `mandates` | The bounds the book agreed to trade inside, with their committed hashes | status, fund |
| `corporate-actions`, `corporate-action-effects` | Issuer announcements; what applying one did to each holding | status, identifier, action_type, from, to; deal_id, action_id |
| `share-classes`, `class-periods` | The classes a fund issues; the per-class roll with its allocation method | fund, status; fund, class, from, to |
| `investors`, `investor-transactions` | The holders; their dealing with units, amounts and NAV per unit | status, kind; fund, investor, kind, from, to |
| `expense-schedules`, `expense-accruals` | The fees a fund carries; what each accrual run produced, with the net assets it was charged on | fund, status; fund, schedule, from, to |
| `statements` | Imported statements without their lines and text (those are the documents of record, read through the action) | netting_set, kind, from, to |
| `breaks` | The reconciliation break queue | netting_set, status, kind, from, to |
These are read only. Writing a deal straight into the table would skip the
engine's validation of its terms, its derived schedule and its terms hash, and
writing a journal entry directly would skip the per-currency balance check, so
every write goes through the action endpoint below.
Reading requires the `trading_credentials` or `orders` scope.
## Minor units are strings
Every monetary field ends in `_minor` and is returned as a **decimal string**,
not a number:
```json
{ "amount_minor": "-125000000", "currency": "USD" }
```
These are 64-bit integers. A JSON number is parsed into a double by most
clients, which alters any value above 2^53. Parse the field into a 64-bit
integer or a bigint.
## Which book
Every read and every action accepts `entity_id`: as a query parameter on a read, in the body of an action. Without it the call is on the key holder's personal book; with it, on that entity's book, and the key holder must be an active member. A non-member is refused by name.
## Operations
`POST /rest-api/v1/middle-office` with an `action` in the body. Requires the
`orders` scope. Actions marked * change the book: a VIEWER of an entity is refused, and each lands on the audit log.
**Deals**
| Action | |
|---|---|
| `book_deal` * | Validate economic terms, derive and hash them, store the deal |
| `list_deals` | The book, by fund, book, status (LIVE for draft and confirmed), product kind or identifier |
| `get_deal` | One deal with its stored events and flows |
| `approve_deal` *, `reject_deal` * | Four-eyes on a draft |
| `record_knock` * | A barrier or touch level traded: verified against the level, stored, moot events cancelled, calendar regenerated |
**Lifecycle**
| Action | |
|---|---|
| `generate_schedule` | Accrual periods from schedule terms, adjusted and unadjusted |
| `generate_events` * | The lifecycle calendar for a deal over a window, and what each waiting event needs |
| `record_observation` * | A fixing, price, dividend or auction recovery, with its source |
| `fixing_coverage` | For every index the pending events name: fed on schedule or keyed by hand and why; the schedule and the last run |
| `set_valuation_policy` *, `get_valuation_policy` | The market the schedule marks with, the reconciliation tolerance, the fixings switch |
**Cash**
| Action | |
|---|---|
| `project_cashflows` * | Determined flows and pending events, separately |
| `cash_ladder` | Net cash by settlement date and currency |
| `accrue` | Interest accrued as of a date |
**Ledger**
| Action | |
|---|---|
| `entries_for_flows` * | Turn settled cash flows into balanced journal entries |
| `post_journal` * | Post a balanced entry into the hash-chained journal |
| `reverse_entry` * | Correct a posted entry with a reversing entry and a required reason |
| `rechain_journal` * | OWNER or ADMIN only. Rewrites the chain hashes from the stored entries; see [The General Ledger](/docs/middle-office/ledger) |
**Fund**
| Action | |
|---|---|
| `record_fund_flow` * | A subscription or redemption (with units), an expense, a fee or non-position income; a positive magnitude, the kind carries the direction |
| `roll_nav` * | The NAV roll-forward for a period; `fx` translates deal cash in other currencies and the period records the rates used |
| `sign_off_nav` *, `reopen_nav_period` * | The second signature, and the reopening with a reason |
| `attribute_pnl` | Attribution at deal, book and fund level; `fx` and `fx_opening` translate foreign amounts and separate the FX component |
| `create_fund` *, `create_book` * | Declare a fund with its base currency, and a book under it with the currency its deals are attributed in |
| `list_funds` | The declared hierarchy with deal counts per book |
| `upsert_security` * | Declare or update an instrument in the book's own master; identifiers verified by the engine, one identifier one security |
| `list_securities` | The book's master, by type, status, text or an exact identifier |
| `resolve_security` | One instrument by key or identifier, from the book's master then the global one then OpenFIGI, saying which |
| `create_cash_account` *, `record_cash_balance` * | A fund's cash accounts, and the balances the custodian stated |
| `create_otc_mandate` *, `commit_otc_mandate` * | A mandate's terms while it is a draft; committing freezes and hashes them |
| `suspend_otc_mandate` *, `revoke_otc_mandate` * | A committed mandate stops binding, with a reason on the record |
| `list_otc_mandates`, `check_mandates { as_of?, deal? }` | The mandates; the live book against them, and a deal before it is booked |
| `list_cash_accounts`, `treasury_ladder` | The accounts with their latest stated balance; per account the statement against the book, the difference, and the projected balance day by day |
**Valuation and risk**
| Action | |
|---|---|
| `mark_deals` * | Mark every deal in scope to fair value; pending marks name their missing inputs |
| `list_valuations` | The stored marks |
| `stress_book` | Full-repricing stress, per scenario, per book and per deal, unpriced deals named |
| `risk_scenarios` | The platform's reference stress scenarios translated into this book's terms, axis by axis, with what could not apply and why |
| `var_shifts` | Historical shifts from the book's own recorded closes and fixings for value at risk: one scenario per observed move over the horizon, with the window, the factors and the dates that produced none |
| `value_at_risk` | Historical-simulation VaR by full repricing over supplied shifts |
**Reconciliation and collateral**
| Action | |
|---|---|
| `import_statement` * | A statement, verbatim: canonical lines, or a source's text through a named layout (VALUATION, POSITION or CASH) |
| `upsert_statement_mapping` * | A statement layout: columns, date and number formats, maps, skip rules; a new version each save |
| `list_statement_mappings`, `preview_statement_mapping` | The layouts; what a layout makes of a file (CSV, XLSX, fixed width, MT940, MT535), nothing stored |
| `upsert_statement_feed` * | A feed: which inbox files are read through which layout, for which netting set and fund, dated how |
| `upload_statement_file` *, `ingest_statement_files` * | A file into the inbox and through the feeds at once; the inbox run now |
| `list_statement_feeds`, `list_statement_files` | The feeds; the ledger of every file received and its outcome |
| `run_reconciliation` * | Population, economics, valuations; one root cause, one break |
| `run_position_reconciliation` * | A custodian's holdings against the fund's listed holdings, matched on identifier, quantities exact |
| `list_breaks`, `resolve_break` * | The break queue and a resolution with its note |
| `collateral_calls` * | CSA margin calls per netting set from the recorded marks |
| `settle_collateral_call` * | The posting that settles an agreed bilateral call |
| `record_broker_margin` *, `margin_calls` | What a broker stated, and the whole margin queue for a date |
| `agree_margin_call` *, `dispute_margin_call` *, `settle_margin_call` * | A call agreed in whole or in part, disputed with its reason, or settled |
| `declare_market_data_source` *, `set_market_data_policy` *, `market_data_policy` | Who supplies a number, which source wins, and how old it may be |
| `record_market_quotes` *, `resolve_market_data` *, `market_data_status` | What each source said; run the policy; what could not be verified and why |
| `set_formula_payoff` *, `evaluate_formula_payoffs`, `record_formula_valuations` * | A payoff the engine has no product for, computed from the book's own observations |
| `record_external_mark` *, `valuation_provenance` | Somebody else's number, held as theirs; how the book's value was arrived at |
| `list_reports`, `run_report` *, `list_report_runs`, `report_run`, `compare_report_runs` | The catalogue, a report produced and kept, and what changed between two runs |
| `create_confirmation` *, `move_confirmation` *, `confirmation_status` | The trade both sides agree they did, and the clock on agreeing it |
| `assign_uti` *, `record_counterparty_uti` *, `match_utis` *, `list_uti_matches` | One identifier per trade, generated by whichever side the waterfall names |
| `build_regulatory_submission` *, `mark_submission_sent` *, `record_acknowledgement` * | The trade in the shape a repository accepts, and what came back |
| `record_administrator_nav` *, `tie_out_nav` *, `sign_off_tieout` * | The official NAV against ours, with a named reason for every franc |
| `snapshot_book` *, `list_book_snapshots`, `reproduce_as_of` | What the book said when it said it, and what has moved since |
| `operations_health` | Every operational check for a date; a check that could not run is unknown, never passing |
## Money in an action
An action's money is an object, never a number: `{ "minor": "-125000000", "currency": "CHF" }`, minor units as a decimal string. It is the same value the REST columns carry as `_minor`. The Python SDK reads it as `Decimal` at the currency's scale and refuses to send a float.
## What is scheduled
Two jobs run against every book that has a valuation policy, each act attributed to the person who set the policy:
```
07:30, 13:30 UTC, weekdays middle-office-fixings
for each book with a policy (fixing_feed on):
events PROJECTED, waiting on an INDEX_FIXING dated <= today
-> SOFR from the New York Fed, €STR from the ECB, one fetch per source
-> record_observation for each published date not already on the book
-> generate_events for each deal that was waiting
-> one audit row: fixings_feed
05:30 UTC, daily middle-office-daily-cycle
for each book with a policy:
mark_deals(today, policy.inputs) -> audit: daily_cycle_mark
for each statement imported for today:
run_reconciliation(netting_set, today) -> audit: daily_cycle_reconcile
collateral_calls(today)
```
A date the administrator has not published stays pending; a fixing the desk recorded is never overwritten; a source that cannot be reached is named on the run and in the log. SARON and EURIBOR are not fetched (SIX and EMMI license them; the free republication of SARON is rounded), and `fixing_coverage` says so per index.
## Errors
A failure is an HTTP status other than 200 with a JSON body `{ "error": "..." }` carrying the engine's or the function's own message, naming the offending input: `field "notional" must be positive, got -3`, `the redemption on 2026-08-14 is booked positive`, `not an active member of this entity`. 400 is a refused input, 401 an unauthenticated call, 403 a role that may not, 500 a store failure. A failed call does not return an empty array, a zero or a null in place of a result, and nothing is retried on the caller's behalf: every write is a booking.
## From Python
```python
from decimal import Decimal
from cpz import CPZClient
mo = CPZClient().middle_office.for_entity(entity_id)
booked = mo.book_deal(terms)
for item in mo.generate_events("D-1", "2026-01-01", "2028-12-31").outstanding:
print(item.required_input.describe()) # "SARON fixing for 2026-06-16"
cash = mo.project_cashflows("D-1", "2026-01-01", "2028-12-31")
print(cash.flows[0].amount) # -90,123.45 CHF, a Decimal underneath
mo.record_fund_flow("ALPHA", "2026-07-01", "SUBSCRIPTION", (Decimal("15000000"), "CHF"), units=14850)
nav = mo.roll_nav("ALPHA", "2026-07-01", "2026-09-30")
report = mo.historical_var("2026-09-30", 0.99)
```
One typed method per action and per read table, `AsyncCPZClient().middle_office` for the same in async code; see the SDK's own documentation.
# Valuation and Marks
Every OTC deal on the book can be marked to fair value through the same compiled engine the risk system and the backtest runner price with. All eight products are covered.
The schedule-dominated products -- swaps, the CDS premium leg -- are valued by discounting the deal's own generated schedule, not a stylised lookalike. A booked swap has a short stub, holiday-adjusted period ends and a payment lag, and it settles on those dates and no others; marking an approximation of it surfaces later as unexplained PnL.
## A missing input is pending, never zero
A deal that cannot be marked names every input it lacks: the discount curve, the forecast curve, a fixing on a running period, an implied volatility, a cost of carry, a credit spread, the FX rate a cross-currency deal converts at. It never returns zero and never substitutes a default. A zero mark is indistinguishable from a genuinely flat position, and both would flow into NAV.
## Curves are built from the market's own quotes
The market does not quote zero rates: it quotes deposits, FRAs and par swap rates. A curve arrives as those instruments and the engine bootstraps the zeros itself -- sequentially, with the par-swap pillars solved through the same swap pricer the marks use, so the finished curve reprices its own input quotes to machine precision by construction. Two instruments pinning the same maturity are a named contradiction, not an average. Hand-built zero pillars and flat rates remain accepted for quick work.
`POST /rest-api/v1/middle-office` ([Middle Office API](/docs/middle-office/middle-office-api)) with `action: "mark_deals"`:
```json
{
"action": "mark_deals",
"as_of": "2026-09-30",
"inputs": {
"discount_curves": [{ "currency": "CHF", "instruments": [
{ "kind": "DEPOSIT", "maturity_years": 0.5, "rate": 0.0138 },
{ "kind": "FRA", "start_years": 0.5, "end_years": 1.0, "rate": 0.0140 },
{ "kind": "SWAP", "tenor_years": 5, "rate": 0.0145 }
] }],
"forecast_curves": [{ "index": "SARON", "flat_rate": 0.0142 }]
}
}
```
Cross-currency swaps mark on two curves: each leg discounted on its own currency's curve, the principal exchanges the lifecycle will actually settle valued alongside the coupons, the pay side converted at spot. A quoted cross-currency basis is applied as a spread on the pay currency's curve, and the mark's method string states whether it was marked with basis or single-basis -- an auditor can always tell which number this was.
Every deal in scope is answered. A valued mark carries exact minor units, the unrounded model value it was quantised from, and the routine that produced it. A pending mark carries no amount at all and the named list of what to fetch.
## Stress testing is full repricing
A stress scenario shifts the market inputs -- parallel curve moves, vol shocks, credit spread multiples, FX and price moves, or all of them at once -- and re-marks every deal through the same routines the daily mark uses. The swaption goes back through its vol model, the CDS back through the hazard curve. The scenario P&L is the difference between two real marks, never a greeks-times-move approximation, and a deal a scenario cannot price is named in the result rather than silently dropped from the total. `action: "stress_book"` runs the house standard set (rates, vols, credit, FX, equity, one combined risk-off) or any custom scenario list, and returns the P&L per deal, per book and per currency.
A scenario states its shifts the way a desk does: a parallel `rate_shift` (0.01 is +100bp) or `rate_shifts` per curve, a relative `vol_shift` or absolute `vol_points` (0.30 is +30 vol points; a lognormal SABR set takes it on alpha, a CEV set is refused by name because an absolute shift on its alpha needs the forward), a `credit_spread_multiplier`, `fx_shift` or `fx_shifts` per pair, `price_shift` or `price_shifts` per underlying. Per-key shifts override the global one for that key, so one scenario can be a day the market actually had.
**The Risk tab** runs the platform's reference scenario library against the book: each reference scenario (2008, COVID, the 2022 rate shock, equities ±10/20%, rates ±100/200bp, USD +10%, vol +20 points, …) is translated into the engine's terms and the translation is shown axis by axis -- an equity shock becomes a price shift on every non-FX underlying the book references, a USD shock moves only the quotes that carry USD on the side USD is on, a crypto or crowding shock is stated as not applicable rather than silently dropped. `action: "risk_scenarios"` returns the same translation for a program. The desk adds its own shifts in basis points, percentages and vol points, and reads the P&L by scenario, by book and by deal, with the deals a scenario could not price and why.
## Smiles and surfaces
A volatility can be one number, a strike smile, or a SABR parameter set. A smile is interpolated at the deal's own strike and REFUSED outside the quoted range -- extrapolated wings are where option books die quietly, so a strike beyond the smile is a pending mark naming the range, never a guess. A SABR set prices every swaption strike off one surface, and the mark's method string says which model produced it.
## Value at risk
`action: "value_at_risk"` is historical-simulation VaR by full repricing: each supplied shift is one day the market actually had, the book is re-marked under every one, and the loss quantile comes from that real distribution -- no covariance matrix, no normality assumption, no delta approximation. Expected shortfall rides along, in exact minor units. A deal that failed to price under any scenario is named on the report with the count it missed: a VaR over a partial book is a partial VaR, and the report says so.
Where the history comes from is stated too. `action: "var_shifts"` builds the shifts from the book's own recorded closes and fixings: one scenario per observed move over the horizon (overlapping windows of actual moves, never one-day moves scaled by the square root of time), prices and FX moving relatively, fixings absolutely and carried to their forecast curve and their currency's discount curve. The reply names the window, how many scenarios each factor reached, and the dates that produced none, so a VaR is never quoted without the record it was struck on. The Risk tab strikes it at 95, 97.5 or 99% over a chosen lookback and horizon, and refuses to strike one on fewer than two moves.
## Mark inputs, every field
```json
{ "discount_curves": [ { "currency": "CHF", "flat_rate": 0.005 } ],
"forecast_curves": [ { "index": "SARON", "flat_rate": 0.0045 } ],
"volatilities": [ { "key": "SMI Index", "vol": 0.17 }, { "key": "SWAPTION:CHF", "vol": 0.0055 } ],
"smiles": [ { "key": "NESN SW", "points": [ { "strike": 90, "vol": 0.21 }, { "strike": 100, "vol": 0.18 } ] } ],
"sabr": [ { "key": "SWAPTION:CHF", "alpha": 0.006, "beta": 0.5, "rho": -0.2, "nu": 0.4 } ],
"carry": [ { "underlying": "SMI Index", "carry": -0.02 } ],
"credit_spreads": [ { "reference_entity": "Deutsche Telekom AG", "spread": 0.0085 } ],
"realised_variance":[ { "underlying": "SMI Index", "variance": 0.0225 } ],
"fx_rates": [ { "from": "EUR", "to": "CHF", "rate": 0.94 } ],
"correlations": [ { "a": "NESN SW", "b": "ROG SW", "rho": 0.55 } ],
"xccy_basis": [ { "pay": "USD", "receive": "CHF", "spread": -0.0015 } ] }
```
A curve is a flat rate or a list of instruments the engine bootstraps. Carry is demanded, not assumed: defaulting it to the risk-free rate would assert the underlying pays no dividend. A smile is interpolated at the deal's strike and refused outside its range. Correlations are keyed by the unordered pair. Observations the desk has recorded (fixings, closes, dividends, recoveries) are the market unless the caller passes its own `market`.
## What a mark carries
Every mark has `fair_value` as exact money, `fair_value_exact` unrounded and `method` naming the routine. Some carry `detail`: a bond's clean and dirty price, accrued, outstanding face, issuer spread, ICMA yield, Macaulay and modified duration, convexity and DV01; a barrier's stated and effective level and knock state; a basket's method, standard error, minimum correlation and vol dispersion.
## Actions and reads
| | |
|---|---|
| `mark_deals { as_of, inputs?, fund?, book? }` | Mark every deal in scope; each mark is stored on the deal and the date |
| `list_valuations { as_of?, deal_id? }` | The stored marks |
| `set_valuation_policy { inputs, tolerance?, fixing_feed? }`, `get_valuation_policy {}` | The market the schedule marks with; the reconciliation tolerance switches the scheduled reconciliation on; `fixing_feed` switches the fixings schedule off for the book |
| `stress_book`, `risk_scenarios`, `var_shifts`, `value_at_risk` | Above |
## The record
Marks are recorded in `otc_deal_valuations`, one row per deal per date per source. ENGINE, MANUAL and COUNTERPARTY marks coexist on the same date so they can be compared rather than overwrite each other. A NAV struck on these rows stays reproducible after the curves move, which recomputing from live market data does not.
# Reconciliation and Collateral
A bilateral book is two books. The counterparty keeps its own record of every deal, and nothing forces the two to agree. EMIR article 11 and the ISDA portfolio reconciliation protocol prescribe the discipline implemented here.
## The order of comparison
1. **Population.** Deals matched by UTI where both sides carry one, else by reference. A deal on one side only is a named break, not a footnote.
2. **Economics.** Product kind, notional, currency, termination date -- compared on matched pairs only. A field the statement does not carry is not compared: "they did not say" and "they disagree" are different facts.
3. **Valuation.** Compared only where the economics agree, against the marks recorded for the statement date -- so a dispute is defended with the number the fund actually used. One root cause raises one break.
The statement carries an explicit sign perspective (their side or ours), because guessing would make every valuation break's sign a coin flip. Tolerances are explicit and zero legitimately means exact-or-break. An unmarked deal whose line carries a value comes back UNVERIFIED, never silently matched.
## The break queue
Every difference lands in `otc_recon_breaks` as a typed break: MISSING_OURS, MISSING_THEIRS, ECONOMIC_MISMATCH, VALUATION_BREAK, UNVERIFIED. A break is resolved with a note or not at all -- the table itself refuses a resolution without one, because the note is the desk's recorded judgement.
## Statement layouts
A counterparty, custodian or administrator sends statements in its own layout: its own column names, dates as 31.12.2026 or 12/31/2026, numbers as 1'234.56 or 1.234,56 or (1,234.56), notionals in thousands, product codes and account references of its own, a title row above the header and a TOTAL row below the data. A statement layout (`upsert_statement_mapping`) declares all of that once, by name: which source column is which canonical field (by header name, by position for a file without a header, or a constant), the delimiter, the date format, the number format including how a negative is written (minus, parentheses, trailing minus, DR/CR), a power-of-ten scale, a sign flip, a value map for product codes or account references, and rules for rows to skip. Every field of a layout is checked when it is saved and a refusal names the field. Saving is a new version; earlier versions stay, because the statements imported under them were read that way.
Three canonical kinds. **VALUATION** is the counterparty's view of the OTC deals in a netting set, what `run_reconciliation` reads. **POSITION** is a custodian's holdings by identifier and quantity; `run_position_reconciliation` matches them to the fund's listed holdings on identifier and compares quantities exactly, summing a custodian's sub-account lines and the book's holdings across books, and raises MISSING_OURS, MISSING_THEIRS, ECONOMIC_MISMATCH or, for a holding that carries no identifier or is listed under two, UNVERIFIED. **CASH** is a custodian's or broker's balances per account; importing one records the balances on the fund's cash accounts, so the treasury ladder can strike the difference.
A layout also states the file's format where it cannot be told from the file itself: a CSV in a stated encoding with a stated or detected delimiter, an Excel workbook (a sheet by name or index; numbers arrive as plain decimals and dates as ISO regardless of the cell's display format), a fixed-width print file (columns by position; the adapter writes the header from their names), a SWIFT MT940 customer statement (one row per page: account, opening and closing balance signed by their D/C mark, closing date, currency, available balance, count of entries) or an MT535 statement of holdings (one row per instrument block: safekeeping account, statement date, ISIN and description, aggregate quantity and its type, price, holding value). SWIFT amounts and workbook numbers reach the mapping as plain decimals; a CSV's cells reach it exactly as written.
`preview_statement_mapping` shows what a layout makes of a file without storing anything. `import_statement { mapping, text }` or `{ mapping, file: { name, content_base64 } }` reads the source's text or file through the layout and stores both the document and the lines (a workbook is archived in storage as the document of record): numbers are parsed to exact decimals and never pass through a float on the way, dates are checked against the calendar, and a file with one bad row is refused whole with every problem named by row, column and value, because a statement imported with a gap is a reconciliation that cannot find the gap.
## The file channel
A statement does not have to be sent by hand either. Every book has an inbox in the `otc-statements` storage bucket (`inbox//`), where a custodian's SFTP bridge, an administrator's delivery job or a desk's upload drops files. A feed (`upsert_statement_feed`) says whose a file is by its name: a glob such as `ZKB_HOLDINGS_*.xlsx`, the layout to read it through, the netting set or custody account and the fund it covers, the perspective for a valuation file, and where the statement date comes from (a date in the file name in a stated form, the date a SWIFT message states in its content, or the day the file was received). Every fifteen minutes on weekdays the function runs each inbox through its feeds; `ingest_statement_files` runs one now and `upload_statement_file` drops a file in and runs it at once. A file that matches no feed, or two, is named, never guessed; a file whose hash matches one already imported is a duplicate, never a second statement; a file with one bad row is refused whole with every problem named. Every file's outcome is on the ledger (`list_statement_files`: imported with the statement it became, refused, unmatched, duplicate) and the file itself moves out of the inbox into `processed/`, `refused/` or `unmatched/` under the scope. An import through a feed is attributed to the feed's author, because a schedule has no hands.
## Feeds
A statement does not have to be pasted. `import_statement`, `run_reconciliation` and `list_breaks` are plain API actions on the public REST surface, so a counterparty, administrator or internal job can push statements and pull breaks programmatically on any schedule. The engine treats a pushed statement exactly like a pasted one: verbatim storage, re-parsed on every run. Statements, layouts and breaks are also readable over REST at `/rest-api/v1/statements`, `/rest-api/v1/statement-mappings` and `/rest-api/v1/breaks`.
## Collateral
The CSA margin call is computed per netting set from the recorded marks: threshold, minimum transfer amount, independent amount, and rounding that never works against the party owed collateral. A set whose master agreement has no CSA reports its exposure and moves nothing. A call computed on a partially marked set names its unmarked deals on the record itself.
Settlement is the posting. `settle_collateral_call` turns an agreed call into a balanced journal entry -- cash against collateral received, or collateral posted against cash -- and the call row cannot claim settled without that entry, by database constraint. A margin call that "settled" without moving a ledger did not settle.
## Margin: one queue for every call
A fund is margined twice. Its bilateral book is margined under a CSA, which the engine computes from its own marks. Its listed and cleared book is margined by a broker, which states a requirement rather than computing one. Those are the same operational job -- somebody has to agree, dispute or pay today -- so they are one queue and not two, and a desk that has to look in two places to answer "what do we owe today" will eventually miss one.
`record_broker_margin { account, as_of, margin_type?, requirement, collateral_held?, minimum_transfer?, rounding?, source }` records what a broker stated. The call is struck from those numbers by the engine, never typed in: a desk cannot record a transfer the agreement does not support. The margin must sit on a declared cash account, in that account's currency, and the call must name the report that stated it, because an unsourced call cannot be disputed.
**Below the minimum transfer amount, nothing moves.** That is what a minimum transfer amount is for, and a call that ignores it generates settlement traffic nobody agreed to. **Rounding never works against the party owed collateral**: a call in rounds up to the increment and a call out rounds down, so the rounding favours the receiver either way.
**Initial and variation margin are two calls, not one.** They are separate obligations on the same account, and netting them is what the segregation rules forbid.
**A dispute keeps the call open.** `dispute_margin_call { id, reason, our_amount? }` records what the fund makes the number beside what was called, with the reason it differs. It cancels nothing and settles nothing. `agree_margin_call { id, amount? }` agrees the call, or the part of it the fund accepts: the difference is recorded as disputed rather than discarded, and **the agreed part still settles**, because holding the whole transfer hostage to a small difference is how a small dispute becomes a large one.
**A recompute voids a stale agreement.** Recomputing a date's bilateral calls leaves any already settled call untouched, and where a recomputed number differs from the one that was agreed, the agreement is cleared and the change recorded: the counterparty agreed to a figure that no longer exists.
Every transition is on the record. `margin_call_events { id }` is the trail behind one call -- issued, agreed, disputed, settled, recomputed -- with the amounts and the reasons. A dispute without its history is an assertion, and the CSA dispute procedure is written on the assumption that the history exists.
`margin_calls { as_of? }` is the whole queue for a date, both sides together, with the totals per currency of what the fund is to post, what it is to receive, and what remains contested. With no date it answers for the latest date a call was struck.
## Actions
| | |
|---|---|
| `import_statement { netting_set, as_of, perspective, lines }` | A counterparty statement, line by line, as `COUNTERPARTY` or `OURS` sees the signs; stored verbatim |
| `import_statement { netting_set, as_of, mapping, text, fund?, perspective? }` | The source's text read through a named layout; a POSITION or CASH statement names the fund it covers; a CASH statement's balances are recorded on the accounts |
| `upsert_statement_mapping { name, spec, notes? }`, `list_statement_mappings`, `preview_statement_mapping { mapping | spec, text | file }` | A layout, versioned; the layouts; what a layout makes of a file, nothing stored |
| `upsert_statement_feed { name, mapping, netting_set, fund?, perspective?, filename_pattern, as_of_rule?, filename_date_format? }`, `list_statement_feeds` | Which inbox files are read through which layout, and dated how |
| `upload_statement_file { file }`, `ingest_statement_files`, `list_statement_files { status?, feed? }` | A file into the inbox and through the feeds; the inbox run now; the ledger of every file and its outcome |
| `run_reconciliation { netting_set, as_of, tolerance? }` | Population, then economics, then valuations; one root cause raises one break |
| `run_position_reconciliation { netting_set, as_of }` | A custodian's holdings against the fund's listed holdings, matched on identifier, quantities exact |
| `list_breaks { netting_set?, as_of?, status? }`, `resolve_break { break_id, note }` | The queue, and a resolution that carries its judgement |
| `collateral_calls { as_of }` | Every netting set's call under its CSA, unmarked deals named on the record |
| `settle_collateral_call { netting_set, as_of }` | The posting that settles an agreed bilateral call |
| `record_broker_margin { account, as_of, margin_type?, requirement, collateral_held?, minimum_transfer?, rounding?, source }` * | What a broker stated, struck into a call under the minimum transfer amount and rounding |
| `margin_calls { as_of?, from?, to?, status?, source? }` | Every call for the date, bilateral and broker together, with the totals per currency |
| `agree_margin_call { id, amount? }` * | The call agreed, in whole or in part; the difference stays on the record |
| `dispute_margin_call { id, reason, our_amount? }` * | What the fund makes it, beside what was called. Nothing settles |
| `settle_margin_call { id }` * | The posting that settles a call, whoever made it. Settles the agreed part |
| `margin_call_events { id }` | The trail behind one call: issued, agreed, disputed, settled, recomputed |
| `declare_market_data_source { code, name, source_kind, contact? }` *, `list_market_data_sources` | Who this book accepts numbers from, with somebody to ask about them |
| `set_market_data_policy { rules }` *, `market_data_policy` | The whole policy: sources in order, how stale is too stale, how many must agree |
| `record_market_quotes { quotes }` * | What each source said, kept apart from what the book decided to use |
| `resolve_market_data { as_of, keys? }` * | Run the policy: only what it verifies becomes an observation |
| `market_data_status { as_of? }` | What resolved, what did not, and the quotes behind each finding |
| `set_formula_payoff { deal_id, expression, currency, bindings, settlement_date? }` *, `list_formula_payoffs` | A payoff the engine has no product for, parsed when it is written |
| `evaluate_formula_payoffs { as_of, deal_id? }` | What the payoffs come to, and what they wait on. Nothing is marked |
| `record_formula_valuations { as_of, deal_id? }` * | The payoffs marked, into the table every other mark lives in |
| `record_external_mark { deal_id, as_of, fair_value, source, method, level }` * | Somebody else's number, held as theirs, with its place in the hierarchy |
| `valuation_provenance { as_of, fund? }` | Modelled, computed, supplied or unmarked, per currency and by level |
| `list_reports` | The catalogue: what this book produces and what each one needs |
| `run_report { report, as_of, parameters }` * | The report, produced and kept, incomplete sections named |
| `list_report_runs { report?, as_of?, limit? }`, `report_run { id }` | Every run; one run whole, as it was produced |
| `compare_report_runs { before, after }` | What changed between two runs of the same report on the same date |
| `create_confirmation { deal_id, method, channel, terms_summary }` * | The draft, against a deal that exists |
| `move_confirmation { deal_id, to, reason? }` * | One legal move, recorded before it is applied |
| `confirmation_status { as_of? }` | What is unconfirmed and how late, counted from the trade date |
| `assign_uti { deal_id }` * | Who generates under the waterfall, and the identifier where that is this book |
| `record_counterparty_uti { deal_id, uti }` * | Theirs, checked for shape and uniqueness, never overwriting a different one |
| `match_utis { counterparty, as_of, theirs }` *, `list_uti_matches` | Their list against ours, kept, with anything on two of our trades named |
| `build_regulatory_submission { as_of, regime?, action_type? }` * | Every report for the date, complete or not, with the missing fields named |
| `regulatory_submission { id }`, `list_regulatory_submissions` | One submission with its file; every submission built |
| `mark_submission_sent { id }` * | It went. Only the complete reports go |
| `record_acknowledgement { id, rejections? }` * | What the repository said, per trade, in its own words |
| `record_administrator_nav` *, `list_administrator_navs` | The official NAV, held as the administrator's |
| `tie_out_nav { fund, as_of, explanations?, tolerance? }` * | Our period against theirs, with the residual computed |
| `list_nav_tieouts { fund?, as_of?, ties_out? }`, `sign_off_tieout` * | Every tie-out; a signature, refused unless it ties |
| `snapshot_book { fund, as_of, reason, knowledge_date? }` * | The figures as they stand, hashed, with the date they were known on |
| `list_book_snapshots { fund?, as_of? }` | Every snapshot, with what it is about and what was known then |
| `reproduce_as_of { fund, as_of, knowledge_date? }` | Recompute against the snapshot: what moved, what arrived late, what changed |
| `operations_health { as_of?, fund? }` | The runbook answered: every check, what it found, and what to do about it |
## The runbook, made answerable
A runbook nobody can check against is a document. `operations_health { as_of?, fund? }` runs the same procedure as named checks the book evaluates on any date, so "did we do the month end" has an answer rather than a recollection.
**A check that cannot be evaluated is UNKNOWN, never PASS.** This is the rule the whole surface exists for. A health screen that goes green because a query failed is worse than no health screen: it converts an outage into a reassurance, and somebody stops looking. Every check that could not be run says why, an unknown check makes the run unclean, and an unknown *blocking* check blocks a NAV signature exactly as a failing one does, because a blocking check nobody could run is a reason to find out rather than a reason to sign.
**Severity is stated per check, not derived from a count.** Two unconfirmed trades and two unexplained NAV differences are not the same kind of problem, and a screen that adds them into one number tells nobody which to work on. Blocking means a NAV should not be signed while it is failing. Important means somebody answers for it today. Advisory is worth knowing.
The daily checks are: every live deal marked, the market data resolved under its policy, no open reconciliation break, every margin call answered, nothing unconfirmed past its deadline, the reportable trades reported, no corporate action past its ex-date unapplied, and the journal balancing. The month-end checks add the administrator tie-out and the snapshot of what was signed.
Each check carries its remedy, so the finding and what to do about it arrive together. The written procedure, including what to do when something is wrong and what is deliberately left to a person, is in `docs/middle-office/RUNBOOK.md`.
Nothing here signs anything. `safe_to_sign` says whether a signature would override a blocking check; a person still signs.
## Reading everything, and knowing when you have not
A REST read with no explicit range comes back capped. The cap is a server setting, it is a thousand rows by default, and **nothing in the response says the answer was cut short**. A book with eleven hundred deals therefore marks a thousand of them and reports success, and the hundred that were never valued are invisible in every figure downstream.
Every bulk read in the middle office now pages. A read either returns everything or raises: it pages until a page comes back shorter than it asked for, which is the only reliable signal the end has been reached, and where the data exceeds the ceiling the caller stated it raises naming the count rather than returning what fitted. An edge function quietly holding four hundred thousand rows is a different failure, not a success.
Pages are requested on a stable order, which the caller must supply. Without a total order two pages can overlap or skip, and a paged read that skips is worse than a truncated one because nothing about it looks wrong.
The reads that page are the ones that grow without bound: the book's deals, the marks for a date, the market observations, the journal entries and their lines, and the register. A reader that ignores the range is caught too, because a page longer than it asked for means the range was not applied and nothing in that answer can be trusted to be complete.
At two thousand deals the engine marks the whole book in about 130 milliseconds and posts two thousand chained journal entries in about 120, which is measured on each run and written out rather than asserted: a test that fails when the machine is busy is a test that gets deleted, and a figure nobody measured is a figure nobody can defend.
## As-of reproduction
An auditor's question is never "what is the NAV". It is "what was the NAV on 30 June, computed from what you knew on 30 June", and the difference between those two questions is where restatements live. A book that can only answer the first can never explain the second.
Two dates, and they are not the same date. **The as-of date** is what the answer is about. **The knowledge date** is what was known when it was answered: a price for 30 June that arrived on 3 July was not known on 30 June, and a reproduction that uses it is not a reproduction of anything.
`snapshot_book { fund, as_of, reason, knowledge_date? }` records the figures as they stand, with the hash of their canonical form. The figures are text throughout, because a JSON number above 2^53 loses units and a snapshot that loses units is not one. A snapshot taken a week after the date carries the later knowledge date honestly rather than pretending the extra knowledge was there at the time.
`reproduce_as_of { fund, as_of, knowledge_date? }` recomputes and puts the two side by side. It names three things, and they are different findings:
- **Every figure that moved**, with both numbers and the gap. A figure present on one side and not the other is its own kind of finding: the two runs did not even compute the same things.
- **Every fact that arrived after the knowledge date**, excluded and listed. What was left out is as much the finding as the number is.
- **Everything recorded in time and changed since.** That is what a restatement is, and it is reported separately from knowledge that simply arrived late.
A reproduction that differs from what was signed is not an error to be hidden. It is the restatement, and a routine that quietly returned the recomputed figure would be erasing the thing an auditor came to see.
A snapshot cannot be deleted. It is what the book said at a moment, and deleting one is deleting the evidence that it said it.
## Tying to the administrator
The fund administrator strikes the official NAV. This book is the shadow, and the month-end control that matters more than any other is proving the two agree: same net assets, same units, same NAV per unit, and for every franc they differ by, a named reason.
`record_administrator_nav` holds their number as theirs, with who struck it and where it came from. `tie_out_nav { fund, as_of, explanations?, tolerance? }` puts our closed period against it.
**The differences must add up.** A tie-out listing four reasons that together explain most of the gap has not explained the gap; it has explained part of it and left a residual nobody can account for. The residual is computed, stored, and enforced by database constraint: a row where the explained and the unexplained do not sum to the difference cannot exist, so nobody can sign a tie-out that never tied.
**The reasons are stated, not derived.** Why two books differ is a judgement about what happened, and a routine that guessed "price" from an amount would be inventing an audit finding. What is computed is whether the stated reasons account for the whole difference.
**A tolerance is a threshold, not an eraser.** A difference inside tolerance is still reported with its amount. What tolerance changes is whether the tie-out passes, not whether the difference existed, and it never makes a residual disappear.
**Units are exact and compared exactly.** A unit count is not money: it has eight decimal places and no minor unit, so it is subtracted on the digits. Any difference at all is a real disagreement about what has been issued, and it stops the tie-out however small it is.
**A NAV per unit is derived, and checked rather than compared.** Two books can agree on it to four decimal places and disagree on the net assets by a million, so it is used to check the administrator's own three numbers against each other. Where they disagree, that is their finding, not ours.
`sign_off_tieout` refuses anything that does not tie. Explaining the residual first is the entire control, and the database refuses the signature whatever the code does.
## Regulatory export
EMIR and FinfraG say the same thing: every derivative is reported to a repository by the end of the next working day, both sides report it under the same identifier, and the valuation is reported daily. The repository then rejects anything malformed, and a rejected report is an unreported trade until somebody fixes it.
`build_regulatory_submission { as_of, regime?, action_type? }` builds every report for the date and keeps it. Two things make that survivable, and neither is the file format.
**A field the book cannot fill is named, not blanked.** A report with an empty notional is not a report with a gap; it is a report the repository will reject, and finding that out a day later is finding it out too late. Every incomplete report is kept beside the complete ones with the fields it lacks, because a submission that quietly dropped what it could not build is one that under-reports, which is the failure the regime exists to catch.
**A value not in the code list is an error.** The repositories validate against fixed enumerations, and reporting a product this book models properly as OTHR would turn a real classification into a shrug that passes validation and says nothing. A product nobody has classified raises an error naming it.
A holding of a security and a bond held outright are not derivative contracts and are not reported at all. They are named as excluded rather than silently absent.
A counterparty identifier that fails its own ISO 17442 check digits stops the report: a report against the wrong firm is worse than a late one. The confirmation status becomes the repository's own code, so a trade confirmed on paper reports as `YCNF` and one confirmed electronically as `ECNF`, and anything short of confirmed is `NCNF` whatever stage it reached.
`mark_submission_sent { id }` records that it went, and only the complete reports go: a submission with nothing complete cannot be sent, because a file with a header and no rows is one somebody submits by accident. `record_acknowledgement { id, rejections? }` records what came back, per trade, with the repository's own code and words. A count is what a dashboard shows; the words are what somebody fixes it from.
Nothing here talks to a repository. Submitting is somebody's decision and somebody's credentials.
## Confirmations, and the clock on them
A booked trade is one firm's opinion. A confirmed trade is two firms' agreement, and the gap between them is where every large operational loss starts. EMIR puts a clock on closing it: one business day for a rates or credit trade between financial counterparties, two for the rest, and a report to the regulator of everything outstanding past five.
`create_confirmation { deal_id, method, channel, terms_summary }` writes the draft. The terms summary is required, because a confirmation nobody can read back is not evidence of what was agreed, and a channel is required for anything but paper, because paper is its own channel.
`move_confirmation { deal_id, to, reason? }` moves it, and the moves are a machine. A draft can be sent or cancelled. A sent confirmation can be affirmed, disputed, confirmed or cancelled. A **disputed one goes back to draft**, never straight to confirmed: marking it confirmed would be one side deciding the other's objection is resolved. A cancelled one does not come back. Every refused move names both states, so the caller learns what it thought and what is true, and a dispute without its reason is refused because the counterparty cannot otherwise know which term is contested. Each move is recorded before it is applied, so a failure leaves an event with no change rather than a change with no event.
`confirmation_status { as_of? }` is the queue. **The clock runs from the trade date, not from when the confirmation was sent**: a trade nobody has drafted one for is as late as one that was sent and never answered, and measuring from "sent" would let a desk stop the clock by not sending. Trades with no confirmation record at all are counted separately, because that is a different failure with a different fix. The report gives each outstanding trade its age in business days against the deadline its product carries, and counts what has been outstanding more than five, which is the figure a regulator asks for.
## One identifier, one trade
A unique trade identifier is the only thing that lets two firms say they are talking about the same trade, so it cannot be invented twice.
**The generator is decided, not negotiated.** `assign_uti { deal_id }` applies the waterfall the CFTC and ESMA both settled on: a cleared trade takes the clearing house's, then a confirmation platform's, then the sell side's, and where neither side is obviously the sell side the lower LEI generates. Every step is deterministic, so both sides reach the same answer without a conversation. Where the counterparty generates, the action says so and waits: inventing an identifier the other side will not report is worse than having none.
**A LEI is checked, not pattern-matched.** ISO 17442 puts two check digits on the end, computed mod 97, and a twenty-character string that fails them is a typo. A typo in a LEI reports the trade against the wrong firm, so it is refused naming the value.
**An identifier is never regenerated and never overwritten.** A trade that carries one keeps it, and `record_counterparty_uti` refuses to replace a different one already there, because one of the two is wrong and picking one would hide which. The book now enforces uniqueness in the database as well: two trades carrying one identifier are two trades reported as one.
`match_utis { counterparty, as_of, theirs }` puts a counterparty's list against ours: what paired, what only one side carries, and any identifier that appears on more than one of our trades. Their list is kept verbatim beside the result, because a disagreement about what they sent is the second argument nobody needs.
## Reports are produced once and kept
The numbers are already in the book. What a report adds is three things an export button does not.
**A run is dated and kept.** `run_report { report, as_of, parameters }` produces a report and records it whole: its parameters, its content section by section, and the hash of its canonical form. The pack that was signed off is still there to be read, rather than being whatever the screen shows today. Re-running produces another run rather than replacing one.
**A report that could not be produced completely says so.** A section whose inputs are missing is kept with the reason it failed and the run is marked incomplete, naming the sections. A partial report that looks complete is worse than no report, because somebody will sign it.
**Two runs of the same report on the same date can be compared.** `compare_report_runs { before, after }` names the sections that moved, with the rows that are no longer there and the rows that were not there before. Rows are compared by their whole content, so a changed row shows as one removed and one added: without a key nobody declared, saying which field moved would be a guess. The hashes settle it where they agree, and object keys are sorted in the canonical form, so a report does not appear to change every time the database returns its rows in another order.
Four reports are produced today. The **NAV pack** is what a NAV is signed off from: the period roll, the classes it was divided between, the register that holds the units, the trial balance behind it, and how each position was marked. The **portfolio valuation** is every position with its mark, how that mark was arrived at, and what it waits on where it has none. **Operations exceptions** is everything outstanding in one document: reconciliation breaks, margin calls not answered, market data that could not be verified, and corporate actions waiting on a decision. **Treasury** is cash by account against what the custodian stated.
A request is checked against what the report declares. A required parameter that is missing is refused naming it, and a parameter the report does not read is refused too, because one nothing reads is usually a typo.
## Payoffs the engine does not know
Every structured book holds notes the engine has no product for. Carried at whatever the issuer last said, they are the part of a fund nobody can recompute. There are two honest ways to hold them, and the difference belongs on every screen.
**A formula payoff is the book's own number.** `set_formula_payoff { deal_id, expression, currency, bindings, settlement_date? }` records the payoff as an expression over observations the book already holds, so anybody can recompute it from the record, today or in five years. The language is deliberately small, and everything it cannot do is a decision:
- **No clock and no randomness.** There is no `today()` and no `rand()`. The same inputs give the same number in a year, which is the point of being able to recompute a valuation at all.
- **No loops and no user functions.** Every expression terminates, and an expression that nests more than sixty-four deep or runs past four thousand characters is refused rather than stored.
- **Nothing is implicit.** An unbound name is an error naming the name, never a zero. A number added to a comparison is refused, because zero is not false here. Division by zero returns nothing rather than an infinity. A chained comparison is refused rather than guessed at.
- **A missing observation is pending.** A binding to an observation nobody recorded leaves the payoff unevaluated and says which one it is waiting for, exactly as a coupon does. Every missing name is reported at once, because a desk chasing four wants one list.
**A binding names an observation, not a value.** `{ "s_final": { "kind": "PRICE", "key": "SMI Index" } }` reads the recorded close for the date being valued; adding `"on"` fixes it to one date. A payoff carrying its own copy of a price would be a second place that price lives, and the two would eventually disagree. Every name the expression reads must be bound, and every binding must be read: a binding nothing uses is usually a renamed variable somebody forgot about.
The expression is parsed when it is written, not on the night it first has to settle. `evaluate_formula_payoffs { as_of }` computes and reports without marking anything; `record_formula_valuations { as_of }` writes the results into the same table every other mark lives in, quantised into minor units exactly once with banker's rounding, so a payoff and the settlement it produces cannot differ by a rappen nobody can explain.
**An external mark is somebody else's number.** `record_external_mark { deal_id, as_of, fair_value, source, method, level }` records what an issuer, an administrator or a vendor said, held because nothing better exists. The source and the method are different questions -- who said it, and how they say they got it -- and an auditor asks both, so both are required, along with the IFRS 13 level. Where the book has declared its market data sources, an external mark must name one of them.
**How the book was marked is reported, not buried.** `valuation_provenance { as_of, fund? }` splits the fair value into what the engine modelled, what a formula computed, what somebody else supplied and how many deals are not marked at all, per currency and by hierarchy level. That split is the substance of a fair-value disclosure, and a fund that cannot produce it cannot be audited. Where a deal carries more than one mark, the payoff formula outranks the engine, because a formula is a statement about that deal written down by somebody where a model is a general routine that happened to accept it; both outrank an opinion, and which was taken is on the row.
A structured note held as a security is its own holding kind, `NOTE`, and its own instrument type, `STRUCTURED_NOTE`. Calling it an equity or an ETF would leave a report unable to say what it is.
## Market data policy: where a number came from
The book already refused to mark without the observations it needs. What it could not say was which source a number came from, how old it was allowed to be, and whether anybody checked it against a second opinion. Those are the three questions a valuation committee asks, and a mark that cannot answer them is a mark nobody can defend.
**A quote and an observation are different things.** A quote is what one source said; an observation is what the book decided to use. They are stored separately, because one key on one date has one observation and can have many quotes, and collapsing them means the second source silently overwrites the first and the disagreement disappears.
`declare_market_data_source` declares who this book accepts numbers from, with a contact: a source nobody can reach is a source whose numbers cannot be queried. A quote naming an undeclared source is refused.
`set_market_data_policy { rules }` saves the policy whole. Each rule states which observation kind and which keys it covers, its sources **in preference order**, how old a quote may be, how many sources must agree, and the tolerance they must agree within. The most specific rule covering a key governs it: an exact key beats a prefix, and a prefix beats the whole kind. Two rules of equal reach covering one key is a policy that cannot be applied, so it is refused rather than resolved by whichever was stored first. A rule naming a source the book has not declared is refused at save time, not at the moment a mark needs it.
`resolve_market_data { as_of }` runs the policy and records what it decided, per key, with the evidence:
- **Stale is pending, not usable.** A quote older than the rule allows is not used and the observation stays missing. Carrying a stale price forward is how a book marks a suspended stock at last week's number. A quote dated after the date being valued is refused too: a price from tomorrow is not fresh, it is wrong.
- **A single quote where the rule wants two is not verified**, so it is recorded and not used. Above one source, this is independent price verification.
- **Disagreement does not pick a winner.** Where two usable quotes differ by more than the tolerance, nothing is chosen and the finding is recorded with both numbers. The alternative is choosing one of two numbers that cannot both be right and not telling anybody.
- **A dividend has two dates.** Two sources agreeing on the amount and differing on the pay date have not agreed, because cash booked on the wrong day is wrong however right the number is.
- **Falling back is visible.** Which source supplied the number is recorded on the observation and on the resolution, so using the second choice is on the record rather than silent.
Only what the policy verified becomes an observation the engine marks from. Everything else stays missing, which is what the engine already knows how to say, and `market_data_status { as_of? }` is the queue: every key that did not resolve, why, and the quotes behind it. The daily cycle runs the policy **before** it marks, because a number that could not be verified must not reach a valuation.
Age is counted in calendar days, or in weekdays where the rule says so. It is called weekdays and not business days deliberately: no holiday calendar is implemented here, and a rule naming one is refused rather than quietly counted as calendar days.
## The daily cycle
Marking and reconciliation run by schedule, because EMIR's obligation is periodic and a book marked when someone remembers is not marked. Each book saves a valuation policy -- the market inputs it is marked with, and optionally the reconciliation tolerance -- and every morning the cycle runs the market data policy, marks every book with a valuation policy, reconciles any statement imported for the day, and computes the collateral calls. The market data policy runs first and not last: a number that could not be verified must not reach a valuation, and the only way to guarantee that is to decide what may be used before anything is marked. Every scheduled act is attributed to the person who set the policy: a schedule has no hands, and an unattributed act does not happen here. A book without a policy is skipped and the cycle result says so.
# Entities, Roles and Governance
An institutional client is a company with several users -- operations, a CIO, an auditor's read-only login -- working shared books under different rights.
## Who sees the Middle Office
The Middle Office is shown to anyone who is an active member of at least one entity, and to the platform's super admins. It is never shown in the anonymous demo session. That is a visibility rule for the navigation and the route; the security boundary is row-level security on every `otc_*` table, which scopes each row to its entity and is enforced by the database whatever the client renders. A newly invited owner who accepts lands on their entity's empty blotter: nothing is ever seeded into a client's entity.
## The shape
| | |
|---|---|
| Entity | The institution: legal name, optional LEI |
| Members | Users seated with a role; suspension ends access but keeps the audit trail |
| Books | Every middle-office row carries a tenant key: the entity for a shared book, the user for a personal one |
## Roles
| Role | May |
|---|---|
| OWNER | Everything. The last active owner cannot be removed, demoted or suspended -- the database refuses it for every caller |
| ADMIN | Manage members and operate the books |
| OPERATIONS | Book deals, record observations, mark, reconcile, post |
| VIEWER | Read everything, change nothing |
Rights are enforced twice: at the service door, where a viewer's mutating request is refused by role, and at the database, where row-level security keys every table on the tenant and the caller's membership. Authorship is separate from ownership -- every row records which member wrote it.
## Four-eyes
An entity can require that no deal is confirmed by the person who booked it. With the control on, a booking lands as a draft awaiting approval, and a different member with a write role confirms it. The rule is enforced by the database itself -- self-approval and viewer-approval are refused at the row, not by a button the interface remembered to hide. Rejection requires a note, and the note outlives the rejected draft on the audit log.
## NAV sign-off
The NAV is the platform's most consequential output, and the person who rolled it must not be the only person who has seen it. On entity books, a rolled period starts unsigned; a different member with a write role signs it off, and the database refuses the roller's own signature. While signed, the period's economics are frozen at the row -- re-rolling is refused until an administrator reopens it with a reason, the reason lands on the audit log, and the period then needs a fresh signature.
## The audit log
Every mutating act lands in an append-only log: who, what, when, in which book. The table refuses UPDATE and DELETE at the database level for every role including the platform's own service role. An act that cannot be recorded fails rather than happening off the record.
## Mandates
A mandate is what the book agreed to trade inside, and it is checked before a deal is booked, not after. It states, per scope and optionally for one fund or one book: which products may be held, in which currencies, against which counterparties (by legal name or LEI), whether a signed master agreement and a CSA are required, the largest single deal, the largest gross position per fund and per counterparty, and the longest tenor from trade to termination. A dimension left null is no bound on that dimension, stated deliberately rather than by omission, and a mandate that bounds nothing is refused as not a mandate.
Two properties make it a limit rather than a suggestion. **A booking that breaks one is refused, with every broken rule named at once** and the numbers on both sides, so a desk fixes the trade in one round trip rather than seven. **A limit that cannot be measured blocks.** Caps are stated in one currency; a deal in another needs a rate from the valuation policy, and where none is quoted the deal is refused naming the missing pair rather than passed. Aggregate caps are measured against the book as it will be after the deal, so the trade that takes a fund over its limit is the one that is refused.
Terms are editable while a mandate is a draft. Committing freezes them, stamps the moment and a SHA-256 over their canonical form, and starts them binding; from then on the mandate can only be suspended, revoked with a reason, or allowed to expire. The freeze is enforced by a database trigger as well as by the function, because a committed bound that could be edited afterwards would record a limit nobody was held to. Committing over an existing book reports every breach it already has rather than leaving them to be discovered.
`check_mandates` answers the same question without booking anything: the live book against its mandates, and optionally a deal that has not been booked yet, so a desk can ask before it trades.
## AI governance
The research partner can read the book, validate terms and project lifecycles, but cannot book, post or resolve. A booking is a recorded, attributed human act -- never a side effect of a conversation.
## Membership
Adding a member starts from their email and resolves against existing accounts. No invitation email is sent: a person who has not signed up has not agreed to anything.
## Which book a call reads
Every action and every REST read takes an optional `entity_id`. Without it the call is on the caller's personal book; with it, on that entity's book, and the caller must be an active member: a non-member gets a named refusal, never an empty list that looks like an empty book. The Python SDK binds it once with `client.middle_office.for_entity(entity_id)`.
## Which actions a viewer may not call
`book_deal`, `approve_deal`, `reject_deal`, `record_observation`, `record_knock`, `record_fund_flow`, `project_cashflows`, `entries_for_flows`, `post_journal`, `rechain_journal`, `roll_nav`, `sign_off_nav`, `reopen_nav_period`, `mark_deals`, `set_valuation_policy`, `import_statement`, `upsert_statement_mapping`, `upsert_statement_feed`, `upload_statement_file`, `ingest_statement_files`, `run_reconciliation`, `run_position_reconciliation`, `resolve_break`, `collateral_calls`, `settle_collateral_call`, `record_broker_margin`, `agree_margin_call`, `dispute_margin_call`, `settle_margin_call`, `declare_market_data_source`, `set_market_data_policy`, `record_market_quotes`, `resolve_market_data`, `set_formula_payoff`, `record_formula_valuations`, `record_external_mark`, `run_report`, `assign_uti`, `record_counterparty_uti`, `match_utis`, `create_confirmation`, `move_confirmation`, `build_regulatory_submission`, `mark_submission_sent`, `record_acknowledgement`, `record_administrator_nav`, `tie_out_nav`, `sign_off_tieout`, `snapshot_book`, `create_fund`, `create_book`, `upsert_security`, `create_cash_account`, `record_cash_balance`, `create_otc_mandate`, `commit_otc_mandate`, `suspend_otc_mandate`, `revoke_otc_mandate`, `install_chart_of_accounts`, `reverse_entry`, `post_valuations`, `create_expense_schedule`, `accrue_expenses`, `create_investor`, `record_investor_transaction`, `create_share_class`, `roll_class_nav`, `record_corporate_action` and `apply_corporate_action` change the book, are refused for a VIEWER at the door, and each lands on the audit log. Everything else is a read.
# Keyboard Shortcuts Reference
Shortcuts work on the app's pages and in the Builder, and do nothing while you are typing in a field unless the table says so. On Windows and Linux, use `Ctrl` where this page says `Cmd`.
## Anywhere
| Shortcut | Action |
|---|---|
| `?` | Show the shortcut list |
| `Cmd+K` | Search |
| `Cmd+/` | Open or close Simons, even while typing |
| `Cmd+Shift+N` | New conversation with Simons, even while typing |
| `Esc` | Close a dialog, even while typing |
## Go to
Press the second key within a second of the first.
| Shortcut | Page |
|---|---|
| `G` then `H` | Home |
| `G` then `S` | Strategy Lab |
| `G` then `B` | Builder |
| `G` then `T` | Builder, on the backtest |
| `G` then `I` | Simons |
| `G` then `D` | Data Lab |
| `G` then `R` | Risk Lab |
| `G` then `P` | Portfolio |
| `G` then `E` | Execution |
| `G` then `N` | Notifications |
| `G` then `,` | Settings |
| `G` then `?` | Help |
## Actions
| Shortcut | Action |
|---|---|
| `N` | New strategy |
## In the Builder
| Shortcut | Action |
|---|---|
| `Shift+Enter` or `Cmd+Enter` | Run the open file |
| `Esc`, or `Cmd+C` with nothing selected | Stop the run |
| `Cmd+S` | Save |
| `B` | Run the backtest, with `backtest.py` open and the cursor outside the editor |
| `Cmd+Shift+A` | Switch Simons between Ask and Agent |
| `Cmd+Shift+D` | Switch Simons to Debug |
# Frequently Asked Questions
## General
**Do I need to know Python?**
Strategies are Python (or Rust or C++), and you can read every line. Simons writes, explains and fixes code with you, so you can start without being fluent.
**Is my data secure?**
Your strategy code is encrypted with AES-256-GCM in your browser before it reaches our database. We hold a copy of your encryption key on our servers so scheduled runs can decrypt your code when your browser is offline; the operator can decrypt your stored strategy code for that purpose. For clients who require execution-time privacy with no server-side key copy, confidential compute enclave deployment is available on request. See [Client Data Privacy](/docs/security-privacy/client-data-privacy).
**Can I use my own data?**
Yes. Upload files, connect your own data providers and warehouses, and read them from your strategies. See [Importing Data](/docs/data-integrations/importing-data) and [Connecting Data Providers](/docs/data-integrations/connecting-data-providers).
**Can colleagues work with me?**
Each person has their own account. Research projects can be shared as **Viewer**, **Commenter** or **Editor**. See [Account Setup](/docs/getting-started/account-setup).
## Trading
**Which brokers can I connect?**
Alpaca, TradeStation®, Saxo Bank, tastytrade, Kalshi, Polymarket and, through a connection portal, Fidelity, Schwab, Robinhood and others. Interactive Brokers is switched on for your account on request. See [Connecting Your Broker](/docs/getting-started/connecting-your-broker).
**What is paper trading?**
A simulated account at your broker: real prices, simulated money. Every one-click deployment trades paper. See [Paper vs. Live Trading](/docs/execution/paper-vs-live).
**How do I stop everything at once?**
**Risk Lab → Mandates → Halt trading** stops every new order and every strategy run. See [Mandates and the Trading Halt](/docs/risk-management/mandates-and-halts).
**How many strategies can I keep?**
Five on the Research Seat. Contact support for more.
## Backtesting
**How realistic are backtests?**
Each backtest script states what it models and what it does not, and the results repeat it. Costs and slippage are settings you choose. Validate on paper before going live. See [What a Backtest Models](/docs/backtesting/broker-and-account).
## Billing
**Can I cancel at any time?**
Yes: **Settings → Subscription → Cancel**. Your access continues until the end of the period you have paid for.
**Is there a free trial?**
Yes: 14 days, when you sign up with your email address. A payment method is required, and you are charged only if you keep the subscription after the trial.
# Common Issues
## Connecting a broker
- **"Coming Soon" next to Interactive Brokers:** it is switched on for your account on request. Contact support.
- **A key is refused:** API keys belong to one environment. A paper key does not connect a live account, and the other way round.
- **Saxo Bank asks for an App Key:** register an application in Saxo's developer portal and use its App Key and App Secret.
## Orders refused
- **"Live trading is not enabled for this account":** live trading is never on during a trial, and CPZAI switches it on for each account. Contact support.
- **Refused by a mandate:** the order broke one of your limits. **Risk Lab → Mandates** shows the mandate and its audit trail.
- **"Trading halt engaged":** **Risk Lab → Mandates → Release halt**. A halt CPZAI engaged for a daily loss is released once the loss check passes.
## Backtests
- **"PRE-FLIGHT CHECK: backtest not started"** with **Missing signal function**: the strategy needs a function the backtest can call, `def generate_signal(data, state)`, `def on_bar(context, data)` or `def generate_signals(data, params)`. **Convert with Simons** adds one.
- **"No data loaded for any of ..."**: check the symbols, the dates and the **Source**. Massive and Databento need your own key.
- **No trades:** the strategy's conditions may never be met in the period, or the period may be too short for its indicators.
## Simons
- **Simons stops responding:** check **Settings → Usage**. **Overage Protection** and a **Monthly Spending Cap** stop usage once they are reached, and a trial has a $5 allowance.
- Start a new conversation if one has grown very long.
## Getting help
- **Help → Contact Support**.
- Email [support@cpz-lab.com](mailto:support@cpz-lab.com).