61 lines
2.8 KiB
Python
61 lines
2.8 KiB
Python
"""应用配置(pydantic-settings)。可由 .env / 环境变量覆盖。"""
|
||
from pydantic import model_validator
|
||
from pydantic_settings import BaseSettings, SettingsConfigDict
|
||
|
||
|
||
class Settings(BaseSettings):
|
||
model_config = SettingsConfigDict(env_file=".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(主数据源)
|
||
data_adjust: str = "qfq" # 复权:qfq 前复权 / hfq 后复权 / "" 不复权
|
||
data_default_start: str = "20200101" # 默认拉取起点(约近 5 年)
|
||
|
||
# ---- 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()
|