-
Notifications
You must be signed in to change notification settings - Fork 1
Custom Go Types
Go's ClientEngine accepts any order, execution-report, and account-adjustment
type that satisfies a small set of interfaces. Policies receive the original
typed value in every callback, so project-specific fields are always available
without a separate side-channel or a cast from interface{}.
Use ClientEngine and its generic builder when:
- your 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.
When you only use model.Order and model.ExecutionReport directly, the plain
Engine and EngineBuilder are simpler and have lower overhead.
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 are parameterized over the concrete payload types so callbacks receive the typed value directly.
| Interface | Typed callbacks |
|---|---|
pretrade.ClientCheckPreTradeStartPolicy[Order, Report] |
CheckPreTradeStart(Context, Order), ApplyExecutionReport(Report)
|
pretrade.ClientPreTradePolicy[Order, Report] |
PerformPreTradeCheck(Context, Order, tx.Mutations), ApplyExecutionReport(Report)
|
accountadjustment.ClientPolicy[Adjustment] |
ApplyAccountAdjustment(Context, param.AccountID, Adjustment, tx.Mutations) |
| 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 |
Embed the SDK model type to inherit its payload interface automatically:
type StrategyOrder struct {
model.Order // EngineOrder() is promoted automatically
StrategyTag string
}
type StrategyReport struct {
model.ExecutionReport // EngineExecutionReport() is promoted automatically
VenueExecID string
}The policy is parameterized over both custom types. Callbacks receive the typed value directly — no cast required:
type StrategyTagPolicy struct{}
func (StrategyTagPolicy) Close() {}
func (StrategyTagPolicy) Name() string { return "StrategyTagPolicy" }
func (StrategyTagPolicy) ApplyExecutionReport(StrategyReport) bool {
return false
}
func (p StrategyTagPolicy) CheckPreTradeStart(
_ pretrade.Context,
order StrategyOrder,
) []reject.Reject {
if order.StrategyTag == "blocked" {
return reject.NewSingleItemList(
reject.CodeComplianceRestriction,
p.Name(),
"strategy blocked",
fmt.Sprintf("strategy tag %q is not allowed", order.StrategyTag),
reject.ScopeOrder,
)
}
return nil
}builder, err := NewClientPreTradeEngineBuilder[StrategyOrder, StrategyReport]()
if err != nil {
return err
}
builder.CheckPreTradeStartPolicy(&StrategyTagPolicy{})
engine, err := builder.Build()
if err != nil {
return err
}
defer engine.Stop()
order := StrategyOrder{Order: model.NewOrder(), StrategyTag: "alpha"}
request, rejects, err := engine.StartPreTrade(order)
if err != nil || rejects != nil {
// handle error or reject
}
defer request.Close()
reservation, rejects, err := request.Execute()
if err != nil || rejects != nil {
// handle error or reject
}
reservation.CommitAndClose()The SDK allocates a cgo.Handle wrapping the client value at call entry and
releases it synchronously:
-
StartPreTrade— handle is released whenExecuteorCloseis called on the returned*ClientRequest, not whenStartPreTradereturns. This means policy callbacks during the main stage still have access to the original payload. -
ExecutePreTrade— handle is released beforeExecutePreTradereturns. -
ApplyExecutionReport— handle is released beforeApplyExecutionReportreturns.
All policy callbacks are invoked synchronously within the same call, so policies can read the typed payload freely without extending its lifetime.
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 you can skip this validation:
builder, err := NewClientPreTradeEngineBuilder[StrategyOrder, StrategyReport](
UnsafeFastClientPayloadCallbacks(),
)A missing or mismatched payload then panics instead of returning a reject. Use this only when the submission path is fully controlled by the caller.
ClientEngine follows the same threading contract as Engine: no concurrent
calls, single-threaded-per-call semantics, thread-portable sequential usage.
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.
- Getting Started: first engine construction and end-to-end flow
- Policy API: custom policy hooks, language interfaces, and rollback patterns
- Custom Rust Types: Rust capability traits and derive-based composition