11회 리뷰 사이클에서 남긴 [코멘트]를 모두 본문에 반영하고 블록을 제거한다. 변경 문서: - docs/architecture.md: SmartScanner 동작 모드(both), 대시보드 10 API, DB 스키마(session_id/fx_pnl/mode), config 변수 갱신 - docs/commands.md: /api/pnl/history, /api/positions 엔드포인트 추가 - docs/testing.md: 테스트 수 고정값 제거, SmartScanner fallback 최신화, Dashboard 10 API routes 반영 - README.md: 고정 수치 제거, Gitea CI 명시, 파일별 수치 'CI 기준 변동' 표기 - CLAUDE.md: SmartScanner 섹션명 변경, 고정 수치 제거 - docs/requirements-log.md: #318~#331 구현 항목 추가 - docs/ouroboros/80_implementation_audit.md: ROOT-5/6/7 분리, REQ-V3-008 함수명 병기, v3 ~85% / 거버넌스 ~60%로 갱신 - docs/ouroboros/85_loss_recovery_action_plan.md: ACT-07 함수명 병기, 테스트 수 갱신, 6.1/6.2 정확도 개선 - docs/ouroboros/60_repo_enforcement_checklist.md: CI job/step 구분 표 추가 - docs/ouroboros/README.md: 50_* 문서 (A)/(B) 보조 표기 Closes #349
756 lines
33 KiB
Markdown
756 lines
33 KiB
Markdown
# System Architecture
|
||
|
||
## Overview
|
||
|
||
Self-evolving AI trading agent for global stock markets via KIS (Korea Investment & Securities) API. The main loop in `src/main.py` orchestrates components across multiple markets with two trading modes: daily (batch API calls) or realtime (per-stock decisions).
|
||
|
||
**v2 Proactive Playbook Architecture**: The system uses a "plan once, execute locally" approach. Pre-market, the AI generates a playbook of scenarios (one Gemini API call per market per day). During trading hours, a local scenario engine matches live market data against these pre-computed scenarios — no additional AI calls needed. This dramatically reduces API costs and latency.
|
||
|
||
## Trading Modes
|
||
|
||
The system supports two trading frequency modes controlled by the `TRADE_MODE` environment variable:
|
||
|
||
### Daily Mode (default)
|
||
|
||
Optimized for Gemini Free tier API limits (20 calls/day):
|
||
|
||
- **Batch decisions**: 1 API call per market per session
|
||
- **Fixed schedule**: 4 sessions per day at 6-hour intervals (configurable)
|
||
- **API efficiency**: Processes all stocks in a market simultaneously
|
||
- **Use case**: Free tier users, cost-conscious deployments
|
||
- **Configuration**:
|
||
```bash
|
||
TRADE_MODE=daily
|
||
DAILY_SESSIONS=4 # Sessions per day (1-10)
|
||
SESSION_INTERVAL_HOURS=6 # Hours between sessions (1-24)
|
||
```
|
||
|
||
**Example**: With 2 markets (US, KR) and 4 sessions/day = 8 API calls/day (within 20 call limit)
|
||
|
||
### Realtime Mode
|
||
|
||
High-frequency trading with individual stock analysis:
|
||
|
||
- **Per-stock decisions**: 1 API call per stock per cycle
|
||
- **60-second interval**: Continuous monitoring
|
||
- **Use case**: Production deployments with Gemini paid tier
|
||
- **Configuration**:
|
||
```bash
|
||
TRADE_MODE=realtime
|
||
```
|
||
|
||
**Note**: Realtime mode requires Gemini API subscription due to high call volume.
|
||
|
||
## Core Components
|
||
|
||
### 1. Broker (`src/broker/`)
|
||
|
||
**KISBroker** (`kis_api.py`) — Async KIS API client for domestic Korean market
|
||
|
||
- Automatic OAuth token refresh (valid for 24 hours)
|
||
- Leaky-bucket rate limiter (configurable RPS, default 2.0)
|
||
- POST body hash-key signing for order authentication
|
||
- Custom SSL context with disabled hostname verification for VTS (virtual trading) endpoint due to known certificate mismatch
|
||
- `fetch_market_rankings()` — Fetch volume surge rankings from KIS API
|
||
- `get_daily_prices()` — Fetch OHLCV history for technical analysis
|
||
|
||
**OverseasBroker** (`overseas.py`) — KIS overseas stock API wrapper
|
||
|
||
- Reuses KISBroker infrastructure (session, token, rate limiter) via composition
|
||
- Supports 9 global markets: US (NASDAQ/NYSE/AMEX), Japan, Hong Kong, China (Shanghai/Shenzhen), Vietnam (Hanoi/HCM)
|
||
- Different API endpoints for overseas price/balance/order operations
|
||
|
||
**Market Schedule** (`src/markets/schedule.py`) — Timezone-aware market management
|
||
|
||
- `MarketInfo` dataclass with timezone, trading hours, lunch breaks
|
||
- Automatic DST handling via `zoneinfo.ZoneInfo`
|
||
- `is_market_open()` checks weekends, trading hours, lunch breaks
|
||
- `get_open_markets()` returns currently active markets
|
||
- `get_next_market_open()` finds next market to open and when
|
||
- 10 global markets defined (KR, US_NASDAQ, US_NYSE, US_AMEX, JP, HK, CN_SHA, CN_SZA, VN_HNX, VN_HSX)
|
||
|
||
**Overseas Ranking API Methods** (added in v0.10.x):
|
||
- `fetch_overseas_rankings()` — Fetch overseas ranking universe (fluctuation / volume)
|
||
- Ranking endpoint paths and TR_IDs are configurable via environment variables
|
||
|
||
### 2. Analysis (`src/analysis/`)
|
||
|
||
**VolatilityAnalyzer** (`volatility.py`) — Technical indicator calculations
|
||
|
||
- ATR (Average True Range) for volatility measurement
|
||
- RSI (Relative Strength Index) using Wilder's smoothing method
|
||
- Price change percentages across multiple timeframes
|
||
- Volume surge ratios and price-volume divergence
|
||
- Momentum scoring (0-100 scale)
|
||
- Breakout/breakdown pattern detection
|
||
|
||
**TripleBarrierLabeler** (`triple_barrier.py`) — Financial time-series labeling (v2)
|
||
|
||
- Triple Barrier method: upper (take-profit), lower (stop-loss), time barrier
|
||
- First-touch labeling: labels confirmed by whichever barrier is breached first
|
||
- `max_holding_minutes` (calendar-minute) time barrier — session-aware, bar-period independent
|
||
- Tie-break mode: `"stop_first"` (conservative) or `"take_first"`
|
||
- Feature-label strict separation to prevent look-ahead bias
|
||
|
||
**BacktestPipeline** (`backtest_pipeline.py`) — End-to-end validation pipeline (v2)
|
||
|
||
- `run_v2_backtest_pipeline()`: cost guard → triple barrier labeling → walk-forward splits → fold scoring
|
||
- `BacktestPipelineResult`: artifact contract for reproducible output
|
||
- `fold_has_leakage()`: leakage detection utility
|
||
|
||
**WalkForwardSplit** (`walk_forward_split.py`) — Time-series validation (v2)
|
||
|
||
- Fold-based walk-forward splits (no random shuffling)
|
||
- Purge/Embargo: excludes N bars before/after fold boundaries to prevent data leakage
|
||
|
||
**BacktestExecutionModel** (`backtest_execution_model.py`) — Conservative fill simulation (v2/v3)
|
||
|
||
- Session-aware slippage: KRX_REG 5bps, NXT_AFTER 15bps, US_REG 3bps, US_PRE/DAY 30-50bps
|
||
- Order failure rate simulation per session
|
||
- Partial fill rate simulation with min/max ratio bounds
|
||
- Unfavorable-direction fill assumption (no simple close-price fill)
|
||
|
||
**BacktestCostGuard** (`backtest_cost_guard.py`) — Cost model validator (v2)
|
||
|
||
- `validate_backtest_cost_model()`: fail-fast check that session cost assumptions are present
|
||
- Enforces realistic cost assumptions before any backtest run proceeds
|
||
|
||
**SmartVolatilityScanner** (`smart_scanner.py`) — Python-first filtering pipeline
|
||
|
||
- **Domestic (KR)**:
|
||
- **Step 1**: Fetch domestic fluctuation ranking as primary universe
|
||
- **Step 2**: Fetch domestic volume ranking for liquidity bonus
|
||
- **Step 3**: Compute volatility-first score (max of daily change% and intraday range%)
|
||
- **Step 4**: Apply liquidity bonus and return top N candidates
|
||
- **Overseas (US/JP/HK/CN/VN)**:
|
||
- **Step 1**: Fetch overseas ranking universe (fluctuation rank + volume rank bonus)
|
||
- **Step 2**: Compute volatility-first score (max of daily change% and intraday range%)
|
||
- **Step 3**: Apply liquidity bonus from volume ranking
|
||
- **Step 4**: Return top N candidates (default 3)
|
||
- **Fallback (overseas only)**: If ranking API is unavailable, uses dynamic universe
|
||
from runtime active symbols + recent traded symbols + current holdings (no static watchlist)
|
||
- **Both modes**: Realtime 중심이지만 Daily 경로(`run_daily_session()`)에서도 후보 선별에 사용
|
||
|
||
**Benefits:**
|
||
- Reduces Gemini API calls from 20-30 stocks to 1-3 qualified candidates
|
||
- Fast Python-based filtering before expensive AI judgment
|
||
- Logs selection context (RSI-compatible proxy, volume_ratio, signal, score) for Evolution system
|
||
|
||
### 3. Brain (`src/brain/`)
|
||
|
||
**GeminiClient** (`gemini_client.py`) — AI decision engine powered by Google Gemini
|
||
|
||
- Constructs structured prompts from market data
|
||
- Parses JSON responses into `TradeDecision` objects (`action`, `confidence`, `rationale`)
|
||
- Forces HOLD when confidence < threshold (default 80)
|
||
- Falls back to safe HOLD on any parse/API error
|
||
- Handles markdown-wrapped JSON, malformed responses, invalid actions
|
||
|
||
**PromptOptimizer** (`prompt_optimizer.py`) — Token efficiency optimization
|
||
|
||
- Reduces prompt size while preserving decision quality
|
||
- Caches optimized prompts
|
||
|
||
**ContextSelector** (`context_selector.py`) — Relevant context selection for prompts
|
||
|
||
- Selects appropriate context layers for current market conditions
|
||
|
||
### 4. Risk Manager & Session Policy (`src/core/`)
|
||
|
||
**RiskManager** (`risk_manager.py`) — Safety circuit breaker and order validation
|
||
|
||
> **READ-ONLY by policy** (see [`docs/agents.md`](./agents.md))
|
||
|
||
- **Circuit Breaker**: Halts all trading via `SystemExit` when daily P&L drops below -3.0%
|
||
- Threshold may only be made stricter, never relaxed
|
||
- Calculated as `(total_eval - purchase_total) / purchase_total * 100`
|
||
- **Fat-Finger Protection**: Rejects orders exceeding 30% of available cash
|
||
- Must always be enforced, cannot be disabled
|
||
|
||
**OrderPolicy** (`order_policy.py`) — Session classification and order type enforcement (v3)
|
||
|
||
- `classify_session_id()`: Classifies current KR/US session from KST clock
|
||
- KR: `NXT_PRE` (08:00-08:50), `KRX_REG` (09:00-15:30), `NXT_AFTER` (15:30-20:00)
|
||
- US: `US_DAY` (10:00-18:00), `US_PRE` (18:00-23:30), `US_REG` (23:30-06:00), `US_AFTER` (06:00-07:00)
|
||
- Low-liquidity session detection: `NXT_AFTER`, `US_PRE`, `US_DAY`, `US_AFTER`
|
||
- Market order forbidden in low-liquidity sessions (`OrderPolicyRejected` raised)
|
||
- Limit/IOC/FOK orders always allowed
|
||
|
||
**KillSwitch** (`kill_switch.py`) — Emergency trading halt orchestration (v2)
|
||
|
||
- Fixed 5-step atomic sequence:
|
||
1. Block new orders (`new_orders_blocked = True`)
|
||
2. Cancel all unfilled orders
|
||
3. Refresh order state (query final status)
|
||
4. Reduce risk (force-close or reduce positions)
|
||
5. Snapshot state + send Telegram alert
|
||
- Async, injectable step callables — each step individually testable
|
||
- Highest priority: overrides overnight exception and all other rules
|
||
|
||
**BlackoutManager** (`blackout_manager.py`) — KIS maintenance window handling (v3)
|
||
|
||
- Configurable blackout windows (e.g., `23:30-00:10 KST`)
|
||
- `queue_order()`: Queues order intent during blackout, enforces max queue size
|
||
- `pop_recovery_batch()`: Returns queued intents after recovery
|
||
- Recovery revalidation path (in `src/main.py`):
|
||
- Stale BUY drop (position already exists)
|
||
- Stale SELL drop (position absent)
|
||
- `validate_order_policy()` rechecked
|
||
- Price drift check (>5% → drop, configurable via `BLACKOUT_RECOVERY_MAX_PRICE_DRIFT_PCT`)
|
||
|
||
### 5. Strategy (`src/strategy/`)
|
||
|
||
**PositionStateMachine** (`position_state_machine.py`) — 4-state sell state machine (v2)
|
||
|
||
- States: `HOLDING` → `BE_LOCK` → `ARMED` → `EXITED`
|
||
- `HOLDING`: Normal holding
|
||
- `BE_LOCK`: Profit ≥ `be_arm_pct` — stop-loss elevated to break-even
|
||
- `ARMED`: Profit ≥ `arm_pct` — peak-tracking trailing stop active
|
||
- `EXITED`: Position closed
|
||
- `promote_state()`: Immediately elevates to highest admissible state (handles gaps/skips)
|
||
- `evaluate_exit_first()`: EXITED conditions checked before state promotion
|
||
- Monotonic: states only move up, never down
|
||
|
||
**ExitRules** (`exit_rules.py`) — 4-layer composite exit logic (v2)
|
||
|
||
- **Hard Stop**: `unrealized <= hard_stop_pct` (always enforced, ATR-adaptive for KR)
|
||
- **Break-Even Lock**: Once in BE_LOCK/ARMED, exit if price falls to entry price
|
||
- **ATR Trailing Stop**: `trailing_stop_price = peak_price - (atr_multiplier_k × ATR)`
|
||
- **Model Signal**: Exit if `pred_down_prob >= model_prob_threshold AND liquidity_weak`
|
||
- `evaluate_exit()`: Returns `ExitEvaluation` with next state, exit flag, reason, trailing price
|
||
- `ExitRuleConfig`: Frozen dataclass with all tunable parameters
|
||
|
||
**Pre-Market Planner** (`pre_market_planner.py`) — AI playbook generation
|
||
|
||
- Runs before market open (configurable `PRE_MARKET_MINUTES`, default 30)
|
||
- Generates scenario-based playbooks via single Gemini API call per market
|
||
- Handles timeout (`PLANNER_TIMEOUT_SECONDS`, default 60) with defensive playbook fallback
|
||
- Persists playbooks to database for audit trail
|
||
|
||
**Scenario Engine** (`scenario_engine.py`) — Local scenario matching
|
||
|
||
- Matches live market data against pre-computed playbook scenarios
|
||
- No AI calls during trading hours — pure Python matching logic
|
||
- Returns matched scenarios with confidence scores
|
||
- Configurable `MAX_SCENARIOS_PER_STOCK` (default 5)
|
||
- Periodic rescan at `RESCAN_INTERVAL_SECONDS` (default 300)
|
||
|
||
**Playbook Store** (`playbook_store.py`) — Playbook persistence
|
||
|
||
- SQLite-backed storage for daily playbooks
|
||
- Date and market-based retrieval
|
||
- Status tracking (generated, active, expired)
|
||
|
||
**Models** (`models.py`) — Pydantic data models
|
||
|
||
- Scenario, Playbook, MatchResult, and related type definitions
|
||
|
||
### 6. Context System (`src/context/`)
|
||
|
||
**Context Store** (`store.py`) — L1-L7 hierarchical memory
|
||
|
||
- 7-layer context system (see [docs/context-tree.md](./context-tree.md)):
|
||
- L1: Tick-level (real-time price)
|
||
- L2: Intraday (session summary)
|
||
- L3: Daily (end-of-day)
|
||
- L4: Weekly (trend analysis)
|
||
- L5: Monthly (strategy review)
|
||
- L6: Daily Review (scorecard)
|
||
- L7: Evolution (long-term learning)
|
||
- Key-value storage with timeframe tagging
|
||
- SQLite persistence in `contexts` table
|
||
|
||
**Context Scheduler** (`scheduler.py`) — Periodic aggregation
|
||
|
||
- Scheduled summarization from lower to higher layers
|
||
- Configurable aggregation intervals
|
||
|
||
**Context Summarizer** (`summarizer.py`) — Layer summarization
|
||
|
||
- Aggregates lower-layer data into higher-layer summaries
|
||
|
||
### 7. Dashboard (`src/dashboard/`)
|
||
|
||
**FastAPI App** (`app.py`) — Read-only monitoring dashboard
|
||
|
||
- Runs as daemon thread when enabled (`--dashboard` CLI flag or `DASHBOARD_ENABLED=true`)
|
||
- Configurable host/port (`DASHBOARD_HOST`, `DASHBOARD_PORT`, default `127.0.0.1:8080`)
|
||
- Serves static HTML frontend
|
||
|
||
**10 API Endpoints:**
|
||
|
||
| Endpoint | Method | Description |
|
||
|----------|--------|-------------|
|
||
| `/` | GET | Static HTML dashboard |
|
||
| `/api/status` | GET | Daily trading status by market |
|
||
| `/api/playbook/{date}` | GET | Playbook for specific date and market |
|
||
| `/api/scorecard/{date}` | GET | Daily scorecard from L6_DAILY context |
|
||
| `/api/performance` | GET | Trading performance metrics (by market + combined) |
|
||
| `/api/context/{layer}` | GET | Query context by layer (L1-L7) |
|
||
| `/api/decisions` | GET | Decision log entries with outcomes |
|
||
| `/api/scenarios/active` | GET | Today's matched scenarios |
|
||
| `/api/pnl/history` | GET | P&L history time series |
|
||
| `/api/positions` | GET | Current open positions |
|
||
|
||
### 8. Notifications (`src/notifications/telegram_client.py`)
|
||
|
||
**TelegramClient** — Real-time event notifications via Telegram Bot API
|
||
|
||
- Sends alerts for trades, circuit breakers, fat-finger rejections, system events
|
||
- Non-blocking: failures are logged but never crash trading
|
||
- Rate-limited: 1 message/second default to respect Telegram API limits
|
||
- Auto-disabled when credentials missing
|
||
|
||
**TelegramCommandHandler** — Bidirectional command interface
|
||
|
||
- Long polling from Telegram API (configurable `TELEGRAM_POLLING_INTERVAL`)
|
||
- 9 interactive commands: `/help`, `/status`, `/positions`, `/report`, `/scenarios`, `/review`, `/dashboard`, `/stop`, `/resume`
|
||
- Authorization filtering by `TELEGRAM_CHAT_ID`
|
||
- Enable/disable via `TELEGRAM_COMMANDS_ENABLED` (default: true)
|
||
|
||
**Notification Types:**
|
||
- Trade execution (BUY/SELL with confidence)
|
||
- Circuit breaker trips (critical alert)
|
||
- Fat-finger protection triggers (order rejection)
|
||
- Market open/close events
|
||
- System startup/shutdown status
|
||
- Playbook generation results
|
||
- Stop-loss monitoring alerts
|
||
|
||
### 9. Evolution (`src/evolution/`)
|
||
|
||
**StrategyOptimizer** (`optimizer.py`) — Self-improvement loop
|
||
|
||
- Analyzes high-confidence losing trades from SQLite
|
||
- Asks Gemini to generate new `BaseStrategy` subclasses
|
||
- Validates generated strategies by running full pytest suite
|
||
- Simulates PR creation for human review
|
||
- Only activates strategies that pass all tests
|
||
|
||
**DailyReview** (`daily_review.py`) — End-of-day review
|
||
|
||
- Generates comprehensive trade performance summary
|
||
- Stores results in L6_DAILY context layer
|
||
- Tracks win rate, P&L, confidence accuracy
|
||
|
||
**DailyScorecard** (`scorecard.py`) — Performance scoring
|
||
|
||
- Calculates daily metrics (trades, P&L, win rate, avg confidence)
|
||
- Enables trend tracking across days
|
||
|
||
**Stop-Loss Monitoring** — Real-time position protection
|
||
|
||
- Monitors positions against stop-loss levels from playbook scenarios
|
||
- Sends Telegram alerts when thresholds approached or breached
|
||
|
||
### 10. Decision Logger (`src/logging/decision_logger.py`)
|
||
|
||
**DecisionLogger** — Comprehensive audit trail
|
||
|
||
- Logs every trading decision with full context snapshot
|
||
- Captures input data, rationale, confidence, and outcomes
|
||
- Supports outcome tracking (P&L, accuracy) for post-analysis
|
||
- Stored in `decision_logs` table with indexed queries
|
||
- Review workflow support (reviewed flag, review notes)
|
||
|
||
### 11. Data Integration (`src/data/`)
|
||
|
||
**External Data Sources** (optional):
|
||
|
||
- `news_api.py` — News sentiment data
|
||
- `market_data.py` — Extended market data
|
||
- `economic_calendar.py` — Economic event calendar
|
||
|
||
### 12. Backup (`src/backup/`)
|
||
|
||
**Disaster Recovery** (see [docs/disaster_recovery.md](./disaster_recovery.md)):
|
||
|
||
- `scheduler.py` — Automated backup scheduling
|
||
- `exporter.py` — Data export to various formats
|
||
- `cloud_storage.py` — S3-compatible cloud backup
|
||
- `health_monitor.py` — Backup integrity verification
|
||
|
||
## Data Flow
|
||
|
||
### Playbook Mode (Daily — Primary v2 Flow)
|
||
|
||
```
|
||
┌─────────────────────────────────────────────────────────────┐
|
||
│ Pre-Market Phase (before market open) │
|
||
└─────────────────────────────────────────────────────────────┘
|
||
│
|
||
▼
|
||
┌──────────────────────────────────┐
|
||
│ Pre-Market Planner │
|
||
│ - 1 Gemini API call per market │
|
||
│ - Generate scenario playbook │
|
||
│ - Store in playbooks table │
|
||
└──────────────────┬───────────────┘
|
||
│
|
||
▼
|
||
┌─────────────────────────────────────────────────────────────┐
|
||
│ Trading Hours (market open → close) │
|
||
└─────────────────────────────────────────────────────────────┘
|
||
│
|
||
▼
|
||
┌──────────────────────────────────┐
|
||
│ Market Schedule Check │
|
||
│ - Get open markets │
|
||
│ - Filter by enabled markets │
|
||
└──────────────────┬───────────────┘
|
||
│
|
||
▼
|
||
┌──────────────────────────────────┐
|
||
│ Scenario Engine (local) │
|
||
│ - Match live data vs playbook │
|
||
│ - No AI calls needed │
|
||
│ - Return matched scenarios │
|
||
└──────────────────┬───────────────┘
|
||
│
|
||
▼
|
||
┌──────────────────────────────────┐
|
||
│ Risk Manager: Validate Order │
|
||
│ - Check circuit breaker │
|
||
│ - Check fat-finger limit │
|
||
└──────────────────┬───────────────┘
|
||
│
|
||
▼
|
||
┌──────────────────────────────────┐
|
||
│ Broker: Execute Order │
|
||
│ - Domestic: send_order() │
|
||
│ - Overseas: send_overseas_order()│
|
||
└──────────────────┬───────────────┘
|
||
│
|
||
▼
|
||
┌──────────────────────────────────┐
|
||
│ Decision Logger + DB │
|
||
│ - Full audit trail │
|
||
│ - Context snapshot │
|
||
│ - Telegram notification │
|
||
└──────────────────┬───────────────┘
|
||
│
|
||
▼
|
||
┌─────────────────────────────────────────────────────────────┐
|
||
│ Post-Market Phase │
|
||
└─────────────────────────────────────────────────────────────┘
|
||
│
|
||
▼
|
||
┌──────────────────────────────────┐
|
||
│ Daily Review + Scorecard │
|
||
│ - Performance summary │
|
||
│ - Store in L6_DAILY context │
|
||
│ - Evolution learning │
|
||
└──────────────────────────────────┘
|
||
```
|
||
|
||
### Realtime Mode (with Smart Scanner)
|
||
|
||
```
|
||
┌─────────────────────────────────────────────────────────────┐
|
||
│ Main Loop (60s cycle per market) │
|
||
└─────────────────────────────────────────────────────────────┘
|
||
│
|
||
▼
|
||
┌──────────────────────────────────┐
|
||
│ Market Schedule Check │
|
||
│ - Get open markets │
|
||
│ - Filter by enabled markets │
|
||
│ - Wait if all closed │
|
||
└──────────────────┬───────────────┘
|
||
│
|
||
▼
|
||
┌──────────────────────────────────┐
|
||
│ Smart Scanner (Python-first) │
|
||
│ - Domestic: fluctuation rank │
|
||
│ + volume rank bonus │
|
||
│ + volatility-first scoring │
|
||
│ - Overseas: ranking universe │
|
||
│ + volatility-first scoring │
|
||
│ - Fallback: dynamic universe │
|
||
│ - Return top 3 qualified stocks │
|
||
└──────────────────┬───────────────┘
|
||
│
|
||
▼
|
||
┌──────────────────────────────────┐
|
||
│ For Each Qualified Candidate │
|
||
└──────────────────┬───────────────┘
|
||
│
|
||
▼
|
||
┌──────────────────────────────────┐
|
||
│ Broker: Fetch Market Data │
|
||
│ - Domestic: orderbook + balance │
|
||
│ - Overseas: price + balance │
|
||
└──────────────────┬───────────────┘
|
||
│
|
||
▼
|
||
┌──────────────────────────────────┐
|
||
│ Brain: Get Decision (AI) │
|
||
│ - Build prompt with market data │
|
||
│ - Call Gemini API │
|
||
│ - Parse JSON response │
|
||
│ - Return TradeDecision │
|
||
└──────────────────┬───────────────┘
|
||
│
|
||
▼
|
||
┌──────────────────────────────────┐
|
||
│ Risk Manager: Validate Order │
|
||
│ - Check circuit breaker │
|
||
│ - Check fat-finger limit │
|
||
└──────────────────┬───────────────┘
|
||
│
|
||
▼
|
||
┌──────────────────────────────────┐
|
||
│ Broker: Execute Order │
|
||
│ - Domestic: send_order() │
|
||
│ - Overseas: send_overseas_order()│
|
||
└──────────────────┬───────────────┘
|
||
│
|
||
▼
|
||
┌──────────────────────────────────┐
|
||
│ Decision Logger + Notifications │
|
||
│ - Log trade to SQLite │
|
||
│ - selection_context (JSON) │
|
||
│ - Telegram notification │
|
||
└──────────────────────────────────┘
|
||
```
|
||
|
||
## Database Schema
|
||
|
||
**SQLite** (`src/db.py`) — Database: `data/trades.db`
|
||
|
||
### trades
|
||
```sql
|
||
CREATE TABLE trades (
|
||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||
timestamp TEXT NOT NULL,
|
||
stock_code TEXT NOT NULL,
|
||
action TEXT NOT NULL, -- BUY | SELL | HOLD
|
||
confidence INTEGER NOT NULL, -- 0-100
|
||
rationale TEXT,
|
||
quantity INTEGER,
|
||
price REAL,
|
||
pnl REAL DEFAULT 0.0,
|
||
market TEXT DEFAULT 'KR',
|
||
exchange_code TEXT DEFAULT 'KRX',
|
||
session_id TEXT DEFAULT 'UNKNOWN', -- v3: KRX_REG | NXT_AFTER | US_REG | US_PRE | ...
|
||
selection_context TEXT, -- JSON: {rsi, volume_ratio, signal, score}
|
||
decision_id TEXT, -- Links to decision_logs
|
||
strategy_pnl REAL, -- v3: Core strategy P&L (separated from FX)
|
||
fx_pnl REAL DEFAULT 0.0, -- v3: FX gain/loss for USD trades (schema ready, activation pending)
|
||
mode TEXT -- paper | live
|
||
);
|
||
```
|
||
|
||
### contexts
|
||
```sql
|
||
CREATE TABLE contexts (
|
||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||
layer TEXT NOT NULL, -- L1 through L7
|
||
timeframe TEXT,
|
||
key TEXT NOT NULL,
|
||
value TEXT NOT NULL, -- JSON data
|
||
created_at TEXT NOT NULL,
|
||
updated_at TEXT NOT NULL
|
||
);
|
||
-- Indices: idx_contexts_layer, idx_contexts_timeframe, idx_contexts_updated
|
||
```
|
||
|
||
### decision_logs
|
||
```sql
|
||
CREATE TABLE decision_logs (
|
||
decision_id TEXT PRIMARY KEY,
|
||
timestamp TEXT NOT NULL,
|
||
stock_code TEXT,
|
||
market TEXT,
|
||
exchange_code TEXT,
|
||
session_id TEXT DEFAULT 'UNKNOWN', -- v3: session when decision was made
|
||
action TEXT,
|
||
confidence INTEGER,
|
||
rationale TEXT,
|
||
context_snapshot TEXT, -- JSON: full context at decision time
|
||
input_data TEXT, -- JSON: market data used
|
||
outcome_pnl REAL,
|
||
outcome_accuracy INTEGER,
|
||
reviewed INTEGER DEFAULT 0,
|
||
review_notes TEXT
|
||
);
|
||
-- Indices: idx_decision_logs_timestamp, idx_decision_logs_reviewed, idx_decision_logs_confidence
|
||
```
|
||
|
||
### playbooks
|
||
```sql
|
||
CREATE TABLE playbooks (
|
||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||
date TEXT NOT NULL,
|
||
market TEXT NOT NULL,
|
||
status TEXT NOT NULL DEFAULT 'pending', -- pending → generated → active → expired
|
||
playbook_json TEXT NOT NULL, -- Full playbook with scenarios
|
||
generated_at TEXT NOT NULL,
|
||
token_count INTEGER,
|
||
scenario_count INTEGER,
|
||
match_count INTEGER DEFAULT 0
|
||
);
|
||
-- Indices: idx_playbooks_date, idx_playbooks_market
|
||
```
|
||
|
||
### context_metadata
|
||
```sql
|
||
CREATE TABLE context_metadata (
|
||
layer TEXT PRIMARY KEY,
|
||
description TEXT,
|
||
retention_days INTEGER,
|
||
aggregation_source TEXT
|
||
);
|
||
```
|
||
|
||
## Configuration
|
||
|
||
**Pydantic Settings** (`src/config.py`)
|
||
|
||
Loaded from `.env` file:
|
||
|
||
```bash
|
||
# Required
|
||
KIS_APP_KEY=your_app_key
|
||
KIS_APP_SECRET=your_app_secret
|
||
KIS_ACCOUNT_NO=XXXXXXXX-XX
|
||
GEMINI_API_KEY=your_gemini_key
|
||
|
||
# Optional — Trading Mode
|
||
MODE=paper # paper | live
|
||
TRADE_MODE=daily # daily | realtime
|
||
DAILY_SESSIONS=4 # Sessions per day (daily mode only)
|
||
SESSION_INTERVAL_HOURS=6 # Hours between sessions (daily mode only)
|
||
|
||
# Optional — Database
|
||
DB_PATH=data/trades.db
|
||
|
||
# Optional — Risk
|
||
CONFIDENCE_THRESHOLD=80
|
||
MAX_LOSS_PCT=3.0
|
||
MAX_ORDER_PCT=30.0
|
||
|
||
# Optional — Markets
|
||
ENABLED_MARKETS=KR,US # Comma-separated market codes
|
||
RATE_LIMIT_RPS=2.0 # KIS API requests per second
|
||
|
||
# Optional — Pre-Market Planner (v2)
|
||
PRE_MARKET_MINUTES=30 # Minutes before market open to generate playbook
|
||
MAX_SCENARIOS_PER_STOCK=5 # Max scenarios per stock in playbook
|
||
PLANNER_TIMEOUT_SECONDS=60 # Timeout for playbook generation
|
||
DEFENSIVE_PLAYBOOK_ON_FAILURE=true # Fallback on AI failure
|
||
RESCAN_INTERVAL_SECONDS=300 # Scenario rescan interval during trading
|
||
|
||
# Optional — v2 Exit Rules (State Machine)
|
||
STAGED_EXIT_BE_ARM_PCT=1.2 # Break-even lock threshold (%)
|
||
STAGED_EXIT_ARM_PCT=3.0 # Armed state threshold (%)
|
||
KR_ATR_STOP_MULTIPLIER_K=2.0 # ATR multiplier for KR dynamic hard stop
|
||
KR_ATR_STOP_MIN_PCT=-2.0 # KR hard stop floor (must tighten, negative)
|
||
KR_ATR_STOP_MAX_PCT=-7.0 # KR hard stop ceiling (loosest, negative)
|
||
|
||
# Optional — v2 Trade Filters
|
||
STOP_LOSS_COOLDOWN_MINUTES=120 # Cooldown after stop-loss before re-entry (same ticker)
|
||
US_MIN_PRICE=5.0 # Minimum US stock price for BUY ($)
|
||
|
||
# Optional — v3 Session Risk Management
|
||
SESSION_RISK_RELOAD_ENABLED=true # Reload risk params at session boundaries
|
||
SESSION_RISK_PROFILES_JSON="{}" # Per-session overrides JSON: {"KRX_REG": {"be_arm_pct": 1.0}}
|
||
OVERNIGHT_EXCEPTION_ENABLED=true # Allow holding through session close (conditions apply)
|
||
|
||
# Optional — v3 Blackout (KIS maintenance windows)
|
||
ORDER_BLACKOUT_ENABLED=true
|
||
ORDER_BLACKOUT_WINDOWS_KST=23:30-00:10 # Comma-separated: "HH:MM-HH:MM"
|
||
ORDER_BLACKOUT_QUEUE_MAX=500 # Max queued orders during blackout
|
||
BLACKOUT_RECOVERY_PRICE_REVALIDATION_ENABLED=true
|
||
BLACKOUT_RECOVERY_MAX_PRICE_DRIFT_PCT=5.0 # Drop recovery order if price drifted >5%
|
||
|
||
# Optional — Smart Scanner (realtime mode only)
|
||
RSI_OVERSOLD_THRESHOLD=30 # 0-50, oversold threshold
|
||
RSI_MOMENTUM_THRESHOLD=70 # 50-100, momentum threshold
|
||
VOL_MULTIPLIER=2.0 # Minimum volume ratio (2.0 = 200%)
|
||
SCANNER_TOP_N=3 # Max qualified candidates per scan
|
||
|
||
# Optional — Dashboard
|
||
DASHBOARD_ENABLED=false # Enable FastAPI dashboard
|
||
DASHBOARD_HOST=127.0.0.1 # Dashboard bind address
|
||
DASHBOARD_PORT=8080 # Dashboard port (1-65535)
|
||
|
||
# Optional — Telegram
|
||
TELEGRAM_BOT_TOKEN=1234567890:ABCdefGHIjklMNOpqrsTUVwxyz
|
||
TELEGRAM_CHAT_ID=123456789
|
||
TELEGRAM_ENABLED=true
|
||
TELEGRAM_COMMANDS_ENABLED=true # Enable bidirectional commands
|
||
TELEGRAM_POLLING_INTERVAL=1.0 # Command polling interval (seconds)
|
||
|
||
# Optional — Backup
|
||
BACKUP_ENABLED=false
|
||
BACKUP_DIR=data/backups
|
||
S3_ENDPOINT_URL=...
|
||
S3_ACCESS_KEY=...
|
||
S3_SECRET_KEY=...
|
||
S3_BUCKET_NAME=...
|
||
S3_REGION=...
|
||
|
||
# Optional — External Data
|
||
NEWS_API_KEY=...
|
||
NEWS_API_PROVIDER=...
|
||
MARKET_DATA_API_KEY=...
|
||
|
||
# Position Sizing (optional)
|
||
POSITION_SIZING_ENABLED=true
|
||
POSITION_BASE_ALLOCATION_PCT=5.0
|
||
POSITION_MIN_ALLOCATION_PCT=1.0
|
||
POSITION_MAX_ALLOCATION_PCT=10.0
|
||
POSITION_VOLATILITY_TARGET_SCORE=50.0
|
||
|
||
# Legacy/compat scanner thresholds (kept for backward compatibility)
|
||
RSI_OVERSOLD_THRESHOLD=30
|
||
RSI_MOMENTUM_THRESHOLD=70
|
||
VOL_MULTIPLIER=2.0
|
||
|
||
# Overseas Ranking API (optional override; account-dependent)
|
||
OVERSEAS_RANKING_ENABLED=true
|
||
OVERSEAS_RANKING_FLUCT_TR_ID=HHDFS76200100
|
||
OVERSEAS_RANKING_VOLUME_TR_ID=HHDFS76200200
|
||
OVERSEAS_RANKING_FLUCT_PATH=/uapi/overseas-price/v1/quotations/inquire-updown-rank
|
||
OVERSEAS_RANKING_VOLUME_PATH=/uapi/overseas-price/v1/quotations/inquire-volume-rank
|
||
```
|
||
|
||
Tests use in-memory SQLite (`DB_PATH=":memory:"`) and dummy credentials via `tests/conftest.py`.
|
||
|
||
## Error Handling
|
||
|
||
### Connection Errors (Broker API)
|
||
- Retry with exponential backoff (2^attempt seconds)
|
||
- Max 3 retries per stock
|
||
- After exhaustion, skip stock and continue with next
|
||
|
||
### API Quota Errors (Gemini)
|
||
- Return safe HOLD decision with confidence=0
|
||
- Log error but don't crash
|
||
- Agent continues trading on next cycle
|
||
|
||
### Circuit Breaker Tripped
|
||
- Immediately halt via `SystemExit`
|
||
- Log critical message
|
||
- Requires manual intervention to restart
|
||
|
||
### Market Closed
|
||
- Wait until next market opens
|
||
- Use `get_next_market_open()` to calculate wait time
|
||
- Sleep until market open time
|
||
|
||
### Telegram API Errors
|
||
- Log warning but continue trading
|
||
- Missing credentials → auto-disable notifications
|
||
- Network timeout → skip notification, no retry
|
||
- Invalid token → log error, trading unaffected
|
||
- Rate limit exceeded → queued via rate limiter
|
||
|
||
### Playbook Generation Failure
|
||
- Timeout → fall back to defensive playbook (`DEFENSIVE_PLAYBOOK_ON_FAILURE`)
|
||
- API error → use previous day's playbook if available
|
||
- No playbook → skip pre-market phase, fall back to direct AI calls
|
||
|
||
**Guarantee**: Notification and dashboard failures never interrupt trading operations.
|