전체적인 리팩토링
This commit is contained in:
217
features/autotrade/apis/autotrade.api.ts
Normal file
217
features/autotrade/apis/autotrade.api.ts
Normal file
@@ -0,0 +1,217 @@
|
||||
/**
|
||||
* [파일 역할]
|
||||
* 자동매매 프론트엔드가 호출하는 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;
|
||||
}
|
||||
Reference in New Issue
Block a user