1Branch0Tags
GL
glucryptoRefresh package-lock for opentool 0.19.5
1e58acc14 hours ago9Commits
typescript
import { z } from "zod"; import { buildHyperliquidProfileAssets, clampHyperliquidInt, parseHyperliquidJson, normalizeHyperliquidBaseSymbol, resolveHyperliquidIntervalCron, resolveHyperliquidScheduleEvery, resolveHyperliquidScheduleUnit, } from "opentool/adapters/hyperliquid"; export type ScheduleConfig = { cron: string; enabled: boolean; notifyEmail: boolean; }; export type IndicatorType = "rsi"; export type ExecutionConfig = { enabled?: boolean; environment?: "testnet" | "mainnet"; symbol?: string; mode?: "long-only" | "long-short"; size?: number; leverage?: number; slippageBps?: number; indicator?: IndicatorType; }; export type SignalBotConfig = { configVersion?: number; platform: "hyperliquid"; signalType: "price"; asset: string; indicators: IndicatorType[]; cadence: "hourly"; hourlyInterval: number; scheduleEvery: number; scheduleUnit: "minutes" | "hours"; allocationMode: "percent_equity" | "fixed"; percentOfEquity: number; maxPercentOfEquity: number; amountUsd?: number; schedule: ScheduleConfig; resolution: "1" | "5" | "15" | "30" | "60" | "240" | "1D" | "1W"; countBack: number; execution?: ExecutionConfig; price?: { rsiPreset?: string; rsi?: { overbought: number; oversold: number }; }; }; const CONFIG_ENV = "OPENTOOL_PUBLIC_HL_RSI_SIGNAL_BOT_CONFIG"; const TEMPLATE_CONFIG_VERSION = 3; const TEMPLATE_CONFIG_ENV_VAR = CONFIG_ENV; const DEFAULT_ASSET = "BTC"; const DEFAULT_PERCENT_OF_EQUITY = 2; const DEFAULT_MAX_PERCENT_OF_EQUITY = 10; const DEFAULT_AMOUNT_USD = 200; const DEFAULT_SCHEDULE_EVERY = 1; const DEFAULT_SCHEDULE_UNIT: SignalBotConfig["scheduleUnit"] = "hours"; const DEFAULT_RESOLUTION: SignalBotConfig["resolution"] = "60"; const DEFAULT_COUNT_BACK = 240; export const DEFAULT_EXECUTION_ENV: NonNullable<ExecutionConfig["environment"]> = "mainnet"; export const DEFAULT_EXECUTION_MODE: NonNullable<ExecutionConfig["mode"]> = "long-only"; export const DEFAULT_SLIPPAGE_BPS = 50; export const DEFAULT_RSI_PRESET = "balanced"; export const DEFAULT_RSI_OVERBOUGHT = 70; export const DEFAULT_RSI_OVERSOLD = 30; export const RSI_PRESETS: Record<string, { overbought: number; oversold: number }> = { balanced: { overbought: 70, oversold: 30 }, tighter: { overbought: 65, oversold: 35 }, wider: { overbought: 80, oversold: 20 }, }; const indicatorSchema = z.literal("rsi"); const configSchema = z .object({ platform: z.literal("hyperliquid").optional(), signalType: z.literal("price").optional(), asset: z.string().min(1).optional(), indicators: z.array(indicatorSchema).optional(), scheduleEvery: z.number().int().min(1).max(59).optional(), scheduleUnit: z.enum(["minutes", "hours"]).optional(), cadence: z.literal("hourly").optional(), hourlyInterval: z.number().int().min(1).max(24).optional(), allocationMode: z.enum(["percent_equity", "fixed", "percent"]).optional(), percentOfEquity: z.number().positive().optional(), maxPercentOfEquity: z.number().positive().optional(), amountUsd: z.number().positive().optional(), schedule: z .object({ cron: z.string().min(1).optional(), enabled: z.boolean().optional(), notifyEmail: z.boolean().optional(), }) .optional(), resolution: z.enum(["1", "5", "15", "30", "60", "240", "1D", "1W"]).optional(), countBack: z.number().int().min(50).max(5000).optional(), price: z .object({ rsiPreset: z.string().optional(), rsi: z .object({ overbought: z.number().optional(), oversold: z.number().optional(), }) .optional(), }) .optional(), execution: z .object({ enabled: z.boolean().optional(), environment: z.enum(["testnet", "mainnet"]).optional(), symbol: z.string().optional(), mode: z.enum(["long-only", "long-short"]).optional(), size: z.number().positive().optional(), leverage: z.number().positive().optional(), slippageBps: z.number().min(0).max(500).optional(), indicator: indicatorSchema.optional(), }) .optional(), }) .partial(); const TEMPLATE_CONFIG_DEFAULTS: SignalBotConfig = { configVersion: TEMPLATE_CONFIG_VERSION, platform: "hyperliquid", signalType: "price", asset: DEFAULT_ASSET, indicators: ["rsi"], cadence: "hourly", hourlyInterval: DEFAULT_SCHEDULE_EVERY, scheduleEvery: DEFAULT_SCHEDULE_EVERY, scheduleUnit: DEFAULT_SCHEDULE_UNIT, allocationMode: "fixed", percentOfEquity: DEFAULT_PERCENT_OF_EQUITY, maxPercentOfEquity: DEFAULT_MAX_PERCENT_OF_EQUITY, amountUsd: DEFAULT_AMOUNT_USD, schedule: { cron: resolveHyperliquidIntervalCron(DEFAULT_SCHEDULE_EVERY, DEFAULT_SCHEDULE_UNIT), enabled: false, notifyEmail: false, }, resolution: DEFAULT_RESOLUTION, countBack: DEFAULT_COUNT_BACK, price: { rsiPreset: DEFAULT_RSI_PRESET, rsi: { overbought: DEFAULT_RSI_OVERBOUGHT, oversold: DEFAULT_RSI_OVERSOLD, }, }, execution: { enabled: false, environment: DEFAULT_EXECUTION_ENV, mode: DEFAULT_EXECUTION_MODE, indicator: "rsi", }, }; const TEMPLATE_CONFIG_SCHEMA = { type: "object", "x-budget": { modeField: "allocationMode", defaultMode: "fixed", title: "Budget & allocation", description: "Core exposure settings are shown first.", modes: { fixed: { fields: ["amountUsd"], }, percent_equity: { fields: ["percentOfEquity", "maxPercentOfEquity"], }, }, }, properties: { configVersion: { type: "number", title: "Config version", description: "Internal version for rsi-signal-bot defaults.", readOnly: true, "x-hidden": true, "x-section": "Meta", "x-order": 1000, }, platform: { type: "string", enum: ["hyperliquid"], title: "Platform", readOnly: true, "x-hidden": true, "x-section": "Meta", "x-order": 1001, }, signalType: { type: "string", enum: ["price"], title: "Signal type", readOnly: true, "x-hidden": true, "x-section": "Meta", "x-order": 1002, }, asset: { type: "string", title: "Asset", description: "Default Hyperliquid perp symbol. Use execution symbol for spot pairs (BASE-QUOTE or BASE/QUOTE) or HIP-3 symbols (dex:BASE).", "x-section": "Strategy", "x-order": 1, }, scheduleEvery: { type: "number", title: "Run every", description: "How often the bot runs.", minimum: 1, maximum: 59, "x-step": 1, "x-section": "Strategy", "x-order": 2, }, scheduleUnit: { type: "string", enum: ["minutes", "hours"], title: "Schedule unit", description: "Run interval unit.", "x-enumLabels": ["Minutes", "Hours"], "x-section": "Strategy", "x-order": 3, }, allocationMode: { type: "string", enum: ["percent_equity", "fixed"], title: "Allocation mode", description: "Position sizing method.", "x-enumLabels": ["Percent of equity", "Fixed USD"], "x-section": "Risk", "x-order": 1, }, percentOfEquity: { type: "number", title: "Percent of equity", description: "Target percent of account value per run.", minimum: 0.01, "x-unit": "%", "x-format": "percent", "x-step": 0.1, "x-visibleIf": { field: "allocationMode", equals: "percent_equity" }, "x-section": "Risk", "x-order": 2, }, maxPercentOfEquity: { type: "number", title: "Max percent of equity", description: "Upper cap for percent-based sizing.", minimum: 0.01, "x-unit": "%", "x-format": "percent", "x-step": 0.1, "x-visibleIf": { field: "allocationMode", equals: "percent_equity" }, "x-section": "Risk", "x-order": 3, }, amountUsd: { type: "number", title: "Fixed amount", description: "USD notional per run when allocation mode is fixed.", minimum: 1, "x-unit": "USD", "x-format": "currency", "x-step": 1, "x-visibleIf": { field: "allocationMode", equals: "fixed" }, "x-section": "Risk", "x-order": 4, }, schedule: { type: "object", title: "Schedule", description: "Cron and notification settings.", "x-section": "Schedule", properties: { cron: { type: "string", title: "Cron expression", description: "Cron expression for automated runs.", "x-order": 1, }, enabled: { type: "boolean", title: "Enabled", description: "Enable scheduled runs.", "x-order": 2, }, notifyEmail: { type: "boolean", title: "Notify email", description: "Send an email after scheduled runs.", "x-order": 3, }, }, }, resolution: { type: "string", enum: ["1", "5", "15", "30", "60", "240", "1D", "1W"], title: "Resolution", description: "Bar timeframe for price data.", "x-section": "Price model", "x-order": 1, }, countBack: { type: "number", title: "Bars to fetch", description: "Number of historical bars for RSI calculations.", minimum: 50, maximum: 5000, "x-step": 1, "x-section": "Price model", "x-order": 2, }, price: { type: "object", title: "RSI settings", description: "RSI-specific tuning.", "x-section": "Price model", properties: { rsiPreset: { type: "string", title: "RSI preset", description: "Preset thresholds for RSI.", "x-order": 1, }, rsi: { type: "object", title: "RSI thresholds", description: "Custom RSI overbought/oversold values.", properties: { overbought: { type: "number", title: "RSI overbought", minimum: 1, maximum: 100, "x-order": 2, }, oversold: { type: "number", title: "RSI oversold", minimum: 1, maximum: 100, "x-order": 3, }, }, }, }, }, execution: { type: "object", title: "Execution", description: "Live trading controls and routing.", "x-section": "Execution", properties: { enabled: { type: "boolean", title: "Execution enabled", description: "Submit live orders when signals trigger.", "x-order": 1, }, environment: { type: "string", enum: ["testnet", "mainnet"], title: "Environment", description: "Execution environment for Hyperliquid.", "x-enumLabels": ["Testnet", "Mainnet"], "x-order": 2, }, symbol: { type: "string", title: "Execution symbol", description: "Optional exact Hyperliquid symbol override. Use BASE-QUOTE or BASE/QUOTE for spot, bare BASE for perps, or dex:BASE for HIP-3.", "x-order": 3, }, mode: { type: "string", enum: ["long-only", "long-short"], title: "Execution mode", description: "Allow long-only or long-short positioning.", "x-enumLabels": ["Long only", "Long + short"], "x-order": 4, }, size: { type: "number", title: "Explicit size", description: "Optional explicit position size override.", minimum: 0, "x-step": 0.0001, "x-order": 5, }, leverage: { type: "number", title: "Leverage", description: "Target leverage for perpetual execution.", minimum: 1, maximum: 50, "x-unit": "x", "x-step": 1, "x-order": 6, }, slippageBps: { type: "number", title: "Execution slippage", description: "Max slippage for signal-driven orders.", minimum: 0, maximum: 500, "x-unit": "bps", "x-format": "bps", "x-step": 1, "x-order": 7, }, }, }, }, }; export const RSI_SIGNAL_BOT_TEMPLATE_CONFIG = { version: TEMPLATE_CONFIG_VERSION, schema: TEMPLATE_CONFIG_SCHEMA, defaults: TEMPLATE_CONFIG_DEFAULTS, envVar: TEMPLATE_CONFIG_ENV_VAR, }; export function readConfig(): SignalBotConfig { const envConfig = parseHyperliquidJson(process.env[CONFIG_ENV] ?? null); const parsed = configSchema.safeParse(envConfig); const input = parsed.success ? parsed.data : {}; const scheduleEvery = resolveHyperliquidScheduleEvery(input.scheduleEvery, { min: 1, max: 59, fallback: DEFAULT_SCHEDULE_EVERY, }); const scheduleUnit = resolveHyperliquidScheduleUnit(input.scheduleUnit, DEFAULT_SCHEDULE_UNIT); const hourlyInterval = scheduleUnit === "hours" ? scheduleEvery : 1; const allocationMode: SignalBotConfig["allocationMode"] = input.allocationMode === "percent_equity" || input.allocationMode === "percent" ? "percent_equity" : "fixed"; const percentOfEquity = input.percentOfEquity ?? DEFAULT_PERCENT_OF_EQUITY; const maxPercentOfEquity = input.maxPercentOfEquity ?? DEFAULT_MAX_PERCENT_OF_EQUITY; const amountUsd = allocationMode === "fixed" ? input.amountUsd ?? DEFAULT_AMOUNT_USD : undefined; const schedule = { cron: input.schedule?.cron ?? resolveHyperliquidIntervalCron(scheduleEvery, scheduleUnit), enabled: input.schedule?.enabled ?? false, notifyEmail: input.schedule?.notifyEmail ?? false, }; 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: input.execution.symbol } : {}), ...(input.execution.size ? { size: input.execution.size } : {}), ...(input.execution.leverage ? { leverage: input.execution.leverage } : {}), indicator: "rsi" as const, } : undefined; const price = input.price ? { ...(input.price.rsiPreset ? { rsiPreset: input.price.rsiPreset } : {}), ...(input.price.rsi || input.price.rsiPreset ? { rsi: { overbought: input.price.rsi?.overbought ?? DEFAULT_RSI_OVERBOUGHT, oversold: input.price.rsi?.oversold ?? DEFAULT_RSI_OVERSOLD, }, } : {}), } : undefined; return { platform: "hyperliquid", signalType: "price", asset: (input.asset ?? DEFAULT_ASSET).toUpperCase(), indicators: ["rsi"], cadence: "hourly", hourlyInterval, scheduleEvery, scheduleUnit, allocationMode, percentOfEquity, maxPercentOfEquity, ...(amountUsd ? { amountUsd } : {}), schedule, resolution: input.resolution ?? DEFAULT_RESOLUTION, countBack: input.countBack ?? DEFAULT_COUNT_BACK, ...(execution ? { execution } : {}), ...(price ? { price } : {}), }; } export function resolveScheduleConfig(config: SignalBotConfig): ScheduleConfig { return config.schedule; } function resolveProfileAssetSymbols(config: SignalBotConfig): string[] { const fallback = config.execution?.symbol ?? config.asset ?? DEFAULT_ASSET; const normalizedFallback = normalizeHyperliquidBaseSymbol(fallback); 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 buildHyperliquidProfileAssets({ environment, assets: [ { assetSymbols: symbols, leverage: config.execution?.leverage, }, ], }); }