Files
auto-trade/features/trade/hooks/useOrder.ts

109 lines
3.0 KiB
TypeScript

import { useState, useCallback } from "react";
import { z } from "zod";
import type { KisRuntimeCredentials } from "@/features/settings/store/use-kis-runtime-store";
import type {
DashboardStockCashOrderRequest,
DashboardStockCashOrderResponse,
} from "@/features/trade/types/trade.types";
import { fetchOrderCash } from "@/features/trade/apis/kis-stock.api";
import { parseKisAccountParts } from "@/lib/kis/account";
const placeOrderRequestSchema = z
.object({
symbol: z.string().trim().regex(/^\d{6}$/),
side: z.enum(["buy", "sell"]),
orderType: z.enum(["limit", "market"]),
quantity: z.number().int().positive(),
price: z.number(),
accountNo: z.string().trim().min(1),
accountProductCode: z.string().trim().optional(),
})
.superRefine((request, ctx) => {
if (request.orderType === "limit" && request.price <= 0) {
ctx.addIssue({
code: z.ZodIssueCode.custom,
path: ["price"],
message: "지정가 주문은 가격이 0보다 커야 합니다.",
});
}
if (request.orderType === "market" && request.price < 0) {
ctx.addIssue({
code: z.ZodIssueCode.custom,
path: ["price"],
message: "시장가 주문은 가격이 0 이상이어야 합니다.",
});
}
if (!parseKisAccountParts(request.accountNo, request.accountProductCode)) {
ctx.addIssue({
code: z.ZodIssueCode.custom,
path: ["accountNo"],
message: "계좌번호 형식이 올바르지 않습니다. (8-2)",
});
}
});
export function useOrder() {
const [isLoading, setIsLoading] = useState(false);
const [error, setError] = useState<string | null>(null);
const [result, setResult] = useState<DashboardStockCashOrderResponse | null>(
null,
);
const placeOrder = useCallback(
async (
request: DashboardStockCashOrderRequest,
credentials: KisRuntimeCredentials | null,
) => {
if (!credentials) {
setError("KIS API 자격 증명이 없습니다.");
return null;
}
setIsLoading(true);
setError(null);
setResult(null);
try {
const validationResult = placeOrderRequestSchema.safeParse(request);
if (!validationResult.success) {
setError(
validationResult.error.issues[0]?.message ??
"주문 요청 값이 올바르지 않습니다.",
);
return null;
}
const data = await fetchOrderCash(request, credentials);
setResult(data);
return data;
} catch (err) {
const message =
err instanceof Error
? err.message
: "주문 처리 중 오류가 발생했습니다.";
setError(message);
return null;
} finally {
setIsLoading(false);
}
},
[],
);
const reset = useCallback(() => {
setError(null);
setResult(null);
setIsLoading(false);
}, []);
return {
placeOrder,
isLoading,
error,
result,
reset,
};
}