---
title: "Custom C++ Types"
description: "The C++ SDK lets a project carry its own order and execution-report payload types through the pre-trade engine. A client payload type derives from."
---

# Custom C++ Types

The C++ SDK lets a project carry its own order and execution-report payload
types through the pre-trade engine. A client payload type derives from
`openpit::model::Order` or `openpit::model::ExecutionReport`; the adapter
templates in `openpit/pretrade/adapters.hpp` recover the concrete type at the
policy boundary and hand the typed value to the policy callback. The root
polymorphic bases are an internal engine seam, and their native serialization
bridge is not a client extension point. The compiler enforces this: the
concrete `NativeView()` implementations are `final`, so a client payload type
cannot substitute its own native serialization. Project-specific fields
(strategy tag, desk identifier, venue annotation) are therefore always
available to the policy without a side-channel or a manual cast from an opaque
handle.

## When to Use

Use a custom payload type when:

- the order or report carries fields the engine model does not own (strategy
  tag, exchange annotation, client metadata);
- you want those fields delivered to a policy callback with their concrete
  type, not as a base reference;
- you prefer a typed policy object over reading raw payload groups out of
  `openpit::model::Order`.

If `openpit::model::Order` and `openpit::model::ExecutionReport` already cover
the integration, the built-in policy configurations and the plain pipeline are
simpler and carry no downcast cost. See [Policies](Policies.md) and
[Pre-trade Pipeline](Pre-trade-Pipeline.md).

## Building Blocks

Custom-type support uses three cooperating pieces from
`openpit/pretrade/adapters.hpp`, `openpit/model/model.hpp`, and
`openpit/pretrade/custom_policy.hpp`.

<!-- markdownlint-disable MD013 -->
| Piece | Role | Header |
| --- | --- | --- |
| `openpit::model::Order` / `openpit::model::ExecutionReport` | Client-extensible models that implement the private engine serialization bridge; the adapter downcasts through the internal polymorphic seam. | `openpit/model/model.hpp` |
| `StartPolicyAdapter` / `PolicyAdapter` | Bridge a typed client policy to the start-stage and main-stage callback signatures the engine expects. | `openpit/pretrade/adapters.hpp` |
| `CastMode::SafeSlow` / `CastMode::UnsafeFast` | The cast strategy each adapter uses to turn the base reference back into the client type. | `openpit/pretrade/adapters.hpp` |
| `CustomPolicy<Handler>` | Owning RAII policy that registers the adapter on the engine builder. | `openpit/pretrade/custom_policy.hpp` |
<!-- markdownlint-enable MD013 -->

### Client policy surface

A client policy is a plain C++ object. The adapters detect each hook by its
signature, so a policy implements only the hooks it needs:

<!-- markdownlint-disable MD013 -->
- `std::string_view Name() const` - stable policy name (required by the
  adapters).
- `std::optional<Reject> CheckPreTradeStart(const ClientOrder&) const` -
  start-stage check; return an engaged `Reject` to reject, `std::nullopt` to
  accept.
- `void PerformPreTradeCheck(const ClientOrder&, const Context&, tx::Mutations&, Result&, PolicyDecision&) const` -
  main-stage check; push zero or more rejects into the `PolicyDecision`, and
  optionally register mutations or push lock prices / outcomes into the
  collectors.
- `std::vector<accounts::AccountBlock> ApplyExecutionReport(const PostTradeContext&, const ClientReport&, PostTradeAdjustments&, PostTradePnls&) const` -
  post-trade hook; return the account blocks raised, and optionally push
  group-tagged adjustment and account-PnL outcomes into the two collectors.
<!-- markdownlint-enable MD013 -->

Callbacks run on the engine hot path. `CustomPolicy` contains callback
exceptions inside the SDK boundary until cleanup finishes. The engine call then
rethrows the original exception. Drop copy is no exception: it compensates the
mutations it collected and rethrows that same exception.
Exceptions represent API failures; rejects remain values. In `SafeSlow` mode,
a payload mismatch throws `openpit::Error` with the policy, expected client
type, and actual type (see below).

## Payload Types

Derive the project types from the concrete `openpit::model::Order` and
`openpit::model::ExecutionReport` models, then add the project fields. The
concrete bases already implement the engine's private serialization bridge, so
the engine can consume the standard fields while the adapter recovers the
project type. Financial fields use the `openpit::param` value types, never
`double`.

The inherited model keeps the standard groups (operation, margin, position,
financial impact, and fill details) on the same object as the project fields.
Native representation remains an implementation detail of the binding.

## Typed Policies and Adapters

A client policy works in terms of concrete project types such as an application
order and execution report. Each callback receives the concrete client type
directly - no cast in the policy body.

The adapter templates wrap the client policy and expose the callback signatures
the engine drives. They downcast the polymorphic `openpit::Order` /
`openpit::ExecutionReport` reference back to the client type. There is
intentionally no default cast strategy - the policy author chooses one
explicitly through the convenience aliases. `PolicyAdapter` is the unified
wrapper for a policy with a main hook: it also forwards any start, dry-run,
post-trade, and account-adjustment hooks implemented by that same policy
instance. `StartPolicyAdapter` remains available for start-only policies.

