Skip to content

Pre-trade Pipeline

OpenPit models pre-trade admission as two explicit stages plus a post-trade update step. Prose uses the conceptual step names start stage, execute request, finalize reservation, and apply execution report; exact API names stay inside the code blocks and type tables.

Non-trade operation (NTO) batch validation is handled by apply account adjustments and documented in Account Adjustments.

Lifecycle

start stage
  -> start-stage policies
  -> either reject immediately or return a deferred request

execute request
  -> main-stage policies
  -> either return one or more rejects or return a reservation

finalize reservation
  -> finalize the reserved state exactly once

apply drop copy
  -> start-stage and main-stage policies without enforcing ordinary rejects
  -> either return one or more evaluation-failure rejects or return an
     applied operation

finalize drop-copy operation
  -> finalize the applied bookkeeping exactly once

apply execution report
  -> update cumulative policy state from realized outcomes

Stage Semantics

  • Start stage: runs fast admission checks in registration order and aggregates rejects from all registered policies.
  • Main stage: runs all registered main-stage policies, aggregates rejects, and rolls back collected mutations when any reject is produced.
  • Reservation: state that must be committed or rolled back explicitly by the caller.
  • Drop-copy operation: applied historical bookkeeping that must be committed or rolled back explicitly by the caller, exactly like a reservation.
  • Post-trade: feeds realized outcomes back into policies through an execution report.
  • Shortcut: execute pre-trade composes start stage and execute request into one call; it does not introduce a new lifecycle.
  • Context seam: start-stage and main-stage callbacks both receive Pre-trade context. Operation payload (order) stays a separate callback argument.

Drop Copy

Drop copy applies a historical order through the same start-stage and main-stage policies, in the same registration order, as ordinary pre-trade. Existing account and account-group blocks do not prevent the operation: the historical order must still be reflected in policy state. Ordinary risk and compliance rejects are non-enforcing for this operation, but their mutations, locks, account adjustments, outcomes, and requested account-control operations are kept.

The account ID is the one field the engine reads for itself, because it is the routing and account-control key of the operation. An order that cannot produce one fails immediately with MissingRequiredField, before any policy runs and before any other order field is touched. That reject carries its own cause - drop-copy requires a readable account ID - not the blocked-set wording used by ordinary pre-trade, because drop copy ignores blocks for admission and a stuck block is never the explanation. No account block of any kind is recorded.

The engine does not prevalidate a common set of order fields beyond that. Each policy reads only the fields it needs and returns an evaluation reject when it cannot do its work. For example, a policy that must value a completed order cannot substitute a current mark for a missing historical execution price: it returns an OrderValueCalculationFailed or MissingRequiredField reject. A policy that does not need price may process the same order. Missing reference data, arithmetic failures, and policy callback failures are also fatal evaluation failures. Each binding reports callback failures through its own callback-error channel.

apply drop copy is accepted-or-rejected exactly like the full pre-trade call. On accept it returns a drop-copy operation; on a fatal evaluation failure it returns the rejects and no operation. The operation mirrors the reservation one-to-one: it owns the mutations the policies collected, and the caller finalizes it exactly once with commit or rollback. commit runs the collected mutation commit callbacks and rollback runs the collected rollback callbacks in reverse registration order. Both calls are void and neither discards a callback failure: a finalizer that fails arms the engine kill switch under the mutation finalizer contract. Releasing an unfinalized operation rolls it back, following each binding's ordinary reservation-release rule.

On a fatal evaluation failure, the engine rolls back every collected mutation in reverse registration order before returning, abandons the account-control operations the evaluation had recorded, and reports the rejects. No operation is produced, so the caller has nothing to finalize.

