diff --git a/App.tsx b/App.tsx index cde410e..1f6e6e2 100644 --- a/App.tsx +++ b/App.tsx @@ -15,7 +15,9 @@ import { Star, LayoutGrid, RotateCcw, - Compass + Compass, + ArrowUpRight, + ArrowDownRight } from 'lucide-react'; import { ApiSettings, StockItem, OrderType, MarketType, TradeOrder, AutoTradeConfig, WatchlistGroup, ReservedOrder, StockTick } from './types'; import { MOCK_STOCKS } from './constants'; @@ -38,14 +40,39 @@ interface LogEntry { timestamp: Date; } -const TopNavItem: React.FC<{ to: string, icon: React.ReactNode, label: string, active: boolean }> = ({ to, icon, label, active }) => ( - - {/* Fix for line 43: Use React.isValidElement and cast to React.ReactElement to allow the size prop for Lucide icons */} +const TopNavItem: React.FC<{ to: string, icon: React.ReactNode, label: string, active: boolean, className?: string }> = ({ to, icon, label, active, className = "" }) => ( + {React.isValidElement(icon) ? React.cloneElement(icon as React.ReactElement, { size: 16 }) : icon} {label} ); +const IndexTicker = () => { + const indices = [ + { name: '코스피', value: '2,561.32', change: '+12.45', percent: '0.49%', isUp: true }, + { name: '코스닥', value: '842.11', change: '-3.21', percent: '0.38%', isUp: false }, + { name: '나스닥', value: '15,628.95', change: '+215.12', percent: '1.40%', isUp: true }, + { name: 'S&P 500', value: '4,850.12', change: '+45.23', percent: '0.94%', isUp: true }, + { name: 'USD/KRW', value: '1,324.50', change: '+2.10', percent: '0.16%', isUp: true }, + ]; + + return ( +
+ {indices.map((idx, i) => ( +
+ {idx.name} + {idx.value} +
+ {idx.isUp ? : } + {idx.percent} +
+ {i < indices.length - 1 &&
} +
+ ))} +
+ ); +}; + const AppContent: React.FC = () => { const [settings, setSettings] = useState(() => { const saved = localStorage.getItem('trader_settings'); @@ -173,13 +200,13 @@ const AppContent: React.FC = () => {
@@ -191,6 +218,7 @@ const AppContent: React.FC = () => {
+
diff --git a/backend/ReadMe.md b/backend/ReadMe.md index 5771575..02c5f0c 100644 --- a/backend/ReadMe.md +++ b/backend/ReadMe.md @@ -7,7 +7,11 @@ ### 1.1 Headless Execution Engine 1. **Batch Engine**: 매 1분마다 `auto_trade_configs`를 스캔하여 예약된 시각에 주문 실행. 2. **Monitoring Engine**: WebSocket 시세를 수신하여 `reserved_orders` 조건 감시 및 자동 매매. -3. **AI Proxy**: API 보안을 위해 AI 분석 및 뉴스 요청 중계. +3. **Market Index Collector**: + - **조회**: 매 5분마다 주요 시장 지수(KOSPI, KOSDAQ, NASDAQ, S&P500, USD/KRW) 수신. + - **기록**: 1시간마다 해당 시점의 최종 데이터를 `market_index_history` 테이블에 기록(Upsert). + - **프론트 연동**: 프론트엔드는 DB에 저장된 최신 데이터를 5분 단위로 폴링하여 대시보드 업데이트. +4. **AI Proxy**: API 보안을 위해 AI 분석 및 뉴스 요청 중계. ## 2. 상세 명세 가이드 - **DB 스키마**: [tables.md](./tables.md) 참조. diff --git a/backend/tables.md b/backend/tables.md index 6b7dee4..ea6c26c 100644 --- a/backend/tables.md +++ b/backend/tables.md @@ -69,3 +69,11 @@ - `foreignNetBuy`: INTEGER - `institutionalNetBuy`: INTEGER - **용도**: `StockItem` 테이블을 직접 확장하거나 별도 통계 테이블로 관리하여 발굴 데이터 조회 성능 최적화. +## 9. market_index_history (시장 지수 이력) +- `index_id`: TEXT (PK - KOSPI, KOSDAQ, NASDAQ, SP500, USDKRW 등) +- `timestamp`: DATETIME (PK - 1시간 단위 정규화된 시각) +- `value`: REAL +- `change`: REAL +- `change_percent`: REAL +- `updated_at`: DATETIME (실제 마지막 갱신 시각) +- **용도**: 트렌드 분석 및 대시보드 인덱스 카드 표시용. 백엔드가 5분 단위로 조회하되, DB에는 1시간 단위로 마지막 데이터를 Upsert 하여 누적. diff --git a/components/StockRow.tsx b/components/StockRow.tsx index b2d6d51..7a48125 100644 --- a/components/StockRow.tsx +++ b/components/StockRow.tsx @@ -25,7 +25,7 @@ export const StockRow: React.FC = ({ className="group cursor-pointer transition-colors hover:bg-slate-50/70" > {showRank && ( - + {rank} )} @@ -39,26 +39,26 @@ export const StockRow: React.FC = ({ )} -
+
{stock.name[0]}
- {stock.name} - {stock.code} + {stock.name} + {stock.code}
- + {stock.market === MarketType.DOMESTIC ? stock.price.toLocaleString() + '원' : '$' + stock.price} {showPL ? (
= 0 ? 'text-rose-500' : 'text-blue-600'}> -

{showPL.pl.toLocaleString()}

-

({showPL.percent.toFixed(2)}%)

+

{showPL.pl.toLocaleString()}

+

({showPL.percent.toFixed(2)}%)

) : ( - = 0 ? 'text-rose-500' : 'text-blue-600'}`}> + = 0 ? 'text-rose-500' : 'text-blue-600'}`}> {stock.changePercent >= 0 ? '+' : ''}{stock.changePercent}% )} @@ -70,7 +70,7 @@ export const StockRow: React.FC = ({
-
+
{stock.buyRatio || 50} {stock.sellRatio || 50}
diff --git a/pages/AutoTrading.tsx b/pages/AutoTrading.tsx index 410a123..118261a 100644 --- a/pages/AutoTrading.tsx +++ b/pages/AutoTrading.tsx @@ -63,85 +63,85 @@ const AutoTrading: React.FC = ({ marketMode, stocks, configs, const filteredGroups = groups.filter(g => g.codes.some(code => stocks.find(s => s.code === code)?.market === marketMode)); return ( -
-
+
+
-

- {marketMode === MarketType.DOMESTIC ? '국내' : '해외'} 매매 엔진 +

+ {marketMode === MarketType.DOMESTIC ? '국내' : '해외'} 매매 엔진

-

- +

+ c.active && c.market === marketMode).length > 0 ? 'bg-emerald-400' : 'bg-slate-300'}`}> - c.active && c.market === marketMode).length > 0 ? 'bg-emerald-500' : 'bg-slate-400'}`}> + c.active && c.market === marketMode).length > 0 ? 'bg-emerald-500' : 'bg-slate-400'}`}> - 현재 {configs.filter(c => c.active && c.market === marketMode).length}개의 로봇 에이전트 활성화됨 + {configs.filter(c => c.active && c.market === marketMode).length}개의 로봇 활성화됨

-
+
{configs.filter(c => c.market === marketMode).map(config => ( -
-
+
+
-
-
-
- {config.groupId ? : } +
+
+
+ {config.groupId ? : }
-

{config.stockName}

-

{config.groupId ? 'GROUP AGENT' : config.stockCode}

+

{config.stockName}

+

{config.groupId ? 'GROUP AGENT' : config.stockCode}

{/* 활성화 토글 스위치 */}
-
-
-
+
+
+
오퍼레이션 {config.groupId ? '그룹 일괄' : '개별 자산'}
-
+
코어 전략 - + {config.type === 'ACCUMULATION' ? '적립식 매수' : 'TS 자동매매'}
-
- 스케줄링 - {getDayLabel(config)} +
+ 스케줄링 + {getDayLabel(config)}
-
- 실행정보 - {config.executionTime} / {config.quantity}주 +
+ 실행정보 + {config.executionTime} / {config.quantity}주
-
-
- - {config.active ? '에이전트 운용 중' : '일시 중단됨'} +
+
+ + {config.active ? '에이전트 운용 중' : '일시 중단됨'}
@@ -149,44 +149,43 @@ const AutoTrading: React.FC = ({ marketMode, stocks, configs, ))}
- {/* 전략 추가 모달 (기존 동일) */} {showAddModal && ( -
-
-
-

- 로봇 전략 설계 +
+
+
+

+ 로봇 전략 설계

- +
-
-
- -
+
+
+ +
-
-
-