전체적인 리팩토링
This commit is contained in:
68
tests/autotrade/order-guard-cost.test.ts
Normal file
68
tests/autotrade/order-guard-cost.test.ts
Normal file
@@ -0,0 +1,68 @@
|
||||
import assert from "node:assert/strict";
|
||||
import test from "node:test";
|
||||
import { clampExecutableOrderQuantity } from "../../lib/autotrade/executable-order-quantity";
|
||||
import {
|
||||
estimateBuyUnitCost,
|
||||
estimateOrderCost,
|
||||
resolveExecutionCostProfile,
|
||||
} from "../../lib/autotrade/execution-cost";
|
||||
|
||||
test("SELL 차단: 보유 없음/매도가능 없음", () => {
|
||||
const result = clampExecutableOrderQuantity({
|
||||
side: "sell",
|
||||
requestedQuantity: 1,
|
||||
holdingQuantity: 0,
|
||||
sellableQuantity: 0,
|
||||
});
|
||||
|
||||
assert.equal(result.ok, false);
|
||||
assert.equal(result.quantity, 0);
|
||||
});
|
||||
|
||||
test("SELL 제한: 보유보다 큰 수량 또는 매도가능 부족 시 clamp", () => {
|
||||
const result = clampExecutableOrderQuantity({
|
||||
side: "sell",
|
||||
requestedQuantity: 5,
|
||||
holdingQuantity: 10,
|
||||
sellableQuantity: 3,
|
||||
});
|
||||
|
||||
assert.equal(result.ok, true);
|
||||
assert.equal(result.quantity, 3);
|
||||
assert.equal(result.adjusted, true);
|
||||
});
|
||||
|
||||
test("BUY 제한: 최대 매수 가능 수량으로 clamp", () => {
|
||||
const result = clampExecutableOrderQuantity({
|
||||
side: "buy",
|
||||
requestedQuantity: 4,
|
||||
maxBuyQuantity: 2,
|
||||
});
|
||||
|
||||
assert.equal(result.ok, true);
|
||||
assert.equal(result.quantity, 2);
|
||||
assert.equal(result.adjusted, true);
|
||||
});
|
||||
|
||||
test("수수료/세금 추정: buy/sell 순액 계산", () => {
|
||||
const profile = resolveExecutionCostProfile();
|
||||
const buyUnitCost = estimateBuyUnitCost(10_000, profile);
|
||||
const buy = estimateOrderCost({
|
||||
side: "buy",
|
||||
price: 10_000,
|
||||
quantity: 10,
|
||||
profile,
|
||||
});
|
||||
const sell = estimateOrderCost({
|
||||
side: "sell",
|
||||
price: 10_000,
|
||||
quantity: 10,
|
||||
profile,
|
||||
});
|
||||
|
||||
assert.ok(buyUnitCost > 10_000);
|
||||
assert.ok(buy.netAmount > buy.grossAmount);
|
||||
assert.ok(sell.netAmount < sell.grossAmount);
|
||||
assert.ok(sell.taxAmount >= 0);
|
||||
});
|
||||
|
||||
82
tests/autotrade/risk-budget.test.ts
Normal file
82
tests/autotrade/risk-budget.test.ts
Normal file
@@ -0,0 +1,82 @@
|
||||
import assert from "node:assert/strict";
|
||||
import test from "node:test";
|
||||
import {
|
||||
calculateEffectiveAllocationAmount,
|
||||
calculateEffectiveDailyLossLimit,
|
||||
resolveValidationCashBalance,
|
||||
resolveOrderQuantity,
|
||||
} from "../../lib/autotrade/risk";
|
||||
|
||||
test("allocationPercent와 allocationAmount를 함께 반영해 실주문 예산 계산", () => {
|
||||
assert.equal(calculateEffectiveAllocationAmount(2_000_000, 10, 500_000), 200_000);
|
||||
assert.equal(calculateEffectiveAllocationAmount(2_000_000, 30, 300_000), 300_000);
|
||||
assert.equal(calculateEffectiveAllocationAmount(2_000_000, 0, 300_000), 0);
|
||||
assert.equal(calculateEffectiveAllocationAmount(0, 30, 300_000), 0);
|
||||
});
|
||||
|
||||
test("dailyLossPercent와 dailyLossAmount를 함께 반영해 실손실 한도 계산", () => {
|
||||
assert.equal(calculateEffectiveDailyLossLimit(200_000, 2, 50_000), 4_000);
|
||||
assert.equal(calculateEffectiveDailyLossLimit(200_000, 10, 5_000), 5_000);
|
||||
assert.equal(calculateEffectiveDailyLossLimit(200_000, 0, 5_000), 5_000);
|
||||
});
|
||||
|
||||
test("매수 수량은 비용 포함 단가 기준으로 계산", () => {
|
||||
const quantity = resolveOrderQuantity({
|
||||
side: "buy",
|
||||
price: 10_000,
|
||||
unitCost: 10_015,
|
||||
requestedQuantity: undefined,
|
||||
effectiveAllocationAmount: 100_000,
|
||||
maxOrderAmountRatio: 1,
|
||||
});
|
||||
|
||||
assert.equal(quantity, 9);
|
||||
});
|
||||
|
||||
test("매수 수량: 비율 상한으로 0주여도 전체 예산 1주 가능하면 최소 1주 허용", () => {
|
||||
const quantity = resolveOrderQuantity({
|
||||
side: "buy",
|
||||
price: 16_730,
|
||||
unitCost: 16_730,
|
||||
requestedQuantity: undefined,
|
||||
effectiveAllocationAmount: 21_631,
|
||||
maxOrderAmountRatio: 0.25,
|
||||
});
|
||||
|
||||
assert.equal(quantity, 1);
|
||||
});
|
||||
|
||||
test("매도 수량은 예산과 무관하게 요청값 기준(이후 보유/매도가능 검증)", () => {
|
||||
const quantity = resolveOrderQuantity({
|
||||
side: "sell",
|
||||
price: 10_000,
|
||||
requestedQuantity: 3,
|
||||
effectiveAllocationAmount: 1_000,
|
||||
maxOrderAmountRatio: 0.1,
|
||||
});
|
||||
|
||||
assert.equal(quantity, 3);
|
||||
});
|
||||
|
||||
test("검증 기준 자금: 예수금/매수가능금액 중 보수적인 값 사용", () => {
|
||||
const bothAvailable = resolveValidationCashBalance({
|
||||
cashBalance: 500_000,
|
||||
orderableCash: 430_000,
|
||||
});
|
||||
assert.equal(bothAvailable.cashBalance, 430_000);
|
||||
assert.equal(bothAvailable.source, "min_of_both");
|
||||
|
||||
const onlyOrderable = resolveValidationCashBalance({
|
||||
cashBalance: 0,
|
||||
orderableCash: 36_664,
|
||||
});
|
||||
assert.equal(onlyOrderable.cashBalance, 36_664);
|
||||
assert.equal(onlyOrderable.source, "orderable_cash");
|
||||
|
||||
const onlyCash = resolveValidationCashBalance({
|
||||
cashBalance: 120_000,
|
||||
orderableCash: 0,
|
||||
});
|
||||
assert.equal(onlyCash.cashBalance, 120_000);
|
||||
assert.equal(onlyCash.source, "cash_balance");
|
||||
});
|
||||
Reference in New Issue
Block a user