release: v0.28.1

This commit is contained in:
github-actions[bot]
2026-10-23 23:06:58 +02:00
committed by Jimmy7892
parent 20e12173fe
commit 4c0b303ea3
4 changed files with 59 additions and 6 deletions
+7 -4
View File
@@ -534,7 +534,10 @@ paid tiers add scale and depth. See [pricing](https://www.manifoldbt.com/#pricin
## License
Apache 2.0 with Commons Clause. The source is available, free to use, modify and
self-host. Reselling the software or offering it as a paid hosted service is not
permitted. See [LICENSE](https://github.com/manifoldbt/manifoldbt/blob/master/LICENSE)
for the full text.
Apache 2.0 with Commons Clause, for what this repository holds (the Python
package, the examples, the tests and the benchmarks) and for the wheels
published on PyPI. The Rust engine inside those wheels is distributed compiled;
its source is not published. Use, modification and self-hosting are permitted.
Reselling the software or offering it as a paid hosted service is not. See
[LICENSE](https://github.com/manifoldbt/manifoldbt/blob/master/LICENSE) for the
full text.
+22 -1
View File
@@ -3288,7 +3288,7 @@ row per event of one order:
|---|---|
| `A` | it entered the book (the new order of a replacement too, flagged) |
| `E` | it executed, at its own price |
| `C` | it executed at another price (a cross), printed or not |
| `C` | it executed at another price than the one it is shown at (`exec_price`), printed or not |
| `X` | part of it was cancelled |
| `D` | it left the book: what was left of it |
| `U` | it was replaced: it left the book, and the order replacing it follows at the same instant, at the back of its queue |
@@ -3343,6 +3343,15 @@ after. Then, event by event:
have met ours first: ours is served, as much as it brings, best price
then first come among our orders it crosses (`book_cross`).
An execution at another price than the one the order is shown at (`C`) is read
where it traded: that order did not wait in the queue of the level it is shown
at (older orders there are often still resting when it executes, a tick away
from their price), so it serves none of ours there. It is rule 2 for the
orders of ours standing at the price it traded at, that it entered after, and
rule 3 for those standing at a worse price. Leaving the level it is shown at,
it takes its size out of the volume ahead of the orders of ours that it was
shown ahead of.
Our fills take nothing out of the market's book: its events are replayed as
they were. Prints against orders the book does not show (`P`) and crosses
serve nothing: they are not in the displayed queue. Everything else is the
@@ -3386,6 +3395,12 @@ On the day of 2025-12-09 (AAPL, SPY, QQQ, NVDA, TSLA):
book on arrival; with rule 4 added to it, the only differences left are the
orders that crossed the spread when they arrived (0.2 to 1.7 % of them),
which the venue executes as takers;
- on the half session of 2025-11-28 (AAPL), every one of the 5,170 quotes a
maker joining the best price had served is backed by the messages of the
day: an execution behind it, one at a worse price, or an order of the
other side at its price. The 727 executions at another price (`C`) all
traded one cent better than the price they are shown at, and 78 % of them
with older orders still resting at that shown price;
- a maker quoting one unit at the best bid and ask every 100 ms, moved when
the best moves, inventory within five units, gives the same posts,
cancellations, fills, inventory and cash as the same maker in that replay,
@@ -3424,6 +3439,12 @@ later on AAPL, 47 µs on QQQ; 1.3 ms and 0.6 ms at the 90th percentile), or not
at all when none does before the quote is cancelled. The aggressor that paid a
worse price than ours met ours first: this venue serves it at that execution.
These figures were taken before an execution at another price (`C`) was read
where it traded. Handed to hftbacktest as a fill of the order, such an
execution serves the orders behind the level it is shown at, as it served them
here; it no longer does here, so a quote served only there can now be one of
these.
With 100 shares a quote, the same quotes are served at the same instants, and
1,704 of them on AAPL, 5,425 on QQQ, for another size: an execution behind
ours, or an order entering the other side, serves here as much as it takes;
+1 -1
View File
@@ -5,7 +5,7 @@
[project]
name = "manifoldbt"
version = "0.28.0"
version = "0.28.1"
description = "Rust-powered backtesting engine for quantitative research"
readme = "README.md"
requires-python = ">=3.9"
+29
View File
@@ -70,6 +70,12 @@ def cancel(ms, ref, qty):
return _frame(_head(b"X", ms) + struct.pack(">QI", ref, qty))
def execute_at(ms, ref, qty, px):
"""An execution at a price other than the one the order is shown at (C)."""
return _frame(_head(b"C", ms) + struct.pack(">QIQ", ref, qty, ref) + b"Y"
+ struct.pack(">I", int(round(px * 10_000))))
def _store(tmp_path, msgs):
path = tmp_path / "day.itch"
path.write_bytes(b"".join(msgs))
@@ -155,6 +161,29 @@ def test_the_queue_a_run_names_is_the_one_served(tmp_path):
assert len(level2.fills_df(backend="pandas")) == 0
def test_an_execution_at_another_price_is_read_where_it_traded(tmp_path):
# Order 3 enters at 10.00 behind ours, shown at 10.00, and executes at
# 10.01: a price ahead of the whole level. It did not wait in the queue of
# 10.00, so its execution does not reach our order, and the 100 shown ahead
# of ours are still there. Traded at 9.99 instead, it went through our level.
for traded, served in ((10.01, []), (9.99, [50.0])):
msgs = [
add(0, 1, True, 100, 10.00),
add(0, 2, False, 100, 10.01),
add(3, 3, True, 40, 10.00),
execute_at(4, 3, 40, traded),
add(20, 4, True, 10, 9.98),
]
day = tmp_path / f"at_{traded}"
day.mkdir()
res, seen = _run_queue(_store(day, msgs))
assert seen[4] == 100.0, traded
fills = res.fills_df(backend="pandas")
assert list(fills["qty"]) == served, traded
if served:
assert set(fills["channel"]) == {"traverse"}
def test_the_exact_queue_is_refused_on_a_book_stored_by_price():
market = bt.sim.Market.from_ladders("X", 0.1, [(0, [(1.0, 1.0)], [(2.0, 1.0)])])
with pytest.raises(Exception, match="stored order by order"):