That compensation can itself fail: a mutation rollback callback may raise. When it does, the engine appends a SystemUnavailable reject after the fatal policy rejects and arms the kill switch. The first reject stays the policy cause, so reading rejects[0] always answers why the operation failed rather than how the cleanup failed. This is the only drop-copy path that carries the failure in its rejects; a caller-driven commit or rollback has no reject channel and surfaces the failure through the binding's ordinary reservation channel instead. Either way the kill switch is armed, and its reach follows the failed mutation's provenance rather than the pipeline - a custom-policy mutation, which every mutation registered through a binding is, blocks every account. See Mutation Finalizer Contract.

Account-control effects are outside the finalization boundary. On the accepted path the recorded block, unblock, and safety operations are applied in their original order before the operation is returned, and they stay applied whichever way the caller finalizes it. This matches the ordinary reservation boundary. RateLimitPolicy likewise spends budget when the start stage observes the attempt and does not refund it after a later evaluation failure or caller rollback.

Compensation of a fatal exit describes the state when apply drop copy returns, not isolation of an entire concurrent pipeline. FullSync makes individual storage accesses safe, but same-account calls may interleave during apply or before the caller finalizes an accepted operation. Callers that must exclude intermediate observations serialize the whole same-account operation lifetime themselves or use an account-pinned async dispatcher.

The accepted operation carries the lock, the applied account adjustments, the first account block produced by this request, and account blocked: a snapshot of the effective state when apply returned. A later unblock can change live state without erasing the request-produced block from the operation. The snapshot is also true when the account or its group was already blocked before drop copy ran. Execution reports are not a drop-copy operation and keep their ordinary non-atomic behavior.

Evaluation Failures in Custom Policies

Policy authors should use an evaluation-failure code only when the policy cannot calculate or apply the historical order's effect. A limit, risk, or compliance decision that would reject an ordinary live order is not an evaluation failure: drop copy retains its successfully calculated bookkeeping and does not enforce that reject.

Use the binding-native classifier and the authoritative code list in Reject Codes. Codes outside that list, including Custom and Other, are ordinary verdicts and are non-enforcing in drop copy. A custom policy that cannot evaluate the historical effect should return the closest standardized evaluation-failure code and put policy-specific context in the reject details or user data.

RejectCode::ArithmeticOverflow means the pre-trade policy could not evaluate or apply the historical order, so apply compensates the collected mutations and returns rejects instead of an operation. It is distinct from post-trade PnlHaltReason::ArithmeticOverflow: an execution report with that halt reason keeps its ordinary halt, account-block, and apply behavior. Execution-report processing does not roll back.

Public Return Types

Step Go Python JS C++ Rust
Start stage (*pretrade.Request, []reject.Reject, error) StartResult StartResult pretrade::StartResult Result<PreTradeRequest<_>, Rejects>
Main stage (*pretrade.Reservation, []reject.Reject, error) ExecuteResult ExecuteResult pretrade::ExecuteResult Result<PreTradeReservation, Rejects>
Drop copy (*pretrade.DropCopyOperation, []reject.Reject, error) DropCopyResult DropCopyResult pretrade::DropCopyResult Result<DropCopyOperation, Rejects>
Post-trade PostTradeResult PostTradeResult PostTradeResult PostTradeResult PostTradeResult

Drop copy uses the same accepted-or-rejected shape as the main stage in every binding: the Rust SDK uses Result for fatal evaluation rejects, Go returns them in its reject slice next to the operation and a transport error, and Python, JavaScript, and C++ return a result envelope carrying either the operation or the rejects. Binding-specific exceptions and errors for boundary failures and policy callback failures stay unchanged.

Request

Pre-trade request is the deferred, single-use handle returned after the start stage passes. The main stage consumes it.

  • In Rust, Request::execute(self) consumes the request by type.
  • In Python, calling request.execute() twice raises RuntimeError.
  • In Go, the caller owns *pretrade.Request and releases it with Close. Close is idempotent, but the caller must serialize methods on the same request. An execute that observes a closed request returns pretrade.ErrRequestClosed.

Reservation

The reservation handle is returned after execute request passes (see the return-type table above for the per-language name).

