The C ABI is FFI-friendly by design: a handful of functions, 10 POD structs, one enum, no callbacks, no opaque types except pf_strategy_t (which is void*). This page shows the canonical ctypes wiring for Python; any language with a C-FFI (Rust libc, Go cgo, Node ffi-napi, Julia ccall) follows the same shape.
import ctypes
_fields_ = [
("open", ctypes.c_double),
("high", ctypes.c_double),
("low", ctypes.c_double),
("close", ctypes.c_double),
("volume", ctypes.c_double),
("timestamp", ctypes.c_int64),
]
_fields_ = [
("entry_time", ctypes.c_int64),
("exit_time", ctypes.c_int64),
("entry_price", ctypes.c_double),
("exit_price", ctypes.c_double),
("pnl", ctypes.c_double),
("pnl_pct", ctypes.c_double),
("is_long", ctypes.c_int),
("max_runup", ctypes.c_double),
("max_drawdown", ctypes.c_double),
("qty", ctypes.c_double),
("commission", ctypes.c_double),
("entry_bar_index", ctypes.c_int32),
("exit_bar_index", ctypes.c_int32),
]
"""ABI v2 — one block each for all / long-only / short-only trades."""
_fields_ = [
("num_trades", ctypes.c_int32), ("num_wins", ctypes.c_int32),
("num_losses", ctypes.c_int32), ("num_even", ctypes.c_int32),
("percent_profitable", ctypes.c_double),
("net_profit", ctypes.c_double), ("net_profit_pct", ctypes.c_double),
("gross_profit", ctypes.c_double), ("gross_profit_pct", ctypes.c_double),
("gross_loss", ctypes.c_double), ("gross_loss_pct", ctypes.c_double),
("profit_factor", ctypes.c_double),
("avg_trade", ctypes.c_double), ("avg_trade_pct", ctypes.c_double),
("avg_win", ctypes.c_double), ("avg_win_pct", ctypes.c_double),
("avg_loss", ctypes.c_double), ("avg_loss_pct", ctypes.c_double),
("ratio_avg_win_avg_loss", ctypes.c_double),
("largest_win", ctypes.c_double), ("largest_win_pct", ctypes.c_double),
("largest_loss", ctypes.c_double), ("largest_loss_pct", ctypes.c_double),
("commission_paid", ctypes.c_double),
("expectancy", ctypes.c_double),
("max_consecutive_wins", ctypes.c_int32), ("max_consecutive_losses", ctypes.c_int32),
("avg_bars_in_trade", ctypes.c_double), ("avg_bars_in_wins", ctypes.c_double),
("avg_bars_in_losses", ctypes.c_double),
]
"""ABI v2 — equity-curve-derived stats (all-trades only)."""
_fields_ = [
("max_equity_drawdown", ctypes.c_double), ("max_equity_drawdown_pct", ctypes.c_double),
("max_equity_runup", ctypes.c_double), ("max_equity_runup_pct", ctypes.c_double),
("buy_hold_return", ctypes.c_double), ("buy_hold_return_pct", ctypes.c_double),
("sharpe_tv", ctypes.c_double), ("sortino_tv", ctypes.c_double),
("sharpe_bar", ctypes.c_double), ("sortino_bar", ctypes.c_double),
("cagr", ctypes.c_double), ("calmar", ctypes.c_double),
("recovery_factor", ctypes.c_double), ("time_in_market_pct", ctypes.c_double),
("open_pl", ctypes.c_double),
]
"""ABI v2 — composite metrics container."""
_fields_ = [("all", pf_trade_stats_t), ("longs", pf_trade_stats_t),
("shorts", pf_trade_stats_t), ("equity", pf_equity_stats_t)]
"""ABI v2 — one per-script-bar equity point."""
_fields_ = [("time_ms", ctypes.c_int64), ("equity", ctypes.c_double),
("open_profit", ctypes.c_double)]
_fields_ = [
("sec_id", ctypes.c_int),
("feed_count", ctypes.c_int64),
("complete_count", ctypes.c_int64),
("partial_count", ctypes.c_int64),
]
_fields_ = [
("timestamp", ctypes.c_int64),
("bar_index", ctypes.c_int32),
("name_id", ctypes.c_int32),
("value", ctypes.c_double),
]
_fields_ = [
("total_trades", ctypes.c_int),
("trades", ctypes.POINTER(pf_trade_t)),
("trades_len", ctypes.c_int),
("net_profit", ctypes.c_double),
("input_bars_processed", ctypes.c_int64),
("script_bars_processed", ctypes.c_int64),
("security_feeds_total", ctypes.c_int64),
("security_complete_total", ctypes.c_int64),
("security_partial_total", ctypes.c_int64),
("magnifier_sub_bars_total", ctypes.c_int64),
("magnifier_sample_ticks_total", ctypes.c_int64),
("input_tf_seconds", ctypes.c_int),
("script_tf_seconds", ctypes.c_int),
("script_tf_ratio", ctypes.c_int),
("needs_aggregation", ctypes.c_int),
("bar_magnifier_enabled", ctypes.c_int),
("security_diag", ctypes.POINTER(pf_security_diag_t)),
("security_diag_len", ctypes.c_int),
("trace", ctypes.POINTER(pf_trace_entry_t)),
("trace_len", ctypes.c_int),
("trace_names", ctypes.POINTER(ctypes.c_char_p)),
("trace_names_len", ctypes.c_int),
("metrics", pf_metrics_t),
("equity_curve", ctypes.POINTER(pf_equity_point_t)),
("equity_curve_len", ctypes.c_int64),
]
_fields_ = [
("major", ctypes.c_int),
("minor", ctypes.c_int),
("patch", ctypes.c_int),
("commit_sha", ctypes.c_char_p),
]
PF_MAGNIFIER_UNIFORM = 0
PF_MAGNIFIER_COSINE = 1
PF_MAGNIFIER_TRIANGLE = 2
PF_MAGNIFIER_ENDPOINTS = 3
PF_MAGNIFIER_FRONT_LOADED = 4
PF_MAGNIFIER_BACK_LOADED = 5
Single OHLCV bar pushed into the engine.
Single per-script-bar equity point.
Equity-curve-derived statistics (all-trades only, like TV).
Composite metrics container: trade stats (all / long / short) + equity-curve stats.
Backtest report filled by run_backtest / run_backtest_full.
Per-request.security() site diagnostic counters.
Single per-bar trace entry.
Trade-level statistics block — computed once each for all / long / short.
Closed-trade record returned in pf_report_t::trades.
Runtime version descriptor returned by pf_version_get.
lib = ctypes.CDLL("./my_strategy.so")
EXPECTED_PF_ABI = 2
try:
lib.pf_abi_version.restype = ctypes.c_int
abi = lib.pf_abi_version()
except AttributeError:
raise RuntimeError(".so predates pf_abi_version (ABI v1); rebuild it")
if abi != EXPECTED_PF_ABI:
raise RuntimeError(f"ABI mismatch: .so={abi}, mirror={EXPECTED_PF_ABI}")
lib.strategy_create.argtypes = [ctypes.c_char_p]
lib.strategy_create.restype = ctypes.c_void_p
lib.strategy_free.argtypes = [ctypes.c_void_p]
lib.strategy_free.restype = None
lib.run_backtest.argtypes = [ctypes.c_void_p,
ctypes.POINTER(pf_bar_t), ctypes.c_int,
ctypes.POINTER(pf_report_t)]
lib.run_backtest.restype = None
lib.run_backtest_full.argtypes = [
ctypes.c_void_p,
ctypes.POINTER(pf_bar_t), ctypes.c_int,
ctypes.c_char_p, ctypes.c_char_p,
ctypes.c_int, ctypes.c_int, ctypes.c_int,
ctypes.POINTER(pf_report_t),
]
lib.run_backtest_full.restype = None
lib.report_free.argtypes = [ctypes.POINTER(pf_report_t)]
lib.report_free.restype = None
lib.strategy_set_input.argtypes = [ctypes.c_void_p,
ctypes.c_char_p, ctypes.c_char_p]
lib.strategy_set_input.restype = None
lib.strategy_set_override.argtypes = [ctypes.c_void_p,
ctypes.c_char_p, ctypes.c_char_p]
lib.strategy_set_override.restype = None