openpondai/agents/hyperliquid-delta-neutral
OpenTool app
typescript
import {
clampHyperliquidAbs,
fetchHyperliquidSpotTickSize,
fetchHyperliquidTickSize,
formatHyperliquidMarketablePrice,
formatHyperliquidOrderSize,
} from "opentool/adapters/hyperliquid";
import type { DeltaNeutralConfig } from "../config";
import {
estimateCarryEdgeUsd,
resolveHyperliquidBuilderFeeRate,
resolveHyperliquidExchangeFeeRate,
resolveHyperliquidTotalFeeRate,
} from "./planner-fees";
import { buildDeltaNeutralMetrics } from "./planner-metrics";
import type {
DeltaNeutralExecutionPlan,
DeltaNeutralPlanInput,
ResolvedPlannedOrder,
} from "./types";
export const MIN_NOTIONAL_USD = 1;
export {
resolveHyperliquidBuilderFeeRate,
resolveHyperliquidExchangeFeeRate,
resolveHyperliquidTotalFeeRate,
} from "./planner-fees";
export { buildDeltaNeutralMetrics } from "./planner-metrics";
export function planDeltaNeutralCycle(
params: DeltaNeutralPlanInput,
): DeltaNeutralExecutionPlan {
const metrics = buildDeltaNeutralMetrics({
config: params.config,
spotPrice: params.spotPrice,
perpPrice: params.perpPrice,
fundingRateBps: params.fundingRateBps,
spotBalance: params.spotBalance,
spotEntryNtl: params.spotEntryNtl,
perpSize: params.perpSize,
perpUnrealizedPnl: params.perpUnrealizedPnl,
});
const hasSpotInventory = Math.abs(metrics.spotValueUsd) >= MIN_NOTIONAL_USD;
const hasPerpHedge = Math.abs(metrics.perpNotionalUsd) >= MIN_NOTIONAL_USD;
const bootstrapSpotOrderUsd = hasSpotInventory
? 0
: clampHyperliquidAbs(params.config.targetNotionalUsd, params.maxPerRunUsd);
const effectiveSpotValueUsd = metrics.spotValueUsd + bootstrapSpotOrderUsd;
const desiredPerpOpenNotionalUsd =
-effectiveSpotValueUsd * Math.max(params.config.hedgeRatio ?? 1, 0);
if (
typeof params.config.basisMaxBps === "number" &&
metrics.basisBps != null &&
Math.abs(metrics.basisBps) > params.config.basisMaxBps &&
!hasPerpHedge
) {
return {
action: bootstrapSpotOrderUsd > 0 ? "delta-neutral-open" : "delta-neutral-hold",
metrics,
spotOrderUsd: bootstrapSpotOrderUsd,
perpOrderUsd: 0,
skipped: true,
reason: bootstrapSpotOrderUsd > 0 ? "bootstrap-spot" : "basis-gate",
};
}
if (!hasPerpHedge && metrics.basisBps != null && metrics.basisBps < 0) {
return {
action: bootstrapSpotOrderUsd > 0 ? "delta-neutral-open" : "delta-neutral-hold",
metrics,
spotOrderUsd: bootstrapSpotOrderUsd,
perpOrderUsd: 0,
skipped: true,
reason: bootstrapSpotOrderUsd > 0 ? "bootstrap-spot" : "basis-open-gate",
};
}
if (
typeof params.config.fundingMinBps === "number" &&
metrics.fundingRateBps != null
) {
const fundingOpenThresholdBps = params.config.fundingMinBps;
const fundingCloseThresholdBps = -Math.max(params.config.fundingMinBps * 3, 0.15);
if (hasPerpHedge && metrics.fundingRateBps < fundingCloseThresholdBps) {
return {
action: "delta-neutral-close",
metrics,
spotOrderUsd: 0,
perpOrderUsd: clampHyperliquidAbs(-metrics.perpNotionalUsd, params.maxPerRunUsd),
skipped: false,
reason: "funding-gate-close",
};
}
if (!hasPerpHedge && metrics.fundingRateBps < fundingOpenThresholdBps) {
return {
action: bootstrapSpotOrderUsd > 0 ? "delta-neutral-open" : "delta-neutral-hold",
metrics,
spotOrderUsd: bootstrapSpotOrderUsd,
perpOrderUsd: 0,
skipped: true,
reason: bootstrapSpotOrderUsd > 0 ? "bootstrap-spot" : "funding-gate",
};
}
}
if (!hasPerpHedge) {
const carryEdge = estimateCarryEdgeUsd({
config: params.config,
basisBps: metrics.basisBps,
fundingRateBps: metrics.fundingRateBps,
hedgeNotionalUsd: Math.abs(desiredPerpOpenNotionalUsd),
});
if (carryEdge.netEdgeUsd <= 0) {
return {
action: bootstrapSpotOrderUsd > 0 ? "delta-neutral-open" : "delta-neutral-hold",
metrics,
spotOrderUsd: bootstrapSpotOrderUsd,
perpOrderUsd: 0,
skipped: true,
reason: bootstrapSpotOrderUsd > 0 ? "bootstrap-spot" : "expected-edge-gate",
};
}
}
const driftUsdThreshold = params.config.deltaDriftUsd ?? 0;
const driftPctThreshold = params.config.deltaDriftPct ?? 0;
const shouldRebalance =
!hasPerpHedge ||
Math.abs(metrics.deltaUsd) >= driftUsdThreshold ||
(metrics.deltaPct != null && metrics.deltaPct >= driftPctThreshold);
if (!shouldRebalance) {
return {
action: "delta-neutral-hold",
metrics,
spotOrderUsd: 0,
perpOrderUsd: 0,
};
}
let spotOrderUsd = 0;
let perpOrderUsd = 0;
const action = !hasPerpHedge
? "delta-neutral-open"
: "delta-neutral-rebalance";
if (!hasPerpHedge) {
spotOrderUsd = bootstrapSpotOrderUsd;
const desiredPerpNotional = desiredPerpOpenNotionalUsd;
perpOrderUsd = clampHyperliquidAbs(
desiredPerpNotional - metrics.perpNotionalUsd,
params.maxPerRunUsd,
);
} else {
const desiredPerpNotional =
-metrics.spotValueUsd * Math.max(params.config.hedgeRatio ?? 1, 0);
perpOrderUsd = clampHyperliquidAbs(
desiredPerpNotional - metrics.perpNotionalUsd,
params.maxPerRunUsd,
);
}
return {
action,
metrics,
spotOrderUsd,
perpOrderUsd,
};
}
export function buildResolvedOrders(params: {
slippageBps: number;
perpSymbol: string;
spotSymbol: string;
perpPrice: number;
spotPrice: number;
perpSzDecimals: number;
spotSzDecimals: number;
perpTick: ReturnType<typeof fetchHyperliquidTickSize> extends Promise<infer T> ? T : never;
spotTick: ReturnType<typeof fetchHyperliquidSpotTickSize> extends Promise<infer T> ? T : never;
currentPerpNotionalUsd: number;
plan: DeltaNeutralExecutionPlan;
}): ResolvedPlannedOrder[] {
const orders: ResolvedPlannedOrder[] = [];
const addPerpOrder = (usdDelta: number) => {
if (Math.abs(usdDelta) < MIN_NOTIONAL_USD) return;
const side: "buy" | "sell" = usdDelta > 0 ? "buy" : "sell";
const size = formatHyperliquidOrderSize(
Math.abs(usdDelta) / params.perpPrice,
params.perpSzDecimals,
);
const quantity = Number.parseFloat(size);
if (!Number.isFinite(quantity) || quantity <= 0) return;
const price = formatHyperliquidMarketablePrice({
mid: params.perpPrice,
side,
slippageBps: params.slippageBps,
tick: params.perpTick,
szDecimals: params.perpSzDecimals,
marketType: "perp",
});
const fillPrice = Number.parseFloat(price);
if (!Number.isFinite(fillPrice) || fillPrice <= 0) return;
const reduceOnly =
params.currentPerpNotionalUsd !== 0 &&
Math.sign(usdDelta) !== Math.sign(params.currentPerpNotionalUsd);
orders.push({
kind: "perp",
symbol: params.perpSymbol,
side,
size,
quantity,
price,
fillPrice,
referencePrice: params.perpPrice,
reduceOnly,
notionalUsd: Math.abs(quantity * fillPrice),
});
};
const addSpotOrder = (usdDelta: number) => {
if (Math.abs(usdDelta) < MIN_NOTIONAL_USD) return;
const side: "buy" | "sell" = usdDelta > 0 ? "buy" : "sell";
const size = formatHyperliquidOrderSize(
Math.abs(usdDelta) / params.spotPrice,
params.spotSzDecimals,
);
const quantity = Number.parseFloat(size);
if (!Number.isFinite(quantity) || quantity <= 0) return;
const price = formatHyperliquidMarketablePrice({
mid: params.spotPrice,
side,
slippageBps: params.slippageBps,
tick: params.spotTick,
szDecimals: params.spotSzDecimals,
marketType: "spot",
});
const fillPrice = Number.parseFloat(price);
if (!Number.isFinite(fillPrice) || fillPrice <= 0) return;
orders.push({
kind: "spot",
symbol: params.spotSymbol,
side,
size,
quantity,
price,
fillPrice,
referencePrice: params.spotPrice,
notionalUsd: Math.abs(quantity * fillPrice),
});
};
addPerpOrder(params.plan.perpOrderUsd);
addSpotOrder(params.plan.spotOrderUsd);
return orders;
}
export function resolveDecisionSignal(params: {
spotOrderUsd: number;
perpOrderUsd: number;
orders: ResolvedPlannedOrder[];
}): "buy" | "sell" | "hold" | "unknown" {
if (params.orders.length === 0) return "hold";
if (params.spotOrderUsd > 0) return "buy";
if (params.spotOrderUsd < 0) return "sell";
if (params.perpOrderUsd < 0) return "buy";
if (params.perpOrderUsd > 0) return "sell";
return "unknown";
}