Files
auto-trade/lib/autotrade/execution-cost.ts

95 lines
2.5 KiB
TypeScript
Raw Normal View History

2026-03-12 09:26:27 +09:00
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));
}