openpit.h is an embeddable pre-trade risk SDK entrypoint for integrating
policy-driven risk checks into trading systems from C and other environments
that consume a C ABI.
For an overview and links to all resources, see the project website openpit.dev. For full project documentation, see the repository README. For conceptual and architectural pages, see the project wiki. For the split C reference manual, see the C API docs.
Before the 1.0 release OpenPit follows a relaxed Semantic Versioning:
PATCHreleases carry bug fixes and small internal corrections.MINORreleases may introduce new features and may also change the public interface.
Breaking API changes can appear in minor releases before 1.0. Pick
version constraints that tolerate API evolution during the pre-stable
phase.
Visit the C API documentation.
For normal end-user installation, use the published GitHub release assets:
openpit.hlibopenpit_ffi.soon Linuxlibopenpit_ffi.dylibon macOSopenpit_ffi.dllon WindowsLICENSEOWNERS
If you need local development/debugging, clone this repository and generate the header plus the reference docs.
With Just:
just ffi-c-apiManual:
python3 scripts/generate_api_c.pyIf you need a workspace build:
With Just:
just buildManual:
cargo build --workspaceIf you need only the release C runtime library:
cargo build -p openpit-ffi --releaseThe engine evaluates an order through a deterministic pre-trade pipeline:
openpit_engine_start_pre_trade(...)runs start-stage policies and creates a deferred requestopenpit_pretrade_pre_trade_request_execute(...)runs main-stage check policiesopenpit_pretrade_pre_trade_reservation_commit(...)applies reserved stateopenpit_pretrade_pre_trade_reservation_rollback(...)reverts reserved stateopenpit_engine_apply_execution_report(...)updates post-trade policy state
Start-stage policies aggregate rejects from all registered policies. Main-stage policies aggregate rejects and run rollback mutations in reverse order when any reject is produced.
Built-in policies:
- order validation
- P&L kill switch
- sliding-window rate limit
- per-settlement order size limits
The primary integration model is to build project-specific policies against the public C API: the C API docs.
Two types of rejections are supported: a full kill switch for the account and a rejection of only the current request. Kill switches are intended for algorithmic trading where automatic order submission must be halted until the situation is analyzed.
Built-in policies that maintain state across calls use the SDK's Storage abstraction internally. The runtime library handles the necessary memory synchronization for policy state; the C consumer is responsible only for the threading contract on the SDK handle.
#include <stdbool.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "openpit.h"
static int report_last_error(const char *context) {
fprintf(stderr, "%s failed\n", context);
return 1;
}
/* Prints the error message, then destroys the handle. */
static int report_out_error(const char *context, OpenPitSharedString *error) {
char buf[512];
OpenPitStringView view = openpit_shared_string_view(error);
size_t n = view.len < sizeof(buf) - 1 ? view.len : sizeof(buf) - 1;
if (view.ptr != NULL && n > 0) {
memcpy(buf, view.ptr, n);
}
buf[n] = '\0';
fprintf(stderr, "%s: %s\n", context, buf);
openpit_destroy_shared_string(error);
return 1;
}
static int make_quantity(
int64_t mantissa,
int32_t scale,
OpenPitParamQuantity *out
) {
if (!openpit_create_param_quantity(
(OpenPitParamDecimal){
.mantissa_lo = mantissa,
.mantissa_hi = mantissa < 0 ? -1 : 0,
.scale = scale},
out,
NULL)) {
return report_last_error("openpit_create_param_quantity");
}
return 0;
}
static int make_volume(
int64_t mantissa,
int32_t scale,
OpenPitParamVolume *out
) {
if (!openpit_create_param_volume(
(OpenPitParamDecimal){
.mantissa_lo = mantissa,
.mantissa_hi = mantissa < 0 ? -1 : 0,
.scale = scale},
out,
NULL)) {
return report_last_error("openpit_create_param_volume");
}
return 0;
}
static int make_price(int64_t mantissa, int32_t scale, OpenPitParamPrice *out) {
if (!openpit_create_param_price(
(OpenPitParamDecimal){
.mantissa_lo = mantissa,
.mantissa_hi = mantissa < 0 ? -1 : 0,
.scale = scale},
out,
NULL)) {
return report_last_error("openpit_create_param_price");
}
return 0;
}
static int make_pnl(int64_t mantissa, int32_t scale, OpenPitParamPnl *out) {
if (!openpit_create_param_pnl(
(OpenPitParamDecimal){
.mantissa_lo = mantissa,
.mantissa_hi = mantissa < 0 ? -1 : 0,
.scale = scale},
out,
NULL)) {
return report_last_error("openpit_create_param_pnl");
}
return 0;
}
static int make_fee(int64_t mantissa, int32_t scale, OpenPitParamFee *out) {
if (!openpit_create_param_fee(
(OpenPitParamDecimal){
.mantissa_lo = mantissa,
.mantissa_hi = mantissa < 0 ? -1 : 0,
.scale = scale},
out,
NULL)) {
return report_last_error("openpit_create_param_fee");
}
return 0;
}
static OpenPitStringView make_string_view(const char *s) {
return (OpenPitStringView){
.ptr = (const uint8_t *)s,
.len = s != NULL ? strlen(s) : 0};
}
static const char *view_to_cstr(OpenPitStringView view, char *buf, size_t cap) {
size_t n = view.len < cap - 1 ? view.len : cap - 1;
if (buf == NULL || cap == 0) {
return "";
}
if (view.ptr == NULL || view.len == 0) {
buf[0] = '\0';
return buf;
}
memcpy(buf, view.ptr, n);
buf[n] = '\0';
return buf;
}
static void print_reject(const OpenPitPretradeReject *reject) {
char policy[128];
char reason[256];
char details[256];
fprintf(
stderr,
"%s [%u]: %s: %s\n",
view_to_cstr(reject->policy, policy, sizeof(policy)),
(unsigned)reject->code,
view_to_cstr(reject->reason, reason, sizeof(reason)),
view_to_cstr(reject->details, details, sizeof(details)));
}
static void print_reject_list(const OpenPitPretradeRejectList *rejects) {
size_t i = 0;
size_t len = openpit_pretrade_reject_list_len(rejects);
for (i = 0; i < len; ++i) {
OpenPitPretradeReject item = {0};
if (openpit_pretrade_reject_list_get(rejects, i, &item)) {
print_reject(&item);
}
}
}
int main(void) {
OpenPitSharedString *error = NULL;
int rc = 1;
bool reservation_committed = false;
/* 1. Configure policies. */
OpenPitPretradePoliciesPnlBoundsBarrier pnl_barrier = {0};
OpenPitPretradePoliciesRateLimitBrokerBarrier rate_limit = {0};
OpenPitPretradePoliciesOrderSizeBrokerBarrier order_size_limit = {0};
OpenPitEngineBuilder *builder = NULL;
OpenPitEngine *engine = NULL;
OpenPitPretradePreTradeRequest *request = NULL;
OpenPitPretradePreTradeReservation *reservation = NULL;
OpenPitPretradeRejectList *start_rejects = NULL;
OpenPitPretradeRejectList *execute_rejects = NULL;
OpenPitPretradeAccountBlockList *account_blocks = NULL;
OpenPitOrder order = {0};
OpenPitExecutionReport report = {0};
pnl_barrier.settlement_asset = make_string_view("USD");
if (make_pnl(-1000, 0, &pnl_barrier.lower_bound.value) != 0) {
goto cleanup;
}
pnl_barrier.lower_bound.is_set = true;
rate_limit.max_orders = 100;
rate_limit.window_nanoseconds = 1000000000;
if (make_quantity(500, 0, &order_size_limit.limit.max_quantity) != 0) {
goto cleanup;
}
if (make_volume(100000, 0, &order_size_limit.limit.max_notional) != 0) {
goto cleanup;
}
/* 2. Build the engine once during platform initialization. */
builder = openpit_create_engine_builder(OpenPitSyncPolicy_Full, &error);
if (builder == NULL) {
report_out_error("openpit_create_engine_builder", error);
error = NULL;
goto cleanup;
}
if (!openpit_engine_builder_add_builtin_order_validation_policy(
builder, &error)) {
report_out_error(
"openpit_engine_builder_add_builtin_order_validation_policy",
error);
error = NULL;
goto cleanup;
}
if (!openpit_engine_builder_add_builtin_pnl_bounds_killswitch_policy(
builder, &pnl_barrier, 1, NULL, 0, &error)) {
report_out_error(
"openpit_engine_builder_add_builtin_pnl_bounds_killswitch_policy",
error);
error = NULL;
goto cleanup;
}
if (!openpit_engine_builder_add_builtin_rate_limit_policy(
builder, &rate_limit, NULL, 0, NULL, 0, NULL, 0, &error)) {
report_out_error(
"openpit_engine_builder_add_builtin_rate_limit_policy",
error);
error = NULL;
goto cleanup;
}
if (!openpit_engine_builder_add_builtin_order_size_limit_policy(
builder, &order_size_limit, NULL, 0, NULL, 0, &error)) {
report_out_error(
"openpit_engine_builder_add_builtin_order_size_limit_policy",
error);
error = NULL;
goto cleanup;
}
engine = openpit_engine_builder_build(builder, &error);
if (engine == NULL) {
report_out_error("openpit_engine_builder_build", error);
error = NULL;
goto cleanup;
}
/* 3. Build one order as a POD payload. */
OpenPitParamQuantity qty = {0};
OpenPitParamPrice px = {0};
if (make_quantity(100, 0, &qty) != 0 || make_price(185, 0, &px) != 0) {
goto cleanup;
}
order.operation.is_set = true;
order.operation.value.instrument.underlying_asset = make_string_view("AAPL");
order.operation.value.instrument.settlement_asset = make_string_view("USD");
order.operation.value.side = OpenPitParamSide_Buy;
order.operation.value.trade_amount.kind = OpenPitParamTradeAmountKind_Quantity;
order.operation.value.trade_amount.value = qty._0;
order.operation.value.price.value = px;
order.operation.value.price.is_set = true;
/* 4. Start-stage checks. */
error = NULL;
request = NULL;
start_rejects = NULL;
OpenPitPretradeStatus start_status = openpit_engine_start_pre_trade(
engine, &order, &request, &start_rejects, &error);
if (start_status == OpenPitPretradeStatus_Error) {
report_out_error("openpit_engine_start_pre_trade", error);
error = NULL;
goto cleanup;
}
if (start_status == OpenPitPretradeStatus_Rejected &&
start_rejects != NULL) {
print_reject_list(start_rejects);
goto cleanup;
}
/* 5. Main-stage checks. */
error = NULL;
execute_rejects = NULL;
reservation = NULL;
OpenPitPretradeStatus exec_status = openpit_pretrade_pre_trade_request_execute(
request, &reservation, &execute_rejects, &error);
if (exec_status == OpenPitPretradeStatus_Error) {
report_out_error("openpit_pretrade_pre_trade_request_execute", error);
error = NULL;
goto cleanup;
}
if (exec_status == OpenPitPretradeStatus_Rejected &&
execute_rejects != NULL) {
print_reject_list(execute_rejects);
goto cleanup;
}
/* 6. If the order was sent successfully, commit the reservation. */
if (reservation == NULL) {
report_last_error(
"openpit_pretrade_pre_trade_request_execute: no rejects and no reservation");
goto cleanup;
}
openpit_pretrade_pre_trade_reservation_commit(reservation);
reservation_committed = true;
/* 7. Feed back one execution report as a by-value POD payload. */
report.operation.is_set = true;
report.operation.value.instrument.underlying_asset = make_string_view("AAPL");
report.operation.value.instrument.settlement_asset = make_string_view("USD");
report.operation.value.side = OpenPitParamSide_Buy;
OpenPitParamPnl pnl = {0};
OpenPitParamFee fee = {0};
if (make_pnl(-50, 0, &pnl) != 0 || make_fee(34, 1, &fee) != 0) {
goto cleanup;
}
report.financial_impact.is_set = true;
report.financial_impact.value.pnl.is_set = true;
report.financial_impact.value.pnl.value = pnl;
report.financial_impact.value.fee.is_set = true;
report.financial_impact.value.fee.value = fee;
if (!openpit_engine_apply_execution_report(
engine, &report, &account_blocks, &error)) {
report_out_error("openpit_engine_apply_execution_report", error);
error = NULL;
goto cleanup;
}
/* 8. After each execution report, kill-switch state may change. */
if (account_blocks != NULL) {
fprintf(stderr, "kill switch triggered\n");
openpit_pretrade_destroy_account_block_list(account_blocks);
account_blocks = NULL;
}
rc = 0;
cleanup:
if (reservation != NULL) {
if (!reservation_committed) {
openpit_pretrade_pre_trade_reservation_rollback(reservation);
}
openpit_destroy_pretrade_pre_trade_reservation(reservation);
}
if (request != NULL) {
openpit_destroy_pretrade_pre_trade_request(request);
}
if (execute_rejects != NULL) {
openpit_pretrade_destroy_reject_list(execute_rejects);
}
if (start_rejects != NULL) {
openpit_pretrade_destroy_reject_list(start_rejects);
}
openpit_destroy_engine(engine);
openpit_destroy_engine_builder(builder);
return rc;
}Example flow:
- configure built-in policies
- build the engine
- create an order as
OpenPitOrderPOD payload - run start-stage checks
- run main-stage checks
- commit the reservation after the venue accepts the order
- apply one execution report built as a POD payload
- inspect whether post-trade state triggered a kill switch
For the full type and ownership reference, use the generated C manual: docs/c-api/index.md.
Business rejects are returned through OpenPitPretradeRejectList* and
related APIs such as openpit_engine_start_pre_trade(...) and
openpit_pretrade_pre_trade_request_execute(...).
Input validation errors and API misuse are reported through two channels:
- thread-local: call
openpit_get_last_error()after a function returns a failure status - out-pointer: pass
OpenPitSharedString **out_errorwhere supported; read the message withopenpit_shared_string_view(), then release withopenpit_destroy_shared_string() - read
OpenPitPretradeRejectCodefor stable machine-readable business reject categories - use the generated C docs for ownership and lifetime rules of every returned pointer
The example above uses both channels:
report_last_error(...)for APIs that only useopenpit_get_last_error()report_out_error(...)for APIs that exposeOpenPitSharedString **out_errorprint_reject(...)andprint_reject_list(...)for business-level rejects
Recommended local flow:
With Just:
just ffi-c-api
just build
cc -xc -fsyntax-only -include bindings/c/openpit.h /dev/nullManual:
python3 scripts/generate_api_c.py
cargo build --workspace
cc -xc -fsyntax-only -include bindings/c/openpit.h /dev/nullFor full build/test command matrix (manual and just), see the repository README.