1Branch0Tags
GL
glucryptoFix exact Hyperliquid symbols
typescript
import { clampHyperliquidInt, parseHyperliquidJson, } from "opentool/adapters/hyperliquid"; import { CONFIG_ENV, DEFAULT_AMOUNT_USD, DEFAULT_ASSET, DEFAULT_BB_PERIOD, DEFAULT_BB_STD_DEV, DEFAULT_COUNT_BACK, DEFAULT_DONCHIAN_PERIOD, DEFAULT_EMA_PERIOD, DEFAULT_EXECUTION_ENV, DEFAULT_EXECUTION_MODE, DEFAULT_MA_CROSS_FAST, DEFAULT_MA_CROSS_SLOW, DEFAULT_RESOLUTION, DEFAULT_RSI_OVERBOUGHT, DEFAULT_RSI_OVERSOLD, DEFAULT_SIGNAL_SCHEDULE_CRON, DEFAULT_SLIPPAGE_BPS, DEFAULT_SMA_PERIOD, TEMPLATE_CONFIG_DEFAULTS, TEMPLATE_CONFIG_ENV_VAR, TEMPLATE_CONFIG_VERSION, } from "./defaults"; import { configSchema, TEMPLATE_CONFIG_SCHEMA } from "./schema"; import type { ScheduleConfig, SignalBotConfig } from "./types"; export const SIGNAL_BOT_TEMPLATE_CONFIG = { version: TEMPLATE_CONFIG_VERSION, schema: TEMPLATE_CONFIG_SCHEMA, defaults: TEMPLATE_CONFIG_DEFAULTS, envVar: TEMPLATE_CONFIG_ENV_VAR, }; export function normalizeConfigMarketSymbol(value: string | null | undefined): string { const trimmed = (value ?? DEFAULT_ASSET).trim(); if (!trimmed) return DEFAULT_ASSET; const dexSeparator = trimmed.indexOf(":"); if (dexSeparator > 0) { const dex = trimmed.slice(0, dexSeparator).trim().toLowerCase(); const symbol = trimmed.slice(dexSeparator + 1).trim().toUpperCase(); return dex && symbol ? `${dex}:${symbol}` : trimmed; } return trimmed.toUpperCase(); } export function readConfig(): SignalBotConfig { const envConfig = parseHyperliquidJson(process.env[CONFIG_ENV] ?? null); const parsed = configSchema.safeParse(envConfig); const input = parsed.success ? parsed.data : {}; const resolution = input.resolution ?? DEFAULT_RESOLUTION; const allocationMode: SignalBotConfig["allocationMode"] = "fixed"; const resolvedIndicators: SignalBotConfig["indicators"] = input.indicators && input.indicators.length > 0 ? (input.indicators as SignalBotConfig["indicators"]) : ["rsi"]; const amountUsd = input.amountUsd ?? DEFAULT_AMOUNT_USD; const schedule = { cron: input.schedule?.cron ?? DEFAULT_SIGNAL_SCHEDULE_CRON, enabled: input.schedule?.enabled ?? false, notifyEmail: input.schedule?.notifyEmail ?? false, }; const base = { platform: "hyperliquid" as const, allocationMode, amountUsd, schedule, resolution, countBack: input.countBack ?? DEFAULT_COUNT_BACK, }; const execution = input.execution ? { enabled: input.execution.enabled ?? false, environment: input.execution.environment ?? DEFAULT_EXECUTION_ENV, mode: input.execution.mode ?? DEFAULT_EXECUTION_MODE, slippageBps: clampHyperliquidInt( input.execution.slippageBps, 0, 500, DEFAULT_SLIPPAGE_BPS, ), ...(input.execution.symbol ? { symbol: normalizeConfigMarketSymbol(input.execution.symbol) } : {}), ...(input.execution.size ? { size: input.execution.size } : {}), ...(input.execution.leverage ? { leverage: input.execution.leverage } : {}), ...(input.execution.indicator ? { indicator: input.execution.indicator } : {}), } : undefined; const price = input.price ? { ...(input.price.rsiPreset ? { rsiPreset: input.price.rsiPreset } : {}), ...(input.price.rsi ? { rsi: { overbought: input.price.rsi?.overbought ?? DEFAULT_RSI_OVERBOUGHT, oversold: input.price.rsi?.oversold ?? DEFAULT_RSI_OVERSOLD, }, } : {}), ...(input.price.movingAverage ? { movingAverage: { type: input.price.movingAverage.type ?? "sma", period: input.price.movingAverage.period ?? DEFAULT_SMA_PERIOD, }, } : {}), ...(input.price.maCross ? { maCross: { type: input.price.maCross.type ?? "sma", fastPeriod: input.price.maCross.fastPeriod ?? DEFAULT_MA_CROSS_FAST, slowPeriod: input.price.maCross.slowPeriod ?? DEFAULT_MA_CROSS_SLOW, }, } : {}), ...(input.price.bollinger ? { bollinger: { period: input.price.bollinger.period ?? DEFAULT_BB_PERIOD, stdDev: input.price.bollinger.stdDev ?? DEFAULT_BB_STD_DEV, }, } : {}), ...(input.price.donchian ? { donchian: { period: input.price.donchian.period ?? DEFAULT_DONCHIAN_PERIOD, }, } : {}), } : undefined; return { ...base, signalType: "price", asset: normalizeConfigMarketSymbol(input.asset), indicators: resolvedIndicators, ...(execution ? { execution } : {}), ...(price ? { price } : {}), }; } export function resolveScheduleConfig(config: SignalBotConfig): ScheduleConfig { return config.schedule; } export function resolveProfileAssetSymbols(config: SignalBotConfig): string[] { const fallback = config.execution?.symbol ?? config.asset ?? DEFAULT_ASSET; const normalizedFallback = fallback.trim(); return normalizedFallback ? [normalizedFallback] : []; } export function resolveProfileAssets(config: SignalBotConfig): Array<{ venue: "hyperliquid"; chain: "hyperliquid" | "hyperliquid-testnet"; assetSymbols: string[]; pair?: string; leverage?: number; }> { const environment = config.execution?.environment ?? DEFAULT_EXECUTION_ENV; const symbols = resolveProfileAssetSymbols(config) .map((symbol) => symbol.trim()) .filter((symbol) => symbol.length > 0); if (symbols.length === 0) { return []; } return [ { venue: "hyperliquid", chain: environment === "testnet" ? ("hyperliquid-testnet" as const) : ("hyperliquid" as const), assetSymbols: Array.from(new Set(symbols)), ...(typeof config.execution?.leverage === "number" ? { leverage: config.execution.leverage } : {}), }, ]; }