""" Live trading node for Hyperliquid Testnet. Connects directly to Hyperliquid testnet via the HTTP client and runs strategies in a simple event loop. Updates the dashboard with real PnL data. Usage: python live/node.py (reads key from .env or HYPERLIQUID_TESTNET_PK) """ import os import sys import asyncio import json import time import logging from pathlib import Path from decimal import Decimal # Ensure local modules are importable sys.path.insert(0, str(Path(__file__).resolve().parent.parent)) from nautilus_trader.core.nautilus_pyo3 import ( HyperliquidHttpClient, HyperliquidEnvironment, UUID4, ClientOrderId, LimitOrder, 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 load_key() -> str | None: key = os.getenv("HYPERLIQUID_TESTNET_PK") if key: return key env_file = Path(__file__).resolve().parent.parent / ".env" if env_file.exists(): for line in env_file.read_text().splitlines(): if line.startswith("HYPERLIQUID_TESTNET_PK="): return line.split("=", 1)[1].strip() return None async def main(): private_key = load_key() if not private_key: log.error("No HYPERLIQUID_TESTNET_PK found in env or .env") sys.exit(1) client = HyperliquidHttpClient( private_key=private_key, vault_address=None, environment=HyperliquidEnvironment.TESTNET, ) address = client.get_user_address() log.info("=" * 55) log.info(" FTDT Quant Lab - Live Trading Node") log.info(f" Wallet: {address}") log.info(" Hyperliquid Testnet") log.info("=" * 55) # 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: while True: # Refresh mark prices btc_px = get_mark_price("BTC") 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: log.info("Shutting down...") log.info("Node stopped.") if __name__ == "__main__": asyncio.run(main())