#![allow(non_camel_case_types)]
#![allow(clippy::not_unsafe_ptr_arg_deref)]
#![doc = include_str!("../README.md")]
mod support;
use std::ffi::{c_char, c_void, CString};
use std::sync::{
atomic::{AtomicBool, Ordering},
Arc,
};
use of_adapters::{AdapterConfig, ProviderKind};
use of_core::{AnalyticsConfig, BookUpdate, DataQualityFlags, SignalState, SymbolId, TradePrint};
use of_execution::{
simulated_engine_with_routes, AllowAllRiskGate, ConcurrentExecutionConfig,
ConcurrentExecutionEngine, ConcurrentExecutionError, ExecutionCommand, ExecutionCommandKind,
ExecutionCommandReport, ExecutionEngine, ExecutionError, ExecutionEventBuffer, InMemoryJournal,
RouteConfig, SimExecutionAdapter,
};
use of_execution_core::{
AmendRequest, CancelRequest, ExecutionEvent, ExecutionSymbol, FixedAscii, OrderPrice, OrderQty,
OrderRequest, OrderSide, OrderState, OrderType, RiskLimits, StrategyId, TimeInForce,
VenueOrderId,
};
use of_runtime::{
build_default_engine, load_engine_config_from_path, DefaultEngine, EngineConfig,
ExternalFeedPolicy, RuntimeError,
};
#[cfg(feature = "tickbar")]
use support::format_bar_series;
use support::{
action_from_ffi, dispatch_callbacks, dispatch_health_callbacks, escape_json,
format_acd_snapshot, format_agent_type_snapshot, format_almgren_chriss_snapshot,
format_amihud_snapshot, format_analytics_snapshot, format_book_analytics_snapshot,
format_book_event_analytics_snapshot, format_book_snapshot, format_cvd_enhancement_snapshot,
format_dark_lit_correlation_snapshot, format_dark_pool_snapshot,
format_derived_analytics_snapshot, format_futures_snapshot, format_hasbrouck_snapshot,
format_institutional_flow_snapshot, format_interval_candle_snapshot,
format_kinetic_energy_snapshot, format_kyle_lambda_snapshot, format_lob_feature_snapshot,
format_noise_snapshot, format_oi_analysis_snapshot, format_options_flow_snapshot,
format_pattern_snapshot, format_regime_snapshot, format_resiliency_snapshot,
format_session_candle_snapshot, format_spread_decomp_snapshot, format_vol_signature_snapshot,
format_volatility_snapshot, format_vpin_snapshot, non_empty_string, parse_csv, side_from_ffi,
symbol_from_ffi, symbol_from_ffi_ref, write_json_to_c_buffer,
};
const API_VERSION: u32 = 0x0001_0000;
const EXECUTION_API_VERSION: u32 = 0x0001_0000;
const BUILD_INFO: &[u8] = concat!("of_ffi_c/", env!("CARGO_PKG_VERSION"), "\0").as_bytes();
const FFI_EVENT_BUFFER_CAP: usize = 32;
#[repr(C)]
#[derive(Debug, Clone, Copy)]
pub struct of_analytics_config_t {
pub agent_small_trade_threshold: f64,
pub institutional_trade_threshold: i64,
pub cancel_arrival_window_ns: u64,
pub vpin_volume_bucket: u32,
pub vpin_max_buckets: u32,
pub kyle_lambda_max_len: u32,
pub cvd_max_len: u32,
pub vol_estimator_max_len: u32,
pub noise_max_len: u32,
pub hasbrouck_max_len: u32,
pub almgren_chriss_max_len: u32,
pub acd_max_len: u32,
pub vol_signature_max_len: u32,
pub agent_max_len: u32,
pub agent_min_samples: u32,
pub institutional_max_len: u32,
pub resiliency_max_len: u32,
pub spread_decomp_max_len: u32,
pub regime_max_len: u32,
pub event_tracker_max_len: u32,
pub spread_tracker_max_len: u32,
pub default_max_len: u32,
}
impl From<of_analytics_config_t> for AnalyticsConfig {
fn from(value: of_analytics_config_t) -> Self {
Self {
vpin_volume_bucket: i64::from(value.vpin_volume_bucket),
vpin_max_buckets: value.vpin_max_buckets,
kyle_lambda_max_len: value.kyle_lambda_max_len,
cvd_max_len: value.cvd_max_len,
vol_estimator_max_len: value.vol_estimator_max_len,
noise_max_len: value.noise_max_len,
hasbrouck_max_len: value.hasbrouck_max_len,
almgren_chriss_max_len: value.almgren_chriss_max_len,
acd_max_len: value.acd_max_len,
vol_signature_max_len: value.vol_signature_max_len,
agent_max_len: value.agent_max_len,
agent_min_samples: value.agent_min_samples,
agent_small_trade_threshold: value.agent_small_trade_threshold,
institutional_trade_threshold: value.institutional_trade_threshold,
institutional_max_len: value.institutional_max_len,
resiliency_max_len: value.resiliency_max_len,
spread_decomp_max_len: value.spread_decomp_max_len,
regime_max_len: value.regime_max_len,
cancel_arrival_window_ns: value.cancel_arrival_window_ns,
event_tracker_max_len: value.event_tracker_max_len,
spread_tracker_max_len: value.spread_tracker_max_len,
default_max_len: value.default_max_len,
}
}
}
#[repr(C)]
pub struct of_engine_config_t {
pub instance_id: *const c_char,
pub config_path: *const c_char,
pub log_level: u32,
pub enable_persistence: u8,
pub audit_max_bytes: u64,
pub audit_max_files: u32,
pub audit_redact_tokens_csv: *const c_char,
pub data_retention_max_bytes: u64,
pub data_retention_max_age_secs: u64,
}
#[repr(C)]
pub struct of_symbol_t {
pub venue: *const c_char,
pub symbol: *const c_char,
pub depth_levels: u16,
}
#[repr(C)]
pub struct of_trade_t {
pub symbol: of_symbol_t,
pub price: i64,
pub size: i64,
pub aggressor_side: u32,
pub sequence: u64,
pub ts_exchange_ns: u64,
pub ts_recv_ns: u64,
}
#[repr(C)]
pub struct of_book_t {
pub symbol: of_symbol_t,
pub side: u32,
pub level: u16,
pub price: i64,
pub size: i64,
pub action: u32,
pub sequence: u64,
pub ts_exchange_ns: u64,
pub ts_recv_ns: u64,
}
#[repr(C)]
pub struct of_external_feed_policy_t {
pub stale_after_ms: u64,
pub enforce_sequence: u8,
}
#[repr(C)]
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum of_error_t {
OF_OK = 0,
OF_ERR_INVALID_ARG = 1,
OF_ERR_STATE = 2,
OF_ERR_IO = 3,
OF_ERR_AUTH = 4,
OF_ERR_BACKPRESSURE = 5,
OF_ERR_DATA_QUALITY = 6,
OF_ERR_RISK = 7,
OF_ERR_INTERNAL = 255,
}
#[repr(C)]
pub struct of_execution_route_config_t {
pub route_id: *const c_char,
pub account_id: *const c_char,
pub venue: *const c_char,
pub instrument: *const c_char,
pub enabled: u8,
pub kill_switch: u8,
pub max_order_qty: i64,
pub max_order_notional: i64,
pub max_open_orders: u32,
pub max_open_notional: i64,
pub price_band_ticks: i64,
}
#[repr(C)]
pub struct of_execution_order_request_t {
pub client_order_id: *const c_char,
pub account_id: *const c_char,
pub route_id: *const c_char,
pub strategy_id: *const c_char,
pub venue: *const c_char,
pub instrument: *const c_char,
pub side: u32,
pub order_type: u32,
pub time_in_force: u32,
pub quantity: i64,
pub limit_price: i64,
pub stop_price: i64,
pub ts_exchange_ns: u64,
pub ts_recv_ns: u64,
}
#[repr(C)]
pub struct of_execution_cancel_request_t {
pub client_order_id: *const c_char,
pub orig_client_order_id: *const c_char,
pub venue_order_id: *const c_char,
pub account_id: *const c_char,
pub route_id: *const c_char,
pub venue: *const c_char,
pub instrument: *const c_char,
pub ts_recv_ns: u64,
}
#[repr(C)]
pub struct of_execution_amend_request_t {
pub client_order_id: *const c_char,
pub orig_client_order_id: *const c_char,
pub venue_order_id: *const c_char,
pub account_id: *const c_char,
pub route_id: *const c_char,
pub venue: *const c_char,
pub instrument: *const c_char,
pub quantity: i64,
pub limit_price: i64,
pub ts_recv_ns: u64,
}
#[repr(C)]
#[derive(Clone, Copy)]
pub struct of_execution_event_t {
pub exec_type: u32,
pub order_status: u32,
pub client_order_id: [c_char; 41],
pub orig_client_order_id: [c_char; 41],
pub venue_order_id: [c_char; 49],
pub execution_id: [c_char; 49],
pub account_id: [c_char; 33],
pub route_id: [c_char; 33],
pub venue: [c_char; 17],
pub instrument: [c_char; 33],
pub last_qty: i64,
pub last_price: i64,
pub cumulative_qty: i64,
pub leaves_qty: i64,
pub average_price: i64,
pub ts_exchange_ns: u64,
pub ts_recv_ns: u64,
pub reason: u32,
pub text: [c_char; 129],
}
#[repr(C)]
#[derive(Clone, Copy)]
pub struct of_execution_order_state_t {
pub client_order_id: [c_char; 41],
pub venue_order_id: [c_char; 49],
pub account_id: [c_char; 33],
pub route_id: [c_char; 33],
pub venue: [c_char; 17],
pub instrument: [c_char; 33],
pub status: u32,
pub order_qty: i64,
pub cumulative_qty: i64,
pub leaves_qty: i64,
pub average_price: i64,
pub updated_ns: u64,
}
#[repr(C)]
#[derive(Clone, Copy)]
pub struct of_execution_health_t {
pub connected: u8,
pub degraded: u8,
pub health_seq: u64,
}
#[repr(C)]
#[derive(Clone, Copy)]
pub struct of_execution_metrics_t {
pub submitted: u64,
pub cancelled: u64,
pub amended: u64,
pub events_applied: u64,
pub risk_rejected: u64,
pub adapter_errors: u64,
pub recovered: u64,
}
#[repr(C)]
#[derive(Clone, Copy)]
pub struct of_execution_concurrent_config_t {
pub command_capacity: u32,
pub report_capacity: u32,
pub event_buffer_capacity: u32,
}
#[repr(C)]
#[derive(Clone, Copy)]
pub struct of_execution_command_report_t {
pub sequence: u64,
pub kind: u32,
pub result_code: i32,
pub event_count: u32,
}
pub struct of_engine {
inner: DefaultEngine,
subs: Vec<SubscriptionRecord>,
}
pub struct of_execution_engine {
inner: ExecutionEngine<SimExecutionAdapter, AllowAllRiskGate, InMemoryJournal>,
}
pub struct of_execution_concurrent_engine {
inner: ConcurrentExecutionEngine,
}
pub struct of_subscription {
token: *mut SubscriptionToken,
}
#[repr(C)]
pub struct of_event_t {
pub ts_exchange_ns: u64,
pub ts_recv_ns: u64,
pub kind: u32,
pub payload: *const c_void,
pub payload_len: u32,
pub schema_id: u32,
pub quality_flags: u32,
}
pub type of_event_cb = extern "C" fn(*const of_event_t, *mut c_void);
struct SubscriptionRecord {
symbol: SymbolId,
kind: u32,
cb: of_event_cb,
user_data: *mut c_void,
active: Arc<AtomicBool>,
last_health_seq: u64,
}
struct SubscriptionToken {
active: Arc<AtomicBool>,
}
#[no_mangle]
pub extern "C" fn of_api_version() -> u32 {
API_VERSION
}
#[no_mangle]
pub extern "C" fn of_build_info() -> *const c_char {
BUILD_INFO.as_ptr() as *const c_char
}
#[no_mangle]
pub extern "C" fn of_execution_api_version() -> u32 {
EXECUTION_API_VERSION
}
#[no_mangle]
pub extern "C" fn of_execution_engine_create(
cfg: *const of_execution_route_config_t,
out_engine: *mut *mut of_execution_engine,
) -> i32 {
if cfg.is_null() || out_engine.is_null() {
return of_error_t::OF_ERR_INVALID_ARG as i32;
}
let cfg = unsafe { &*cfg };
let route = match route_config_from_ffi(cfg) {
Ok(route) => route,
Err(_) => return of_error_t::OF_ERR_INVALID_ARG as i32,
};
create_execution_engine_from_routes(vec![route], out_engine)
}
#[no_mangle]
pub extern "C" fn of_execution_engine_create_multi(
routes: *const of_execution_route_config_t,
route_count: u32,
out_engine: *mut *mut of_execution_engine,
) -> i32 {
if routes.is_null() || route_count == 0 || out_engine.is_null() {
return of_error_t::OF_ERR_INVALID_ARG as i32;
}
let route_configs = match route_configs_from_ffi(routes, route_count) {
Ok(routes) => routes,
Err(_) => return of_error_t::OF_ERR_INVALID_ARG as i32,
};
create_execution_engine_from_routes(route_configs, out_engine)
}
fn create_execution_engine_from_routes(
routes: Vec<RouteConfig>,
out_engine: *mut *mut of_execution_engine,
) -> i32 {
let engine = Box::new(of_execution_engine {
inner: simulated_engine_with_routes(routes),
});
unsafe {
*out_engine = Box::into_raw(engine);
}
of_error_t::OF_OK as i32
}
#[no_mangle]
pub extern "C" fn of_execution_concurrent_engine_create_multi(
routes: *const of_execution_route_config_t,
route_count: u32,
config: *const of_execution_concurrent_config_t,
out_engine: *mut *mut of_execution_concurrent_engine,
) -> i32 {
if routes.is_null() || route_count == 0 || out_engine.is_null() {
return of_error_t::OF_ERR_INVALID_ARG as i32;
}
let route_configs = match route_configs_from_ffi(routes, route_count) {
Ok(routes) => routes,
Err(_) => return of_error_t::OF_ERR_INVALID_ARG as i32,
};
let cfg = concurrent_config_from_ffi(config);
let engine = simulated_engine_with_routes(route_configs);
let inner = match ConcurrentExecutionEngine::spawn(engine, cfg) {
Ok(engine) => engine,
Err(err) => return map_concurrent_execution_error(&err),
};
let wrapped = Box::new(of_execution_concurrent_engine { inner });
unsafe {
*out_engine = Box::into_raw(wrapped);
}
of_error_t::OF_OK as i32
}
#[no_mangle]
pub extern "C" fn of_execution_concurrent_engine_destroy(
engine: *mut of_execution_concurrent_engine,
) {
if engine.is_null() {
return;
}
unsafe {
let _ = Box::from_raw(engine);
}
}
#[no_mangle]
pub extern "C" fn of_execution_concurrent_stop(
engine: *mut of_execution_concurrent_engine,
out_sequence: *mut u64,
) -> i32 {
if engine.is_null() {
return of_error_t::OF_ERR_INVALID_ARG as i32;
}
let engine = unsafe { &mut *engine };
match engine.inner.request_stop() {
Ok(sequence) => {
write_optional_u64(out_sequence, sequence);
of_error_t::OF_OK as i32
}
Err(err) => map_concurrent_execution_error(&err),
}
}
#[no_mangle]
pub extern "C" fn of_execution_concurrent_submit_order(
engine: *mut of_execution_concurrent_engine,
req: *const of_execution_order_request_t,
out_sequence: *mut u64,
) -> i32 {
if engine.is_null() || req.is_null() {
return of_error_t::OF_ERR_INVALID_ARG as i32;
}
let req = match order_request_from_ffi(unsafe { &*req }) {
Ok(req) => req,
Err(_) => return of_error_t::OF_ERR_INVALID_ARG as i32,
};
let engine = unsafe { &mut *engine };
send_concurrent_command(engine, ExecutionCommand::Submit(req), out_sequence)
}
#[no_mangle]
pub extern "C" fn of_execution_concurrent_cancel_order(
engine: *mut of_execution_concurrent_engine,
req: *const of_execution_cancel_request_t,
out_sequence: *mut u64,
) -> i32 {
if engine.is_null() || req.is_null() {
return of_error_t::OF_ERR_INVALID_ARG as i32;
}
let req = match cancel_request_from_ffi(unsafe { &*req }) {
Ok(req) => req,
Err(_) => return of_error_t::OF_ERR_INVALID_ARG as i32,
};
let engine = unsafe { &mut *engine };
send_concurrent_command(engine, ExecutionCommand::Cancel(req), out_sequence)
}
#[no_mangle]
pub extern "C" fn of_execution_concurrent_amend_order(
engine: *mut of_execution_concurrent_engine,
req: *const of_execution_amend_request_t,
out_sequence: *mut u64,
) -> i32 {
if engine.is_null() || req.is_null() {
return of_error_t::OF_ERR_INVALID_ARG as i32;
}
let req = match amend_request_from_ffi(unsafe { &*req }) {
Ok(req) => req,
Err(_) => return of_error_t::OF_ERR_INVALID_ARG as i32,
};
let engine = unsafe { &mut *engine };
send_concurrent_command(engine, ExecutionCommand::Amend(req), out_sequence)
}
#[no_mangle]
pub extern "C" fn of_execution_concurrent_poll(
engine: *mut of_execution_concurrent_engine,
out_sequence: *mut u64,
) -> i32 {
if engine.is_null() {
return of_error_t::OF_ERR_INVALID_ARG as i32;
}
let engine = unsafe { &mut *engine };
send_concurrent_command(engine, ExecutionCommand::Poll, out_sequence)
}
#[no_mangle]
pub extern "C" fn of_execution_concurrent_try_recv_report(
engine: *mut of_execution_concurrent_engine,
out_report: *mut of_execution_command_report_t,
out_events: *mut of_execution_event_t,
inout_len: *mut u32,
) -> i32 {
if engine.is_null() || out_report.is_null() || inout_len.is_null() {
return of_error_t::OF_ERR_INVALID_ARG as i32;
}
let engine = unsafe { &mut *engine };
let report = match engine.inner.try_recv_report() {
Ok(report) => report,
Err(err) => return map_concurrent_execution_error(&err),
};
write_concurrent_report(&report, out_report, out_events, inout_len)
}
#[no_mangle]
pub extern "C" fn of_execution_engine_start(engine: *mut of_execution_engine) -> i32 {
if engine.is_null() {
return of_error_t::OF_ERR_INVALID_ARG as i32;
}
let engine = unsafe { &mut *engine };
map_execution_result(engine.inner.start())
}
#[no_mangle]
pub extern "C" fn of_execution_engine_stop(engine: *mut of_execution_engine) -> i32 {
if engine.is_null() {
return of_error_t::OF_ERR_INVALID_ARG as i32;
}
of_error_t::OF_OK as i32
}
#[no_mangle]
pub extern "C" fn of_execution_engine_destroy(engine: *mut of_execution_engine) {
if engine.is_null() {
return;
}
unsafe {
let _ = Box::from_raw(engine);
}
}
#[no_mangle]
pub extern "C" fn of_execution_submit_order(
engine: *mut of_execution_engine,
req: *const of_execution_order_request_t,
out_events: *mut of_execution_event_t,
inout_len: *mut u32,
) -> i32 {
if engine.is_null() || req.is_null() || inout_len.is_null() {
return of_error_t::OF_ERR_INVALID_ARG as i32;
}
let req = match order_request_from_ffi(unsafe { &*req }) {
Ok(req) => req,
Err(_) => return of_error_t::OF_ERR_INVALID_ARG as i32,
};
let engine = unsafe { &mut *engine };
let mut events = ExecutionEventBuffer::with_capacity(FFI_EVENT_BUFFER_CAP);
let rc = match engine.inner.submit(req, &mut events) {
Ok(()) => of_error_t::OF_OK as i32,
Err(err) => map_execution_error(&err),
};
let copy_rc = copy_execution_events(&events, out_events, inout_len);
if copy_rc != of_error_t::OF_OK as i32 {
copy_rc
} else {
rc
}
}
#[no_mangle]
pub extern "C" fn of_execution_cancel_order(
engine: *mut of_execution_engine,
req: *const of_execution_cancel_request_t,
out_events: *mut of_execution_event_t,
inout_len: *mut u32,
) -> i32 {
if engine.is_null() || req.is_null() || inout_len.is_null() {
return of_error_t::OF_ERR_INVALID_ARG as i32;
}
let req = match cancel_request_from_ffi(unsafe { &*req }) {
Ok(req) => req,
Err(_) => return of_error_t::OF_ERR_INVALID_ARG as i32,
};
let engine = unsafe { &mut *engine };
let mut events = ExecutionEventBuffer::with_capacity(FFI_EVENT_BUFFER_CAP);
let rc = match engine.inner.cancel(req, &mut events) {
Ok(()) => of_error_t::OF_OK as i32,
Err(err) => map_execution_error(&err),
};
let copy_rc = copy_execution_events(&events, out_events, inout_len);
if copy_rc != of_error_t::OF_OK as i32 {
copy_rc
} else {
rc
}
}
#[no_mangle]
pub extern "C" fn of_execution_amend_order(
engine: *mut of_execution_engine,
req: *const of_execution_amend_request_t,
out_events: *mut of_execution_event_t,
inout_len: *mut u32,
) -> i32 {
if engine.is_null() || req.is_null() || inout_len.is_null() {
return of_error_t::OF_ERR_INVALID_ARG as i32;
}
let req = match amend_request_from_ffi(unsafe { &*req }) {
Ok(req) => req,
Err(_) => return of_error_t::OF_ERR_INVALID_ARG as i32,
};
let engine = unsafe { &mut *engine };
let mut events = ExecutionEventBuffer::with_capacity(FFI_EVENT_BUFFER_CAP);
let rc = match engine.inner.amend(req, &mut events) {
Ok(()) => of_error_t::OF_OK as i32,
Err(err) => map_execution_error(&err),
};
let copy_rc = copy_execution_events(&events, out_events, inout_len);
if copy_rc != of_error_t::OF_OK as i32 {
copy_rc
} else {
rc
}
}
#[no_mangle]
pub extern "C" fn of_execution_poll(
engine: *mut of_execution_engine,
out_events: *mut of_execution_event_t,
inout_len: *mut u32,
) -> i32 {
if engine.is_null() || inout_len.is_null() {
return of_error_t::OF_ERR_INVALID_ARG as i32;
}
let engine = unsafe { &mut *engine };
let mut events = ExecutionEventBuffer::with_capacity(FFI_EVENT_BUFFER_CAP);
let rc = match engine.inner.poll(&mut events) {
Ok(_) => of_error_t::OF_OK as i32,
Err(err) => map_execution_error(&err),
};
let copy_rc = copy_execution_events(&events, out_events, inout_len);
if copy_rc != of_error_t::OF_OK as i32 {
copy_rc
} else {
rc
}
}
#[no_mangle]
pub extern "C" fn of_execution_get_order_state(
engine: *const of_execution_engine,
client_order_id: *const c_char,
out_state: *mut of_execution_order_state_t,
) -> i32 {
if engine.is_null() || client_order_id.is_null() || out_state.is_null() {
return of_error_t::OF_ERR_INVALID_ARG as i32;
}
let id = match fixed_from_ptr::<40>(client_order_id) {
Ok(id) => id,
Err(_) => return of_error_t::OF_ERR_INVALID_ARG as i32,
};
let engine = unsafe { &*engine };
let Some(state) = engine.inner.order_state(&id) else {
return of_error_t::OF_ERR_STATE as i32;
};
unsafe {
*out_state = order_state_to_ffi(&state);
}
of_error_t::OF_OK as i32
}
#[no_mangle]
pub extern "C" fn of_execution_health(
engine: *const of_execution_engine,
out_health: *mut of_execution_health_t,
) -> i32 {
if engine.is_null() || out_health.is_null() {
return of_error_t::OF_ERR_INVALID_ARG as i32;
}
let health = unsafe { &*engine }.inner.health();
unsafe {
*out_health = of_execution_health_t {
connected: u8::from(health.connected),
degraded: u8::from(health.degraded),
health_seq: health.health_seq,
};
}
of_error_t::OF_OK as i32
}
#[no_mangle]
pub extern "C" fn of_execution_metrics(
engine: *const of_execution_engine,
out_metrics: *mut of_execution_metrics_t,
) -> i32 {
if engine.is_null() || out_metrics.is_null() {
return of_error_t::OF_ERR_INVALID_ARG as i32;
}
let metrics = unsafe { &*engine }.inner.metrics();
unsafe {
*out_metrics = of_execution_metrics_t {
submitted: metrics.submitted,
cancelled: metrics.cancelled,
amended: metrics.amended,
events_applied: metrics.events_applied,
risk_rejected: metrics.risk_rejected,
adapter_errors: metrics.adapter_errors,
recovered: metrics.recovered,
};
}
of_error_t::OF_OK as i32
}
#[no_mangle]
pub extern "C" fn of_engine_create(
cfg: *const of_engine_config_t,
out_engine: *mut *mut of_engine,
) -> i32 {
if cfg.is_null() || out_engine.is_null() {
return of_error_t::OF_ERR_INVALID_ARG as i32;
}
let cfg_ref = unsafe { &*cfg };
let mut runtime_cfg = if let Some(path) = non_empty_string(cfg_ref.config_path) {
match load_engine_config_from_path(&path) {
Ok(v) => v,
Err(_) => return of_error_t::OF_ERR_INVALID_ARG as i32,
}
} else {
EngineConfig {
instance_id: "default".to_string(),
enable_persistence: false,
data_root: "data".to_string(),
audit_log_path: "audit/orderflow_audit.log".to_string(),
audit_max_bytes: 10 * 1024 * 1024,
audit_max_files: 5,
audit_redact_tokens: vec![
"secret".to_string(),
"password".to_string(),
"token".to_string(),
"api_key".to_string(),
],
data_retention_max_bytes: 10 * 1024 * 1024,
data_retention_max_age_secs: 7 * 24 * 60 * 60,
adapter: AdapterConfig {
provider: ProviderKind::Mock,
..AdapterConfig::default()
},
signal_threshold: 100,
}
};
if let Some(instance_id) = non_empty_string(cfg_ref.instance_id) {
runtime_cfg.instance_id = instance_id;
}
runtime_cfg.enable_persistence = cfg_ref.enable_persistence != 0;
if cfg_ref.audit_max_bytes > 0 {
runtime_cfg.audit_max_bytes = cfg_ref.audit_max_bytes;
}
if cfg_ref.audit_max_files > 0 {
runtime_cfg.audit_max_files = cfg_ref.audit_max_files;
}
if let Some(tokens) = parse_csv(cfg_ref.audit_redact_tokens_csv) {
runtime_cfg.audit_redact_tokens = tokens;
}
if cfg_ref.data_retention_max_bytes > 0 {
runtime_cfg.data_retention_max_bytes = cfg_ref.data_retention_max_bytes;
}
if cfg_ref.data_retention_max_age_secs > 0 {
runtime_cfg.data_retention_max_age_secs = cfg_ref.data_retention_max_age_secs;
}
let engine = match build_default_engine(runtime_cfg) {
Ok(v) => v,
Err(_) => return of_error_t::OF_ERR_STATE as i32,
};
let wrapped = Box::new(of_engine {
inner: engine,
subs: Vec::new(),
});
unsafe {
*out_engine = Box::into_raw(wrapped);
}
of_error_t::OF_OK as i32
}
#[no_mangle]
pub extern "C" fn of_engine_start(engine: *mut of_engine) -> i32 {
if engine.is_null() {
return of_error_t::OF_ERR_INVALID_ARG as i32;
}
let engine = unsafe { &mut *engine };
match engine.inner.start() {
Ok(_) => of_error_t::OF_OK as i32,
Err(_) => of_error_t::OF_ERR_STATE as i32,
}
}
#[no_mangle]
pub extern "C" fn of_engine_stop(engine: *mut of_engine) -> i32 {
if engine.is_null() {
return of_error_t::OF_ERR_INVALID_ARG as i32;
}
let engine = unsafe { &mut *engine };
engine.inner.stop();
of_error_t::OF_OK as i32
}
#[no_mangle]
pub extern "C" fn of_engine_destroy(engine: *mut of_engine) {
if !engine.is_null() {
unsafe {
drop(Box::from_raw(engine));
}
}
}
#[no_mangle]
pub extern "C" fn of_subscribe(
engine: *mut of_engine,
symbol: *const of_symbol_t,
_kind: u32,
cb: Option<of_event_cb>,
user_data: *mut c_void,
out_sub: *mut *mut of_subscription,
) -> i32 {
if engine.is_null() || symbol.is_null() || out_sub.is_null() {
return of_error_t::OF_ERR_INVALID_ARG as i32;
}
let (symbol, depth_levels) = match symbol_from_ffi(symbol) {
Ok(v) => v,
Err(e) => return e as i32,
};
let engine = unsafe { &mut *engine };
if engine
.inner
.subscribe(symbol.clone(), depth_levels)
.is_err()
{
return of_error_t::OF_ERR_STATE as i32;
}
let active = Arc::new(AtomicBool::new(true));
if let Some(cb_fn) = cb {
engine.subs.push(SubscriptionRecord {
symbol: symbol.clone(),
kind: _kind,
cb: cb_fn,
user_data,
active: active.clone(),
last_health_seq: 0,
});
}
let token = Box::new(SubscriptionToken { active });
let sub = Box::new(of_subscription {
token: Box::into_raw(token),
});
unsafe {
*out_sub = Box::into_raw(sub);
}
of_error_t::OF_OK as i32
}
#[no_mangle]
pub extern "C" fn of_unsubscribe(sub: *mut of_subscription) -> i32 {
if sub.is_null() {
return of_error_t::OF_ERR_INVALID_ARG as i32;
}
unsafe {
let sub = Box::from_raw(sub);
if !sub.token.is_null() {
let token = Box::from_raw(sub.token);
token.active.store(false, Ordering::Release);
}
}
of_error_t::OF_OK as i32
}
#[no_mangle]
pub extern "C" fn of_unsubscribe_symbol(engine: *mut of_engine, symbol: *const of_symbol_t) -> i32 {
if engine.is_null() || symbol.is_null() {
return of_error_t::OF_ERR_INVALID_ARG as i32;
}
let (symbol, _) = match symbol_from_ffi(symbol) {
Ok(v) => v,
Err(e) => return e as i32,
};
let engine = unsafe { &mut *engine };
if engine.inner.unsubscribe(symbol.clone()).is_err() {
return of_error_t::OF_ERR_STATE as i32;
}
for sub in &mut engine.subs {
if sub.symbol == symbol {
sub.active.store(false, Ordering::Release);
}
}
engine.subs.retain(|s| s.active.load(Ordering::Acquire));
of_error_t::OF_OK as i32
}
#[no_mangle]
pub extern "C" fn of_reset_symbol_session(
engine: *mut of_engine,
symbol: *const of_symbol_t,
) -> i32 {
if engine.is_null() || symbol.is_null() {
return of_error_t::OF_ERR_INVALID_ARG as i32;
}
let (symbol, _) = match symbol_from_ffi(symbol) {
Ok(v) => v,
Err(e) => return e as i32,
};
let engine = unsafe { &mut *engine };
if engine.inner.reset_symbol_session(symbol).is_err() {
return of_error_t::OF_ERR_STATE as i32;
}
of_error_t::OF_OK as i32
}
#[no_mangle]
pub extern "C" fn of_ingest_trade(
engine: *mut of_engine,
trade: *const of_trade_t,
quality_flags: u32,
) -> i32 {
if engine.is_null() || trade.is_null() {
return of_error_t::OF_ERR_INVALID_ARG as i32;
}
let trade = unsafe { &*trade };
let (symbol, _) = match symbol_from_ffi_ref(&trade.symbol) {
Ok(v) => v,
Err(e) => return e as i32,
};
let aggressor_side = match side_from_ffi(trade.aggressor_side) {
Ok(v) => v,
Err(e) => return e as i32,
};
let q = DataQualityFlags::from_bits_truncate(quality_flags);
let event = TradePrint {
symbol,
price: trade.price,
size: trade.size,
aggressor_side,
sequence: trade.sequence,
ts_exchange_ns: trade.ts_exchange_ns,
ts_recv_ns: trade.ts_recv_ns,
};
let engine = unsafe { &mut *engine };
match engine.inner.ingest_trade(event, q) {
Ok(_) => {
dispatch_callbacks(engine, engine.inner.current_quality_flags_bits());
of_error_t::OF_OK as i32
}
Err(_) => of_error_t::OF_ERR_STATE as i32,
}
}
#[no_mangle]
pub extern "C" fn of_ingest_book(
engine: *mut of_engine,
book: *const of_book_t,
quality_flags: u32,
) -> i32 {
if engine.is_null() || book.is_null() {
return of_error_t::OF_ERR_INVALID_ARG as i32;
}
let book = unsafe { &*book };
let (symbol, _) = match symbol_from_ffi_ref(&book.symbol) {
Ok(v) => v,
Err(e) => return e as i32,
};
let side = match side_from_ffi(book.side) {
Ok(v) => v,
Err(e) => return e as i32,
};
let action = match action_from_ffi(book.action) {
Ok(v) => v,
Err(e) => return e as i32,
};
let q = DataQualityFlags::from_bits_truncate(quality_flags);
let event = BookUpdate {
symbol,
side,
level: book.level,
price: book.price,
size: book.size,
action,
sequence: book.sequence,
ts_exchange_ns: book.ts_exchange_ns,
ts_recv_ns: book.ts_recv_ns,
};
let engine = unsafe { &mut *engine };
match engine.inner.ingest_book(event, q) {
Ok(_) => {
dispatch_callbacks(engine, engine.inner.current_quality_flags_bits());
of_error_t::OF_OK as i32
}
Err(_) => of_error_t::OF_ERR_STATE as i32,
}
}
#[no_mangle]
pub extern "C" fn of_configure_external_feed(
engine: *mut of_engine,
policy: *const of_external_feed_policy_t,
) -> i32 {
if engine.is_null() || policy.is_null() {
return of_error_t::OF_ERR_INVALID_ARG as i32;
}
let engine = unsafe { &mut *engine };
let policy = unsafe { &*policy };
match engine.inner.configure_external_feed(ExternalFeedPolicy {
stale_after_ms: policy.stale_after_ms,
enforce_sequence: policy.enforce_sequence != 0,
}) {
Ok(_) => {
dispatch_health_callbacks(engine, engine.inner.current_quality_flags_bits());
of_error_t::OF_OK as i32
}
Err(_) => of_error_t::OF_ERR_STATE as i32,
}
}
#[no_mangle]
pub extern "C" fn of_external_set_reconnecting(engine: *mut of_engine, reconnecting: u8) -> i32 {
if engine.is_null() {
return of_error_t::OF_ERR_INVALID_ARG as i32;
}
let engine = unsafe { &mut *engine };
match engine.inner.set_external_reconnecting(reconnecting != 0) {
Ok(_) => {
dispatch_health_callbacks(engine, engine.inner.current_quality_flags_bits());
of_error_t::OF_OK as i32
}
Err(_) => of_error_t::OF_ERR_STATE as i32,
}
}
#[no_mangle]
pub extern "C" fn of_external_health_tick(engine: *mut of_engine) -> i32 {
if engine.is_null() {
return of_error_t::OF_ERR_INVALID_ARG as i32;
}
let engine = unsafe { &mut *engine };
match engine.inner.external_health_tick() {
Ok(_) => {
dispatch_health_callbacks(engine, engine.inner.current_quality_flags_bits());
of_error_t::OF_OK as i32
}
Err(_) => of_error_t::OF_ERR_STATE as i32,
}
}
#[no_mangle]
pub extern "C" fn of_get_book_snapshot(
engine: *mut of_engine,
symbol: *const of_symbol_t,
out_buf: *mut c_void,
inout_len: *mut u32,
) -> i32 {
if engine.is_null() {
return of_error_t::OF_ERR_INVALID_ARG as i32;
}
let (symbol, _) = match symbol_from_ffi(symbol) {
Ok(v) => v,
Err(e) => return e as i32,
};
let engine = unsafe { &mut *engine };
let payload = match engine.inner.book_snapshot(&symbol) {
Some(snapshot) => format_book_snapshot(&snapshot),
None => "{}".to_string(),
};
match write_json_to_c_buffer(&payload, out_buf, inout_len) {
Ok(_) => of_error_t::OF_OK as i32,
Err(e) => e as i32,
}
}
#[no_mangle]
pub extern "C" fn of_get_book_analytics_snapshot(
engine: *mut of_engine,
symbol: *const of_symbol_t,
out_buf: *mut c_void,
inout_len: *mut u32,
) -> i32 {
if engine.is_null() {
return of_error_t::OF_ERR_INVALID_ARG as i32;
}
let (symbol, _) = match symbol_from_ffi(symbol) {
Ok(v) => v,
Err(e) => return e as i32,
};
let engine = unsafe { &mut *engine };
let payload = match engine.inner.book_analytics_snapshot(&symbol) {
Some(snap) => format_book_analytics_snapshot(&snap),
None => "{}".to_string(),
};
match write_json_to_c_buffer(&payload, out_buf, inout_len) {
Ok(_) => of_error_t::OF_OK as i32,
Err(e) => e as i32,
}
}
#[no_mangle]
pub extern "C" fn of_compute_weighted_average_price(
engine: *mut of_engine,
symbol: *const of_symbol_t,
qty: i64,
out_buf: *mut c_void,
inout_len: *mut u32,
) -> i32 {
if engine.is_null() {
return of_error_t::OF_ERR_INVALID_ARG as i32;
}
let (symbol, _) = match symbol_from_ffi(symbol) {
Ok(v) => v,
Err(e) => return e as i32,
};
let engine = unsafe { &mut *engine };
let payload = match engine.inner.weighted_average_price(&symbol, qty) {
Some(price) => format!("{{\"price\":{}}}", price),
None => "{}".to_string(),
};
match write_json_to_c_buffer(&payload, out_buf, inout_len) {
Ok(_) => of_error_t::OF_OK as i32,
Err(e) => e as i32,
}
}
#[no_mangle]
pub extern "C" fn of_compute_depth_slope(
engine: *mut of_engine,
symbol: *const of_symbol_t,
levels: u32,
out_buf: *mut c_void,
inout_len: *mut u32,
) -> i32 {
if engine.is_null() {
return of_error_t::OF_ERR_INVALID_ARG as i32;
}
let (symbol, _) = match symbol_from_ffi(symbol) {
Ok(v) => v,
Err(e) => return e as i32,
};
let engine = unsafe { &mut *engine };
let slope = engine.inner.depth_slope(&symbol, levels as usize);
let payload = format!("{{\"slope\":{:.4}}}", slope);
match write_json_to_c_buffer(&payload, out_buf, inout_len) {
Ok(_) => of_error_t::OF_OK as i32,
Err(e) => e as i32,
}
}
#[no_mangle]
pub extern "C" fn of_get_mid_price(
engine: *mut of_engine,
symbol: *const of_symbol_t,
out_buf: *mut c_void,
inout_len: *mut u32,
) -> i32 {
if engine.is_null() {
return of_error_t::OF_ERR_INVALID_ARG as i32;
}
let (symbol, _) = match symbol_from_ffi(symbol) {
Ok(v) => v,
Err(e) => return e as i32,
};
let engine = unsafe { &mut *engine };
let payload = match engine.inner.mid_price(&symbol) {
Some(mid) => format!("{{\"mid\":{}}}", mid),
None => "{}".to_string(),
};
match write_json_to_c_buffer(&payload, out_buf, inout_len) {
Ok(_) => of_error_t::OF_OK as i32,
Err(e) => e as i32,
}
}
#[no_mangle]
pub extern "C" fn of_get_effective_spread_bps(
engine: *mut of_engine,
symbol: *const of_symbol_t,
out_buf: *mut c_void,
inout_len: *mut u32,
) -> i32 {
if engine.is_null() {
return of_error_t::OF_ERR_INVALID_ARG as i32;
}
let (symbol, _) = match symbol_from_ffi(symbol) {
Ok(v) => v,
Err(e) => return e as i32,
};
let engine = unsafe { &mut *engine };
let bps = engine.inner.effective_spread_bps(&symbol);
let payload = format!("{{\"bps\":{}}}", bps);
match write_json_to_c_buffer(&payload, out_buf, inout_len) {
Ok(_) => of_error_t::OF_OK as i32,
Err(e) => e as i32,
}
}
#[no_mangle]
pub extern "C" fn of_get_half_spread_cost_bps(
engine: *mut of_engine,
symbol: *const of_symbol_t,
window: u32,
out_buf: *mut c_void,
inout_len: *mut u32,
) -> i32 {
if engine.is_null() {
return of_error_t::OF_ERR_INVALID_ARG as i32;
}
let (symbol, _) = match symbol_from_ffi(symbol) {
Ok(v) => v,
Err(e) => return e as i32,
};
let engine = unsafe { &mut *engine };
let bps = engine.inner.half_spread_cost_bps(&symbol, window as usize);
let payload = format!("{{\"bps\":{}}}", bps);
match write_json_to_c_buffer(&payload, out_buf, inout_len) {
Ok(_) => of_error_t::OF_OK as i32,
Err(e) => e as i32,
}
}
#[no_mangle]
pub extern "C" fn of_get_realised_spread_bps(
engine: *mut of_engine,
symbol: *const of_symbol_t,
hold_ticks: u32,
out_buf: *mut c_void,
inout_len: *mut u32,
) -> i32 {
if engine.is_null() {
return of_error_t::OF_ERR_INVALID_ARG as i32;
}
let (symbol, _) = match symbol_from_ffi(symbol) {
Ok(v) => v,
Err(e) => return e as i32,
};
let engine = unsafe { &mut *engine };
let bps = engine
.inner
.realised_spread_bps(&symbol, hold_ticks as usize);
let payload = format!("{{\"bps\":{}}}", bps);
match write_json_to_c_buffer(&payload, out_buf, inout_len) {
Ok(_) => of_error_t::OF_OK as i32,
Err(e) => e as i32,
}
}
#[no_mangle]
pub extern "C" fn of_get_book_event_analytics(
engine: *mut of_engine,
symbol: *const of_symbol_t,
window_ns: u64,
out_buf: *mut c_void,
inout_len: *mut u32,
) -> i32 {
if engine.is_null() {
return of_error_t::OF_ERR_INVALID_ARG as i32;
}
let (symbol, _) = match symbol_from_ffi(symbol) {
Ok(v) => v,
Err(e) => return e as i32,
};
let engine = unsafe { &mut *engine };
let snap = engine.inner.book_event_analytics(&symbol, window_ns);
let payload = format_book_event_analytics_snapshot(&snap);
match write_json_to_c_buffer(&payload, out_buf, inout_len) {
Ok(_) => of_error_t::OF_OK as i32,
Err(e) => e as i32,
}
}
#[no_mangle]
pub extern "C" fn of_get_resiliency_snapshot(
engine: *mut of_engine,
symbol: *const of_symbol_t,
out_buf: *mut c_void,
inout_len: *mut u32,
) -> i32 {
if engine.is_null() {
return of_error_t::OF_ERR_INVALID_ARG as i32;
}
let (symbol, _) = match symbol_from_ffi(symbol) {
Ok(v) => v,
Err(e) => return e as i32,
};
let engine = unsafe { &mut *engine };
let snap = engine.inner.resiliency_snapshot(&symbol);
let payload = format_resiliency_snapshot(&snap);
match write_json_to_c_buffer(&payload, out_buf, inout_len) {
Ok(_) => of_error_t::OF_OK as i32,
Err(e) => e as i32,
}
}
#[no_mangle]
pub extern "C" fn of_get_vpin_snapshot(
engine: *mut of_engine,
symbol: *const of_symbol_t,
out_buf: *mut c_void,
inout_len: *mut u32,
) -> i32 {
if engine.is_null() {
return of_error_t::OF_ERR_INVALID_ARG as i32;
}
let (symbol, _) = match symbol_from_ffi(symbol) {
Ok(v) => v,
Err(e) => return e as i32,
};
let engine = unsafe { &mut *engine };
let payload = format_vpin_snapshot(&engine.inner.vpin_snapshot(&symbol));
match write_json_to_c_buffer(&payload, out_buf, inout_len) {
Ok(_) => of_error_t::OF_OK as i32,
Err(e) => e as i32,
}
}
#[no_mangle]
pub extern "C" fn of_get_kyle_lambda_snapshot(
engine: *mut of_engine,
symbol: *const of_symbol_t,
out_buf: *mut c_void,
inout_len: *mut u32,
) -> i32 {
if engine.is_null() {
return of_error_t::OF_ERR_INVALID_ARG as i32;
}
let (symbol, _) = match symbol_from_ffi(symbol) {
Ok(v) => v,
Err(e) => return e as i32,
};
let engine = unsafe { &mut *engine };
let payload = format_kyle_lambda_snapshot(&engine.inner.kyle_lambda_snapshot(&symbol));
match write_json_to_c_buffer(&payload, out_buf, inout_len) {
Ok(_) => of_error_t::OF_OK as i32,
Err(e) => e as i32,
}
}
#[no_mangle]
pub extern "C" fn of_get_amihud_snapshot(
engine: *mut of_engine,
symbol: *const of_symbol_t,
out_buf: *mut c_void,
inout_len: *mut u32,
) -> i32 {
if engine.is_null() {
return of_error_t::OF_ERR_INVALID_ARG as i32;
}
let (symbol, _) = match symbol_from_ffi(symbol) {
Ok(v) => v,
Err(e) => return e as i32,
};
let engine = unsafe { &mut *engine };
let payload = format_amihud_snapshot(&engine.inner.amihud_snapshot(&symbol));
match write_json_to_c_buffer(&payload, out_buf, inout_len) {
Ok(_) => of_error_t::OF_OK as i32,
Err(e) => e as i32,
}
}
#[no_mangle]
pub extern "C" fn of_get_cvd_enhancement_snapshot(
engine: *mut of_engine,
symbol: *const of_symbol_t,
out_buf: *mut c_void,
inout_len: *mut u32,
) -> i32 {
if engine.is_null() {
return of_error_t::OF_ERR_INVALID_ARG as i32;
}
let (symbol, _) = match symbol_from_ffi(symbol) {
Ok(v) => v,
Err(e) => return e as i32,
};
let engine = unsafe { &mut *engine };
let payload = format_cvd_enhancement_snapshot(&engine.inner.cvd_enhancement_snapshot(&symbol));
match write_json_to_c_buffer(&payload, out_buf, inout_len) {
Ok(_) => of_error_t::OF_OK as i32,
Err(e) => e as i32,
}
}
#[no_mangle]
pub extern "C" fn of_get_pattern_snapshot(
engine: *mut of_engine,
symbol: *const of_symbol_t,
out_buf: *mut c_void,
inout_len: *mut u32,
) -> i32 {
if engine.is_null() {
return of_error_t::OF_ERR_INVALID_ARG as i32;
}
let (symbol, _) = match symbol_from_ffi(symbol) {
Ok(v) => v,
Err(e) => return e as i32,
};
let engine = unsafe { &mut *engine };
let payload = format_pattern_snapshot(&engine.inner.pattern_snapshot(&symbol));
match write_json_to_c_buffer(&payload, out_buf, inout_len) {
Ok(_) => of_error_t::OF_OK as i32,
Err(e) => e as i32,
}
}
macro_rules! snapshot_c_abi {
($name:ident, $format:ident, $method:ident) => {
#[no_mangle]
pub extern "C" fn $name(
engine: *mut of_engine,
symbol: *const of_symbol_t,
out_buf: *mut c_void,
inout_len: *mut u32,
) -> i32 {
if engine.is_null() {
return of_error_t::OF_ERR_INVALID_ARG as i32;
}
let (symbol, _) = match symbol_from_ffi(symbol) {
Ok(v) => v,
Err(e) => return e as i32,
};
let engine = unsafe { &mut *engine };
let payload = $format(&engine.inner.$method(&symbol));
match write_json_to_c_buffer(&payload, out_buf, inout_len) {
Ok(_) => of_error_t::OF_OK as i32,
Err(e) => e as i32,
}
}
};
}
snapshot_c_abi!(
of_get_volatility_snapshot,
format_volatility_snapshot,
volatility_snapshot
);
snapshot_c_abi!(of_get_noise_snapshot, format_noise_snapshot, noise_snapshot);
snapshot_c_abi!(
of_get_hasbrouck_snapshot,
format_hasbrouck_snapshot,
hasbrouck_snapshot
);
snapshot_c_abi!(
of_get_almgren_chriss_snapshot,
format_almgren_chriss_snapshot,
almgren_chriss_snapshot
);
snapshot_c_abi!(
of_get_spread_decomp_snapshot,
format_spread_decomp_snapshot,
spread_decomp_snapshot
);
snapshot_c_abi!(of_get_acd_snapshot, format_acd_snapshot, acd_snapshot);
snapshot_c_abi!(
of_get_regime_snapshot,
format_regime_snapshot,
regime_snapshot
);
snapshot_c_abi!(
of_get_kinetic_energy_snapshot,
format_kinetic_energy_snapshot,
kinetic_energy_snapshot
);
snapshot_c_abi!(
of_get_dark_pool_snapshot,
format_dark_pool_snapshot,
dark_pool_snapshot
);
snapshot_c_abi!(
of_get_options_flow_snapshot,
format_options_flow_snapshot,
options_flow_snapshot
);
snapshot_c_abi!(
of_get_futures_snapshot,
format_futures_snapshot,
futures_snapshot
);
snapshot_c_abi!(
of_get_vol_signature_snapshot,
format_vol_signature_snapshot,
vol_signature_snapshot
);
snapshot_c_abi!(
of_get_agent_type_snapshot,
format_agent_type_snapshot,
agent_type_snapshot
);
snapshot_c_abi!(
of_get_dark_lit_correlation_snapshot,
format_dark_lit_correlation_snapshot,
dark_lit_correlation_snapshot
);
snapshot_c_abi!(
of_get_institutional_flow_snapshot,
format_institutional_flow_snapshot,
institutional_flow_snapshot
);
snapshot_c_abi!(
of_get_oi_analysis_snapshot,
format_oi_analysis_snapshot,
oi_analysis_snapshot
);
#[no_mangle]
pub extern "C" fn of_compute_lob_features(
engine: *mut of_engine,
symbol: *const of_symbol_t,
trade_imbalance: f64,
cancel_rate: f64,
arrival_rate: f64,
out_buf: *mut c_void,
inout_len: *mut u32,
) -> i32 {
if engine.is_null() {
return of_error_t::OF_ERR_INVALID_ARG as i32;
}
let (symbol, _) = match symbol_from_ffi(symbol) {
Ok(v) => v,
Err(e) => return e as i32,
};
let engine = unsafe { &*engine };
let payload = format_lob_feature_snapshot(&engine.inner.lob_features(
&symbol,
trade_imbalance,
cancel_rate,
arrival_rate,
));
match write_json_to_c_buffer(&payload, out_buf, inout_len) {
Ok(_) => of_error_t::OF_OK as i32,
Err(e) => e as i32,
}
}
#[no_mangle]
pub extern "C" fn of_get_analytics_snapshot(
engine: *mut of_engine,
symbol: *const of_symbol_t,
out_buf: *mut c_void,
inout_len: *mut u32,
) -> i32 {
if engine.is_null() {
return of_error_t::OF_ERR_INVALID_ARG as i32;
}
let (symbol, _) = match symbol_from_ffi(symbol) {
Ok(v) => v,
Err(e) => return e as i32,
};
let engine = unsafe { &mut *engine };
let payload = match engine.inner.analytics_snapshot(&symbol) {
Some(snap) => format_analytics_snapshot(&snap),
None => "{}".to_string(),
};
match write_json_to_c_buffer(&payload, out_buf, inout_len) {
Ok(_) => of_error_t::OF_OK as i32,
Err(e) => e as i32,
}
}
#[no_mangle]
pub extern "C" fn of_get_derived_analytics_snapshot(
engine: *mut of_engine,
symbol: *const of_symbol_t,
out_buf: *mut c_void,
inout_len: *mut u32,
) -> i32 {
if engine.is_null() {
return of_error_t::OF_ERR_INVALID_ARG as i32;
}
let (symbol, _) = match symbol_from_ffi(symbol) {
Ok(v) => v,
Err(e) => return e as i32,
};
let engine = unsafe { &mut *engine };
let payload = match engine.inner.derived_analytics_snapshot(&symbol) {
Some(snap) => format_derived_analytics_snapshot(&snap),
None => "{}".to_string(),
};
match write_json_to_c_buffer(&payload, out_buf, inout_len) {
Ok(_) => of_error_t::OF_OK as i32,
Err(e) => e as i32,
}
}
#[no_mangle]
pub extern "C" fn of_get_session_candle_snapshot(
engine: *mut of_engine,
symbol: *const of_symbol_t,
out_buf: *mut c_void,
inout_len: *mut u32,
) -> i32 {
if engine.is_null() {
return of_error_t::OF_ERR_INVALID_ARG as i32;
}
let (symbol, _) = match symbol_from_ffi(symbol) {
Ok(v) => v,
Err(e) => return e as i32,
};
let engine = unsafe { &mut *engine };
let payload = match engine.inner.session_candle_snapshot(&symbol) {
Some(snap) => format_session_candle_snapshot(&snap),
None => "{}".to_string(),
};
match write_json_to_c_buffer(&payload, out_buf, inout_len) {
Ok(_) => of_error_t::OF_OK as i32,
Err(e) => e as i32,
}
}
#[no_mangle]
pub extern "C" fn of_get_interval_candle_snapshot(
engine: *mut of_engine,
symbol: *const of_symbol_t,
window_ns: u64,
out_buf: *mut c_void,
inout_len: *mut u32,
) -> i32 {
if engine.is_null() {
return of_error_t::OF_ERR_INVALID_ARG as i32;
}
let (symbol, _) = match symbol_from_ffi(symbol) {
Ok(v) => v,
Err(e) => return e as i32,
};
let engine = unsafe { &mut *engine };
let payload = match engine.inner.interval_candle_snapshot(&symbol, window_ns) {
Some(snap) => format_interval_candle_snapshot(&snap),
None => "{}".to_string(),
};
match write_json_to_c_buffer(&payload, out_buf, inout_len) {
Ok(_) => of_error_t::OF_OK as i32,
Err(e) => e as i32,
}
}
#[cfg(feature = "tickbar")]
#[no_mangle]
pub extern "C" fn of_engine_set_tickbar_interval(engine: *mut of_engine, interval_ns: i64) -> i32 {
if engine.is_null() {
return of_error_t::OF_ERR_INVALID_ARG as i32;
}
let engine = unsafe { &mut *engine };
if interval_ns > 0 {
engine.inner.set_tickbar_interval(Some(interval_ns));
} else {
engine.inner.set_tickbar_interval(None);
}
of_error_t::OF_OK as i32
}
#[cfg(not(feature = "tickbar"))]
#[no_mangle]
pub extern "C" fn of_engine_set_tickbar_interval(engine: *mut of_engine, _interval_ns: i64) -> i32 {
if engine.is_null() {
return of_error_t::OF_ERR_INVALID_ARG as i32;
}
of_error_t::OF_ERR_STATE as i32
}
#[cfg(feature = "tickbar")]
#[no_mangle]
pub extern "C" fn of_get_bar_series(
engine: *mut of_engine,
symbol: *const of_symbol_t,
out_buf: *mut c_void,
inout_len: *mut u32,
) -> i32 {
if engine.is_null() {
return of_error_t::OF_ERR_INVALID_ARG as i32;
}
let (symbol, _) = match symbol_from_ffi(symbol) {
Ok(v) => v,
Err(e) => return e as i32,
};
let engine = unsafe { &mut *engine };
let payload = match engine.inner.bar_series(&symbol) {
Some(bars) => format_bar_series(&bars),
None => "[]".to_string(),
};
match write_json_to_c_buffer(&payload, out_buf, inout_len) {
Ok(_) => of_error_t::OF_OK as i32,
Err(e) => e as i32,
}
}
#[cfg(not(feature = "tickbar"))]
#[no_mangle]
pub extern "C" fn of_get_bar_series(
engine: *mut of_engine,
symbol: *const of_symbol_t,
out_buf: *mut c_void,
inout_len: *mut u32,
) -> i32 {
if engine.is_null() || symbol.is_null() || out_buf.is_null() || inout_len.is_null() {
return of_error_t::OF_ERR_INVALID_ARG as i32;
}
of_error_t::OF_ERR_STATE as i32
}
#[no_mangle]
pub extern "C" fn of_get_signal_snapshot(
engine: *mut of_engine,
symbol: *const of_symbol_t,
out_buf: *mut c_void,
inout_len: *mut u32,
) -> i32 {
if engine.is_null() {
return of_error_t::OF_ERR_INVALID_ARG as i32;
}
let (symbol, _) = match symbol_from_ffi(symbol) {
Ok(v) => v,
Err(e) => return e as i32,
};
let engine = unsafe { &mut *engine };
let payload = match engine.inner.signal_snapshot(&symbol) {
Some(snap) => {
let state = match snap.state {
SignalState::Neutral => "neutral",
SignalState::LongBias => "long_bias",
SignalState::ShortBias => "short_bias",
SignalState::Blocked => "blocked",
};
format!(
"{{\"module\":\"{}\",\"state\":\"{}\",\"confidence_bps\":{},\"quality_flags\":{},\"reason\":\"{}\"}}",
escape_json(snap.module_id),
state,
snap.confidence_bps,
snap.quality_flags,
escape_json(&snap.reason)
)
}
None => "{}".to_string(),
};
match write_json_to_c_buffer(&payload, out_buf, inout_len) {
Ok(_) => of_error_t::OF_OK as i32,
Err(e) => e as i32,
}
}
#[no_mangle]
pub extern "C" fn of_get_metrics_json(
engine: *mut of_engine,
out_json: *mut *const c_char,
out_len: *mut u32,
) -> i32 {
if engine.is_null() || out_json.is_null() || out_len.is_null() {
return of_error_t::OF_ERR_INVALID_ARG as i32;
}
let engine = unsafe { &mut *engine };
let metrics = engine.inner.metrics_json();
let c = match CString::new(metrics) {
Ok(c) => c,
Err(_) => return of_error_t::OF_ERR_INTERNAL as i32,
};
let len = c.as_bytes().len() as u32;
let ptr = c.into_raw();
unsafe {
*out_json = ptr;
*out_len = len;
}
of_error_t::OF_OK as i32
}
#[no_mangle]
pub extern "C" fn of_string_free(p: *const c_char) {
if p.is_null() {
return;
}
unsafe {
let _ = CString::from_raw(p as *mut c_char);
}
}
#[no_mangle]
pub extern "C" fn of_engine_poll_once(engine: *mut of_engine, quality_flags: u32) -> i32 {
if engine.is_null() {
return of_error_t::OF_ERR_INVALID_ARG as i32;
}
let engine = unsafe { &mut *engine };
let q = DataQualityFlags::from_bits_truncate(quality_flags);
match engine.inner.poll_once(q) {
Ok(_) => {
dispatch_callbacks(engine, engine.inner.current_quality_flags_bits());
of_error_t::OF_OK as i32
}
Err(err) => {
let status = map_runtime_error(&err);
if err.is_backpressure() {
dispatch_callbacks(engine, engine.inner.current_quality_flags_bits());
}
status
}
}
}
#[no_mangle]
pub extern "C" fn of_engine_set_analytics_config(
engine: *mut of_engine,
config: *const of_analytics_config_t,
) -> i32 {
if engine.is_null() {
return of_error_t::OF_ERR_INVALID_ARG as i32;
}
let engine = unsafe { &mut *engine };
if config.is_null() {
engine
.inner
.set_analytics_config(AnalyticsConfig::default());
} else {
let cfg = unsafe { *config };
engine.inner.set_analytics_config(cfg.into());
}
of_error_t::OF_OK as i32
}
fn map_runtime_error(err: &RuntimeError) -> i32 {
if err.is_backpressure() {
of_error_t::OF_ERR_BACKPRESSURE as i32
} else {
of_error_t::OF_ERR_STATE as i32
}
}
fn map_execution_result(result: Result<(), ExecutionError>) -> i32 {
match result {
Ok(()) => of_error_t::OF_OK as i32,
Err(err) => map_execution_error(&err),
}
}
fn map_execution_error(err: &ExecutionError) -> i32 {
match err {
ExecutionError::RiskRejected(_) => of_error_t::OF_ERR_RISK as i32,
ExecutionError::BufferFull => of_error_t::OF_ERR_BACKPRESSURE as i32,
ExecutionError::Disconnected | ExecutionError::RouteNotFound => {
of_error_t::OF_ERR_STATE as i32
}
ExecutionError::Core(_) => of_error_t::OF_ERR_INVALID_ARG as i32,
ExecutionError::Adapter(_) | ExecutionError::Journal(_) => {
of_error_t::OF_ERR_INTERNAL as i32
}
}
}
fn map_concurrent_execution_error(err: &ConcurrentExecutionError) -> i32 {
match err {
ConcurrentExecutionError::Backpressure => of_error_t::OF_ERR_BACKPRESSURE as i32,
ConcurrentExecutionError::Stopped | ConcurrentExecutionError::WorkerPanic => {
of_error_t::OF_ERR_STATE as i32
}
ConcurrentExecutionError::Execution(err) => map_execution_error(err),
}
}
fn route_configs_from_ffi(
routes: *const of_execution_route_config_t,
route_count: u32,
) -> Result<Vec<RouteConfig>, ()> {
let routes = unsafe { std::slice::from_raw_parts(routes, route_count as usize) };
let mut route_configs = Vec::with_capacity(routes.len());
for route in routes {
route_configs.push(route_config_from_ffi(route)?);
}
Ok(route_configs)
}
fn concurrent_config_from_ffi(
config: *const of_execution_concurrent_config_t,
) -> ConcurrentExecutionConfig {
if config.is_null() {
return ConcurrentExecutionConfig::default();
}
let config = unsafe { *config };
ConcurrentExecutionConfig {
command_capacity: nonzero_usize(config.command_capacity, 1024),
report_capacity: nonzero_usize(config.report_capacity, 1024),
event_buffer_capacity: nonzero_usize(config.event_buffer_capacity, FFI_EVENT_BUFFER_CAP),
}
}
fn nonzero_usize(value: u32, default_value: usize) -> usize {
if value == 0 {
default_value
} else {
value as usize
}
}
fn send_concurrent_command(
engine: &mut of_execution_concurrent_engine,
command: ExecutionCommand,
out_sequence: *mut u64,
) -> i32 {
match engine.inner.try_send(command) {
Ok(sequence) => {
write_optional_u64(out_sequence, sequence);
of_error_t::OF_OK as i32
}
Err(err) => map_concurrent_execution_error(&err),
}
}
fn write_concurrent_report(
report: &ExecutionCommandReport,
out_report: *mut of_execution_command_report_t,
out_events: *mut of_execution_event_t,
inout_len: *mut u32,
) -> i32 {
let copy_rc = copy_execution_events(&report.events, out_events, inout_len);
let event_count = unsafe { *inout_len };
unsafe {
*out_report = of_execution_command_report_t {
sequence: report.sequence,
kind: execution_command_kind_to_u32(report.kind),
result_code: match &report.result {
Ok(_) => of_error_t::OF_OK as i32,
Err(err) => map_execution_error(err),
},
event_count,
};
}
copy_rc
}
fn execution_command_kind_to_u32(kind: ExecutionCommandKind) -> u32 {
match kind {
ExecutionCommandKind::Submit => 1,
ExecutionCommandKind::Cancel => 2,
ExecutionCommandKind::Amend => 3,
ExecutionCommandKind::Poll => 4,
ExecutionCommandKind::RecoverOpenOrders => 5,
ExecutionCommandKind::Stop => 6,
}
}
fn write_optional_u64(ptr: *mut u64, value: u64) {
if !ptr.is_null() {
unsafe {
*ptr = value;
}
}
}
fn fixed_from_ptr<const N: usize>(ptr: *const c_char) -> Result<FixedAscii<N>, ()> {
let value = non_empty_string(ptr).ok_or(())?;
FixedAscii::new(&value).map_err(|_| ())
}
fn route_config_from_ffi(cfg: &of_execution_route_config_t) -> Result<RouteConfig, ()> {
Ok(RouteConfig {
route_id: fixed_from_ptr::<32>(cfg.route_id)?,
account_id: fixed_from_ptr::<32>(cfg.account_id)?,
symbol: ExecutionSymbol {
venue: fixed_from_ptr::<16>(cfg.venue)?,
instrument: fixed_from_ptr::<32>(cfg.instrument)?,
},
enabled: cfg.enabled != 0,
risk_limits: RiskLimits {
kill_switch: cfg.kill_switch != 0,
max_order_qty: cfg.max_order_qty,
max_order_notional: i128::from(cfg.max_order_notional),
max_open_orders: cfg.max_open_orders,
max_open_notional: i128::from(cfg.max_open_notional),
price_band_ticks: cfg.price_band_ticks,
},
})
}
fn order_request_from_ffi(req: &of_execution_order_request_t) -> Result<OrderRequest, ()> {
Ok(OrderRequest {
client_order_id: fixed_from_ptr::<40>(req.client_order_id)?,
account_id: fixed_from_ptr::<32>(req.account_id)?,
route_id: fixed_from_ptr::<32>(req.route_id)?,
strategy_id: fixed_from_ptr::<32>(req.strategy_id).unwrap_or_else(|_| StrategyId::empty()),
symbol: ExecutionSymbol {
venue: fixed_from_ptr::<16>(req.venue)?,
instrument: fixed_from_ptr::<32>(req.instrument)?,
},
side: side_from_execution_ffi(req.side)?,
order_type: order_type_from_ffi(req.order_type)?,
time_in_force: tif_from_ffi(req.time_in_force)?,
quantity: OrderQty(req.quantity),
limit_price: OrderPrice(req.limit_price),
stop_price: OrderPrice(req.stop_price),
ts_exchange_ns: req.ts_exchange_ns,
ts_recv_ns: req.ts_recv_ns,
})
}
fn cancel_request_from_ffi(req: &of_execution_cancel_request_t) -> Result<CancelRequest, ()> {
Ok(CancelRequest {
client_order_id: fixed_from_ptr::<40>(req.client_order_id)?,
orig_client_order_id: fixed_from_ptr::<40>(req.orig_client_order_id)?,
venue_order_id: fixed_from_ptr::<48>(req.venue_order_id)
.unwrap_or_else(|_| VenueOrderId::empty()),
account_id: fixed_from_ptr::<32>(req.account_id)?,
route_id: fixed_from_ptr::<32>(req.route_id)?,
symbol: ExecutionSymbol {
venue: fixed_from_ptr::<16>(req.venue)?,
instrument: fixed_from_ptr::<32>(req.instrument)?,
},
ts_recv_ns: req.ts_recv_ns,
})
}
fn amend_request_from_ffi(req: &of_execution_amend_request_t) -> Result<AmendRequest, ()> {
Ok(AmendRequest {
client_order_id: fixed_from_ptr::<40>(req.client_order_id)?,
orig_client_order_id: fixed_from_ptr::<40>(req.orig_client_order_id)?,
venue_order_id: fixed_from_ptr::<48>(req.venue_order_id)
.unwrap_or_else(|_| VenueOrderId::empty()),
account_id: fixed_from_ptr::<32>(req.account_id)?,
route_id: fixed_from_ptr::<32>(req.route_id)?,
symbol: ExecutionSymbol {
venue: fixed_from_ptr::<16>(req.venue)?,
instrument: fixed_from_ptr::<32>(req.instrument)?,
},
quantity: OrderQty(req.quantity),
limit_price: OrderPrice(req.limit_price),
ts_recv_ns: req.ts_recv_ns,
})
}
fn side_from_execution_ffi(value: u32) -> Result<OrderSide, ()> {
match value {
1 => Ok(OrderSide::Buy),
2 => Ok(OrderSide::Sell),
_ => Err(()),
}
}
fn order_type_from_ffi(value: u32) -> Result<OrderType, ()> {
match value {
1 => Ok(OrderType::Market),
2 => Ok(OrderType::Limit),
3 => Ok(OrderType::Stop),
4 => Ok(OrderType::StopLimit),
_ => Err(()),
}
}
fn tif_from_ffi(value: u32) -> Result<TimeInForce, ()> {
match value {
1 => Ok(TimeInForce::Day),
2 => Ok(TimeInForce::Gtc),
3 => Ok(TimeInForce::Ioc),
4 => Ok(TimeInForce::Fok),
5 => Ok(TimeInForce::Gtd),
_ => Err(()),
}
}
fn copy_execution_events(
events: &ExecutionEventBuffer,
out_events: *mut of_execution_event_t,
inout_len: *mut u32,
) -> i32 {
if inout_len.is_null() {
return of_error_t::OF_ERR_INVALID_ARG as i32;
}
let capacity = unsafe { *inout_len as usize };
let needed = events.len();
unsafe {
*inout_len = needed as u32;
}
if needed == 0 {
return of_error_t::OF_OK as i32;
}
if out_events.is_null() {
return of_error_t::OF_ERR_BACKPRESSURE as i32;
}
if capacity < needed {
return of_error_t::OF_ERR_BACKPRESSURE as i32;
}
for (idx, event) in events.as_slice().iter().enumerate() {
unsafe {
*out_events.add(idx) = event_to_ffi(event);
}
}
of_error_t::OF_OK as i32
}
fn event_to_ffi(event: &ExecutionEvent) -> of_execution_event_t {
of_execution_event_t {
exec_type: event.exec_type as u32,
order_status: event.order_status as u32,
client_order_id: cstr_array(event.client_order_id.as_str()),
orig_client_order_id: cstr_array(event.orig_client_order_id.as_str()),
venue_order_id: cstr_array(event.venue_order_id.as_str()),
execution_id: cstr_array(event.execution_id.as_str()),
account_id: cstr_array(event.account_id.as_str()),
route_id: cstr_array(event.route_id.as_str()),
venue: cstr_array(event.symbol.venue.as_str()),
instrument: cstr_array(event.symbol.instrument.as_str()),
last_qty: event.last_qty.0,
last_price: event.last_price.0,
cumulative_qty: event.cumulative_qty.0,
leaves_qty: event.leaves_qty.0,
average_price: event.average_price.0,
ts_exchange_ns: event.ts_exchange_ns,
ts_recv_ns: event.ts_recv_ns,
reason: event.reason as u32,
text: cstr_array(event.text.as_str()),
}
}
fn order_state_to_ffi(state: &OrderState) -> of_execution_order_state_t {
of_execution_order_state_t {
client_order_id: cstr_array(state.client_order_id.as_str()),
venue_order_id: cstr_array(state.venue_order_id.as_str()),
account_id: cstr_array(state.account_id.as_str()),
route_id: cstr_array(state.route_id.as_str()),
venue: cstr_array(state.symbol.venue.as_str()),
instrument: cstr_array(state.symbol.instrument.as_str()),
status: state.status as u32,
order_qty: state.order_qty.0,
cumulative_qty: state.cumulative_qty.0,
leaves_qty: state.leaves_qty.0,
average_price: state.average_price.0,
updated_ns: state.updated_ns,
}
}
fn cstr_array<const N: usize>(value: &str) -> [c_char; N] {
let mut out = [0 as c_char; N];
if N == 0 {
return out;
}
let bytes = value.as_bytes();
let max = bytes.len().min(N - 1);
for idx in 0..max {
out[idx] = bytes[idx] as c_char;
}
out
}
#[cfg(test)]
include!("tests.rs");