109 lines
3.0 KiB
TypeScript
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,
|
|
};
|
|
}
|