Custom JS Types¶
JavaScript policies already receive the typed OpenPit SDK surface directly:
Order, ExecutionReport, Context, PolicyReject, and the other callback
types are regular JS/TS objects exposed by the binding. Intersect an SDK model
with application fields and use it as a Policy type parameter; callbacks then
receive a fresh clone of the submitted object with those fields preserved.
When to Use¶
Use the plain JS policy interface when:
- you want a custom pre-trade or post-trade policy in JavaScript or TypeScript;
- you need project-specific fields next to the standard
Order/ExecutionReportfields; - you want callback typing directly from the package exports.
The standard fields remain validated by OpenPit. Host-specific fields stay opaque to the engine and are available only to custom policy code.
Building Blocks¶
The custom-type seam uses the ordinary JS package surface:
| Piece | Role |
|---|---|
Order / ExecutionReport |
Validated SDK models that application fields can intersect or extend. |
Policy<OrderModel, ExecutionReportModel> |
Keeps the application payload types visible in every applicable callback. |
Engine.builder().preTrade(policy) |
Registers the typed policy directly; no adapter or separate client engine is required. |
Use an SDK wrapper when the application wants model methods and setters. A
plain object with the same standard groups is also accepted at the engine
boundary, but an intersection with Order or ExecutionReport is the most
direct way to type custom policy callbacks.
Example¶
Complete typed policy and engine¶
import { Engine } from "@openpit/engine";
import { Order } from "@openpit/engine/model";
import { TradeAmount } from "@openpit/engine/param";
import {
type Context,
type Policy,
type PolicyPreTradeResult,
type PolicyReject,
} from "@openpit/engine/pretrade";
type DeskOrder = Order & { deskId: string };
// Rejects SELL orders for this desk; accepts everything else. The callback
// reads both the validated OpenPit fields and application-owned metadata.
const sellGate: Policy<DeskOrder> = {
name: "sell-gate",
policyGroupId: 0,
checkPreTradeStart(
ctx: Context,
order: DeskOrder,
): Iterable<PolicyReject> {
void ctx;
if (order.deskId === "cash-equities" && order.operation?.side === "SELL") {
return [
{
code: "InvalidFieldValue",
reason: "sells are disabled for this desk",
details: "sell-gate",
scope: "order",
},
];
}
return [];
},
performPreTradeCheck(
ctx: Context,
order: DeskOrder,
): PolicyPreTradeResult | null {
void ctx;
void order;
return null;
},
};
const engine = Engine.builder().preTrade(sellGate).build();
const order: DeskOrder = Object.assign(new Order(), {
deskId: "cash-equities",
});
order.operation = {
underlyingAsset: "AAPL",
settlementAsset: "USD",
accountId: 99224416,
side: "BUY",
tradeAmount: TradeAmount.quantity("100"),
price: "185.00",
};
const result = engine.executePreTrade(order);
if (result.ok) {
// The policy accepted; finalize the reservation it produced.
const reservation = result.reservation;
if (reservation === undefined) {
throw new Error("accepted execute result is missing its reservation");
}
reservation.commit();
} else {
for (const reject of result.rejects) {
console.log(
`rejected by ${reject.policy} [${reject.code}]: ${reject.reason}`,
);
}
}
Lifecycle / Payload Contract¶
- The application model must include the standard
OrderorExecutionReportsurface so the engine can validate it. Policy<OrderModel, ExecutionReportModel>keeps host fields typed in every applicable callback. The default model parameters remainOrderandExecutionReport.- Each policy receives its own fresh clone, so one callback cannot mutate the object observed by a later policy.
- Cloning preserves application-owned string keys, symbol keys, prototypes, cycles, and shared references. Standard SDK fields are normalized and validated independently.
- Input payloads are borrowed for the duration of the call. Only lifecycle
handles returned by the engine (
Request,Reservation, andMutation) are single-use and consumed by their terminal operation.
Threading¶
The WebAssembly engine is single-threaded in JS, so a custom policy runs synchronously on the caller's thread. There is no user-selectable sync mode or off-thread callback execution. Use one engine per worker or isolate when the application needs parallelism.
Related Pages¶
- Policy API: full callback contract and rollback patterns
- Getting Started: first engine construction and end-to-end flow
- Policies: built-in controls and custom policy registration