Files
The-Ouroboros/docs/architecture.md
agentson 733e6b36e9
Some checks failed
CI / test (pull_request) Has been cancelled
feat: unify domestic scanner and sizing; update docs
2026-02-17 06:29:36 +09:00

27 KiB

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:
    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:
    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

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)
  • Realtime mode only: Daily mode uses batch processing for API efficiency

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 (src/core/risk_manager.py)

RiskManager — Safety circuit breaker and order validation

READ-ONLY by policy (see docs/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

5. Strategy (src/strategy/)

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):
    • 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

8 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

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):

  • 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

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',
    selection_context TEXT,        -- JSON: {rsi, volume_ratio, signal, score}
    decision_id TEXT              -- Links to decision_logs
);

contexts

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

CREATE TABLE decision_logs (
    decision_id TEXT PRIMARY KEY,
    timestamp TEXT NOT NULL,
    stock_code TEXT,
    market TEXT,
    exchange_code TEXT,
    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 REAL,
    reviewed INTEGER DEFAULT 0,
    review_notes TEXT
);
-- Indices: idx_decision_logs_timestamp, idx_decision_logs_reviewed, idx_decision_logs_confidence

playbooks

CREATE TABLE playbooks (
    id INTEGER PRIMARY KEY AUTOINCREMENT,
    date TEXT NOT NULL,
    market TEXT NOT NULL,
    status TEXT DEFAULT 'generated',
    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

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:

# 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 — 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.