Add files to chanlun_1
This commit is contained in:
@@ -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'
|
||||
]
|
||||
@@ -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)
|
||||
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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)
|
||||
@@ -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
|
||||
Reference in New Issue
Block a user