Comprehensive fix: live node resilience + CSS contrast + win_rate + equity curves
- Mainnet API fallback when testnet unavailable (prices, orderbook, instruments) - Bypassed broken SDK instrument loading, uses raw mainnet meta API - Dynamic BTC/ETH perp ID lookup (handles "-USD-PERP" suffix changes) - Strategy-level equity tracking for per-strategy detail charts - Win rate fixed: checks pnl_net/pnl_gross not just pnl field - CSS contrast improved: --tx #6b6b7b→#9e9eae, borders/highlights brightened - Equity curve recalculated on fee tier change (chart adjusts visually) - Added Open Positions & Orders panel placeholder
This commit is contained in:
+58
-6
@@ -26,6 +26,7 @@ log = logging.getLogger("ftdt-quant")
|
||||
|
||||
METRICS_FILE = "/tmp/ftdt-metrics.json"
|
||||
TESTNET_API = "https://api.hyperliquid-testnet.xyz/info"
|
||||
MAINNET_INFO = "https://api.hyperliquid.xyz/info"
|
||||
TOTAL_EQUITY = 898.0
|
||||
RESERVE = 398.0
|
||||
MAKER_FEE = 0.0002
|
||||
@@ -42,6 +43,7 @@ STRATEGIES = {
|
||||
|
||||
trades_log: list[dict] = []
|
||||
equity_history: list[dict] = []
|
||||
strategy_equity: dict[str, list] = {}
|
||||
seen_fills: set[int] = set()
|
||||
btc_prices: deque = deque(maxlen=60)
|
||||
eth_prices: deque = deque(maxlen=60)
|
||||
@@ -97,7 +99,9 @@ def write_metrics(addr):
|
||||
"total_equity":TOTAL_EQUITY+total_pnl,"base_equity":TOTAL_EQUITY,
|
||||
"total_pnl":total_pnl,"total_pnl_pct":total_pnl_pct,
|
||||
"reserve":RESERVE,"equity_history":equity_history[-600:],
|
||||
"strategies":STRATEGIES,"trades":trades_log[-200:],"status":"running"
|
||||
"strategies":STRATEGIES,"trades":trades_log[-200:],"status":"running",
|
||||
"strategy_equity":{k: v[-600:] for k,v in strategy_equity.items()},
|
||||
"open_positions":[],"open_orders":[]
|
||||
}
|
||||
try:
|
||||
with open(METRICS_FILE,"w") as f: json.dump(data,f,default=str)
|
||||
@@ -168,10 +172,48 @@ async def main():
|
||||
addr = client.get_user_address()
|
||||
client.set_account_id("HYPERLIQUID-"+addr)
|
||||
|
||||
insts = await client.load_instrument_definitions(include_perps=True)
|
||||
perps = {str(i.id.symbol): i for i in insts if "PERP" in str(i.id.symbol)}
|
||||
for inst in perps.values(): client.cache_instrument(inst)
|
||||
btc_perp = perps["BTC-USD-PERP"]; eth_perp = perps["ETH-USD-PERP"]
|
||||
# Load instrument definitions — try testnet SDK first, fallback to raw APIs
|
||||
insts = []; perps = {}
|
||||
try:
|
||||
insts = await client.load_instrument_definitions(include_perps=True)
|
||||
perps = {str(i.id.symbol): i for i in insts if "PERP" in str(i.id.symbol)}
|
||||
for inst in perps.values(): client.cache_instrument(inst)
|
||||
except Exception as e:
|
||||
log.warning(f"SDK instrument load failed: {e}")
|
||||
# Fallback: load from raw API (mainnet) if SDK failed or returned empty
|
||||
if not perps:
|
||||
log.info("Loading perps from mainnet API directly...")
|
||||
try:
|
||||
meta_r = requests.post(MAINNET_INFO, json={"type":"meta"}, timeout=10)
|
||||
meta = meta_r.json()
|
||||
for asset in meta.get("universe", []):
|
||||
name = asset.get("name", "")
|
||||
if name:
|
||||
# Build a minimal perp-like object for our purposes
|
||||
perps[name] = type('Perp', (), {
|
||||
'id': type('ID', (), {'symbol': name})(),
|
||||
'base': name,
|
||||
'quote': 'USD',
|
||||
})()
|
||||
log.info(f"Loaded {len(perps)} perps from mainnet meta")
|
||||
except Exception as e:
|
||||
log.error(f"Mainnet meta fallback failed: {e}")
|
||||
if perps:
|
||||
log.info(f"Perps available: {list(perps.keys())[:10]}...")
|
||||
else:
|
||||
log.error("No perps loaded — cannot continue")
|
||||
sys.exit(1)
|
||||
# Find BTC/ETH perps dynamically (testnet IDs may differ from mainnet)
|
||||
btc_perp = None; eth_perp = None
|
||||
for k, v in perps.items():
|
||||
ku = k.upper()
|
||||
if btc_perp is None and ("BTC" in ku):
|
||||
btc_perp = v
|
||||
if eth_perp is None and ("ETH" in ku):
|
||||
eth_perp = v
|
||||
if not btc_perp or not eth_perp:
|
||||
log.error(f"Could not find BTC/ETH perps. Available: {list(perps.keys())[:10]}")
|
||||
sys.exit(1)
|
||||
|
||||
prices = get_mark_prices()
|
||||
btc_bid, btc_ask, btc_mid = get_orderbook("BTC")
|
||||
@@ -201,6 +243,7 @@ async def main():
|
||||
log.info(f"Tracking {len(seen_fills)} existing fills")
|
||||
|
||||
for s in STRATEGIES.values(): s["status"]="running"
|
||||
for name in STRATEGIES: strategy_equity[name]=[]
|
||||
write_metrics(addr)
|
||||
|
||||
tick=0; names=list(STRATEGIES.keys()); idx=0
|
||||
@@ -233,6 +276,7 @@ async def main():
|
||||
STRATEGIES[strat]["fee_paid"]+=abs(fee)
|
||||
if closed_pnl>0: STRATEGIES[strat]["wins"]+=1
|
||||
STRATEGIES[strat]["pnl_pct"]=STRATEGIES[strat]["pnl"]/STRATEGIES[strat]["allocation"]*100
|
||||
strategy_equity[strat].append({"t":time.time(),"v":STRATEGIES[strat]["allocation"]+STRATEGIES[strat]["pnl"]})
|
||||
trades_log.append({"time":datetime.now().strftime("%H:%M:%S"),"strategy":strat,"side":"BUY" if side=="B" else "SELL","size":sz,"price":px,"pnl":round(net,4),"fee":round(abs(fee),4)})
|
||||
new_fills+=1
|
||||
|
||||
@@ -242,7 +286,15 @@ async def main():
|
||||
# Place/refresh orders every 3-5 ticks
|
||||
if tick>=3 and tick%random.randint(3,5)==0:
|
||||
btc_bid, btc_ask, btc_mid = get_orderbook("BTC")
|
||||
eth_bid, eth_ask, eth_mid = get_orderbook("ETH")
|
||||
try:
|
||||
btc_bid, btc_ask, btc_mid = get_orderbook("BTC")
|
||||
except Exception as e:
|
||||
log.debug(f"OB BTC error: {e}")
|
||||
btc_bid = btc_ask = btc_mid = 0
|
||||
try:
|
||||
eth_bid, eth_ask, eth_mid = get_orderbook("ETH")
|
||||
except Exception as e:
|
||||
eth_bid = eth_ask = eth_mid = 0
|
||||
|
||||
name = names[idx%7]; idx+=1; cfg=STRATEGIES[name]
|
||||
coin="BTC" if "BTC" in cfg["instrument"] else "ETH"
|
||||
|
||||
Reference in New Issue
Block a user