openpondai/agents/polymarket-market-watcher

OpenTool app

1Branch0Tags
OP
OpenPond Syncsync: merge local template master into production
typescript
import { buildHyperliquidMarketIdentity, fetchHyperliquidClearinghouseState, fetchHyperliquidSizeDecimals, fetchHyperliquidSpotAccountValue, fetchHyperliquidSpotClearinghouseState, formatHyperliquidMarketablePrice, formatHyperliquidOrderSize, isHyperliquidSpotSymbol, normalizeHyperliquidBaseSymbol, placeHyperliquidOrder, planHyperliquidTrade, readHyperliquidAccountValue, readHyperliquidPerpPositionSize, readHyperliquidSpotBalanceSize, resolveHyperliquidChainConfig, resolveHyperliquidErrorDetail, resolveHyperliquidLeverageMode, resolveHyperliquidPair, resolveHyperliquidSymbol, resolveHyperliquidTargetSize, updateHyperliquidLeverage, type HyperliquidEnvironment, type HyperliquidOrderResponse, } from "opentool/adapters/hyperliquid"; import { store } from "opentool/store"; import { wallet } from "opentool/wallet"; import type { WalletFullContext } from "opentool/wallet"; import type { PolymarketMarketWatcherConfig, PolymarketWatcherSignal } from "./config"; import { resolveExecutableMarketSymbol } from "./market-symbol"; type LiveExecutionResult = | { ok: true; execution: Record<string, unknown>; } | { ok: false; error: string; execution: Record<string, unknown>; }; export async function executePolymarketWatcherTrade(params: { config: PolymarketMarketWatcherConfig; tradeSignal: PolymarketWatcherSignal; currentPrice: number; }): Promise<LiveExecutionResult> { const { config, tradeSignal, currentPrice } = params; const executionConfig = config.execution; if (!executionConfig.enabled) { return { ok: true, execution: { enabled: false, action: "disabled", signal: tradeSignal, }, }; } const environment = executionConfig.environment as HyperliquidEnvironment; const mode = executionConfig.mode; const slippageBps = executionConfig.slippageBps; const executableSymbol = await resolveExecutableMarketSymbol(config); const orderSymbol = resolveHyperliquidSymbol( executableSymbol, executionConfig.symbol ?? config.marketSymbol, ); const isSpot = isHyperliquidSpotSymbol(orderSymbol); const baseSymbol = normalizeHyperliquidBaseSymbol(orderSymbol); const pair = resolveHyperliquidPair(orderSymbol); const leverageMode = resolveHyperliquidLeverageMode(orderSymbol); const leverage = executionConfig.leverage; try { const chain = resolveHyperliquidChainConfig(environment).chain; const ctx = await wallet({ chain }); if (isSpot && orderSymbol.startsWith("@")) { throw new Error("spot execution requires BASE-QUOTE or BASE/QUOTE symbols (no @ ids)."); } if (isSpot && typeof leverage === "number" && Number.isFinite(leverage)) { throw new Error("leverage is not supported for spot markets."); } const clearing = isSpot ? await fetchHyperliquidSpotClearinghouseState({ environment, user: ctx.address as `0x${string}`, }) : await fetchHyperliquidClearinghouseState({ environment, walletAddress: ctx.address as `0x${string}`, }); const currentSize = isSpot ? readHyperliquidSpotBalanceSize(clearing, orderSymbol) : readHyperliquidPerpPositionSize(clearing, orderSymbol, { prefixMatch: true }); const accountValue = isSpot ? await fetchHyperliquidSpotAccountValue({ environment, balances: (clearing as { balances?: unknown }).balances, }) : readHyperliquidAccountValue(clearing); const { targetSize, budgetUsd } = resolveHyperliquidTargetSize({ config, execution: executionConfig, accountValue, currentPrice, }); const plan = planHyperliquidTrade({ signal: tradeSignal, mode, currentSize, targetSize, }); const orderResponses: HyperliquidOrderResponse[] = []; let sizeDecimals: number | undefined; if (!isSpot && typeof leverage === "number" && Number.isFinite(leverage)) { await updateHyperliquidLeverage({ wallet: ctx as WalletFullContext, environment, input: { symbol: orderSymbol, leverageMode, leverage, }, }); } if (plan) { const price = formatHyperliquidMarketablePrice({ mid: currentPrice, side: plan.side, slippageBps, }); sizeDecimals = await fetchHyperliquidSizeDecimals({ symbol: orderSymbol, environment, }); const size = formatHyperliquidOrderSize(plan.size, sizeDecimals); if (size === "0") { throw new Error(`Order size too small for ${orderSymbol} (szDecimals=${sizeDecimals}).`); } const response = await placeHyperliquidOrder({ wallet: ctx as WalletFullContext, environment, orders: [ { symbol: orderSymbol, side: plan.side, price, size, tif: "FrontendMarket", reduceOnly: plan.reduceOnly, }, ], }); const orderIds = resolveOrderIds([response]); const orderRef = orderIds.cloids[0] ?? orderIds.oids[0] ?? `polymarket-market-watcher-${Date.now()}`; const marketIdentity = buildHyperliquidMarketIdentity({ environment, symbol: pair ?? orderSymbol, rawSymbol: orderSymbol, isSpot, base: baseSymbol ?? null, }); if (!marketIdentity) { throw new Error("Unable to resolve market identity for order."); } await store({ source: "hyperliquid", ref: orderRef, status: "submitted", walletAddress: ctx.address, action: "order", notional: size, network: environment === "mainnet" ? "hyperliquid" : "hyperliquid-testnet", market: marketIdentity, metadata: { symbol: baseSymbol, pair: pair ?? undefined, side: plan.side, price, size, reduceOnly: plan.reduceOnly, ...(typeof leverage === "number" ? { leverage } : {}), environment, cloid: orderIds.cloids[0] ?? null, orderIds, orderResponse: response, strategy: "polymarket-market-watcher", }, }); orderResponses.push(response); } return { ok: true, execution: { enabled: true, signal: tradeSignal, action: plan ? "order" : "noop", environment, mode, symbol: baseSymbol, pair, leverageMode, ...(typeof leverage === "number" ? { leverage } : {}), slippageBps, sizeDecimals, budgetUsd, targetSize, currentSize, orderIds: resolveOrderIds(orderResponses), orderResponses: orderResponses.length ? orderResponses : undefined, }, }; } catch (error) { const message = error instanceof Error ? error.message : "execution_failed"; const errorDetail = resolveHyperliquidErrorDetail(error); return { ok: false, error: message, execution: { enabled: true, signal: tradeSignal, action: "error", environment, mode, symbol: baseSymbol, leverageMode, ...(typeof leverage === "number" ? { leverage } : {}), error: message, ...(errorDetail ? { errorDetail } : {}), }, }; } } function resolveOrderIds(responses: HyperliquidOrderResponse[]): { cloids: string[]; oids: string[] } { const cloids = new Set<string>(); const oids = new Set<string>(); for (const response of responses) { const statuses = response.response?.data?.statuses; if (!Array.isArray(statuses)) continue; for (const status of statuses) { if (typeof status === "string") continue; if ("resting" in status) { if (status.resting.cloid) cloids.add(status.resting.cloid); oids.add(String(status.resting.oid)); continue; } if ("filled" in status) { if (status.filled.cloid) cloids.add(status.filled.cloid); oids.add(String(status.filled.oid)); } } } return { cloids: Array.from(cloids), oids: Array.from(oids), }; }