1Branch0Tags
GL
glucryptoFix exact Hyperliquid symbols
typescript
import { resolveBacktestAccountValueUsd, resolveBacktestWindow } from "opentool/backtest"; import { fetchHyperliquidBars, normalizeHyperliquidBaseSymbol, normalizeHyperliquidIndicatorBars, resolveHyperliquidTargetSize, } from "opentool/adapters/hyperliquid"; import { DEFAULT_EXECUTION_MODE, normalizeConfigMarketSymbol, type IndicatorType, type SignalBotConfig, } from "../config"; import { buildIndicatorDecisionOutput } from "./indicator-output"; import { buildSignalTargetSizeConfig, buildSignalTargetSizeExecution } from "./target-size"; import { resolveTradeSignal } from "./trade-signal"; import type { BacktestDecisionPoint } from "./types"; export type BacktestDecisionSeriesResult = { symbol: string; timeframeStart: string; timeframeEnd: string; barsEvaluated: number; resolution: SignalBotConfig["resolution"]; mode: "long-only" | "long-short"; indicator: IndicatorType; decisions: BacktestDecisionPoint[]; }; export async function buildBacktestDecisionSeries(params: { config: SignalBotConfig; symbol?: string; timeframeStart?: string; timeframeEnd?: string; from?: number; to?: number; lookbackDays?: number; accountValueUsd?: number; }) { const symbolOverride = params.symbol ? normalizeConfigMarketSymbol(params.symbol) : ""; const config: SignalBotConfig = symbolOverride ? { ...params.config, asset: symbolOverride } : params.config; const execution = config.execution ?? {}; const indicator = execution.indicator ?? config.indicators[0] ?? "rsi"; const mode = execution.mode ?? DEFAULT_EXECUTION_MODE; const window = resolveBacktestWindow({ fallbackCountBack: config.countBack, lookbackDays: params.lookbackDays, resolution: config.resolution, from: params.from, to: params.to, timeframeStart: params.timeframeStart, timeframeEnd: params.timeframeEnd, }); const resolvedFrom = window.fromSeconds; const resolvedTo = window.toSeconds; const countBack = window.countBack; const rawBars = await fetchHyperliquidBars({ symbol: config.asset, resolution: config.resolution, countBack, ...(resolvedFrom != null && Number.isFinite(resolvedFrom) ? { fromSeconds: Math.max(0, Math.trunc(resolvedFrom)) } : {}), ...(resolvedTo != null && Number.isFinite(resolvedTo) ? { toSeconds: Math.max(0, Math.trunc(resolvedTo)) } : {}), }); const bars = normalizeHyperliquidIndicatorBars(rawBars); if (bars.length === 0) { throw new Error("No price data returned."); } const accountValue = resolveBacktestAccountValueUsd(params.accountValueUsd) ?? null; const decisions: BacktestDecisionPoint[] = []; for (let index = 0; index < bars.length; index += 1) { const snapshotBars = bars.slice(0, index + 1); const bar = snapshotBars[snapshotBars.length - 1]!; const indicators = buildIndicatorDecisionOutput({ config, bars: snapshotBars, indicator, }); const signal = resolveTradeSignal(indicator, indicators); const { targetSize, budgetUsd } = resolveHyperliquidTargetSize({ config: buildSignalTargetSizeConfig(config), execution: buildSignalTargetSizeExecution(config), accountValue, currentPrice: bar.close, }); decisions.push({ ts: new Date(bar.time).toISOString(), price: bar.close, signal, targetSize, budgetUsd, indicator, indicators, }); } return { symbol: (normalizeHyperliquidBaseSymbol(config.asset) ?? config.asset).toUpperCase(), timeframeStart: resolvedFrom != null ? new Date(resolvedFrom * 1000).toISOString() : new Date(bars[0]!.time).toISOString(), timeframeEnd: resolvedTo != null ? new Date(resolvedTo * 1000).toISOString() : new Date(bars[bars.length - 1]!.time).toISOString(), barsEvaluated: bars.length, resolution: config.resolution, mode, indicator, decisions, }; }