Both finalizing calls are void in every binding, and a mutation finalizer they run has no right to fail. When one reports failure anyway, the call still returns normally and the engine arms a kill switch instead of dropping the failure - see Mutation Finalizer Contract for the cause it records and how far the block reaches.

  • commit keeps the reserved state.
  • rollback cancels it.
  • Dropping a Rust reservation without finalization rolls it back automatically.
  • Destroying an unresolved C++ reservation rolls it back automatically. Call Commit() or Rollback() explicitly when the decision is known; RAII is the exception-safety fallback.
  • In Python, finalizing the same reservation twice raises RuntimeError. commit and rollback re-raise an exception from a mutation commit or rollback callback, with its original type and message, once every callback has run. The implicit rollback that runs when the reservation is garbage-collected has no caller to raise to, so there the engine kill switch is the only channel left.
  • In JavaScript, releasing a reservation without commit or rollback rolls its mutations back implicitly. That rollback is inside the engine, so a mutation rollback callback that calls back into the same engine throws LifecycleError; calling a different engine instance stays allowed.
  • In Go, nothing releases a reservation on the caller's behalf - there is no finalizer, because resolving a reservation applies or unwinds engine state and that call must stay on a goroutine the caller serializes, never the garbage collector's. Resolve it exactly once with CommitAndClose, RollbackAndClose, or Close. Whichever of Commit or Rollback runs first resolves the reservation and any later one is a no-op. The caller must serialize methods on the same reservation. A reservation dropped without Close leaks its native handle and holds its state reserved for the remainder of the process.

The same Go ownership contract covers pretrade.Request, pretrade.DryRunReport, pretrade.DropCopyOperation, ClientRequest, marketdata.Service, and ReferenceBook. Release each with Close; it is idempotent. Callers must serialize methods on the same pretrade.Request, pretrade.Reservation, pretrade.DryRunReport, or pretrade.DropCopyOperation. ClientRequest, marketdata.Service, and ReferenceBook remain safe for concurrent Close and method calls as documented by those types. A method that observes a closed value never panics. It reports the exported closed-handle sentinel for that type - pretrade.ErrReservationClosed, pretrade.ErrRequestClosed, pretrade.ErrDryRunReportClosed, pretrade.ErrDropCopyOperationClosed, ErrClientRequestClosed, marketdata.ErrServiceClosed, or ErrReferenceBookClosed - whenever the value it would otherwise return could be read as an answer: a verdict, an empty outcome list, or an absent account block. Reservation.Lock, DryRunReport.Lock, and DropCopyOperation.Lock follow the same rule and return their type's closed-handle sentinel after Close; the zero lock's Bytes and Equal results could otherwise be read as an answer.

The reservation also carries a pre-trade lock: the reservation-time context a policy needs to reconcile later. If a registered policy (such as Spot Funds) reconciles fills against what it reserved, read the lock off the reservation, persist it, and attach it to every execution report for that order - see Execution Reports.

Drop-Copy Operation

The drop-copy operation separates applying policy state from the caller's local persistence decision. Persist the caller-owned order and price metadata, then commit the operation. If persistence fails, roll it back instead.

