2Branches0Tags
GL
glucryptoSync production with master template updates
dc969c112 days ago54Commits
typescript
import { wallet } from "opentool/wallet"; import type { WalletFullContext } from "opentool/wallet"; import { extractHyperliquidOrderIds, fetchHyperliquidClearinghouseState, fetchHyperliquidPerpMarketInfo, fetchHyperliquidSpotClearinghouseState, fetchHyperliquidSpotMarketInfo, fetchHyperliquidSpotTickSize, fetchHyperliquidTickSize, placeHyperliquidOrder, readHyperliquidPerpPosition, readHyperliquidSpotBalance, resolveHyperliquidChainConfig, resolveHyperliquidErrorDetail, resolveHyperliquidPerpSymbol, resolveHyperliquidSpotSymbol, updateHyperliquidLeverage, type HyperliquidEnvironment, } from "opentool/adapters/hyperliquid"; import type { DeltaNeutralConfig } from "../config"; import { buildResolvedOrders, planDeltaNeutralCycle } from "./planner"; import { resolveDeltaNeutralLegAssets } from "./schedule"; import type { DeltaNeutralMetrics, DeltaNeutralResult, PlannedOrder } from "./types"; function buildEmptyMetrics(): DeltaNeutralMetrics { return { spotValueUsd: 0, perpNotionalUsd: 0, deltaUsd: 0, deltaPct: null, basisBps: null, fundingRateBps: null, spotBalance: 0, perpSize: 0, spotEntryNtl: null, perpUnrealizedPnl: null, spotUnrealizedPnl: null, }; } export async function runDeltaNeutral(config: DeltaNeutralConfig): Promise<DeltaNeutralResult> { const environment = (config.environment ?? "mainnet") as HyperliquidEnvironment; const { spotAsset, perpAsset } = resolveDeltaNeutralLegAssets(config); const perpSymbol = resolveHyperliquidPerpSymbol(perpAsset); const spot = resolveHyperliquidSpotSymbol(spotAsset); const slippageBps = Math.max(0, config.slippageBps); const maxPerRunUsd = Math.max(0, config.maxPerRunUsd); const chain = resolveHyperliquidChainConfig(environment).chain; const ctx = await wallet({ chain }); const walletAddress = ctx.address as string; const [perpMarket, spotMarket, perpClearing, spotClearing] = await Promise.all([ fetchHyperliquidPerpMarketInfo({ environment, symbol: perpSymbol }), fetchHyperliquidSpotMarketInfo({ environment, base: spot.base, quote: spot.quote }), fetchHyperliquidClearinghouseState({ environment, walletAddress: ctx.address as `0x${string}`, }), fetchHyperliquidSpotClearinghouseState({ environment, user: ctx.address as `0x${string}`, }), ]); const [perpTick, spotTick] = await Promise.all([ fetchHyperliquidTickSize({ environment, symbol: perpSymbol }), fetchHyperliquidSpotTickSize({ environment, marketIndex: spotMarket.marketIndex, }), ]); if (!perpClearing.ok || !perpClearing.data) { return { ok: false, action: "delta-neutral-hold", environment, walletAddress, asset: spotAsset, spotAsset, perpAsset, perpSymbol, spotSymbol: spot.symbol, metrics: buildEmptyMetrics(), plannedOrders: [], executedOrders: [], orderResponses: [], error: "Failed to load Hyperliquid perp state.", errorDetail: perpClearing.data, }; } const perpPosition = readHyperliquidPerpPosition(perpClearing.data, perpSymbol); const spotPosition = readHyperliquidSpotBalance(spotClearing, spotMarket.base); const plan = planDeltaNeutralCycle({ config, spotPrice: spotMarket.price, perpPrice: perpMarket.price, fundingRateBps: perpMarket.fundingRate != null ? perpMarket.fundingRate * 10_000 : null, spotBalance: spotPosition.total, spotEntryNtl: spotPosition.entryNtl, perpSize: perpPosition.size, perpUnrealizedPnl: perpPosition.unrealizedPnl, maxPerRunUsd, }); const plannedOrders = buildResolvedOrders({ slippageBps, perpSymbol, spotSymbol: spot.symbol, perpPrice: perpMarket.price, spotPrice: spotMarket.price, perpSzDecimals: perpMarket.szDecimals, spotSzDecimals: spotMarket.szDecimals, perpTick, spotTick, currentPerpNotionalUsd: plan.metrics.perpNotionalUsd, plan, }).map((order) => ({ kind: order.kind, symbol: order.symbol, side: order.side, size: order.size, price: order.price, reduceOnly: order.reduceOnly, notionalUsd: order.notionalUsd, })) satisfies PlannedOrder[]; if (plannedOrders.length === 0) { return { ok: true, action: "delta-neutral-hold", environment, walletAddress, asset: spotAsset, spotAsset, perpAsset, perpSymbol, spotSymbol: spot.symbol, metrics: plan.metrics, plannedOrders, executedOrders: [], orderResponses: [], ...(plan.reason ? { skipped: true, reason: plan.reason } : {}), ...(!plan.reason ? { skipped: true, reason: "order-too-small" } : {}), }; } const executionOrder = plan.action === "delta-neutral-open" ? ["spot", "perp"] : ["perp", "spot"]; const executedOrders: PlannedOrder[] = []; const orderResponses: DeltaNeutralResult["orderResponses"] = []; try { const perpOrder = plannedOrders.find((entry) => entry.kind === "perp"); if (perpOrder) { await updateHyperliquidLeverage({ wallet: ctx as WalletFullContext, environment, input: { symbol: perpSymbol, leverageMode: config.perpLeverageMode ?? "cross", leverage: Number.isFinite(config.perpLeverage) && config.perpLeverage > 0 ? config.perpLeverage : 1, }, }); } for (const kind of executionOrder) { const order = plannedOrders.find((entry) => entry.kind === kind); if (!order) continue; const response = await placeHyperliquidOrder({ wallet: ctx as WalletFullContext, environment, orders: [ { symbol: order.symbol, side: order.side, price: order.price, size: order.size, tif: "FrontendMarket", reduceOnly: order.reduceOnly, }, ], }); orderResponses.push(response); executedOrders.push(order); } } catch (error) { return { ok: false, action: plan.action, environment, walletAddress, asset: spotAsset, spotAsset, perpAsset, perpSymbol, spotSymbol: spot.symbol, metrics: plan.metrics, plannedOrders, executedOrders, orderResponses, error: error instanceof Error ? error.message : "Order submission failed", errorDetail: resolveHyperliquidErrorDetail(error), }; } const orderIds = extractHyperliquidOrderIds( orderResponses as unknown as Array<{ response?: { data?: { statuses?: Array<Record<string, unknown>> } }; }>, ); return { ok: true, action: plan.action, environment, walletAddress, asset: spotAsset, spotAsset, perpAsset, perpSymbol, spotSymbol: spot.symbol, metrics: plan.metrics, plannedOrders, executedOrders, orderResponses, ...(orderIds.cloids.length || orderIds.oids.length ? { orderIds } : {}), }; }