"""应用配置(pydantic-settings)。可由 .env / 环境变量覆盖。""" from pathlib import Path from pydantic import model_validator from pydantic_settings import BaseSettings, SettingsConfigDict # 确保 .env 文件总是相对于 config.py 所在目录加载,无论从哪个目录启动 uvicorn _ENV_FILE = Path(__file__).resolve().parent.parent / ".env" class Settings(BaseSettings): model_config = SettingsConfigDict(env_file=str(_ENV_FILE) if _ENV_FILE.exists() else ".env", extra="ignore") app_name: str = "Stock Backtest" # 开发、测试、生产统一使用 PostgreSQL,避免不同数据库行为产生偏差。 database_url: str = "postgresql+asyncpg://postgres:postgres@localhost:5432/stock" # ---- 鉴权 ---- auth_cookie_name: str = "stock_session" auth_cookie_secure: bool = False # 生产 HTTPS 必须覆盖为 true auth_session_hours: int = 12 auth_max_failed_logins: int = 5 auth_lock_minutes: int = 15 auth_min_password_length: int = 12 cors_origins: str = "http://localhost:5173" expose_api_docs: bool = True # 真实数据源 tushare_token: str = "" # Tushare Pro token(主数据源) # 15000 积分档 token 走 quicksync 镜像(官方 api.tushare.pro 对其返回 40101); # 留空 = 官方接口。SDK 侧需改写 DataApi.__http_url,见 tushare_provider.get_pro tushare_api_url: str = "" data_adjust: str = "qfq" # 复权:qfq 前复权 / hfq 后复权 / "" 不复权 data_default_start: str = "20200101" # 默认拉取起点(约近 5 年) # ---- Redis 读缓存(股票列表/筛选项等读多写少接口;留空 = 不缓存,直查数据库)---- redis_url: str = "" stocks_cache_ttl: int = 300 # 股票列表缓存秒数(行情列允许最多滞后这么多秒) facets_cache_ttl: int = 3600 # 行业/地域筛选项缓存秒数(stock_basic 很少变) # ---- 主页大盘总览(live 层腾讯实时 + EOD 层 tushare 收盘,见 data/market_overview.py)---- market_live_ttl: int = 30 # 腾讯实时行情进程内缓存秒数(成功) market_eod_fresh_ttl: int = 14400 # EOD 层新鲜期(秒),过期走 SWR(先返旧值后台刷新) market_eod_redis_ttl: int = 86400 # EOD 层 Redis TTL(SWR 陈旧值的兜底存活期) tencent_quote_timeout: float = 3.0 # 腾讯行情 HTTP 超时(秒) sync_stats_redis_ttl: int = 43200 # 同步状态重聚合的 Redis 缓存秒数(key 带版本号,同步完成即失效) # ---- LLM(智能选股的自然语言解析;DeepSeek,OpenAI 兼容协议,可换任意兼容网关)---- llm_base_url: str = "https://api.deepseek.com" llm_api_key: str = "" # 留空则智能选股不可用(其余功能不受影响) llm_model: str = "deepseek-chat" llm_timeout: float = 60.0 # ---- 智能选股 ---- screener_market_days: int = 90 # 全市场同步窗口(交易日数) screener_default_limit: int = 200 # 选股结果条数上限 screener_sync_interval: float = 0.35 # 全市场批量调用间隔(秒),Tushare 控频 # A股交易成本(基准日 2026-08)——做成可配置参数,便于将来按生效日期版本化 stamp_duty_rate: float = 0.0005 # 印花税 0.05%,单边卖出(2023-08-28 减半) transfer_fee_rate: float = 0.00001 # 过户费 0.001%,沪深双边(2022 调整) commission_rate: float = 0.0001 # 佣金 万1(含规费) commission_min: float = 5.0 # 最低 5 元 slippage_rate: float = 0.0005 # 滑点近似(按价格比例) @model_validator(mode="after") def require_postgresql(self) -> "Settings": if not self.database_url.startswith("postgresql+asyncpg://"): raise ValueError("DATABASE_URL 必须使用 postgresql+asyncpg://,本项目不再支持 SQLite") if self.auth_session_hours <= 0: raise ValueError("AUTH_SESSION_HOURS 必须大于 0") return self @property def allowed_origins(self) -> list[str]: return [origin.strip() for origin in self.cors_origins.split(",") if origin.strip()] settings = Settings()