openpondai/agents/pair-trade
OpenTool app
typescript
import type { PairTradeConfig } from "../config";
export const DEFAULT_PAIR_TRADE_SCHEDULE_MINUTES = 60;
export function resolvePairTradeLegAssets(config: PairTradeConfig) {
const longAsset = config.longAsset.trim().toUpperCase();
const shortAsset = config.shortAsset.trim().toUpperCase();
return {
longAsset,
shortAsset,
pairLabel:
longAsset && shortAsset && longAsset !== shortAsset
? `${longAsset}/${shortAsset}`
: longAsset || shortAsset,
};
}
export function resolvePairTradeLegMarkets(config: PairTradeConfig) {
const { longAsset, shortAsset, pairLabel } = resolvePairTradeLegAssets(config);
return {
longAsset,
shortAsset,
longMarketType: config.longMarketType ?? "spot",
shortMarketType: config.shortMarketType ?? "perp",
pairLabel,
};
}
export function resolveBacktestResolution(minutes: number): "60" | "240" | "1D" {
if (minutes <= 60) return "60";
if (minutes <= 240) return "240";
return "1D";
}
export function resolveCronFields(cron: string | undefined) {
if (!cron) return null;
const fields = cron.trim().split(/\s+/).filter(Boolean);
if (fields.length === 5) return fields;
if (fields.length === 6) return fields.slice(1);
return null;
}
export function resolvePairTradeScheduleMinutes(config: PairTradeConfig): number {
const cronFields = resolveCronFields(config.schedule?.cron);
if (!cronFields) {
return DEFAULT_PAIR_TRADE_SCHEDULE_MINUTES;
}
const [minute, hour, dayOfMonth, month, dayOfWeek] = cronFields;
if (
minute &&
hour &&
dayOfMonth &&
month &&
dayOfWeek &&
/^\*\/(\d+)$/.test(minute) &&
hour === "*" &&
dayOfMonth === "*" &&
month === "*" &&
dayOfWeek === "*"
) {
const intervalMinutes = Number.parseInt(minute.slice(2), 10);
if (Number.isFinite(intervalMinutes) && intervalMinutes > 0) {
return intervalMinutes;
}
}
if (
minute &&
hour &&
dayOfMonth &&
month &&
dayOfWeek &&
/^\d+$/.test(minute) &&
hour === "*" &&
dayOfMonth === "*" &&
month === "*" &&
dayOfWeek === "*"
) {
return 60;
}
if (
minute &&
hour &&
dayOfMonth &&
month &&
dayOfWeek &&
/^\d+$/.test(minute) &&
/^\*\/(\d+)$/.test(hour) &&
dayOfMonth === "*" &&
month === "*" &&
dayOfWeek === "*"
) {
const intervalHours = Number.parseInt(hour.slice(2), 10);
if (Number.isFinite(intervalHours) && intervalHours > 0) {
return intervalHours * 60;
}
}
if (
minute &&
hour &&
dayOfMonth &&
month &&
dayOfWeek &&
/^\d+$/.test(minute) &&
/^\d+$/.test(hour) &&
dayOfMonth === "*" &&
month === "*" &&
dayOfWeek === "*"
) {
return 24 * 60;
}
if (
minute &&
hour &&
dayOfMonth &&
month &&
dayOfWeek &&
/^\d+$/.test(minute) &&
/^\d+$/.test(hour) &&
dayOfMonth === "*" &&
month === "*" &&
dayOfWeek !== "*"
) {
const weeklyRuns = dayOfWeek
.split(",")
.map((value) => value.trim())
.filter((value) => value.length > 0).length;
return weeklyRuns > 1
? Math.round((7 * 24 * 60) / weeklyRuns)
: 7 * 24 * 60;
}
if (
minute &&
hour &&
dayOfMonth &&
month &&
dayOfWeek &&
/^\d+$/.test(minute) &&
/^\d+$/.test(hour) &&
dayOfMonth !== "*" &&
month === "*" &&
dayOfWeek === "*"
) {
return 30 * 24 * 60;
}
return DEFAULT_PAIR_TRADE_SCHEDULE_MINUTES;
}
export function computePeriodsPerYear(resolution: string): number {
if (resolution === "1") return 365 * 24 * 60;
if (resolution === "5") return 365 * 24 * 12;
if (resolution === "15") return 365 * 24 * 4;
if (resolution === "30") return 365 * 24 * 2;
if (resolution === "60") return 365 * 24;
if (resolution === "240") return 365 * 6;
if (resolution === "1D") return 365;
return 52;
}
export function resolutionToMinutes(resolution: "5" | "15" | "30" | "60" | "240" | "1D") {
if (resolution === "5") return 5;
if (resolution === "15") return 15;
if (resolution === "30") return 30;
if (resolution === "60") return 60;
if (resolution === "240") return 240;
return 1440;
}