# ๐Ÿค– AI Trading Bot โ€” ํ”„๋กœ์ ํŠธ ์„ค๊ณ„ ๋ฌธ์„œ (CLAUDE.md) > **์ตœ์ข… ๊ฐฑ์‹ **: 2026-03-19 > **๋Ÿฐํƒ€์ž„**: Windows (Python 3.x, PyTorch CUDA, FastAPI, Ollama) > **ํ•˜๋“œ์›จ์–ด**: AMD 9800X3D + RTX 5070 Ti (16 GB VRAM) --- ## 1. ์‹œ์Šคํ…œ ์•„ํ‚คํ…์ฒ˜ ๊ฐœ์š” ``` โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”‚ main_server.py โ”‚ โ”‚ FastAPI (uvicorn, port 8000) โ€” ํ”„๋กœ์„ธ์Šค ๋งค๋‹ˆ์ € & REST API ์„œ๋ฒ„ โ”‚ โ”‚ โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”‚ โ”‚ โ”‚ Trading Bot โ”‚ โ”‚ Telegram Bot โ”‚ โ”‚ ProcessWatchdog โ”‚ โ”‚ โ”‚ โ”‚ (Process #1) โ”‚ โ”‚ (Process #2) โ”‚ โ”‚ (Daemon Thread) โ”‚ โ”‚ โ”‚ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ”‚ โ”‚ โ”‚ โ”‚ โ”‚ โ”‚ โ”‚ โ””โ”€โ”€โ”€ Shared Memory (IPC) โ”€โ”€โ”€โ”˜ Health Check / Restart โ”‚ โ”‚ + Command Queue โ”‚ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ ``` ### 1.1 ๋ฉ€ํ‹ฐ ํ”„๋กœ์„ธ์Šค ๊ตฌ์„ฑ | ํ”„๋กœ์„ธ์Šค | ์—ญํ•  | ์ง„์ž…์  | |---------|------|--------| | **Main Server (Uvicorn)** | FastAPI REST API ์„œ๋ฒ„, ํ”„๋กœ์„ธ์Šค ์˜ค์ผ€์ŠคํŠธ๋ ˆ์ดํ„ฐ | `main_server.py` | | **Trading Bot** | ์ž๋™๋งค๋งค ๋ฉ”์ธ ๋ฃจํ”„ (์Šค์ผ€์ค„๋Ÿฌ, ๋ถ„์„, ์ฃผ๋ฌธ) | `modules/bot.py` โ†’ `AutoTradingBot.loop()` | | **Telegram Bot** | ์‚ฌ์šฉ์ž ์ธํ„ฐ๋ž™์…˜ (๋ช…๋ น์–ด ์ฒ˜๋ฆฌ, ์•Œ๋ฆผ) | `modules/services/telegram_bot/runner.py` | | **ProcessWatchdog** | ์ž์‹ ํ”„๋กœ์„ธ์Šค ํ—ฌ์Šค์ฒดํฌ & ์ž๋™ ์žฌ์‹œ์ž‘ (30์ดˆ ๊ฐ„๊ฒฉ) | `modules/utils/process_tracker.py` | ### 1.2 ํ”„๋กœ์„ธ์Šค ๊ฐ„ ํ†ต์‹  (IPC) ``` โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” SharedMemory (128KB) โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”‚ Trading Bot โ”‚ โ”€โ”€โ”€ write_status() โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ–บ โ”‚ Telegram Bot โ”‚ โ”‚ โ”‚ โ—„โ”€โ”€ read_status() โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ โ”‚ โ”‚ โ”‚ โ”‚ โ”‚ โ”‚ โ”‚ โ”‚ multiprocessing.Queue โ”‚ โ”‚ โ”‚ โ”‚ โ—„โ”€โ”€ send_command() โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ โ”‚ โ”‚ โ”‚ โ”‚ (ํ…”๋ ˆ๊ทธ๋žจ โ†’ ๋ด‡ ๋ช…๋ น) โ”‚ โ”‚ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ ``` - **SharedMemory** (`web_ai_bot_ipc`, 128KB): ๋ฉ”์ธ ๋ด‡์ด ์ƒํƒœ ๋ฐ์ดํ„ฐ(์ž”๊ณ , GPU, ๋งคํฌ๋กœ ์ง€ํ‘œ ๋“ฑ)๋ฅผ JSON์œผ๋กœ ๊ธฐ๋ก, ํ…”๋ ˆ๊ทธ๋žจ ๋ด‡์ด ์ฝ๊ธฐ - **Command Queue** (`multiprocessing.Queue`): ํ…”๋ ˆ๊ทธ๋žจ โ†’ ๋ฉ”์ธ ๋ด‡ ์–‘๋ฐฉํ–ฅ ๋ช…๋ น ์ฑ„๋„ (`restart`, `evaluate` ๋“ฑ) - **Lock** (`multiprocessing.Lock`): SharedMemory ๋™์‹œ ์ ‘๊ทผ ๋ณดํ˜ธ - **IPC Staleness**: 600์ดˆ (10๋ถ„ ์ด์ƒ ์˜ค๋ž˜๋œ ๋ฐ์ดํ„ฐ๋Š” ๋ฌด์‹œ) ### 1.3 ์„œ๋ฒ„ ์ƒ๋ช…์ฃผ๊ธฐ (Lifespan) ```python # main_server.py > lifespan() 1. Config.validate() # ํ™˜๊ฒฝ๋ณ€์ˆ˜ ๊ฒ€์ฆ 2. ProcessTracker.check_and_kill_zombies() # ์ข€๋น„ ํ”„๋กœ์„ธ์Šค ์ •๋ฆฌ 3. ์ „์—ญ ๊ฐ์ฒด ์ดˆ๊ธฐํ™” (OllamaManager, KISClient, NewsCollector) 4. Shared Resources ์ƒ์„ฑ (Lock, Queue, Event) 5. Trading Bot ํ”„๋กœ์„ธ์Šค ์ƒ์„ฑ & ์‹œ์ž‘ 6. Telegram Bot ํ”„๋กœ์„ธ์Šค ์ƒ์„ฑ & ์‹œ์ž‘ 7. ProcessWatchdog ์‹œ์ž‘ (30์ดˆ ๊ฐ„๊ฒฉ ํ—ฌ์Šค์ฒดํฌ) 8. โ†’ yield (์„œ๋ฒ„ ์ •์ƒ ์šด์˜) 9. [์ข…๋ฃŒ] shutdown_event ์„ค์ • โ†’ ์ž์‹ ์ข…๋ฃŒ โ†’ SharedMemory ํ•ด์ œ ``` --- ## 2. ๋””๋ ‰ํ† ๋ฆฌ ๊ตฌ์กฐ ``` web-ai/ โ”œโ”€โ”€ main_server.py # [Entry Point] FastAPI + ํ”„๋กœ์„ธ์Šค ๋งค๋‹ˆ์ € โ”œโ”€โ”€ warmup_and_restart.py # LSTM ์‚ฌ์ „ํ•™์Šต + ๋ด‡ ์ž๋™ ์‹œ์ž‘ ์Šคํฌ๋ฆฝํŠธ โ”œโ”€โ”€ watchlist_manager.py # ๋‰ด์Šค ๊ธฐ๋ฐ˜ ์ผ์ผ Watchlist ์ž๋™ ์—…๋ฐ์ดํŠธ โ”œโ”€โ”€ backtester.py # ์ „๋žต ๋ฐฑํ…Œ์ŠคํŒ… CLI โ”œโ”€โ”€ theme_manager.py # ์ข…๋ชฉ๋ณ„ ํ…Œ๋งˆ/์„นํ„ฐ ๊ด€๋ฆฌ โ”œโ”€โ”€ .env # ํ™˜๊ฒฝ๋ณ€์ˆ˜ (KIS, Telegram, Ollama ๋“ฑ) โ”‚ โ”œโ”€โ”€ modules/ โ”‚ โ”œโ”€โ”€ __init__.py โ”‚ โ”œโ”€โ”€ config.py # [Config] ํ™˜๊ฒฝ๋ณ€์ˆ˜ & ์ƒ์ˆ˜ ์ •์˜ โ”‚ โ”œโ”€โ”€ bot.py # [Core] AutoTradingBot (์ƒํƒœ ๋จธ์‹  & ์Šค์ผ€์ค„๋Ÿฌ) โ”‚ โ”‚ โ”‚ โ”œโ”€โ”€ analysis/ # [AI Brain] ๋ถ„์„ ์—”์ง„ โ”‚ โ”‚ โ”œโ”€โ”€ deep_learning.py # Attention-LSTM (7D ํ”ผ์ฒ˜, PyTorch GPU) โ”‚ โ”‚ โ”œโ”€โ”€ technical.py # ๊ธฐ์ˆ ์  ์ง€ํ‘œ (RSI, MACD, BB, ADX, OBV...) โ”‚ โ”‚ โ”œโ”€โ”€ macro.py # ๊ฑฐ์‹œ๊ฒฝ์ œ ๋ถ„์„ (KOSPI/KOSDAQ/MSI) โ”‚ โ”‚ โ”œโ”€โ”€ ensemble.py # ์ ์‘ํ˜• ์•™์ƒ๋ธ” (3์‹ ํ˜ธ ๊ฐ€์ค‘์น˜ ์ž๋™์กฐ์ •) โ”‚ โ”‚ โ”œโ”€โ”€ evaluator.py # ์ฃผ๊ฐ„ ์„ฑ๊ณผ ํ‰๊ฐ€ + LLM ์ „๋ฌธ๊ฐ€ ํŒจ๋„ โ”‚ โ”‚ โ””โ”€โ”€ backtest.py # ๋ฐฑํ…Œ์ŠคํŒ… ํ”„๋ ˆ์ž„์›Œํฌ (Sharpe, MDD ๋“ฑ) โ”‚ โ”‚ โ”‚ โ”œโ”€โ”€ strategy/ # [Decision] ๋งค๋งค ์˜์‚ฌ๊ฒฐ์ • โ”‚ โ”‚ โ””โ”€โ”€ process.py # ์›Œ์ปค ํ”„๋กœ์„ธ์Šค์šฉ ๋ถ„์„ ํ•จ์ˆ˜ (๋ณ‘๋ ฌ ์ฒ˜๋ฆฌ) โ”‚ โ”‚ โ”‚ โ”œโ”€โ”€ services/ # [I/O] ์™ธ๋ถ€ ์„œ๋น„์Šค ์—ฐ๋™ โ”‚ โ”‚ โ”œโ”€โ”€ kis.py # ํ•œ๊ตญํˆฌ์ž์ฆ๊ถŒ REST API (๋™๊ธฐ + ๋น„๋™๊ธฐ) โ”‚ โ”‚ โ”œโ”€โ”€ ollama.py # Ollama LLM ์ธํ„ฐํŽ˜์ด์Šค (GPU ์ถฉ๋Œ ๋ฐฉ์ง€) โ”‚ โ”‚ โ”œโ”€โ”€ news.py # Google News RSS ํฌ๋กค๋ง (๋™๊ธฐ + ๋น„๋™๊ธฐ) โ”‚ โ”‚ โ”œโ”€โ”€ telegram.py # ํ…”๋ ˆ๊ทธ๋žจ ๋ฉ”์‹œ์ง€ ๋ฐœ์†ก (Fire-and-forget) โ”‚ โ”‚ โ””โ”€โ”€ telegram_bot/ โ”‚ โ”‚ โ”œโ”€โ”€ server.py # ํ…”๋ ˆ๊ทธ๋žจ ๋ด‡ ์„œ๋ฒ„ (๋ช…๋ น์–ด ํ•ธ๋“ค๋Ÿฌ) โ”‚ โ”‚ โ””โ”€โ”€ runner.py # ํ…”๋ ˆ๊ทธ๋žจ ๋ด‡ ๋…๋ฆฝ ํ”„๋กœ์„ธ์Šค ์‹คํ–‰๊ธฐ โ”‚ โ”‚ โ”‚ โ””โ”€โ”€ utils/ # [Util] ์œ ํ‹ธ๋ฆฌํ‹ฐ โ”‚ โ”œโ”€โ”€ ipc.py # SharedMemory + Command Queue IPC โ”‚ โ”œโ”€โ”€ process_tracker.py # PID ์ถ”์  & ์ข€๋น„ ์ •๋ฆฌ & Watchdog โ”‚ โ”œโ”€โ”€ monitor.py # CPU/GPU/RAM ์„œํ‚ท ๋ธŒ๋ ˆ์ด์ปค โ”‚ โ””โ”€โ”€ performance_db.py # ์ผ๋ณ„ ์Šค๋ƒ…์ƒท & ๋งค๋งค ๊ธฐ๋ก ์˜๊ตฌ ์ €์žฅ โ”‚ โ”œโ”€โ”€ data/ # [Runtime Data] โ”‚ โ”œโ”€โ”€ watchlist.json # ํ˜„์žฌ ๊ฐ์‹œ ์ข…๋ชฉ ๋ฆฌ์ŠคํŠธ โ”‚ โ”œโ”€โ”€ daily_trade_history.json # ์ผ์ผ ๋งค๋งค ๊ธฐ๋ก โ”‚ โ”œโ”€โ”€ kis_token.json # KIS OAuth ํ† ํฐ ์บ์‹œ โ”‚ โ”œโ”€โ”€ peak_prices.json # ํŠธ๋ ˆ์ผ๋ง ์Šคํƒ‘์šฉ ์ตœ๊ณ ๊ฐ€ โ”‚ โ”œโ”€โ”€ ensemble_history.json # AdaptiveEnsemble ๊ฐ€์ค‘์น˜ + ๋งค๋งค ํžˆ์Šคํ† ๋ฆฌ (์ข…๋ชฉ๋ณ„) โ”‚ โ”œโ”€โ”€ models/ # LSTM ์ฒดํฌํฌ์ธํŠธ (์ข…๋ชฉ๋ณ„ .pt ํŒŒ์ผ) โ”‚ โ””โ”€โ”€ performance/ # ์„ฑ๊ณผ ๋ฐ์ดํ„ฐ (daily_snapshots, trade_records) โ”‚ โ””โ”€โ”€ tests/ # ํ…Œ์ŠคํŠธ ``` --- ## 3. ํ•ต์‹ฌ ๋ชจ๋“ˆ ์ƒ์„ธ ### 3.1 AutoTradingBot (`modules/bot.py`) **๋ฉ”์ธ ํŠธ๋ ˆ์ด๋”ฉ ๋ฃจํ”„** โ€” ์žฅ ์‹œ์ž‘(09:00) ~ ์žฅ ๋งˆ๊ฐ(15:30) ์‚ฌ์ด์— ์ž๋™ ์‹คํ–‰ ``` [v3.1 ์ฃผ์š” ๊ธฐ๋Šฅ] โ”œโ”€โ”€ ATR ๊ธฐ๋ฐ˜ ๋™์  ์†์ ˆ/์ต์ ˆ + ํŠธ๋ ˆ์ผ๋ง ์Šคํƒ‘ โ”œโ”€โ”€ Kelly Criterion ํฌ์ง€์…˜ ์‚ฌ์ด์ง• (์‹ค์ „ ์Šน๋ฅ ยท์†์ต๋น„ ๊ธฐ๋ฐ˜, Half-Kelly) โ”œโ”€โ”€ AdaptiveEnsemble ์—ฐ๋™ (๋งค๋„ ํ›„ ๊ฐ€์ค‘์น˜ ์ž๋™ ํ•™์Šต) โ”œโ”€โ”€ ๋‹น์ผ ๋ˆ„์  ๋งค์ˆ˜ ์ถ”์  (_today_buy_total) - KIS T+2 ๋ฏธ์ฐจ๊ฐ ๋ณด์™„ โ”œโ”€โ”€ ์‚ฌ์ดํด๋‹น ์ตœ๋Œ€ ๋งค์ˆ˜ ์ข…๋ชฉ ์ˆ˜ ์ œํ•œ (MAX_BUY_PER_CYCLE) โ”œโ”€โ”€ ProcessPoolExecutor ๋ณ‘๋ ฌ ๋ถ„์„ (์›Œ์ปค 1๊ฐœ, OOM ๋Œ€์‘ ์ž๋™ ์žฌ์‹œ์ž‘) โ”œโ”€โ”€ ์ผ๋ณ„ ์ž์‚ฐ ์Šค๋ƒ…์ƒท (09:05~09:15) โ”œโ”€โ”€ ์ฃผ๊ฐ„ ์„ฑ๊ณผ ํ‰๊ฐ€ (์›”์š”์ผ ์•„์นจ) โ”œโ”€โ”€ CPU ์„œํ‚ท ๋ธŒ๋ ˆ์ด์ปค ์—ฐ๋™ โ””โ”€โ”€ IPC Command Queue ํด๋ง (ํ…”๋ ˆ๊ทธ๋žจ ๋ช…๋ น ์ฒ˜๋ฆฌ) ``` **์ž”๊ณ  ์ถ”์  ๋กœ์ง (v3.1 โ€” ๊ณผ๋งค์ˆ˜ ๋ฐฉ์ง€)**: ``` KIS get_balance() โ†’ raw_deposit (dnca_tot_amt) โ†“ max_daily_buy = raw_deposit ร— MAX_DAILY_BUY_RATIO (80%) tracking_deposit = max_daily_buy - effective_today_buy โ†‘ max(kis_today_buy, self._today_buy_total) (KIS thdt_buy_amt vs ๋กœ์ปฌ ๋ˆ„์  ์ค‘ ํฐ ๊ฐ’) ``` - `_today_buy_total`: ์ธ์Šคํ„ด์Šค ๋ณ€์ˆ˜, ์‚ฌ์ดํด ๊ฐ„ ์œ ์ง€ (09:00 ๋ฆฌ์…‹) - `_buy_scores`: BUY ์‹œ ์‹ ํ˜ธ ์ ์ˆ˜ ์ €์žฅ โ†’ SELL ์‹œ `record_trade()` ์ „๋‹ฌ **run_cycle() ํ๋ฆ„**: 1. ์‹œ์Šคํ…œ ํ—ฌ์Šค ์ฒดํฌ (CPU/GPU/RAM) 2. ๊ฑฐ์‹œ๊ฒฝ์ œ ๋ถ„์„ (KOSPI/KOSDAQ/MSI) 3. ์œ„ํ—˜ ์ƒํƒœ๋ณ„ ๋ถ„๊ธฐ (SAFE/CAUTION/DANGER) 4. Watchlist ์ข…๋ชฉ OHLCV ์ˆ˜์ง‘ (KIS ๋น„๋™๊ธฐ ๋ฐฐ์น˜) 5. ์ž”๊ณ  ์กฐํšŒ + ๋‹น์ผ ๋ˆ„์  ๋งค์ˆ˜ ์ฐจ๊ฐ โ†’ ์‹ค์ œ ๊ฐ€์šฉ ์˜ˆ์ˆ˜๊ธˆ ๊ณ„์‚ฐ 6. `ProcessPoolExecutor`๋กœ ์ข…๋ชฉ ๋ณ‘๋ ฌ ๋ถ„์„ (Kelly Criterion + Ensemble ๊ฐ€์ค‘์น˜) 7. ์•™์ƒ๋ธ” ์ ์ˆ˜ ๊ธฐ๋ฐ˜ ๋งค์ˆ˜/๋งค๋„ ํŒ๋‹จ (์‚ฌ์ดํด๋‹น MAX_BUY_PER_CYCLE ์ œํ•œ) 8. ์ฃผ๋ฌธ ์‹คํ–‰ & ๊ฒฐ๊ณผ ํ…”๋ ˆ๊ทธ๋žจ ์•Œ๋ฆผ 9. ๋งค๋„ ์‹œ `record_trade()` โ†’ Ensemble ๊ฐ€์ค‘์น˜ ํ•™์Šต 10. IPC ์ƒํƒœ ๊ฐฑ์‹  ### 3.2 AI ๋ถ„์„ ํŒŒ์ดํ”„๋ผ์ธ ``` โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”‚ analyze_stock_ โ”‚ โ”‚ process() โ”‚ โ”‚ (strategy/process)โ”‚ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ”‚ โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ–ผ โ–ผ โ–ผ โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”‚ Technical โ”‚ โ”‚ Deep Learning โ”‚ โ”‚ LLM (Ollama) โ”‚ โ”‚ Analyzer โ”‚ โ”‚ LSTM โ”‚ โ”‚ Sentiment โ”‚ โ”‚ (๊ธฐ์ˆ ์  ์ง€ํ‘œ) โ”‚ โ”‚ (์ฃผ๊ฐ€ ์˜ˆ์ธก) โ”‚ โ”‚ (๋‰ด์Šค ๊ฐ์„ฑ๋ถ„์„) โ”‚ โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค โ”‚ RSI 25% โ”‚ โ”‚ Attention-LSTM โ”‚ โ”‚ qwen2.5:7b โ”‚ โ”‚ ์ด๊ฒฉ๋„ 15% โ”‚ โ”‚ 4Lร—512H โ”‚ โ”‚ JSON ํฌ๋งท ์š”์ฒญ โ”‚ โ”‚ MACD 15% โ”‚ โ”‚ 7์ฐจ์› ํ”ผ์ฒ˜ โ”‚ โ”‚ ๋‰ด์Šค+์ง€ํ‘œ ํ†ตํ•ฉ โ”‚ โ”‚ Stochastic 5% โ”‚ โ”‚ 60์ผ ์‹œํ€€์Šค โ”‚ โ”‚ ๊ฐ์„ฑ+์‹ ๋ขฐ๋„ โ”‚ โ”‚ BB 15% โ”‚ โ”‚ GPU ๊ฐ€์† โ”‚ โ”‚ โ”‚ โ”‚ ADX 15% โ”‚ โ”‚ ์ข…๋ชฉ๋ณ„ ๋ชจ๋ธ โ”‚ โ”‚ โ”‚ โ”‚ MTF 10% โ”‚ โ”‚ (ModelRegistry)โ”‚ โ”‚ โ”‚ โ”‚ OBV ยฑ๋ณด๋„ˆ์Šค โ”‚ โ”‚ โ”‚ โ”‚ โ”‚ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ”‚ โ”‚ โ”‚ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ”‚ โ–ผ โ”‚ โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”‚ โ”‚ AdaptiveEnsembleโ”‚ โ—„โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ”‚ (ํ•™์Šตํ˜• ๊ฐ€์ค‘์น˜) โ”‚ โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค โ”‚ get_weights() โ”‚ โ† ๊ณผ๊ฑฐ ๋งค๋งค ๊ฒฐ๊ณผ ๋ฐ˜์˜ โ”‚ (ADX+macro+conf)โ”‚ ํฌ๊ธฐ ๊ฐ€์ค‘ ์ •ํ™•๋„ ๊ธฐ์ค€ โ”‚ ๊ฒฝ๊ณ„: 0.10~0.65 โ”‚ Water-Filling ์ •๊ทœํ™” โ”‚ Kelly Fraction โ”‚ โ† ์Šน๋ฅ ยท์†์ต๋น„ ๊ธฐ๋ฐ˜ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ–ผ โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”‚ ๋งค์ˆ˜/๋งค๋„/ํ™€๋“œ โ”‚ โ”‚ ์ตœ์ข… ํŒ๋‹จ โ”‚ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ ``` #### 3.2.1 Deep Learning โ€” Attention-LSTM (`analysis/deep_learning.py`) | ํ•ญ๋ชฉ | ๊ฐ’ | |------|-----| | **์•„ํ‚คํ…์ฒ˜** | 4-Layer Stacked LSTM + Attention + FC | | **Hidden Size** | 512 | | **Input Features** | 7 (close, open, high, low, volume_norm, rsi_14, macd_hist) | | **์‹œํ€€์Šค ๊ธธ์ด** | 60์ผ | | **ํ•™์Šต ์—ํฌํฌ** | ์ตœ๋Œ€ 200 (Early Stopping patience=15) | | **๋น ๋ฅธ ์žฌํ•™์Šต** | 30 ์—ํฌํฌ (์ฒดํฌํฌ์ธํŠธ ์กด์žฌ ์‹œ) | | **์ฟจ๋‹ค์šด** | 1200์ดˆ (20๋ถ„, ๋™์ผ ์ข…๋ชฉ ์žฌํ•™์Šต ๋ฐฉ์ง€) | | **ModelRegistry** | LRU ๋ฐฉ์‹, ์ตœ๋Œ€ 5๊ฐœ ๋ชจ๋ธ ๋™์‹œ ์ ์žฌ | | **์ฒดํฌํฌ์ธํŠธ** | `data/models/{ticker}_v3.pt` | | **GPU ๊ด€๋ฆฌ** | LSTM ํ•™์Šต ์‹œ Ollama ์ž๋™ ์–ธ๋กœ๋“œ/๋ฆฌ๋กœ๋“œ | #### 3.2.2 ๊ธฐ์ˆ ์  ๋ถ„์„ (`analysis/technical.py`) `TechnicalAnalyzer.get_technical_score()` โ†’ 0.0 ~ 1.0 ํ†ตํ•ฉ ์ ์ˆ˜ | ์ง€ํ‘œ | ๋น„์ค‘ | ์„ค๋ช… | |------|------|------| | RSI (14์ผ) | 25% | Wilder ๋ฐฉ์‹, 30 ์ดํ•˜ ๊ณผ๋งค๋„/70 ์ด์ƒ ๊ณผ๋งค์ˆ˜ | | ์ด๋™ํ‰๊ท  ์ด๊ฒฉ๋„ | 15% | 20์ผ MA ๋Œ€๋น„ ํ˜„์žฌ๊ฐ€ ์œ„์น˜ | | MACD | 15% | 12/26/9, ํžˆ์Šคํ† ๊ทธ๋žจ ๋ฐฉํ–ฅ | | Stochastic | 5% | Fast %K/%D (14/3/3) | | Bollinger Bands | 15% | 20์ผ/2ฯƒ, %B ์œ„์น˜ + ๋ฐด๋“œํญ | | ADX | 15% | ์ถ”์„ธ ๊ฐ•๋„ (>25 ๊ฐ•ํ•œ ์ถ”์„ธ) | | Multi-Timeframe | 10% | 5์ผ/20์ผ/60์ผ ์ถ”์„ธ ์ผ๊ด€์„ฑ | | OBV | ยฑ0.1 ๋ณด๋„ˆ์Šค | ๊ฑฐ๋ž˜๋Ÿ‰ ๊ธฐ๋ฐ˜ ๋งค์ง‘/๋ถ„์‚ฐ ๊ฐ์ง€ | ์ถ”๊ฐ€ ๊ธฐ๋Šฅ: - `calculate_atr()` โ†’ ATR ๊ธฐ๋ฐ˜ ๋™์  ์†์ ˆ/์ต์ ˆ - `calculate_dynamic_sl_tp()` โ†’ ๋ณ€๋™์„ฑ ์ ์‘ํ˜• SL/TP - `calculate_obv()` โ†’ ์Šค๋งˆํŠธ ๋จธ๋‹ˆ ๋‹ค์ด๋ฒ„์ „์Šค ๊ฐ์ง€ #### 3.2.3 ๊ฑฐ์‹œ๊ฒฝ์ œ ๋ถ„์„ (`analysis/macro.py`) ```python MacroAnalyzer.get_macro_status(kis_client) โ†’ { "status": "SAFE" | "CAUTION" | "DANGER", "risk_score": int, "indicators": { "KOSPI": {"price", "change", "high", "low", "prev_close", "volume"}, "KOSDAQ": {"price", "change", ...}, "KOSPI200":{"price", "change", ...}, "MSI": float # Market Stress Index (0~100) } } ``` - **SAFE** (risk_score < 1): ์ •์ƒ ๋งค๋งค - **CAUTION** (1 โ‰ค risk_score < 3): ๋งค์ˆ˜ ๊ทœ๋ชจ ์ถ•์†Œ - **DANGER** (risk_score โ‰ฅ 3): ๋งค์ˆ˜ ์ค‘๋‹จ, ๋ณด์œ ๋ถ„๋งŒ ๊ด€๋ฆฌ #### 3.2.4 ์•™์ƒ๋ธ” (`analysis/ensemble.py`) `AdaptiveEnsemble` โ€” ๊ณผ๊ฑฐ ๋งค๋งค ๊ฒฐ๊ณผ ๊ธฐ๋ฐ˜ ๊ฐ€์ค‘์น˜ ์ž๋™ ์กฐ์ • + Kelly Criterion: **๊ฐ€์ค‘์น˜ ํ•™์Šต ํ๋ฆ„**: ``` BUY ์ฒด๊ฒฐ โ†’ bot._buy_scores[ticker] = {tech, sentiment, lstm} ์ €์žฅ SELL ์ฒด๊ฒฐ โ†’ ensemble.record_trade(ticker, ..., outcome_pct=yld) โ†’ _update_weights() โ†’ EMA(alpha=0.10) ๊ฐ€์ค‘์น˜ ์ ์ง„ ์กฐ์ • โ†’ _save() โ†’ data/ensemble_history.json ์›Œ์ปค ํ”„๋กœ์„ธ์Šค โ†’ reload_if_stale() โ†’ ํŒŒ์ผ mtime ๊ฐ์ง€ ์‹œ ์žฌ๋กœ๋“œ ``` **์ฃผ์š” ๋ฉ”์„œ๋“œ**: - `get_weights(ticker, adx, macro_state, ai_confidence)` โ†’ `SignalWeights` - ์‹œ์žฅ ์ปจํ…์ŠคํŠธ (strong_trend/sideways/danger/default) ๋ณ„ ๊ธฐ๋ณธ ๊ฐ€์ค‘์น˜ - ์ข…๋ชฉ๋ณ„ ์ตœ๊ทผ 10๊ฑฐ๋ž˜ ํฌ๊ธฐ ๊ฐ€์ค‘ ์ •ํ™•๋„ ๋ฐ˜์˜ - ai_confidence >= 0.75 โ†’ LSTM ๊ฐ€์ค‘์น˜ +25% (confidence ์ƒํ•œ 0.80 ๋ฐ˜์˜) - `get_kelly_fraction(ticker, half_kelly=True)` โ†’ 0.03~0.25 ๋ฒ”์œ„ ํˆฌ์ž ๋น„์ค‘ - f* = (pยทb - q) / b (p=์Šน๋ฅ , b=์†์ต๋น„) - ๊ฑฐ๋ž˜ ๋ฐ์ดํ„ฐ < 10๊ฑด โ†’ ๋ณด์ˆ˜์  ๊ธฐ๋ณธ๊ฐ’ 8% - Half-Kelly ์ ์šฉ์œผ๋กœ ๋ณ€๋™์„ฑ ๊ณผ๋Œ€์ถ”์ • ๋ณด์™„ - `compute_ensemble_score(tech, sentiment, lstm, investor, weights)` โ†’ ํ†ตํ•ฉ ์ ์ˆ˜ - `reload_if_stale()` โ†’ ํŒŒ์ผ mtime ๊ธฐ๋ฐ˜ cross-process ๋™๊ธฐํ™” **`SignalWeights.normalize()` โ€” Water-Filling ์•Œ๊ณ ๋ฆฌ์ฆ˜**: - ๊ฒฝ๊ณ„(0.10~0.65) ์œ„๋ฐ˜ ์‹œ ํ•ด๋‹น ๊ฐ’์„ ๊ฒฝ๊ณ„์— ๊ณ ์ •, ๋‚˜๋จธ์ง€์— ์ž”์—ฌ ๋น„์ค‘ ๋น„๋ก€ ๋ฐฐ๋ถ„ - 2์ฐจ ์ •๊ทœํ™”(ํ•ฉ=1 ๋ณด์žฅ)์™€ ๊ฒฝ๊ณ„ ํด๋žจํ•‘์ด ์ƒ์ถฉํ•˜๋Š” ๋ฌธ์ œ ํ•ด๊ฒฐ - ์˜๊ตฌ ์ €์žฅ: `data/ensemble_history.json` (๊ฐ€์ค‘์น˜ + ๋งค๋งค ํžˆ์Šคํ† ๋ฆฌ ํ†ตํ•ฉ) #### 3.2.5 ์„ฑ๊ณผ ํ‰๊ฐ€ (`analysis/evaluator.py`) `PerformanceEvaluator.generate_weekly_report()`: - ํ•ต์‹ฌ ์ง€ํ‘œ: ์ด์ˆ˜์ต๋ฅ , Sharpe Ratio, MDD, ์Šน๋ฅ , ํ‰๊ท ์†์ต๋น„, KOSPI ์ƒ๊ด€๋„ - S/A/B/C/D/F ๋“ฑ๊ธ‰ ์‚ฐ์ถœ - **5๋ช… ์ „๋ฌธ๊ฐ€ LLM ํŒจ๋„** (Ollama): ๊ฐ๊ฐ ๋‹ค๋ฅธ ๊ด€์ ์œผ๋กœ ํ‰๊ฐ€ - HTML ํฌ๋งท ํ…”๋ ˆ๊ทธ๋žจ ์ฃผ๊ฐ„ ๋ณด๊ณ ์„œ ์ž๋™ ์ƒ์„ฑ --- ## 4. ์™ธ๋ถ€ ์„œ๋น„์Šค ์—ฐ๋™ ### 4.1 ํ•œ๊ตญํˆฌ์ž์ฆ๊ถŒ KIS API (`services/kis.py`) #### ์ธ์ฆ ```python KISClient.ensure_token() # OAuth 2.0 โ†’ access_token ๋ฐœ๊ธ‰ โ†’ data/kis_token.json์— ์บ์‹œ # ํ† ํฐ ๋งŒ๋ฃŒ ์‹œ ์ž๋™ ๊ฐฑ์‹  (_request_api์—์„œ ์ฒ˜๋ฆฌ) ``` | ์„ค์ • | ๋ชจ์˜ํˆฌ์ž | ์‹ค์ „ํˆฌ์ž | |------|---------|---------| | Base URL | `openapivts.koreainvestment.com:29443` | `openapi.koreainvestment.com:9443` | | ํ™˜๊ฒฝ๋ณ€์ˆ˜ | `KIS_VIRTUAL_APP_KEY/SECRET/ACCOUNT` | `KIS_REAL_APP_KEY/SECRET/ACCOUNT` | | ์ „ํ™˜ | `.env` โ†’ `KIS_ENV_TYPE=virtual` | `.env` โ†’ `KIS_ENV_TYPE=real` | #### API ์Šค๋กœํ‹€๋ง - ์ดˆ๋‹น 2ํšŒ ์ œํ•œ (`_throttle()` โ€” 0.5์ดˆ ๋”œ๋ ˆ์ด) - ํ† ํฐ ๋งŒ๋ฃŒ ์‹œ ์ž๋™ ๊ฐฑ์‹  (403 โ†’ retry with new token) #### ์ฃผ์š” API ์—”๋“œํฌ์ธํŠธ ๋งคํ•‘ | ๊ธฐ๋Šฅ | KISClient ๋ฉ”์„œ๋“œ | KIS TR_ID | |------|-----------------|-----------| | ์ž”๊ณ  ์กฐํšŒ | `get_balance()` โ†’ `{holdings, total_eval, deposit, today_buy_amt}` | `VTTC8434R` (๋ชจ์˜) / `TTTC8434R` (์‹ค์ „) | | ์ฃผ๋ฌธ (๋งค์ˆ˜/๋งค๋„) | `order()` | `VTTC0802U` / `VTTC0801U` (๋ชจ์˜) | | ํ˜„์žฌ๊ฐ€ ์กฐํšŒ | `get_current_price()` | `FHKST01010100` | | ์ผ๋ด‰ OHLCV | `get_daily_ohlcv()` โ†’ `_get_daily_ohlcv_by_range()` | `FHKST03010100` | | ์ผ๋ด‰ ์ข…๊ฐ€ | `get_daily_price()` โ†’ `_get_daily_price_by_range()` | `FHKST03010100` | | ๊ฑฐ๋ž˜๋Ÿ‰ ์ˆœ์œ„ | `get_volume_rank()` | `FHPST01710000` | | ์ง€์ˆ˜ ํ˜„์žฌ๊ฐ€ | `get_current_index()` | `FHPUP02100000` | | ์ง€์ˆ˜ ์ผ๋ด‰ | `get_daily_index_price()` | `FHKUP03500100` | | ํˆฌ์ž์ž ๋™ํ–ฅ | `get_investor_trend()` | `FHKST01010900` | | Hash Key | `get_hash_key()` | - | #### ๋น„๋™๊ธฐ ํด๋ผ์ด์–ธํŠธ (`KISAsyncClient`) `aiohttp` ๊ธฐ๋ฐ˜ โ€” ๋‹ค์ค‘ ์ข…๋ชฉ ๋™์‹œ ์ˆ˜์ง‘์šฉ: - `get_daily_price_batch()` โ€” ์—ฌ๋Ÿฌ ์ข…๋ชฉ ์ผ๋ด‰ ๋ณ‘๋ ฌ ์ˆ˜์ง‘ - `get_daily_ohlcv_batch()` โ€” ์—ฌ๋Ÿฌ ์ข…๋ชฉ OHLCV ๋ณ‘๋ ฌ ์ˆ˜์ง‘ - `get_investor_trends_batch()` โ€” ์—ฌ๋Ÿฌ ์ข…๋ชฉ ํˆฌ์ž์ž ๋™ํ–ฅ ๋ณ‘๋ ฌ ์ˆ˜์ง‘ --- ### 4.2 Ollama LLM (`services/ollama.py`) | ์„ค์ • | ๊ฐ’ | |------|-----| | **๋ชจ๋ธ** | `qwen2.5:7b-instruct-q4_K_M` (VRAM ~4GB) | | **API URL** | `http://localhost:11434` | | **Context Window** | 4096 ํ† ํฐ | | **Max Output** | 200 ํ† ํฐ | | **Temperature** | 0.1 (๊ฒฐ์ •๋ก ์ , JSON ์•ˆ์ •์„ฑ) | | **Keep Alive** | 5๋ถ„ (๋น„ํ™œ์„ฑ ์‹œ ์ž๋™ ์–ธ๋กœ๋“œ) | | **Timeout** | 90์ดˆ | | **CPU Threads** | 8 (9800X3D ์ตœ์ ํ™”) | | **์‘๋‹ต ํฌ๋งท** | JSON (format: "json") | **GPU ์ถฉ๋Œ ๋ฐฉ์ง€**: - LSTM ํ•™์Šต ์ค‘ โ†’ Ollama ์ถ”๋ก  ์ตœ๋Œ€ 60์ดˆ ๋Œ€๊ธฐ - VRAM > 12GB โ†’ ๋ชจ๋ธ ์ฆ‰์‹œ ์–ธ๋กœ๋“œ (`keep_alive=0`) - LSTM ํ•™์Šต ์ „ โ†’ Ollama ์ž๋™ ์–ธ๋กœ๋“œ, ํ•™์Šต ํ›„ โ†’ ์ž๋™ ๋ฆฌ๋กœ๋“œ --- ### 4.3 ๋‰ด์Šค ์ˆ˜์ง‘ (`services/news.py`) - **์†Œ์Šค**: Google News RSS (`news.google.com/rss/search`) - **๋™๊ธฐ**: `NewsCollector.get_market_news()` โ€” ์‹œ์žฅ ์ผ๋ฐ˜ ๋‰ด์Šค 5๊ฑด - **๋น„๋™๊ธฐ**: `AsyncNewsCollector` - `get_market_news_async()` โ€” ์‹œ์žฅ ๋‰ด์Šค (5๋ถ„ ์บ์‹œ) - `get_stock_news_async()` โ€” ์ข…๋ชฉ๋ณ„ ๋‰ด์Šค (5๋ถ„ ์บ์‹œ) --- ## 5. ์›น ๋ฐฑ์—”๋“œ ์„œ๋ฒ„ API (FastAPI) ### 5.1 ์„œ๋ฒ„ ์ •๋ณด | ํ•ญ๋ชฉ | ๊ฐ’ | |------|-----| | **ํ”„๋ ˆ์ž„์›Œํฌ** | FastAPI + Uvicorn | | **ํ˜ธ์ŠคํŠธ** | `0.0.0.0:8000` | | **NAS ๋ฐฑ์—”๋“œ** | `http://192.168.45.54:18500` (์›น ํ”„๋ก ํŠธ์—”๋“œ ์„œ๋ฒ„) | ### 5.2 API ์—”๋“œํฌ์ธํŠธ #### `GET /` โ€” ์„œ๋ฒ„ ์ƒํƒœ ```json { "status": "online", "gpu_vram": 4.2, "service": "Windows AI Server (Refactored)" } ``` #### `GET /trade/balance` | `GET /api/trade/balance` โ€” ์ž”๊ณ  ์กฐํšŒ KIS API๋ฅผ ํ†ตํ•ด ํ˜„์žฌ ๊ณ„์ขŒ ์ž”๊ณ (์˜ˆ์ˆ˜๊ธˆ, ๋ณด์œ ์ข…๋ชฉ, ํ‰๊ฐ€๊ธˆ์•ก) ์กฐํšŒ. ```json { "total_eval": 10500000, "deposit": 5000000, "holdings": [ { "ticker": "005930", "name": "์‚ผ์„ฑ์ „์ž", "qty": 10, "avg_price": 72000, "current_price": 73500, "profit_rate": 2.08 } ] } ``` #### `POST /trade/order` | `POST /api/trade/order` โ€” ์ˆ˜๋™ ์ฃผ๋ฌธ ```json // Request Body { "ticker": "005930", "action": "BUY", // "BUY" | "SELL" "quantity": 10 } // Response { "status": "executed", "kis_result": { ... } } ``` #### `POST /analyze/portfolio` | `POST /api/analyze/portfolio` โ€” AI ํฌํŠธํด๋ฆฌ์˜ค ๋ถ„์„ ํ˜„์žฌ ์ž”๊ณ  + ์ตœ์‹  ๋‰ด์Šค๋ฅผ ์ข…ํ•ฉํ•˜์—ฌ Ollama LLM์œผ๋กœ ํฌํŠธํด๋ฆฌ์˜ค ๋ถ„์„. ```json { "analysis": "... AI ๋ถ„์„ ๊ฒฐ๊ณผ (ํ•œ๊ตญ์–ด) ..." } ``` ### 5.3 NAS ์„œ๋ฒ„์™€์˜ ํ†ต์‹  ํ๋ฆ„ ``` โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” HTTP Request โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”‚ NAS Backend โ”‚ โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ–บ โ”‚ Windows AI Server โ”‚ โ”‚ (์›น ํ”„๋ก ํŠธ) โ”‚ โ”‚ (FastAPI:8000) โ”‚ โ”‚ :18500 โ”‚ โ—„โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ โ”‚ โ”‚ โ”‚ โ”‚ JSON Response โ”‚ โ”‚ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ [ํ†ต์‹  ์‹œ๋‚˜๋ฆฌ์˜ค] 1. ์›น โ†’ /api/trade/balance โ†’ ์ž”๊ณ  ๋ฐ์ดํ„ฐ ํ‘œ์‹œ 2. ์›น โ†’ /api/trade/order โ†’ ์ˆ˜๋™ ๋งค์ˆ˜/๋งค๋„ ์‹คํ–‰ 3. ์›น โ†’ /api/analyze/portfolio โ†’ AI ๋ถ„์„ ๊ฒฐ๊ณผ ํ‘œ์‹œ 4. ์›น โ†’ / โ†’ ์„œ๋ฒ„ ์ƒํƒœ ๋ฐ GPU ์ •๋ณด ``` - **NAS ์„œ๋ฒ„** (`192.168.45.54:18500`): ์›น ํ”„๋ก ํŠธ์—”๋“œ ํ˜ธ์ŠคํŒ…, ์‚ฌ์šฉ์ž ์ธํ„ฐํŽ˜์ด์Šค ์ œ๊ณต - **Windows AI ์„œ๋ฒ„** (`0.0.0.0:8000`): GPU ์—ฐ์‚ฐ, KIS API ํ†ต์‹ , AI ๋ถ„์„ ์ฒ˜๋ฆฌ - ๋‚ด๋ถ€ ๋„คํŠธ์›Œํฌ (LAN) ํ†ต์‹ , ์™ธ๋ถ€ ๋…ธ์ถœ ์—†์Œ --- ## 6. ํ…”๋ ˆ๊ทธ๋žจ ๋ด‡ ์„ค์ • & ๋ช…๋ น์–ด ### 6.1 ํ™˜๊ฒฝ๋ณ€์ˆ˜ ```env TELEGRAM_BOT_TOKEN=8546032918:AAF5GJcP92DrtpSoQdaimMIZe7bz_xtGGPo TELEGRAM_CHAT_ID=7388056964 ``` ### 6.2 ๋ด‡ ํ”„๋กœ์„ธ์Šค ์•„ํ‚คํ…์ฒ˜ ``` runner.py โ””โ”€โ”€ run_telegram_bot_standalone() โ”œโ”€โ”€ SharedIPC ์ดˆ๊ธฐํ™” (lock, queue, shutdown_event) โ”œโ”€โ”€ TelegramBotServer ์ƒ์„ฑ โ”œโ”€โ”€ IPC์—์„œ ์ดˆ๊ธฐ ๋ฐ์ดํ„ฐ ๋กœ๋“œ โ”œโ”€โ”€ bot_server.run() (python-telegram-bot polling) โ””โ”€โ”€ Conflict ๊ฐ์ง€ ์‹œ ๋ฐฑ์˜คํ”„ ์žฌ์‹œ๋„ (์ตœ๋Œ€ 10ํšŒ) ``` - **๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ**: `python-telegram-bot` (Application, CommandHandler) - **๋ฉ”์‹œ์ง€ ํฌ๋งท**: HTML (`parse_mode="HTML"`) - **๋™์‹œ ์—…๋ฐ์ดํŠธ**: `concurrent_updates=True` - **๋กœ๊น…**: `telegram_bot.log` (ํŒŒ์ผ + ์ฝ˜์†”) ### 6.3 ๋ช…๋ น์–ด ๋ชฉ๋ก | ๋ช…๋ น์–ด | ์„ค๋ช… | ๋ฐ์ดํ„ฐ ์†Œ์Šค | |--------|------|------------| | `/start` | ๋ด‡ ์‹œ์ž‘ & ์ „์ฒด ๋ช…๋ น์–ด ์•ˆ๋‚ด | - | | `/status` | ๋ด‡ ์ƒํƒœ, ์‹œ์žฅ ์ง€์ˆ˜, AI ๋ชจ๋ธ ์ƒํƒœ | IPC (SharedMemory) | | `/portfolio` | ๋ณด์œ  ์ข…๋ชฉ & ์ˆ˜์ต๋ฅ  ์กฐํšŒ | IPC โ†’ FakeKIS.get_balance() | | `/watchlist` | ํ˜„์žฌ ๊ฐ์‹œ ์ข…๋ชฉ ๋ฆฌ์ŠคํŠธ | IPC โ†’ watchlist ๋ฐ์ดํ„ฐ | | `/update_watchlist` | Watchlist ์ฆ‰์‹œ ์—…๋ฐ์ดํŠธ ์š”์ฒญ | Command Queue โ†’ ๋ฉ”์ธ ๋ด‡ | | `/macro` | ๊ฑฐ์‹œ๊ฒฝ์ œ ๋ถ„์„ (KOSPI/KOSDAQ/MSI) | IPC โ†’ macro_indices | | `/system` | CPU/GPU/RAM ์‹œ์Šคํ…œ ์ƒํƒœ | IPC โ†’ gpu_status + psutil | | `/ai` | AI ๋ชจ๋ธ ์ƒํƒœ (VRAM, ํ•™์Šต ์—ฌ๋ถ€) | IPC โ†’ gpu_status | | `/restart` | ๋ฉ”์ธ ๋ด‡ ์žฌ์‹œ์ž‘ ๋ช…๋ น | Command Queue | | `/stop` | ๋ด‡ ์ข…๋ฃŒ | shutdown_event.set() | | `/exec ` | ์„œ๋ฒ„ ์‰˜ ๋ช…๋ น์–ด ์ง์ ‘ ์‹คํ–‰ | subprocess (10์ดˆ ํƒ€์ž„์•„์›ƒ) | | `/evaluate` | ์ฆ‰์‹œ ์„ฑ๊ณผ ํ‰๊ฐ€ ๋ณด๊ณ ์„œ ์ƒ์„ฑ | PerformanceEvaluator | ### 6.4 TelegramMessenger (`services/telegram.py`) ๋‹จ๋ฐฉํ–ฅ ์•Œ๋ฆผ ์ „์šฉ (๋ฉ”์ธ ๋ด‡ โ†’ ์‚ฌ์šฉ์ž): - **๋น„๋™๊ธฐ ์ „์†ก**: `threading.Thread(daemon=True)` โ€” Fire-and-forget - **HTML ํŒŒ์‹ฑ**: ๋งˆํฌ๋‹ค์šด ์—๋Ÿฌ ๋ฐฉ์ง€ - ๋งค๋งค ์‹คํ–‰, ์„œ๋ฒ„ ์‹œ์ž‘/์ข…๋ฃŒ, ์—๋Ÿฌ ์•Œ๋ฆผ ๋“ฑ์— ์‚ฌ์šฉ ### 6.5 Conflict ์ฒ˜๋ฆฌ ํ…”๋ ˆ๊ทธ๋žจ ๋ด‡ API๋Š” ๋™์‹œ์— ํ•˜๋‚˜์˜ polling ์ธ์Šคํ„ด์Šค๋งŒ ํ—ˆ์šฉ: - `Conflict` ์—๋Ÿฌ ๊ฐ์ง€ ์‹œ ์ง€์ˆ˜ ๋ฐฑ์˜คํ”„ (5s โ†’ 10s โ†’ ... โ†’ 30s) - ์ตœ๋Œ€ 10ํšŒ ์žฌ์‹œ๋„ ํ›„ ํ”„๋กœ์„ธ์Šค ์ข…๋ฃŒ - Watchdog๊ฐ€ ๊ฐ์ง€ํ•˜์—ฌ ์ž๋™ ์žฌ์‹œ์ž‘ --- ## 7. ํ™˜๊ฒฝ ์„ค์ • (`modules/config.py`) ### 7.1 ์ฃผ์š” ์„ค์ • ์ƒ์ˆ˜ | ๊ทธ๋ฃน | ํ‚ค | ๊ฐ’ | ์„ค๋ช… | |------|-----|-----|------| | **๋งค๋งค** | `MAX_INVESTMENT_PER_STOCK` | 3,000,000์› | ์ข…๋ชฉ๋‹น ์ตœ๋Œ€ ํˆฌ์ž๊ธˆ | | **๋งค๋งค** | `MAX_BUY_PER_CYCLE` | 2 | ์‚ฌ์ดํด๋‹น ์ตœ๋Œ€ ๋งค์ˆ˜ ์ข…๋ชฉ ์ˆ˜ (env: `MAX_BUY_PER_CYCLE`) | | **๋งค๋งค** | `MAX_DAILY_BUY_RATIO` | 0.80 | ์˜ˆ์ˆ˜๊ธˆ ๋Œ€๋น„ ์ผ์ผ ์ตœ๋Œ€ ๋งค์ˆ˜ ๋น„์œจ (env: `MAX_DAILY_BUY_RATIO`) | | **IPC** | `SHM_NAME` | `web_ai_bot_ipc` | SharedMemory ์ด๋ฆ„ | | **IPC** | `SHM_SIZE` | 131,072 (128KB) | SharedMemory ํฌ๊ธฐ | | **IPC** | `IPC_STALENESS` | 600์ดˆ | ๋ฐ์ดํ„ฐ ์œ ํšจ ๊ธฐ๊ฐ„ | | **GPU** | `VRAM_WARNING_THRESHOLD` | 12.0 GB | VRAM ๊ฒฝ๊ณ  ์ž„๊ณ„๊ฐ’ | | **ํ”„๋กœ์„ธ์Šค** | `WATCHDOG_INTERVAL` | 30์ดˆ | ํ—ฌ์Šค์ฒดํฌ ๊ฐ„๊ฒฉ | | **ํ”„๋กœ์„ธ์Šค** | `MAX_RESTART_COUNT` | 3 | ์ตœ๋Œ€ ์ž๋™ ์žฌ์‹œ์ž‘ ํšŸ์ˆ˜ | | **LSTM** | `LSTM_COOLDOWN` | 1,200์ดˆ | ๋™์ผ ์ข…๋ชฉ ์žฌํ•™์Šต ๋ฐฉ์ง€ | | **LSTM** | `LSTM_FAST_EPOCHS` | 30 | ๋น ๋ฅธ ์žฌํ•™์Šต ์—ํฌํฌ | | **CPU** | `CPU_CIRCUIT_BREAKER_THRESHOLD` | 92% | ์„œํ‚ท ๋ธŒ๋ ˆ์ด์ปค ์ž„๊ณ„๊ฐ’ | | **CPU** | `CPU_CIRCUIT_BREAKER_CONSECUTIVE` | 2ํšŒ | ์—ฐ์† ์ดˆ๊ณผ ์‹œ ๋ฐœ๋™ | | **Ollama** | `OLLAMA_NUM_CTX` | 4,096 | ์ปจํ…์ŠคํŠธ ์œˆ๋„์šฐ | | **Ollama** | `OLLAMA_NUM_PREDICT` | 200 | ์ตœ๋Œ€ ์ถœ๋ ฅ ํ† ํฐ | | **Ollama** | `OLLAMA_NUM_THREAD` | 8 | CPU ์Šค๋ ˆ๋“œ ์ˆ˜ | | **Network** | `HTTP_TIMEOUT` | 10์ดˆ | ๊ธฐ๋ณธ HTTP ์š”์ฒญ ํƒ€์ž„์•„์›ƒ | ### 7.2 .env ํŒŒ์ผ ๊ตฌ์กฐ ```env # NAS Backend (์›น ํ”„๋ก ํŠธ์—”๋“œ ์„œ๋ฒ„) NAS_API_URL=http://192.168.45.54:18500 # Ollama LLM OLLAMA_API_URL=http://localhost:11434 OLLAMA_MODEL=qwen2.5:7b-instruct-q4_K_M # KIS API (virtual/real ์ „ํ™˜) KIS_ENV_TYPE=virtual KIS_REAL_APP_KEY=... KIS_REAL_APP_SECRET=... KIS_REAL_ACCOUNT=XXXXXXXX-XX KIS_VIRTUAL_APP_KEY=... KIS_VIRTUAL_APP_SECRET=... KIS_VIRTUAL_ACCOUNT=XXXXXXXX-XX # Telegram Bot TELEGRAM_BOT_TOKEN=... TELEGRAM_CHAT_ID=... ``` --- ## 8. ์šด์˜ ๊ฐ€์ด๋“œ ### 8.1 ์‹œ์ž‘ ๋ฐฉ๋ฒ• ```bash # ์ผ๋ฐ˜ ์‹œ์ž‘ python main_server.py # LSTM ์‚ฌ์ „ํ•™์Šต ํ›„ ์ž๋™ ์‹œ์ž‘ python warmup_and_restart.py # ํ…”๋ ˆ๊ทธ๋žจ ๋ด‡๋งŒ ๋‹จ๋… ์‹คํ–‰ (๋””๋ฒ„๊น…์šฉ) python -m modules.services.telegram_bot.runner ``` ### 8.2 ์ข€๋น„ ํ”„๋กœ์„ธ์Šค ๊ด€๋ฆฌ - `main_server.py` ์‹คํ–‰ ์‹œ ์ž๋™์œผ๋กœ ์ด์ „ ์ข€๋น„ ํ”„๋กœ์„ธ์Šค ์ •๋ฆฌ - `pids.txt` ๊ธฐ๋ฐ˜ โ†’ ๋ฉ”๋ชจ๋ฆฌ ๊ธฐ๋ฐ˜ PID ์ถ”์ ์œผ๋กœ ์ „ํ™˜ ์™„๋ฃŒ - ์ˆ˜๋™ ํ™•์ธ: `Get-Process python` (PowerShell) ### 8.3 ๋กœ๊ทธ ํŒŒ์ผ | ํŒŒ์ผ | ์šฉ๋„ | |------|------| | `server.log` | Uvicorn ์„œ๋ฒ„ ๋กœ๊ทธ | | `telegram_bot.log` | ํ…”๋ ˆ๊ทธ๋žจ ๋ด‡ ๋กœ๊ทธ | | `warmup.log` | LSTM ์‚ฌ์ „ํ•™์Šต ์ง„ํ–‰ ๋กœ๊ทธ | | `bot_output.log` | ํŠธ๋ ˆ์ด๋”ฉ ๋ด‡ ์ถœ๋ ฅ ๋กœ๊ทธ | ### 8.4 ํŠธ๋Ÿฌ๋ธ”์ŠˆํŒ… | ์ฆ์ƒ | ์›์ธ | ํ•ด๊ฒฐ | |------|------|------| | KIS 403 Forbidden | ํ† ํฐ ๋งŒ๋ฃŒ ๋˜๋Š” Rate Limit | `data/kis_token.json` ์‚ญ์ œ ํ›„ ์žฌ์‹œ์ž‘ | | Telegram Conflict | ์ด์ „ ๋ด‡ ํ”„๋กœ์„ธ์Šค ๋ฏธ์ข…๋ฃŒ | `main_server.py` ์žฌ์‹œ์ž‘ (์ž๋™ ์ •๋ฆฌ) | | GPU OOM | LSTM + Ollama ๋™์‹œ ์ ์žฌ | `VRAM_WARNING_THRESHOLD` ๋‚ฎ์ถ”๊ธฐ | | CPU 100% ๊ณ ์ • | ์ข€๋น„ ์›Œ์ปค ํ”„๋กœ์„ธ์Šค | `main_server.py` ์žฌ์‹œ์ž‘ | | IPC ๋ฐ์ดํ„ฐ ์˜ค๋ž˜๋จ | ๋ฉ”์ธ ๋ด‡ ํฌ๋ž˜์‹œ | Watchdog ์ž๋™ ์žฌ์‹œ์ž‘ ํ™•์ธ, ์ˆ˜๋™ ์žฌ์‹œ์ž‘ | | ์˜ˆ์ˆ˜๊ธˆ ์ดˆ๊ณผ ๋งค์ˆ˜ | KIS ๋ชจ์˜ํˆฌ์ž T+2 ๋ฏธ์ฐจ๊ฐ | `MAX_DAILY_BUY_RATIO` / `MAX_BUY_PER_CYCLE` ์กฐ์ • | | Kelly ๋น„์ค‘์ด ๋„ˆ๋ฌด ๋‚ฎ์Œ | ๊ฑฐ๋ž˜ ๊ธฐ๋ก ๋ถ€์กฑ (< 10๊ฑด) | ์ดˆ๊ธฐ์—๋Š” ๊ธฐ๋ณธ๊ฐ’ 8% ์‚ฌ์šฉ, ๊ฑฐ๋ž˜ ๋ˆ„์  ํ›„ ์ž๋™ ์กฐ์ • | | ์•™์ƒ๋ธ” ๊ฐ€์ค‘์น˜ ๊ฐฑ์‹  ์•ˆ ๋จ | ๋งค๋„ ์ฒด๊ฒฐ ์—†์Œ ๋˜๋Š” `_buy_scores` ๋ˆ„๋ฝ | ๋ด‡ ์žฌ์‹œ์ž‘ ์ „ ๋งค๋„ ์™„๋ฃŒ ํ™•์ธ; `data/ensemble_history.json` ํ™•์ธ | --- ## 9. ๋ฐ์ดํ„ฐ ํ๋ฆ„ ์š”์•ฝ ``` [์‹œ์žฅ ๊ฐœ์žฅ ์ „] WatchlistManager โ†’ ๋‰ด์Šค ๋ถ„์„ โ†’ Watchlist ๊ฐฑ์‹  [์žฅ์ค‘ ์‚ฌ์ดํด (โ‰ˆ5๋ถ„ ๊ฐ„๊ฒฉ)] 1. SystemMonitor.check_health() โ†’ CPU/GPU ํ™•์ธ 2. MacroAnalyzer.get_macro_status() โ†’ ์‹œ์žฅ ์ƒํƒœ ํŒ๋‹จ 3. KIS โ†’ get_balance() โ†’ raw_deposit - today_buy_total = ๊ฐ€์šฉ ์˜ˆ์ˆ˜๊ธˆ 4. KIS โ†’ get_daily_ohlcv_batch() โ†’ OHLCV ์ˆ˜์ง‘ 5. ProcessPool โ†’ analyze_stock_process() ร— N์ข…๋ชฉ โ”œโ”€โ”€ ensemble.reload_if_stale() โ†’ ํŒŒ์ผ mtime ๊ฐ์ง€ ์‹œ ๊ฐ€์ค‘์น˜ ์žฌ๋กœ๋“œ โ”œโ”€โ”€ TechnicalAnalyzer โ†’ ๊ธฐ์ˆ ์  ์ ์ˆ˜ โ”œโ”€โ”€ PricePredictor โ†’ LSTM ์˜ˆ์ธก โ”œโ”€โ”€ OllamaManager โ†’ LLM ๊ฐ์„ฑ ๋ถ„์„ โ”œโ”€โ”€ AdaptiveEnsemble.get_weights() โ†’ ํ•™์Šต๋œ ๋™์  ๊ฐ€์ค‘์น˜ โ””โ”€โ”€ calculate_position_size() โ†’ Kelly Criterion ์ˆ˜๋Ÿ‰ ์‚ฐ์ถœ 6. ๋งค์ˆ˜ ํŒ๋‹จ โ†’ ์˜ˆ์ˆ˜๊ธˆ ํ™•์ธ โ†’ KIS ์ฃผ๋ฌธ โ”œโ”€โ”€ _buy_scores[ticker] ์ €์žฅ (์•™์ƒ๋ธ” ํ•™์Šต์šฉ) โ”œโ”€โ”€ _today_buy_total += ๋งค์ˆ˜๊ธˆ์•ก โ””โ”€โ”€ buys_this_cycle++ (MAX_BUY_PER_CYCLE ์ œํ•œ) 7. ๋งค๋„ ํŒ๋‹จ โ†’ KIS ์ฃผ๋ฌธ โ””โ”€โ”€ ensemble.record_trade() โ†’ ๊ฐ€์ค‘์น˜ ํ•™์Šต + ensemble_history.json ์ €์žฅ 8. SharedIPC.write_status() โ†’ ํ…”๋ ˆ๊ทธ๋žจ ๋ด‡์— ๊ณต์œ  9. TelegramMessenger โ†’ ๊ฒฐ๊ณผ ์•Œ๋ฆผ [์žฅ ๋งˆ๊ฐ ํ›„] PerformanceDB.save_daily_snapshot() โ†’ ์ผ๋ณ„ ์ž์‚ฐ ๊ธฐ๋ก Evaluator โ†’ ์ฃผ๊ฐ„ ๋ณด๊ณ ์„œ (์›”์š”์ผ) ``` --- ## 10. ๋ฒ„์ „ ๋ณ€๊ฒฝ ์ด๋ ฅ ### v3.1 (2026-03-19) โ€” ์ž”๊ณ  ๊ด€๋ฆฌ & ์•™์ƒ๋ธ” ํ•™์Šต ์™„์„ฑ **๋ฒ„๊ทธ ์ˆ˜์ •**: - `tracking_deposit` ์‚ฌ์ดํด ๊ฐ„ ์ดˆ๊ธฐํ™” ๋ฌธ์ œ โ†’ `_today_buy_total` ์ธ์Šคํ„ด์Šค ๋ณ€์ˆ˜๋กœ ๋ˆ„์  ์ถ”์  - KIS ๋ชจ์˜ํˆฌ์ž T+2 ๋ฏธ์ฐจ๊ฐ์œผ๋กœ ์ธํ•œ ์˜ˆ์ˆ˜๊ธˆ ์ดˆ๊ณผ ๋งค์ˆ˜ ๋ฐฉ์ง€ - `ai_confidence >= 0.85` ์ž„๊ณ„๊ฐ’ ๋ฒ„๊ทธ (LSTM confidence ์ƒํ•œ 0.80 ๋ฏธ๋ฐ˜์˜) โ†’ 0.75๋กœ ์ˆ˜์ • - OHLCV ํ”ผ์ฒ˜ ๋ˆ„๋ฝ ์‹œ silent fallback โ†’ ๊ฒฝ๊ณ  ๋กœ๊ทธ ์ถœ๋ ฅ **์‹ ๊ทœ ๊ธฐ๋Šฅ**: - `MAX_BUY_PER_CYCLE`: ์‚ฌ์ดํด๋‹น ์ตœ๋Œ€ ๋งค์ˆ˜ ์ข…๋ชฉ ์ˆ˜ ์ œํ•œ (๊ธฐ๋ณธ 2) - `MAX_DAILY_BUY_RATIO`: ์˜ˆ์ˆ˜๊ธˆ ๋Œ€๋น„ ์ผ์ผ ์ตœ๋Œ€ ๋งค์ˆ˜ ๋น„์œจ (๊ธฐ๋ณธ 80%) - `kis.get_balance()` โ†’ `today_buy_amt` ํ•„๋“œ ์ถ”๊ฐ€ (`thdt_buy_amt`) **์•™์ƒ๋ธ” (`analysis/ensemble.py`)**: - `AdaptiveEnsemble`์„ `process.py`์— ์‹ค์ œ ์—ฐ๋™ (ํ•˜๋“œ์ฝ”๋”ฉ ๊ฐ€์ค‘์น˜ ์ œ๊ฑฐ) - `get_kelly_fraction()`: Half-Kelly Criterion ํฌ์ง€์…˜ ๋น„์ค‘ ๊ณ„์‚ฐ ์ถ”๊ฐ€ - `SignalWeights.normalize()`: Water-Filling ์•Œ๊ณ ๋ฆฌ์ฆ˜์œผ๋กœ ๊ฒฝ๊ณ„ ์œ„๋ฐ˜ ๋ฌธ์ œ ํ•ด๊ฒฐ - `_accuracy()` ์ด์ง„ ์ง€ํ‘œ ์ œ๊ฑฐ โ†’ `_accuracy_weighted()` (ํฌ๊ธฐ ๊ฐ€์ค‘) ํ†ต์ผ - `reload_if_stale()`: ํŒŒ์ผ mtime ๊ธฐ๋ฐ˜ cross-process ๋™๊ธฐํ™” **ํฌ์ง€์…˜ ์‚ฌ์ด์ง• (`strategy/process.py`)**: - `calculate_position_size()`: ํ•˜๋“œ์ฝ”๋”ฉ 10% โ†’ Kelly Criterion (๊ณผ๊ฑฐ ์Šน๋ฅ ยท์†์ต๋น„ ๊ธฐ๋ฐ˜) - `bot.py` ์ค‘๋ณต ๊ณ„์‚ฐ ์ œ๊ฑฐ โ†’ ์›Œ์ปค์˜ `suggested_qty` ์ง์ ‘ ์‚ฌ์šฉ **์•™์ƒ๋ธ” ํ•™์Šต ๋ฃจํ”„ (`bot.py`)**: - BUY ์ฒด๊ฒฐ ์‹œ `_buy_scores[ticker]` ์‹ ํ˜ธ ์ ์ˆ˜ ์ €์žฅ - SELL ์ฒด๊ฒฐ ์‹œ `ensemble.record_trade()` โ†’ `ensemble_history.json` ๊ฐฑ์‹  - ์›Œ์ปค ํ”„๋กœ์„ธ์Šค๋Š” `reload_if_stale()`๋กœ ์ž๋™ ๋ฐ˜์˜