1Branch0Tags
GL
glucryptoAdopt opentool 0.19.8
1f37f4912 days ago17Commits
typescript
import { buildHyperliquidMarketIdentity, extractHyperliquidOrderIds, fetchHyperliquidResolvedMarketDescriptor, placeHyperliquidTwapOrder, resolveHyperliquidLeverageMode, updateHyperliquidLeverage, type HyperliquidEnvironment, } from "opentool/adapters/hyperliquid"; import { store } from "opentool/store"; import { wallet } from "opentool/wallet"; import type { WalletFullContext } from "opentool/wallet"; import { normalizeMarketSymbol, type TwapBotConfig } from "../config"; import { isSpotSymbol, readObservedPrice } from "./market"; export type TwapBotResult = { ok: boolean; simulated: boolean; environment: HyperliquidEnvironment; asset: string; marketSymbol: string; side: "buy" | "sell"; amountUsd: number; minutes: number; randomize: boolean; price: number; size: number; orderIds?: { cloids: string[]; oids: string[]; }; orderResponses?: unknown[]; response?: unknown; }; export async function runTwapBot(config: TwapBotConfig): Promise<TwapBotResult> { const environment = config.execution?.environment ?? "mainnet"; const marketSymbol = normalizeMarketSymbol(config.marketSymbol || config.asset); const marketDescriptor = await fetchHyperliquidResolvedMarketDescriptor({ environment, symbol: marketSymbol, }); const orderSymbol = marketDescriptor.orderSymbol; const isSpot = marketDescriptor.kind === "spot" || marketDescriptor.kind === "spotIndex"; const price = await readObservedPrice({ environment, symbol: marketSymbol, }); const size = config.amountUsd / price; if (!Number.isFinite(size) || size <= 0) { throw new Error(`Unable to derive a TWAP size for ${marketSymbol}.`); } let response: unknown | undefined; if (config.execution?.enabled) { const walletContext = (await wallet()) as WalletFullContext; if ( !isSpot && typeof config.execution?.leverage === "number" ) { const leverageMode = resolveHyperliquidLeverageMode(orderSymbol); await updateHyperliquidLeverage({ wallet: walletContext, environment, input: { symbol: orderSymbol, leverageMode, leverage: config.execution.leverage, }, }); } response = await placeHyperliquidTwapOrder({ wallet: walletContext, environment, twap: { symbol: orderSymbol, side: config.twap.side, size, minutes: config.twap.minutes, randomize: config.twap.randomize, reduceOnly: false, }, }); const orderIds = extractHyperliquidOrderIds([ response as { response?: { data?: { statuses?: Array<Record<string, unknown>>; }; }; }, ]); const marketIdentity = buildHyperliquidMarketIdentity({ environment, symbol: marketDescriptor.pair ?? orderSymbol, rawSymbol: orderSymbol, isSpot, base: marketDescriptor.base ?? config.asset, quote: marketDescriptor.quote, }); if (marketIdentity) { await store({ source: "hyperliquid", ref: orderIds.cloids[0] ?? orderIds.oids[0] ?? `twap-bot-${Date.now()}-${marketSymbol}`, status: "submitted", walletAddress: walletContext.address, action: "order", notional: String(size), network: environment === "mainnet" ? "hyperliquid" : "hyperliquid-testnet", market: marketIdentity, metadata: { strategy: "twap-bot", symbol: marketSymbol, side: config.twap.side, minutes: config.twap.minutes, randomize: config.twap.randomize, amountUsd: config.amountUsd, price, size, cloid: orderIds.cloids[0] ?? null, orderIds, response, orderResponses: [response], }, }); } } const result: TwapBotResult = { ok: true, simulated: !config.execution?.enabled, environment, asset: config.asset, marketSymbol, side: config.twap.side, amountUsd: config.amountUsd, minutes: config.twap.minutes, randomize: config.twap.randomize, price, size, ...(response ? { orderIds: extractHyperliquidOrderIds([ response as { response?: { data?: { statuses?: Array<Record<string, unknown>>; }; }; }, ]), orderResponses: [response], } : {}), ...(response ? { response } : {}), }; await store({ source: "twap-bot", ref: `twap-bot-${Date.now()}`, status: "info", action: "signal", metadata: result, }); return result; }