The lifecycle follows the reservation's ownership boundary. Release behavior remains language-specific, and the differences - including repeated finalization - are spelled out below together with the accessors:

  • Go: Lock, AccountAdjustments, AccountBlock, and IsAccountBlocked each return a value and an error, reporting pretrade.ErrDropCopyOperationClosed after Close. Resolve the operation with CommitAndClose, RollbackAndClose, or Close. Repeated Commit or Rollback calls are pointer-level no-ops.
  • Python: the lock, account_adjustments, account_block, and is_account_blocked properties raise RuntimeError once the operation is finalized. Repeated commit or rollback calls raise RuntimeError("drop-copy operation has already been finalized"). Garbage collection rolls an unfinalized operation back.
  • JavaScript: lock(), accountAdjustments(), accountBlock(), and isAccountBlocked() throw LifecycleError once the operation is finalized. Repeated commit() or rollback() calls also throw LifecycleError. free() or collection rolls an unfinalized operation back.
  • C++: move-only openpit::pretrade::DropCopyOperation, with Lock(), AccountAdjustments(), AccountBlock(), and IsAccountBlocked() returning owned snapshots, and Commit() / Rollback(). Its RAII destructor rolls an unfinalized operation back. Repeated Commit() or Rollback() calls are pointer-level no-ops.
  • Rust: DropCopyOperation, with lock(), account_adjustments(), account_block(), is_account_blocked(), commit(&mut self), and rollback(&mut self). A second commit panics, repeated rollback and rollback after commit are no-ops, and commit after rollback panics. Dropping an unfinalized value rolls it back.
  • C: caller-owned OpenPitPretradeDropCopyOperation, produced by openpit_engine_apply_drop_copy, with the matching getters, openpit_pretrade_drop_copy_operation_commit and _rollback, and openpit_destroy_pretrade_drop_copy_operation, which rolls back an unfinalized handle. Repeated finalization of the same pointer is a no-op.

Python and JavaScript invalidate the accessors as soon as the operation is finalized, so read what you need before commit or rollback. Go, C++, Rust, and C keep them readable until the handle itself is released.

Rejects

Every reject carries the same public fields across the SDKs:

Field Meaning
policy Stable policy name that produced the reject
code Stable machine-readable reject code
reason Short human-readable reject summary
details Case-specific explanation
scope order or account
user data Opaque caller-defined payload, absent by default

scope = account means the caller should treat the condition as broader than one request, for example an active kill switch.

user data is a caller token the SDK never inspects, dereferences, or frees; see Threading Contract.

Execution Reports

Execution reports carry realized post-trade outcomes back into the engine. The common record-style surface is:

  • underlying asset or instrument.underlying asset: asset that was traded
  • settlement asset or instrument.settlement asset: asset in which P&L and fees are measured
  • pnl: realized P&L contribution
  • fee: fee or rebate contribution
  • lock: the pre-trade lock captured at reservation time; attach the order's stored lock so reconciling policies can settle the reservation. Omitting it can block the account with MissingRequiredField.

PostTradeResult account blocks is non-empty when at least one registered policy reports a blocked state after the report is applied. Post-trade processing is not atomic: account-PnL and account-adjustment outcomes describe mutations that are already applied. Consume and propagate both outcome lists even when account blocks are present.

Example: Handle a Start-Stage Reject

Go
request, rejects, err := engine.StartPreTrade(order)
if err != nil {
  panic(err)
}
if rejects != nil {
  for _, r := range rejects {
    fmt.Printf(
      "rejected by %s [%d]: %s (%s)\n",
      r.Policy,
      r.Code,
      r.Reason,
      r.Details,
    )
  }
  return
}
defer request.Close()
Python
# Start stage returns either a reject or a deferred request handle.
start_result = engine.start_pre_trade(order=order)
if not start_result:
    for reject in start_result.rejects:
        print(
            f"rejected by {reject.policy} "
            f"[{reject.code}]: {reject.reason}: {reject.details}"
        )
else:
    # Keep the request object if later code wants to enter the main stage.
    request = start_result.request
JavaScript
import { Engine } from "@openpit/engine";
import { TradeAmount } from "@openpit/engine/param";
import { type OrderInit } from "@openpit/engine/model";
import { buildOrderValidation } from "@openpit/engine/pretrade/policies";

const engine = Engine.builder().builtin(buildOrderValidation()).build();
const order: OrderInit = {
  operation: {
    underlyingAsset: "AAPL",
    settlementAsset: "USD",
    accountId: 99224416,
    side: "BUY",
    tradeAmount: TradeAmount.quantity("100"),
    price: "185",
  },
};

