/** * [파일 역할] * 자동매매 프론트엔드가 호출하는 API 클라이언트 모음입니다. * * [주요 책임] * - compile/validate/session/signal 관련 Next API 호출을 캡슐화합니다. * - 공통 응답 파싱/오류 메시지 처리를 제공합니다. */ import type { KisRuntimeCredentials } from "@/features/settings/store/use-kis-runtime-store"; import { buildKisRequestHeaders } from "@/features/settings/apis/kis-api-utils"; import type { AutotradeAiMode, AutotradeCompileResponse, AutotradeCompiledStrategy, AutotradeMarketSnapshot, AutotradeSessionInfo, AutotradeSessionResponse, AutotradeSignalResponse, AutotradeStopReason, AutotradeValidateResponse, } from "@/features/autotrade/types/autotrade.types"; interface AutotradeErrorPayload { ok?: boolean; message?: string; errorCode?: string; } // [목적] UI 설정값을 서버 compile 라우트로 전달해 실행 전략(JSON)을 받습니다. export async function compileAutotradeStrategy(payload: { aiMode: AutotradeAiMode; subscriptionCliVendor?: "auto" | "codex" | "gemini"; subscriptionCliModel?: string; prompt: string; selectedTechniques: AutotradeCompiledStrategy["selectedTechniques"]; confidenceThreshold: number; }) { const response = await fetch("/api/autotrade/strategies/compile", { method: "POST", headers: { "content-type": "application/json", }, body: JSON.stringify(payload), cache: "no-store", }); return parseAutotradeResponse( response, "자동매매 전략 컴파일 중 오류가 발생했습니다.", ); } // [목적] 가용자산/손실한도를 서버에서 동일 규칙으로 계산해 검증 결과를 받습니다. export async function validateAutotradeStrategy(payload: { cashBalance: number; allocationPercent: number; allocationAmount: number; dailyLossPercent: number; dailyLossAmount: number; }) { const response = await fetch("/api/autotrade/strategies/validate", { method: "POST", headers: { "content-type": "application/json", }, body: JSON.stringify(payload), cache: "no-store", }); return parseAutotradeResponse( response, "자동매매 리스크 검증 중 오류가 발생했습니다.", ); } // [목적] 자동매매 실행 세션을 서버에 등록합니다. export async function startAutotradeSession( payload: { symbol: string; leaderTabId: string; effectiveAllocationAmount: number; effectiveDailyLossLimit: number; strategySummary: string; }, credentials: KisRuntimeCredentials, ) { const response = await fetch("/api/autotrade/sessions/start", { method: "POST", headers: { ...buildKisRequestHeaders(credentials, { jsonContentType: true, includeAccountNo: true, }), }, body: JSON.stringify(payload), cache: "no-store", }); return parseAutotradeResponse( response, "자동매매 세션 시작 중 오류가 발생했습니다.", ); } // [목적] 실행 중 세션 생존 신호를 주기적으로 갱신합니다. export async function heartbeatAutotradeSession(payload: { sessionId: string; leaderTabId: string; }) { const response = await fetch("/api/autotrade/sessions/heartbeat", { method: "POST", headers: { "content-type": "application/json", }, body: JSON.stringify(payload), cache: "no-store", }); return parseAutotradeResponse( response, "자동매매 heartbeat 전송 중 오류가 발생했습니다.", ); } // [목적] 수동/비상/종료 등 중지 사유를 서버 세션에 반영합니다. export async function stopAutotradeSession(payload: { sessionId?: string; reason?: AutotradeStopReason; }) { const response = await fetch("/api/autotrade/sessions/stop", { method: "POST", headers: { "content-type": "application/json", }, body: JSON.stringify(payload), cache: "no-store", }); return parseAutotradeResponse<{ ok: boolean; session: AutotradeSessionInfo | null; }>(response, "자동매매 세션 종료 중 오류가 발생했습니다."); } // [목적] 현재 사용자의 실행 중 세션 존재 여부를 조회합니다. export async function fetchActiveAutotradeSession() { const response = await fetch("/api/autotrade/sessions/active", { method: "GET", cache: "no-store", }); return parseAutotradeResponse<{ ok: boolean; session: AutotradeSessionInfo | null; }>(response, "자동매매 세션 조회 중 오류가 발생했습니다."); } // [목적] 시세 스냅샷 + 전략을 서버에 보내 매수/매도/대기 신호를 생성합니다. export async function generateAutotradeSignal(payload: { aiMode: AutotradeAiMode; subscriptionCliVendor?: "auto" | "codex" | "gemini"; subscriptionCliModel?: string; prompt: string; strategy: AutotradeCompiledStrategy; snapshot: AutotradeMarketSnapshot; }) { const response = await fetch("/api/autotrade/signals/generate", { method: "POST", headers: { "content-type": "application/json", }, body: JSON.stringify(payload), cache: "no-store", }); return parseAutotradeResponse( response, "자동매매 신호 생성 중 오류가 발생했습니다.", ); } // [목적] 브라우저 종료 직전 stop 요청을 보내기 위한 비동기 beacon 경로입니다. export function sendAutotradeStopBeacon(payload: { sessionId?: string; reason: AutotradeStopReason; }) { if (typeof navigator === "undefined") return false; try { const body = JSON.stringify(payload); const blob = new Blob([body], { type: "application/json" }); return navigator.sendBeacon("/api/autotrade/sessions/stop", blob); } catch { return false; } } async function parseAutotradeResponse( response: Response, fallbackMessage: string, ): Promise { let payload: unknown = null; try { payload = (await response.json()) as unknown; } catch { throw new Error(fallbackMessage); } if (!response.ok) { const errorPayload = payload as AutotradeErrorPayload; throw new Error(errorPayload.message || fallbackMessage); } return payload as T; }