전체적인 리팩토링
This commit is contained in:
@@ -1,4 +1,4 @@
|
||||
import { kisPost } from "@/lib/kis/client";
|
||||
import { kisGet, kisPost } from "@/lib/kis/client";
|
||||
import { KisCredentialInput } from "@/lib/kis/config";
|
||||
import {
|
||||
DashboardOrderSide,
|
||||
@@ -25,6 +25,14 @@ interface KisOrderCashBody {
|
||||
ORD_UNPR: string; // 주문단가
|
||||
}
|
||||
|
||||
interface KisInquirePsblOrderOutput {
|
||||
ord_psbl_cash?: string; // 주문가능현금
|
||||
nrcvb_buy_amt?: string; // 미수없는매수금액
|
||||
max_buy_amt?: string; // 최대매수금액
|
||||
nrcvb_buy_qty?: string; // 미수없는매수수량
|
||||
max_buy_qty?: string; // 최대매수수량
|
||||
}
|
||||
|
||||
/**
|
||||
* 현금 주문(매수/매도) 실행
|
||||
*/
|
||||
@@ -62,6 +70,57 @@ export async function executeOrderCash(
|
||||
return response.output ?? {};
|
||||
}
|
||||
|
||||
/**
|
||||
* 매수가능금액(주문가능현금) 조회
|
||||
*/
|
||||
export async function executeInquireOrderableCash(
|
||||
params: {
|
||||
symbol: string;
|
||||
price: number;
|
||||
orderType: DashboardOrderType;
|
||||
accountNo: string;
|
||||
accountProductCode: string;
|
||||
},
|
||||
credentials?: KisCredentialInput,
|
||||
) {
|
||||
const trId = resolveInquireOrderableTrId(credentials?.tradingEnv);
|
||||
const ordDvsn = resolveOrderDivision(params.orderType);
|
||||
const ordUnpr = Math.max(1, Math.floor(params.price || 0));
|
||||
|
||||
const response = await kisGet<KisInquirePsblOrderOutput | KisInquirePsblOrderOutput[]>(
|
||||
"/uapi/domestic-stock/v1/trading/inquire-psbl-order",
|
||||
trId,
|
||||
{
|
||||
CANO: params.accountNo,
|
||||
ACNT_PRDT_CD: params.accountProductCode,
|
||||
PDNO: params.symbol,
|
||||
ORD_UNPR: String(ordUnpr),
|
||||
ORD_DVSN: ordDvsn,
|
||||
CMA_EVLU_AMT_ICLD_YN: "N",
|
||||
OVRS_ICLD_YN: "N",
|
||||
},
|
||||
credentials,
|
||||
);
|
||||
|
||||
const rawRow = Array.isArray(response.output)
|
||||
? (response.output[0] ?? {})
|
||||
: (response.output ?? {});
|
||||
|
||||
const orderableCash = pickFirstPositive(
|
||||
toSafeNumber(rawRow.nrcvb_buy_amt),
|
||||
toSafeNumber(rawRow.ord_psbl_cash),
|
||||
toSafeNumber(rawRow.max_buy_amt),
|
||||
);
|
||||
|
||||
return {
|
||||
orderableCash,
|
||||
noReceivableBuyAmount: toSafeNumber(rawRow.nrcvb_buy_amt),
|
||||
maxBuyAmount: toSafeNumber(rawRow.max_buy_amt),
|
||||
noReceivableBuyQuantity: Math.floor(toSafeNumber(rawRow.nrcvb_buy_qty)),
|
||||
maxBuyQuantity: Math.floor(toSafeNumber(rawRow.max_buy_qty)),
|
||||
};
|
||||
}
|
||||
|
||||
function resolveOrderTrId(side: DashboardOrderSide, env?: "real" | "mock") {
|
||||
const isMock = env === "mock";
|
||||
if (side === "buy") {
|
||||
@@ -73,8 +132,27 @@ function resolveOrderTrId(side: DashboardOrderSide, env?: "real" | "mock") {
|
||||
}
|
||||
}
|
||||
|
||||
function resolveInquireOrderableTrId(env?: "real" | "mock") {
|
||||
return env === "mock" ? "VTTC8908R" : "TTTC8908R";
|
||||
}
|
||||
|
||||
function resolveOrderDivision(type: DashboardOrderType) {
|
||||
// 00: 지정가, 01: 시장가
|
||||
if (type === "market") return "01";
|
||||
return "00";
|
||||
}
|
||||
|
||||
function toSafeNumber(value?: string) {
|
||||
if (!value) return 0;
|
||||
const parsed = Number(value.replaceAll(",", "").trim());
|
||||
if (!Number.isFinite(parsed)) return 0;
|
||||
return parsed;
|
||||
}
|
||||
|
||||
function pickFirstPositive(...values: number[]) {
|
||||
for (const value of values) {
|
||||
if (value > 0) return value;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user