// Start stage returns either rejects or a deferred request handle.
const start = engine.startPreTrade(order);
if (!start.ok) {
  for (const reject of start.rejects) {
    console.log(
      `rejected by ${reject.policy} [${reject.code}]: ${reject.reason}: ${reject.details}`,
    );
  }
} else {
  // Keep the request object if later code wants to enter the main stage.
  const request = start.request;
  if (request === undefined) {
    throw new Error("accepted start result is missing its request");
  }
  void request;
}
C++
// Start stage returns either a reject or a deferred request handle.
openpit::pretrade::StartResult startResult = engine.StartPreTrade(order);
if (!startResult.Passed()) {
  for (const openpit::pretrade::Reject& reject : startResult.rejects) {
    std::cout << "rejected by " << reject.policy << " ["
              << static_cast<int>(reject.code) << "]: " << reject.reason
              << ": " << reject.details << '\n';
  }
} else {
  // Keep the request if later code wants to enter the main stage.
  openpit::pretrade::Request request = std::move(*startResult.request);
}
Rust
// Start stage returns either a reject or a deferred request handle.
match engine.start_pre_trade(order) {
    Ok(request) => {
        // Keep the request object if later code wants to enter the main stage.
        let _request = request;
    }
    Err(rejects) => {
        for reject in rejects.iter() {
            eprintln!(
                "rejected by {} [{}]: {} ({})",
                reject.policy,
                reject.code,
                reject.reason,
                reject.details
            );
        }
    }
}

Example: Execute the Main Stage and Finalize the Reservation

Go
request, rejects, err := engine.StartPreTrade(order)
if err != nil {
  panic(err)
}
if rejects != nil {
  panic("start stage rejected")
}
defer request.Close()

reservation, rejects, err := request.Execute()
if err != nil {
  panic(err)
}
if rejects != nil {
  for _, r := range rejects {
    fmt.Printf(
      "rejected by %s [%d]: %s (%s)\n",
      r.Policy,
      r.Code,
      r.Reason,
      r.Details,
    )
  }
  return
}
defer reservation.Close()

// Commit only after the caller knows the reservation should become durable.
reservation.Commit()
Python
start_result = engine.start_pre_trade(order=order)
# Main stage consumes the deferred request and returns reservation or rejects.
execute_result = start_result.request.execute()

if execute_result:
    # Commit only after the caller knows the reservation should become durable.
    execute_result.reservation.commit()
else:
    for reject in execute_result.rejects:
        print(
            f"rejected by {reject.policy} "
            f"[{reject.code}]: {reject.reason}: {reject.details}"
        )
JavaScript
import { Engine } from "@openpit/engine";
import { TradeAmount } from "@openpit/engine/param";
import { type OrderInit } from "@openpit/engine/model";
import { buildOrderValidation } from "@openpit/engine/pretrade/policies";

const engine = Engine.builder().builtin(buildOrderValidation()).build();
const order: OrderInit = {
  operation: {
    underlyingAsset: "AAPL",
    settlementAsset: "USD",
    accountId: 99224416,
    side: "BUY",
    tradeAmount: TradeAmount.quantity("100"),
    price: "185",
  },
};
const start = engine.startPreTrade(order);
const request = start.request;
if (request === undefined) {
  throw new Error("start stage must accept the order");
}

// Main stage consumes the deferred request and returns a reservation or
// rejects.
const execute = request.execute();

if (execute.ok) {
  // Commit only after the caller knows the reservation should become durable.
  const reservation = execute.reservation;
  if (reservation === undefined) {
    throw new Error("accepted execute result is missing its reservation");
  }
  reservation.commit();
} else {
  for (const reject of execute.rejects) {
    console.log(
      `rejected by ${reject.policy} [${reject.code}]: ${reject.reason}: ${reject.details}`,
    );
  }
}
C++
openpit::pretrade::StartResult startResult = engine.StartPreTrade(order);
// Main stage consumes the deferred request and returns reservation or rejects.
openpit::pretrade::ExecuteResult executeResult = startResult.request->Execute();

