Skip to content

Custom Go Types

Eugene Palchukovsky edited this page Jul 13, 2026 · 7 revisions

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.

Interface Required method Package
pretrade.ClientOrder EngineOrder() model.Order pretrade
pretrade.ClientExecutionReport EngineExecutionReport() model.ExecutionReport pretrade
accountadjustment.ClientAccountAdjustment EngineAccountAdjustment() model.AccountAdjustment accountadjustment

Policy interfaces

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

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)

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

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

Canonical Safe Example

The runnable Policy API - 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.

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.

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.

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

Clone this wiki locally