openpondai/agents/directional-bot
OpenTool app
typescript
import { z } from "zod";
import { buildHyperliquidProfileAssets } from "opentool/adapters/hyperliquid";
export type SimpleHyperliquidScheduleConfig = {
cron: string;
enabled: boolean;
notifyEmail: boolean;
};
export type SimpleHyperliquidExecutionConfig = {
enabled: boolean;
environment: "mainnet" | "testnet";
slippageBps: number;
};
export type SimpleHyperliquidBacktestConfig = {
configVersion?: number;
platform: "hyperliquid";
asset: string;
environment: "mainnet" | "testnet";
budgetUsd: number;
twapDurationDays: number;
monitoringPeriodHours: number;
aggressiveSellThresholdPct: number;
aggressiveMultiplier: number;
resolution: "15" | "60" | "240" | "1D";
schedule?: SimpleHyperliquidScheduleConfig;
execution?: SimpleHyperliquidExecutionConfig;
};
const TEMPLATE_CONFIG_VERSION = 3;
const TEMPLATE_CONFIG_ENV_VAR = "OPENTOOL_PUBLIC_HL_DIRECTIONAL_BOT_CONFIG";
const TEMPLATE_CONFIG_DEFAULTS: SimpleHyperliquidBacktestConfig = {
configVersion: TEMPLATE_CONFIG_VERSION,
platform: "hyperliquid",
asset: "BTC",
environment: "mainnet",
budgetUsd: 1_000,
twapDurationDays: 7,
monitoringPeriodHours: 4,
aggressiveSellThresholdPct: 2,
aggressiveMultiplier: 1.5,
resolution: "60",
schedule: {
cron: "0 * * * *",
enabled: false,
notifyEmail: false,
},
execution: {
enabled: false,
environment: "mainnet",
slippageBps: 30,
},
};
const TEMPLATE_CONFIG_DEFAULT_SCHEDULE = TEMPLATE_CONFIG_DEFAULTS.schedule!;
const TEMPLATE_CONFIG_DEFAULT_EXECUTION = TEMPLATE_CONFIG_DEFAULTS.execution!;
const TEMPLATE_CONFIG_SCHEMA = {
type: "object",
required: [
"platform",
"asset",
"environment",
"budgetUsd",
"twapDurationDays",
"monitoringPeriodHours",
"aggressiveSellThresholdPct",
"aggressiveMultiplier",
"resolution",
],
properties: {
configVersion: {
type: "number",
title: "Config version",
description: "Internal schema version for this TWAP starter.",
readOnly: true,
},
platform: {
type: "string",
enum: ["hyperliquid"],
title: "Platform",
description: "Execution venue for this starter.",
readOnly: true,
},
asset: {
type: "string",
title: "Asset",
description: "Base asset to sell over time.",
},
environment: {
type: "string",
enum: ["mainnet", "testnet"],
title: "Environment",
description: "Hyperliquid environment for reads and live execution.",
},
budgetUsd: {
type: "number",
title: "Budget USD",
description: "Total notional budget distributed across TWAP slices.",
minimum: 1,
},
twapDurationDays: {
type: "number",
title: "TWAP duration days",
description: "How many days the sell program should span.",
minimum: 1,
maximum: 30,
},
monitoringPeriodHours: {
type: "number",
title: "Monitoring period hours",
description: "Hours between baseline TWAP slices and drop checks.",
minimum: 1,
maximum: 24,
},
aggressiveSellThresholdPct: {
type: "number",
title: "Aggressive sell threshold %",
description: "If price drops by at least this percent over one monitoring period, sell more aggressively.",
minimum: 0.1,
maximum: 25,
},
aggressiveMultiplier: {
type: "number",
title: "Aggressive multiplier",
description: "Multiplier applied to the baseline slice when the drop threshold is hit.",
minimum: 1,
maximum: 5,
},
resolution: {
type: "string",
enum: ["15", "60", "240", "1D"],
title: "Bar resolution",
description: "Resolution used for preview backtests and live reads.",
},
schedule: {
type: "object",
title: "Schedule",
description: "Run cadence for scheduled checks.",
properties: {
cron: {
type: "string",
title: "Cron expression",
description: "Cron string for scheduled runs.",
},
enabled: {
type: "boolean",
title: "Enabled",
description: "Enable the schedule.",
},
notifyEmail: {
type: "boolean",
title: "Notify email",
description: "Send an email after each scheduled execution.",
},
},
},
execution: {
type: "object",
title: "Execution",
description: "Optional live execution settings for real Hyperliquid orders.",
properties: {
enabled: {
type: "boolean",
title: "Enable live execution",
description: "If enabled, live runs can place real Hyperliquid marketable orders.",
},
environment: {
type: "string",
enum: ["mainnet", "testnet"],
title: "Execution environment",
description: "Hyperliquid environment used for live execution.",
},
slippageBps: {
type: "number",
title: "Slippage basis points",
description: "Marketable order slippage used for live execution.",
minimum: 0,
maximum: 500,
},
},
},
},
};
export const DIRECTIONAL_BOT_TEMPLATE_CONFIG = {
version: TEMPLATE_CONFIG_VERSION,
schema: TEMPLATE_CONFIG_SCHEMA,
defaults: TEMPLATE_CONFIG_DEFAULTS,
envVar: TEMPLATE_CONFIG_ENV_VAR,
};
const scheduleSchema = z
.object({
cron: z.string().min(1).optional(),
enabled: z.boolean().optional(),
notifyEmail: z.boolean().optional(),
})
.optional();
const executionSchema = z
.object({
enabled: z.boolean().optional(),
environment: z.enum(["mainnet", "testnet"]).optional(),
slippageBps: z.number().min(0).max(500).optional(),
})
.optional();
const configSchema = z.object({
platform: z.literal("hyperliquid").optional(),
asset: z.string().min(1).optional(),
environment: z.enum(["mainnet", "testnet"]).optional(),
budgetUsd: z.number().positive().optional(),
twapDurationDays: z.number().int().min(1).max(30).optional(),
monitoringPeriodHours: z.number().int().min(1).max(24).optional(),
aggressiveSellThresholdPct: z.number().positive().max(25).optional(),
aggressiveMultiplier: z.number().min(1).max(5).optional(),
resolution: z.enum(["15", "60", "240", "1D"]).optional(),
schedule: scheduleSchema,
execution: executionSchema,
});
function normalizeConfig(
config: SimpleHyperliquidBacktestConfig
): SimpleHyperliquidBacktestConfig {
return {
...config,
asset: config.asset.trim().toUpperCase() || TEMPLATE_CONFIG_DEFAULTS.asset,
budgetUsd: Math.max(1, Number(config.budgetUsd)),
twapDurationDays: Math.max(1, Math.round(config.twapDurationDays)),
monitoringPeriodHours: Math.max(1, Math.round(config.monitoringPeriodHours)),
aggressiveSellThresholdPct: Math.max(
0.1,
Number(config.aggressiveSellThresholdPct)
),
aggressiveMultiplier: Math.max(1, Number(config.aggressiveMultiplier)),
};
}
function mergeSchedule(
schedule: Partial<SimpleHyperliquidScheduleConfig> | undefined
): SimpleHyperliquidScheduleConfig {
return {
cron: schedule?.cron ?? TEMPLATE_CONFIG_DEFAULT_SCHEDULE.cron,
enabled: schedule?.enabled ?? TEMPLATE_CONFIG_DEFAULT_SCHEDULE.enabled,
notifyEmail:
schedule?.notifyEmail ?? TEMPLATE_CONFIG_DEFAULT_SCHEDULE.notifyEmail,
};
}
function mergeExecution(
execution: Partial<SimpleHyperliquidExecutionConfig> | undefined
): SimpleHyperliquidExecutionConfig {
return {
enabled: execution?.enabled ?? TEMPLATE_CONFIG_DEFAULT_EXECUTION.enabled,
environment:
execution?.environment ?? TEMPLATE_CONFIG_DEFAULT_EXECUTION.environment,
slippageBps: Math.max(
0,
Math.min(
500,
Number(
execution?.slippageBps ?? TEMPLATE_CONFIG_DEFAULT_EXECUTION.slippageBps
)
)
),
};
}
export function readConfig(): SimpleHyperliquidBacktestConfig {
const raw = process.env[TEMPLATE_CONFIG_ENV_VAR];
if (!raw) {
return normalizeConfig(TEMPLATE_CONFIG_DEFAULTS);
}
try {
const parsed = configSchema.parse(JSON.parse(raw));
return normalizeConfig({
...TEMPLATE_CONFIG_DEFAULTS,
...parsed,
schedule: mergeSchedule(parsed.schedule),
execution: mergeExecution(parsed.execution),
});
} catch {
return normalizeConfig(TEMPLATE_CONFIG_DEFAULTS);
}
}
export function resolveScheduleConfig(config: SimpleHyperliquidBacktestConfig) {
const schedule = config.schedule ?? TEMPLATE_CONFIG_DEFAULT_SCHEDULE;
if (!schedule?.cron) return undefined;
return {
cron: schedule.cron,
enabled: schedule.enabled,
notifyEmail: schedule.notifyEmail,
};
}
export function resolveProfileAssets(config: SimpleHyperliquidBacktestConfig) {
return buildHyperliquidProfileAssets({
environment: config.environment,
assets: [{ assetSymbols: [config.asset] }],
});
}