diff --git a/src/api.js b/src/api.js index a357b85..a080079 100644 --- a/src/api.js +++ b/src/api.js @@ -99,6 +99,6 @@ export function getTradeBalance() { return apiGet("/api/trade/balance"); } -export function requestAutoTrade(payload) { - return apiPost("/api/trade/auto", payload); +export function createTradeOrder(payload) { + return apiPost("/api/trade/order", payload); } diff --git a/src/pages/stock/Stock.css b/src/pages/stock/Stock.css index 90655d4..ef70c0f 100644 --- a/src/pages/stock/Stock.css +++ b/src/pages/stock/Stock.css @@ -45,6 +45,15 @@ gap: 14px; } +.stock-ideas { + margin: 0; + padding-left: 18px; + color: var(--muted); + font-size: 13px; + display: grid; + gap: 6px; +} + .stock-card__title { margin: 0; font-weight: 600; @@ -133,6 +142,18 @@ grid-column: 1 / -1; } +.stock-filter-row { + display: grid; + gap: 18px; + grid-template-columns: repeat(auto-fit, minmax(320px, 1fr)); + align-items: stretch; +} + +.stock-filter-row .stock-panel { + width: 100%; + max-width: none; +} + .stock-panel__head { display: flex; justify-content: space-between; @@ -367,44 +388,66 @@ border-radius: 12px; padding: 10px; display: grid; - grid-template-columns: minmax(0, 1.2fr) repeat(4, minmax(0, 0.6fr)); + grid-template-columns: minmax(0, 1.2fr) repeat(5, minmax(0, 0.6fr)); gap: 10px; - font-size: 12px; + font-size: 13px; color: var(--muted); background: rgba(255, 255, 255, 0.02); + align-items: center; } .stock-holdings__name { margin: 0; font-weight: 600; color: var(--text); + font-size: 14px; } .stock-holdings__code { + font-size: 12px; +} + +.stock-holdings__metric { + display: grid; + gap: 4px; + justify-items: start; +} + +.stock-holdings__metric span { font-size: 11px; + color: var(--muted); } -.stock-ai { - display: grid; - gap: 12px; +.stock-holdings__metric strong { + font-size: 14px; + color: var(--text); } -.stock-ai__grid { - display: grid; - gap: 12px; - grid-template-columns: repeat(auto-fit, minmax(220px, 1fr)); +.stock-profit { + color: var(--text); } -.stock-ai__card { +.stock-profit.is-up { + color: #f3a7a7; +} + +.stock-profit.is-down { + color: #9fc5ff; +} + +.stock-profit.is-flat { + color: var(--muted); +} + +.stock-result { border: 1px solid var(--line); border-radius: 14px; padding: 12px; - display: grid; - gap: 10px; background: rgba(0, 0, 0, 0.2); + margin-top: 10px; } -.stock-ai__title { +.stock-result__title { margin: 0; font-size: 12px; text-transform: uppercase; @@ -412,22 +455,53 @@ color: var(--muted); } -.stock-ai__reason { - margin: 0; +.stock-result pre { + margin: 8px 0 0; + white-space: pre-wrap; font-size: 12px; color: var(--muted); - line-height: 1.4; } -.stock-ai__raw { +.stock-modal { + position: fixed; + inset: 0; + z-index: 50; + display: grid; + place-items: center; +} + +.stock-modal__backdrop { + position: absolute; + inset: 0; + background: rgba(0, 0, 0, 0.55); +} + +.stock-modal__card { + position: relative; + width: min(520px, 90vw); border: 1px solid var(--line); - border-radius: 14px; - padding: 12px; - background: rgba(0, 0, 0, 0.2); + border-radius: 16px; + background: var(--surface); + padding: 16px; + display: grid; + gap: 12px; + box-shadow: 0 20px 60px rgba(0, 0, 0, 0.4); } -.stock-ai__raw pre { - margin: 8px 0 0; +.stock-modal__head { + display: flex; + align-items: center; + justify-content: space-between; + gap: 12px; +} + +.stock-modal__head h4 { + margin: 0; + font-size: 16px; +} + +.stock-modal pre { + margin: 0; white-space: pre-wrap; font-size: 12px; color: var(--muted); diff --git a/src/pages/stock/Stock.jsx b/src/pages/stock/Stock.jsx index bbaa271..2749326 100644 --- a/src/pages/stock/Stock.jsx +++ b/src/pages/stock/Stock.jsx @@ -30,7 +30,7 @@ const normalizeIndices = (data) => { if (Array.isArray(data)) { return data.map((item) => ({ - name: item?.name ?? '-', + name: item?.name ?? item?.key ?? '-', value: item?.value ?? '-', change: item?.change_value ?? item?.change ?? '', percent: item?.change_percent ?? item?.percent ?? '', @@ -40,7 +40,7 @@ const normalizeIndices = (data) => { if (Array.isArray(data?.indices)) { return data.indices.map((item) => ({ - name: item?.name ?? '-', + name: item?.name ?? item?.key ?? '-', value: item?.value ?? '-', change: item?.change_value ?? item?.change ?? '', percent: item?.change_percent ?? item?.percent ?? '', @@ -146,9 +146,10 @@ const Stock = () => { 'KOSPI', 'KOSDAQ', 'KOSPI200', - 'USD/KRW', - 'NASDAQ', + '다우산업', + '나스닥', 'S&P500', + '원달러 환율', ]; const sortedIndices = [...indices].sort((a, b) => { const aIndex = indexOrder.indexOf(a.name); @@ -158,7 +159,7 @@ const Stock = () => { } return a.name.localeCompare(b.name); }); - const highlighted = new Set(['KOSPI', 'KOSDAQ', 'USD/KRW']); + const highlighted = new Set(['KOSPI', 'KOSDAQ', '원달러 환율']); const activeNews = newsCategory === 'domestic' ? newsDomestic : newsOverseas; @@ -185,97 +186,85 @@ const Stock = () => {
뉴스 요약
-다음 업데이트 아이디어
+스냅샷
-- 주요 지수 값과 등락을 함께 확인합니다. -
-스냅샷
++ 주요 지수 값과 등락을 함께 확인합니다. +
{indicesError}
- ) : sortedIndices.length === 0 ? ( -- 지수 데이터가 없습니다. -
- ) : ( - sortedIndices.map((item) => { - const direction = getDirection( - item.change, - item.percent, - item.direction - ); - const changeText = [ - item.change, - item.percent, - ] - .filter(Boolean) - .join(' '); - return ( -{item.name}
- {item.value ?? '--'} - - {changeText || '--'} - -{indicesError}
+ ) : sortedIndices.length === 0 ? ( ++ 지수 데이터가 없습니다. +
+ ) : ( + sortedIndices.map((item) => { + const direction = getDirection( + item.change, + item.percent, + item.direction + ); + const changeText = [item.change, item.percent] + .filter(Boolean) + .join(' '); + return ( +{item.name}
+ {item.value ?? '--'} + + {changeText || '--'} + +필터
@@ -306,6 +295,31 @@ const Stock = () => {요약
++ 최신 발행 시각과 기사 수를 확인합니다. +
+거래 데스크
- 연결된 계좌 잔고를 확인하고 AI 자동 매매 판단을 - 요청하세요. + 연결된 계좌 잔고를 확인하고 필요한 주문을 요청하세요.
{balanceError}
: null} - {autoError ?{autoError}
: null}- {item.name ?? item.code ?? 'N/A'} -
- - {item.code ?? ''} - + {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 ?? ''} + +보유 종목이 없습니다.
@@ -257,94 +304,119 @@ const StockTrade = () => {자동 매매
-수동 주문
+- 분석에 몇 초 걸릴 수 있습니다. 결과는 아래에 - 표시됩니다. + 종목명 또는 종목코드를 입력하고 매수/매도 주문을 + 요청합니다.
- 아직 자동 매매 요청이 없습니다. -
- ) : autoStatus === 'failed_parse' ? ( -원문 응답
+{kisModal}
+