### `SafeSlow` versus `UnsafeFast`

The cast mode is the `CastMode` enum template parameter; the aliases pick it for
you.

<!-- markdownlint-disable MD013 -->
| Mode | Cast | Order mismatch | Report mismatch | Use when |
| --- | --- | --- | --- | --- |
| `CastMode::SafeSlow` | `dynamic_cast` | throws `openpit::Error` | throws `openpit::Error` | dynamic boundaries; safe default |
| `CastMode::UnsafeFast` | `static_cast` | undefined behavior | undefined behavior | closed systems with compile-time payload pairing |
<!-- markdownlint-enable MD013 -->

`UnsafeFast` removes the adapter's runtime cast verification at the policy
boundary and uses a direct `static_cast`. The C++ binding still requires RTTI
regardless of cast mode: every value-overload submission checks the payload's
dynamic type with `typeid` in the ownership seam before taking ownership.
Select `UnsafeFast` only when the submission path is fully controlled by the
caller and the payload type that reaches a given policy is guaranteed at
compile time, because a wrong wiring is undefined behavior rather than a
reject.

## Canonical Safe Example

The runnable [Policy API - C++ Custom Models](Policy-API.md#c-custom-models)
example uses `SafeSlow`. It is the default integration path; `UnsafeFast` is
documented above without a separate runnable example because it is valid only
for a caller-controlled submission path.

## Lifecycle and Payload Contract

The custom payload type and the policy object follow the SDK ownership rules:

- The `CustomPolicy<Adapter>` is a move-only owning RAII handle. The adapter it
  holds owns the client policy by value. Registration on the builder retains its
  own reference, so the engine keeps the policy alive after `Build()`; the
  caller's handle may be released after registration completes.
- Policy callbacks are synchronous and run within the engine call that triggered
  them. The payload reference handed to a callback is valid only for that call -
  do not retain it. The same holds for the `Context`, which is non-owning and
  callback-scoped.
- `StartPreTrade(order)` moves an rvalue or copies an lvalue into the deferred
  request. The request owns the concrete order until `Request::Execute()`, so
  execution does not depend on the caller's lifetime. A base-typed reference to
  a derived order is rejected; type-erased callers pass a
  `std::unique_ptr<const openpit::Order>` to `StartPreTrade`.
- Callback exceptions are captured inside the SDK boundary until cleanup
  finishes, then the invoking engine method rethrows the original exception -
  `ApplyDropCopy` included, after it compensates the mutations it collected.
  Use that channel for API failures; use `Reject` /
  `PolicyDecision` for expected business outcomes. `SafeSlow` treats a payload
  type mismatch as a wiring error and throws `openpit::Error`.
- A mutation commit or rollback callback registered through `tx::Mutations::Push`
  is a finalizer and has no right to fail. It is rethrown from `Commit()` or
  `Rollback()` on the same channel, once the batch has finished, and the engine
  additionally arms its kill switch across every account - a C++ mutation is
  always a custom-policy mutation. On the destructor's implicit rollback nothing
  can be rethrown, so the block is the only channel. See
  [Account Blocking - Mutation Finalizer Contract](Account-Blocking.md#mutation-finalizer-contract).

## Why a Polymorphic Base Instead of One Record

OpenPit targets latency-sensitive trading code, not a single universal order
record carrying every conceivable field. The polymorphic-base design exists so
that:

- a policy declares the exact client payload type it consumes;
- the host carries only the fields its code path uses, next to the standard
  model groups;
- the downcast cost is paid once, at the policy boundary, and only in
  `SafeSlow` mode;
- the core model stays fixed while projects extend their payloads freely.

The trade-off is explicit adapter wiring (a cast mode and the client type
parameters) in exchange for typed callbacks and a stable core model.

## Threading Addendum

Custom C++ types follow the OpenPit threading contract: concurrent calls on the
same engine handle are safe under `SyncPolicy::Full`, forbidden under
`SyncPolicy::None`, and under `SyncPolicy::Account` safe only when the caller
guarantees that calls for the same account are never concurrent. If a custom
policy runs under `SyncPolicy::Full`, calls for one account may interleave;
storage access safety does not make a multi-step callback or engine call
atomic. If a custom payload or policy holds state shared across SDK calls,
align that state's thread-safety with the sync policy chosen on the builder. See
[Threading Contract](Threading-Contract.md).

## Related Pages

- [Getting Started](Getting-Started.md): first engine construction and end-to-end
  flow
- [Policies](Policies.md): built-in policy configurations and registration
- [Pre-trade Pipeline](Pre-trade-Pipeline.md): start/main stages and reservation
  finalization
- [Threading Contract](Threading-Contract.md): sync policies and callback
  concurrency rules
- [Account Blocking](Account-Blocking.md): the mutation finalizer contract and the
  engine-wide block
- [Custom Go Types](Custom-Go-Types.md): typed Go client payloads
- [Custom Python Types](Custom-Python-Types.md): Python model subclasses
- [Custom JS Types](Custom-JS-Types.md): typed JavaScript policy payloads
- [Custom Rust Types](Custom-Rust-Types.md): Rust capability-trait composition
