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
33 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 APIget_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
MarketInfodataclass with timezone, trading hours, lunch breaks- Automatic DST handling via
zoneinfo.ZoneInfo is_market_open()checks weekends, trading hours, lunch breaksget_open_markets()returns currently active marketsget_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 scoringBacktestPipelineResult: artifact contract for reproducible outputfold_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
TradeDecisionobjects (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)
- Circuit Breaker: Halts all trading via
SystemExitwhen 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)
- KR:
- Low-liquidity session detection:
NXT_AFTER,US_PRE,US_DAY,US_AFTER - Market order forbidden in low-liquidity sessions (
OrderPolicyRejectedraised) - Limit/IOC/FOK orders always allowed
KillSwitch (kill_switch.py) — Emergency trading halt orchestration (v2)
- Fixed 5-step atomic sequence:
- Block new orders (
new_orders_blocked = True) - Cancel all unfilled orders
- Refresh order state (query final status)
- Reduce risk (force-close or reduce positions)
- Snapshot state + send Telegram alert
- Block new orders (
- 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 sizepop_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→EXITEDHOLDING: Normal holdingBE_LOCK: Profit ≥be_arm_pct— stop-loss elevated to break-evenARMED: Profit ≥arm_pct— peak-tracking trailing stop activeEXITED: 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(): ReturnsExitEvaluationwith next state, exit flag, reason, trailing priceExitRuleConfig: 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):
- 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
contextstable
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 (
--dashboardCLI flag orDASHBOARD_ENABLED=true) - Configurable host/port (
DASHBOARD_HOST,DASHBOARD_PORT, default127.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
BaseStrategysubclasses - 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_logstable with indexed queries - Review workflow support (reviewed flag, review notes)
11. Data Integration (src/data/)
External Data Sources (optional):
news_api.py— News sentiment datamarket_data.py— Extended market dataeconomic_calendar.py— Economic event calendar
12. Backup (src/backup/)
Disaster Recovery (see docs/disaster_recovery.md):
scheduler.py— Automated backup schedulingexporter.py— Data export to various formatscloud_storage.py— S3-compatible cloud backuphealth_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',
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
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,
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
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
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 — 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.