openpondai/agents/pair-trade
OpenTool app
typescript
import {
fetchHyperliquidDexMetaAndAssetCtxs,
fetchHyperliquidPerpMarketInfo,
fetchHyperliquidResolvedMarketDescriptor,
normalizeHyperliquidMetaSymbol,
resolveHyperliquidLeverageMode,
resolveHyperliquidPerpSymbol,
type HyperliquidEnvironment,
} from "opentool/adapters/hyperliquid";
const PARAGON_PERP_ALIASES: Record<string, string> = {
"BTC-D": "para:BTCD",
"BTC.D": "para:BTCD",
BTCD: "para:BTCD",
OTHERS: "para:OTHERS",
TOTAL2: "para:TOTAL2",
};
export type PairTradePerpMarketInfo = {
symbol: string;
price: number;
fundingRate: number | null;
szDecimals: number;
};
function readNumber(value: unknown): number | null {
if (typeof value === "number" && Number.isFinite(value)) return value;
if (typeof value === "string" && value.trim().length > 0) {
const parsed = Number(value);
return Number.isFinite(parsed) ? parsed : null;
}
return null;
}
function resolveParagonAlias(value: string) {
const normalized = value.trim().replace(/\s+/g, "").toUpperCase();
const withoutUsdQuote = normalized.replace(/[-/]USDC$/, "");
return PARAGON_PERP_ALIASES[withoutUsdQuote] ?? value;
}
export function resolvePairTradePerpSymbol(asset: string): string {
return resolveHyperliquidPerpSymbol(resolveParagonAlias(asset));
}
export function resolvePairTradePerpLeverageMode(
symbol: string,
configuredMode: "cross" | "isolated",
): "cross" | "isolated" {
return symbol.includes(":") ? resolveHyperliquidLeverageMode(symbol) : configuredMode;
}
export async function fetchPairTradePerpMarketInfo(params: {
environment: HyperliquidEnvironment;
symbol: string;
}): Promise<PairTradePerpMarketInfo> {
const descriptor = await fetchHyperliquidResolvedMarketDescriptor({
environment: params.environment,
symbol: params.symbol,
});
if (descriptor.kind !== "perp") {
throw new Error(`Expected Hyperliquid perp market: ${params.symbol}`);
}
if (!descriptor.dex) {
return fetchHyperliquidPerpMarketInfo({
environment: params.environment,
symbol: descriptor.orderSymbol,
});
}
const data = (await fetchHyperliquidDexMetaAndAssetCtxs(
params.environment,
descriptor.dex,
)) as [
{ universe?: Array<{ name?: string; szDecimals?: number | string }> },
Array<Record<string, unknown>>,
];
const universe = Array.isArray(data?.[0]?.universe) ? data[0].universe : [];
const contexts = Array.isArray(data?.[1]) ? data[1] : [];
const targetSymbol = descriptor.orderSymbol.toUpperCase();
const targetBase = normalizeHyperliquidMetaSymbol(descriptor.orderSymbol).toUpperCase();
const idx = universe.findIndex((entry) => {
const name = entry?.name ?? "";
return (
name.toUpperCase() === targetSymbol ||
normalizeHyperliquidMetaSymbol(name).toUpperCase() === targetBase
);
});
if (idx < 0) {
throw new Error(`Unknown Hyperliquid dex perp asset: ${descriptor.orderSymbol}`);
}
const ctx = contexts[idx] ?? null;
const price = readNumber(ctx?.markPx ?? ctx?.midPx ?? ctx?.oraclePx);
if (!price || price <= 0) {
throw new Error(`No perp price available for ${descriptor.orderSymbol}`);
}
const szDecimals = readNumber(universe[idx]?.szDecimals);
if (szDecimals == null) {
throw new Error(`No size decimals available for ${descriptor.orderSymbol}`);
}
return {
symbol: descriptor.orderSymbol,
price,
fundingRate: readNumber(ctx?.funding),
szDecimals,
};
}