if (executeResult.Passed()) {
  // Commit only after the caller knows the reservation should become durable.
  executeResult.reservation->Commit();
} else {
  for (const openpit::pretrade::Reject& reject : executeResult.rejects) {
    std::cout << "rejected by " << reject.policy << " ["
              << static_cast<int>(reject.code) << "]: " << reject.reason
              << ": " << reject.details << '\n';
  }
}
Rust
let request = engine.start_pre_trade(order).expect("start stage must pass");

// Main stage consumes the deferred request and returns reservation or rejects.
match request.execute() {
    Ok(mut reservation) => {
        // Commit only after the caller knows the reservation should become durable.
        reservation.commit()
    }
    Err(rejects) => {
        for reject in rejects.iter() {
            eprintln!(
                "rejected by {} [{}]: {} ({})",
                reject.policy,
                reject.code,
                reject.reason,
                reject.details
            );
        }
    }
}

Example: Shortcut for Start + Main Stages

Go
// The shortcut runs start stage and main stage as one convenience call.
reservation, rejects, err := engine.ExecutePreTrade(order)
if err != nil {
  panic(err)
}
if rejects != nil {
  for _, r := range rejects {
    fmt.Printf(
      "rejected by %s [%d]: %s (%s)\n",
      r.Policy,
      r.Code,
      r.Reason,
      r.Details,
    )
  }
  return
}
defer reservation.Close()

// Finalization is still explicit even when the two stages are composed.
reservation.Commit()
Python
# The shortcut runs start stage and main stage as one convenience call.
execute_result = engine.execute_pre_trade(order=order)
if execute_result:
    # Finalization is still explicit even when the two stages are composed.
    execute_result.reservation.commit()
else:
    for reject in execute_result.rejects:
        print(
            f"rejected by {reject.policy} "
            f"[{reject.code}]: {reject.reason}: {reject.details}"
        )
JavaScript
import { Engine } from "@openpit/engine";
import { TradeAmount } from "@openpit/engine/param";
import { type OrderInit } from "@openpit/engine/model";
import { buildOrderValidation } from "@openpit/engine/pretrade/policies";

const engine = Engine.builder().builtin(buildOrderValidation()).build();
const order: OrderInit = {
  operation: {
    underlyingAsset: "AAPL",
    settlementAsset: "USD",
    accountId: 99224416,
    side: "BUY",
    tradeAmount: TradeAmount.quantity("100"),
    price: "185",
  },
};

// The shortcut runs start stage and main stage as one convenience call.
const execute = engine.executePreTrade(order);
if (execute.ok) {
  // Finalization is still explicit even when the two stages are composed.
  const reservation = execute.reservation;
  if (reservation === undefined) {
    throw new Error("accepted execute result is missing its reservation");
  }
  reservation.commit();
} else {
  for (const reject of execute.rejects) {
    console.log(
      `rejected by ${reject.policy} [${reject.code}]: ${reject.reason}: ${reject.details}`,
    );
  }
}
C++
// The shortcut runs start stage and main stage as one convenience call.
openpit::pretrade::ExecuteResult executeResult = engine.ExecutePreTrade(order);
if (executeResult.Passed()) {
  // Finalization is still explicit even when the two stages are composed.
  executeResult.reservation->Commit();
} else {
  for (const openpit::pretrade::Reject& reject : executeResult.rejects) {
    std::cout << "rejected by " << reject.policy << " ["
              << static_cast<int>(reject.code) << "]: " << reject.reason
              << ": " << reject.details << '\n';
  }
}
Rust
// The shortcut runs start stage and main stage as one convenience call.
match engine.execute_pre_trade(order) {
    Ok(mut reservation) => {
        // Finalization is still explicit even when the two stages are composed.
        reservation.commit()
    }
    Err(rejects) => {
        for reject in rejects.iter() {
            eprintln!(
                "rejected by {} [{}]: {} ({})",
                reject.policy,
                reject.code,
                reject.reason,
                reject.details
            );
        }
    }
}

