1Branch0Tags
GL
glucryptouse HIP-4 series key identity
e149f868 days ago3Commits
typescript
import type { Hip4EdgeBotConfig, Hip4ModelWindow } from "../config"; import type { Hip4GatewayAnalytics, Hip4GatewayMarket } from "./types"; const YEAR_MS = 365 * 24 * 60 * 60 * 1000; const VOL_WINDOWS = ["1h", "6h", "24h", "7d"] as const; type VolWindow = (typeof VOL_WINDOWS)[number]; export type Hip4EdgeCalculation = { fairYes: Record<VolWindow, number | null> & { selected: number | null; min: number | null; max: number | null; }; fairNoSelected: number | null; yesBuyEdge: number | null; noBuyEdge: number | null; yesBuyRoi: number | null; noBuyRoi: number | null; }; function erf(value: number): number { const sign = value < 0 ? -1 : 1; const x = Math.abs(value); const t = 1 / (1 + 0.3275911 * x); const a1 = 0.254829592; const a2 = -0.284496736; const a3 = 1.421413741; const a4 = -1.453152027; const a5 = 1.061405429; const y = 1 - (((((a5 * t + a4) * t + a3) * t + a2) * t + a1) * t * Math.exp(-x * x)); return sign * y; } function normalCdf(value: number): number { return 0.5 * (1 + erf(value / Math.SQRT2)); } function clampProbability(value: number): number { return Math.max(0, Math.min(1, value)); } function toTimestamp(value: string | null | undefined): number { if (!value) return Number.NaN; const timestamp = Date.parse(value); return Number.isFinite(timestamp) ? timestamp : Number.NaN; } function calculateFairYesProbability(params: { underlyingMid: number | null; targetPrice: number | null; expiryMs: number; nowMs: number; realizedVolatility: number | null; }): number | null { const { underlyingMid, targetPrice, realizedVolatility } = params; if ( underlyingMid == null || targetPrice == null || underlyingMid <= 0 || targetPrice <= 0 || !Number.isFinite(params.expiryMs) || !Number.isFinite(params.nowMs) ) { return null; } const yearsToExpiry = Math.max(params.expiryMs - params.nowMs, 0) / YEAR_MS; if (yearsToExpiry <= 0 || realizedVolatility == null || realizedVolatility <= 0) { return underlyingMid >= targetPrice ? 1 : 0; } const sigmaRootT = realizedVolatility * Math.sqrt(yearsToExpiry); if (!Number.isFinite(sigmaRootT) || sigmaRootT <= 0) { return underlyingMid >= targetPrice ? 1 : 0; } const d2 = (Math.log(underlyingMid / targetPrice) - 0.5 * realizedVolatility ** 2 * yearsToExpiry) / sigmaRootT; return clampProbability(normalCdf(d2)); } function resolveSelectedFairYes(params: { fairYes: Record<VolWindow, number | null>; selectedModel: Hip4ModelWindow; }): number | null { if (params.selectedModel !== "blend") { return params.fairYes[params.selectedModel]; } const values = VOL_WINDOWS.map((window) => params.fairYes[window]).filter( (value): value is number => value != null, ); if (values.length === 0) return null; return values.reduce((sum, value) => sum + value, 0) / values.length; } function edgeRoi(edge: number | null, price: number | null): number | null { if (edge == null || price == null || price <= 0) return null; return edge / price; } function readVolatility( analytics: Hip4GatewayAnalytics, market: Hip4GatewayMarket, window: VolWindow, ) { const marketValue = market.model.realizedVolatility[window]; if (typeof marketValue === "number" && Number.isFinite(marketValue)) { return marketValue; } const analyticsValue = analytics.realizedVolatility[window]; return typeof analyticsValue === "number" && Number.isFinite(analyticsValue) ? analyticsValue : null; } export function calculateHip4Edge(params: { config: Hip4EdgeBotConfig; analytics: Hip4GatewayAnalytics; market: Hip4GatewayMarket; }): Hip4EdgeCalculation { const nowMs = toTimestamp(params.analytics.asOf) || Date.now(); const expiryMs = toTimestamp(params.market.expiryAt); const fairYesBase = Object.fromEntries( VOL_WINDOWS.map((window) => [ window, calculateFairYesProbability({ underlyingMid: params.analytics.btcReference.btcMid, targetPrice: params.market.targetPrice, expiryMs, nowMs, realizedVolatility: readVolatility(params.analytics, params.market, window), }), ]), ) as Record<VolWindow, number | null>; const fairYesSelected = resolveSelectedFairYes({ fairYes: fairYesBase, selectedModel: params.config.model.selectedModel, }); const fairValues = VOL_WINDOWS.map((window) => fairYesBase[window]).filter( (value): value is number => value != null, ); const fairYesMin = fairValues.length > 0 ? Math.min(...fairValues) : null; const fairYesMax = fairValues.length > 0 ? Math.max(...fairValues) : null; const fairNoSelected = fairYesSelected != null ? clampProbability(1 - fairYesSelected) : null; const yesBuyEdge = fairYesSelected != null && params.market.yes.ask != null ? fairYesSelected - params.market.yes.ask : null; const noBuyEdge = fairNoSelected != null && params.market.no.ask != null ? fairNoSelected - params.market.no.ask : null; return { fairYes: { ...fairYesBase, selected: fairYesSelected, min: fairYesMin, max: fairYesMax, }, fairNoSelected, yesBuyEdge, noBuyEdge, yesBuyRoi: edgeRoi(yesBuyEdge, params.market.yes.ask), noBuyRoi: edgeRoi(noBuyEdge, params.market.no.ask), }; }