openpit is an embeddable pre-trade risk SDK for integrating policy-driven
risk checks into trading systems from Python.
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.
Until Pit reaches a stable 1.0 release, the project follows a relaxed
interpretation of Semantic Versioning.
During this phase:
PATCHreleases are used for bug fixes and small internal corrections.MINORreleases may introduce new features and may also change the public interface.
This means that breaking API changes can appear in minor releases before 1.0.
Consumers of the library should take this into account when declaring
dependencies and consider using version constraints that tolerate API
evolution during the pre‑stable phase.
Visit the PyPI package.
For normal end-user installation, use the published PyPI package:
pip install openpitIf you need local development/debugging, clone this repository and build from source with Maturin:
maturin develop --manifest-path bindings/python/Cargo.tomlLocal release build:
maturin develop --release --manifest-path bindings/python/Cargo.tomlThe engine evaluates an order through a deterministic pre-trade pipeline:
engine.start_pre_trade(order=...)runs start-stage policies and makes lightweight check policiesrequest.execute()runs main-stage check policiesreservation.commit()applies reserved statereservation.rollback()reverts reserved stateengine.apply_execution_report(report=...)updates post-trade policy state
Start-stage policies stop on the first reject. Main-stage policies aggregate rejects and run rollback mutations in reverse order when any reject is produced.
Built-in policies currently include:
OrderValidationPolicyPnlKillSwitchPolicyRateLimitPolicyOrderSizeLimitPolicy
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.
import openpit
# 1. Configure policies.
pnl_policy = openpit.pretrade.policies.PnlKillSwitchPolicy(
settlement_asset=openpit.param.Asset("USD"),
barrier=openpit.param.Pnl("1000"),
)
rate_limit_policy = openpit.pretrade.policies.RateLimitPolicy(
max_orders=100,
window_seconds=1,
)
order_size_policy = openpit.pretrade.policies.OrderSizeLimitPolicy(
limit=openpit.pretrade.policies.OrderSizeLimit(
settlement_asset=openpit.param.Asset("USD"),
max_quantity=openpit.param.Quantity("500"),
max_notional=openpit.param.Volume("100000"),
),
)
# 2. Build the engine (one time at the platform initialization).
engine = (
openpit.Engine.builder()
.check_pre_trade_start_policy(
policy=openpit.pretrade.policies.OrderValidationPolicy(),
)
.check_pre_trade_start_policy(policy=pnl_policy)
.check_pre_trade_start_policy(policy=rate_limit_policy)
.check_pre_trade_start_policy(policy=order_size_policy)
.build()
)
# 3. Check an order.
order = openpit.Order(
operation=openpit.OrderOperation(
instrument=openpit.Instrument(
openpit.param.Asset("AAPL"),
openpit.param.Asset("USD"),
),
side=openpit.param.Side.BUY,
trade_amount=openpit.param.Quantity("100"),
price=openpit.param.Price("185"),
),
)
start_result = engine.start_pre_trade(order=order)
if not start_result:
reject = start_result.reject
raise RuntimeError(
f"{reject.policy} [{reject.code}]: {reject.reason}: {reject.details}"
)
request = start_result.request
# 4. 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.
# 5. Real pre-trade and risk control.
execute_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)
reservation = execute_result.reservation
# 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.
try:
send_order_to_venue(order)
except Exception:
reservation.rollback()
raise
reservation.commit()
# 7. The order goes to the venue and returns with an execution report.
report = openpit.ExecutionReport(
operation=openpit.ExecutionReportOperation(
instrument=openpit.Instrument(
openpit.param.Asset("AAPL"),
openpit.param.Asset("USD"),
),
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)
# 8. 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 is FalsePolicy rejects from engine.start_pre_trade() and request.execute() are
returned as StartPreTradeResult and ExecuteResult.
Input validation errors and API misuse still raise exceptions:
ValueErrorfor invalid assets/sides/malformed numeric inputsRuntimeErrorfor lifecycle misuse, for example executing the same request twice or finalizing the same reservation twice- Business rejects use stable reject codes such as
openpit.pretrade.RejectCode.ORDER_VALUE_CALCULATION_FAILEDwhen a policy cannot evaluate order value withoutprice
Recommended local flow:
maturin develop --manifest-path bindings/python/Cargo.toml
python -m pytest bindings/python/testsRun only unit tests:
maturin develop --manifest-path bindings/python/Cargo.toml
python -m pytest bindings/python/tests/unitRun only integration test:
maturin develop --manifest-path bindings/python/Cargo.toml
python -m pytest bindings/python/tests/integrationFor full build/test command matrix (manual and just), see
the repository README.