Example: Apply a Historical Order with Drop Copy

Drop copy is accepted or rejected exactly like the full pre-trade call: on accept the caller receives an operation, on a fatal evaluation failure it receives the rejects. Persist the caller's local order and price metadata, then commit the operation. If that persistence fails, roll it back instead.

Drop copy still needs a readable order account because the engine uses it as its routing and account-control key. If account_id is missing or unreadable, the engine returns a fatal MissingRequiredField reject before any policy runs, state changes, or account block is recorded.

The persist...metadata calls below are illustrative application-store helpers, not OpenPit API. Mirror tests provide only this local harness.

Go
operation, rejects, err := engine.ApplyDropCopy(order)
if err != nil {
  panic(err)
}
if rejects != nil {
  for _, r := range rejects {
    fmt.Printf(
      "could not apply historical order: %s [%d]: %s\n",
      r.Policy,
      r.Code,
      r.Reason,
    )
  }
  return
}
defer operation.Close()

blocked, err := operation.IsAccountBlocked()
if err != nil {
  panic(err)
}
fmt.Printf("applied; account blocked: %t\n", blocked)

if err := persistHistoricalOrderMetadata(order); err != nil {
  operation.Rollback()
  panic(err)
}
operation.Commit()
Python
result = engine.apply_drop_copy(order=order)
if result:
    operation = result.operation
    print(f"applied; account blocked: {operation.is_account_blocked}")
    # Store the historical order, then make the bookkeeping durable.
    try:
        persist_historical_order_metadata(order)
    except Exception:
        operation.rollback()
        raise
    else:
        operation.commit()
else:
    for reject in result.rejects:
        print(
            "could not apply historical order: "
            f"{reject.policy} [{reject.code}]: {reject.reason}"
        )
JavaScript
import { Engine } from "@openpit/engine";
import { TradeAmount } from "@openpit/engine/param";
import { type OrderInit } from "@openpit/engine/model";
import { buildOrderValidation } from "@openpit/engine/pretrade/policies";

const engine = Engine.builder().builtin(buildOrderValidation()).build();
const order: OrderInit = {
  operation: {
    underlyingAsset: "AAPL",
    settlementAsset: "USD",
    accountId: 99224416,
    side: "BUY",
    tradeAmount: TradeAmount.quantity("100"),
    price: "185",
  },
};

const result = engine.applyDropCopy(order);
if (result.ok) {
  const operation = result.operation;
  if (operation === undefined) {
    throw new Error("applied drop copy is missing its operation");
  }
  console.log(`applied; account blocked: ${operation.isAccountBlocked()}`);
  // Store the historical order, then make the bookkeeping durable.
  try {
    persistHistoricalOrderMetadata(order);
  } catch (error) {
    operation.rollback();
    throw error;
  }
  operation.commit();
} else {
  for (const reject of result.rejects) {
    console.log(
      `could not apply historical order: ${reject.policy} ` +
        `[${reject.code}]: ${reject.reason}`,
    );
  }
}
C++
openpit::pretrade::DropCopyResult dropCopyResult = engine.ApplyDropCopy(order);
if (!dropCopyResult.Passed()) {
  for (const openpit::pretrade::Reject& reject : dropCopyResult.rejects) {
    std::cout << "could not apply historical order: " << reject.policy << " ["
              << static_cast<int>(reject.code) << "]: " << reject.reason
              << '\n';
  }
} else {
  std::cout << "applied; account blocked: "
            << dropCopyResult.operation->IsAccountBlocked() << '\n';
  try {
    PersistHistoricalOrderMetadata(order);
  } catch (...) {
    dropCopyResult.operation->Rollback();
    throw;
  }
  dropCopyResult.operation->Commit();
}
Rust
let metadata = historical_order_metadata(&order);
match engine.apply_drop_copy(order) {
    Ok(mut operation) => {
        eprintln!(
            "applied; account blocked: {}",
            operation.is_account_blocked()
        );
        // Store the historical order, then make the bookkeeping durable.
        match persist_historical_order_metadata(&metadata) {
            Ok(()) => operation.commit(),
            Err(persistence_error) => {
                operation.rollback();
                return Err(persistence_error.into());
            }
        }
    }
    Err(rejects) => {
        for reject in rejects.iter() {
            eprintln!(
                "could not apply historical order: {} [{}]: {}",
                reject.policy, reject.code, reject.reason
            );
        }
    }
}

