export interface AutotradeExecutionCostProfile { buyFeeRate: number; sellFeeRate: number; sellTaxRate: number; } export interface AutotradeEstimatedOrderCost { side: "buy" | "sell"; price: number; quantity: number; grossAmount: number; feeAmount: number; taxAmount: number; netAmount: number; } export function resolveExecutionCostProfile(): AutotradeExecutionCostProfile { return { buyFeeRate: readRateFromEnv("AUTOTRADE_BUY_FEE_RATE", 0.00015), sellFeeRate: readRateFromEnv("AUTOTRADE_SELL_FEE_RATE", 0.00015), sellTaxRate: readRateFromEnv("AUTOTRADE_SELL_TAX_RATE", 0.0018), }; } export function estimateBuyUnitCost( price: number, profile: AutotradeExecutionCostProfile, ) { const safePrice = Math.max(0, price); if (safePrice <= 0) return 0; return safePrice * (1 + Math.max(0, profile.buyFeeRate)); } export function estimateSellNetUnit( price: number, profile: AutotradeExecutionCostProfile, ) { const safePrice = Math.max(0, price); if (safePrice <= 0) return 0; const totalRate = Math.max(0, profile.sellFeeRate) + Math.max(0, profile.sellTaxRate); return safePrice * (1 - totalRate); } export function estimateOrderCost(params: { side: "buy" | "sell"; price: number; quantity: number; profile: AutotradeExecutionCostProfile; }): AutotradeEstimatedOrderCost { const safePrice = Math.max(0, params.price); const safeQuantity = Math.max(0, Math.floor(params.quantity)); const grossAmount = safePrice * safeQuantity; if (params.side === "buy") { const feeAmount = grossAmount * Math.max(0, params.profile.buyFeeRate); return { side: params.side, price: safePrice, quantity: safeQuantity, grossAmount, feeAmount, taxAmount: 0, netAmount: grossAmount + feeAmount, }; } const feeAmount = grossAmount * Math.max(0, params.profile.sellFeeRate); const taxAmount = grossAmount * Math.max(0, params.profile.sellTaxRate); return { side: params.side, price: safePrice, quantity: safeQuantity, grossAmount, feeAmount, taxAmount, netAmount: grossAmount - feeAmount - taxAmount, }; } function readRateFromEnv(envName: string, fallback: number) { const raw = Number.parseFloat(process.env[envName] ?? ""); if (!Number.isFinite(raw)) return fallback; if (raw >= 1 && raw <= 100) { return clamp(raw / 100, 0, 0.5); } return clamp(raw, 0, 0.5); } function clamp(value: number, min: number, max: number) { if (!Number.isFinite(value)) return min; return Math.min(max, Math.max(min, value)); }