openpondai/agents/polymarket-market-watcher

OpenTool app

1Branch0Tags
OP
OpenPond Syncsync: merge local template master into production
typescript
import { buildHyperliquidProfileAssets, normalizeHyperliquidBaseSymbol } from "opentool/adapters/hyperliquid"; import { z } from "zod"; const CONFIG_ENV = "OPENTOOL_PUBLIC_PM_MARKET_WATCHER_CONFIG"; const TEMPLATE_CONFIG_VERSION = 2; const DEFAULT_ASSET = "BTC"; const DEFAULT_AMOUNT_USD = 200; const DEFAULT_PERCENT_OF_EQUITY = 2; const DEFAULT_MAX_PERCENT_OF_EQUITY = 10; const DEFAULT_RESOLUTION = "60" as const; const DEFAULT_COUNT_BACK = 240; const DEFAULT_EXECUTION_ENV = "mainnet" as const; const DEFAULT_EXECUTION_MODE = "long-only" as const; const DEFAULT_SLIPPAGE_BPS = 50; const scheduleSchema = z .object({ cron: z.string().min(1), enabled: z.boolean().optional(), notifyEmail: z.boolean().optional(), }) .strict(); const scheduleOverrideSchema = z .object({ cron: z.string().min(1).optional(), enabled: z.boolean().optional(), notifyEmail: z.boolean().optional(), }) .strict(); const executionSchema = z .object({ enabled: z.boolean().optional(), environment: z.enum(["testnet", "mainnet"]).optional(), symbol: z.string().min(1).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(), }) .strict(); const baseConfigSchema = z .object({ configVersion: z.number().int().optional(), platform: z.literal("hyperliquid").optional(), marketId: z.string().min(1).optional(), marketSlug: z.string().min(1).optional(), conditionId: z.string().min(1).optional(), watchedOutcome: z.enum(["yes", "no"]).optional(), minProbability: z.number().min(0).max(1).optional(), minLiquidity: z.number().nonnegative().optional(), action: z.enum(["buy", "sell"]).optional(), onNoMatch: z.enum(["hold", "unknown"]).optional(), asset: z.string().min(1).optional(), marketSymbol: z.string().min(1).optional(), allocationMode: z.enum(["fixed", "percent_equity"]).optional(), amountUsd: z.number().positive().optional(), percentOfEquity: z.number().positive().optional(), maxPercentOfEquity: z.number().positive().optional(), resolution: z.enum(["1", "5", "15", "30", "60", "240", "1D", "1W"]).optional(), countBack: z.number().int().min(50).max(5000).optional(), historyFidelitySeconds: z.number().int().min(60).max(86400).optional(), schedule: scheduleSchema.optional(), execution: executionSchema.optional(), }) .strict() .refine((value) => Boolean(value.marketId || value.marketSlug || value.conditionId), { message: "marketId, marketSlug, or conditionId is required", path: ["marketSlug"], }); export const requestOverrideSchema = z .object({ marketId: z.string().min(1).optional(), marketSlug: z.string().min(1).optional(), conditionId: z.string().min(1).optional(), watchedOutcome: z.enum(["yes", "no"]).optional(), minProbability: z.number().min(0).max(1).optional(), minLiquidity: z.number().nonnegative().optional(), action: z.enum(["buy", "sell"]).optional(), onNoMatch: z.enum(["hold", "unknown"]).optional(), asset: z.string().min(1).optional(), marketSymbol: z.string().min(1).optional(), allocationMode: z.enum(["fixed", "percent_equity"]).optional(), amountUsd: z.number().positive().optional(), percentOfEquity: z.number().positive().optional(), maxPercentOfEquity: z.number().positive().optional(), resolution: z.enum(["1", "5", "15", "30", "60", "240", "1D", "1W"]).optional(), countBack: z.number().int().min(50).max(5000).optional(), historyFidelitySeconds: z.number().int().min(60).max(86400).optional(), schedule: scheduleOverrideSchema.optional(), execution: executionSchema.optional(), asOf: z.string().datetime().optional(), }) .strict(); export type PolymarketMarketWatcherRequestOverrides = z.infer<typeof requestOverrideSchema>; type PartialScheduleConfig = z.infer<typeof scheduleSchema>; type PartialExecutionConfig = z.infer<typeof executionSchema>; export type PolymarketWatcherSignal = "buy" | "sell" | "hold" | "unknown"; export type ScheduleConfig = { cron: string; enabled: boolean; notifyEmail: boolean; }; export type ExecutionConfig = { enabled: boolean; environment: "testnet" | "mainnet"; symbol?: string; mode: "long-only" | "long-short"; size?: number; leverage?: number; slippageBps: number; }; export type PolymarketMarketWatcherConfig = { configVersion: number; platform: "hyperliquid"; marketId?: string; marketSlug?: string; conditionId?: string; watchedOutcome: "yes" | "no"; minProbability: number; minLiquidity?: number; action: "buy" | "sell"; onNoMatch: "hold" | "unknown"; asset: string; marketSymbol?: string; allocationMode: "fixed" | "percent_equity"; amountUsd?: number; percentOfEquity: number; maxPercentOfEquity: number; resolution: "1" | "5" | "15" | "30" | "60" | "240" | "1D" | "1W"; countBack: number; historyFidelitySeconds: number; schedule: ScheduleConfig; execution: ExecutionConfig; }; const DEFAULT_SCHEDULE: ScheduleConfig = { cron: "0 * * * *", enabled: false, notifyEmail: false, }; const TEMPLATE_CONFIG_DEFAULTS: PolymarketMarketWatcherConfig = { configVersion: TEMPLATE_CONFIG_VERSION, platform: "hyperliquid", marketSlug: "us-x-iran-ceasefire-by-march-31", watchedOutcome: "no", minProbability: 0.8, minLiquidity: 100_000, action: "buy", onNoMatch: "hold", asset: DEFAULT_ASSET, allocationMode: "fixed", amountUsd: DEFAULT_AMOUNT_USD, percentOfEquity: DEFAULT_PERCENT_OF_EQUITY, maxPercentOfEquity: DEFAULT_MAX_PERCENT_OF_EQUITY, resolution: DEFAULT_RESOLUTION, countBack: DEFAULT_COUNT_BACK, historyFidelitySeconds: 3600, schedule: DEFAULT_SCHEDULE, execution: { enabled: false, environment: DEFAULT_EXECUTION_ENV, mode: DEFAULT_EXECUTION_MODE, slippageBps: DEFAULT_SLIPPAGE_BPS, }, }; const TEMPLATE_CONFIG_SCHEMA = { type: "object", required: ["watchedOutcome", "minProbability", "asset"], properties: { configVersion: { type: "number", title: "Config version", description: "Internal schema version for the market watcher defaults.", readOnly: true, "x-hidden": true, }, platform: { type: "string", enum: ["hyperliquid"], title: "Platform", description: "Execution venue for this strategy.", readOnly: true, }, marketSlug: { type: "string", title: "Market slug", description: "Exact Polymarket market slug to watch.", }, marketId: { type: "string", title: "Market id", description: "Exact Polymarket market id if you prefer id-based lookup.", }, conditionId: { type: "string", title: "Condition id", description: "Exact Polymarket condition id for the watched market.", }, watchedOutcome: { type: "string", enum: ["yes", "no"], title: "Watched outcome", description: "Outcome probability to monitor on the chosen market.", }, minProbability: { type: "number", title: "Minimum probability", description: "Trigger once the watched outcome reaches at least this probability.", minimum: 0, maximum: 1, }, minLiquidity: { type: "number", title: "Minimum liquidity", description: "Optional liquidity floor for the watched market.", minimum: 0, }, action: { type: "string", enum: ["buy", "sell"], title: "Trade action", description: "Hyperliquid action to take when the threshold passes.", }, onNoMatch: { type: "string", enum: ["hold", "unknown"], title: "Fallback action", description: "What to return when the threshold is not met.", }, asset: { type: "string", title: "Asset", description: "Default Hyperliquid perp symbol. Use marketSymbol or execution.symbol for exact spot pairs (BASE-QUOTE or BASE/QUOTE) or HIP-3 symbols (dex:BASE).", "x-format": "asset", }, marketSymbol: { type: "string", title: "Exact market symbol", description: "Optional exact Hyperliquid symbol used for replay and execution. Use BASE-QUOTE or BASE/QUOTE for spot, bare BASE for perps, or dex:BASE for HIP-3.", }, allocationMode: { type: "string", enum: ["fixed", "percent_equity"], title: "Allocation mode", description: "Fixed USD or percent-of-equity sizing.", }, amountUsd: { type: "number", title: "Amount USD", description: "Fixed notional size when allocation mode is fixed.", minimum: 1, }, percentOfEquity: { type: "number", title: "Percent of equity", description: "Sizing target when allocation mode is percent-of-equity.", minimum: 0.1, maximum: 100, }, maxPercentOfEquity: { type: "number", title: "Max percent of equity", description: "Absolute sizing cap for percent-of-equity mode.", minimum: 0.1, maximum: 100, }, resolution: { type: "string", enum: ["1", "5", "15", "30", "60", "240", "1D", "1W"], title: "Bar resolution", description: "Resolution used for Hyperliquid price alignment in backtests and live reads.", }, historyFidelitySeconds: { type: "number", title: "Polymarket history fidelity", description: "Sampling interval in seconds for Polymarket price-history replay.", minimum: 60, maximum: 86400, }, schedule: { type: "object", title: "Schedule", properties: { cron: { type: "string", title: "Cron expression" }, enabled: { type: "boolean", title: "Enabled" }, notifyEmail: { type: "boolean", title: "Notify email" }, }, }, execution: { type: "object", title: "Execution", properties: { enabled: { type: "boolean", title: "Enable live execution" }, environment: { type: "string", enum: ["testnet", "mainnet"], title: "Environment", }, 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.", }, mode: { type: "string", enum: ["long-only", "long-short"], title: "Execution mode", }, size: { type: "number", title: "Override size", minimum: 0.00000001, }, leverage: { type: "number", title: "Leverage", minimum: 1, maximum: 50, }, slippageBps: { type: "number", title: "Slippage bps", minimum: 0, maximum: 500, }, }, }, }, }; export const POLYMARKET_MARKET_WATCHER_TEMPLATE_CONFIG = { version: TEMPLATE_CONFIG_VERSION, schema: TEMPLATE_CONFIG_SCHEMA, defaults: TEMPLATE_CONFIG_DEFAULTS, envVar: CONFIG_ENV, }; function parseJson(raw: string | null): unknown { if (!raw) return null; try { return JSON.parse(raw); } catch { return null; } } function mergeSchedule(config?: PartialScheduleConfig): ScheduleConfig { return { cron: config?.cron ?? DEFAULT_SCHEDULE.cron, enabled: config?.enabled ?? DEFAULT_SCHEDULE.enabled, notifyEmail: config?.notifyEmail ?? DEFAULT_SCHEDULE.notifyEmail, }; } function mergeExecution(config?: PartialExecutionConfig): ExecutionConfig { return { enabled: config?.enabled ?? TEMPLATE_CONFIG_DEFAULTS.execution.enabled, environment: config?.environment ?? TEMPLATE_CONFIG_DEFAULTS.execution.environment, ...(config?.symbol?.trim() ? { symbol: config.symbol.trim() } : {}), mode: config?.mode ?? TEMPLATE_CONFIG_DEFAULTS.execution.mode, ...(typeof config?.size === "number" ? { size: config.size } : {}), ...(typeof config?.leverage === "number" ? { leverage: config.leverage } : {}), slippageBps: config?.slippageBps ?? TEMPLATE_CONFIG_DEFAULTS.execution.slippageBps, }; } function normalizeBaseSymbol(value: string): string { const normalized = normalizeHyperliquidBaseSymbol(value); if (normalized) return normalized; return value.trim().toUpperCase(); } function normalizeConfig( raw: z.infer<typeof baseConfigSchema>, ): PolymarketMarketWatcherConfig { return { configVersion: TEMPLATE_CONFIG_VERSION, platform: "hyperliquid", ...(raw.marketId?.trim() ? { marketId: raw.marketId.trim() } : {}), ...(raw.marketSlug?.trim() ? { marketSlug: raw.marketSlug.trim() } : {}), ...(raw.conditionId?.trim() ? { conditionId: raw.conditionId.trim() } : {}), watchedOutcome: raw.watchedOutcome ?? TEMPLATE_CONFIG_DEFAULTS.watchedOutcome, minProbability: raw.minProbability ?? TEMPLATE_CONFIG_DEFAULTS.minProbability, ...(typeof raw.minLiquidity === "number" ? { minLiquidity: raw.minLiquidity } : {}), action: raw.action ?? TEMPLATE_CONFIG_DEFAULTS.action, onNoMatch: raw.onNoMatch ?? TEMPLATE_CONFIG_DEFAULTS.onNoMatch, asset: normalizeBaseSymbol(raw.asset ?? TEMPLATE_CONFIG_DEFAULTS.asset), ...(raw.marketSymbol?.trim() ? { marketSymbol: raw.marketSymbol.trim() } : {}), allocationMode: raw.allocationMode ?? TEMPLATE_CONFIG_DEFAULTS.allocationMode, amountUsd: raw.amountUsd ?? TEMPLATE_CONFIG_DEFAULTS.amountUsd, percentOfEquity: raw.percentOfEquity ?? TEMPLATE_CONFIG_DEFAULTS.percentOfEquity, maxPercentOfEquity: raw.maxPercentOfEquity ?? TEMPLATE_CONFIG_DEFAULTS.maxPercentOfEquity, resolution: raw.resolution ?? TEMPLATE_CONFIG_DEFAULTS.resolution, countBack: raw.countBack ?? TEMPLATE_CONFIG_DEFAULTS.countBack, historyFidelitySeconds: raw.historyFidelitySeconds ?? TEMPLATE_CONFIG_DEFAULTS.historyFidelitySeconds, schedule: mergeSchedule(raw.schedule), execution: mergeExecution(raw.execution), }; } export function resolveRuntimeConfig( overrides?: PolymarketMarketWatcherRequestOverrides, ): PolymarketMarketWatcherConfig { const baseRaw = parseJson(process.env[CONFIG_ENV] ?? null); const parsed = baseConfigSchema.safeParse(baseRaw ?? TEMPLATE_CONFIG_DEFAULTS); const base = parsed.success ? parsed.data : TEMPLATE_CONFIG_DEFAULTS; const merged = { ...base, ...overrides, schedule: { cron: overrides?.schedule?.cron ?? base.schedule?.cron ?? DEFAULT_SCHEDULE.cron, enabled: overrides?.schedule?.enabled ?? base.schedule?.enabled, notifyEmail: overrides?.schedule?.notifyEmail ?? base.schedule?.notifyEmail, }, execution: { ...base.execution, ...overrides?.execution, }, }; return normalizeConfig(merged); } export function resolveScheduleConfig(config: PolymarketMarketWatcherConfig) { if (!config.schedule.cron) return undefined; return { cron: config.schedule.cron, enabled: config.schedule.enabled, notifyEmail: config.schedule.notifyEmail, }; } export function resolveProfileAssets(config: PolymarketMarketWatcherConfig) { return buildHyperliquidProfileAssets({ environment: config.execution.environment, assets: [{ assetSymbols: [config.asset] }], }); }