openpit 0.1.0

Embeddable pre-trade risk SDK
Documentation

Pit: Pre-trade Integrity Toolkit

openpit is an embeddable pre-trade risk SDK for integrating policy-driven risk checks into trading systems.

For full project documentation, see the repository README. For conceptual and architectural pages, see the project wiki.

Install

[dependencies]
openpit = "0.1.0"

Engine

Overview

The engine evaluates an order through a deterministic pre-trade pipeline:

  • start_pre_trade(order) runs lightweight start-stage policies
  • Request::execute() runs main-stage policies
  • Reservation::commit() applies reserved state
  • dropping Reservation rolls state back automatically
  • apply_execution_report(report) updates post-trade policy state

Start-stage policies stop on the first reject. Main-stage policies aggregate rejects and roll back registered mutations in reverse order when any reject is produced.

Built-in start-stage policies currently include:

  • OrderValidationPolicy
  • PnlKillSwitchPolicy
  • RateLimitPolicy
  • OrderSizeLimitPolicy

There are two types of rejections: a full kill switch for the account and a rejection of only the current request. This is useful in algorithmic trading when automatic order submission must be halted until the situation is analyzed.

Usage

use std::time::Duration;

use openpit::param::{Asset, Fee, Pnl, Price, Quantity, Side, Volume};
use openpit::pretrade::policies::{OrderSizeLimit, OrderSizeLimitPolicy};
use openpit::pretrade::policies::OrderValidationPolicy;
use openpit::pretrade::policies::PnlKillSwitchPolicy;
use openpit::pretrade::policies::RateLimitPolicy;
use openpit::pretrade::{ExecutionReport, Instrument, Order};
use openpit::Engine;

let usd = Asset::new("USD").expect("asset code must be valid");

// 1. Configure policies.
let pnl = PnlKillSwitchPolicy::new(
    (usd.clone(), Pnl::from_str("1000").expect("valid pnl literal")),
    [],
);

let rate_limit = RateLimitPolicy::new(100, Duration::from_secs(1));

let size = OrderSizeLimitPolicy::new(
    OrderSizeLimit {
        settlement_asset: usd.clone(),
        max_quantity: Quantity::from_str("500").expect("valid quantity literal"),
        max_notional: Volume::from_str("100000").expect("valid volume literal"),
    },
    [],
);

// 2. Build the engine (one time at the platform initialization).
let engine = Engine::builder()
    .check_pre_trade_start_policy(OrderValidationPolicy::new())
    .check_pre_trade_start_policy(pnl)
    .check_pre_trade_start_policy(rate_limit)
    .check_pre_trade_start_policy(size)
    .build()
    .expect("engine config must be valid");

// 3. Check an order.
let order = Order {
    instrument: Instrument::new(
        Asset::new("AAPL").expect("asset code must be valid"),
        usd.clone(),
    ),
    side: Side::Buy,
    quantity: Quantity::from_f64(100).expect("valid quantity value"),
    price: Price::from_f64(185).expect("valid price value"),
};

let request = engine
    .start_pre_trade(order)
    .expect("start-stage checks must pass");

// 4. 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.

// 5. Real pre-trade and risk control.
let reservation = request.execute().expect("main-stage checks must pass");

// 6. 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();

// 5. The order goes to the venue and returns with an execution report.
let report = ExecutionReport {
    instrument: Instrument::new(
        Asset::new("AAPL").expect("asset code must be valid"),
        usd,
    ),
    pnl: Pnl::from_f64(-50).expect("valid pnl value"),
    fee: Fee::from_f64(3.4).expect("valid fee value"),
};

let result = engine.apply_execution_report(&report);

// 6. 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.kill_switch_triggered);

Errors

Rejects from start_pre_trade(order) and Request::execute() are returned as Err(Reject) and Result<Reservation, Vec<Reject>>.

Each Reject contains:

  • policy: policy name
  • code: stable machine-readable code (for example RejectCode::OrderQtyExceedsLimit)
  • reason: short human-readable reject type (for example "order quantity exceeded")
  • details: concrete case details (for example "requested 11, max allowed: 10")
  • scope: RejectScope::Order or RejectScope::Account

RejectCode values are standardized and stable across Rust, Python, and C FFI.