Files
chanlun_1/core/trading_signal.py
T
2025-05-23 19:09:55 +08:00

576 lines
23 KiB
Python

"""
买卖点信号模块:识别缠论中的各类买卖点
包括一类买卖点、二类买卖点、三类买卖点的识别逻辑
"""
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