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 (

거래 데스크

주식 거래

연결된 계좌 잔고를 확인하고 필요한 주문을 요청하세요.

주식 랩으로 돌아가기

계좌 요약

총 평가금액 {formatNumber(totalEval)}
예수금 {formatNumber(deposit)}
보유 종목 {holdings.length}
{summary.note ? (

{summary.note}

) : null}
{balanceError ?

{balanceError}

: null}

잔고

보유 현황

연결 계좌의 실시간 잔고와 보유 종목을 확인합니다.

{balanceLoading ? ( 조회 중 ) : null}
{[ { label: '총 평가', value: totalEval, }, { label: '예수금', value: deposit, }, ].map((item) => (
{item.label} {formatNumber(item.value)}
))}
{holdings.length ? (
{holdings.map((item, idx) => { const profitLoss = getProfitLoss(item); const profitLossNumeric = toNumeric(profitLoss); const profitClass = profitLossNumeric > 0 ? 'is-up' : profitLossNumeric < 0 ? 'is-down' : profitLossNumeric === 0 ? 'is-flat' : ''; const profitRate = getProfitRate(item); const profitRateNumeric = toNumeric(profitRate); const profitRateClass = profitRateNumeric > 0 ? 'is-up' : profitRateNumeric < 0 ? 'is-down' : profitRateNumeric === 0 ? 'is-flat' : ''; return (

{item.name ?? item.code ?? 'N/A'}

{item.code ?? ''}
수량 {formatNumber(getQty(item))}
매입가 {formatNumber(getBuyPrice(item))}
현재가 {formatNumber( getCurrentPrice(item) )}
수익률 {formatPercent(profitRate)}
평가손익 {formatNumber(profitLoss)}
); })}
) : (

보유 종목이 없습니다.

)}

수동 주문

직접 매수/매도

종목명 또는 종목코드를 입력하고 매수/매도 주문을 요청합니다.

{manualError ? (

{manualError}

) : null} {manualResult ? (

요청 결과

                                {typeof manualResult === 'string'
                                    ? manualResult
                                    : JSON.stringify(manualResult, null, 2)}
                            
) : null}
{kisModal ? (
setKisModal('')} />

주문 결과

{kisModal}
) : null}
); }; export default StockTrade;