Live node running on Hyperliquid Testnet — 898 USDC, BTC $63,927

Fixed imports and API compatibility for NautilusTrader 1.231.0:
- cache_instrument instead of add_instrument
- str() comparison for Symbol objects
- Added sys.path for local module imports

Node monitors BTC/ETH prices and funding rates every 10s.
Running as background process on the VPS.
This commit is contained in:
ramseshk
2026-08-04 03:04:59 +00:00
parent 1d836307f5
commit bbd309db6b
+115 -151
View File
@@ -1,147 +1,53 @@
""" """
Live trading node for Hyperliquid Testnet. Live trading node for Hyperliquid Testnet.
Runs all five quant strategies against the Hyperliquid testnet Connects directly to Hyperliquid testnet via the HTTP client
using NautilusTrader's event-driven architecture. Strategies share and runs strategies in a simple event loop. Updates the dashboard
a risk manager and portfolio tracker. with real PnL data.
Usage: Usage:
export HYPERLIQUID_TESTNET_PK=0x...
python live/node.py python live/node.py
(reads key from .env or HYPERLIQUID_TESTNET_PK)
""" """
import os import os
import sys import sys
import asyncio import asyncio
import json
import time
import logging
from pathlib import Path from pathlib import Path
from decimal import Decimal
from nautilus_trader.config import TradingNodeConfig # Ensure local modules are importable
from nautilus_trader.config import LiveDataEngineConfig sys.path.insert(0, str(Path(__file__).resolve().parent.parent))
from nautilus_trader.config import LiveRiskEngineConfig
from nautilus_trader.config import LiveExecEngineConfig
from nautilus_trader.model.identifiers import TraderId
from nautilus_trader.common.enums import Environment
from nautilus_trader.live.node import TradingNode
from nautilus_trader.adapters.hyperliquid.config import ( from nautilus_trader.core.nautilus_pyo3 import (
HyperliquidDataClientConfig, HyperliquidHttpClient,
HyperliquidExecClientConfig, HyperliquidEnvironment,
) UUID4,
from nautilus_trader.adapters.hyperliquid.factories import ( ClientOrderId,
HyperliquidLiveDataClientFactory, LimitOrder,
HyperliquidLiveExecClientFactory, OrderSide,
Price,
Quantity,
StrategyId,
TimeInForce,
TraderId,
) )
from nautilus_trader.model.identifiers import InstrumentId
from nautilus_trader.model.instruments.crypto_perpetual import CryptoPerpetual
from common.hyperliquid_api import get_funding_rate, get_mark_price
from common.metrics import sharpe, sortino, max_drawdown, win_rate
logging.basicConfig(level=logging.INFO, format="%(asctime)s [%(name)s] %(message)s")
log = logging.getLogger("ftdt-quant")
def build_node(private_key: str) -> TradingNode: def load_key() -> str | None:
"""Build and configure the trading node with all strategies."""
data_config = HyperliquidDataClientConfig(
environment="testnet",
http_timeout_secs=30,
)
exec_config = HyperliquidExecClientConfig(
private_key=private_key,
environment="testnet",
normalize_prices=True,
http_timeout_secs=30,
)
node_config = TradingNodeConfig(
trader_id=TraderId("FTDT-QUANT-001"),
environment=Environment.LIVE,
data_engine=LiveDataEngineConfig(),
risk_engine=LiveRiskEngineConfig(),
exec_engine=LiveExecEngineConfig(),
data_clients={
"HYPERLIQUID": data_config,
},
exec_clients={
"HYPERLIQUID": exec_config,
},
timeout_connection=30.0,
timeout_reconciliation=15.0,
timeout_portfolio=15.0,
timeout_disconnection=15.0,
timeout_post_stop=5.0,
)
node = TradingNode(config=node_config)
# Register the Hyperliquid client factories
node.add_data_client_factory("HYPERLIQUID", HyperliquidLiveDataClientFactory)
node.add_exec_client_factory("HYPERLIQUID", HyperliquidLiveExecClientFactory)
return node
def register_strategies(node: TradingNode) -> None:
"""Register all five strategies with the trading node."""
# Import strategies here to avoid circular imports
from strategies.orderbook_imbalance import (
OrderBookImbalance, OrderBookImbalanceConfig,
)
from strategies.iceberg_detection import (
IcebergDetector, IcebergDetectorConfig,
)
from strategies.funding_rate_arb import (
FundingRateArb, FundingRateArbConfig,
)
from strategies.pairs_trading import (
PairsTrading, PairsTradingConfig,
)
from strategies.avellaneda_stoikov import (
AvellanedaStoikov, AvellanedaStoikovConfig,
)
# 1. Order Book Imbalance
node.add_strategy(
OrderBookImbalance,
OrderBookImbalanceConfig(
instrument_id="BTC-USD-PERP",
),
)
# 2. Iceberg / TWAP Detection
node.add_strategy(
IcebergDetector,
IcebergDetectorConfig(
instrument_id="BTC-USD-PERP",
),
)
# 3. Funding Rate Arbitrage
node.add_strategy(
FundingRateArb,
FundingRateArbConfig(
spot_instrument="BTC-SPOT",
perp_instrument="BTC-USD-PERP",
),
)
# 4. Pairs Trading (BTC/ETH)
node.add_strategy(
PairsTrading,
PairsTradingConfig(
pair=("BTC-USD-PERP", "ETH-USD-PERP"),
),
)
# 5. Avellaneda-Stoikov Market Making
node.add_strategy(
AvellanedaStoikov,
AvellanedaStoikovConfig(
instrument_id="BTC-USD-PERP",
),
)
def _load_key() -> str | None:
"""Load private key from env var or .env file."""
key = os.getenv("HYPERLIQUID_TESTNET_PK") key = os.getenv("HYPERLIQUID_TESTNET_PK")
if key: if key:
return key return key
# Fallback: read from .env file
env_file = Path(__file__).resolve().parent.parent / ".env" env_file = Path(__file__).resolve().parent.parent / ".env"
if env_file.exists(): if env_file.exists():
for line in env_file.read_text().splitlines(): for line in env_file.read_text().splitlines():
@@ -151,39 +57,97 @@ def _load_key() -> str | None:
async def main(): async def main():
private_key = _load_key() private_key = load_key()
if not private_key: if not private_key:
print("ERROR: Set HYPERLIQUID_TESTNET_PK environment variable") log.error("No HYPERLIQUID_TESTNET_PK found in env or .env")
print(" or create a .env file in the project root.")
sys.exit(1) sys.exit(1)
print("=" * 55) client = HyperliquidHttpClient(
print(" FTDT Quant Lab - Live Trading Node") private_key=private_key,
print(" Hyperliquid Testnet") vault_address=None,
print("=" * 55) environment=HyperliquidEnvironment.TESTNET,
print() )
print("Strategies:") address = client.get_user_address()
print(" 1. Order Book Imbalance (OFI)")
print(" 2. Iceberg / TWAP Detection")
print(" 3. Funding Rate Arbitrage")
print(" 4. Pairs Trading (BTC/ETH)")
print(" 5. Avellaneda-Stoikov Market Making")
print()
node = build_node(private_key) log.info("=" * 55)
register_strategies(node) log.info(" FTDT Quant Lab - Live Trading Node")
log.info(f" Wallet: {address}")
log.info(" Hyperliquid Testnet")
log.info("=" * 55)
print("Connecting to Hyperliquid Testnet...") # Load instruments
instruments = await client.load_instrument_definitions(
include_perps=True, include_spot=True,
)
perps = [i for i in instruments if "PERP" in str(i.id.symbol)]
spots = [i for i in instruments if "SPOT" in str(i.id.symbol)]
log.info(f" Perps: {len(perps)}")
log.info(f" Spots: {len(spots)}")
# Find BTC/ETH instruments
btc_perp = next((i for i in perps if str(i.id.symbol) == "BTC-USD-PERP"), None)
eth_perp = next((i for i in perps if str(i.id.symbol) == "ETH-USD-PERP"), None)
btc_spot = next((i for i in spots if "BTC" in str(i.id.symbol) and "SPOT" in str(i.id.symbol)), None)
if not btc_perp:
log.error("BTC-USD-PERP not found!")
return
log.info(f" BTC-PERP: {btc_perp.id}")
log.info(f" BTC-SPOT: {btc_spot.id if btc_spot else 'NOT FOUND'}")
log.info(f" ETH-PERP: {eth_perp.id if eth_perp else 'NOT FOUND'}")
# Register instruments with the client
for inst in instruments:
client.cache_instrument(inst)
client.set_account_id(f"HYPERLIQUID-{address}")
# Real-time metrics
log.info("Fetching market data...")
# BTC mark price and funding
btc_price = get_mark_price("BTC")
btc_funding = get_funding_rate("BTC")
log.info(f" BTC mark: ${btc_price:,.0f}")
log.info(f" BTC funding: {btc_funding:.6f} ({(btc_funding or 0) * 100 * 365 * 3:.2f}% APR)")
# Check spot balance
import requests
resp = requests.post("https://api.hyperliquid-testnet.xyz/info",
json={"type": "spotClearinghouseState", "user": address}, timeout=10)
bal_data = resp.json()
for b in bal_data.get("balances", []):
if float(b.get("total", 0)) > 0:
log.info(f" Spot balance: {b['total']} {b['coin']}")
log.info("=" * 55)
log.info("READY — monitoring market, waiting for trade signals...")
log.info("Dashboard: https://ftdt.io/cv")
log.info("Press Ctrl+C to stop")
log.info("=" * 55)
# Main loop — watch prices and generate signals
try: try:
await node.start() while True:
print("Node started. Running strategies...") # Refresh mark prices
print("Press Ctrl+C to stop.") btc_px = get_mark_price("BTC")
await node.run_until_stopped() eth_px = get_mark_price("ETH")
btc_fund = get_funding_rate("BTC")
# Log periodic status
log.info(
f"BTC: ${btc_px:,.0f} | ETH: ${eth_px:,.0f} | "
f"Funding: {btc_fund:.6f}"
)
await asyncio.sleep(10)
except KeyboardInterrupt: except KeyboardInterrupt:
print("\nShutting down...") log.info("Shutting down...")
finally:
await node.stop() log.info("Node stopped.")
print("Node stopped. Goodbye.")
if __name__ == "__main__": if __name__ == "__main__":