stock-ai-agent/backend/app/config.py
2026-04-27 22:09:22 +08:00

255 lines
12 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

"""
配置管理模块
从环境变量加载配置
"""
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"
console_access_password: str = "223388" # 总控台访问密码
console_access_expire_days: int = 30 # 总控台访问会话有效期
# 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 # 旧的统一阈值,兼容保留
crypto_intraday_signal_threshold: float = 0.68 # 日内信号置信度阈值
crypto_trend_signal_threshold: float = 0.72 # 趋势信号置信度阈值
# 价格监控模式配置
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 = 8 # 日内 LLM 分析冷却时间
crypto_trend_llm_cooldown_minutes: int = 25 # 趋势 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 = 5 # 同一交易对事件触发分析冷却
# 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 # 默认执行杠杆(启动时同步到交易对)
bitget_breakeven_threshold: float = 1.0 # Bitget 保本止损触发阈值(盈利百分比)
bitget_trailing_stop_enabled: bool = True # Bitget 是否启用移动止损
bitget_trailing_stop_threshold_multiplier: float = 2.0 # Bitget 移动止损触发倍数(相对于保本阈值)
bitget_trailing_stop_ratio: float = 0.5 # Bitget 移动止损锁定利润比例
bitget_trailing_min_move_step: float = 0.4 # Bitget 每次上移止损的最小利润阶梯(百分比)
bitget_dynamic_tp_enabled: bool = True # Bitget 止损抬升时是否联动止盈
bitget_dynamic_tp_distance_ratio: float = 0.8 # Bitget 联动止盈时保留原始止盈距离的比例
# 账户级止损(所有平台通用)
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()