Initial commit: manifoldbt public repo

Python DSL, examples, docs, benchmarks, and tests.
Rust engine distributed as pre-compiled wheel via PyPI.
This commit is contained in:
Jimmy7892
2026-03-17 16:15:40 +01:00
commit 67ab17280b
51 changed files with 10227 additions and 0 deletions
+332
View File
@@ -0,0 +1,332 @@
"""Pure-Python tests for the Expr DSL serialization.
These tests verify that the Python DSL produces JSON matching the Rust
bt_expr::Expr serde (externally-tagged) format. No compiled Rust
extension needed.
"""
from manifoldbt.expr import Expr, col, lit, param, when
def test_column_serializes():
assert col("close").to_json() == {"Column": "close"}
def test_literal_float():
assert lit(1.0).to_json() == {"Literal": {"Float64": 1.0}}
def test_literal_int():
assert lit(42).to_json() == {"Literal": {"Int64": 42}}
def test_literal_bool():
assert lit(True).to_json() == {"Literal": {"Bool": True}}
def test_literal_null():
assert lit(None).to_json() == {"Literal": "Null"}
def test_parameter():
assert param("size", default=1.0).to_json() == {"Parameter": "size"}
def test_add():
expr = col("close") + lit(1.0)
assert expr.to_json() == {
"Add": [{"Column": "close"}, {"Literal": {"Float64": 1.0}}]
}
def test_sub():
expr = col("close") - col("open")
assert expr.to_json() == {
"Sub": [{"Column": "close"}, {"Column": "open"}]
}
def test_mul_with_raw_float():
expr = col("signal") * 0.5
assert expr.to_json() == {
"Mul": [{"Column": "signal"}, {"Literal": {"Float64": 0.5}}]
}
def test_rmul():
expr = 2.0 * col("signal")
assert expr.to_json() == {
"Mul": [{"Literal": {"Float64": 2.0}}, {"Column": "signal"}]
}
def test_neg():
expr = -col("x")
assert expr.to_json() == {
"Mul": [{"Literal": {"Float64": -1.0}}, {"Column": "x"}]
}
def test_div():
expr = col("a") / col("b")
assert expr.to_json() == {
"Div": [{"Column": "a"}, {"Column": "b"}]
}
def test_gt():
expr = col("close") > lit(100.0)
assert expr.to_json() == {
"Gt": [{"Column": "close"}, {"Literal": {"Float64": 100.0}}]
}
def test_lt():
expr = col("close") < 50.0
assert expr.to_json() == {
"Lt": [{"Column": "close"}, {"Literal": {"Float64": 50.0}}]
}
def test_eq():
expr = col("side") == lit(1)
assert expr.to_json() == {
"Eq": [{"Column": "side"}, {"Literal": {"Int64": 1}}]
}
def test_and_or():
a = col("x") > lit(0.0)
b = col("y") < lit(1.0)
expr = a & b
assert expr.to_json()["And"][0] == {"Gt": [{"Column": "x"}, {"Literal": {"Float64": 0.0}}]}
expr2 = a | b
assert "Or" in expr2.to_json()
def test_not():
expr = ~(col("flag") == lit(True))
assert expr.to_json()["Not"]["Eq"][0] == {"Column": "flag"}
def test_rolling_mean():
expr = col("close").rolling_mean(20)
assert expr.to_json() == {"RollingMean": [{"Column": "close"}, 20]}
def test_rolling_std():
expr = col("close").rolling_std(30)
assert expr.to_json() == {"RollingStd": [{"Column": "close"}, 30]}
def test_lag():
expr = col("close").lag(5)
assert expr.to_json() == {"Lag": [{"Column": "close"}, 5]}
def test_diff():
expr = col("close").diff()
assert expr.to_json() == {"Diff": [{"Column": "close"}, 1]}
def test_pct_change():
expr = col("close").pct_change(3)
assert expr.to_json() == {"PctChange": [{"Column": "close"}, 3]}
def test_ewm_mean():
expr = col("close").ewm_mean(10.0)
assert expr.to_json() == {"EwmMean": [{"Column": "close"}, 10.0]}
def test_zscore():
expr = col("close").zscore(20)
assert expr.to_json() == {"ZScore": [{"Column": "close"}, 20]}
def test_cumsum():
expr = col("volume").cumsum()
assert expr.to_json() == {"CumSum": {"Column": "volume"}}
def test_cumprod():
expr = col("returns").cumprod()
assert expr.to_json() == {"CumProd": {"Column": "returns"}}
def test_rank():
expr = col("score").rank()
assert expr.to_json() == {"Rank": {"Column": "score"}}
def test_if_else():
cond = col("x") > lit(0.0)
expr = when(cond, lit(1.0), lit(-1.0))
expected = {
"IfElse": [
{"Gt": [{"Column": "x"}, {"Literal": {"Float64": 0.0}}]},
{"Literal": {"Float64": 1.0}},
{"Literal": {"Float64": -1.0}},
]
}
assert expr.to_json() == expected
def test_complex_sma_cross():
"""SMA crossover — the canonical DSL example."""
close = col("close")
sma_fast = close.rolling_mean(20)
sma_slow = close.rolling_mean(60)
signal = when(sma_fast > sma_slow, lit(1.0), lit(-1.0))
result = signal.to_json()
assert result["IfElse"][0]["Gt"][0] == {"RollingMean": [{"Column": "close"}, 20]}
assert result["IfElse"][0]["Gt"][1] == {"RollingMean": [{"Column": "close"}, 60]}
assert result["IfElse"][1] == {"Literal": {"Float64": 1.0}}
assert result["IfElse"][2] == {"Literal": {"Float64": -1.0}}
def test_param_with_meta():
p = param("size", default=1.0, range=(0.5, 2.0), description="position size")
assert p.to_json() == {"Parameter": "size"}
meta = p._param_meta
assert meta["name"] == "size"
assert meta["default"] == 1.0
assert meta["range"] == (0.5, 2.0)
assert meta["description"] == "position size"
# -- Datetime extraction tests -----------------------------------------------
def test_hour():
expr = col("timestamp").hour()
assert expr.to_json() == {"Hour": {"Column": "timestamp"}}
def test_minute():
expr = col("timestamp").minute()
assert expr.to_json() == {"Minute": {"Column": "timestamp"}}
def test_day_of_week():
expr = col("timestamp").day_of_week()
assert expr.to_json() == {"DayOfWeek": {"Column": "timestamp"}}
def test_month():
expr = col("timestamp").month()
assert expr.to_json() == {"Month": {"Column": "timestamp"}}
def test_day_of_month():
expr = col("timestamp").day_of_month()
assert expr.to_json() == {"DayOfMonth": {"Column": "timestamp"}}
def test_datetime_in_filter_expression():
"""Datetime functions compose with arithmetic and boolean ops."""
ts = col("timestamp")
# US market hours filter: hour >= 14 AND hour < 21
in_us = (ts.hour() > lit(13.5)) & (ts.hour() < lit(21.0))
result = in_us.to_json()
assert "And" in result
assert "Gt" in result["And"][0]
assert result["And"][0]["Gt"][0] == {"Hour": {"Column": "timestamp"}}
def test_datetime_indicators_module():
"""indicators.hour() etc. default to timestamp column."""
from manifoldbt.indicators import hour, day_of_week, month
assert hour().to_json() == {"Hour": {"Column": "timestamp"}}
assert day_of_week().to_json() == {"DayOfWeek": {"Column": "timestamp"}}
assert month().to_json() == {"Month": {"Column": "timestamp"}}
# -- Scan (stateful fold) tests ----------------------------------------------
def test_scan_prev_json():
from manifoldbt.expr import s
assert s.prev("x").to_json() == {"ScanPrev": "x"}
def test_scan_var_json():
from manifoldbt.expr import s
assert s.var("k").to_json() == {"ScanVar": "k"}
def test_scan_cumsum_json():
from manifoldbt.expr import s, scan
cumsum = scan(
state={"total": lit(0.0)},
update={"total": s.prev("total") + col("value")},
output="total",
)
result = cumsum.to_json()
assert "Scan" in result
data = result["Scan"]
assert data["state_names"] == ["total"]
assert data["update_names"] == ["total"]
assert data["output"] == "total"
# init_exprs should be [Literal(Float64(0.0))]
assert data["init_exprs"] == [{"Literal": {"Float64": 0.0}}]
# update_exprs should be [Add(ScanPrev("total"), Column("value"))]
assert data["update_exprs"] == [
{"Add": [{"ScanPrev": "total"}, {"Column": "value"}]}
]
def test_scan_kalman_json():
from manifoldbt.expr import s, scan
kalman = scan(
state={"x": col("close"), "p": lit(1.0)},
update={
"p_pred": s.prev("p") + param("q"),
"k": s.var("p_pred") / (s.var("p_pred") + param("r")),
"x": s.prev("x") + s.var("k") * (col("close") - s.prev("x")),
"p": (lit(1.0) - s.var("k")) * s.var("p_pred"),
},
output="x",
)
result = kalman.to_json()
assert "Scan" in result
data = result["Scan"]
assert data["state_names"] == ["x", "p"]
assert data["update_names"] == ["p_pred", "k", "x", "p"]
assert data["output"] == "x"
assert len(data["init_exprs"]) == 2
assert len(data["update_exprs"]) == 4
def test_kalman_indicator():
from manifoldbt.indicators import kalman
result = kalman().to_json()
assert "Scan" in result
data = result["Scan"]
assert data["state_names"] == ["x", "p"]
assert data["output"] == "x"
def test_garch_indicator():
from manifoldbt.indicators import garch
result = garch().to_json()
assert "Scan" in result
data = result["Scan"]
assert "sigma2" in data["state_names"]
assert data["output"] == "sigma"
def test_scan_exports():
"""Verify scan and s are accessible from the top-level package."""
import manifoldbt as bt
assert hasattr(bt, "scan")
assert hasattr(bt, "s")
assert bt.s.prev("x").to_json() == {"ScanPrev": "x"}