---
title: "Custom Go Types"
description: "Go's ClientEngine accepts any order, execution-report, and account-adjustment type that satisfies a small set of interfaces. Policy callbacks receive the."
---

# Custom Go Types

Go's `ClientEngine` accepts any order, execution-report, and
account-adjustment type that satisfies a small set of interfaces. Policy
callbacks receive the original typed value, so project-specific fields are
always available without a side-channel or a cast from `interface{}`.

## When to Use

Use `ClientEngine` and its generic builder when:

- the order type carries fields the engine does not own (strategy tag,
  exchange annotation, client metadata);
- you want those fields delivered to policy callbacks without manual
  bookkeeping;
- you prefer typed callbacks over casting raw payload handles.

If `model.Order` and `model.ExecutionReport` cover the integration, the
plain `Engine` and `EngineBuilder` are simpler and have lower overhead.

## Building Blocks

### Payload interfaces

Each payload interface requires a single method that returns the standard
engine view. `model.Order`, `model.ExecutionReport`, and
`model.AccountAdjustment` each implement their own interface and can be
embedded in a project struct to satisfy it automatically.

<!-- markdownlint-disable MD013 -->
| Interface | Required method | Package |
| --- | --- | --- |
| `pretrade.ClientOrder` | `EngineOrder() model.Order` | `pretrade` |
| `pretrade.ClientExecutionReport` | `EngineExecutionReport() model.ExecutionReport` | `pretrade` |
| `accountadjustment.ClientAccountAdjustment` | `EngineAccountAdjustment() model.AccountAdjustment` | `accountadjustment` |
<!-- markdownlint-enable MD013 -->

### Policy interfaces

Policy interfaces are parameterized over the concrete payload types so callbacks
receive the typed value directly.

<!-- markdownlint-disable MD013 -->
| Interface | Typed callbacks |
| --- | --- |
| `pretrade.ClientPreTradePolicy[Order, Report]` | `PolicyGroupID() model.PolicyGroupID`, `CheckPreTradeStart(Context, Order)`, `PerformPreTradeCheck(Context, Order, tx.Mutations, pretrade.Result)`, `ApplyExecutionReport(pretrade.PostTradeContext, Report, pretrade.PostTradeAdjustments, pretrade.PostTradePnls) []reject.AccountBlock`, `ApplyAccountAdjustment(accountadjustment.Context, param.AccountID, model.AccountAdjustment, tx.Mutations, pretrade.AccountOutcomes) (pretrade.PolicyAccountAdjustmentResult, []reject.Reject)` |
<!-- markdownlint-enable MD013 -->

`ApplyExecutionReport` returns account blocks and may push group-tagged
account-adjustment and account-PnL outcomes into the two collectors. The return
value and both collectors are independent channels.

Account adjustments always use `model.AccountAdjustment`, regardless of the
client type - the adjustment payload is not routed through the client typing
system.

### Engine builders

<!-- markdownlint-disable MD013 -->
| Builder | Custom types |
| --- | --- |
| `NewClientPreTradeEngineBuilder[Order, Report]()` | order and report; adjustment stays on `model.AccountAdjustment` |
| `NewClientAccountAdjustmentEngineBuilder[Adjustment]()` | adjustment only; order and report stay on `model.Order` / `model.ExecutionReport` |
| `NewClientEngineBuilder[Order, Report, Adjustment]()` | all three |
<!-- markdownlint-enable MD013 -->

## Canonical Safe Example

