release: v0.5.0

This commit is contained in:
github-actions[bot]
2026-06-16 17:00:05 +00:00
parent e35d54875c
commit 864dee7616
9 changed files with 683 additions and 7 deletions
+23
View File
@@ -34,6 +34,7 @@ from manifoldbt.config import (
ExecutionConfig,
FeeConfig,
OrderConfig,
VenueFees,
resolve_universe,
)
from manifoldbt.exceptions import (
@@ -110,6 +111,11 @@ import atexit
atexit.register(_print_pro_summary)
def license_info() -> tuple:
"""Get license info: (tier, email). tier is "Pro" or "Community", email is str or None."""
return _license_info()
def _is_pro() -> bool:
"""Check if current license is Pro."""
try:
@@ -270,6 +276,22 @@ def _prepare_config(config: BacktestConfig, strategy, store: DataStore) -> Backt
if cfg.provider and not cfg.signal_source:
cfg.signal_source = cfg.provider
# --- Resolve per-venue fee mapping: symbol_venue keys may be symbol names ---
# Users key symbol_venue by name (e.g. "dydx:BTC-USD:perp" or "BTC-USDT:perp")
# for ergonomics; the engine needs integer SymbolIds. Resolve them here using
# the same name→id mapping as the universe.
fees = getattr(cfg, "fees", None)
if fees is not None and getattr(fees, "symbol_venue", None):
resolved_sv = {}
for key, venue in fees.symbol_venue.items():
if isinstance(key, int):
resolved_sv[key] = venue
elif cfg.symbol_names and key in cfg.symbol_names:
resolved_sv[int(cfg.symbol_names[key])] = venue
else:
resolved_sv[int(store.resolve_symbol(key))] = venue
fees.symbol_venue = resolved_sv
# Merge orders from strategy into execution config
if strategy and hasattr(strategy, '_orders') and strategy._orders:
if cfg.execution.orders is None:
@@ -1140,6 +1162,7 @@ __all__ = [
"BacktestConfig",
"ExecutionConfig",
"FeeConfig",
"VenueFees",
"OrderConfig",
# Helpers
"date_to_ns",
+80 -1
View File
@@ -103,7 +103,13 @@ class ExecutionConfig:
@dataclass
class FeeConfig:
class VenueFees:
"""Fee schedule for a single venue (exchange).
The same fields as a flat (single-venue) :class:`FeeConfig`. Used as the
value type of :attr:`FeeConfig.per_venue` to express per-exchange fees.
"""
maker_fee_bps: float = 0.0
taker_fee_bps: float = 0.0
funding_rate_column: Optional[str] = None
@@ -122,6 +128,79 @@ class FeeConfig:
"default_fill_type": self.default_fill_type,
}
@dataclass
class FeeConfig:
"""Transaction-cost configuration.
The flat fields below describe the *default* venue, applied to any symbol
not present in ``symbol_venue``. Per-venue fees are opt-in via ``per_venue``
(named fee schedules) plus ``symbol_venue`` (which symbol trades where).
Single-venue configs are unchanged — leaving ``per_venue``/``symbol_venue``
empty serializes to the exact same JSON as before.
"""
maker_fee_bps: float = 0.0
taker_fee_bps: float = 0.0
funding_rate_column: Optional[str] = None
borrow_rate_annual_bps: float = 0.0
min_fee: float = 0.0
default_fill_type: str = "Taker"
"""Default fill type for fee calculation: "Maker" or "Taker" (conservative)."""
per_venue: Dict[str, VenueFees] = field(default_factory=dict)
"""Named per-venue fee overrides, keyed by venue name (e.g. ``"binance"``)."""
symbol_venue: Dict[int, str] = field(default_factory=dict)
"""Maps a ``SymbolId`` (integer) to the name of the venue it executes on.
Symbols absent from this map use the default-venue fields above."""
def to_json_dict(self) -> dict:
d: dict = {
"maker_fee_bps": self.maker_fee_bps,
"taker_fee_bps": self.taker_fee_bps,
"funding_rate_column": self.funding_rate_column,
"borrow_rate_annual_bps": self.borrow_rate_annual_bps,
"min_fee": self.min_fee,
"default_fill_type": self.default_fill_type,
}
# Emit per-venue keys only when populated so single-venue configs stay
# byte-identical to the legacy flat shape (matches Rust serde flatten).
if self.per_venue:
d["per_venue"] = {
name: (v.to_json_dict() if isinstance(v, VenueFees) else dict(v))
for name, v in self.per_venue.items()
}
if self.symbol_venue:
d["symbol_venue"] = {
str(sid): name for sid, name in self.symbol_venue.items()
}
return d
@classmethod
def multi_venue(
cls,
default: Optional[VenueFees] = None,
venues: Optional[Dict[str, VenueFees]] = None,
symbol_venue: Optional[Dict[int, str]] = None,
) -> "FeeConfig":
"""Build a per-venue fee config.
Args:
default: Fee schedule for symbols without a venue mapping.
venues: Named per-venue fee schedules (e.g. ``{"binance": VenueFees(...)}``).
symbol_venue: Maps integer ``SymbolId`` to a venue name in ``venues``.
"""
d = default or VenueFees()
return cls(
maker_fee_bps=d.maker_fee_bps,
taker_fee_bps=d.taker_fee_bps,
funding_rate_column=d.funding_rate_column,
borrow_rate_annual_bps=d.borrow_rate_annual_bps,
min_fee=d.min_fee,
default_fill_type=d.default_fill_type,
per_venue=venues or {},
symbol_venue=symbol_venue or {},
)
@classmethod
def binance_perps(cls) -> "FeeConfig":
"""Binance USDM perpetual futures defaults (taker fees + funding)."""