flox-py 0.5.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,16 @@
1
+ find_package(pybind11 REQUIRED)
2
+
3
+ pybind11_add_module(flox_py flox_py.cpp)
4
+
5
+ target_link_libraries(flox_py PRIVATE flox)
6
+ target_include_directories(flox_py PRIVATE
7
+ ${CMAKE_CURRENT_SOURCE_DIR}/../include
8
+ )
9
+
10
+ if(NOT MSVC)
11
+ target_compile_options(flox_py PRIVATE
12
+ $<$<CONFIG:Release>:-O3 -march=native -flto>
13
+ )
14
+ endif()
15
+
16
+ install(TARGETS flox_py LIBRARY DESTINATION . COMPONENT python_module)
flox_py-0.5.0/PKG-INFO ADDED
@@ -0,0 +1,80 @@
1
+ Metadata-Version: 2.4
2
+ Name: flox-py
3
+ Version: 0.5.0
4
+ Summary: Python bindings for the Flox indicator library
5
+ Keywords: trading,backtest,indicators,technical-analysis
6
+ Author: FLOX Foundation
7
+ License-Expression: MIT
8
+ Classifier: Development Status :: 4 - Beta
9
+ Classifier: Intended Audience :: Financial and Insurance Industry
10
+ Classifier: Programming Language :: C++
11
+ Classifier: Programming Language :: Python :: 3
12
+ Project-URL: Repository, https://github.com/FLOX-Foundation/flox
13
+ Requires-Python: >=3.9
14
+ Requires-Dist: numpy
15
+ Description-Content-Type: text/markdown
16
+
17
+ # flox-py
18
+
19
+ Python bindings for the Flox indicator library. Tested against TA-Lib.
20
+
21
+ ## Install
22
+
23
+ ```bash
24
+ pip install flox-py
25
+ ```
26
+
27
+ ## Usage
28
+
29
+ ```python
30
+ import flox_py as flox
31
+ import numpy as np
32
+
33
+ close = np.array([...])
34
+ high = np.array([...])
35
+ low = np.array([...])
36
+
37
+ ema50 = flox.ema(close, 50)
38
+ atr14 = flox.atr(high, low, close, 14)
39
+ rsi14 = flox.rsi(close, 14)
40
+
41
+ signal_long = (rsi14 > 70).astype(np.int8)
42
+ signal_short = ((rsi14 < 30) * -1).astype(np.int8)
43
+
44
+ returns = flox.bar_returns(signal_long, signal_short, log_returns)
45
+ trades = flox.trade_pnl(signal_long, signal_short, log_returns)
46
+ pf = flox.profit_factor(returns)
47
+ wr = flox.win_rate(trades)
48
+ ```
49
+
50
+ ## Indicators
51
+
52
+ | Function | Description |
53
+ |----------|-------------|
54
+ | `flox.ema(input, period)` | Exponential Moving Average |
55
+ | `flox.sma(input, period)` | Simple Moving Average |
56
+ | `flox.rma(input, period)` | Wilder Moving Average |
57
+ | `flox.rsi(input, period)` | Relative Strength Index |
58
+ | `flox.atr(high, low, close, period)` | Average True Range |
59
+ | `flox.adx(high, low, close, period)` | ADX, +DI, -DI |
60
+ | `flox.macd(input, fast, slow, signal)` | MACD line, signal, histogram |
61
+ | `flox.bollinger(input, period, stddev)` | upper, middle, lower bands |
62
+ | `flox.stochastic(high, low, close, k, d)` | %K and %D |
63
+ | `flox.cci(high, low, close, period)` | Commodity Channel Index |
64
+ | `flox.slope(input, length)` | price slope |
65
+ | `flox.kama(input, period)` | Kaufman Adaptive MA |
66
+ | `flox.dema(input, period)` | Double EMA |
67
+ | `flox.tema(input, period)` | Triple EMA |
68
+ | `flox.chop(high, low, close, period)` | Choppiness Index |
69
+ | `flox.obv(close, volume)` | On-Balance Volume |
70
+ | `flox.vwap(close, volume, window)` | rolling VWAP |
71
+ | `flox.cvd(open, high, low, close, volume)` | Cumulative Volume Delta |
72
+
73
+ ## Metrics
74
+
75
+ | Function | Description |
76
+ |----------|-------------|
77
+ | `flox.bar_returns(sig_long, sig_short, log_ret)` | per-bar returns with signal shift |
78
+ | `flox.trade_pnl(sig_long, sig_short, log_ret)` | per-trade PnL array |
79
+ | `flox.profit_factor(returns)` | sum positive / abs sum negative |
80
+ | `flox.win_rate(trade_pnls)` | fraction of winning trades |
@@ -0,0 +1,64 @@
1
+ # flox-py
2
+
3
+ Python bindings for the Flox indicator library. Tested against TA-Lib.
4
+
5
+ ## Install
6
+
7
+ ```bash
8
+ pip install flox-py
9
+ ```
10
+
11
+ ## Usage
12
+
13
+ ```python
14
+ import flox_py as flox
15
+ import numpy as np
16
+
17
+ close = np.array([...])
18
+ high = np.array([...])
19
+ low = np.array([...])
20
+
21
+ ema50 = flox.ema(close, 50)
22
+ atr14 = flox.atr(high, low, close, 14)
23
+ rsi14 = flox.rsi(close, 14)
24
+
25
+ signal_long = (rsi14 > 70).astype(np.int8)
26
+ signal_short = ((rsi14 < 30) * -1).astype(np.int8)
27
+
28
+ returns = flox.bar_returns(signal_long, signal_short, log_returns)
29
+ trades = flox.trade_pnl(signal_long, signal_short, log_returns)
30
+ pf = flox.profit_factor(returns)
31
+ wr = flox.win_rate(trades)
32
+ ```
33
+
34
+ ## Indicators
35
+
36
+ | Function | Description |
37
+ |----------|-------------|
38
+ | `flox.ema(input, period)` | Exponential Moving Average |
39
+ | `flox.sma(input, period)` | Simple Moving Average |
40
+ | `flox.rma(input, period)` | Wilder Moving Average |
41
+ | `flox.rsi(input, period)` | Relative Strength Index |
42
+ | `flox.atr(high, low, close, period)` | Average True Range |
43
+ | `flox.adx(high, low, close, period)` | ADX, +DI, -DI |
44
+ | `flox.macd(input, fast, slow, signal)` | MACD line, signal, histogram |
45
+ | `flox.bollinger(input, period, stddev)` | upper, middle, lower bands |
46
+ | `flox.stochastic(high, low, close, k, d)` | %K and %D |
47
+ | `flox.cci(high, low, close, period)` | Commodity Channel Index |
48
+ | `flox.slope(input, length)` | price slope |
49
+ | `flox.kama(input, period)` | Kaufman Adaptive MA |
50
+ | `flox.dema(input, period)` | Double EMA |
51
+ | `flox.tema(input, period)` | Triple EMA |
52
+ | `flox.chop(high, low, close, period)` | Choppiness Index |
53
+ | `flox.obv(close, volume)` | On-Balance Volume |
54
+ | `flox.vwap(close, volume, window)` | rolling VWAP |
55
+ | `flox.cvd(open, high, low, close, volume)` | Cumulative Volume Delta |
56
+
57
+ ## Metrics
58
+
59
+ | Function | Description |
60
+ |----------|-------------|
61
+ | `flox.bar_returns(sig_long, sig_short, log_ret)` | per-bar returns with signal shift |
62
+ | `flox.trade_pnl(sig_long, sig_short, log_ret)` | per-trade PnL array |
63
+ | `flox.profit_factor(returns)` | sum positive / abs sum negative |
64
+ | `flox.win_rate(trade_pnls)` | fraction of winning trades |
@@ -0,0 +1,210 @@
1
+ // python/aggregator_bindings.h
2
+
3
+ #pragma once
4
+
5
+ #include <pybind11/numpy.h>
6
+ #include <pybind11/pybind11.h>
7
+ #include <pybind11/stl.h>
8
+
9
+ #include "flox/aggregator/aggregation_policy.h"
10
+ #include "flox/aggregator/bar.h"
11
+ #include "flox/aggregator/policies/heikin_ashi_bar_policy.h"
12
+ #include "flox/aggregator/policies/range_bar_policy.h"
13
+ #include "flox/aggregator/policies/renko_bar_policy.h"
14
+ #include "flox/aggregator/policies/tick_bar_policy.h"
15
+ #include "flox/aggregator/policies/time_bar_policy.h"
16
+ #include "flox/aggregator/policies/volume_bar_policy.h"
17
+ #include "flox/book/events/trade_event.h"
18
+ #include "flox/book/trade.h"
19
+ #include "flox/common.h"
20
+ #include "flox/util/base/time.h"
21
+
22
+ #include <chrono>
23
+ #include <cstring>
24
+ #include <vector>
25
+
26
+ namespace py = pybind11;
27
+
28
+ namespace
29
+ {
30
+
31
+ using namespace flox;
32
+
33
+ #pragma pack(push, 1)
34
+ struct PyExtBar
35
+ {
36
+ int64_t start_time_ns;
37
+ int64_t end_time_ns;
38
+ int64_t open_raw;
39
+ int64_t high_raw;
40
+ int64_t low_raw;
41
+ int64_t close_raw;
42
+ int64_t volume_raw;
43
+ int64_t buy_volume_raw;
44
+ int64_t trade_count;
45
+ };
46
+ #pragma pack(pop)
47
+ static_assert(sizeof(PyExtBar) == 72);
48
+
49
+ inline int64_t toUnixNs(TimePoint tp)
50
+ {
51
+ return tp.time_since_epoch().count() - unix_to_flox_offset_ns().load(std::memory_order_relaxed);
52
+ }
53
+
54
+ inline PyExtBar barToExtBar(const Bar& b)
55
+ {
56
+ return {.start_time_ns = toUnixNs(b.startTime),
57
+ .end_time_ns = toUnixNs(b.endTime),
58
+ .open_raw = b.open.raw(),
59
+ .high_raw = b.high.raw(),
60
+ .low_raw = b.low.raw(),
61
+ .close_raw = b.close.raw(),
62
+ .volume_raw = b.volume.raw(),
63
+ .buy_volume_raw = b.buyVolume.raw(),
64
+ .trade_count = b.tradeCount.raw()};
65
+ }
66
+
67
+ // Batch aggregation: takes pre-extracted vectors (no GIL needed)
68
+ template <typename Policy>
69
+ std::vector<PyExtBar> doAggregate(Policy& policy, const int64_t* ts, const double* px,
70
+ const double* qty, const uint8_t* ib, size_t n)
71
+ {
72
+ std::vector<PyExtBar> bars;
73
+ bars.reserve(n / 10); // rough estimate
74
+ Bar currentBar;
75
+ bool initialized = false;
76
+
77
+ for (size_t i = 0; i < n; ++i)
78
+ {
79
+ TradeEvent trade;
80
+ trade.trade.price = Price::fromDouble(px[i]);
81
+ trade.trade.quantity = Quantity::fromDouble(qty[i]);
82
+ trade.trade.isBuy = (ib[i] != 0);
83
+ trade.trade.exchangeTsNs = ts[i];
84
+ trade.trade.symbol = 1;
85
+ trade.trade.instrument = InstrumentType::Spot;
86
+
87
+ if (!initialized)
88
+ {
89
+ policy.initBar(trade, currentBar);
90
+ initialized = true;
91
+ continue;
92
+ }
93
+
94
+ if (policy.shouldClose(trade, currentBar))
95
+ {
96
+ bars.push_back(barToExtBar(currentBar));
97
+ policy.initBar(trade, currentBar);
98
+ continue;
99
+ }
100
+
101
+ policy.update(trade, currentBar);
102
+ }
103
+
104
+ if (initialized)
105
+ {
106
+ bars.push_back(barToExtBar(currentBar));
107
+ }
108
+
109
+ return bars;
110
+ }
111
+
112
+ template <typename Policy>
113
+ py::array_t<PyExtBar> aggregateBars(Policy policy, py::array_t<int64_t> timestamps,
114
+ py::array_t<double> prices, py::array_t<double> quantities,
115
+ py::array_t<uint8_t> isBuy)
116
+ {
117
+ size_t n = timestamps.size();
118
+ const auto* ts = timestamps.data();
119
+ const auto* px = prices.data();
120
+ const auto* qt = quantities.data();
121
+ const auto* ib = isBuy.data();
122
+
123
+ std::vector<PyExtBar> bars;
124
+ {
125
+ py::gil_scoped_release release;
126
+ bars = doAggregate(policy, ts, px, qt, ib, n);
127
+ }
128
+
129
+ py::array_t<PyExtBar> result(bars.size());
130
+ if (!bars.empty())
131
+ {
132
+ std::memcpy(result.mutable_data(), bars.data(), bars.size() * sizeof(PyExtBar));
133
+ }
134
+ return result;
135
+ }
136
+
137
+ } // namespace
138
+
139
+ inline void bindAggregators(py::module_& m)
140
+ {
141
+ PYBIND11_NUMPY_DTYPE(PyExtBar, start_time_ns, end_time_ns, open_raw, high_raw, low_raw,
142
+ close_raw, volume_raw, buy_volume_raw, trade_count);
143
+
144
+ // Ensure time mapping is initialized
145
+ flox::init_timebase_mapping();
146
+
147
+ m.def(
148
+ "aggregate_time_bars",
149
+ [](py::array_t<int64_t> ts, py::array_t<double> px, py::array_t<double> qty,
150
+ py::array_t<uint8_t> is_buy, double interval_seconds)
151
+ {
152
+ auto policy = flox::TimeBarPolicy(
153
+ std::chrono::duration_cast<std::chrono::nanoseconds>(
154
+ std::chrono::duration<double>(interval_seconds)));
155
+ return aggregateBars(std::move(policy), ts, px, qty, is_buy);
156
+ },
157
+ "Aggregate trades into time bars",
158
+ py::arg("timestamps"), py::arg("prices"), py::arg("quantities"),
159
+ py::arg("is_buy"), py::arg("interval_seconds"));
160
+
161
+ m.def(
162
+ "aggregate_tick_bars",
163
+ [](py::array_t<int64_t> ts, py::array_t<double> px, py::array_t<double> qty,
164
+ py::array_t<uint8_t> is_buy, uint32_t tick_count)
165
+ { return aggregateBars(flox::TickBarPolicy(tick_count), ts, px, qty, is_buy); },
166
+ "Aggregate trades into tick bars",
167
+ py::arg("timestamps"), py::arg("prices"), py::arg("quantities"),
168
+ py::arg("is_buy"), py::arg("tick_count"));
169
+
170
+ m.def(
171
+ "aggregate_volume_bars",
172
+ [](py::array_t<int64_t> ts, py::array_t<double> px, py::array_t<double> qty,
173
+ py::array_t<uint8_t> is_buy, double volume_threshold)
174
+ { return aggregateBars(flox::VolumeBarPolicy::fromDouble(volume_threshold), ts, px, qty, is_buy); },
175
+ "Aggregate trades into volume bars",
176
+ py::arg("timestamps"), py::arg("prices"), py::arg("quantities"),
177
+ py::arg("is_buy"), py::arg("volume_threshold"));
178
+
179
+ m.def(
180
+ "aggregate_range_bars",
181
+ [](py::array_t<int64_t> ts, py::array_t<double> px, py::array_t<double> qty,
182
+ py::array_t<uint8_t> is_buy, double range_size)
183
+ { return aggregateBars(flox::RangeBarPolicy::fromDouble(range_size), ts, px, qty, is_buy); },
184
+ "Aggregate trades into range bars",
185
+ py::arg("timestamps"), py::arg("prices"), py::arg("quantities"),
186
+ py::arg("is_buy"), py::arg("range_size"));
187
+
188
+ m.def(
189
+ "aggregate_renko_bars",
190
+ [](py::array_t<int64_t> ts, py::array_t<double> px, py::array_t<double> qty,
191
+ py::array_t<uint8_t> is_buy, double brick_size)
192
+ { return aggregateBars(flox::RenkoBarPolicy::fromDouble(brick_size), ts, px, qty, is_buy); },
193
+ "Aggregate trades into renko bars",
194
+ py::arg("timestamps"), py::arg("prices"), py::arg("quantities"),
195
+ py::arg("is_buy"), py::arg("brick_size"));
196
+
197
+ m.def(
198
+ "aggregate_heikin_ashi_bars",
199
+ [](py::array_t<int64_t> ts, py::array_t<double> px, py::array_t<double> qty,
200
+ py::array_t<uint8_t> is_buy, double interval_seconds)
201
+ {
202
+ auto policy = flox::HeikinAshiBarPolicy(
203
+ std::chrono::duration_cast<std::chrono::nanoseconds>(
204
+ std::chrono::duration<double>(interval_seconds)));
205
+ return aggregateBars(std::move(policy), ts, px, qty, is_buy);
206
+ },
207
+ "Aggregate trades into Heikin-Ashi bars",
208
+ py::arg("timestamps"), py::arg("prices"), py::arg("quantities"),
209
+ py::arg("is_buy"), py::arg("interval_seconds"));
210
+ }
@@ -0,0 +1,210 @@
1
+ // python/backtest_bindings.h
2
+
3
+ #pragma once
4
+
5
+ #include <pybind11/numpy.h>
6
+ #include <pybind11/pybind11.h>
7
+ #include <pybind11/stl.h>
8
+
9
+ #include "flox/backtest/backtest_result.h"
10
+ #include "flox/backtest/simulated_clock.h"
11
+ #include "flox/backtest/simulated_executor.h"
12
+ #include "flox/common.h"
13
+
14
+ #include <cstring>
15
+ #include <memory>
16
+ #include <vector>
17
+
18
+ namespace py = pybind11;
19
+
20
+ namespace
21
+ {
22
+
23
+ using namespace flox;
24
+
25
+ #pragma pack(push, 1)
26
+ struct PyFill
27
+ {
28
+ uint64_t order_id;
29
+ uint32_t symbol;
30
+ uint8_t side;
31
+ uint8_t _pad[3];
32
+ int64_t price_raw;
33
+ int64_t quantity_raw;
34
+ int64_t timestamp_ns;
35
+ };
36
+ #pragma pack(pop)
37
+ static_assert(sizeof(PyFill) == 40);
38
+
39
+ #pragma pack(push, 1)
40
+ struct PyTradeRecord
41
+ {
42
+ uint32_t symbol;
43
+ uint8_t side;
44
+ uint8_t _pad[3];
45
+ int64_t entry_price_raw;
46
+ int64_t exit_price_raw;
47
+ int64_t quantity_raw;
48
+ int64_t entry_time_ns;
49
+ int64_t exit_time_ns;
50
+ int64_t pnl_raw;
51
+ int64_t fee_raw;
52
+ };
53
+ #pragma pack(pop)
54
+ static_assert(sizeof(PyTradeRecord) == 64);
55
+
56
+ inline PyFill fillToPyFill(const Fill& f)
57
+ {
58
+ return {.order_id = f.orderId,
59
+ .symbol = f.symbol,
60
+ .side = static_cast<uint8_t>(f.side == Side::BUY ? 0 : 1),
61
+ ._pad = {},
62
+ .price_raw = f.price.raw(),
63
+ .quantity_raw = f.quantity.raw(),
64
+ .timestamp_ns = static_cast<int64_t>(f.timestampNs)};
65
+ }
66
+
67
+ inline PyTradeRecord tradeRecToPy(const TradeRecord& t)
68
+ {
69
+ return {.symbol = t.symbol,
70
+ .side = static_cast<uint8_t>(t.side == Side::BUY ? 0 : 1),
71
+ ._pad = {},
72
+ .entry_price_raw = t.entryPrice.raw(),
73
+ .exit_price_raw = t.exitPrice.raw(),
74
+ .quantity_raw = t.quantity.raw(),
75
+ .entry_time_ns = static_cast<int64_t>(t.entryTimeNs),
76
+ .exit_time_ns = static_cast<int64_t>(t.exitTimeNs),
77
+ .pnl_raw = t.pnl.raw(),
78
+ .fee_raw = t.fee.raw()};
79
+ }
80
+
81
+ // Wraps SimulatedExecutor + SimulatedClock for standalone use
82
+ class PySimulatedExecutor
83
+ {
84
+ public:
85
+ PySimulatedExecutor() : _executor(_clock) { _executor.start(); }
86
+
87
+ void submitOrder(uint64_t id, const std::string& sideStr, double price, double qty,
88
+ const std::string& typeStr, uint32_t symbol)
89
+ {
90
+ Order order;
91
+ order.id = id;
92
+ order.side = (sideStr == "buy") ? Side::BUY : Side::SELL;
93
+ order.price = Price::fromDouble(price);
94
+ order.quantity = Quantity::fromDouble(qty);
95
+ order.symbol = symbol;
96
+ order.type = parseOrderType(typeStr);
97
+ _executor.submitOrder(order);
98
+ }
99
+
100
+ void cancelOrder(uint64_t id) { _executor.cancelOrder(id); }
101
+ void cancelAll(uint32_t symbol) { _executor.cancelAllOrders(symbol); }
102
+
103
+ void onBar(uint32_t symbol, double closePrice)
104
+ {
105
+ _executor.onBar(symbol, Price::fromDouble(closePrice));
106
+ }
107
+
108
+ void onTrade(uint32_t symbol, double price, bool isBuy)
109
+ {
110
+ _executor.onTrade(symbol, Price::fromDouble(price), isBuy);
111
+ }
112
+
113
+ void advanceClock(int64_t timestampNs) { _clock.advanceTo(timestampNs); }
114
+
115
+ py::array_t<PyFill> fills() const
116
+ {
117
+ const auto& f = _executor.fills();
118
+ py::array_t<PyFill> result(f.size());
119
+ auto* out = result.mutable_data();
120
+ for (size_t i = 0; i < f.size(); ++i)
121
+ {
122
+ out[i] = fillToPyFill(f[i]);
123
+ }
124
+ return result;
125
+ }
126
+
127
+ py::list fillsList() const
128
+ {
129
+ py::list result;
130
+ for (const auto& f : _executor.fills())
131
+ {
132
+ py::dict d;
133
+ d["order_id"] = f.orderId;
134
+ d["symbol"] = f.symbol;
135
+ d["side"] = (f.side == Side::BUY) ? "buy" : "sell";
136
+ d["price"] = f.price.toDouble();
137
+ d["quantity"] = f.quantity.toDouble();
138
+ d["timestamp_ns"] = static_cast<int64_t>(f.timestampNs);
139
+ result.append(d);
140
+ }
141
+ return result;
142
+ }
143
+
144
+ size_t fillCount() const { return _executor.fills().size(); }
145
+
146
+ private:
147
+ static OrderType parseOrderType(const std::string& s)
148
+ {
149
+ if (s == "limit")
150
+ {
151
+ return OrderType::LIMIT;
152
+ }
153
+ if (s == "stop_market")
154
+ {
155
+ return OrderType::STOP_MARKET;
156
+ }
157
+ if (s == "stop_limit")
158
+ {
159
+ return OrderType::STOP_LIMIT;
160
+ }
161
+ if (s == "take_profit_market")
162
+ {
163
+ return OrderType::TAKE_PROFIT_MARKET;
164
+ }
165
+ if (s == "take_profit_limit")
166
+ {
167
+ return OrderType::TAKE_PROFIT_LIMIT;
168
+ }
169
+ if (s == "trailing_stop")
170
+ {
171
+ return OrderType::TRAILING_STOP;
172
+ }
173
+ return OrderType::MARKET;
174
+ }
175
+
176
+ SimulatedClock _clock;
177
+ SimulatedExecutor _executor;
178
+ };
179
+
180
+ } // namespace
181
+
182
+ inline void bindBacktest(py::module_& m)
183
+ {
184
+ PYBIND11_NUMPY_DTYPE(PyFill, order_id, symbol, side, price_raw, quantity_raw, timestamp_ns);
185
+ PYBIND11_NUMPY_DTYPE(PyTradeRecord, symbol, side, entry_price_raw, exit_price_raw,
186
+ quantity_raw, entry_time_ns, exit_time_ns, pnl_raw, fee_raw);
187
+
188
+ py::class_<PySimulatedExecutor>(m, "SimulatedExecutor")
189
+ .def(py::init<>())
190
+ .def("submit_order", &PySimulatedExecutor::submitOrder,
191
+ "Submit an order to the simulated exchange",
192
+ py::arg("id"), py::arg("side"), py::arg("price"), py::arg("quantity"),
193
+ py::arg("type") = "market", py::arg("symbol") = 1)
194
+ .def("cancel_order", &PySimulatedExecutor::cancelOrder, py::arg("order_id"))
195
+ .def("cancel_all", &PySimulatedExecutor::cancelAll, py::arg("symbol"))
196
+ .def("on_bar", &PySimulatedExecutor::onBar,
197
+ "Feed a bar close price for order matching",
198
+ py::arg("symbol"), py::arg("close_price"))
199
+ .def("on_trade", &PySimulatedExecutor::onTrade,
200
+ "Feed a trade for order matching",
201
+ py::arg("symbol"), py::arg("price"), py::arg("is_buy"))
202
+ .def("advance_clock", &PySimulatedExecutor::advanceClock,
203
+ "Advance simulation clock to timestamp",
204
+ py::arg("timestamp_ns"))
205
+ .def("fills", &PySimulatedExecutor::fills,
206
+ "Get all fills as numpy structured array")
207
+ .def("fills_list", &PySimulatedExecutor::fillsList,
208
+ "Get all fills as list of dicts")
209
+ .def_property_readonly("fill_count", &PySimulatedExecutor::fillCount);
210
+ }