전체적인 리팩토링
This commit is contained in:
94
lib/autotrade/execution-cost.ts
Normal file
94
lib/autotrade/execution-cost.ts
Normal file
@@ -0,0 +1,94 @@
|
||||
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));
|
||||
}
|
||||
Reference in New Issue
Block a user