"""Strictly typed configuration loaded from environment variables.""" from __future__ import annotations from pydantic import Field from pydantic_settings import BaseSettings class Settings(BaseSettings): """Application settings — loaded from .env or environment variables.""" # KIS Open API KIS_APP_KEY: str KIS_APP_SECRET: str KIS_ACCOUNT_NO: str # format: "XXXXXXXX-XX" KIS_BASE_URL: str = "https://openapivts.koreainvestment.com:29443" # Google Gemini GEMINI_API_KEY: str GEMINI_MODEL: str = "gemini-2.0-flash" # External Data APIs (optional — for data-driven decisions) NEWS_API_KEY: str | None = None NEWS_API_PROVIDER: str = "alphavantage" # "alphavantage" or "newsapi" MARKET_DATA_API_KEY: str | None = None # Legacy field names (for backward compatibility) ALPHA_VANTAGE_API_KEY: str | None = None NEWSAPI_KEY: str | None = None # Risk Management CIRCUIT_BREAKER_PCT: float = Field(default=-3.0, le=0.0) FAT_FINGER_PCT: float = Field(default=30.0, gt=0.0, le=100.0) CONFIDENCE_THRESHOLD: int = Field(default=80, ge=0, le=100) # Smart Scanner Configuration RSI_OVERSOLD_THRESHOLD: int = Field(default=30, ge=0, le=50) RSI_MOMENTUM_THRESHOLD: int = Field(default=70, ge=50, le=100) VOL_MULTIPLIER: float = Field(default=2.0, gt=1.0, le=10.0) SCANNER_TOP_N: int = Field(default=3, ge=1, le=10) POSITION_SIZING_ENABLED: bool = True POSITION_BASE_ALLOCATION_PCT: float = Field(default=5.0, gt=0.0, le=30.0) POSITION_MIN_ALLOCATION_PCT: float = Field(default=1.0, gt=0.0, le=20.0) POSITION_MAX_ALLOCATION_PCT: float = Field(default=10.0, gt=0.0, le=50.0) POSITION_VOLATILITY_TARGET_SCORE: float = Field(default=50.0, gt=0.0, le=100.0) # Database DB_PATH: str = "data/trade_logs.db" # Rate Limiting (requests per second for KIS API) # Conservative limit to avoid EGW00201 "초당 거래건수 초과" errors. # KIS API real limit is ~2 RPS; 2.0 provides maximum safety. RATE_LIMIT_RPS: float = 2.0 # Trading mode MODE: str = Field(default="paper", pattern="^(paper|live)$") # Simulated USD cash for VTS (paper) overseas trading. # KIS VTS overseas balance API returns errors for most accounts. # This value is used as a fallback when the balance API returns 0 in paper mode. PAPER_OVERSEAS_CASH: float = Field(default=50000.0, ge=0.0) USD_BUFFER_MIN: float = Field(default=1000.0, ge=0.0) # Trading frequency mode (daily = batch API calls, realtime = per-stock calls) TRADE_MODE: str = Field(default="daily", pattern="^(daily|realtime)$") DAILY_SESSIONS: int = Field(default=4, ge=1, le=10) SESSION_INTERVAL_HOURS: int = Field(default=6, ge=1, le=24) ORDER_BLACKOUT_ENABLED: bool = True ORDER_BLACKOUT_WINDOWS_KST: str = "23:30-00:10" ORDER_BLACKOUT_QUEUE_MAX: int = Field(default=500, ge=10, le=5000) # Pre-Market Planner PRE_MARKET_MINUTES: int = Field(default=30, ge=10, le=120) MAX_SCENARIOS_PER_STOCK: int = Field(default=5, ge=1, le=10) PLANNER_TIMEOUT_SECONDS: int = Field(default=60, ge=10, le=300) DEFENSIVE_PLAYBOOK_ON_FAILURE: bool = True RESCAN_INTERVAL_SECONDS: int = Field(default=300, ge=60, le=900) # Market selection (comma-separated market codes) ENABLED_MARKETS: str = "KR,US" # Backup and Disaster Recovery (optional) BACKUP_ENABLED: bool = True BACKUP_DIR: str = "data/backups" S3_ENDPOINT_URL: str | None = None # For MinIO, Backblaze B2, etc. S3_ACCESS_KEY: str | None = None S3_SECRET_KEY: str | None = None S3_BUCKET_NAME: str | None = None S3_REGION: str = "us-east-1" # Telegram Notifications (optional) TELEGRAM_BOT_TOKEN: str | None = None TELEGRAM_CHAT_ID: str | None = None TELEGRAM_ENABLED: bool = True # Telegram Commands (optional) TELEGRAM_COMMANDS_ENABLED: bool = True TELEGRAM_POLLING_INTERVAL: float = 1.0 # seconds # Telegram notification type filters (granular control) # circuit_breaker is always sent regardless — safety-critical TELEGRAM_NOTIFY_TRADES: bool = True # BUY/SELL execution alerts TELEGRAM_NOTIFY_MARKET_OPEN_CLOSE: bool = True # Market open/close alerts TELEGRAM_NOTIFY_FAT_FINGER: bool = True # Fat-finger rejection alerts TELEGRAM_NOTIFY_SYSTEM_EVENTS: bool = True # System start/shutdown alerts TELEGRAM_NOTIFY_PLAYBOOK: bool = True # Playbook generated/failed alerts TELEGRAM_NOTIFY_SCENARIO_MATCH: bool = True # Scenario matched alerts (most frequent) TELEGRAM_NOTIFY_ERRORS: bool = True # Error alerts # Overseas ranking API (KIS endpoint/TR_ID may vary by account/product) # Override these from .env if your account uses different specs. OVERSEAS_RANKING_ENABLED: bool = True OVERSEAS_RANKING_FLUCT_TR_ID: str = "HHDFS76290000" OVERSEAS_RANKING_VOLUME_TR_ID: str = "HHDFS76270000" OVERSEAS_RANKING_FLUCT_PATH: str = ( "/uapi/overseas-stock/v1/ranking/updown-rate" ) OVERSEAS_RANKING_VOLUME_PATH: str = ( "/uapi/overseas-stock/v1/ranking/volume-surge" ) # Dashboard (optional) DASHBOARD_ENABLED: bool = False DASHBOARD_HOST: str = "127.0.0.1" DASHBOARD_PORT: int = Field(default=8080, ge=1, le=65535) model_config = {"env_file": ".env", "env_file_encoding": "utf-8"} @property def account_number(self) -> str: return self.KIS_ACCOUNT_NO.split("-")[0] @property def account_product_code(self) -> str: return self.KIS_ACCOUNT_NO.split("-")[1] @property def enabled_market_list(self) -> list[str]: """Parse ENABLED_MARKETS into list of market codes.""" from src.markets.schedule import expand_market_codes raw = [m.strip() for m in self.ENABLED_MARKETS.split(",") if m.strip()] return expand_market_codes(raw)