""" 加密货币K线形态策略模块 基于原有K线形态策略,适配加密货币市场 """ import pandas as pd from typing import Dict, List, Any from datetime import datetime, timedelta from loguru import logger from ..data.binance_fetcher import BinanceFetcher from ..utils.notification import NotificationManager from ..database.mysql_database_manager import MySQLDatabaseManager from .kline_pattern_strategy import KLinePatternStrategy from .base_strategy import StrategyResult class CryptoKLinePatternStrategy(KLinePatternStrategy): """加密货币K线形态策略类""" def __init__(self, data_fetcher: BinanceFetcher, notification_manager: NotificationManager, config: Dict[str, Any], db_manager: MySQLDatabaseManager = None): """ 初始化加密货币K线形态策略 Args: data_fetcher: Binance数据获取器 notification_manager: 通知管理器 config: 策略配置 db_manager: 数据库管理器 """ # 先调用父类构造函数初始化基本属性 super().__init__(data_fetcher, notification_manager, config, db_manager) # 然后替换和定制加密货币特有属性 self.data_fetcher = data_fetcher # 替换为Binance数据获取器 self.strategy_name = "加密货币K线形态策略" self.timeframes = config.get('timeframes', ['4hour', 'daily', 'weekly']) self.max_turnover_ratio = config.get('max_turnover_ratio', 100.0) # 加密货币换手率通常更高 # 加密货币特有参数 self.quote_asset = config.get('quote_asset', 'USDT') # 计价货币 self.min_volume_usdt = config.get('min_volume_usdt', 1000000) # 最小24h交易量(USDT) # 热门币种缓存机制 self._hot_symbols_cache = None self._cache_timestamp = None self._cache_duration = 300 # 缓存5分钟 # 更新策略ID为加密货币版本 self.strategy_id = self.db_manager.create_or_update_strategy( strategy_name=self.strategy_name, strategy_type="crypto_kline_pattern", description="加密货币强势上涨+多空博弈+突破确认形态识别策略", config=config ) logger.info(f"加密货币K线形态策略初始化完成 (策略ID: {self.strategy_id})") def _get_cached_hot_symbols(self, max_symbols: int) -> List[str]: """ 获取缓存的热门交易对列表 Args: max_symbols: 最大交易对数量 Returns: 交易对列表 """ import time current_time = time.time() # 检查缓存是否有效 if (self._hot_symbols_cache is not None and self._cache_timestamp is not None and current_time - self._cache_timestamp < self._cache_duration): logger.info(f"🔄 使用缓存的热门交易对数据 ({len(self._hot_symbols_cache)} 个)") return self._hot_symbols_cache[:max_symbols] # 缓存失效或不存在,重新获取 logger.info(f"🔥 获取热门交易对 (TOP{max_symbols})...") hot_symbols = self.data_fetcher.get_top_volume_symbols( quote_asset=self.quote_asset, limit=max_symbols * 2 # 多获取一些以备过滤 ) if hot_symbols: self._hot_symbols_cache = hot_symbols self._cache_timestamp = current_time logger.info(f"✅ 热门交易对获取成功,已缓存 {len(self._hot_symbols_cache)} 个") return self._hot_symbols_cache[:max_symbols] else: logger.error("❌ 热门交易对数据为空") return [] def clear_hot_symbols_cache(self): """清除热门交易对缓存,强制下次重新获取""" self._hot_symbols_cache = None self._cache_timestamp = None logger.info("🔄 热门交易对缓存已清除") def _check_current_price_validity(self, symbol: str, entry_price: float, tolerance: float = 0.05) -> bool: """ 检查当前价格是否仍然适合入场(不低于入场价的5%) Args: symbol: 交易对符号 entry_price: 建议入场价格 tolerance: 价格下跌容忍度,默认5% Returns: bool: 当前价格是否仍然有效 """ try: # 获取最新价格数据(使用24小时数据) current_data = self.data_fetcher.get_historical_klines( symbol, '1d', limit=2 # 获取最近2天数据 ) if current_data.empty: logger.warning(f"无法获取 {symbol} 的最新价格数据") return True # 无法获取价格时保守处理,保留信号 current_price = current_data.iloc[-1]['close'] min_valid_price = entry_price * (1 - tolerance) is_valid = current_price >= min_valid_price if not is_valid: price_drop_pct = (entry_price - current_price) / entry_price * 100 logger.info(f"🔻 {symbol} 价格过滤: 当前价 {current_price:.4f} < 入场价 {entry_price:.4f} (下跌{price_drop_pct:.1f}%)") return is_valid except Exception as e: logger.error(f"检查 {symbol} 当前价格失败: {e}") return True # 出错时保守处理,保留信号 def _filter_recent_signals(self, signals: List[Dict[str, Any]], days: int = 7) -> List[Dict[str, Any]]: """ 过滤最近N天内产生的信号,并检查当前价格有效性(加密货币版本) Args: signals: 信号列表 days: 最近天数,默认7天 Returns: 过滤后的信号列表 """ if not signals: return signals from datetime import datetime, date import pandas as pd current_date = datetime.now().date() recent_signals = [] price_filtered_count = 0 for signal in signals: signal_date = signal.get('confirmation_date') or signal.get('date') # 处理不同的日期格式 if isinstance(signal_date, str): try: signal_date = pd.to_datetime(signal_date).date() except: continue elif hasattr(signal_date, 'date'): signal_date = signal_date.date() elif not isinstance(signal_date, date): continue # 计算信号距今天数 days_ago = (current_date - signal_date).days # 只保留最近N天内的信号 if days_ago <= days: # 检查当前价格是否仍然有效 symbol = signal.get('stock_code', '') # 在加密货币中,stock_code存储的是交易对符号 entry_price = signal.get('yin_high', 0) if symbol and entry_price > 0: if self._check_current_price_validity(symbol, entry_price): recent_signals.append(signal) logger.debug(f"✅ 保留有效信号: {symbol} {signal_date} (距今{days_ago}天)") else: price_filtered_count += 1 logger.debug(f"🔻 价格过滤信号: {symbol} {signal_date} (价格已跌破入场价)") else: # 缺少必要信息时保守处理 recent_signals.append(signal) logger.debug(f"✅ 保留信号(缺少价格信息): {signal_date} (距今{days_ago}天)") else: logger.debug(f"🗓️ 过滤历史信号: {signal_date} (距今{days_ago}天)") # 统计过滤结果 time_filtered_count = len(signals) - len(recent_signals) - price_filtered_count if len(recent_signals) != len(signals): logger.info(f"📅 加密货币信号过滤统计: 总共{len(signals)}个 → 保留{len(recent_signals)}个") if time_filtered_count > 0: logger.info(f" 🗓️ 时间过滤: {time_filtered_count}个") if price_filtered_count > 0: logger.info(f" 🔻 价格过滤: {price_filtered_count}个") return recent_signals def analyze_symbol(self, symbol: str, timeframes: List[str] = None, session_id: int = None) -> Dict[str, StrategyResult]: """ 分析单个交易对的K线形态 Args: symbol: 交易对符号,如'BTCUSDT' timeframes: 时间周期列表,如果为None则使用策略默认周期 session_id: 扫描会话ID Returns: 时间周期到策略结果的映射 """ if timeframes is None: timeframes = self.timeframes results = {} symbol_name = self.data_fetcher.get_symbol_name(symbol) for timeframe in timeframes: start_time = datetime.now() try: # 转换时间周期格式 binance_interval = self.data_fetcher.convert_timeframe(timeframe) # 计算需要获取的K线数量(使用limit更稳定) if timeframe == 'daily': limit = 60 # 获取60根日线 elif timeframe == '4hour': limit = 180 # 获取180根4小时线(约30天) elif timeframe == 'weekly': limit = 26 # 获取26根周线(约半年) else: limit = 100 # 默认100根 logger.info(f"🔍 分析交易对: {symbol} | 周期: {timeframe}") # 获取历史数据(使用limit参数,更可靠) df = self.data_fetcher.get_historical_klines( symbol, binance_interval, limit=limit ) if df.empty: logger.warning(f"{symbol} {timeframe} 数据为空") results[timeframe] = StrategyResult( strategy_name=self.strategy_name, stock_code=symbol, timeframe=timeframe, signals=[], success=False, error="数据为空", execution_time=(datetime.now() - start_time).total_seconds() ) continue # 计算K线特征 df_with_features = self.calculate_kline_features(df) # 检测形态(返回已确认信号和形态形成) confirmed_signals, formed_patterns = self.detect_pattern(df_with_features) # 过滤3天内的信号,7天内的形态 recent_signals = self._filter_recent_signals(confirmed_signals, days=3) recent_patterns = self._filter_recent_signals(formed_patterns, days=7) # 处理确认信号格式 formatted_signals = [] for signal in recent_signals: formatted_signal = { 'date': signal['date'], 'signal_type': signal['pattern_type'], 'price': signal['breakout_price'], 'confidence': signal['final_yang_entity_ratio'], 'stock_code': symbol, # 添加交易对代码 'stock_name': symbol_name, 'status': 'confirmed', 'details': { 'yin_high': signal['yin_high'], 'breakout_amount': signal['breakout_amount'], 'breakout_pct': signal['breakout_pct'], 'ema20_price': signal['ema20_price'], 'turnover_ratio': signal['turnover_ratio'], 'breakout_position': signal.get('breakout_position', 4), 'pattern_subtype': signal.get('pattern_subtype', '') # 添加形态子类型 } } # 添加回踩确认信息 if not signal.get('confirmation_pending', True): formatted_signal['details'].update({ 'new_high_confirmed': signal.get('new_high_confirmed', True), 'new_high_price': signal.get('new_high_price'), 'new_high_date': signal.get('new_high_date'), 'confirmation_date': signal.get('confirmation_date'), 'confirmation_days': signal.get('confirmation_days'), 'pullback_distance': signal.get('pullback_distance') }) formatted_signals.append(formatted_signal) # 将信号添加到监控列表 signal['stock_code'] = symbol signal['stock_name'] = symbol_name signal['timeframe'] = timeframe self.add_triggered_signal(signal) # 保存信号到数据库(标记为加密货币) if session_id is not None: try: signal_id = self.db_manager.save_stock_signal( session_id=session_id, strategy_id=self.strategy_id, signal=signal, asset_type='crypto' # 标记为加密货币 ) logger.debug(f"加密货币信号已保存到数据库: signal_id={signal_id}") except Exception as e: logger.error(f"保存加密货币信号到数据库失败: {e}") # 处理形态形成格式 formatted_patterns = [] for pattern in recent_patterns: formatted_pattern = { 'date': pattern['date'], 'pattern_type': pattern['pattern_type'], 'price': pattern['breakout_price'], 'confidence': pattern['final_yang_entity_ratio'], 'stock_code': symbol, # 添加交易对代码 'stock_name': symbol_name, 'status': 'formed', 'details': { 'yin_high': pattern['yin_high'], 'breakout_amount': pattern['breakout_amount'], 'breakout_pct': pattern['breakout_pct'], 'ema20_price': pattern['ema20_price'], 'turnover_ratio': pattern['turnover_ratio'], 'breakout_position': pattern.get('breakout_position', 4), 'pattern_subtype': pattern.get('pattern_subtype', '') # 添加形态子类型 } } formatted_patterns.append(formatted_pattern) execution_time = (datetime.now() - start_time).total_seconds() results[timeframe] = StrategyResult( strategy_name=self.strategy_name, stock_code=symbol, timeframe=timeframe, signals=formatted_signals, patterns=formatted_patterns, success=True, execution_time=execution_time ) # 美化统计日志 if formatted_signals or formatted_patterns: logger.info(f"✅ {symbol} {timeframe}周期: 信号={len(formatted_signals)}个, 形态={len(formatted_patterns)}个") if formatted_signals: logger.info(f" 🎯 已确认信号:") for i, signal in enumerate(formatted_signals, 1): logger.info(f" {i}. {signal['date']} | 价格: {signal['price']:.4f} | {signal['signal_type']}") if formatted_patterns: logger.info(f" 📊 形态形成:") for i, pattern in enumerate(formatted_patterns, 1): logger.info(f" {i}. {pattern['date']} | 价格: {pattern['price']:.4f} | {pattern['pattern_type']}") else: logger.debug(f"📭 {symbol} {timeframe}周期: 无信号和形态") except Exception as e: logger.error(f"分析交易对 {symbol} {timeframe}周期失败: {e}") execution_time = (datetime.now() - start_time).total_seconds() results[timeframe] = StrategyResult( strategy_name=self.strategy_name, stock_code=symbol, timeframe=timeframe, signals=[], patterns=[], success=False, error=str(e), execution_time=execution_time ) return results def scan_market(self, symbol_list: List[str] = None, max_symbols: int = 100) -> Dict[str, Dict[str, List[Dict[str, Any]]]]: """ 扫描加密货币市场 Args: symbol_list: 交易对列表,如果为None则使用热门交易对 max_symbols: 最大扫描交易对数量 Returns: 所有交易对的分析结果 """ logger.info("🚀" + "="*70) logger.info("🌍 开始加密货币市场K线形态扫描") logger.info("🚀" + "="*70) # 创建扫描会话 scan_config = { 'max_symbols': max_symbols, 'data_source': 'Binance', 'quote_asset': self.quote_asset, 'timeframes': self.timeframes } session_id = self.db_manager.create_scan_session( strategy_id=self.strategy_id, scan_config=scan_config ) if symbol_list is None: # 使用缓存的热门交易对数据 symbol_list = self._get_cached_hot_symbols(max_symbols) if symbol_list: logger.info(f"📊 数据源: Binance热门交易对 | 扫描交易对: {len(symbol_list)} 个") else: logger.error("❌ 热门交易对数据为空,无法进行扫描") return {} results = {} total_signals = 0 total_patterns = 0 for i, symbol in enumerate(symbol_list): logger.info(f"⏳ 扫描进度: [{i+1:3d}/{len(symbol_list):3d}] 🔍 {symbol}") try: symbol_results = self.analyze_symbol(symbol, session_id=session_id) # 统计信号和形态数量 symbol_signal_count = sum(result.get_signal_count() for result in symbol_results.values()) symbol_pattern_count = sum(result.get_pattern_count() for result in symbol_results.values()) # 只保留有确认信号的交易对结果,不包括仅有形态的交易对 if symbol_signal_count > 0: results[symbol] = symbol_results total_signals += symbol_signal_count total_patterns += symbol_pattern_count except Exception as e: logger.error(f"扫描交易对 {symbol} 失败: {e}") continue # 更新扫描会话统计 try: self.db_manager.update_scan_session_stats( session_id=session_id, total_scanned=len(symbol_list), total_signals=total_signals ) logger.debug(f"扫描会话统计已更新: {session_id}") except Exception as e: logger.error(f"更新扫描会话统计失败: {e}") # 美化最终扫描结果 logger.info("🎉" + "="*70) logger.info(f"🌍 加密货币市场K线形态扫描完成!") logger.info(f"📊 扫描统计:") logger.info(f" 🔍 总扫描交易对: {len(symbol_list)} 个") logger.info(f" 🎯 确认信号: {total_signals} 个") logger.info(f" 📊 形态形成: {total_patterns} 个") logger.info(f" 📈 涉及交易对: {len(results)} 个") logger.info(f" 💾 扫描会话ID: {session_id}") if results: logger.info(f"📋 详细结果:") signal_count = 0 pattern_count = 0 for symbol, symbol_results in results.items(): for timeframe, result in symbol_results.items(): # 显示确认信号 for signal in result.signals: signal_count += 1 logger.info(f" 🎯 信号#{signal_count}: {symbol} | {timeframe} | {signal['date']} | {signal['price']:.4f}") # 显示形态形成 for pattern in result.patterns: pattern_count += 1 logger.info(f" 📊 形态#{pattern_count}: {symbol} | {timeframe} | {pattern['date']} | {pattern['price']:.4f}") logger.info("🎉" + "="*70) # 发送汇总通知 if results: scan_stats = { 'total_scanned': len(symbol_list), 'data_source': f'Binance-{self.quote_asset}' } try: success = self.notification_manager.send_strategy_summary(results, scan_stats) if success: logger.info("📱 策略信号汇总通知发送完成") else: logger.warning("📱 策略信号汇总通知发送失败") except Exception as e: logger.error(f"发送汇总通知失败: {e}") return results def get_strategy_description(self) -> str: """获取策略描述""" trend_desc = "" if self.strong_trend_enabled: trend_desc = f""" 【多头排列先决条件】✅ - EMA5 > EMA10 > EMA20(三重多头排列) - 在形态识别、突破确认、回踩信号三个阶段都进行检查 - 确保整个过程中始终保持强势趋势 """ else: trend_desc = "\n【多头排列先决条件】❌ 未启用\n" return f"""加密货币K线形态策略 - 强势上涨+多空博弈+突破确认 {trend_desc} 新形态设计:强势上涨 → 多空博弈 → 突破确认 【形态A:双阳+博弈】 1. 强势阶段:2根连续阳线 2. 博弈阶段:2-3根K线,平均实体≤40%(阴线/十字星/小阳线) 3. 突破确认:博弈后1-3根K线内,大阳线(实体≥55%)突破博弈阶段最高价 【形态B:高实体+博弈】 1. 强势阶段:1根高实体阳线(实体≥60%) 2. 博弈阶段:2-3根K线,平均实体≤40%(阴线/十字星/小阳线) 3. 突破确认:博弈后1-3根K线内,大阳线(实体≥55%)突破博弈阶段最高价 【严格约束条件】 - EMA5 > EMA10 > EMA20(三重多头排列)全程检查 - 价格不能跌破EMA10支撑(博弈、突破、回踩全程) - 价格必须创新高后回踩到博弈阶段最高点附近才产生正式信号 - 回踩容忍度:{self.pullback_tolerance:.0%} - 确认窗口:{self.pullback_confirmation_days}天 【加密货币市场特点】 - 计价货币: {self.quote_asset} - 最小24h交易量: ${self.min_volume_usdt:,.0f} - 支持时间周期:{', '.join(self.timeframes)} 【策略优势】 - 真实市场心理:强势→博弈→突破的完整过程 - 多重验证:EMA多头排列 + EMA10支撑 + 创新高回踩确认 - 降低假突破:严格的形态和趋势验证 - 优质入场时机:回踩确认提供更好的风险收益比 """ if __name__ == "__main__": # 测试代码 from ..data.binance_fetcher import BinanceFetcher from ..utils.notification import NotificationManager # 模拟配置 strategy_config = { 'min_entity_ratio': 0.55, 'timeframes': ['daily', '4hour'], 'quote_asset': 'USDT' } notification_config = { 'dingtalk': { 'enabled': False, 'webhook_url': '' } } # 初始化组件 data_fetcher = BinanceFetcher() notification_manager = NotificationManager(notification_config) strategy = CryptoKLinePatternStrategy(data_fetcher, notification_manager, strategy_config) print("加密货币K线形态策略初始化完成") print(strategy.get_strategy_description())