190 lines
5.8 KiB
JavaScript
190 lines
5.8 KiB
JavaScript
// src/api.js
|
|
// 프론트와 API가 동일 도메인(nginx 프록시)이므로 항상 상대 경로 사용.
|
|
// 절대 URL(VITE_API_BASE)은 Mixed Content를 유발하므로 사용하지 않음.
|
|
const toApiUrl = (path) => path;
|
|
|
|
export async function apiGet(path) {
|
|
const res = await fetch(toApiUrl(path), {
|
|
headers: { "Accept": "application/json" },
|
|
});
|
|
if (!res.ok) {
|
|
const text = await res.text().catch(() => "");
|
|
throw new Error(`HTTP ${res.status} ${res.statusText}: ${text}`);
|
|
}
|
|
return res.json();
|
|
}
|
|
|
|
export async function apiDelete(path) {
|
|
const res = await fetch(toApiUrl(path), { method: "DELETE" });
|
|
if (!res.ok) {
|
|
const text = await res.text().catch(() => "");
|
|
throw new Error(`HTTP ${res.status} ${res.statusText}: ${text}`);
|
|
}
|
|
return res.json();
|
|
}
|
|
|
|
export async function apiPost(path, body) {
|
|
const res = await fetch(toApiUrl(path), {
|
|
method: "POST",
|
|
headers: {
|
|
"Accept": "application/json",
|
|
...(body ? { "Content-Type": "application/json" } : {}),
|
|
},
|
|
body: body ? JSON.stringify(body) : undefined,
|
|
});
|
|
if (!res.ok) {
|
|
const text = await res.text().catch(() => "");
|
|
throw new Error(`HTTP ${res.status} ${res.statusText}: ${text}`);
|
|
}
|
|
return res.json();
|
|
}
|
|
|
|
export async function apiPut(path, body) {
|
|
const res = await fetch(toApiUrl(path), {
|
|
method: "PUT",
|
|
headers: {
|
|
"Accept": "application/json",
|
|
...(body ? { "Content-Type": "application/json" } : {}),
|
|
},
|
|
body: body ? JSON.stringify(body) : undefined,
|
|
});
|
|
if (!res.ok) {
|
|
const text = await res.text().catch(() => "");
|
|
throw new Error(`HTTP ${res.status} ${res.statusText}: ${text}`);
|
|
}
|
|
return res.json();
|
|
}
|
|
|
|
export function getLatest() {
|
|
return apiGet("/api/lotto/latest");
|
|
}
|
|
|
|
export function getStats() {
|
|
return apiGet("/api/lotto/stats");
|
|
}
|
|
|
|
export function recommend(params) {
|
|
const qs = new URLSearchParams({
|
|
recent_window: String(params.recent_window),
|
|
recent_weight: String(params.recent_weight),
|
|
avoid_recent_k: String(params.avoid_recent_k),
|
|
});
|
|
return apiGet(`/api/lotto/recommend?${qs.toString()}`);
|
|
}
|
|
|
|
export function getHistory(limit = 30, offset = 0) {
|
|
return apiGet(`/api/history?limit=${limit}&offset=${offset}`);
|
|
}
|
|
|
|
export function deleteHistory(id) {
|
|
return apiDelete(`/api/history/${id}`);
|
|
}
|
|
|
|
// ── 시뮬레이션 관련 API ──────────────────────────────────────────────────────
|
|
|
|
export function getBestPicks(limit = 20) {
|
|
return apiGet(`/api/lotto/best?limit=${limit}`);
|
|
}
|
|
|
|
export function getAnalysis() {
|
|
return apiGet('/api/lotto/analysis');
|
|
}
|
|
|
|
export function triggerSimulate(nCandidates = 20000, topK = 100, bestN = 20) {
|
|
const qs = new URLSearchParams({
|
|
n_candidates: String(nCandidates),
|
|
top_k: String(topK),
|
|
best_n: String(bestN),
|
|
});
|
|
return apiPost(`/api/admin/simulate?${qs.toString()}`);
|
|
}
|
|
|
|
export function getStockNews(limit = 20, category) {
|
|
const qs = new URLSearchParams({ limit: String(limit) });
|
|
if (category) {
|
|
qs.set("category", category);
|
|
}
|
|
return apiGet(`/api/stock/news?${qs.toString()}`);
|
|
}
|
|
|
|
export function getStockIndices() {
|
|
return apiGet("/api/stock/indices");
|
|
}
|
|
|
|
export function getTradeBalance() {
|
|
return apiGet("/api/trade/balance");
|
|
}
|
|
|
|
export function createTradeOrder(payload) {
|
|
return apiPost("/api/trade/order", payload);
|
|
}
|
|
|
|
// ── 포트폴리오 (수동 입력) API ──────────────────────────────────────────────
|
|
|
|
export function getPortfolio() {
|
|
return apiGet("/api/portfolio");
|
|
}
|
|
|
|
export function addPortfolio(item) {
|
|
return apiPost("/api/portfolio", item);
|
|
}
|
|
|
|
export function updatePortfolio(id, fields) {
|
|
return apiPut(`/api/portfolio/${id}`, fields);
|
|
}
|
|
|
|
export function deletePortfolio(id) {
|
|
return apiDelete(`/api/portfolio/${id}`);
|
|
}
|
|
|
|
// ── 예수금 API ───────────────────────────────────────────────────────────────
|
|
|
|
export function upsertCash(broker, cash) {
|
|
return apiPut('/api/portfolio/cash', { broker, cash });
|
|
}
|
|
|
|
export function deleteCash(broker) {
|
|
return apiDelete(`/api/portfolio/cash/${encodeURIComponent(broker)}`);
|
|
}
|
|
|
|
// ── 시장 심리 지표 API ────────────────────────────────────────────────────────
|
|
|
|
// CNN Fear & Greed Index (개발: vite proxy /ext/feargreed, 프로덕션: nginx proxy 필요)
|
|
export async function getFearAndGreed() {
|
|
const res = await fetch('/ext/feargreed', { headers: { Accept: 'application/json' } });
|
|
if (!res.ok) throw new Error(`HTTP ${res.status}`);
|
|
return res.json();
|
|
}
|
|
|
|
// VIX 지수 (Yahoo Finance 공개 API)
|
|
export async function getVix() {
|
|
const res = await fetch('/ext/vix', { headers: { Accept: 'application/json' } });
|
|
if (!res.ok) throw new Error(`HTTP ${res.status}`);
|
|
const data = await res.json();
|
|
const price = data?.chart?.result?.[0]?.meta?.regularMarketPrice;
|
|
if (price === undefined || price === null) throw new Error('VIX 데이터 없음');
|
|
return { value: Math.round(price * 100) / 100 };
|
|
}
|
|
|
|
// ── TODO API ─────────────────────────────────────────────────────────────────
|
|
|
|
export function getTodos() {
|
|
return apiGet('/api/todos');
|
|
}
|
|
|
|
export function addTodo(data) {
|
|
return apiPost('/api/todos', data);
|
|
}
|
|
|
|
export function updateTodo(id, data) {
|
|
return apiPut(`/api/todos/${id}`, data);
|
|
}
|
|
|
|
export function deleteTodo(id) {
|
|
return apiDelete(`/api/todos/${id}`);
|
|
}
|
|
|
|
export function clearTodos() {
|
|
return apiDelete('/api/todos/done');
|
|
}
|