244 lines
11 KiB
Python
244 lines
11 KiB
Python
"""
|
||
配置管理模块
|
||
从环境变量加载配置
|
||
"""
|
||
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()
|