Example: Apply Post-Trade Feedback

Go
// Execution reports feed realized outcomes back into cumulative policy state.
result, err := engine.ApplyExecutionReport(report)
if err != nil {
  panic(err)
}
for _, outcome := range result.AccountPnls {
  fmt.Printf("account P&L outcome for %v\n", outcome.AccountID)
}
for _, outcome := range result.AccountAdjustments {
  fmt.Printf("account adjustment from group %d\n", outcome.PolicyGroupID)
}
if len(result.AccountBlocks) > 0 {
  fmt.Println("halt new orders until the blocked state is cleared")
}
Python
# Execution reports feed realized outcomes back into cumulative policy state.
result = engine.apply_execution_report(report=report)
for outcome in result.account_pnls:
    print(f"account P&L outcome for {outcome.account_id}")
for outcome in result.account_adjustments:
    print(f"account adjustment from group {outcome.policy_group_id}")
if result.account_blocks:
    print("halt new orders until the blocked state is cleared")
JavaScript
import { Engine } from "@openpit/engine";
import { type ExecutionReportInit } from "@openpit/engine/model";
import { buildOrderValidation } from "@openpit/engine/pretrade/policies";

const engine = Engine.builder().builtin(buildOrderValidation()).build();
const report: ExecutionReportInit = {
  operation: {
    underlyingAsset: "AAPL",
    settlementAsset: "USD",
    accountId: 99224416,
    side: "BUY",
  },
  financialImpact: { pnl: "-50", fee: "3.4" },
};

// Execution reports feed realized outcomes back into cumulative policy state.
const result = engine.applyExecutionReport(report);
for (const outcome of result.accountPnls) {
  console.log(`account P&L outcome for ${outcome.accountId.toString()}`);
}
for (const outcome of result.accountAdjustments) {
  console.log(`account adjustment from group ${outcome.policyGroupId}`);
}
if (result.accountBlocks.length > 0) {
  console.log("halt new orders until the blocked state is cleared");
}
C++
// Execution reports feed realized outcomes back into cumulative policy state.
const openpit::PostTradeResult result = engine.ApplyExecutionReport(report);
for (const auto& outcome : result.accountPnls) {
  std::cout << "account P&L outcome for " << outcome.accountId.ToString()
            << '\n';
}
for (const auto& outcome : result.accountAdjustments) {
  std::cout << "account adjustment from group "
            << outcome.policyGroupId.Value() << '\n';
}
if (!result.accountBlocks.empty()) {
  std::cout << "halt new orders until the blocked state is cleared" << '\n';
}
Rust
// Execution reports feed realized outcomes back into cumulative policy state.
let result = engine.apply_execution_report(&report);
for outcome in &result.account_pnls {
    eprintln!("account P&L outcome for {}", outcome.account_id);
}
for outcome in &result.account_adjustments {
    eprintln!(
        "account adjustment from group {}",
        outcome.policy_group_id.value()
    );
}
if !result.account_blocks.is_empty() {
    eprintln!("halt new orders until the blocked state is cleared");
}

Caller Responsibilities

  • Finalize every successful reservation exactly once.
  • Keep the reservation alive until the host knows whether the order should keep consuming reserved state.
  • Feed realized outcomes back through execution reports so cumulative controls stay aligned with external truth.