Files
auto-trade/features/autotrade/apis/autotrade.api.ts

218 lines
6.2 KiB
TypeScript

/**
* [파일 역할]
* 자동매매 프론트엔드가 호출하는 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<AutotradeCompileResponse>(
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<AutotradeValidateResponse>(
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<AutotradeSessionResponse>(
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<AutotradeSessionResponse>(
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<AutotradeSignalResponse>(
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<T>(
response: Response,
fallbackMessage: string,
): Promise<T> {
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;
}