""" 配置文件 - API密钥和交易参数配置 支持从数据库读取配置(优先),回退到环境变量和默认值 支持动态重载配置 """ import os from typing import Optional from pathlib import Path # 加载 .env 文件(优先从 trading_system/.env,其次从 backend/.env,再到项目根目录/.env) try: from dotenv import load_dotenv trading_system_dir = Path(__file__).parent project_root = trading_system_dir.parent backend_dir = project_root / "backend" env_files = [ trading_system_dir / '.env', # trading_system/.env backend_dir / '.env', # backend/.env(线上常见放这里:DB/REDIS/ATS_MASTER_KEY) project_root / '.env', # 项目根目录/.env ] for env_file in env_files: if env_file.exists(): load_dotenv(env_file, override=True) print(f"[config.py] 已加载 .env 文件: {env_file}") break else: # 如果都不存在,尝试加载但不报错 load_dotenv(project_root / '.env', override=False) except ImportError: # python-dotenv 未安装时忽略 pass except Exception as e: # 加载 .env 文件失败时忽略,不影响程序运行 print(f"[config.py] 加载 .env 文件时出错(可忽略): {e}") # 尝试从数据库加载配置 USE_DB_CONFIG = False _config_manager = None def _init_config_manager(): """初始化配置管理器""" global USE_DB_CONFIG, _config_manager if _config_manager is not None: return _config_manager # 使用基础日志(因为可能还没有配置好日志系统) import sys from pathlib import Path try: # 从trading_system目录向上两级到项目根目录,然后找backend project_root = Path(__file__).parent.parent backend_path = project_root / 'backend' print(f"[配置管理器] 尝试初始化,项目根目录: {project_root}") print(f"[配置管理器] backend路径: {backend_path}") print(f"[配置管理器] backend目录存在: {backend_path.exists()}") if not backend_path.exists(): print(f"[配置管理器] ❌ backend目录不存在: {backend_path}") USE_DB_CONFIG = False return None # 检查backend目录内容 backend_files = list(backend_path.iterdir()) if backend_path.exists() else [] print(f"[配置管理器] backend目录内容: {[f.name for f in backend_files[:10]]}") # 检查config_manager.py是否存在 config_manager_file = backend_path / 'config_manager.py' print(f"[配置管理器] config_manager.py存在: {config_manager_file.exists()}") if not config_manager_file.exists(): print(f"[配置管理器] ❌ config_manager.py不存在: {config_manager_file}") USE_DB_CONFIG = False return None # 添加到sys.path backend_str = str(backend_path) if backend_str not in sys.path: sys.path.insert(0, backend_str) print(f"[配置管理器] 已添加路径到sys.path: {backend_str}") # 尝试导入 try: print("[配置管理器] 尝试导入config_manager...") from config_manager import ConfigManager # type: ignore print("[配置管理器] ✓ 导入成功") # 从环境变量获取 account_id(trading_system 进程通过 ATS_ACCOUNT_ID 指定) try: account_id = int(os.getenv("ATS_ACCOUNT_ID") or os.getenv("ACCOUNT_ID") or 1) except Exception: account_id = 1 print(f"[配置管理器] 使用 account_id={account_id} (从环境变量 ATS_ACCOUNT_ID 读取)") # 为当前账号创建 ConfigManager 实例(而不是使用全局的) config_manager = ConfigManager.for_account(account_id) print(f"[配置管理器] ✓ 已创建 ConfigManager 实例 (account_id={account_id})") # 测试数据库连接 try: print("[配置管理器] 测试数据库连接...") config_manager.reload() print(f"[配置管理器] ✓ 数据库连接成功,已加载 {len(config_manager._cache)} 个配置项") # 检查API密钥(使用正确的 account_id) api_key = config_manager.get('BINANCE_API_KEY') api_secret = config_manager.get('BINANCE_API_SECRET') print(f"[配置管理器] API密钥检查: KEY存在={bool(api_key)}, SECRET存在={bool(api_secret)}") if api_key and api_key != 'your_api_key_here': print(f"[配置管理器] API_KEY前4位: {api_key[:4]}...") _config_manager = config_manager USE_DB_CONFIG = True print("[配置管理器] ✓ 配置管理器初始化成功,将从数据库读取配置") return config_manager except Exception as db_error: print(f"[配置管理器] ⚠ 数据库连接失败: {db_error}") print(f"[配置管理器] 错误类型: {type(db_error).__name__}") import traceback print(f"[配置管理器] 错误详情:\n{traceback.format_exc()}") USE_DB_CONFIG = False return None except ImportError as e: print(f"[配置管理器] ❌ 无法导入config_manager: {e}") import traceback print(f"[配置管理器] 导入错误详情:\n{traceback.format_exc()}") USE_DB_CONFIG = False return None except Exception as e: print(f"[配置管理器] ❌ 配置管理器初始化失败: {e}") import traceback print(f"[配置管理器] 错误详情:\n{traceback.format_exc()}") USE_DB_CONFIG = False return None except Exception as e: print(f"[配置管理器] ❌ 初始化异常: {e}") import traceback print(f"[配置管理器] 异常详情:\n{traceback.format_exc()}") USE_DB_CONFIG = False return None # 初始化配置管理器(在模块加载时执行) # 注意:此时日志系统可能还没初始化,所以使用print输出 try: _init_config_manager() except Exception as e: print(f"[config.py] 配置管理器初始化异常: {e}") import traceback print(traceback.format_exc()) def _get_config_value(key, default=None): """获取配置值(支持动态重载)""" if _config_manager: try: # 不每次都reload,避免性能问题,只在需要时reload value = _config_manager.get(key) # 如果从数据库获取到值,直接返回(排除None和空字符串,除非是敏感配置) if value is not None: # 对于API密钥,即使是空字符串也返回(表示数据库中有配置但值为空) if key in ['BINANCE_API_KEY', 'BINANCE_API_SECRET']: return value if value else default # 对于其他配置,空字符串视为无效,返回default if value == '': return default return value except Exception as e: import logging logger = logging.getLogger(__name__) logger.debug(f"从配置管理器获取{key}失败: {e},尝试环境变量") # 回退到环境变量 env_value = os.getenv(key) if env_value is not None and env_value != '': return env_value # 最后返回默认值 return default def _get_trading_config(): """获取交易配置(支持动态重载,优先从Redis读取最新值)""" if _config_manager: # 从Redis重新加载配置(轻量级,只从Redis读取,不查数据库) # 注意:如果Redis不可用或没有数据,reload_from_redis()会保持现有缓存,不会触发数据库加载 _config_manager.reload_from_redis() return _config_manager.get_trading_config() # 回退到默认配置 return { # ===== 用户风险旋钮(傻瓜化)===== 'AUTO_TRADE_ENABLED': True, # 自动交易总开关 'MAX_OPEN_POSITIONS': 3, # 同时持仓数量上限 'MAX_DAILY_ENTRIES': 8, # 每日最多开仓次数 'MAX_POSITION_PERCENT': 0.08, # 提高单笔仓位到8%(原来5%),增加收益 'MAX_TOTAL_POSITION_PERCENT': 0.40, # 提高总仓位到40%(原来30%),允许更多持仓 'MIN_POSITION_PERCENT': 0.02, # 提高最小仓位到2%(原来1%),避免过小仓位 'MIN_MARGIN_USDT': 5.0, # 提高最小保证金到5美元(原来0.5U),确保收益可观 'MIN_CHANGE_PERCENT': 0.5, # 降低到0.5%以获取更多推荐(推荐系统可以更宽松) 'TOP_N_SYMBOLS': 50, # 每次扫描后处理的交易对数量(增加到50以获取更多推荐) 'MAX_SCAN_SYMBOLS': 500, # 扫描的最大交易对数量(0表示扫描所有) 'STOP_LOSS_PERCENT': 0.10, # 止损百分比(相对于保证金),默认10% 'TAKE_PROFIT_PERCENT': 0.25, # 止盈百分比(相对于保证金),默认25%(从30%放宽,配合ATR止盈放大盈亏比) 'MIN_STOP_LOSS_PRICE_PCT': 0.02, # 最小止损价格变动百分比(如0.02表示2%),防止止损过紧,默认2% 'MIN_TAKE_PROFIT_PRICE_PCT': 0.02, # 最小止盈价格变动百分比(如0.02表示2%),防止ATR过小时计算出不切实际的微小止盈距离,默认2% 'USE_ATR_STOP_LOSS': True, # 是否使用ATR动态止损(优先于固定百分比) 'ATR_STOP_LOSS_MULTIPLIER': 2.5, # ATR止损倍数(放宽至2.5,配合固定风险2%,提升胜率) 'ATR_TAKE_PROFIT_MULTIPLIER': 1.5, # ATR止盈倍数(1.5:1盈亏比,更容易达成) 'RISK_REWARD_RATIO': 1.5, # 盈亏比(配合止盈倍数) 'ATR_PERIOD': 14, # ATR计算周期(默认14) 'USE_DYNAMIC_ATR_MULTIPLIER': False, # 是否根据波动率动态调整ATR倍数 'ATR_MULTIPLIER_MIN': 1.5, # 动态ATR倍数最小值 'ATR_MULTIPLIER_MAX': 2.5, # 动态ATR倍数最大值 'SCAN_INTERVAL': 1800, 'KLINE_INTERVAL': '1h', 'PRIMARY_INTERVAL': '1h', 'CONFIRM_INTERVAL': '4h', 'ENTRY_INTERVAL': '15m', 'MIN_VOLUME_24H': 5000000, # 降低到500万以获取更多推荐(推荐系统可以更宽松) 'MIN_VOLUME_24H_STRICT': 10000000, # ⚠️ 优化:严格成交量过滤,24H Volume低于1000万美金直接剔除 'MIN_VOLATILITY': 0.02, 'MIN_SIGNAL_STRENGTH': 8, # 提高至8,只交易高质量信号(低频波段) # ===== 动态过滤优化 ===== 'BETA_FILTER_ENABLED': True, # 大盘共振过滤:BTC/ETH下跌时屏蔽多单 'BETA_FILTER_THRESHOLD': -0.03, # -3%,BTC/ETH下跌超过此阈值时屏蔽多单 'ATR_SPIKE_THRESHOLD': 2.0, # ATR异常激增阈值(当前ATR / 平均ATR) 'SIGNAL_STRENGTH_POSITION_MULTIPLIER': {8: 0.5, 9: 1.0, 10: 1.0}, # 信号强度分级:8分50%仓位,9-10分100%仓位 # ===== 仓位管理优化 ===== 'USE_FIXED_RISK_SIZING': True, # 使用固定风险百分比计算仓位(凯利公式) 'FIXED_RISK_PERCENT': 0.02, # 每笔单子承受的风险(2%) 'MAX_LEVERAGE_SMALL_CAP': 5, # 小众币最大杠杆限制 'ATR_LEVERAGE_REDUCTION_THRESHOLD': 0.05, # ATR超过5%时降低杠杆 'LEVERAGE': 10, # 基础杠杆倍数 'USE_DYNAMIC_LEVERAGE': True, # 是否启用动态杠杆(根据信号强度调整) 'MAX_LEVERAGE': 15, # 最大杠杆倍数(降低到15,更保守,配合更大的保证金) # 移动止损:默认关闭(避免过早截断利润,让利润奔跑) 'USE_TRAILING_STOP': False, 'TRAILING_STOP_ACTIVATION': 0.10, # 移动止损激活提高到10%(盈利10%后激活,给趋势更多空间) 'TRAILING_STOP_PROTECT': 0.05, # 保护利润提高到5%(保护5%利润,更合理) # 最小持仓时间锁(强制波段持仓纪律,避免分钟级平仓) 'MIN_HOLD_TIME_SEC': 1800, # 默认30分钟(1800秒),强制延长持仓时间 'POSITION_SYNC_INTERVAL': 60, # 持仓状态同步间隔(秒),缩短到1分钟,确保状态及时同步 # ===== 自动交易过滤(用于提升胜率/控频)===== # 是否仅在 marketRegime=trending 时才自动交易;否则只生成推荐 'AUTO_TRADE_ONLY_TRENDING': True, # 是否允许 4H 趋势为 neutral 时自动交易;默认不允许(震荡最易扫损) 'AUTO_TRADE_ALLOW_4H_NEUTRAL': False, # ===== 智能入场(方案C)===== # 根治方案:默认关闭。关闭后回归“纯限价单模式”(不追价/不市价兜底/未成交撤单跳过) 'SMART_ENTRY_ENABLED': False, 'SMART_ENTRY_STRONG_SIGNAL': 8, # 强信号阈值:≥8 更倾向趋势模式(允许市价兜底) 'ENTRY_SYMBOL_COOLDOWN_SEC': 120, # 同一symbol两次入场尝试的冷却时间(避免反复挂单/重入) 'ENTRY_TIMEOUT_SEC': 180, # 智能入场总预算(秒)(限价/追价逻辑内部使用) 'ENTRY_STEP_WAIT_SEC': 15, # 每步等待成交时间(秒) 'ENTRY_CHASE_MAX_STEPS': 4, # 最多追价步数(逐步减少 offset) 'ENTRY_MARKET_FALLBACK_AFTER_SEC': 45, # 趋势强时:超过该秒数仍未成交 -> 评估是否市价兜底 'ENTRY_CONFIRM_TIMEOUT_SEC': 30, # 下单后最终确认成交的等待时间(秒) 'ENTRY_MAX_DRIFT_PCT_TRENDING': 0.6, # 趋势强时允许的最大追价偏离(相对初始限价) 'ENTRY_MAX_DRIFT_PCT_RANGING': 0.3, # 震荡/弱趋势时允许的最大追价偏离 } # 币安API配置(优先从数据库,回退到环境变量和默认值) # 注意:在模块加载时,配置管理器可能还未初始化完成,所以先使用默认值 # 在main.py中会调用reload_config()重新加载 BINANCE_API_KEY: Optional[str] = _get_config_value('BINANCE_API_KEY', 'your_api_key_here') BINANCE_API_SECRET: Optional[str] = _get_config_value('BINANCE_API_SECRET', 'your_api_secret_here') USE_TESTNET: bool = _get_config_value('USE_TESTNET', False) if _get_config_value('USE_TESTNET') is not None else os.getenv('USE_TESTNET', 'False').lower() == 'true' # 交易参数配置(优先从数据库读取,支持动态重载) TRADING_CONFIG = _get_trading_config() # 确保包含所有必要的默认值 defaults = { # 用户风险旋钮(即使DB里没配置,也能用) 'AUTO_TRADE_ENABLED': True, 'MAX_OPEN_POSITIONS': 3, 'MAX_DAILY_ENTRIES': 8, 'SCAN_INTERVAL': 1800, 'KLINE_INTERVAL': '1h', 'PRIMARY_INTERVAL': '1h', 'CONFIRM_INTERVAL': '4h', 'ENTRY_INTERVAL': '15m', 'LIMIT_ORDER_OFFSET_PCT': 0.5, # 限价单偏移百分比(默认0.5%) # 智能入场默认值(即使DB里没配置,也能用) 'SMART_ENTRY_ENABLED': False, 'ENTRY_SYMBOL_COOLDOWN_SEC': 120, 'ENTRY_TIMEOUT_SEC': 180, 'ENTRY_STEP_WAIT_SEC': 15, 'ENTRY_CHASE_MAX_STEPS': 4, 'ENTRY_MARKET_FALLBACK_AFTER_SEC': 45, 'ENTRY_CONFIRM_TIMEOUT_SEC': 30, 'ENTRY_MAX_DRIFT_PCT_TRENDING': 0.6, 'ENTRY_MAX_DRIFT_PCT_RANGING': 0.3, # 自动交易过滤默认值 'AUTO_TRADE_ONLY_TRENDING': True, 'AUTO_TRADE_ALLOW_4H_NEUTRAL': False, # 最小持仓时间锁(强制波段持仓纪律,避免分钟级平仓) 'MIN_HOLD_TIME_SEC': 1800, # 默认30分钟(1800秒) } for key, value in defaults.items(): if key not in TRADING_CONFIG: TRADING_CONFIG[key] = value # 提供一个函数来重新加载配置 def reload_config(): """重新加载配置(供外部调用)""" global TRADING_CONFIG, BINANCE_API_KEY, BINANCE_API_SECRET, USE_TESTNET, _config_manager, USE_DB_CONFIG global REDIS_URL, REDIS_USE_TLS, REDIS_SSL_CERT_REQS, REDIS_SSL_CA_CERTS, REDIS_USERNAME, REDIS_PASSWORD, REDIS_SELECT # 如果配置管理器不存在,尝试初始化 if _config_manager is None: _init_config_manager() # 如果配置管理器存在,重新加载 if _config_manager: try: _config_manager.reload() USE_DB_CONFIG = True except Exception as e: import logging logger = logging.getLogger(__name__) logger.warning(f"重新加载配置失败: {e}") USE_DB_CONFIG = False # 重新获取API密钥(优先从配置管理器) new_api_key = _get_config_value('BINANCE_API_KEY', 'your_api_key_here') new_api_secret = _get_config_value('BINANCE_API_SECRET', 'your_api_secret_here') # 如果获取到有效值,更新全局变量 if new_api_key and new_api_key != 'your_api_key_here': BINANCE_API_KEY = new_api_key if new_api_secret and new_api_secret != 'your_api_secret_here': BINANCE_API_SECRET = new_api_secret USE_TESTNET = _get_config_value('USE_TESTNET', False) if _get_config_value('USE_TESTNET') is not None else os.getenv('USE_TESTNET', 'False').lower() == 'true' TRADING_CONFIG = _get_trading_config() # 重新加载 Redis 配置 REDIS_URL = _get_config_value('REDIS_URL', os.getenv('REDIS_URL', REDIS_URL)) REDIS_USE_TLS = _get_config_value('REDIS_USE_TLS', False) if _get_config_value('REDIS_USE_TLS') is not None else os.getenv('REDIS_USE_TLS', 'False').lower() == 'true' REDIS_SSL_CERT_REQS = _get_config_value('REDIS_SSL_CERT_REQS', REDIS_SSL_CERT_REQS) REDIS_SSL_CA_CERTS = _get_config_value('REDIS_SSL_CA_CERTS', REDIS_SSL_CA_CERTS) REDIS_USERNAME = _get_config_value('REDIS_USERNAME', os.getenv('REDIS_USERNAME', REDIS_USERNAME)) REDIS_PASSWORD = _get_config_value('REDIS_PASSWORD', os.getenv('REDIS_PASSWORD', REDIS_PASSWORD)) REDIS_SELECT = _get_config_value('REDIS_SELECT', os.getenv('REDIS_SELECT', REDIS_SELECT)) # 确保默认值 for key, value in defaults.items(): if key not in TRADING_CONFIG: TRADING_CONFIG[key] = value # 连接配置 CONNECTION_TIMEOUT = int(os.getenv('CONNECTION_TIMEOUT', '30')) # 连接超时时间(秒) CONNECTION_RETRIES = int(os.getenv('CONNECTION_RETRIES', '3')) # 连接重试次数 # Redis 缓存配置(优先从数据库,回退到环境变量和默认值) REDIS_URL = _get_config_value('REDIS_URL', os.getenv('REDIS_URL', 'redis://localhost:6379')) REDIS_USE_TLS = _get_config_value('REDIS_USE_TLS', False) if _get_config_value('REDIS_USE_TLS') is not None else os.getenv('REDIS_USE_TLS', 'False').lower() == 'true' REDIS_SSL_CERT_REQS = _get_config_value('REDIS_SSL_CERT_REQS', 'required') REDIS_SSL_CA_CERTS = _get_config_value('REDIS_SSL_CA_CERTS', None) REDIS_USERNAME = _get_config_value('REDIS_USERNAME', os.getenv('REDIS_USERNAME', None)) REDIS_PASSWORD = _get_config_value('REDIS_PASSWORD', os.getenv('REDIS_PASSWORD', None)) REDIS_SELECT = _get_config_value('REDIS_SELECT', os.getenv('REDIS_SELECT', 0)) # 日志配置 LOG_LEVEL = os.getenv('LOG_LEVEL', 'INFO') LOG_FILE = 'trading_bot.log'