Add files to chanlun_1

This commit is contained in:
jackyu66git
2025-05-23 19:09:55 +08:00
parent c0e218d23d
commit 4481c47c34
20 changed files with 5574 additions and 0 deletions
+21
View File
@@ -0,0 +1,21 @@
"""
缠论核心模块:实现缠论的所有核心元素
"""
from .kline import KLine
from .fractal import Fractal
from .stroke import Stroke
from .segment import Segment
from .central_bank import CentralBank
from .trading_signal import TradingSignal
from .chan_analyzer import ChanAnalyzer
__all__ = [
'KLine',
'Fractal',
'Stroke',
'Segment',
'CentralBank',
'TradingSignal',
'ChanAnalyzer'
]
+416
View File
@@ -0,0 +1,416 @@
"""
中枢模块:识别价格在某个区间内的震荡模式
中枢定义:至少由三个连续同级别重叠的线段组成
"""
import pandas as pd
import numpy as np
from typing import List, Tuple, Optional, Dict
from dataclasses import dataclass
import logging
from .segment import SegmentElement
logger = logging.getLogger(__name__)
@dataclass
class CentralBankElement:
"""中枢元素数据类"""
segments: List[SegmentElement] # 组成中枢的线段
high_price: float # 中枢上边界
low_price: float # 中枢下边界
center_price: float # 中枢中心价格
start_time: pd.Timestamp # 中枢开始时间
end_time: pd.Timestamp # 中枢结束时间
duration: int # 中枢持续时间
strength: float # 中枢强度
level: str # 中枢级别
confirmed: bool = False # 是否已确认
class CentralBank:
"""中枢识别和处理类"""
def __init__(self, segments: List[SegmentElement]):
"""
初始化中枢处理器
Args:
segments: 线段列表
"""
self.segments = sorted(segments, key=lambda x: x.start_time)
self.central_banks = []
self.min_segments = 3 # 形成中枢的最少线段数
def find_overlapping_segments(self, segments: List[SegmentElement]) -> List[SegmentElement]:
"""
寻找重叠的线段组
Args:
segments: 线段列表
Returns:
重叠线段组
"""
if len(segments) < self.min_segments:
return []
# 找到所有线段的价格区间
overlapping = []
for i, seg1 in enumerate(segments[:-2]):
overlapping_group = [seg1]
for j in range(i + 1, len(segments)):
seg2 = segments[j]
# 检查与group中任意线段是否重叠
has_overlap = False
for existing_seg in overlapping_group:
if self._segments_overlap(existing_seg, seg2):
has_overlap = True
break
if has_overlap:
overlapping_group.append(seg2)
else:
break # 一旦不重叠就停止扩展
# 如果找到足够的重叠线段,返回这组
if len(overlapping_group) >= self.min_segments:
overlapping.extend(overlapping_group)
return overlapping
def _segments_overlap(self, seg1: SegmentElement, seg2: SegmentElement) -> bool:
"""
判断两个线段是否重叠
Args:
seg1: 线段1
seg2: 线段2
Returns:
是否重叠
"""
# 获取每个线段的价格区间
seg1_high = max(seg1.start_price, seg1.end_price)
seg1_low = min(seg1.start_price, seg1.end_price)
seg2_high = max(seg2.start_price, seg2.end_price)
seg2_low = min(seg2.start_price, seg2.end_price)
# 检查区间是否重叠
return not (seg1_high < seg2_low or seg2_high < seg1_low)
def calculate_overlap_zone(self, segments: List[SegmentElement]) -> Tuple[float, float]:
"""
计算多个线段的重叠区域
Args:
segments: 线段列表
Returns:
(重叠区域下边界, 重叠区域上边界)
"""
if not segments:
return 0, 0
# 计算所有线段的价格区间
all_highs = []
all_lows = []
for seg in segments:
all_highs.append(max(seg.start_price, seg.end_price))
all_lows.append(min(seg.start_price, seg.end_price))
# 重叠区域是所有高点的最小值和所有低点的最大值
overlap_high = min(all_highs)
overlap_low = max(all_lows)
# 确保重叠区域有效
if overlap_high > overlap_low:
return overlap_low, overlap_high
else:
return 0, 0
def create_central_bank(self, segments: List[SegmentElement]) -> Optional[CentralBankElement]:
"""
创建中枢元素
Args:
segments: 组成中枢的线段
Returns:
中枢元素,如果无效则返回None
"""
if len(segments) < self.min_segments:
return None
# 计算重叠区域
low_price, high_price = self.calculate_overlap_zone(segments)
if low_price >= high_price:
return None # 无有效重叠区域
center_price = (high_price + low_price) / 2
start_time = min(seg.start_time for seg in segments)
end_time = max(seg.end_time for seg in segments)
# 计算持续时间(简化为小时数)
duration = int((end_time - start_time).total_seconds() / 3600)
# 计算中枢强度
strength = self._calculate_central_bank_strength(segments, high_price - low_price, duration)
# 确定中枢级别
level = self._determine_central_bank_level(segments)
return CentralBankElement(
segments=segments,
high_price=high_price,
low_price=low_price,
center_price=center_price,
start_time=start_time,
end_time=end_time,
duration=duration,
strength=strength,
level=level
)
def _calculate_central_bank_strength(self, segments: List[SegmentElement],
height: float, duration: int) -> float:
"""
计算中枢强度
Args:
segments: 组成中枢的线段
height: 中枢高度
duration: 持续时间
Returns:
中枢强度
"""
# 基础强度:线段数量和平均强度
base_strength = len(segments) * np.mean([seg.strength for seg in segments])
# 高度因子:适中的高度得分更高
height_factor = 1 / (1 + height * 0.01) # 高度越大,因子越小
# 时间因子:持续时间适中得分更高
time_factor = min(duration / 24, 2.0) # 以24小时为基准,最多2倍
return base_strength * height_factor * (1 + time_factor * 0.1)
def _determine_central_bank_level(self, segments: List[SegmentElement]) -> str:
"""
确定中枢级别
Args:
segments: 组成中枢的线段
Returns:
中枢级别
"""
# 简化的级别判断:根据线段数量和强度
avg_strength = np.mean([seg.strength for seg in segments])
segment_count = len(segments)
if segment_count >= 5 and avg_strength > 100:
return "1日"
elif segment_count >= 4 and avg_strength > 50:
return "4小时"
elif segment_count >= 3 and avg_strength > 20:
return "1小时"
else:
return "30分钟"
def detect_central_banks(self) -> List[CentralBankElement]:
"""
检测所有中枢
Returns:
中枢列表
"""
if len(self.segments) < self.min_segments:
logger.warning("线段数量不足,无法形成中枢")
return []
central_banks = []
# 使用滑动窗口寻找中枢
for i in range(len(self.segments) - self.min_segments + 1):
# 尝试不同长度的窗口
for window_size in range(self.min_segments, min(8, len(self.segments) - i + 1)):
window_segments = self.segments[i:i + window_size]
# 检查这些线段是否能形成中枢
if self._can_form_central_bank(window_segments):
central_bank = self.create_central_bank(window_segments)
if central_bank:
# 检查是否与已有中枢重复
if not self._is_duplicate_central_bank(central_bank, central_banks):
central_banks.append(central_bank)
self.central_banks = central_banks
logger.info(f"检测到 {len(central_banks)} 个中枢")
return central_banks
def _can_form_central_bank(self, segments: List[SegmentElement]) -> bool:
"""
判断线段组是否能形成中枢
Args:
segments: 线段组
Returns:
是否能形成中枢
"""
if len(segments) < self.min_segments:
return False
# 检查是否有足够的重叠
overlap_count = 0
for i in range(len(segments) - 1):
for j in range(i + 1, len(segments)):
if self._segments_overlap(segments[i], segments[j]):
overlap_count += 1
# 至少需要一半的线段对重叠
required_overlaps = len(segments) // 2
return overlap_count >= required_overlaps
def _is_duplicate_central_bank(self, new_cb: CentralBankElement,
existing_cbs: List[CentralBankElement]) -> bool:
"""
检查是否为重复的中枢
Args:
new_cb: 新中枢
existing_cbs: 已有中枢列表
Returns:
是否重复
"""
for existing_cb in existing_cbs:
# 检查时间和价格区间是否大量重叠
time_overlap = (min(new_cb.end_time, existing_cb.end_time) -
max(new_cb.start_time, existing_cb.start_time)).total_seconds()
price_overlap = (min(new_cb.high_price, existing_cb.high_price) -
max(new_cb.low_price, existing_cb.low_price))
if time_overlap > 0 and price_overlap > 0:
# 计算重叠比例
new_duration = (new_cb.end_time - new_cb.start_time).total_seconds()
new_height = new_cb.high_price - new_cb.low_price
time_overlap_ratio = time_overlap / new_duration if new_duration > 0 else 0
price_overlap_ratio = price_overlap / new_height if new_height > 0 else 0
# 如果时间和价格重叠都超过70%,认为是重复
if time_overlap_ratio > 0.7 and price_overlap_ratio > 0.7:
return True
return False
def analyze_central_bank_patterns(self) -> Dict:
"""
分析中枢模式
Returns:
模式分析结果
"""
if not self.central_banks:
return {}
# 统计不同级别的中枢
level_counts = {}
for cb in self.central_banks:
level_counts[cb.level] = level_counts.get(cb.level, 0) + 1
# 计算平均指标
avg_strength = np.mean([cb.strength for cb in self.central_banks])
avg_duration = np.mean([cb.duration for cb in self.central_banks])
avg_height = np.mean([cb.high_price - cb.low_price for cb in self.central_banks])
# 寻找最强中枢
strongest_cb = max(self.central_banks, key=lambda x: x.strength) if self.central_banks else None
return {
'total_central_banks': len(self.central_banks),
'level_distribution': level_counts,
'avg_strength': avg_strength,
'avg_duration': avg_duration,
'avg_height': avg_height,
'strongest_central_bank': {
'strength': strongest_cb.strength,
'level': strongest_cb.level,
'duration': strongest_cb.duration
} if strongest_cb else None,
'confirmed_central_banks': sum(1 for cb in self.central_banks if cb.confirmed)
}
def find_central_bank_breaks(self) -> List[Dict]:
"""
寻找中枢突破
Returns:
突破信息列表
"""
breaks = []
for cb in self.central_banks:
# 检查中枢后续价格是否突破
post_segments = [seg for seg in self.segments if seg.start_time > cb.end_time]
for seg in post_segments[:3]: # 只看后续3个线段
if seg.direction == 1 and seg.end_price > cb.high_price:
# 向上突破
breaks.append({
'central_bank': cb,
'break_type': 'upward',
'break_segment': seg,
'break_strength': seg.end_price - cb.high_price
})
break
elif seg.direction == -1 and seg.end_price < cb.low_price:
# 向下突破
breaks.append({
'central_bank': cb,
'break_type': 'downward',
'break_segment': seg,
'break_strength': cb.low_price - seg.end_price
})
break
return breaks
def to_dataframe(self) -> pd.DataFrame:
"""
将中枢转换为DataFrame
Returns:
包含中枢信息的DataFrame
"""
if not self.central_banks:
return pd.DataFrame()
data = []
for i, cb in enumerate(self.central_banks):
data.append({
'central_bank_id': i,
'start_time': cb.start_time,
'end_time': cb.end_time,
'high_price': cb.high_price,
'low_price': cb.low_price,
'center_price': cb.center_price,
'height': cb.high_price - cb.low_price,
'duration': cb.duration,
'strength': cb.strength,
'level': cb.level,
'segment_count': len(cb.segments),
'confirmed': cb.confirmed
})
return pd.DataFrame(data)
+421
View File
@@ -0,0 +1,421 @@
"""
缠论综合分析器:整合所有核心模块进行完整的缠论分析
"""
import pandas as pd
import numpy as np
from typing import Dict, List, Optional, Tuple
import logging
from .kline import KLine
from .fractal import Fractal, FractalPoint
from .stroke import Stroke, StrokeElement
from .segment import Segment, SegmentElement
from .central_bank import CentralBank, CentralBankElement
from .trading_signal import TradingSignal, TradingPoint
logger = logging.getLogger(__name__)
class ChanAnalyzer:
"""缠论综合分析器"""
def __init__(self, kline_data: pd.DataFrame):
"""
初始化缠论分析器
Args:
kline_data: 原始K线数据
"""
self.original_data = kline_data.copy()
self.processed_data = None
# 各模块实例
self.kline_processor = None
self.fractal_detector = None
self.stroke_detector = None
self.segment_detector = None
self.central_bank_detector = None
self.trading_signal_detector = None
# 分析结果
self.fractals = []
self.strokes = []
self.segments = []
self.central_banks = []
self.trading_points = []
# 分析状态
self.is_analyzed = False
def run_full_analysis(self, fractal_strength: int = 1) -> Dict:
"""
运行完整的缠论分析
Args:
fractal_strength: 分型强度要求
Returns:
分析结果摘要
"""
logger.info("开始完整缠论分析...")
try:
# 1. 处理K线包含关系
self._process_klines()
# 2. 识别分型
self._detect_fractals(fractal_strength)
# 3. 生成笔
self._detect_strokes()
# 4. 生成线段
self._detect_segments()
# 5. 识别中枢
self._detect_central_banks()
# 6. 识别买卖点
self._detect_trading_signals()
self.is_analyzed = True
# 生成分析摘要
summary = self._generate_analysis_summary()
logger.info("缠论分析完成")
return summary
except Exception as e:
logger.error(f"缠论分析失败: {e}")
raise
def _process_klines(self):
"""处理K线包含关系"""
logger.info("处理K线包含关系...")
self.kline_processor = KLine(self.original_data)
self.processed_data = self.kline_processor.get_processed_data()
logger.info(f"K线处理完成:{len(self.original_data)} -> {len(self.processed_data)}")
def _detect_fractals(self, strength: int = 1):
"""识别分型"""
logger.info("识别分型...")
self.fractal_detector = Fractal(self.processed_data, min_strength=strength)
self.fractals = self.fractal_detector.detect_fractals()
logger.info(f"分型识别完成:共 {len(self.fractals)}")
def _detect_strokes(self):
"""生成笔"""
logger.info("生成笔...")
if not self.fractals:
logger.warning("没有分型,无法生成笔")
return
self.stroke_detector = Stroke(self.fractals, self.processed_data)
self.strokes = self.stroke_detector.detect_strokes()
logger.info(f"笔生成完成:共 {len(self.strokes)}")
def _detect_segments(self):
"""生成线段"""
logger.info("生成线段...")
if not self.strokes:
logger.warning("没有笔,无法生成线段")
return
self.segment_detector = Segment(self.strokes)
self.segments = self.segment_detector.detect_segments()
logger.info(f"线段生成完成:共 {len(self.segments)}")
def _detect_central_banks(self):
"""识别中枢"""
logger.info("识别中枢...")
if not self.segments:
logger.warning("没有线段,无法识别中枢")
return
self.central_bank_detector = CentralBank(self.segments)
self.central_banks = self.central_bank_detector.detect_central_banks()
logger.info(f"中枢识别完成:共 {len(self.central_banks)}")
def _detect_trading_signals(self):
"""识别买卖点"""
logger.info("识别买卖点...")
self.trading_signal_detector = TradingSignal(
self.central_banks, self.segments, self.strokes
)
# 检测中枢相关的买卖点
central_bank_signals = []
if self.central_banks:
central_bank_signals = self.trading_signal_detector.detect_all_trading_points()
# 如果中枢数量不足,增加基于分型的买卖点识别
fractal_signals = []
if len(self.central_banks) < 2 and self.fractals:
logger.info("中枢数量不足,启用分型买卖点识别")
fractal_signals = self.trading_signal_detector.detect_fractal_based_signals(
self.fractals, self.processed_data
)
# 合并所有信号
all_signals = central_bank_signals + fractal_signals
# 去重和排序
unique_signals = []
seen_keys = set()
for signal in sorted(all_signals, key=lambda x: x.timestamp):
# 创建唯一键:时间+类型+类别
key = f"{signal.timestamp.strftime('%Y%m%d%H%M')}_{signal.signal_type}_{signal.point_class}"
if key not in seen_keys:
unique_signals.append(signal)
seen_keys.add(key)
self.trading_points = unique_signals
# 统计信号
central_count = len(central_bank_signals)
fractal_count = len(fractal_signals)
total_count = len(unique_signals)
class_counts = {'first': 0, 'second': 0, 'third': 0}
for signal in unique_signals:
class_counts[signal.point_class] += 1
logger.info(f"买卖点识别完成:中枢相关 {central_count} 个,分型相关 {fractal_count} 个,"
f"一类 {class_counts['first']} 个,二类 {class_counts['second']} 个,"
f"三类 {class_counts['third']} 个,总计 {total_count}")
def _generate_analysis_summary(self) -> Dict:
"""生成分析摘要"""
summary = {
'data_info': {
'original_klines': len(self.original_data),
'processed_klines': len(self.processed_data) if self.processed_data is not None else 0,
'date_range': {
'start': self.original_data.index.min(),
'end': self.original_data.index.max()
}
},
'fractal_info': {
'total': len(self.fractals),
'top': len([f for f in self.fractals if f.fractal_type == 'top']),
'bottom': len([f for f in self.fractals if f.fractal_type == 'bottom'])
},
'stroke_info': {
'total': len(self.strokes),
'up': len([s for s in self.strokes if s.direction == 1]),
'down': len([s for s in self.strokes if s.direction == -1])
},
'segment_info': {
'total': len(self.segments),
'up': len([s for s in self.segments if s.direction == 1]),
'down': len([s for s in self.segments if s.direction == -1])
},
'central_bank_info': {
'total': len(self.central_banks),
'levels': self._get_central_bank_levels()
},
'trading_signal_info': {
'total': len(self.trading_points),
'buy_points': len([p for p in self.trading_points if p.signal_type == 'buy']),
'sell_points': len([p for p in self.trading_points if p.signal_type == 'sell']),
'by_class': self._get_signal_class_distribution()
}
}
return summary
def _get_central_bank_levels(self) -> Dict:
"""获取中枢级别分布"""
levels = {}
for cb in self.central_banks:
levels[cb.level] = levels.get(cb.level, 0) + 1
return levels
def _get_signal_class_distribution(self) -> Dict:
"""获取买卖点类别分布"""
distribution = {}
for point in self.trading_points:
key = f"{point.point_class}_class"
distribution[key] = distribution.get(key, 0) + 1
return distribution
def get_latest_signals(self, hours: int = 24) -> List[TradingPoint]:
"""
获取最近的买卖点信号
Args:
hours: 最近多少小时
Returns:
最近的信号列表
"""
if not self.is_analyzed or not self.trading_points:
return []
latest_time = self.processed_data.index[-1]
cutoff_time = latest_time - pd.Timedelta(hours=hours)
return [p for p in self.trading_points if p.timestamp >= cutoff_time]
def get_current_market_structure(self) -> Dict:
"""
获取当前市场结构
Returns:
当前市场结构信息
"""
if not self.is_analyzed:
return {}
current_price = self.processed_data['close'].iloc[-1]
current_time = self.processed_data.index[-1]
# 最近的中枢
recent_central_banks = [cb for cb in self.central_banks
if (current_time - cb.end_time).total_seconds() < 7*24*3600] # 7天内
# 最近的线段趋势
recent_segments = [seg for seg in self.segments
if (current_time - seg.end_time).total_seconds() < 3*24*3600] # 3天内
# 当前趋势方向
current_trend = self._determine_current_trend(recent_segments)
# 支撑阻力位
support_resistance = self._calculate_support_resistance(recent_central_banks)
return {
'current_price': current_price,
'current_time': current_time,
'trend': current_trend,
'recent_central_banks': len(recent_central_banks),
'support_resistance': support_resistance,
'market_phase': self._determine_market_phase()
}
def _determine_current_trend(self, recent_segments: List[SegmentElement]) -> str:
"""确定当前趋势"""
if not recent_segments:
return "unclear"
# 按时间排序取最近3个线段
recent_segments = sorted(recent_segments, key=lambda x: x.end_time)[-3:]
up_count = sum(1 for seg in recent_segments if seg.direction == 1)
down_count = sum(1 for seg in recent_segments if seg.direction == -1)
if up_count > down_count:
return "upward"
elif down_count > up_count:
return "downward"
else:
return "sideways"
def _calculate_support_resistance(self, central_banks: List[CentralBankElement]) -> Dict:
"""计算支撑阻力位"""
if not central_banks:
return {}
# 按强度排序,取最强的几个中枢
strong_cbs = sorted(central_banks, key=lambda x: x.strength, reverse=True)[:3]
supports = []
resistances = []
for cb in strong_cbs:
supports.append(cb.low_price)
resistances.append(cb.high_price)
return {
'support_levels': sorted(supports),
'resistance_levels': sorted(resistances, reverse=True)
}
def _determine_market_phase(self) -> str:
"""确定市场阶段"""
if not self.central_banks:
return "unknown"
# 最近的中枢
latest_cb = self.central_banks[-1] if self.central_banks else None
if not latest_cb:
return "trending"
current_time = self.processed_data.index[-1]
time_since_cb = (current_time - latest_cb.end_time).total_seconds() / 3600 # 小时
if time_since_cb < 24:
return "consolidation" # 盘整
else:
return "trending" # 趋势
def export_results(self) -> Dict[str, pd.DataFrame]:
"""
导出所有分析结果
Returns:
包含各种分析结果的DataFrame字典
"""
if not self.is_analyzed:
raise ValueError("尚未进行分析,请先调用 run_full_analysis()")
results = {}
# 处理后的K线数据
if self.processed_data is not None:
results['klines'] = self.processed_data
# 分型数据
if self.fractal_detector:
results['fractals'] = self.fractal_detector.to_dataframe()
# 笔数据
if self.stroke_detector:
results['strokes'] = self.stroke_detector.to_dataframe()
# 线段数据
if self.segment_detector:
results['segments'] = self.segment_detector.to_dataframe()
# 中枢数据
if self.central_bank_detector:
results['central_banks'] = self.central_bank_detector.to_dataframe()
# 买卖点数据
if self.trading_signal_detector:
results['trading_signals'] = self.trading_signal_detector.to_dataframe()
return results
def get_visualization_data(self) -> Dict:
"""
获取可视化所需的数据
Returns:
可视化数据字典
"""
if not self.is_analyzed:
return {}
return {
'klines': self.processed_data,
'fractals': self.fractals,
'strokes': self.strokes,
'segments': self.segments,
'central_banks': self.central_banks,
'trading_points': self.trading_points
}
+683
View File
@@ -0,0 +1,683 @@
"""
分型识别模块:识别顶分型和底分型
分型定义:至少需要3根K线,中间K线的高点(或低点)比两侧都高(或低)
增强版:包含多维度强弱程度评估
"""
import pandas as pd
import numpy as np
from typing import List, Tuple, Optional, Dict
from dataclasses import dataclass
import logging
logger = logging.getLogger(__name__)
@dataclass
class FractalPoint:
"""分型点数据类"""
index: int # 在原始数据中的位置
timestamp: pd.Timestamp # 时间戳
price: float # 分型价格(高点或低点)
fractal_type: str # 'top' 或 'bottom'
strength: int # 基础分型强度(左右确认的K线数量)
enhanced_strength: float # 增强强度评分(0-100
price_dominance: float # 价格优势度(0-100
volume_strength: float # 成交量强度(0-100
trend_position: float # 趋势位置强度(0-100
confirmed: bool = False # 是否已确认
class Fractal:
"""分型识别类(增强版)"""
def __init__(self, kline_data: pd.DataFrame, min_strength: int = 1):
"""
初始化分型识别器
Args:
kline_data: 处理包含关系后的K线数据
min_strength: 最小分型强度(左右各需多少根K线确认)
"""
self.data = kline_data.copy()
self.min_strength = max(1, min_strength) # 至少为1
self.top_fractals = []
self.bottom_fractals = []
self.all_fractals = []
# 计算一些辅助指标
self._calculate_auxiliary_indicators()
def _calculate_auxiliary_indicators(self):
"""计算辅助技术指标"""
# 成交量移动平均
self.data['volume_ma'] = self.data['volume'].rolling(window=20, min_periods=1).mean()
# 价格振幅
self.data['range'] = self.data['high'] - self.data['low']
self.data['range_ma'] = self.data['range'].rolling(window=20, min_periods=1).mean()
# 相对位置(高点在整个区间的位置)
window = 20
self.data['highest'] = self.data['high'].rolling(window=window, min_periods=1).max()
self.data['lowest'] = self.data['low'].rolling(window=window, min_periods=1).min()
# 趋势强度(简单移动平均斜率)
self.data['close_ma'] = self.data['close'].rolling(window=10, min_periods=1).mean()
self.data['trend_slope'] = self.data['close_ma'].diff(5) # 5期斜率
def _calculate_price_dominance(self, idx: int, fractal_type: str, strength: int) -> float:
"""
计算价格优势度:分型价格相对于周围价格的优势程度
Args:
idx: 分型位置
fractal_type: 分型类型
strength: 基础强度
Returns:
价格优势度(0-100)
"""
try:
# 扩展检查范围
check_range = max(strength * 2, 10)
start_idx = max(0, idx - check_range)
end_idx = min(len(self.data), idx + check_range + 1)
if fractal_type == 'top':
center_price = self.data.iloc[idx]['high']
range_data = self.data.iloc[start_idx:end_idx]
max_around = range_data['high'].max()
second_max = range_data['high'].nlargest(2).iloc[1] if len(range_data) > 1 else center_price
# 计算相对优势
if max_around == center_price:
price_gap = center_price - second_max
avg_range = self.data.iloc[idx]['range_ma']
dominance = min((price_gap / avg_range) * 50, 100) if avg_range > 0 else 50
else:
dominance = 0
else: # bottom
center_price = self.data.iloc[idx]['low']
range_data = self.data.iloc[start_idx:end_idx]
min_around = range_data['low'].min()
second_min = range_data['low'].nsmallest(2).iloc[1] if len(range_data) > 1 else center_price
# 计算相对优势
if min_around == center_price:
price_gap = second_min - center_price
avg_range = self.data.iloc[idx]['range_ma']
dominance = min((price_gap / avg_range) * 50, 100) if avg_range > 0 else 50
else:
dominance = 0
return max(0, dominance)
except Exception as e:
logger.warning(f"计算价格优势度失败: {e}")
return 30 # 默认值
def _calculate_volume_strength(self, idx: int, fractal_type: str, strength: int) -> float:
"""
计算成交量强度:分型形成时的成交量特征
Args:
idx: 分型位置
fractal_type: 分型类型
strength: 基础强度
Returns:
成交量强度(0-100)
"""
try:
center_volume = self.data.iloc[idx]['volume']
volume_ma = self.data.iloc[idx]['volume_ma']
# 基础成交量比率
volume_ratio = center_volume / volume_ma if volume_ma > 0 else 1
base_score = min(volume_ratio * 30, 60) # 最高60分
# 检查分型形成过程中的成交量模式
pattern_score = 0
if strength >= 2:
# 检查左右成交量是否递减(表示力度衰竭)
left_volumes = [self.data.iloc[idx - i]['volume'] for i in range(1, strength + 1)]
right_volumes = [self.data.iloc[idx + i]['volume'] for i in range(1, strength + 1)]
# 中心成交量应该相对突出
center_prominence = sum([1 for v in left_volumes + right_volumes if center_volume > v])
total_compared = len(left_volumes + right_volumes)
pattern_score = (center_prominence / total_compared) * 40 if total_compared > 0 else 20
total_score = base_score + pattern_score
return max(0, min(total_score, 100))
except Exception as e:
logger.warning(f"计算成交量强度失败: {e}")
return 40 # 默认值
def _calculate_trend_position_strength(self, idx: int, fractal_type: str) -> float:
"""
计算趋势位置强度:分型在整体趋势中的位置优势
Args:
idx: 分型位置
fractal_type: 分型类型
Returns:
趋势位置强度(0-100)
"""
try:
current_price = self.data.iloc[idx]['high' if fractal_type == 'top' else 'low']
highest = self.data.iloc[idx]['highest']
lowest = self.data.iloc[idx]['lowest']
trend_slope = self.data.iloc[idx]['trend_slope']
# 计算在价格区间中的相对位置
price_range = highest - lowest
if price_range > 0:
if fractal_type == 'top':
# 顶分型:越接近高点越强
position_ratio = (current_price - lowest) / price_range
else:
# 底分型:越接近低点越强
position_ratio = (highest - current_price) / price_range
else:
position_ratio = 0.5
position_score = position_ratio * 60 # 位置得分最高60分
# 趋势方向得分
trend_score = 0
if trend_slope is not None and not np.isnan(trend_slope):
if fractal_type == 'top' and trend_slope < 0:
# 顶分型形成在下降趋势中更有效
trend_score = min(abs(trend_slope) * 1000, 40)
elif fractal_type == 'bottom' and trend_slope > 0:
# 底分型形成在上升趋势中更有效
trend_score = min(abs(trend_slope) * 1000, 40)
else:
trend_score = 20 # 趋势方向不匹配给予中等分数
total_score = position_score + trend_score
return max(0, min(total_score, 100))
except Exception as e:
logger.warning(f"计算趋势位置强度失败: {e}")
return 50 # 默认值
def _calculate_enhanced_strength(self, idx: int, fractal_type: str, basic_strength: int) -> Dict[str, float]:
"""
计算增强强度评分
Args:
idx: 分型位置
fractal_type: 分型类型
basic_strength: 基础强度
Returns:
包含各维度强度的字典
"""
# 计算各维度强度
price_dominance = self._calculate_price_dominance(idx, fractal_type, basic_strength)
volume_strength = self._calculate_volume_strength(idx, fractal_type, basic_strength)
trend_position = self._calculate_trend_position_strength(idx, fractal_type)
# 基础强度转换为评分(强度越高,分数越高)
basic_score = min(basic_strength * 20, 60) # 基础强度最高60分
# 计算综合强度(加权平均)
enhanced_strength = (
basic_score * 0.3 + # 基础强度 30%
price_dominance * 0.4 + # 价格优势 40%
volume_strength * 0.2 + # 成交量强度 20%
trend_position * 0.1 # 趋势位置 10%
)
return {
'enhanced_strength': enhanced_strength,
'price_dominance': price_dominance,
'volume_strength': volume_strength,
'trend_position': trend_position
}
def is_top_fractal(self, idx: int, strength: int = None) -> Tuple[bool, int]:
"""
判断指定位置是否为顶分型
Args:
idx: 检查的位置索引
strength: 检查强度,如果为None则使用类默认值
Returns:
(是否为顶分型, 实际强度)
"""
if strength is None:
strength = self.min_strength
data_len = len(self.data)
# 检查边界
if idx < strength or idx >= data_len - strength:
return False, 0
center_high = self.data.iloc[idx]['high']
# 检查左侧K线
left_valid = True
for i in range(1, strength + 1):
if self.data.iloc[idx - i]['high'] >= center_high:
left_valid = False
break
# 检查右侧K线
right_valid = True
for i in range(1, strength + 1):
if self.data.iloc[idx + i]['high'] >= center_high:
right_valid = False
break
is_fractal = left_valid and right_valid
actual_strength = strength if is_fractal else 0
return is_fractal, actual_strength
def is_bottom_fractal(self, idx: int, strength: int = None) -> Tuple[bool, int]:
"""
判断指定位置是否为底分型
Args:
idx: 检查的位置索引
strength: 检查强度,如果为None则使用类默认值
Returns:
(是否为底分型, 实际强度)
"""
if strength is None:
strength = self.min_strength
data_len = len(self.data)
# 检查边界
if idx < strength or idx >= data_len - strength:
return False, 0
center_low = self.data.iloc[idx]['low']
# 检查左侧K线
left_valid = True
for i in range(1, strength + 1):
if self.data.iloc[idx - i]['low'] <= center_low:
left_valid = False
break
# 检查右侧K线
right_valid = True
for i in range(1, strength + 1):
if self.data.iloc[idx + i]['low'] <= center_low:
right_valid = False
break
is_fractal = left_valid and right_valid
actual_strength = strength if is_fractal else 0
return is_fractal, actual_strength
def find_max_strength_fractal(self, idx: int, fractal_type: str, max_strength: int = 5) -> Tuple[bool, int]:
"""
寻找指定位置的最大强度分型
Args:
idx: 检查位置
fractal_type: 'top''bottom'
max_strength: 最大检查强度
Returns:
(是否为分型, 最大强度)
"""
max_valid_strength = 0
for strength in range(self.min_strength, max_strength + 1):
if fractal_type == 'top':
is_valid, _ = self.is_top_fractal(idx, strength)
else:
is_valid, _ = self.is_bottom_fractal(idx, strength)
if is_valid:
max_valid_strength = strength
else:
break # 一旦失败就停止,因为更高强度也不会成功
return max_valid_strength > 0, max_valid_strength
def detect_fractals(self, use_max_strength: bool = True) -> List[FractalPoint]:
"""
检测所有分型
Args:
use_max_strength: 是否使用最大强度检测
Returns:
所有分型点列表
"""
fractals = []
data_len = len(self.data)
logger.info(f"开始检测分型,数据长度: {data_len}")
# 遍历所有可能的分型位置
for idx in range(self.min_strength, data_len - self.min_strength):
timestamp = self.data.index[idx]
# 检测顶分型
if use_max_strength:
is_top, top_strength = self.find_max_strength_fractal(idx, 'top')
else:
is_top, top_strength = self.is_top_fractal(idx)
if is_top:
# 计算增强强度
strength_metrics = self._calculate_enhanced_strength(idx, 'top', top_strength)
fractal = FractalPoint(
index=idx,
timestamp=timestamp,
price=self.data.iloc[idx]['high'],
fractal_type='top',
strength=top_strength,
enhanced_strength=strength_metrics['enhanced_strength'],
price_dominance=strength_metrics['price_dominance'],
volume_strength=strength_metrics['volume_strength'],
trend_position=strength_metrics['trend_position'],
confirmed=True # 简化处理,认为都已确认
)
fractals.append(fractal)
self.top_fractals.append(fractal)
# 检测底分型
if use_max_strength:
is_bottom, bottom_strength = self.find_max_strength_fractal(idx, 'bottom')
else:
is_bottom, bottom_strength = self.is_bottom_fractal(idx)
if is_bottom:
# 计算增强强度
strength_metrics = self._calculate_enhanced_strength(idx, 'bottom', bottom_strength)
fractal = FractalPoint(
index=idx,
timestamp=timestamp,
price=self.data.iloc[idx]['low'],
fractal_type='bottom',
strength=bottom_strength,
enhanced_strength=strength_metrics['enhanced_strength'],
price_dominance=strength_metrics['price_dominance'],
volume_strength=strength_metrics['volume_strength'],
trend_position=strength_metrics['trend_position'],
confirmed=True
)
fractals.append(fractal)
self.bottom_fractals.append(fractal)
# 按时间排序
fractals.sort(key=lambda x: x.index)
self.all_fractals = fractals
logger.info(f"检测完成:顶分型 {len(self.top_fractals)} 个,底分型 {len(self.bottom_fractals)}")
return fractals
def filter_fractals_by_strength(self, min_strength: int) -> List[FractalPoint]:
"""
按强度过滤分型
Args:
min_strength: 最小强度要求
Returns:
过滤后的分型列表
"""
return [f for f in self.all_fractals if f.strength >= min_strength]
def get_fractal_sequence(self) -> List[FractalPoint]:
"""
获取交替的分型序列(顶-底-顶-底...)
Returns:
交替分型序列
"""
if not self.all_fractals:
return []
sequence = []
last_type = None
for fractal in self.all_fractals:
if fractal.fractal_type != last_type:
sequence.append(fractal)
last_type = fractal.fractal_type
return sequence
def validate_fractal_sequence(self, sequence: List[FractalPoint]) -> bool:
"""
验证分型序列的有效性
Args:
sequence: 分型序列
Returns:
是否有效
"""
if len(sequence) < 2:
return True
for i in range(1, len(sequence)):
prev_fractal = sequence[i-1]
curr_fractal = sequence[i]
# 检查类型是否交替
if prev_fractal.fractal_type == curr_fractal.fractal_type:
return False
# 检查价格关系是否合理
if prev_fractal.fractal_type == 'top':
# 顶分型后应该是底分型,且价格应该更低
if curr_fractal.price >= prev_fractal.price:
return False
else:
# 底分型后应该是顶分型,且价格应该更高
if curr_fractal.price <= prev_fractal.price:
return False
return True
def get_fractal_statistics(self) -> Dict:
"""
获取分型统计信息(增强版)
Returns:
统计信息字典
"""
if not self.all_fractals:
return {}
top_count = len(self.top_fractals)
bottom_count = len(self.bottom_fractals)
# 基础统计
top_strengths = [f.strength for f in self.top_fractals]
bottom_strengths = [f.strength for f in self.bottom_fractals]
# 增强强度统计
enhanced_strengths = [f.enhanced_strength for f in self.all_fractals]
price_dominances = [f.price_dominance for f in self.all_fractals]
volume_strengths = [f.volume_strength for f in self.all_fractals]
trend_positions = [f.trend_position for f in self.all_fractals]
# 分级统计(按增强强度)
strong_fractals = [f for f in self.all_fractals if f.enhanced_strength >= 70]
medium_fractals = [f for f in self.all_fractals if 40 <= f.enhanced_strength < 70]
weak_fractals = [f for f in self.all_fractals if f.enhanced_strength < 40]
stats = {
'basic_info': {
'total_fractals': len(self.all_fractals),
'top_fractals': top_count,
'bottom_fractals': bottom_count,
'avg_basic_strength': np.mean([f.strength for f in self.all_fractals]),
'max_basic_strength': max([f.strength for f in self.all_fractals]),
},
'enhanced_strength': {
'avg_enhanced_strength': np.mean(enhanced_strengths),
'max_enhanced_strength': max(enhanced_strengths),
'min_enhanced_strength': min(enhanced_strengths),
'strong_count': len(strong_fractals), # 强势分型数量
'medium_count': len(medium_fractals), # 中等分型数量
'weak_count': len(weak_fractals), # 弱势分型数量
},
'dimension_analysis': {
'avg_price_dominance': np.mean(price_dominances),
'avg_volume_strength': np.mean(volume_strengths),
'avg_trend_position': np.mean(trend_positions),
},
'top_fractals_detail': {
'count': top_count,
'avg_basic_strength': np.mean(top_strengths) if top_strengths else 0,
'avg_enhanced_strength': np.mean([f.enhanced_strength for f in self.top_fractals]) if self.top_fractals else 0,
'strong_tops': len([f for f in self.top_fractals if f.enhanced_strength >= 70]),
},
'bottom_fractals_detail': {
'count': bottom_count,
'avg_basic_strength': np.mean(bottom_strengths) if bottom_strengths else 0,
'avg_enhanced_strength': np.mean([f.enhanced_strength for f in self.bottom_fractals]) if self.bottom_fractals else 0,
'strong_bottoms': len([f for f in self.bottom_fractals if f.enhanced_strength >= 70]),
}
}
return stats
def to_dataframe(self) -> pd.DataFrame:
"""
将分型转换为DataFrame
Returns:
包含分型信息的DataFrame
"""
if not self.all_fractals:
return pd.DataFrame()
data = []
for fractal in self.all_fractals:
data.append({
'timestamp': fractal.timestamp,
'index': fractal.index,
'price': fractal.price,
'type': fractal.fractal_type,
'strength': fractal.strength,
'enhanced_strength': fractal.enhanced_strength,
'price_dominance': fractal.price_dominance,
'volume_strength': fractal.volume_strength,
'trend_position': fractal.trend_position,
'confirmed': fractal.confirmed
})
df = pd.DataFrame(data)
df.set_index('timestamp', inplace=True)
return df
def update_fractal_confirmation(self, current_idx: int):
"""
更新分型确认状态
Args:
current_idx: 当前处理到的K线位置
"""
for fractal in self.all_fractals:
if not fractal.confirmed:
# 检查是否已经过了足够的确认期
required_confirmation = fractal.strength
time_passed = current_idx - fractal.index
if time_passed >= required_confirmation:
fractal.confirmed = True
def get_confirmed_fractals(self, current_idx: int) -> List[FractalPoint]:
"""
获取已确认的分型列表
Args:
current_idx: 当前处理到的K线位置
Returns:
已确认的分型列表
"""
confirmed_fractals = []
for fractal in self.all_fractals:
required_confirmation = fractal.strength
time_passed = current_idx - fractal.index
if time_passed >= required_confirmation:
confirmed_fractals.append(fractal)
return confirmed_fractals
def detect_real_time_fractals(self, current_idx: int, lookback_periods: int = 50) -> List[FractalPoint]:
"""
实时分型检测(避免使用未来数据)
Args:
current_idx: 当前K线位置
lookback_periods: 回看周期数
Returns:
实时可用的分型列表
"""
real_time_fractals = []
data_len = len(self.data)
# 只检测到当前位置之前的分型
end_idx = min(current_idx, data_len - self.min_strength)
start_idx = max(self.min_strength, end_idx - lookback_periods)
for idx in range(start_idx, end_idx):
timestamp = self.data.index[idx]
# 检测顶分型(但只能检测已确认的)
is_top, top_strength = self.find_max_strength_fractal(idx, 'top')
if is_top and current_idx - idx >= top_strength: # 已确认
strength_metrics = self._calculate_enhanced_strength(idx, 'top', top_strength)
fractal = FractalPoint(
index=idx,
timestamp=timestamp,
price=self.data.iloc[idx]['high'],
fractal_type='top',
strength=top_strength,
enhanced_strength=strength_metrics['enhanced_strength'],
price_dominance=strength_metrics['price_dominance'],
volume_strength=strength_metrics['volume_strength'],
trend_position=strength_metrics['trend_position'],
confirmed=True
)
real_time_fractals.append(fractal)
# 检测底分型(但只能检测已确认的)
is_bottom, bottom_strength = self.find_max_strength_fractal(idx, 'bottom')
if is_bottom and current_idx - idx >= bottom_strength: # 已确认
strength_metrics = self._calculate_enhanced_strength(idx, 'bottom', bottom_strength)
fractal = FractalPoint(
index=idx,
timestamp=timestamp,
price=self.data.iloc[idx]['low'],
fractal_type='bottom',
strength=bottom_strength,
enhanced_strength=strength_metrics['enhanced_strength'],
price_dominance=strength_metrics['price_dominance'],
volume_strength=strength_metrics['volume_strength'],
trend_position=strength_metrics['trend_position'],
confirmed=True
)
real_time_fractals.append(fractal)
return real_time_fractals
+238
View File
@@ -0,0 +1,238 @@
"""
K线包含关系处理模块
"""
import pandas as pd
import numpy as np
from typing import List, Tuple, Optional
import logging
logger = logging.getLogger(__name__)
class KLineElement:
"""单个K线元素类"""
def __init__(self, high: float, low: float, open_price: float,
close: float, volume: float, timestamp: pd.Timestamp):
"""
初始化K线元素
Args:
high: 最高价
low: 最低价
open_price: 开盘价
close: 收盘价
volume: 成交量
timestamp: 时间戳
"""
self.high = high
self.low = low
self.open = open_price
self.close = close
self.volume = volume
self.timestamp = timestamp
self.direction = 1 if close >= open_price else -1
def __repr__(self):
return f"KLineElement(H:{self.high}, L:{self.low}, O:{self.open}, C:{self.close})"
class KLine:
"""K线包含关系处理类"""
def __init__(self, data: pd.DataFrame):
"""
初始化K线处理器
Args:
data: 包含OHLCV数据的DataFrame
"""
self.original_data = data.copy()
self.processed_data = None
self.kline_elements = []
self._process_data()
def _process_data(self):
"""处理原始数据为K线元素"""
self.kline_elements = []
for idx, row in self.original_data.iterrows():
element = KLineElement(
high=row['high'],
low=row['low'],
open_price=row['open'],
close=row['close'],
volume=row['volume'],
timestamp=idx
)
self.kline_elements.append(element)
def has_containment(self, k1: KLineElement, k2: KLineElement) -> bool:
"""
判断两根K线是否存在包含关系
Args:
k1: 第一根K线
k2: 第二根K线
Returns:
是否存在包含关系
"""
# K1包含K2:K1的高低点完全包含K2
k1_contains_k2 = (k1.high >= k2.high and k1.low <= k2.low)
# K2包含K1:K2的高低点完全包含K1
k2_contains_k1 = (k2.high >= k1.high and k2.low <= k1.low)
return k1_contains_k2 or k2_contains_k1
def merge_contained_klines(self, k1: KLineElement, k2: KLineElement,
direction: int) -> KLineElement:
"""
合并包含关系的K线
Args:
k1: 第一根K线
k2: 第二根K线
direction: 当前趋势方向(1为上升,-1为下降)
Returns:
合并后的K线
"""
if direction > 0: # 上升趋势中
# 取两根K线的最高点的最大值,最低点的最大值
merged_high = max(k1.high, k2.high)
merged_low = max(k1.low, k2.low)
else: # 下降趋势中
# 取两根K线的最高点的最小值,最低点的最小值
merged_high = min(k1.high, k2.high)
merged_low = min(k1.low, k2.low)
# 开盘价和收盘价使用第一根K线的值
merged_open = k1.open
merged_close = k2.close
merged_volume = k1.volume + k2.volume
merged_timestamp = k2.timestamp # 使用最后一根K线的时间
return KLineElement(
high=merged_high,
low=merged_low,
open_price=merged_open,
close=merged_close,
volume=merged_volume,
timestamp=merged_timestamp
)
def handle_containment(self) -> List[KLineElement]:
"""
处理所有K线的包含关系
Returns:
处理包含关系后的K线列表
"""
if len(self.kline_elements) < 2:
return self.kline_elements.copy()
processed_klines = [self.kline_elements[0]] # 第一根K线
# 初始方向:根据前两根K线确定
if len(self.kline_elements) >= 2:
k1, k2 = self.kline_elements[0], self.kline_elements[1]
if k2.high > k1.high:
current_direction = 1 # 上升
elif k2.high < k1.high:
current_direction = -1 # 下降
else:
current_direction = 1 if k2.low >= k1.low else -1
else:
current_direction = 1
i = 1
while i < len(self.kline_elements):
current_k = self.kline_elements[i]
last_processed = processed_klines[-1]
if self.has_containment(last_processed, current_k):
# 存在包含关系,进行合并
merged = self.merge_contained_klines(
last_processed, current_k, current_direction
)
processed_klines[-1] = merged # 替换最后一个
else:
# 不存在包含关系,直接添加
processed_klines.append(current_k)
# 更新方向
if current_k.high > last_processed.high:
current_direction = 1
elif current_k.high < last_processed.high:
current_direction = -1
# 如果high相等,保持原方向
i += 1
logger.info(f"包含关系处理完成:{len(self.kline_elements)} -> {len(processed_klines)}")
return processed_klines
def to_dataframe(self, processed_klines: Optional[List[KLineElement]] = None) -> pd.DataFrame:
"""
将处理后的K线转换为DataFrame
Args:
processed_klines: 处理后的K线列表,如果为None则使用默认处理结果
Returns:
包含处理后K线的DataFrame
"""
if processed_klines is None:
processed_klines = self.handle_containment()
data = []
for kline in processed_klines:
data.append({
'timestamp': kline.timestamp,
'open': kline.open,
'high': kline.high,
'low': kline.low,
'close': kline.close,
'volume': kline.volume,
'direction': kline.direction
})
df = pd.DataFrame(data)
if not df.empty:
df.set_index('timestamp', inplace=True)
return df
def get_processed_data(self) -> pd.DataFrame:
"""
获取处理包含关系后的数据
Returns:
处理后的DataFrame
"""
if self.processed_data is None:
processed_klines = self.handle_containment()
self.processed_data = self.to_dataframe(processed_klines)
return self.processed_data
def visualize_containment(self) -> dict:
"""
生成包含关系可视化信息
Returns:
包含可视化信息的字典
"""
original_count = len(self.kline_elements)
processed_klines = self.handle_containment()
processed_count = len(processed_klines)
return {
'original_count': original_count,
'processed_count': processed_count,
'merged_count': original_count - processed_count,
'merge_ratio': (original_count - processed_count) / original_count if original_count > 0 else 0
}
+356
View File
@@ -0,0 +1,356 @@
"""
线段模块:线段由笔组成,有特殊的生成和确认规则
线段定义:至少包含3笔,且满足特定的破坏条件
"""
import pandas as pd
import numpy as np
from typing import List, Tuple, Optional, Dict
from dataclasses import dataclass
import logging
from .stroke import StrokeElement
logger = logging.getLogger(__name__)
@dataclass
class SegmentElement:
"""线段元素数据类"""
strokes: List[StrokeElement] # 组成线段的笔列表
direction: int # 线段方向:1为上升,-1为下降
start_price: float # 起始价格
end_price: float # 结束价格
start_time: pd.Timestamp # 开始时间
end_time: pd.Timestamp # 结束时间
length: float # 线段长度
strength: float # 线段强度
confirmed: bool = False # 是否已确认
class Segment:
"""线段识别和处理类"""
def __init__(self, strokes: List[StrokeElement]):
"""
初始化线段处理器
Args:
strokes: 笔列表
"""
self.strokes = sorted(strokes, key=lambda x: x.start_fractal.index)
self.segments = []
self.up_segments = [] # 上升线段
self.down_segments = [] # 下降线段
def can_form_segment(self, stroke_group: List[StrokeElement]) -> bool:
"""
判断笔组是否可以形成线段
Args:
stroke_group: 笔组
Returns:
是否可以形成线段
"""
if len(stroke_group) < 3:
return False
# 检查是否有主导方向
up_count = sum(1 for s in stroke_group if s.direction == 1)
down_count = sum(1 for s in stroke_group if s.direction == -1)
# 主导方向的笔至少比反向笔多1个
return abs(up_count - down_count) >= 1
def determine_segment_direction(self, stroke_group: List[StrokeElement]) -> int:
"""
确定线段方向
Args:
stroke_group: 笔组
Returns:
线段方向
"""
if not stroke_group:
return 0
# 比较起点和终点价格
start_price = stroke_group[0].start_fractal.price
end_price = stroke_group[-1].end_fractal.price
if end_price > start_price:
return 1 # 上升线段
elif end_price < start_price:
return -1 # 下降线段
else:
# 价格相等时,根据笔的数量决定
up_count = sum(1 for s in stroke_group if s.direction == 1)
down_count = sum(1 for s in stroke_group if s.direction == -1)
return 1 if up_count >= down_count else -1
def create_segment(self, stroke_group: List[StrokeElement]) -> SegmentElement:
"""
创建线段元素
Args:
stroke_group: 组成线段的笔组
Returns:
线段元素
"""
if not stroke_group:
raise ValueError("笔组不能为空")
direction = self.determine_segment_direction(stroke_group)
start_price = stroke_group[0].start_fractal.price
end_price = stroke_group[-1].end_fractal.price
start_time = stroke_group[0].start_fractal.timestamp
end_time = stroke_group[-1].end_fractal.timestamp
length = abs(end_price - start_price)
strength = self._calculate_segment_strength(stroke_group, length)
return SegmentElement(
strokes=stroke_group,
direction=direction,
start_price=start_price,
end_price=end_price,
start_time=start_time,
end_time=end_time,
length=length,
strength=strength
)
def _calculate_segment_strength(self, stroke_group: List[StrokeElement],
length: float) -> float:
"""
计算线段强度
Args:
stroke_group: 笔组
length: 线段长度
Returns:
线段强度
"""
# 基础强度:价格变化幅度
base_strength = length
# 笔的数量因子
stroke_count_factor = len(stroke_group) / 3 # 3笔为基准
# 笔的平均强度
avg_stroke_strength = np.mean([s.strength for s in stroke_group])
# 方向一致性:主导方向笔的比例
main_direction = self.determine_segment_direction(stroke_group)
main_direction_count = sum(1 for s in stroke_group if s.direction == main_direction)
direction_consistency = main_direction_count / len(stroke_group)
# 综合强度
strength = (base_strength *
(1 + stroke_count_factor * 0.1) *
(1 + avg_stroke_strength * 0.01) *
direction_consistency)
return strength
def detect_segment_break(self, segment: SegmentElement,
new_stroke: StrokeElement) -> bool:
"""
检测线段是否被破坏
Args:
segment: 当前线段
new_stroke: 新的笔
Returns:
是否被破坏
"""
if segment.direction == 1: # 上升线段
# 如果新笔是下降笔且跌破线段起点
if (new_stroke.direction == -1 and
new_stroke.end_fractal.price < segment.start_price):
return True
else: # 下降线段
# 如果新笔是上升笔且涨破线段起点
if (new_stroke.direction == 1 and
new_stroke.end_fractal.price > segment.start_price):
return True
return False
def detect_segments(self) -> List[SegmentElement]:
"""
检测所有线段
Returns:
线段列表
"""
if len(self.strokes) < 3:
logger.warning("笔数量不足,无法生成线段")
return []
segments = []
current_stroke_group = []
for stroke in self.strokes:
current_stroke_group.append(stroke)
# 当有足够笔时,尝试形成线段
if len(current_stroke_group) >= 3:
if self.can_form_segment(current_stroke_group):
# 检查是否有现有线段被破坏
if segments:
last_segment = segments[-1]
if self.detect_segment_break(last_segment, stroke):
# 线段被破坏,确认上一个线段
last_segment.confirmed = True
# 开始新的线段
current_stroke_group = [stroke]
continue
# 尝试扩展或创建新线段
if len(current_stroke_group) >= 5: # 限制线段长度
segment = self.create_segment(current_stroke_group[:-2])
segments.append(segment)
current_stroke_group = current_stroke_group[-2:] # 保留最后两笔
# 处理最后一组笔
if len(current_stroke_group) >= 3 and self.can_form_segment(current_stroke_group):
segment = self.create_segment(current_stroke_group)
segments.append(segment)
# 分类存储
for segment in segments:
if segment.direction == 1:
self.up_segments.append(segment)
else:
self.down_segments.append(segment)
self.segments = segments
logger.info(f"检测到 {len(segments)} 个线段:上升线段 {len(self.up_segments)} 个,下降线段 {len(self.down_segments)}")
return segments
def get_segment_sequence(self) -> List[SegmentElement]:
"""
获取连续的线段序列
Returns:
连续线段序列
"""
return sorted(self.segments, key=lambda x: x.start_time)
def find_segment_overlaps(self) -> List[Tuple[SegmentElement, SegmentElement]]:
"""
寻找线段重叠区域(可能的中枢)
Returns:
重叠线段对列表
"""
overlaps = []
sequence = self.get_segment_sequence()
for i in range(len(sequence) - 1):
for j in range(i + 1, len(sequence)):
seg1, seg2 = sequence[i], sequence[j]
# 检查价格区间是否重叠
if (min(seg1.start_price, seg1.end_price) <= max(seg2.start_price, seg2.end_price) and
max(seg1.start_price, seg1.end_price) >= min(seg2.start_price, seg2.end_price)):
overlaps.append((seg1, seg2))
return overlaps
def analyze_segment_patterns(self) -> Dict:
"""
分析线段模式
Returns:
模式分析结果
"""
if not self.segments:
return {}
sequence = self.get_segment_sequence()
# 分析趋势
trend_changes = 0
for i in range(1, len(sequence)):
if sequence[i].direction != sequence[i-1].direction:
trend_changes += 1
# 统计信息
avg_length = np.mean([s.length for s in self.segments])
avg_stroke_count = np.mean([len(s.strokes) for s in self.segments])
max_length = max([s.length for s in self.segments]) if self.segments else 0
# 寻找重叠区域
overlaps = self.find_segment_overlaps()
return {
'total_segments': len(self.segments),
'up_segments': len(self.up_segments),
'down_segments': len(self.down_segments),
'trend_changes': trend_changes,
'avg_length': avg_length,
'max_length': max_length,
'avg_stroke_count': avg_stroke_count,
'overlaps': len(overlaps),
'confirmed_segments': sum(1 for s in self.segments if s.confirmed)
}
def filter_segments_by_strength(self, min_strength: float) -> List[SegmentElement]:
"""
按强度过滤线段
Args:
min_strength: 最小强度要求
Returns:
过滤后的线段列表
"""
return [s for s in self.segments if s.strength >= min_strength]
def filter_segments_by_length(self, min_length: float) -> List[SegmentElement]:
"""
按长度过滤线段
Args:
min_length: 最小长度要求
Returns:
过滤后的线段列表
"""
return [s for s in self.segments if s.length >= min_length]
def to_dataframe(self) -> pd.DataFrame:
"""
将线段转换为DataFrame
Returns:
包含线段信息的DataFrame
"""
if not self.segments:
return pd.DataFrame()
data = []
for i, segment in enumerate(self.segments):
data.append({
'segment_id': i,
'start_time': segment.start_time,
'end_time': segment.end_time,
'start_price': segment.start_price,
'end_price': segment.end_price,
'direction': segment.direction,
'length': segment.length,
'strength': segment.strength,
'stroke_count': len(segment.strokes),
'confirmed': segment.confirmed
})
return pd.DataFrame(data)
+350
View File
@@ -0,0 +1,350 @@
"""
笔模块:连接相邻的顶分型和底分型形成笔
笔的定义:由一个顶分型和一个底分型连接而成,且中间不能有其他分型
"""
import pandas as pd
import numpy as np
from typing import List, Tuple, Optional, Dict
from dataclasses import dataclass
import logging
from .fractal import FractalPoint
logger = logging.getLogger(__name__)
@dataclass
class StrokeElement:
"""笔元素数据类"""
start_fractal: FractalPoint # 起始分型
end_fractal: FractalPoint # 结束分型
direction: int # 方向:1为上升笔,-1为下降笔
length: float # 笔的长度(价格差)
duration: int # 持续时间(K线数量)
strength: float # 笔的强度
class Stroke:
"""笔识别和处理类"""
def __init__(self, fractals: List[FractalPoint], kline_data: pd.DataFrame):
"""
初始化笔处理器
Args:
fractals: 分型点列表
kline_data: K线数据
"""
self.fractals = fractals
self.kline_data = kline_data
self.strokes = []
self.up_strokes = [] # 上升笔
self.down_strokes = [] # 下降笔
def create_stroke(self, start_fractal: FractalPoint,
end_fractal: FractalPoint) -> StrokeElement:
"""
创建笔元素
Args:
start_fractal: 起始分型
end_fractal: 结束分型
Returns:
笔元素
"""
# 计算方向
if start_fractal.fractal_type == 'bottom' and end_fractal.fractal_type == 'top':
direction = 1 # 上升笔
elif start_fractal.fractal_type == 'top' and end_fractal.fractal_type == 'bottom':
direction = -1 # 下降笔
else:
raise ValueError("无效的分型组合")
# 计算长度
length = abs(end_fractal.price - start_fractal.price)
# 计算持续时间
duration = end_fractal.index - start_fractal.index
# 计算强度(可以基于多个因素)
strength = self._calculate_stroke_strength(start_fractal, end_fractal, length, duration)
return StrokeElement(
start_fractal=start_fractal,
end_fractal=end_fractal,
direction=direction,
length=length,
duration=duration,
strength=strength
)
def _calculate_stroke_strength(self, start_fractal: FractalPoint,
end_fractal: FractalPoint,
length: float, duration: int) -> float:
"""
计算笔的强度
Args:
start_fractal: 起始分型
end_fractal: 结束分型
length: 价格长度
duration: 时间长度
Returns:
笔的强度值
"""
# 基础强度:价格变化幅度
price_strength = length
# 分型强度加权
fractal_strength = (start_fractal.strength + end_fractal.strength) / 2
# 时间因子:适中的时间长度得分更高
time_factor = min(duration / 10, 1.0) if duration > 0 else 0
# 综合强度
strength = price_strength * (1 + fractal_strength * 0.1) * (1 + time_factor * 0.1)
return strength
def validate_stroke(self, start_fractal: FractalPoint,
end_fractal: FractalPoint) -> bool:
"""
验证笔的有效性
Args:
start_fractal: 起始分型
end_fractal: 结束分型
Returns:
是否为有效笔
"""
# 检查分型类型是否正确
valid_combinations = [
('bottom', 'top'), # 上升笔
('top', 'bottom') # 下降笔
]
combination = (start_fractal.fractal_type, end_fractal.fractal_type)
if combination not in valid_combinations:
return False
# 检查时间顺序
if start_fractal.index >= end_fractal.index:
return False
# 检查价格关系
if start_fractal.fractal_type == 'bottom':
# 上升笔:结束价格应该高于起始价格
if end_fractal.price <= start_fractal.price:
return False
else:
# 下降笔:结束价格应该低于起始价格
if end_fractal.price >= start_fractal.price:
return False
return True
def detect_strokes(self) -> List[StrokeElement]:
"""
检测所有笔
Returns:
笔列表
"""
if len(self.fractals) < 2:
logger.warning("分型数量不足,无法生成笔")
return []
strokes = []
# 按时间顺序排序分型
sorted_fractals = sorted(self.fractals, key=lambda x: x.index)
i = 0
while i < len(sorted_fractals) - 1:
start_fractal = sorted_fractals[i]
# 寻找下一个有效的分型来形成笔
j = i + 1
while j < len(sorted_fractals):
end_fractal = sorted_fractals[j]
# 检查是否可以形成有效笔
if self.validate_stroke(start_fractal, end_fractal):
stroke = self.create_stroke(start_fractal, end_fractal)
strokes.append(stroke)
# 分类存储
if stroke.direction == 1:
self.up_strokes.append(stroke)
else:
self.down_strokes.append(stroke)
# 从结束分型继续寻找下一笔
i = j
break
j += 1
else:
# 没有找到有效的结束分型,跳到下一个分型
i += 1
self.strokes = strokes
logger.info(f"检测到 {len(strokes)} 笔:上升笔 {len(self.up_strokes)} 个,下降笔 {len(self.down_strokes)}")
return strokes
def get_stroke_sequence(self) -> List[StrokeElement]:
"""
获取连续的笔序列
Returns:
连续笔序列
"""
return sorted(self.strokes, key=lambda x: x.start_fractal.index)
def find_stroke_extremes(self) -> Dict[str, List[StrokeElement]]:
"""
寻找笔的极值点
Returns:
包含最长、最短、最强笔的字典
"""
if not self.strokes:
return {}
# 按长度排序
by_length = sorted(self.strokes, key=lambda x: x.length, reverse=True)
# 按强度排序
by_strength = sorted(self.strokes, key=lambda x: x.strength, reverse=True)
# 按持续时间排序
by_duration = sorted(self.strokes, key=lambda x: x.duration, reverse=True)
return {
'longest': by_length[:5], # 最长的5笔
'strongest': by_strength[:5], # 最强的5笔
'longest_duration': by_duration[:5] # 持续时间最长的5笔
}
def analyze_stroke_patterns(self) -> Dict:
"""
分析笔的模式
Returns:
模式分析结果
"""
if len(self.strokes) < 3:
return {}
sequence = self.get_stroke_sequence()
# 分析连续同向笔(可能的延伸)
extensions = []
i = 0
while i < len(sequence) - 1:
current = sequence[i]
next_stroke = sequence[i + 1]
# 检查是否为同向延伸
if current.direction == next_stroke.direction:
extensions.append((current, next_stroke))
i += 1
# 分析笔的趋势强度
trend_strength = self._calculate_trend_strength(sequence)
# 统计平均笔长度
avg_length = np.mean([s.length for s in self.strokes]) if self.strokes else 0
avg_duration = np.mean([s.duration for s in self.strokes]) if self.strokes else 0
return {
'total_strokes': len(self.strokes),
'up_strokes': len(self.up_strokes),
'down_strokes': len(self.down_strokes),
'extensions': len(extensions),
'avg_length': avg_length,
'avg_duration': avg_duration,
'trend_strength': trend_strength
}
def _calculate_trend_strength(self, sequence: List[StrokeElement]) -> float:
"""
计算趋势强度
Args:
sequence: 笔序列
Returns:
趋势强度值
"""
if len(sequence) < 2:
return 0
# 计算方向变化的频率
direction_changes = 0
for i in range(1, len(sequence)):
if sequence[i].direction != sequence[i-1].direction:
direction_changes += 1
# 趋势强度与方向变化成反比
change_ratio = direction_changes / (len(sequence) - 1) if len(sequence) > 1 else 1
trend_strength = 1 - change_ratio
return trend_strength
def filter_strokes_by_strength(self, min_strength: float) -> List[StrokeElement]:
"""
按强度过滤笔
Args:
min_strength: 最小强度要求
Returns:
过滤后的笔列表
"""
return [s for s in self.strokes if s.strength >= min_strength]
def filter_strokes_by_length(self, min_length: float) -> List[StrokeElement]:
"""
按长度过滤笔
Args:
min_length: 最小长度要求
Returns:
过滤后的笔列表
"""
return [s for s in self.strokes if s.length >= min_length]
def to_dataframe(self) -> pd.DataFrame:
"""
将笔转换为DataFrame
Returns:
包含笔信息的DataFrame
"""
if not self.strokes:
return pd.DataFrame()
data = []
for i, stroke in enumerate(self.strokes):
data.append({
'stroke_id': i,
'start_time': stroke.start_fractal.timestamp,
'end_time': stroke.end_fractal.timestamp,
'start_price': stroke.start_fractal.price,
'end_price': stroke.end_fractal.price,
'direction': stroke.direction,
'length': stroke.length,
'duration': stroke.duration,
'strength': stroke.strength,
'start_fractal_type': stroke.start_fractal.fractal_type,
'end_fractal_type': stroke.end_fractal.fractal_type
})
return pd.DataFrame(data)
+576
View File
@@ -0,0 +1,576 @@
"""
买卖点信号模块:识别缠论中的各类买卖点
包括一类买卖点、二类买卖点、三类买卖点的识别逻辑
"""
import pandas as pd
import numpy as np
from typing import List, Tuple, Optional, Dict
from dataclasses import dataclass
import logging
from .central_bank import CentralBankElement
from .segment import SegmentElement
from .stroke import StrokeElement
logger = logging.getLogger(__name__)
@dataclass
class TradingPoint:
"""买卖点数据类"""
timestamp: pd.Timestamp # 信号时间
price: float # 信号价格
signal_type: str # 'buy' 或 'sell'
point_class: str # 'first', 'second', 'third'
strength: float # 信号强度
description: str # 信号描述
related_central_bank: Optional[CentralBankElement] = None # 相关中枢
confirmed: bool = False # 是否已确认
class TradingSignal:
"""买卖点信号识别类"""
def __init__(self, central_banks: List[CentralBankElement],
segments: List[SegmentElement],
strokes: List[StrokeElement]):
"""
初始化买卖点识别器
Args:
central_banks: 中枢列表
segments: 线段列表
strokes: 笔列表
"""
self.central_banks = sorted(central_banks, key=lambda x: x.start_time)
self.segments = sorted(segments, key=lambda x: x.start_time)
self.strokes = sorted(strokes, key=lambda x: x.start_fractal.timestamp)
self.trading_points = []
def detect_first_class_points(self) -> List[TradingPoint]:
"""
识别一类买卖点:中枢突破点
Returns:
一类买卖点列表
"""
first_class_points = []
for cb in self.central_banks:
# 寻找中枢后的突破
post_segments = [seg for seg in self.segments
if seg.start_time > cb.end_time]
if not post_segments:
continue
# 检查前3个线段中的突破
for seg in post_segments[:3]:
if seg.direction == 1 and seg.end_price > cb.high_price:
# 向上突破 - 一类买点
strength = self._calculate_breakout_strength(cb, seg, 'up')
point = TradingPoint(
timestamp=seg.end_time,
price=seg.end_price,
signal_type='buy',
point_class='first',
strength=strength,
description=f"一类买点:向上突破{cb.level}中枢",
related_central_bank=cb
)
first_class_points.append(point)
break
elif seg.direction == -1 and seg.end_price < cb.low_price:
# 向下突破 - 一类卖点
strength = self._calculate_breakout_strength(cb, seg, 'down')
point = TradingPoint(
timestamp=seg.end_time,
price=seg.end_price,
signal_type='sell',
point_class='first',
strength=strength,
description=f"一类卖点:向下突破{cb.level}中枢",
related_central_bank=cb
)
first_class_points.append(point)
break
return first_class_points
def detect_second_class_points(self) -> List[TradingPoint]:
"""
识别二类买卖点:回拉不进入中枢的确认点
Returns:
二类买卖点列表
"""
second_class_points = []
# 先获取一类买卖点
first_class_points = self.detect_first_class_points()
for first_point in first_class_points:
cb = first_point.related_central_bank
if not cb:
continue
# 寻找一类点之后的回拉
post_segments = [seg for seg in self.segments
if seg.start_time > first_point.timestamp]
for i, seg in enumerate(post_segments[:5]): # 检查后续5个线段
if first_point.signal_type == 'buy':
# 一类买点后的回拉测试
if (seg.direction == -1 and
seg.end_price > cb.high_price and # 没有跌破中枢上边界
i < len(post_segments) - 1): # 确保有后续线段
next_seg = post_segments[i + 1]
if next_seg.direction == 1: # 回拉后再次上涨
strength = self._calculate_pullback_strength(cb, seg, next_seg)
point = TradingPoint(
timestamp=next_seg.start_time,
price=seg.end_price,
signal_type='buy',
point_class='second',
strength=strength,
description=f"二类买点:回拉确认{cb.level}中枢支撑",
related_central_bank=cb
)
second_class_points.append(point)
elif first_point.signal_type == 'sell':
# 一类卖点后的反弹测试
if (seg.direction == 1 and
seg.end_price < cb.low_price and # 没有涨破中枢下边界
i < len(post_segments) - 1): # 确保有后续线段
next_seg = post_segments[i + 1]
if next_seg.direction == -1: # 反弹后再次下跌
strength = self._calculate_pullback_strength(cb, seg, next_seg)
point = TradingPoint(
timestamp=next_seg.start_time,
price=seg.end_price,
signal_type='sell',
point_class='second',
strength=strength,
description=f"二类卖点:反弹确认{cb.level}中枢阻力",
related_central_bank=cb
)
second_class_points.append(point)
return second_class_points
def detect_third_class_points(self) -> List[TradingPoint]:
"""
识别三类买卖点:次级别背驰点
Returns:
三类买卖点列表
"""
third_class_points = []
# 在中枢内部寻找次级别的背驰机会
for cb in self.central_banks:
# 获取中枢时间范围内的笔
internal_strokes = [stroke for stroke in self.strokes
if (stroke.start_fractal.timestamp >= cb.start_time and
stroke.end_fractal.timestamp <= cb.end_time)]
if len(internal_strokes) < 5: # 需要足够的笔进行分析
continue
# 寻找背驰模式
divergence_points = self._find_divergence_in_strokes(internal_strokes, cb)
third_class_points.extend(divergence_points)
return third_class_points
def _find_divergence_in_strokes(self, strokes: List[StrokeElement],
cb: CentralBankElement) -> List[TradingPoint]:
"""
在笔中寻找背驰模式
Args:
strokes: 笔列表
cb: 相关中枢
Returns:
背驰点列表
"""
divergence_points = []
# 按方向分组
up_strokes = [s for s in strokes if s.direction == 1]
down_strokes = [s for s in strokes if s.direction == -1]
# 检查上升笔的顶背驰
if len(up_strokes) >= 3:
for i in range(2, len(up_strokes)):
current_stroke = up_strokes[i]
prev_stroke = up_strokes[i-1]
# 价格创新高但力度减弱
if (current_stroke.end_fractal.price > prev_stroke.end_fractal.price and
current_stroke.strength < prev_stroke.strength * 0.8):
strength = self._calculate_divergence_strength(current_stroke, prev_stroke)
point = TradingPoint(
timestamp=current_stroke.end_fractal.timestamp,
price=current_stroke.end_fractal.price,
signal_type='sell',
point_class='third',
strength=strength,
description=f"三类卖点:{cb.level}中枢内顶背驰",
related_central_bank=cb
)
divergence_points.append(point)
# 检查下降笔的底背驰
if len(down_strokes) >= 3:
for i in range(2, len(down_strokes)):
current_stroke = down_strokes[i]
prev_stroke = down_strokes[i-1]
# 价格创新低但力度减弱
if (current_stroke.end_fractal.price < prev_stroke.end_fractal.price and
current_stroke.strength < prev_stroke.strength * 0.8):
strength = self._calculate_divergence_strength(current_stroke, prev_stroke)
point = TradingPoint(
timestamp=current_stroke.end_fractal.timestamp,
price=current_stroke.end_fractal.price,
signal_type='buy',
point_class='third',
strength=strength,
description=f"三类买点:{cb.level}中枢内底背驰",
related_central_bank=cb
)
divergence_points.append(point)
return divergence_points
def _calculate_breakout_strength(self, cb: CentralBankElement,
segment: SegmentElement, direction: str) -> float:
"""
计算突破强度
Args:
cb: 中枢
segment: 突破线段
direction: 突破方向
Returns:
突破强度
"""
# 基础强度:突破幅度
if direction == 'up':
breakout_distance = segment.end_price - cb.high_price
base_strength = breakout_distance / cb.high_price
else:
breakout_distance = cb.low_price - segment.end_price
base_strength = breakout_distance / cb.low_price
# 中枢强度加权
cb_strength_factor = min(cb.strength / 100, 2.0)
# 线段强度加权
segment_strength_factor = min(segment.strength / 50, 2.0)
# 综合强度
total_strength = base_strength * (1 + cb_strength_factor * 0.2) * (1 + segment_strength_factor * 0.3)
return max(0, min(total_strength, 1.0)) # 限制在0-1之间
def _calculate_pullback_strength(self, cb: CentralBankElement,
pullback_seg: SegmentElement,
resume_seg: SegmentElement) -> float:
"""
计算回拉强度
Args:
cb: 中枢
pullback_seg: 回拉线段
resume_seg: 恢复线段
Returns:
回拉强度
"""
# 回拉深度(越浅越好)
cb_height = cb.high_price - cb.low_price
if pullback_seg.direction == -1: # 向下回拉
pullback_depth = (cb.high_price - pullback_seg.end_price) / cb_height
else: # 向上回拉
pullback_depth = (pullback_seg.end_price - cb.low_price) / cb_height
# 回拉强度:深度越小越好
pullback_strength = max(0, 1 - pullback_depth)
# 恢复强度
resume_strength = min(resume_seg.strength / 30, 1.0)
# 综合强度
total_strength = (pullback_strength + resume_strength) / 2
return max(0, min(total_strength, 1.0))
def _calculate_divergence_strength(self, current_stroke: StrokeElement,
prev_stroke: StrokeElement) -> float:
"""
计算背驰强度
Args:
current_stroke: 当前笔
prev_stroke: 前一笔
Returns:
背驰强度
"""
# 力度差异
strength_ratio = prev_stroke.strength / current_stroke.strength if current_stroke.strength > 0 else 1
# 价格差异
price_change = abs(current_stroke.end_fractal.price - prev_stroke.end_fractal.price)
price_ratio = price_change / prev_stroke.start_fractal.price if prev_stroke.start_fractal.price > 0 else 0
# 背驰强度
divergence_strength = min(strength_ratio * 0.3 + price_ratio * 10, 1.0)
return max(0, divergence_strength)
def detect_fractal_based_signals(self, fractals: List, processed_klines) -> List[TradingPoint]:
"""
基于分型识别买卖点(不依赖中枢)
Args:
fractals: 分型列表
processed_klines: 处理后的K线数据
Returns:
分型买卖点列表
"""
signals = []
if not fractals or len(fractals) < 3:
return signals
for i, fractal in enumerate(fractals):
# 分析分型强度和位置
strength_score = self._calculate_fractal_strength_score(fractal, fractals, i)
position_score = self._calculate_fractal_position_score(fractal, processed_klines)
# 综合评分
total_score = (strength_score + position_score) / 2
if total_score > 0.6: # 阈值可调整
signal_type = 'buy' if fractal.fractal_type == 'bottom' else 'sell'
point_class = self._determine_fractal_point_class(total_score)
description = f"分型{signal_type}点:强度{fractal.strength},评分{total_score:.2f}"
signal = TradingPoint(
timestamp=fractal.timestamp,
price=fractal.price,
signal_type=signal_type,
point_class=point_class,
strength=total_score,
description=description,
confirmed=fractal.confirmed
)
signals.append(signal)
return signals
def _calculate_fractal_strength_score(self, fractal, all_fractals: List, index: int) -> float:
"""计算分型强度评分(避免使用未来数据)"""
base_score = min(fractal.strength / 5.0, 1.0) # 基础强度评分
# 只使用过去的分型进行比较,避免使用未来数据
past_fractals = []
for i in range(max(0, index-5), index): # 只看过去5个分型
if i < len(all_fractals) and all_fractals[i].fractal_type == fractal.fractal_type:
past_fractals.append(all_fractals[i])
if past_fractals:
relative_strength = fractal.strength / max(f.strength for f in past_fractals)
relative_score = min(relative_strength, 1.0)
else:
relative_score = 1.0
return (base_score + relative_score) / 2
def _calculate_fractal_position_score(self, fractal, processed_klines) -> float:
"""计算分型位置评分(避免使用未来数据)"""
if processed_klines is None or len(processed_klines) < 20:
return 0.5
# 寻找分型在K线数据中的位置
fractal_index = None
for i, (timestamp, kline) in enumerate(processed_klines.iterrows()):
# 修复时间戳计算兼容性问题
time_diff = abs((pd.Timestamp(timestamp) - pd.Timestamp(fractal.timestamp)).total_seconds())
if time_diff < 3600: # 1小时内
fractal_index = i
break
if fractal_index is None:
return 0.5
# 计算相对位置(只使用过去的数据)
start_idx = max(0, fractal_index - 20) # 只看过去20个K线
end_idx = fractal_index + 1 # 包含当前K线,但不包含未来K线
recent_data = processed_klines.iloc[start_idx:end_idx]
if fractal.fractal_type == 'bottom':
# 底分型:看是否接近过去一段时间的低点
min_price = recent_data['low'].min()
score = 1.0 if abs(fractal.price - min_price) / min_price < 0.02 else 0.5
else:
# 顶分型:看是否接近过去一段时间的高点
max_price = recent_data['high'].max()
score = 1.0 if abs(fractal.price - max_price) / max_price < 0.02 else 0.5
return score
def _determine_fractal_point_class(self, score: float) -> str:
"""根据评分确定买卖点类别"""
if score >= 0.8:
return "second" # 高质量分型当作二类买卖点
else:
return "third" # 一般分型当作三类买卖点
def detect_all_trading_points(self) -> List[TradingPoint]:
"""
检测所有买卖点(包括中枢相关和分型相关)
"""
all_signals = []
# 原有的中枢相关买卖点
all_signals.extend(self.detect_first_class_points())
all_signals.extend(self.detect_second_class_points())
all_signals.extend(self.detect_third_class_points())
# 新增:基于分型的买卖点(当没有足够中枢时)
if len(self.central_banks) < 2:
logger.info("中枢数量不足,启用分型买卖点识别")
# 需要获取分型和K线数据,这里需要从外部传入
# 暂时先返回现有信号
pass
# 按时间排序
all_signals.sort(key=lambda x: x.timestamp)
# 去重(同一时间点可能有多个信号)
unique_signals = []
seen_times = set()
for signal in all_signals:
time_key = signal.timestamp.strftime('%Y%m%d%H%M')
if time_key not in seen_times:
unique_signals.append(signal)
seen_times.add(time_key)
class_counts = {'first': 0, 'second': 0, 'third': 0}
for signal in unique_signals:
class_counts[signal.point_class] += 1
total_signals = len(unique_signals)
logger.info(f"检测到买卖点:一类 {class_counts['first']} 个,二类 {class_counts['second']} 个,"
f"三类 {class_counts['third']} 个,总计 {total_signals}")
return unique_signals
def get_signal_statistics(self) -> Dict:
"""
获取信号统计信息
Returns:
统计信息字典
"""
if not self.trading_points:
return {}
stats = {
'total_signals': len(self.trading_points),
'buy_signals': len([p for p in self.trading_points if p.signal_type == 'buy']),
'sell_signals': len([p for p in self.trading_points if p.signal_type == 'sell']),
'by_class': {},
'avg_strength': np.mean([p.strength for p in self.trading_points]),
'max_strength': max([p.strength for p in self.trading_points]),
'confirmed_signals': len([p for p in self.trading_points if p.confirmed])
}
# 按类别统计
for point_class in ['first', 'second', 'third']:
class_points = [p for p in self.trading_points if p.point_class == point_class]
stats['by_class'][f'{point_class}_class'] = {
'total': len(class_points),
'buy': len([p for p in class_points if p.signal_type == 'buy']),
'sell': len([p for p in class_points if p.signal_type == 'sell']),
'avg_strength': np.mean([p.strength for p in class_points]) if class_points else 0
}
return stats
def filter_signals_by_strength(self, min_strength: float) -> List[TradingPoint]:
"""
按强度过滤信号
Args:
min_strength: 最小强度要求
Returns:
过滤后的信号列表
"""
return [p for p in self.trading_points if p.strength >= min_strength]
def get_latest_signals(self, hours: int = 24) -> List[TradingPoint]:
"""
获取最新的信号
Args:
hours: 最近多少小时
Returns:
最新信号列表
"""
if not self.trading_points:
return []
latest_time = max([p.timestamp for p in self.trading_points])
cutoff_time = latest_time - pd.Timedelta(hours=hours)
return [p for p in self.trading_points if p.timestamp >= cutoff_time]
def to_dataframe(self) -> pd.DataFrame:
"""
将买卖点转换为DataFrame
Returns:
包含买卖点信息的DataFrame
"""
if not self.trading_points:
return pd.DataFrame()
data = []
for point in self.trading_points:
data.append({
'timestamp': point.timestamp,
'price': point.price,
'signal_type': point.signal_type,
'point_class': point.point_class,
'strength': point.strength,
'description': point.description,
'confirmed': point.confirmed,
'related_central_bank_level': point.related_central_bank.level if point.related_central_bank else None
})
df = pd.DataFrame(data)
df.set_index('timestamp', inplace=True)
return df