Engine lifecycle¶
The engine owns the policy instances registered by the builder. Build one engine per independent risk-control state and choose the synchronization policy that matches the host’s call pattern.
Build an engine¶
import openpit
import openpit.pretrade.policies
engine = (
openpit.Engine.builder()
.no_sync()
.builtin(
openpit.pretrade.policies.build_order_validation(),
)
.pre_trade(policy=MyMainStagePolicy())
.pre_trade(policy=MyAdjustmentCheck())
.build()
)
Policy names must be unique within one engine configuration.
Use full_sync() when the same engine is called concurrently from multiple
OS threads. Use account_sync() only when calls are serialized on one
engine handle and each account is pinned to one processing chain.
Run the explicit two-stage flow¶
start_result = engine.start_pre_trade(order=order)
if not start_result:
messages = ", ".join(
f"{reject.policy} [{reject.code}]: {reject.reason}"
for reject in start_result.rejects
)
raise RuntimeError(messages)
else:
execute_result = start_result.request.execute()
Use the explicit flow when there is useful work between the lightweight start stage and the heavier main stage.
Run the shortcut flow¶
execute_result = engine.execute_pre_trade(order=order)
The shortcut returns the same ExecuteResult shape as request.execute(). It
can contain start-stage rejects or main-stage rejects.
Apply drop copy¶
engine.apply_drop_copy(order=order) evaluates a historical order through the
same policy pipeline without enforcing ordinary pre-trade rejects. It returns
a DropCopyResult in the same shape as ExecuteResult: on success it carries
a single-use DropCopyOperation, otherwise it carries fatal evaluation
rejects. The operation exposes the applied lock, account_adjustments,
account_block, and is_account_blocked, and is finalized exactly like a
reservation - commit() keeps the applied bookkeeping, rollback() reverts
it, and garbage collection rolls back an unfinalized operation.
Rollback does not reverse account-control operations or refund rate-limit
attempts. Existing account and group blocks do not prevent drop-copy
evaluation, and DropCopyOperation.is_account_blocked reports the apply-time
blocked-state snapshot independently from the block requested by this call.
That snapshot is captured before apply_drop_copy returns and does not track
later registry changes. The engine must read the order account before
evaluation; an order without a readable account returns a fatal
MissingRequiredField reject before any policy runs or state changes.
Finalize reservations¶
def send_order(order: openpit.Order) -> None:
_ = order
if execute_result:
reservation = execute_result.reservation
try:
send_order(order)
except Exception:
reservation.rollback()
raise
else:
reservation.commit()
A reservation is single-use. Calling commit() or rollback() after it has
already been finalized raises RuntimeError.
Finalization must not fail. A mutation callable that raises is re-raised to the
caller once the rest of the batch has run, and independently arms the engine
kill switch: a mutation registered by a custom policy - every Mutation
registered from Python - blocks every account, so the next pre-trade call is
rejected with SystemUnavailable until an operator calls
engine.accounts().unblock_all().
Apply post-trade reports¶
post_trade = engine.apply_execution_report(report=report)
if post_trade.account_blocks:
print("halt new orders until the blocked state is cleared")
Reports are how stateful policies receive realized trading outcomes.