Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,7 @@ add_library(pineforge STATIC
src/engine_lower_tf.cpp
src/engine_metrics.cpp
src/engine_orders.cpp
src/engine_order_events.cpp
src/engine_path_resolve.cpp
src/engine_report.cpp
src/engine_risk.cpp
Expand Down
5 changes: 3 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -120,7 +120,7 @@ for the full tool catalog, request schemas, and env vars (`PINEFORGE_ALLOW_ANYWH

- 🎯 **TradingView-exact.** 251 of 252 reference strategies match TV trade-for-trade. The lone outlier is a stress probe at the 1× margin boundary where TV's broker emulator is non-deterministic — engine is correct. **100 of 100** PineForge excellent vs PyneCore + PineTS on the public three-way benchmark (~167,000 TV trades; PyneCore: 85 of 100; PineTS indicator-only).
- ⚡ **Microsecond-class.** Median **162× faster than PyneCore** across 99 commonly-timed strategies (full 41,307-bar OHLCV, magnifier-on hot loop; see [benchmarks/results/speed.md](benchmarks/results/speed.md)). Parameter sweeps load one `.so` and re-run with new inputs — no recompile, no fork, no IPC.
- 🔒 **Stable C ABI.** 26 functions, one header (`<pineforge/pineforge.h>`). Append-only across minor versions, `static_assert`-pinned struct layouts, hidden-visibility hygiene. Drop a strategy `.so` in any harness; it just runs.
- 🔒 **Stable C ABI.** 27 functions, one header (`<pineforge/pineforge.h>`). Append-only across minor versions, `static_assert`-pinned struct layouts, hidden-visibility hygiene. Drop a strategy `.so` in any harness; it just runs.
- 🧪 **Reproducible to the bit.** Deterministic float ordering, deterministic bar magnifier, no internal RNG seeded from time. Two runs with the same inputs produce bit-identical trade lists.
- 🧰 **FFI-friendly.** Call from Python (`ctypes`), Rust (`libloading`), Go (`cgo`), Node, Julia. Worked examples for [pure C](https://cdocs.pineforge.dev/examples_c.html), [Python sweep](https://cdocs.pineforge.dev/examples_python_sweep.html), [Rust](https://cdocs.pineforge.dev/examples_rust.html), [multi-strategy harness](https://cdocs.pineforge.dev/examples_multi.html), and [magnifier A/B](https://cdocs.pineforge.dev/examples_magnifier.html) ship in the docs.
- 🌍 **Cross-platform CI.** Linux + macOS × Release + Debug. Universal mac binary. Static library, no runtime DSO surprises at deploy time.
Expand Down Expand Up @@ -324,6 +324,7 @@ int main(void) {
| `strategy_set_trace_enabled` | Toggle per-bar trace recording |
| `strategy_set_trade_start_time` | Suppress historical order placement |
| `strategy_stream_begin` | Warm on OHLCV, then enter realtime mode |
| `strategy_stream_set_gap_policy` | Select fixed-grid or data-driven quiet bars |
| `strategy_stream_push_tick` | Push one normalized ordered trade |
| `strategy_stream_push_ticks` | Push a raw-trade batch |
| `strategy_stream_advance_time` | Confirm bars / materialize quiet intervals |
Expand All @@ -341,7 +342,7 @@ int main(void) {
| `pf_version_string` | Full runtime version string |


POD types (`pf_bar_t`, `pf_trade_tick_t`, `pf_trade_t`, `pf_report_t`, `pf_security_diag_t`, `pf_trace_entry_t`, `pf_version_t`, and — since ABI v2 — `pf_trade_stats_t`, `pf_equity_stats_t`, `pf_metrics_t`, `pf_equity_point_t`) and the `pf_magnifier_distribution_t` enum complete the surface. ABI v2 (`PF_ABI_VERSION == 2`, exported as `pf_abi_version()`) appends computed trading metrics and a per-script-bar equity curve to `pf_report_t`; callers must verify the version before running since the report struct is caller-allocated.
POD types (`pf_bar_t`, `pf_trade_tick_t`, `pf_trade_t`, `pf_report_t`, `pf_order_event_t`, diagnostics, metrics, and version records) plus magnifier and stream-gap enums complete the surface. ABI v3 (`PF_ABI_VERSION == 3`, exported as `pf_abi_version()`) appends deterministic order lifecycle events and their rolling count/hash; callers must verify the version before running since the report struct is caller-allocated.

**Stability guarantee:** within the same `PINEFORGE_VERSION_MAJOR`, struct layouts and `extern "C"` signatures are append-only. New fields may be appended; existing fields are never reordered, removed, or retyped. New functions may be added; existing functions are never removed or signature-changed. Compile-time `static_assert`s in `src/c_abi.cpp` pin the layouts against drift.

Expand Down
68 changes: 67 additions & 1 deletion docker/run_json.py
Original file line number Diff line number Diff line change
Expand Up @@ -401,6 +401,38 @@ class TraceEntryC(ctypes.Structure):
]


class OrderEventC(ctypes.Structure):
_fields_ = [
("transition_sequence", ctypes.c_uint64),
("command_revision_id", ctypes.c_uint64),
("order_leg_id", ctypes.c_uint64),
("priority_sequence", ctypes.c_uint64),
("fill_id", ctypes.c_uint64),
("entry_lot_id", ctypes.c_uint64),
("position_episode_id", ctypes.c_uint64),
("event_timestamp", ctypes.c_int64),
("event_sequence", ctypes.c_uint64),
("input_bar_index", ctypes.c_int64),
("script_bar_index", ctypes.c_int32),
("command_kind", ctypes.c_int32), ("leg_kind", ctypes.c_int32),
("state_before", ctypes.c_int32), ("state_after", ctypes.c_int32),
("transition", ctypes.c_int32), ("reason", ctypes.c_int32),
("side", ctypes.c_int32), ("oca_type", ctypes.c_int32),
("requested_quantity", ctypes.c_double),
("remaining_quantity", ctypes.c_double),
("filled_quantity", ctypes.c_double),
("observed_price", ctypes.c_double), ("stop_price", ctypes.c_double),
("limit_price", ctypes.c_double),
("trail_activation_price", ctypes.c_double),
("trail_watermark", ctypes.c_double), ("fill_price", ctypes.c_double),
("position_size_before", ctypes.c_double),
("position_size_after", ctypes.c_double),
("equity_before", ctypes.c_double), ("equity_after", ctypes.c_double),
("id", ctypes.c_char_p), ("from_entry", ctypes.c_char_p),
("oca_name", ctypes.c_char_p),
]


class ReportC(ctypes.Structure):
_fields_ = [
("total_trades", ctypes.c_int),
Expand Down Expand Up @@ -428,6 +460,11 @@ class ReportC(ctypes.Structure):
("metrics", MetricsC),
("equity_curve", ctypes.POINTER(EquityPointC)),
("equity_curve_len", ctypes.c_int64), # int64, NOT c_int
("order_events", ctypes.POINTER(OrderEventC)),
("order_events_len", ctypes.c_int64),
("order_event_count", ctypes.c_uint64),
("order_event_hash", ctypes.c_uint64),
("order_event_dropped", ctypes.c_uint64),
]


Expand Down Expand Up @@ -456,7 +493,7 @@ def engine_version(lib: ctypes.CDLL) -> dict:

# pf_report_t is CALLER-allocated: a .so built against a different ABI
# writes past (or short of) our ReportC buffer. Assert version up front.
EXPECTED_PF_ABI = 2
EXPECTED_PF_ABI = 3


def check_abi(lib: ctypes.CDLL) -> None:
Expand Down Expand Up @@ -650,6 +687,31 @@ def build_report_dict(report: ReportC, ohlcv_path: Path,
"open_profit": _num(p.open_profit),
})

order_events = []
for i in range(int(report.order_events_len)):
e = report.order_events[i]
text = lambda p: p.decode("utf-8", "replace") if p else ""
order_events.append({
"transition_sequence": int(e.transition_sequence),
"command_revision_id": int(e.command_revision_id),
"order_leg_id": int(e.order_leg_id),
"priority_sequence": int(e.priority_sequence),
"fill_id": int(e.fill_id),
"entry_lot_id": int(e.entry_lot_id),
"position_episode_id": int(e.position_episode_id),
"event_timestamp": int(e.event_timestamp),
"event_sequence": int(e.event_sequence),
"script_bar_index": int(e.script_bar_index),
"command_kind": int(e.command_kind), "leg_kind": int(e.leg_kind),
"state_before": int(e.state_before), "state_after": int(e.state_after),
"transition": int(e.transition), "reason": int(e.reason),
"id": text(e.id), "from_entry": text(e.from_entry),
"oca_name": text(e.oca_name),
"filled_quantity": _num(e.filled_quantity),
"observed_price": _num(e.observed_price),
"fill_price": _num(e.fill_price),
})

return {
"engine": "pineforge",
"input": {
Expand Down Expand Up @@ -682,10 +744,14 @@ def build_report_dict(report: ReportC, ohlcv_path: Path,
"magnifier_sub_bars_total": int(report.magnifier_sub_bars_total),
"magnifier_sample_ticks_total": int(report.magnifier_sample_ticks_total),
"bar_magnifier_enabled": bool(report.bar_magnifier_enabled),
"order_event_count": int(report.order_event_count),
"order_event_hash": f"{int(report.order_event_hash):016x}",
"order_event_dropped": int(report.order_event_dropped),
},
"trades": trades,
"metrics": metrics,
"equity_curve": equity_curve,
"order_events": order_events,
}


Expand Down
2 changes: 2 additions & 0 deletions docs/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ docs/
├── Doxyfile # Doxygen config — input, theme, options
├── build.sh # one-shot: fetch theme, run doxygen → docs/site/html/
├── coverage.md # canonical Pine v6 coverage map (also referenced as a page)
├── probes/ # manually captured external semantic oracles
│ └── tradingview-realtime/ # P1-P9 Pine v6 JSON/webhook probe kit
├── pages/ # narrative markdown pages
│ ├── index.md # @mainpage — landing
│ ├── getting-started.md
Expand Down
Loading
Loading