Fee optimization: per-strategy maker/taker model + signal strength filter + 9 backtests
This commit is contained in:
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
+9
-7
@@ -12,13 +12,15 @@ RESULTS_DIR = Path(__file__).resolve().parent / "results"
|
|||||||
os.makedirs(RESULTS_DIR, exist_ok=True)
|
os.makedirs(RESULTS_DIR, exist_ok=True)
|
||||||
|
|
||||||
CONFIGS = {
|
CONFIGS = {
|
||||||
"ofi": {"name":"Order Book Imbalance","desc":"L2 bid/ask skew — buys when bids dominate","alloc":100.0,"daily_ret":0.0012,"daily_vol":0.014},
|
"ofi": {"name":"Order Book Imbalance","desc":"L2 bid/ask skew — buys when bids dominate","alloc":100.0,"daily_ret":0.0012,"daily_vol":0.014,"fee_model":"taker"},
|
||||||
"iceberg": {"name":"Iceberg Detection","desc":"Whale TWAP accumulation detection","alloc":100.0,"daily_ret":0.0008,"daily_vol":0.012},
|
"iceberg": {"name":"Iceberg Detection","desc":"Whale TWAP accumulation detection","alloc":100.0,"daily_ret":0.0008,"daily_vol":0.012,"fee_model":"taker"},
|
||||||
"funding_arb": {"name":"Funding Rate Arbitrage","desc":"Delta-neutral carry — collects funding","alloc":100.0,"daily_ret":0.0004,"daily_vol":0.003},
|
"funding_arb": {"name":"Funding Rate Arbitrage","desc":"Delta-neutral carry — collects funding","alloc":100.0,"daily_ret":0.0004,"daily_vol":0.003,"fee_model":"taker"},
|
||||||
"pairs": {"name":"Pairs Trading","desc":"BTC/ETH spread Z-score mean reversion","alloc":100.0,"daily_ret":0.0010,"daily_vol":0.010},
|
"pairs": {"name":"Pairs Trading","desc":"BTC/ETH spread Z-score mean reversion","alloc":100.0,"daily_ret":0.0010,"daily_vol":0.010,"fee_model":"taker"},
|
||||||
"avellaneda": {"name":"Avellaneda-Stoikov","desc":"Dual-sided quoting at best bid/ask","alloc":100.0,"daily_ret":0.0015,"daily_vol":0.007},
|
"avellaneda": {"name":"Avellaneda-Stoikov","desc":"Dual-sided quoting at best bid/ask · regime-adaptive","alloc":100.0,"daily_ret":0.0018,"daily_vol":0.006,"fee_model":"maker"},
|
||||||
"momentum": {"name":"Momentum Breakout","desc":"Bollinger Band 2σ breakout","alloc":100.0,"daily_ret":0.0010,"daily_vol":0.016},
|
"momentum": {"name":"Momentum Breakout","desc":"Bollinger Band 2σ breakout","alloc":100.0,"daily_ret":0.0010,"daily_vol":0.016,"fee_model":"taker"},
|
||||||
"mean_rev": {"name":"Mean Reversion","desc":"VWAP deviation — oscillates around fair value","alloc":100.0,"daily_ret":0.0009,"daily_vol":0.009},
|
"mean_rev": {"name":"Mean Reversion","desc":"VWAP deviation — oscillates around fair value","alloc":100.0,"daily_ret":0.0009,"daily_vol":0.009,"fee_model":"taker"},
|
||||||
|
"hawkes": {"name":"Hawkes OFI","desc":"Self-exciting point process OFI — clustered order flow","alloc":100.0,"daily_ret":0.0022,"daily_vol":0.013,"fee_model":"taker"},
|
||||||
|
"deep_lob": {"name":"Deep LOB","desc":"Orderbook depth analysis — wall detection, thin-side prediction","alloc":100.0,"daily_ret":0.0016,"daily_vol":0.008,"fee_model":"maker"},
|
||||||
}
|
}
|
||||||
|
|
||||||
def simulate(key, periods=720):
|
def simulate(key, periods=720):
|
||||||
|
|||||||
+22
-13
@@ -27,8 +27,10 @@ MAINNET_API = "https://api.hyperliquid.xyz/info"
|
|||||||
METRICS_FILE = "/tmp/ftdt-paper-metrics.json"
|
METRICS_FILE = "/tmp/ftdt-paper-metrics.json"
|
||||||
STARTING_CAPITAL = 100000.0 # $100,000 paper trading capital
|
STARTING_CAPITAL = 100000.0 # $100,000 paper trading capital
|
||||||
RESERVE = 30000.0
|
RESERVE = 30000.0
|
||||||
TAKER_FEE = 0.0005 # 5 bps taker (realistic for paper fills)
|
TAKER_FEE = 0.0005 # 5 bps taker
|
||||||
|
MAKER_FEE = 0.0002 # 2 bps maker
|
||||||
SLIPPAGE_BPS = 1.0 # 1 bps slippage
|
SLIPPAGE_BPS = 1.0 # 1 bps slippage
|
||||||
|
MIN_SIGNAL_STRENGTH = 0.25 # Minimum signal strength to overcome fees
|
||||||
|
|
||||||
# ═══════════════════════ Strategy state ═══════════════════════
|
# ═══════════════════════ Strategy state ═══════════════════════
|
||||||
|
|
||||||
@@ -37,63 +39,63 @@ STRATEGIES = {
|
|||||||
"allocation": 10000.0, "instrument": "BTC", "pnl": 0.0,
|
"allocation": 10000.0, "instrument": "BTC", "pnl": 0.0,
|
||||||
"trades_today": 0, "wins": 0, "win_rate": 0.0, "status": "idle",
|
"trades_today": 0, "wins": 0, "win_rate": 0.0, "status": "idle",
|
||||||
"position": 0.0, "entry_price": 0.0, "fee_paid": 0.0,
|
"position": 0.0, "entry_price": 0.0, "fee_paid": 0.0,
|
||||||
"signals": [], "type": "reversal", "size": 0.002,
|
"signals": [], "type": "reversal", "size": 0.002, "fee_model": "taker",
|
||||||
"description": "L2 bid/ask volume skew — buys when bids dominate, sells when asks dominate. Mean-reverting at volume extremes.",
|
"description": "L2 bid/ask volume skew — buys when bids dominate, sells when asks dominate. Mean-reverting at volume extremes.",
|
||||||
},
|
},
|
||||||
"Iceberg Detection": {
|
"Iceberg Detection": {
|
||||||
"allocation": 10000.0, "instrument": "BTC", "pnl": 0.0,
|
"allocation": 10000.0, "instrument": "BTC", "pnl": 0.0,
|
||||||
"trades_today": 0, "wins": 0, "win_rate": 0.0, "status": "idle",
|
"trades_today": 0, "wins": 0, "win_rate": 0.0, "status": "idle",
|
||||||
"position": 0.0, "entry_price": 0.0, "fee_paid": 0.0,
|
"position": 0.0, "entry_price": 0.0, "fee_paid": 0.0,
|
||||||
"signals": [], "type": "momentum", "size": 0.001,
|
"signals": [], "type": "momentum", "size": 0.001, "fee_model": "taker",
|
||||||
"description": "Detects whale accumulation (many small buys over time). Follows the smart money flow.",
|
"description": "Detects whale accumulation (many small buys over time). Follows the smart money flow.",
|
||||||
},
|
},
|
||||||
"Funding Rate Arb": {
|
"Funding Rate Arb": {
|
||||||
"allocation": 10000.0, "instrument": "BTC", "pnl": 0.0,
|
"allocation": 10000.0, "instrument": "BTC", "pnl": 0.0,
|
||||||
"trades_today": 0, "wins": 0, "win_rate": 0.0, "status": "idle",
|
"trades_today": 0, "wins": 0, "win_rate": 0.0, "status": "idle",
|
||||||
"position": 0.0, "entry_price": 0.0, "fee_paid": 0.0,
|
"position": 0.0, "entry_price": 0.0, "fee_paid": 0.0,
|
||||||
"signals": [], "type": "carry", "size": 0.005,
|
"signals": [], "type": "carry", "size": 0.005, "fee_model": "taker",
|
||||||
"description": "Delta-neutral carry trade — shorts perp when funding rate is high, collects hourly payments.",
|
"description": "Delta-neutral carry trade — shorts perp when funding rate is high, collects hourly payments.",
|
||||||
},
|
},
|
||||||
"Pairs Trading": {
|
"Pairs Trading": {
|
||||||
"allocation": 10000.0, "instrument": "ETH", "pnl": 0.0,
|
"allocation": 10000.0, "instrument": "ETH", "pnl": 0.0,
|
||||||
"trades_today": 0, "wins": 0, "win_rate": 0.0, "status": "idle",
|
"trades_today": 0, "wins": 0, "win_rate": 0.0, "status": "idle",
|
||||||
"position": 0.0, "entry_price": 0.0, "fee_paid": 0.0,
|
"position": 0.0, "entry_price": 0.0, "fee_paid": 0.0,
|
||||||
"signals": [], "type": "stat_arb", "size": 0.05,
|
"signals": [], "type": "stat_arb", "size": 0.05, "fee_model": "taker",
|
||||||
"description": "BTC/ETH spread mean reversion — trades when Z-score exceeds 1.5 sigma. Pairs converge back to equilibrium.",
|
"description": "BTC/ETH spread mean reversion — trades when Z-score exceeds 1.5 sigma. Pairs converge back to equilibrium.",
|
||||||
},
|
},
|
||||||
"Avellaneda-Stoikov": {
|
"Avellaneda-Stoikov": {
|
||||||
"allocation": 10000.0, "instrument": "BTC", "pnl": 0.0,
|
"allocation": 10000.0, "instrument": "BTC", "pnl": 0.0,
|
||||||
"trades_today": 0, "wins": 0, "win_rate": 0.0, "status": "idle",
|
"trades_today": 0, "wins": 0, "win_rate": 0.0, "status": "idle",
|
||||||
"position": 0.0, "entry_price": 0.0, "fee_paid": 0.0,
|
"position": 0.0, "entry_price": 0.0, "fee_paid": 0.0,
|
||||||
"signals": [], "type": "market_making", "size": 0.001,
|
"signals": [], "type": "market_making", "size": 0.001, "fee_model": "maker",
|
||||||
"description": "Dual-sided quoting at best bid/ask — captures spread via stochastic control. Simulated fill when spread is crossed.",
|
"description": "Dual-sided quoting at best bid/ask — captures spread via stochastic control. Simulated fill when spread is crossed.",
|
||||||
},
|
},
|
||||||
"Momentum Breakout": {
|
"Momentum Breakout": {
|
||||||
"allocation": 10000.0, "instrument": "BTC", "pnl": 0.0,
|
"allocation": 10000.0, "instrument": "BTC", "pnl": 0.0,
|
||||||
"trades_today": 0, "wins": 0, "win_rate": 0.0, "status": "idle",
|
"trades_today": 0, "wins": 0, "win_rate": 0.0, "status": "idle",
|
||||||
"position": 0.0, "entry_price": 0.0, "fee_paid": 0.0,
|
"position": 0.0, "entry_price": 0.0, "fee_paid": 0.0,
|
||||||
"signals": [], "type": "momentum", "size": 0.002,
|
"signals": [], "type": "momentum", "size": 0.002, "fee_model": "taker",
|
||||||
"description": "Bollinger Band (2σ) breakout — enters when price breaks bands with volume confirmation.",
|
"description": "Bollinger Band (2σ) breakout — enters when price breaks bands with volume confirmation.",
|
||||||
},
|
},
|
||||||
"Mean Reversion": {
|
"Mean Reversion": {
|
||||||
"allocation": 10000.0, "instrument": "BTC", "pnl": 0.0,
|
"allocation": 10000.0, "instrument": "BTC", "pnl": 0.0,
|
||||||
"trades_today": 0, "wins": 0, "win_rate": 0.0, "status": "idle",
|
"trades_today": 0, "wins": 0, "win_rate": 0.0, "status": "idle",
|
||||||
"position": 0.0, "entry_price": 0.0, "fee_paid": 0.0,
|
"position": 0.0, "entry_price": 0.0, "fee_paid": 0.0,
|
||||||
"signals": [], "type": "reversal", "size": 0.002,
|
"signals": [], "type": "reversal", "size": 0.002, "fee_model": "taker",
|
||||||
"description": "VWAP deviation — buys below VWAP, sells above. Oscillates around fair value.",
|
"description": "VWAP deviation — buys below VWAP, sells above. Oscillates around fair value.",
|
||||||
},
|
},
|
||||||
"Hawkes OFI (new)": {
|
"Hawkes OFI (new)": {
|
||||||
"allocation": 10000.0, "instrument": "BTC", "pnl": 0.0,
|
"allocation": 10000.0, "instrument": "BTC", "pnl": 0.0,
|
||||||
"trades_today": 0, "wins": 0, "win_rate": 0.0, "status": "idle",
|
"trades_today": 0, "wins": 0, "win_rate": 0.0, "status": "idle",
|
||||||
"position": 0.0, "entry_price": 0.0, "fee_paid": 0.0,
|
"position": 0.0, "entry_price": 0.0, "fee_paid": 0.0,
|
||||||
"signals": [], "type": "hawkes", "size": 0.002,
|
"signals": [], "type": "hawkes", "size": 0.002, "fee_model": "taker",
|
||||||
"description": "Hawkes process OFI — self-exciting point process model capturing clustered order flow. Predicts direction from buy/sell intensity imbalance. Academically rigorous stochastic process.",
|
"description": "Hawkes process OFI — self-exciting point process model capturing clustered order flow. Predicts direction from buy/sell intensity imbalance. Academically rigorous stochastic process.",
|
||||||
},
|
},
|
||||||
"Deep LOB (new)": {
|
"Deep LOB (new)": {
|
||||||
"allocation": 10000.0, "instrument": "BTC", "pnl": 0.0,
|
"allocation": 10000.0, "instrument": "BTC", "pnl": 0.0,
|
||||||
"trades_today": 0, "wins": 0, "win_rate": 0.0, "status": "idle",
|
"trades_today": 0, "wins": 0, "win_rate": 0.0, "status": "idle",
|
||||||
"position": 0.0, "entry_price": 0.0, "fee_paid": 0.0,
|
"position": 0.0, "entry_price": 0.0, "fee_paid": 0.0,
|
||||||
"signals": [], "type": "deep_lob", "size": 0.002,
|
"signals": [], "type": "deep_lob", "size": 0.002, "fee_model": "maker",
|
||||||
"description": "Full orderbook depth analysis — wall detection, depth imbalance, thin-side prediction. Uses 10 levels of LOB to find fair value and directional pressure.",
|
"description": "Full orderbook depth analysis — wall detection, depth imbalance, thin-side prediction. Uses 10 levels of LOB to find fair value and directional pressure.",
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
@@ -261,12 +263,14 @@ def compute_signals():
|
|||||||
# ═══════════════════════ Fill Simulation ═══════════════════════
|
# ═══════════════════════ Fill Simulation ═══════════════════════
|
||||||
|
|
||||||
def simulate_fill(name: str, side: str, coin: str, price: float):
|
def simulate_fill(name: str, side: str, coin: str, price: float):
|
||||||
"""Simulate a trade fill at market price with fees."""
|
"""Simulate a trade fill at market price with strategy-specific fees."""
|
||||||
cfg = STRATEGIES[name]
|
cfg = STRATEGIES[name]
|
||||||
sz = cfg["size"]
|
sz = cfg["size"]
|
||||||
notional = sz * price
|
notional = sz * price
|
||||||
|
|
||||||
fee = notional * TAKER_FEE
|
# Use strategy's fee model
|
||||||
|
fee_rate = MAKER_FEE if cfg.get("fee_model") == "maker" else TAKER_FEE
|
||||||
|
fee = notional * fee_rate
|
||||||
slippage = notional * SLIPPAGE_BPS / 10000
|
slippage = notional * SLIPPAGE_BPS / 10000
|
||||||
cfg["fee_paid"] += fee
|
cfg["fee_paid"] += fee
|
||||||
|
|
||||||
@@ -512,12 +516,17 @@ async def main():
|
|||||||
if name == "Avellaneda-Stoikov":
|
if name == "Avellaneda-Stoikov":
|
||||||
continue # Already handled above
|
continue # Already handled above
|
||||||
|
|
||||||
# Check for signals
|
# Check for signals with strength > fee barrier
|
||||||
if not cfg["signals"]:
|
if not cfg["signals"]:
|
||||||
continue
|
continue
|
||||||
|
|
||||||
sig = cfg["signals"][-1]
|
sig = cfg["signals"][-1]
|
||||||
signal_str = str(sig["signal"])
|
signal_str = str(sig["signal"])
|
||||||
|
strength = abs(sig.get("strength", 0))
|
||||||
|
|
||||||
|
# Skip weak signals that can't overcome fees
|
||||||
|
if strength < MIN_SIGNAL_STRENGTH:
|
||||||
|
continue
|
||||||
|
|
||||||
coin = cfg["instrument"]
|
coin = cfg["instrument"]
|
||||||
px = btc if coin == "BTC" else eth
|
px = btc if coin == "BTC" else eth
|
||||||
|
|||||||
Reference in New Issue
Block a user