import React, { useEffect, useMemo, useState } from 'react'; import { Link } from 'react-router-dom'; import { createTradeOrder, getTradeBalance } from '../../api'; import './Stock.css'; const formatNumber = (value) => { if (value === null || value === undefined || value === '') return '-'; const numeric = Number(value); if (Number.isNaN(numeric)) return value; return new Intl.NumberFormat('ko-KR').format(numeric); }; const formatPercent = (value) => { if (value === null || value === undefined || value === '') return '-'; if (typeof value === 'string' && value.includes('%')) return value; const numeric = Number(value); if (Number.isNaN(numeric)) return value; return `${numeric.toFixed(2)}%`; }; const pickFirst = (...values) => values.find((value) => value !== undefined && value !== null && value !== ''); const getQty = (item) => pickFirst(item?.qty, item?.quantity, item?.holding, item?.hold_qty); const getBuyPrice = (item) => pickFirst( item?.buy_price, item?.avg_price, item?.avg, item?.purchase_price, item?.buyPrice, item?.price ); const getCurrentPrice = (item) => pickFirst( item?.current_price, item?.current, item?.cur_price, item?.now_price, item?.market_price ); const getProfitRate = (item) => pickFirst( item?.profit_rate, item?.profitRate, item?.profit_pct, item?.profitPercent, item?.pnl_rate, item?.return_rate, item?.yield ); const getProfitLoss = (item) => pickFirst(item?.profit_loss, item?.pnl, item?.profitLoss); const toNumeric = (value) => { if (value === null || value === undefined || value === '') return null; const numeric = Number(String(value).replace(/[^0-9.-]/g, '')); return Number.isNaN(numeric) ? null : numeric; }; const StockTrade = () => { const [balance, setBalance] = useState(null); const [balanceLoading, setBalanceLoading] = useState(false); const [balanceError, setBalanceError] = useState(''); const [manualForm, setManualForm] = useState({ code: '', qty: 1, price: 0, type: 'buy', }); const [manualLoading, setManualLoading] = useState(false); const [manualError, setManualError] = useState(''); const [manualResult, setManualResult] = useState(null); const [kisModal, setKisModal] = useState(''); const loadBalance = async () => { setBalanceLoading(true); setBalanceError(''); try { const data = await getTradeBalance(); setBalance(data); } catch (err) { setBalanceError(err?.message ?? String(err)); } finally { setBalanceLoading(false); } }; const submitManualOrder = async (event) => { event.preventDefault(); setManualLoading(true); setManualError(''); setManualResult(null); try { const payload = { ticker: manualForm.code.trim(), action: manualForm.type === 'sell' ? 'SELL' : 'BUY', quantity: Number(manualForm.qty), price: Number(manualForm.price), }; const result = await createTradeOrder(payload); setManualResult(result ?? { ok: true }); if (result?.kis_result !== undefined) { const message = typeof result.kis_result === 'string' ? result.kis_result : JSON.stringify(result.kis_result, null, 2); setKisModal(message); } await loadBalance(); } catch (err) { setManualError(err?.message ?? String(err)); } finally { setManualLoading(false); } }; useEffect(() => { loadBalance(); }, []); const holdings = useMemo(() => { if (!balance) return []; if (Array.isArray(balance.holdings)) return balance.holdings; if (Array.isArray(balance.positions)) return balance.positions; if (Array.isArray(balance.items)) return balance.items; return []; }, [balance]); const summary = balance?.summary ?? {}; const totalEval = summary.total_eval ?? balance?.total_eval ?? balance?.total_value; const deposit = summary.deposit ?? balance?.deposit ?? balance?.available_cash; return (
거래 데스크
연결된 계좌 잔고를 확인하고 필요한 주문을 요청하세요.
계좌 요약
{summary.note}
) : null}{balanceError}
: null}잔고
연결 계좌의 실시간 잔고와 보유 종목을 확인합니다.
{item.name ?? item.code ?? 'N/A'}
{item.code ?? ''}보유 종목이 없습니다.
)}수동 주문
종목명 또는 종목코드를 입력하고 매수/매도 주문을 요청합니다.
{kisModal}