The runnable [Policy API - Go Custom Models](Policy-API.md#go-custom-models)
example uses the checked callback path. It is the default integration path;
`UnsafeFastClientPayloadCallbacks` is intentionally documented below without a
separate runnable example because it is valid only for a caller-controlled
submission path.

<!-- Preserve existing wiki links when MkDocs generates a different anchor. -->
<!-- markdownlint-disable-next-line MD033 -->
<a id="lifecycle--payload-contract"></a>

## Lifecycle / Payload Contract

The SDK allocates a `cgo.Handle` wrapping the client value at call entry and
releases it synchronously:

- `StartPreTrade` - the handle is released when `Execute` or `Close` is
  called on the returned `*ClientRequest`, not when `StartPreTrade` itself
  returns. Policy callbacks during the main stage still have access to the
  original payload.
- `ExecutePreTrade` - the handle is released before `ExecutePreTrade`
  returns.
- `ApplyExecutionReport` - the handle is released before
  `ApplyExecutionReport` returns.

All policy callbacks run synchronously within the same call, so policies
can read the typed payload freely without extending its lifetime.

The same holds for the `pretrade.Context`, `pretrade.PostTradeContext`,
`accountadjustment.Context`, `pretrade.Result`, `pretrade.PostTradeAdjustments`,
`pretrade.PostTradePnls`, `pretrade.AccountOutcomes`, and `tx.Mutations` handed
to a policy hook: they are non-owning and callback-scoped, valid only during
the hook call that received them. Do not retain them or use them after the
hook returns: that is undefined behavior, and the SDK does not detect it.

An `AccountControl` obtained from a context during a hook stays valid through
the commit or rollback of the transaction the context belongs to - the
reservation, the drop-copy operation, or the account-adjustment batch. It may
be captured by a mutation callback for deferred blocking; do not use it after
that transaction completes.

Every `model.Order`, `model.ExecutionReport`, or `model.AccountAdjustment` hook
argument is an engine-memory view valid only during the hook, except the order
and report arguments of `ClientPreTradePolicy`, which are the caller's own
payload objects. Copy engine-view data you keep inside the hook:

- An engine report's `Values()` and `Fill()` still borrow the fill's
  engine-owned pre-trade lock;
  `model.NewExecutionReportFromValues(report.Values())` built inside the hook
  is an independent deep copy.
- `model.NewOrderFromValues(order.Values())` and
  `model.NewAccountAdjustmentFromValues(adjustment.Values())` are not
  independent copies: nested operation and margin values keep the engine's
  string views. Copy leaf values instead: `Asset()`, `Instrument()`, and
  `CollateralAsset()` return independent values, and scalar fields are plain
  values.

## UnsafeFastClientPayloadCallbacks

By default, each callback adapter validates that the arriving payload type
matches the builder's declared types. When only one payload type flows
through the engine, this validation can be skipped.

A missing or mismatched payload then panics instead of producing a reject.
Use this only when the submission path is fully controlled by the caller.

## Threading Addendum

Custom Go types follow the OpenPit threading contract: concurrent calls
on the same engine handle are safe under `FullSync()`, forbidden under
`NoSync()`, and under `AccountSync()` safe only when the caller
guarantees that calls for the same account are never concurrent. If the
engine uses `FullSync()`, callbacks for one account may interleave; storage
access safety does not make a multi-step callback or engine call atomic. If the
custom type holds state shared across SDK calls (atomics, a
mutex-protected struct, etc.), align that state's thread-safety with the
sync mode chosen on the engine builder. See
[Threading Contract](Threading-Contract.md).

Payload handles (`cgo.Handle`) are always released within the same logical
call that created them. No cross-goroutine ownership of payload handles
occurs, and callers never need to extend payload lifetime beyond the
submitting call.

## Related Pages

- [Getting Started](Getting-Started.md): first engine construction and end-to-end
  flow
- [Policy API](Policy-API.md): custom policy hooks, language interfaces, and
  rollback patterns
- [Custom Python Types](Custom-Python-Types.md): Python model subclasses
- [Custom JS Types](Custom-JS-Types.md): typed JavaScript policy payloads
- [Custom Cpp Types](Custom-Cpp-Types.md): C++ polymorphic client payloads
- [Custom Rust Types](Custom-Rust-Types.md): Rust capability traits and
  derive-based composition
