-
Notifications
You must be signed in to change notification settings - Fork 1
Getting Started
OpenPit ships public SDKs for:
- Go module
go.openpit.dev/openpit - Python package
openpit - JavaScript / TypeScript package
@openpit/engine - C++
OpenPit::openpitCMake target - Rust crate
openpit - C API header and docs
All SDKs follow the same operational flow:
- Build an engine once during application startup.
- Run the
start stagefor each order. -
Execute requestif the start stage passes. -
Finalize reservationexplicitly. -
Apply execution reportafter realized outcomes are known.
Prose uses the conceptual step names; exact API names stay inside the code blocks.
go get go.openpit.dev/openpitpip install openpitnpm install @openpit/engineNode.js 18 and later load the packaged Node WebAssembly build. Browser and edge
bundles use the base64-inlined WebAssembly build, so they do not need fetch,
fs, or a separately hosted .wasm file. Both runtimes initialize
synchronously on import for the common path. See
Runtime Delivery for the full resolution matrix.
No build step is needed in a browser or in Deno: import the package straight from a CDN.
<script type="module">
import { Engine } from "https://esm.sh/@openpit/engine";
// or: https://cdn.jsdelivr.net/npm/@openpit/engine/+esm
</script>Deno also accepts the npm specifier directly.
import { Engine as DenoEngine } from "npm:@openpit/engine";The C++ binding is a CMake config package. Pull it in with FetchContent and
link the OpenPit::openpit alias; the prebuilt runtime is resolved
automatically.
include(FetchContent)
FetchContent_Declare(
openpit
GIT_REPOSITORY https://github.com/openpitkit/pit.git
SOURCE_SUBDIR bindings/cpp
)
FetchContent_MakeAvailable(openpit)
target_link_libraries(my_app PRIVATE OpenPit::openpit)Include <openpit/openpit.hpp> for the complete SDK surface, or
<openpit/fwd.hpp> in precompiled headers when forward declarations are
enough. See Runtime Delivery for how the package finds the
runtime library and for the Windows DLL-copy helper.
vcpkg is the packaged alternative. Declare the dependency
in the project manifest, vcpkg.json:
{
"name": "your-project",
"version-string": "0",
"builtin-baseline": "<microsoft-vcpkg-commit>",
"dependencies": ["openpit"]
}builtin-baseline is a commit of the microsoft/vcpkg checkout the project
builds against; vcpkg x-update-baseline --add-initial-baseline fills it in. It
fixes the package versions of the whole build and is required for both
installation paths below.
Consume the port like any other CMake package, then configure the project with the vcpkg toolchain file. The normal runtime resolver takes over from there.
find_package(OpenPit CONFIG REQUIRED)
target_link_libraries(your_target PRIVATE OpenPit::openpit)The preferred path. The manifest above is the whole setup, because vcpkg resolves the package from the registry it already ships with. Ports land there through upstream review, so a fresh release becomes installable this way with a delay.
The managed registry receives
every release first. Use it when the release you need has not reached the
public registry yet, or when pinning an exact version. The cost is one extra
file next to the manifest, vcpkg-configuration.json:
{
"registries": [
{
"kind": "git",
"repository": "https://github.com/openpitkit/vcpkg-registry.git",
"baseline": "<openpit-registry-commit>",
"packages": ["openpit"]
}
]
}This baseline is a commit of the managed registry, not of microsoft/vcpkg;
every release note publishes the commit to use.
cargo add openpitGo
package main
import (
"fmt"
"log"
"time"
"go.openpit.dev/openpit"
"go.openpit.dev/openpit/model"
"go.openpit.dev/openpit/param"
"go.openpit.dev/openpit/pkg/optional"
"go.openpit.dev/openpit/pretrade/policies"
)
func main() {
usd, err := param.NewAsset("USD")
if err != nil {
log.Fatal(err)
}
lowerBound, err := param.NewPnlFromString("-1000")
if err != nil {
log.Fatal(err)
}
maxQty, err := param.NewQuantityFromString("500")
if err != nil {
log.Fatal(err)
}
maxNotional, err := param.NewVolumeFromString("100000")
if err != nil {
log.Fatal(err)
}
// 1. Build the engine (one time at the platform initialization).
engine, err := openpit.NewEngineBuilder().
FullSync().
Builtin(policies.BuildOrderValidation()).
Builtin(
policies.BuildPnlBoundsKillSwitch().
BrokerBarriers(
policies.PnlBoundsBrokerBarrier{
SettlementAsset: usd,
LowerBound: optional.Some(lowerBound),
},
),
).
Builtin(
policies.BuildRateLimit().
BrokerBarrier(
policies.RateLimitBrokerBarrier{
Limit: policies.RateLimit{
MaxOrders: 100,
Window: time.Second,
},
},
),
).
Builtin(
policies.BuildOrderSizeLimit().
AssetBarriers(
policies.OrderSizeAssetBarrier{
SettlementAsset: usd,
Limit: policies.OrderSizeLimit{
MaxQuantity: maxQty,
MaxNotional: maxNotional,
},
},
).
BrokerBarrier(
policies.OrderSizeBrokerBarrier{
Limit: policies.OrderSizeLimit{
MaxQuantity: maxQty,
MaxNotional: maxNotional,
},
},
),
).
Build()
if err != nil {
log.Fatal(err)
}
defer engine.Stop()
// 2. Check an order.
order := model.NewOrder()
op := order.EnsureOperationView()
aapl, err := param.NewAsset("AAPL")
if err != nil {
log.Fatal(err)
}
op.SetInstrument(param.NewInstrument(aapl, usd))
op.SetAccountID(param.NewAccountIDFromUint64(99224416))
op.SetSide(param.SideBuy)
price, _ := param.NewPriceFromString("185")
qty, _ := param.NewQuantityFromString("100")
op.SetTradeAmount(param.NewQuantityTradeAmount(qty))
op.SetPrice(price)
request, rejects, err := engine.StartPreTrade(order)
if err != nil {
log.Fatal(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()
// 3. Quick, lightweight checks were performed during start stage. The
// system state has not yet changed (except controls that must observe every
// request). Before the heavy-duty checks, other work on the request can be
// performed simply by holding the request object.
// 4. Real pre-trade and risk control.
reservation, rejects, err := request.Execute()
if err != nil {
log.Fatal(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()
// Optional shortcut for the same two-stage flow:
// reservation, rejects, err := engine.ExecutePreTrade(order)
// 5. If the request is successfully sent to the venue, it must be committed.
// The rollback must be called otherwise to revert all performed reservations.
reservation.Commit()
// 6. The order goes to the venue and returns with an execution report.
report := model.NewExecutionReport()
reportOp := model.NewExecutionReportOperation()
reportOp.SetInstrument(param.NewInstrument(aapl, usd))
reportOp.SetAccountID(param.NewAccountIDFromUint64(99224416))
reportOp.SetSide(param.SideBuy)
report.SetOperation(reportOp)
pnl, _ := param.NewPnlFromString("-50")
fee, _ := param.NewFeeFromString("3.4")
impact := model.NewExecutionReportFinancialImpact()
impact.SetPnl(pnl)
impact.SetFee(fee)
report.SetFinancialImpact(impact)
result, err := engine.ApplyExecutionReport(report)
if err != nil {
log.Fatal(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)
}
// 7. After each execution report is applied, the system may report that it
// has been determined in advance that all subsequent requests will be
// rejected if the account status does not change.
if len(result.AccountBlocks) > 0 {
fmt.Println("halt new orders until the blocked state is cleared")
}
}Python
import datetime
import openpit
import openpit.pretrade.policies
# 1. Build the engine (one time at the platform initialization).
max_qty = openpit.param.Quantity("500")
max_notional = openpit.param.Volume("100000")
engine = (
openpit.Engine.builder()
.no_sync()
.builtin(openpit.pretrade.policies.build_order_validation())
.builtin(
openpit.pretrade.policies.build_pnl_bounds_killswitch()
.broker_barriers(
openpit.pretrade.policies.PnlBoundsBrokerBarrier(
settlement_asset="USD",
lower_bound=openpit.param.Pnl("-1000"),
),
)
)
.builtin(
openpit.pretrade.policies.build_rate_limit()
.broker_barrier(
openpit.pretrade.policies.RateLimitBrokerBarrier(
limit=openpit.pretrade.policies.RateLimit(
max_orders=100,
window=datetime.timedelta(seconds=1),
),
),
)
)
.builtin(
openpit.pretrade.policies.build_order_size_limit()
.broker_barrier(
openpit.pretrade.policies.OrderSizeBrokerBarrier(
limit=openpit.pretrade.policies.OrderSizeLimit(
max_quantity=max_qty,
max_notional=max_notional,
),
)
)
.asset_barriers(
openpit.pretrade.policies.OrderSizeAssetBarrier(
limit=openpit.pretrade.policies.OrderSizeLimit(
max_quantity=max_qty,
max_notional=max_notional,
),
settlement_asset="USD",
),
)
)
.build()
)
# 2. Check an order.
order = openpit.Order(
operation=openpit.OrderOperation(
instrument=openpit.Instrument("AAPL", "USD"),
account_id=openpit.param.AccountId.from_int(99224416),
side=openpit.param.Side.BUY,
trade_amount=openpit.param.TradeAmount.quantity(100.0),
price=openpit.param.Price(185.0),
),
)
start_result = engine.start_pre_trade(order=order)
if not start_result:
messages = ", ".join(
f"{r.policy} [{r.code}]: {r.reason}: {r.details}"
for r in start_result.rejects
)
raise RuntimeError(messages)
request = start_result.request
# 3. Quick, lightweight checks, such as fat-finger scope or enabled kill
# switch, were performed during pre-trade request creation. The system state
# has not yet changed, except in cases where each request, even rejected ones,
# must be considered. Before the heavy-duty checks, other work on the request
# can be performed simply by holding the request object.
# 4. Real pre-trade and risk control.
execute_result = request.execute()
# Optional shortcut for the same two-stage flow:
# execute_result = engine.execute_pre_trade(order=order)
if not execute_result:
messages = ", ".join(
f"{reject.policy} [{reject.code}]: {reject.reason}: {reject.details}"
for reject in execute_result.rejects
)
raise RuntimeError(messages)
reservation = execute_result.reservation
# 5. If the request is successfully sent to the venue, it must be committed.
# The rollback must be called otherwise to revert all performed reservations.
try:
send_order_to_venue(order)
except Exception:
reservation.rollback()
raise
reservation.commit()
# 6. The order goes to the venue and returns with an execution report.
report = openpit.ExecutionReport(
operation=openpit.ExecutionReportOperation(
instrument=openpit.Instrument("AAPL", "USD"),
account_id=openpit.param.AccountId.from_int(99224416),
side=openpit.param.Side.BUY,
),
financial_impact=openpit.FinancialImpact(
pnl=openpit.param.Pnl("-50"),
fee=openpit.param.Fee("3.4"),
),
)
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}")
# 7. After each execution report is applied, the system may report that it has
# been determined in advance that all subsequent requests will be rejected if
# the account status does not change.
assert not result.account_blocksJavaScript
import { Engine } from "@openpit/engine";
import { TradeAmount } from "@openpit/engine/param";
import {
type ExecutionReportInit,
type OrderInit,
} from "@openpit/engine/model";
import {
buildOrderSizeLimit,
buildOrderValidation,
buildPnlBoundsKillswitch,
buildRateLimit,
OrderSizeAssetBarrier,
OrderSizeBrokerBarrier,
OrderSizeLimit,
PnlBoundsBrokerBarrier,
RateLimit,
RateLimitBrokerBarrier,
} from "@openpit/engine/pretrade/policies";
// 1. Build the engine (one time at the platform initialization). The WASM
// engine is single-threaded and has no user-selectable sync mode. The first
// builtin() advances the staged builder to the ready builder; the rest register
// in place.
const ready = Engine.builder().builtin(buildOrderValidation());
ready.builtin(
buildPnlBoundsKillswitch().brokerBarriers([
new PnlBoundsBrokerBarrier("USD", "-1000", undefined),
]),
);
ready.builtin(
buildRateLimit().brokerBarrier(
new RateLimitBrokerBarrier(new RateLimit(100, 1000)),
),
);
ready.builtin(
buildOrderSizeLimit()
.brokerBarrier(
new OrderSizeBrokerBarrier(new OrderSizeLimit("500", "100000")),
)
.assetBarriers([
new OrderSizeAssetBarrier(new OrderSizeLimit("500", "100000"), "USD"),
]),
);
const engine = ready.build();
// 2. Check an order. Scalars accept plain values (the account id as a number,
// the price as a decimal string); the order is an object literal.
const order: OrderInit = {
operation: {
underlyingAsset: "AAPL",
settlementAsset: "USD",
accountId: 99224416,
side: "BUY",
tradeAmount: TradeAmount.quantity("100"),
price: "185",
},
};
const start = engine.startPreTrade(order);
if (!start.ok) {
const reasons = start.rejects
.map((r) => `${r.policy} [${r.code}]: ${r.reason} (${r.details})`)
.join(", ");
throw new Error(reasons);
}
// 3. Quick, lightweight checks were performed during the start stage. The
// system state has not yet changed. Before the heavy-duty checks, other work on
// the request can be performed by holding the request object.
// 4. Real pre-trade and risk control.
const request = start.request;
if (request === undefined) {
throw new Error("accepted start result is missing its request");
}
const execute = request.execute();
if (!execute.ok) {
const reasons = execute.rejects
.map((r) => `${r.policy} [${r.code}]: ${r.reason} (${r.details})`)
.join(", ");
throw new Error(reasons);
}
// Optional shortcut for the same two-stage flow:
// const execute = engine.executePreTrade(order);
// 5. If the request is successfully sent to the venue, commit; roll back
// otherwise to revert all performed reservations.
const reservation = execute.reservation;
if (reservation === undefined) {
throw new Error("accepted execute result is missing its reservation");
}
try {
// sendOrderToVenue(order);
reservation.commit();
} catch (err) {
reservation.rollback();
throw err;
}
// 6. The order goes to the venue and returns with an execution report.
const report: ExecutionReportInit = {
operation: {
underlyingAsset: "AAPL",
settlementAsset: "USD",
accountId: 99224416,
side: "BUY",
},
financialImpact: { pnl: "-50", fee: "3.4" },
};
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}`);
}
// 7. A non-empty accountBlocks means a kill switch has fired for the account.
if (result.accountBlocks.length > 0) {
console.log("halt new orders until the blocked state is cleared");
}C++
#include <openpit/openpit.hpp>
#include <openpit/pretrade/policies.hpp>
#include <iostream>
namespace policies = openpit::pretrade::policies;
using openpit::param::Asset;
using openpit::param::Fee;
using openpit::param::Pnl;
using openpit::param::Price;
using openpit::param::Quantity;
using openpit::param::Volume;
int main() {
const Quantity maxQty = Quantity::FromString("500");
const Volume maxNotional = Volume::FromString("100000");
// 1. Build the engine (one time at the platform initialization).
openpit::EngineBuilder builder(openpit::SyncPolicy::None);
builder.Add(policies::OrderValidationPolicy{});
policies::PnlBoundsBrokerBarrier pnlBarrier{openpit::param::Asset("USD")};
pnlBarrier.lowerBound = Pnl::FromString("-1000");
builder.Add(policies::PnlBoundsKillSwitchPolicy{}.BrokerBarrier(pnlBarrier));
builder.Add(
policies::RateLimitPolicy{}.BrokerBarrier(policies::RateLimitBrokerBarrier(
policies::RateLimit(/*maxOrders=*/100,
/*windowNanoseconds=*/1'000'000'000))));
builder.Add(
policies::OrderSizeLimitPolicy{}
.BrokerBarrier(policies::OrderSizeBrokerBarrier(
policies::OrderSizeLimit(maxQty, maxNotional)))
.AssetBarrier(policies::OrderSizeAssetBarrier(
policies::OrderSizeLimit(maxQty, maxNotional),
openpit::param::Asset("USD"))));
const openpit::Engine engine = builder.Build();
// 2. Check an order.
openpit::model::Order order = openpit::model::Order::Limit(
openpit::model::Instrument(::openpit::param::Asset("AAPL"),
::openpit::param::Asset("USD")),
openpit::param::AccountId::FromUint64(99224416),
openpit::model::Side::Buy,
openpit::model::TradeAmount::OfQuantity(Quantity::FromString("100")),
Price::FromString("185"));
openpit::pretrade::StartResult start = engine.StartPreTrade(order);
if (!start.Passed()) {
for (const openpit::pretrade::Reject& r : start.rejects) {
std::cout << "rejected by " << r.policy << " [" << static_cast<int>(r.code)
<< "]: " << r.reason << " (" << r.details << ")\n";
}
return 0;
}
openpit::pretrade::Request request = std::move(*start.request);
// 3. Quick, lightweight checks were performed during the start stage. The
// system state has not yet changed (except controls that must observe every
// request). Before the heavy-duty checks, other work on the request can be
// performed simply by holding the request object.
// 4. Real pre-trade and risk control.
openpit::pretrade::ExecuteResult executed = request.Execute();
// Optional shortcut for the same two-stage flow:
// openpit::pretrade::ExecuteResult executed = engine.ExecutePreTrade(order);
if (!executed.Passed()) {
for (const openpit::pretrade::Reject& r : executed.rejects) {
std::cout << "rejected by " << r.policy << " [" << static_cast<int>(r.code)
<< "]: " << r.reason << " (" << r.details << ")\n";
}
return 0;
}
openpit::pretrade::Reservation reservation = std::move(*executed.reservation);
// 5. Commit after a successful venue handoff. Call Rollback() on a known
// failure; if an exception exits this scope first, Reservation destruction
// rolls the pending state back automatically.
reservation.Commit();
// 6. The order goes to the venue and returns with an execution report.
openpit::model::ExecutionReport report;
openpit::model::ExecutionReportOperation reportOp;
reportOp.instrument = openpit::model::Instrument(
::openpit::param::Asset("AAPL"), ::openpit::param::Asset("USD"));
reportOp.accountId = openpit::param::AccountId::FromUint64(99224416);
reportOp.side = openpit::model::Side::Buy;
report.operation = std::move(reportOp);
openpit::model::FinancialImpact impact;
impact.pnl = Pnl::FromString("-50");
impact.fee = Fee::FromString("3.4");
report.financialImpact = std::move(impact);
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';
}
// 7. After each execution report is applied, the system may report that it
// has been determined in advance that all subsequent requests will be
// rejected if the account status does not change.
if (!result.accountBlocks.empty()) {
std::cout << "halt new orders until the blocked state is cleared\n";
}
return 0;
}Rust
use std::time::Duration;
use openpit::{
FinancialImpact, ExecutionReportOperation, OrderOperation,
WithFinancialImpact, WithExecutionReportOperation,
};
use openpit::param::{
AccountId, Asset, Fee, Pnl, Price, Quantity, Side, TradeAmount, Volume,
};
use openpit::pretrade::policies::{
OrderSizeAssetBarrier, OrderSizeBrokerBarrier, OrderSizeLimit, OrderSizeLimitPolicy,
OrderSizeLimitSettings, OrderValidationPolicy,
PnlBoundsBrokerBarrier, PnlBoundsKillSwitchPolicy, PnlBoundsKillSwitchSettings,
RateLimit, RateLimitBrokerBarrier, RateLimitPolicy, RateLimitSettings,
};
use openpit::storage::NoLocking;
use openpit::{Engine, Instrument};
# fn main() -> Result<(), Box<dyn std::error::Error>> {
let usd = Asset::new("USD")?;
// 1. Build the engine builder.
type Report = WithExecutionReportOperation<WithFinancialImpact<()>>;
let builder = Engine::builder::<OrderOperation, Report, ()>().no_sync();
// 2. Configure policies.
let pnl_policy = PnlBoundsKillSwitchPolicy::new(
PnlBoundsKillSwitchSettings::new(
[PnlBoundsBrokerBarrier {
settlement_asset: usd.clone(),
lower_bound: Some(Pnl::from_str("-1000")?),
upper_bound: None,
}],
[],
)?,
builder.storage_builder(),
);
let rate_limit_policy = RateLimitPolicy::new(
RateLimitSettings::new(
Some(RateLimitBrokerBarrier {
limit: RateLimit {
max_orders: 100,
window: Duration::from_secs(1),
},
}),
[],
[],
[],
)?,
builder.storage_builder(),
);
// 3. Build the engine (one time at the platform initialization).
let engine = builder
.pre_trade(OrderValidationPolicy::new())
.pre_trade(pnl_policy)
.pre_trade(rate_limit_policy)
.pre_trade(OrderSizeLimitPolicy::<NoLocking>::new(
OrderSizeLimitSettings::new(
Some(OrderSizeBrokerBarrier {
limit: OrderSizeLimit {
max_quantity: Quantity::from_str("500")?,
max_notional: Volume::from_str("100000")?,
},
}),
[OrderSizeAssetBarrier {
limit: OrderSizeLimit {
max_quantity: Quantity::from_str("500")?,
max_notional: Volume::from_str("100000")?,
},
settlement_asset: usd.clone(),
}],
[],
)?,
))
.build()?;
// 4. Check an order.
let order = OrderOperation {
instrument: Instrument::new(
Asset::new("AAPL")?,
usd.clone(),
),
account_id: AccountId::from_u64(99224416),
side: Side::Buy,
trade_amount: TradeAmount::Quantity(
Quantity::from_f64(100.0)?,
),
price: Some(Price::from_str("185")?),
};
let request = engine.start_pre_trade(order).map_err(|rejects| {
let message = rejects
.iter()
.map(|r| format!(
"rejected by {} [{}]: {} ({})",
r.policy, r.code, r.reason, r.details,
))
.collect::<Vec<_>>()
.join(", ");
std::io::Error::other(message)
})?;
// 5. Quick, lightweight checks, such as fat-finger scope or enabled killswitch,
// were performed during pre-trade request creation. The system state has not
// yet changed, except in cases where each request, even rejected ones, must be
// considered (for example, to prevent frequent transfers). Before the
// heavy-duty checks, other work on the request can be performed simply by
// holding the request object.
// 6. Real pre-trade and risk control.
let mut reservation = request.execute().map_err(|rejects| {
let message = rejects
.iter()
.map(|r| format!(
"rejected by {} [{}]: {} ({})",
r.policy, r.code, r.reason, r.details,
))
.collect::<Vec<_>>()
.join(", ");
std::io::Error::other(message)
})?;
// Optional shortcut for the same two-stage flow:
// let reservation = engine.execute_pre_trade(order)?;
// 7. If the request is successfully sent to the venue, it must be committed.
// The rollback must be called otherwise to revert all performed reservations.
reservation.commit();
// 8. The order goes to the venue and returns with an execution report.
let report = WithExecutionReportOperation {
inner: WithFinancialImpact {
inner: (),
financial_impact: FinancialImpact {
pnl: Pnl::from_str("-50")?,
fee: Fee::from_str("3.4")?,
},
},
operation: ExecutionReportOperation {
instrument: Instrument::new(
Asset::new("AAPL")?,
usd,
),
account_id: AccountId::from_u64(99224416),
side: Side::Buy,
},
};
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()
);
}
// 9. After each execution report is applied, the system may report that it has
// been determined in advance that all subsequent requests will be rejected if
// the account status does not change.
assert!(result.account_blocks.is_empty());
# Ok(())
# }Go
reservation, rejects, err := engine.ExecutePreTrade(order)
if err != nil {
log.Fatal(err)
}
if rejects != nil {
for _, r := range rejects {
log.Printf(
"rejected by %s [%d]: %s (%s)",
r.Policy,
r.Code,
r.Reason,
r.Details,
)
}
return
}
defer reservation.Close()
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 executed = engine.ExecutePreTrade(order);
if (executed.Passed()) {
// Finalization is still explicit even when the two stages are composed.
executed.reservation->Commit();
} else {
for (const openpit::pretrade::Reject& reject : executed.rejects) {
std::cerr << "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
);
}
}
}Go
request, rejects, err := engine.StartPreTrade(order)
if err != nil {
log.Fatal(err)
}
if rejects != nil {
for _, r := range rejects {
log.Printf(
"rejected by %s [%d]: %s (%s)",
r.Policy,
r.Code,
r.Reason,
r.Details,
)
}
return
}
defer request.Close()
reservation, rejects, err := request.Execute()
if err != nil {
log.Fatal(err)
}
if rejects != nil {
for _, r := range rejects {
log.Printf(
"rejected by %s [%d]: %s (%s)",
r.Policy,
r.Code,
r.Reason,
r.Details,
)
}
return
}
defer reservation.Close()
reservation.Commit()Python
start_result = engine.start_pre_trade(order=order)
if not start_result:
messages = ", ".join(
f"{r.policy} [{r.code}]: {r.reason}: {r.details}"
for r in start_result.rejects
)
raise RuntimeError(messages)
execute_result = start_result.request.execute()
if not execute_result:
messages = ", ".join(
f"{reject.policy} [{reject.code}]: {reject.reason}: {reject.details}"
for reject in execute_result.rejects
)
raise RuntimeError(messages)
execute_result.reservation.commit()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);
if (!start.ok) {
const reasons = start.rejects
.map((r) => `${r.policy} [${r.code}]: ${r.reason}: ${r.details}`)
.join(", ");
throw new Error(reasons);
}
const request = start.request;
if (request === undefined) {
throw new Error("accepted start result is missing its request");
}
const execute = request.execute();
if (!execute.ok) {
const reasons = execute.rejects
.map((r) => `${r.policy} [${r.code}]: ${r.reason}: ${r.details}`)
.join(", ");
throw new Error(reasons);
}
const reservation = execute.reservation;
if (reservation === undefined) {
throw new Error("accepted execute result is missing its reservation");
}
reservation.commit();C++
openpit::pretrade::StartResult start = engine.StartPreTrade(order);
if (!start.Passed()) {
for (const openpit::pretrade::Reject& reject : start.rejects) {
std::cerr << "rejected by " << reject.policy << " ["
<< static_cast<int>(reject.code) << "]: " << reject.reason << " ("
<< reject.details << ")\n";
}
return;
}
openpit::pretrade::ExecuteResult executed = start.request->Execute();
if (!executed.Passed()) {
for (const openpit::pretrade::Reject& reject : executed.rejects) {
std::cerr << "rejected by " << reject.policy << " ["
<< static_cast<int>(reject.code) << "]: " << reject.reason << " ("
<< reject.details << ")\n";
}
return;
}
executed.reservation->Commit();Rust
let request = match engine.start_pre_trade(order) {
Ok(request) => request,
Err(rejects) => {
for reject in rejects.iter() {
eprintln!(
"rejected by {} [{}]: {} ({})",
reject.policy,
reject.code,
reject.reason,
reject.details
);
}
return;
}
};
let mut reservation = match request.execute() {
Ok(reservation) => reservation,
Err(rejects) => {
for reject in rejects.iter() {
eprintln!(
"rejected by {} [{}]: {} ({})",
reject.policy,
reject.code,
reject.reason,
reject.details
);
}
return;
}
};
reservation.commit();Go
// Execution reports feed realized outcomes back into cumulative policy state.
result, err := engine.ApplyExecutionReport(report)
if err != nil {
log.Fatal(err)
}
for _, outcome := range result.AccountPnls {
log.Printf("account P&L outcome for %v", outcome.AccountID)
}
for _, outcome := range result.AccountAdjustments {
log.Printf("account adjustment from group %d", outcome.PolicyGroupID)
}
if len(result.AccountBlocks) > 0 {
log.Print("halt new orders until the blocked state is cleared")
}Python
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::cerr << "account P&L outcome for " << outcome.accountId.ToString()
<< '\n';
}
for (const auto& outcome : result.accountAdjustments) {
std::cerr << "account adjustment from group "
<< outcome.policyGroupId.Value() << '\n';
}
if (!result.accountBlocks.empty()) {
std::cerr << "halt new orders until the blocked state is cleared\n";
}Rust
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");
}- OpenPit is in-memory. Persistence belongs to the host system.
- OpenPit does not route orders or talk to venues.
- Most policies consume caller-supplied realized outcomes. Spot Funds is the exception: it derives independent position and account P&L from reconciled fills, cost basis, fees, account currency, and available FX quotes.
- A shared engine instance behaves according to the chosen sync mode (full, local, or account). See Threading Contract for the per-mode contract.
- Treat custom-policy state according to the same sync mode. Under full
sync, shared state must be thread-safe; under local or account sync, do
not access it concurrently with engine calls. Prefer feeding state
corrections through
apply account adjustments.
- Pre-trade Pipeline: Request, reject, and reservation semantics
- Policies: Built-in controls and custom policy hooks
- Dynamic Policy Reconfiguration: Retune built-in policies at runtime
- Reject Codes: Standard business reject codes
- Architecture: Public integration model