""" 配置管理模块 从环境变量加载配置 """ import os from functools import lru_cache from pathlib import Path from typing import Any, Dict, List, Optional from pydantic_settings import BaseSettings # 查找.env文件的位置 def find_env_file(): """查找.env文件,支持从backend目录或项目根目录启动""" current_dir = Path.cwd() # 尝试当前目录 env_path = current_dir / ".env" if env_path.exists(): print(f"[Config] 找到.env文件: {env_path}") return str(env_path) # 尝试父目录(项目根目录) env_path = current_dir.parent / ".env" if env_path.exists(): print(f"[Config] 找到.env文件: {env_path}") return str(env_path) # 尝试backend的父目录 if current_dir.name == "backend": env_path = current_dir.parent / ".env" if env_path.exists(): print(f"[Config] 找到.env文件: {env_path}") return str(env_path) # 尝试从环境变量指定的路径 if os.getenv('ENV_FILE'): env_path = Path(os.getenv('ENV_FILE')) if env_path.exists(): print(f"[Config] 从ENV_FILE环境变量找到.env文件: {env_path}") return str(env_path) print(f"[Config] 警告:未找到.env文件,当前目录: {current_dir}") # 默认返回当前目录的.env return ".env" class Settings(BaseSettings): """应用配置""" # LLM配置 zhipuai_api_key: str = "" deepseek_api_key: str = "" # 数据库配置 database_url: str = "sqlite:///./stock_agent.db" # API配置 api_host: str = "0.0.0.0" api_port: int = 8000 debug: bool = True # 安全配置 secret_key: str = "change-this-secret-key-in-production" rate_limit: str = "100/minute" # JWT配置 jwt_algorithm: str = "HS256" jwt_expire_days: int = 7 # 腾讯云短信配置 tencent_sms_app_id: str = "1400961527" tencent_sms_secret_id: str = "AKIDxnbGj281iHtKallqqzvlV5YxBCrPltnS" # 腾讯云SecretId tencent_sms_secret_key: str = "ta6PXTMBsX7dzA7IN6uYUFn8F9uTovoU" # 腾讯云SecretKey tencent_sms_sign_id: str = "629073" tencent_sms_template_id: str = "2353142" # 验证码配置 code_expire_minutes: int = 5 code_resend_seconds: int = 60 code_max_per_hour: int = 10 # 白名单手机号(无需验证码即可登录) whitelist_phones: str = "18583366860,18583926860" # CORS配置 cors_origins: str = "http://localhost:8000,http://127.0.0.1:8000" # Binance 配置(公开数据不需要 API 密钥) binance_api_key: str = "" binance_api_secret: str = "" # 飞书机器人配置 feishu_crypto_webhook_url: str = "https://open.feishu.cn/open-apis/bot/v2/hook/8a1dcf69-6753-41e2-a393-edc4f7822db0" feishu_paper_trading_webhook_url: str = "https://open.feishu.cn/open-apis/bot/v2/hook/3f5642e7-420b-45f7-8f88-fff92bb98c69" # 模拟交易通知(交易信号+决策+执行) feishu_error_webhook_url: str = "https://open.feishu.cn/open-apis/bot/v2/hook/ba6952c9-3b0c-4bc1-8a43-ceaacb27b043" # 系统异常通知 feishu_enabled: bool = True # 是否启用飞书通知 # Telegram 机器人配置 telegram_bot_token: str = "" # 从 @BotFather 获取 telegram_channel_id: str = "" # 频道 ID,如 @your_channel 或 -1001234567890 telegram_enabled: bool = True # 是否启用 Telegram 通知 # 钉钉机器人配置 dingtalk_webhook_url: str = "https://oapi.dingtalk.com/robot/send?access_token=a4fa1c1a6a07a5ed07d79c701f79b44efb1e726da3b47b50495ebdc9190423ec" # 钉钉群机器人 Webhook dingtalk_secret: str = "SECdc6dffe3b6838a5d8afde3486d5415b9a17d3ebc9cbf934438883acee1189e8d" # 加签密钥 dingtalk_enabled: bool = True # 是否启用钉钉通知 # 加密货币交易智能体配置 crypto_symbols: str = "BTCUSDT,ETHUSDT" # 监控的交易对,逗号分隔 crypto_llm_threshold: float = 0.70 # 触发 LLM 分析的置信度阈值 # 价格监控模式配置 use_bitget_websocket: bool = True # 是否使用 Bitget WebSocket 实时价格(默认 False 使用 Binance 轮询) # 波动率过滤配置(节省 LLM 调用) crypto_volatility_filter_enabled: bool = True # 是否启用波动率过滤 crypto_min_volatility_percent: float = 0.5 # 最小波动率(百分比),低于此值跳过分析 crypto_min_price_range_percent: float = 0.3 # 最小价格变动范围(百分比),低于此值跳过分析 crypto_5m_surge_threshold: float = 1.0 # 5分钟突发波动阈值(百分比),超过此值即使1小时波动率低也会触发分析 crypto_intraday_llm_cooldown_minutes: int = 15 # 日内 LLM 分析冷却时间 crypto_trend_llm_cooldown_minutes: int = 60 # 趋势 LLM 分析冷却时间 crypto_force_llm_surge_threshold: float = 1.2 # 15分钟突发波动强制触发 LLM 的阈值 crypto_force_llm_trade_zone_pct: float = 0.25 # 接近关键交易区时强制触发 LLM 的距离阈值 crypto_event_analysis_enabled: bool = True # 是否启用实时行情事件触发分析 crypto_event_analysis_window_minutes: int = 5 # 实时行情异动检测窗口 crypto_event_analysis_price_change_percent: float = 0.8 # 检测窗口内涨跌超过该阈值触发日内分析 crypto_event_analysis_cooldown_minutes: int = 10 # 同一交易对事件触发分析冷却 # Brave Search API 配置 brave_api_key: str = "" # 模拟交易配置 paper_trading_enabled: bool = True # 是否启用模拟交易 paper_trading_initial_balance: float = 10000 # 初始本金 (USDT) paper_trading_leverage: int = 10 # 单笔订单杠杆倍数(从20改为10) paper_trading_max_total_leverage: float = 10 # 总杠杆上限(持仓+挂单,倍数) paper_trading_margin_per_order: float = 1000 # 每单保证金 (USDT) paper_trading_max_orders: int = 10 # 最大持仓+挂单总数 paper_trading_auto_close_opposite: bool = False # 是否自动平掉反向持仓(智能策略) paper_trading_breakeven_threshold: float = 1 # 保本止损触发阈值(盈利百分比),0表示禁用 paper_trading_order_timeout_hours: float = 4 # 挂单超时时间(小时),默认4小时 # 移动止损配置 paper_trading_trailing_stop_enabled: bool = True # 是否启用移动止损 paper_trading_trailing_stop_threshold_multiplier: float = 2 # 移动止损触发倍数(相对于保本阈值) paper_trading_trailing_stop_ratio: float = 0.5 # 移动止损跟随比例(0-1之间,1表示完全跟随) # 动态止盈配置(趋势过滤) paper_trading_dynamic_tp_enabled: bool = True # 是否启用动态止盈 paper_trading_strong_trend_ratio: float = 0.7 # 强趋势时移动止损跟随比例(70%) paper_trading_weak_trend_ratio: float = 0.3 # 弱趋势时移动止损跟随比例(30%) paper_trading_sideways_tp_percent: float = 3 # 震荡市固定止盈百分比(3%) # 废弃的配置(保留兼容性) paper_trading_position_a: float = 1000 # A级信号仓位 (USDT) paper_trading_position_b: float = 500 # B级信号仓位 (USDT) paper_trading_position_c: float = 200 # C级信号仓位 (USDT) # ========== 实盘交易配置 ========== # Bitget API 配置 bitget_api_key: str = "" # Bitget API Key bitget_api_secret: str = "" # Bitget API Secret bitget_passphrase: str = "" # Bitget API Passphrase bitget_use_testnet: bool = True # 是否使用测试网(测试时设为 true) bitget_use_unified_account: bool = True # 使用统一账户(UTA)接口 # 实盘风险控制(Bitget 实盘共用) bitget_max_single_position: float = 1000 # 单笔最大持仓金额 (USDT) bitget_max_total_leverage: float = 10 # 总杠杆上限(倍数) bitget_default_leverage: int = 10 # 默认执行杠杆(启动时同步到交易对) # 账户级止损(所有平台通用) account_max_drawdown: float = 0.25 # 账户最大回撤(25%),超过则停止交易并平仓 account_drawdown_alert: float = 0.15 # 回撤警告阈值(15%),触发告警通知 # Agent 模型配置 (可选值: zhipu, deepseek) crypto_agent_model: str = "deepseek" # ========== Bitget 实盘交易配置 ========== bitget_trading_enabled: bool = False # Bitget 实盘交易开关(默认关闭) bitget_accounts: str = "" # 多账号列表,例如: "main,sub1" def get_bitget_account_ids(self) -> List[str]: """返回已启用的 Bitget 账号列表,未配置时兼容 default 单账号。""" raw = str(self.bitget_accounts or "").strip() if raw: account_ids = [item.strip() for item in raw.split(',') if item.strip()] if account_ids: return list(dict.fromkeys(account_ids)) return ['default'] def get_bitget_account_config(self, account_id: str = "default") -> Dict[str, Any]: """获取指定 Bitget 账号配置,兼容单账号与多账号命名。""" normalized = (account_id or "default").strip() or "default" if normalized == "default": return { "account_id": "default", "api_key": self.bitget_api_key, "api_secret": self.bitget_api_secret, "passphrase": self.bitget_passphrase, "enabled": bool(self.bitget_trading_enabled and self.bitget_api_key and self.bitget_api_secret), "use_testnet": self.bitget_use_testnet, "use_unified_account": self.bitget_use_unified_account, } prefix = f"bitget_{normalized}_" api_key = getattr(self, f"{prefix}api_key", "") api_secret = getattr(self, f"{prefix}api_secret", "") passphrase = getattr(self, f"{prefix}passphrase", "") enabled_value = getattr(self, f"{prefix}enabled", True) use_testnet = getattr(self, f"{prefix}use_testnet", self.bitget_use_testnet) use_unified = getattr(self, f"{prefix}use_unified_account", self.bitget_use_unified_account) return { "account_id": normalized, "api_key": api_key, "api_secret": api_secret, "passphrase": passphrase, "enabled": bool(self.bitget_trading_enabled and enabled_value and api_key and api_secret), "use_testnet": use_testnet, "use_unified_account": use_unified, } def get_enabled_bitget_accounts(self) -> List[Dict[str, Any]]: """返回所有已启用且凭证完整的 Bitget 账号配置。""" configs: List[Dict[str, Any]] = [] for account_id in self.get_bitget_account_ids(): config = self.get_bitget_account_config(account_id) if config.get("enabled"): configs.append(config) return configs class Config: env_file = find_env_file() case_sensitive = False extra = "ignore" @lru_cache() def get_settings() -> Settings: """获取配置单例""" return Settings()