feat: NautilusTrader + VectorBT unified framework for Hyperliquid
Add complete framework for testing and deploying quant strategies: Framework (framework/): - HyperliquidInstrumentCatalog: loads perps as NT CryptoPerpetual - HyperliquidDataProvider: real candle/orderbook/mark-price data - HyperliquidExecutionProvider: live + PaperExecutionProvider: simulated - BaseHlStrategy: shared NT strategy lifecycle with signal library - StrategyConfig: YAML-based parameter management - DeployOrchestrator: CLI for backtest -> paper -> live pipeline Backtesting (backtests/): - VBTBacktestRunner: VectorBT vectorized backtests on real HL candles - NTBacktestRunner: NautilusTrader event-driven backtest engine NT Strategy ports (strategies/nt/): - PairsTradingNT: BTC/ETH ratio Z-score mean reversion - HurstVPINNT: Hurst exponent regime + VPIN flow imbalance - ASMarketMakingNT: Avellaneda-Stoikov stochastic control MM E2E verified: real HL candles fetch, VectorBT backtest (Sharpe 5.2 on Hurst/VPIN), instrument catalog, deploy CLI --list, strategy signals. Existing live/node.py and paper_trader.py unchanged.
This commit is contained in:
@@ -0,0 +1,68 @@
|
||||
"""
|
||||
Strategy configuration — YAML-based parameter management.
|
||||
|
||||
Each strategy gets a YAML file in config/ with its parameters for
|
||||
backtest, paper, and live environments. The StrategyConfig class
|
||||
loads and validates these configs.
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
import yaml
|
||||
from dataclasses import dataclass, field
|
||||
from pathlib import Path
|
||||
from typing import Any
|
||||
|
||||
|
||||
CONFIG_DIR = Path(__file__).resolve().parent.parent / "config"
|
||||
|
||||
|
||||
@dataclass
|
||||
class StrategyConfig:
|
||||
"""Unified strategy configuration across backtest / paper / live."""
|
||||
|
||||
name: str
|
||||
instrument: str
|
||||
asset: str # Base currency (BTC, ETH, etc.)
|
||||
allocation: float = 10000.0 # Capital allocated
|
||||
order_size: float = 0.001 # Default order size (in base units)
|
||||
maker_fee: float = 0.0002
|
||||
taker_fee: float = 0.0005
|
||||
slippage_bps: float = 1.0
|
||||
testnet: bool = True
|
||||
|
||||
# Signal parameters (strategy-specific)
|
||||
params: dict[str, Any] = field(default_factory=dict)
|
||||
|
||||
# Risk
|
||||
max_position: float = 0.0 # 0 = based on allocation / price
|
||||
max_drawdown: float = 0.10
|
||||
stop_loss_pct: float = 0.0 # 0 = no stop
|
||||
|
||||
# Derived
|
||||
fee_model: str = "taker" # taker or maker
|
||||
|
||||
@classmethod
|
||||
def from_yaml(cls, path: str | Path) -> StrategyConfig:
|
||||
with open(path) as f:
|
||||
data = yaml.safe_load(f)
|
||||
return cls(**data)
|
||||
|
||||
def to_yaml(self, path: str | Path) -> None:
|
||||
with open(path, "w") as f:
|
||||
yaml.safe_dump(self.__dict__, f, default_flow_style=False)
|
||||
|
||||
def effective_fee(self) -> float:
|
||||
return self.maker_fee if self.fee_model == "maker" else self.taker_fee
|
||||
|
||||
@classmethod
|
||||
def load_by_name(cls, name: str, env: str = "paper") -> StrategyConfig:
|
||||
"""Load a strategy config from config/{name}.yaml."""
|
||||
config_path = CONFIG_DIR / f"{name}.yaml"
|
||||
if not config_path.exists():
|
||||
raise FileNotFoundError(f"Config not found: {config_path}")
|
||||
cfg = cls.from_yaml(config_path)
|
||||
if env == "testnet":
|
||||
cfg.testnet = True
|
||||
elif env in ("mainnet", "live"):
|
||||
cfg.testnet = False
|
||||
return cfg
|
||||
Reference in New Issue
Block a user