mirror of
https://github.com/manifoldbt/manifoldbt.git
synced 2026-08-24 14:38:04 +00:00
112 lines
4.1 KiB
Python
112 lines
4.1 KiB
Python
"""Example 17: Per-Venue Fees — charge each symbol its own fee schedule.
|
|
|
|
Real desks route different assets to different exchanges (or liquidity tiers),
|
|
each with its own maker/taker fees, funding column and borrow rate. ``FeeConfig``
|
|
models this directly: a ``default`` venue plus named ``per_venue`` overrides and a
|
|
``symbol_venue`` map saying which symbol trades where.
|
|
|
|
Here a 4-asset momentum portfolio executes the majors (BTC, ETH) on a cheap
|
|
venue and the alts (XRP, DOT) on a more expensive one. Single-provider universe,
|
|
so it runs without Pro.
|
|
|
|
Usage:
|
|
python examples/17_per_venue_fees.py
|
|
"""
|
|
import os
|
|
import time
|
|
import manifoldbt as mbt
|
|
from manifoldbt.indicators import close, ema, roc, high, low
|
|
from manifoldbt.helpers import time_range, Slippage, Interval
|
|
|
|
# -- Indicators ---------------------------------------------------------------
|
|
mom = ema(roc(close, 14), 6)
|
|
avg_range = (high - low).rolling_mean(14)
|
|
norm_vol = avg_range / (close + mbt.lit(1e-12))
|
|
safe_vol = mbt.when(norm_vol > 0.0005, norm_vol, 0.0005)
|
|
|
|
# -- Strategy -----------------------------------------------------------------
|
|
signal = mbt.when(mom > 0.0, mom / safe_vol, 0.0)
|
|
|
|
strategy = (
|
|
mbt.Strategy.create("per_venue_momentum")
|
|
.signal("momentum", mom)
|
|
.signal("norm_vol", norm_vol)
|
|
.size(signal * 0.01)
|
|
.describe("Multi-asset momentum with per-venue fees")
|
|
)
|
|
|
|
# -- Per-venue fees -----------------------------------------------------------
|
|
# Majors fill on a cheap venue; alts on a pricier one. Symbols absent from
|
|
# `symbol_venue` would fall back to `default`. Keys are symbol names (qualified
|
|
# with the provider), resolved to SymbolIds automatically.
|
|
fees = mbt.FeeConfig.multi_venue(
|
|
default=mbt.VenueFees(maker_fee_bps=2.0, taker_fee_bps=5.0),
|
|
venues={
|
|
"cheap": mbt.VenueFees(maker_fee_bps=1.0, taker_fee_bps=3.0),
|
|
"expensive": mbt.VenueFees(maker_fee_bps=5.0, taker_fee_bps=12.0),
|
|
},
|
|
symbol_venue={
|
|
"binance:BTC-USDT:perp": "cheap",
|
|
"binance:ETH-USDT:perp": "cheap",
|
|
"binance:XRP-USDT:perp": "expensive",
|
|
"binance:DOT-USDT:perp": "expensive",
|
|
},
|
|
)
|
|
|
|
# -- Config -------------------------------------------------------------------
|
|
start, end = time_range("2022-01-01", "2025-01-01")
|
|
|
|
config = mbt.BacktestConfig(
|
|
universe={
|
|
"binance": ["BTC-USDT:perp", "ETH-USDT:perp",
|
|
"XRP-USDT:perp", "DOT-USDT:perp"],
|
|
},
|
|
time_range_start=start,
|
|
time_range_end=end,
|
|
bar_interval=Interval.hours(12),
|
|
initial_capital=10_000,
|
|
execution=mbt.ExecutionConfig(
|
|
signal_delay=1,
|
|
max_position_pct=0.3,
|
|
allow_short=False,
|
|
),
|
|
fees=fees,
|
|
slippage=Slippage.fixed_bps(2),
|
|
warmup_bars=25,
|
|
)
|
|
|
|
# -- Run ----------------------------------------------------------------------
|
|
if __name__ == "__main__":
|
|
root = os.path.join(os.path.dirname(__file__), "..")
|
|
data_root = os.path.abspath(os.path.join(root, "data"))
|
|
store = mbt.DataStore(
|
|
data_root=data_root,
|
|
metadata_db=os.path.abspath(os.path.join(root, "metadata", "metadata.sqlite")),
|
|
arrow_dir=os.path.join(data_root, "mega"),
|
|
)
|
|
|
|
t0 = time.perf_counter()
|
|
result = mbt.run(strategy, config, store)
|
|
elapsed = time.perf_counter() - t0
|
|
|
|
print(result.summary())
|
|
|
|
# Show that fees actually differ by venue: average fee in bps per symbol.
|
|
trades = result.trades
|
|
if trades.num_rows > 0:
|
|
sids = trades.column("symbol_id").to_pylist()
|
|
fee_vals = trades.column("fees").to_pylist()
|
|
qty = trades.column("quantity").to_pylist()
|
|
fill = trades.column("fill_price").to_pylist()
|
|
agg: dict[int, list[float]] = {}
|
|
for sid, f, q, p in zip(sids, fee_vals, qty, fill):
|
|
notional = abs(q) * p
|
|
if notional > 0:
|
|
agg.setdefault(sid, []).append(f / notional * 10_000)
|
|
print("\nRealized fee (bps) by symbol_id:")
|
|
for sid in sorted(agg):
|
|
bps = sum(agg[sid]) / len(agg[sid])
|
|
print(f" symbol {sid}: {bps:.2f} bps ({len(agg[sid])} fills)")
|
|
|
|
print(f"\nElapsed: {elapsed:.3f}s")
|