2451 lines
97 KiB
Python
2451 lines
97 KiB
Python
import copy
|
||
from typing import Dict, Optional
|
||
|
||
from ChanEnum import Chan_FX_TYPE, Chan_KLINE_DIR, Chan_BI_DIR, Chan_KLC_FX
|
||
import ChanKLU
|
||
import ChanCTime
|
||
|
||
# 根据结合律合并K线后的K线
|
||
class ChanKLC():
|
||
def __init__(self, klu: ChanKLU, index, ddir=Chan_KLINE_DIR.UP):
|
||
self.start_time = klu.time
|
||
self.end_time = None
|
||
self.high = klu.high
|
||
self.low = klu.low
|
||
self.dir = ddir
|
||
self.index = index
|
||
self.klus = []
|
||
self.add_klu(klu)
|
||
self.fx = Chan_FX_TYPE.UNKNOWN
|
||
self.next = None
|
||
self.pre = None
|
||
self.start_klu = klu
|
||
self.end_klu = None
|
||
self.state = "00"
|
||
self.open = klu.open
|
||
self.close = klu.close
|
||
self.volume = klu.volume
|
||
self.bi = None
|
||
self.distance = 0
|
||
self.klc_fx_type = Chan_KLC_FX.UNKNOWN
|
||
self.rsi = klu.rsi
|
||
self.volume_ratio = klu.volume_ratio
|
||
self.macdhist = 0
|
||
def set_klc_fx_type(self, klc_fx_type):
|
||
#print(self.start_time, klc_fx_type, self.get_feature_data()['klu_macd'], self.get_feature_data()['klu_macdhist'], self.get_feature_data()['klu_rsi'])
|
||
self.klc_fx_type = klc_fx_type
|
||
def add_klu(self, klu):
|
||
self.klus.append(klu)
|
||
def set_end_klu(self, klu):
|
||
self.end_klu = klu
|
||
self.end_time = klu.time
|
||
self.close = klu.close
|
||
for index in range(1, len(self.klus)):
|
||
self.volume += self.klus[index].volume
|
||
self.rsi += self.klus[index].rsi
|
||
self.volume_ratio += self.klus[index].volume_ratio
|
||
self.macdhist += self.klus[index].macdhist
|
||
self.rsi = self.rsi / len(self.klus)
|
||
self.volume_ratio = self.volume_ratio / len(self.klus)
|
||
self.volume = self.volume / len(self.klus)
|
||
self.macdhist = self.macdhist / len(self.klus)
|
||
def contain_klu_fx(self):
|
||
if len(self.klus) > 0:
|
||
for klu in self.klus:
|
||
klu.update_realtime_analysis()
|
||
if klu.fx_type == self.fx and klu.fx_strength > 1.8:
|
||
#print(klu.time, klu.fx_type, klu.fx_strength)
|
||
return True
|
||
return False
|
||
def set_next(self, klc):
|
||
self.next = klc
|
||
def set_pre(self, klc):
|
||
self.pre = klc
|
||
def set_state(self, state):
|
||
self.state = state
|
||
def check_klu_included(self, klu):
|
||
if self.high >= klu.high:
|
||
# high大于,low小于,左包含
|
||
if self.low <= klu.low:
|
||
self.add_klu(klu=klu)
|
||
# gn>gn-1
|
||
if self.dir == Chan_KLINE_DIR.UP:
|
||
# UP -> max(dn)
|
||
self.low = klu.low
|
||
else:
|
||
# DOWN -> min(gn)
|
||
self.high = klu.high
|
||
#self.print(klu, "Z")
|
||
return True
|
||
# high大于,low大于,不包含
|
||
else:
|
||
# if self.low > klu.low
|
||
# high相等,右包含
|
||
if self.high == klu.high:
|
||
self.add_klu(klu=klu)
|
||
# UP -> max(gn)
|
||
if self.dir == Chan_KLINE_DIR.UP:
|
||
self.high = klu.high
|
||
else:
|
||
# DOWN -> min(dn)
|
||
self.low = klu.low
|
||
return True
|
||
else:
|
||
return False
|
||
else:
|
||
# high小于,low大于,右包含
|
||
if self.low >= klu.low:
|
||
self.add_klu(klu=klu)
|
||
# gn>gn-1
|
||
if self.dir == Chan_KLINE_DIR.UP:
|
||
# UP -> max(gn)
|
||
self.high = klu.high
|
||
else:
|
||
# DOWN -> min(dn)
|
||
self.low = klu.low
|
||
#self.print(klu, "Y")
|
||
return True
|
||
else:
|
||
# high小于,low小于,不包含
|
||
return False
|
||
def set_fx(self, fx: Chan_FX_TYPE):
|
||
self.fx = fx
|
||
def print(self):
|
||
print(self.time, self.high, self.low, self.start_time, self.end_time, self.fx, self.index)
|
||
def copy(self):
|
||
"""创建KLC对象的浅拷贝, 避免循环引用"""
|
||
new_klc = ChanKLC(self.start_klu, self.index, self.dir)
|
||
new_klc.high = self.high
|
||
new_klc.low = self.low
|
||
new_klc.state = self.state
|
||
new_klc.fx = self.fx
|
||
# 不复制 next 和 pre 引用,避免循环引用
|
||
return new_klc
|
||
def set_pre_fx(self):
|
||
if self.pre and self.pre.pre:
|
||
self.pre.fx = self.check_fx(self.pre.pre, self.pre)
|
||
def check_fx(self, k1, k2):
|
||
if k2.high > k1.high and k2.high > self.high:
|
||
return Chan_FX_TYPE.TOP
|
||
elif k2.low < k1.low and k2.low < self.low:
|
||
return Chan_FX_TYPE.BOTTOM
|
||
else:
|
||
return Chan_FX_TYPE.UNKNOWN
|
||
def set_bi(self, bi):
|
||
self.bi = bi
|
||
self.distance = self.index - bi.start_klc.index
|
||
#print(self.start_time, self.distance, bi.index, bi.dir)
|
||
def cal_fx(self):
|
||
"""
|
||
根据缠论分型强弱判断规则计算分型强度
|
||
返回值:
|
||
- 0: 不是分型或无效分型
|
||
- 1-100: 分型强度,数值越大表示分型越强
|
||
"""
|
||
# 检查基本条件:必须是分型且有前后KLC
|
||
if (self.fx == Chan_FX_TYPE.UNKNOWN or
|
||
self.pre is None or self.next is None or
|
||
self.next.end_klu is None):
|
||
return 0
|
||
|
||
# 获取分型的三根K线(KLC)
|
||
klc1 = self.pre # 第1条
|
||
klc2 = self # 第2条(分型中心)
|
||
klc3 = self.next # 第3条
|
||
|
||
if self.fx == Chan_FX_TYPE.TOP:
|
||
return self._calculate_top_fx_strength(klc1, klc2, klc3)
|
||
elif self.fx == Chan_FX_TYPE.BOTTOM:
|
||
return self._calculate_bottom_fx_strength(klc1, klc2, klc3)
|
||
else:
|
||
return 0
|
||
|
||
def _calculate_top_fx_strength(self, klc1, klc2, klc3):
|
||
"""
|
||
计算顶分型强度
|
||
"""
|
||
strength = 50 # 基础分数
|
||
|
||
# 1. 检查包含关系(规则1)
|
||
has_inclusion = self._has_inclusion_relationship(klc1, klc2, klc3)
|
||
if not has_inclusion:
|
||
strength += 20 # 没有包含关系加分
|
||
else:
|
||
strength -= 10 # 有包含关系减分
|
||
|
||
# 检查最坏的包含关系(规则4)
|
||
if self._is_worst_inclusion_for_top(klc2, klc3):
|
||
strength -= 20 # 第3条大阴线"吃掉"第2条阳线
|
||
|
||
# 2. 检查第1条K线是否为大阳线,第2、3条为小K线(规则2)
|
||
if self._is_big_bullish_followed_by_small(klc1, klc2, klc3):
|
||
strength -= 25 # 中继顶分型可能性大
|
||
|
||
# 3. 检查第2条K线形态和第3条K线位置(规则3)
|
||
if self._has_strong_top_pattern(klc2, klc3):
|
||
strength += 25 # 力度比较大的分型
|
||
|
||
# 4. 检查第3条K线是否跌破第1条K线(规则5)
|
||
if self._breaks_first_klc_bottom_for_top(klc1, klc3):
|
||
strength -= 15 # 较弱的顶分型
|
||
|
||
# 5. 成交量确认
|
||
volume_factor = self._get_volume_factor(klc2)
|
||
strength += volume_factor
|
||
|
||
return max(0, min(100, strength))
|
||
|
||
def _calculate_bottom_fx_strength(self, klc1, klc2, klc3):
|
||
"""
|
||
计算底分型强度
|
||
"""
|
||
strength = 50 # 基础分数
|
||
|
||
# 1. 检查包含关系
|
||
has_inclusion = self._has_inclusion_relationship(klc1, klc2, klc3)
|
||
if not has_inclusion:
|
||
strength += 20 # 没有包含关系加分
|
||
else:
|
||
strength -= 10 # 有包含关系减分
|
||
|
||
# 2. 检查第3条K线高点与第1条K线高点的关系(规则1-3)
|
||
high_relationship = self._analyze_bottom_high_relationship(klc1, klc3)
|
||
if high_relationship == "strong": # 第3条高点远高于第1条
|
||
strength += 25
|
||
elif high_relationship == "normal": # 第3条高点接近第1条
|
||
strength += 5
|
||
else: # 第3条高点低于第1条
|
||
strength -= 15
|
||
|
||
# 3. 检查后续K线确认(规则4)
|
||
if self._has_follow_through_for_bottom():
|
||
strength += 15
|
||
|
||
# 4. 成交量确认
|
||
volume_factor = self._get_volume_factor(klc2)
|
||
strength += volume_factor
|
||
|
||
return max(0, min(100, strength))
|
||
|
||
def _has_inclusion_relationship(self, klc1, klc2, klc3):
|
||
"""
|
||
检查构成分型的三根原始K线(KLU)是否存在包含关系
|
||
"""
|
||
# 检查任意两根KLU之间是否存在包含关系
|
||
return (klc1.start_klu.index - klc1.end_klu.index != 0 or klc2.start_klu.index - klc2.end_klu.index != 0 or klc3.start_klu.index - klc3.end_klu.index != 0)
|
||
|
||
def _is_worst_inclusion_for_top(self, klc2, klc3):
|
||
"""
|
||
检查是否为最坏的包含关系:第3根KLU大阴线"吃掉"第2根KLU阳线
|
||
"""
|
||
# 获取代表性的KLU
|
||
# 第2根KLU:取klc2的最后一根KLU
|
||
klu2 = klc2.end_klu if klc2.end_klu else klc2.start_klu
|
||
# 第3根KLU:取klc3的第一根KLU
|
||
klu3 = klc3.start_klu
|
||
|
||
if not klu2 or not klu3:
|
||
return False
|
||
|
||
# 检查klu2是否为阳线
|
||
klu2_is_bullish = klu2.close > klu2.open
|
||
|
||
# 检查klu3是否为大阴线(实体占总区间70%以上)
|
||
klu3_range = klu3.high - klu3.low
|
||
klu3_body = abs(klu3.close - klu3.open)
|
||
klu3_is_big_bearish = (klu3.close < klu3.open and
|
||
klu3_range > 0 and
|
||
klu3_body > klu3_range * 0.7)
|
||
|
||
# 检查klu3是否包含klu2(klu3的高点≥klu2的高点 且 klu3的低点≤klu2的低点)
|
||
klu3_contains_klu2 = (klu3.high >= klu2.high and klu3.low <= klu2.low)
|
||
|
||
return klu2_is_bullish and klu3_is_big_bearish and klu3_contains_klu2
|
||
|
||
def _is_big_bullish_followed_by_small(self, klc1, klc2, klc3):
|
||
"""
|
||
检查第1条是否为大阳线,第2、3条为小K线
|
||
"""
|
||
# 第1条为大阳线
|
||
klc1_big_bullish = (klc1.close > klc1.open and
|
||
abs(klc1.close - klc1.open) > (klc1.high - klc1.low) * 0.6)
|
||
|
||
# 第2、3条为小K线
|
||
klc2_small = abs(klc2.close - klc2.open) < (klc2.high - klc2.low) * 0.4
|
||
klc3_small = abs(klc3.close - klc3.open) < (klc3.high - klc3.low) * 0.4
|
||
|
||
return klc1_big_bullish and klc2_small and klc3_small
|
||
|
||
def _has_strong_top_pattern(self, klc2, klc3):
|
||
"""
|
||
检查是否有强力度的顶分型模式
|
||
"""
|
||
# 第2条K线有长上影线或为大阴线
|
||
klc2_range = klc2.high - klc2.low
|
||
if klc2_range > 0:
|
||
upper_shadow_ratio = (klc2.high - max(klc2.open, klc2.close)) / klc2_range
|
||
has_long_upper_shadow = upper_shadow_ratio > 0.3
|
||
else:
|
||
has_long_upper_shadow = False
|
||
|
||
klc2_big_bearish = (klc2.close < klc2.open and
|
||
abs(klc2.close - klc2.open) > klc2_range * 0.6)
|
||
|
||
klc2_strong = has_long_upper_shadow or klc2_big_bearish
|
||
|
||
# 第3条K线不能以阳线收在第2条K线区间的一半之上
|
||
klc2_mid = (klc2.high + klc2.low) / 2
|
||
klc3_weak_position = (klc3.close <= klc2_mid or klc3.close < klc3.open)
|
||
|
||
return klc2_strong and klc3_weak_position
|
||
|
||
def _breaks_first_klc_bottom_for_top(self, klc1, klc3):
|
||
"""
|
||
检查第3条是否跌破第1条K线底部且不能高于第1条区间一半之上
|
||
"""
|
||
breaks_bottom = klc3.low < klc1.low
|
||
klc1_mid = (klc1.high + klc1.low) / 2
|
||
below_mid = klc3.close <= klc1_mid
|
||
|
||
return breaks_bottom and below_mid
|
||
|
||
def _analyze_bottom_high_relationship(self, klc1, klc3):
|
||
"""
|
||
分析底分型中第3条K线高点与第1条K线高点的关系
|
||
"""
|
||
high_diff_ratio = (klc3.high - klc1.high) / klc1.high if klc1.high > 0 else 0
|
||
|
||
if high_diff_ratio > 0.02: # 高出2%以上
|
||
return "strong"
|
||
elif high_diff_ratio >= -0.01: # 接近或略高
|
||
return "normal"
|
||
else: # 明显低于
|
||
return "weak"
|
||
|
||
def _has_follow_through_for_bottom(self):
|
||
"""
|
||
检查底分型后续是否有确认
|
||
"""
|
||
# 检查后续第1条K线的低点是否高于底分型的上边沿
|
||
if self.next and self.next.next:
|
||
follow_klc = self.next.next
|
||
bottom_fx_top = max(self.pre.high, self.high, self.next.high)
|
||
return follow_klc.low > bottom_fx_top
|
||
return False
|
||
|
||
def _get_volume_factor(self, klc):
|
||
"""
|
||
获取成交量因子
|
||
"""
|
||
avg_volume = self._calculate_average_volume(lookback=5)
|
||
if avg_volume > 0:
|
||
volume_ratio = klc.volume / avg_volume
|
||
if volume_ratio > 2.0:
|
||
return 10 # 大量确认
|
||
elif volume_ratio > 1.5:
|
||
return 5 # 放量
|
||
elif volume_ratio < 0.5:
|
||
return -5 # 缩量
|
||
return 0
|
||
def check_pre_has_fx(self):
|
||
if self.pre:
|
||
return self.pre.fx != Chan_FX_TYPE.UNKNOWN
|
||
elif self.pre.pre:
|
||
return self.pre.pre.fx != Chan_FX_TYPE.UNKNOWN
|
||
elif self.pre.pre.pre:
|
||
return self.pre.pre.pre.fx != Chan_FX_TYPE.UNKNOWN
|
||
elif self.pre.pre.pre.pre:
|
||
return self.pre.pre.pre.pre.fx != Chan_FX_TYPE.UNKNOWN
|
||
else:
|
||
return False
|
||
def cal_klu_features(self):
|
||
features = dict()
|
||
feature_sums = dict()
|
||
feature_counts = dict()
|
||
|
||
# 遍历所有klu,累计每个特征的总和和计数
|
||
for klu in self.klus:
|
||
for key, value in klu.get_feature_data().items():
|
||
if key not in feature_sums:
|
||
feature_sums[key] = 0
|
||
feature_counts[key] = 0
|
||
|
||
feature_sums[key] += value
|
||
feature_counts[key] += 1
|
||
|
||
# 计算每个特征的平均值
|
||
for key in feature_sums:
|
||
features[key] = feature_sums[key] / feature_counts[key]
|
||
|
||
return features
|
||
def get_feature_data(self):
|
||
features = dict()
|
||
# 原有基础特征
|
||
features['klc_close'] = self.close #0
|
||
features['klc_open'] = self.open #1
|
||
features['klc_high'] = self.high #2
|
||
features['klc_low'] = self.low #3
|
||
features['klc_index'] = self.index #4
|
||
features['klc_dir'] = 0 if self.dir == Chan_KLINE_DIR.UP else 1 #5
|
||
features['klc_state'] = self.state #6
|
||
features['klc_fx'] = 0 if self.fx == Chan_FX_TYPE.UNKNOWN else 1 if self.fx == Chan_FX_TYPE.TOP else 2 #7
|
||
features['klc_klus'] = len(self.klus) #8
|
||
features['klc_volume'] = self.volume #9
|
||
features['klc_pre_fx'] = (0 if self.pre.fx == Chan_FX_TYPE.UNKNOWN else 1 if self.pre.fx == Chan_FX_TYPE.TOP else 2) if self.pre else 0 #10
|
||
features['klc_pre_pre_fx'] = (0 if self.pre.pre.fx == Chan_FX_TYPE.UNKNOWN else 1 if self.pre.pre.fx == Chan_FX_TYPE.TOP else 2) if self.pre and self.pre.pre else 0 #11
|
||
features['klc_pre_pre_pre_fx'] = (0 if self.pre.pre.pre.fx == Chan_FX_TYPE.UNKNOWN else 1 if self.pre.pre.pre.fx == Chan_FX_TYPE.TOP else 2) if self.pre and self.pre.pre and self.pre.pre.pre else 0 #12
|
||
features['klc_distance'] = self.distance #13
|
||
features['klc_volume_ratio'] = self.volume_ratio #14
|
||
features['klc_rsi'] = self.rsi #15
|
||
# New Add 20250422
|
||
#features['klc_macdhist'] = self.get_macdhist() #11
|
||
#features['klc_bi_macdhist'] = self.bi_macdhist #12
|
||
#features['klc_bi_macd_div'] = self.bi_macd_div #13
|
||
#features['klc_bi_dir'] = 1 if self.bi.dir == Chan_BI_DIR.UP else -1 #14
|
||
# ===== 2.1 K线形态因子 =====
|
||
|
||
# K线实体大小
|
||
if self.open != 0: # 避免除以零
|
||
features['klc_body_size_rel'] = abs(self.close - self.open) / self.open # 相对实体大小
|
||
else:
|
||
features['klc_body_size_rel'] = 0
|
||
features['klc_body_size_abs'] = abs(self.close - self.open) # 绝对实体大小
|
||
|
||
# 上下影线长度
|
||
max_oc = max(self.open, self.close)
|
||
min_oc = min(self.open, self.close)
|
||
high_low_range = self.high - self.low
|
||
|
||
if high_low_range != 0: # 避免除以零
|
||
features['klc_upper_shadow'] = (self.high - max_oc) / high_low_range # 上影线相对长度
|
||
features['klc_lower_shadow'] = (min_oc - self.low) / high_low_range # 下影线相对长度
|
||
else:
|
||
features['klc_upper_shadow'] = 0
|
||
features['klc_lower_shadow'] = 0
|
||
|
||
# K线波动范围
|
||
if self.close != 0: # 避免除以零
|
||
features['klc_range'] = 0 #(self.high - self.low) / self.close
|
||
else:
|
||
features['klc_range'] = 0
|
||
|
||
# 与前K线的价格关系
|
||
if self.pre:
|
||
# 当前K线最高价与前一根K线最高价的比较
|
||
if self.pre.high != 0: # 避免除以零
|
||
features['klc_high_ratio'] = self.high / self.pre.high
|
||
else:
|
||
features['klc_high_ratio'] = 1
|
||
|
||
# 当前K线最低价与前一根K线最低价的比较
|
||
if self.pre.low != 0: # 避免除以零
|
||
features['klc_low_ratio'] = self.low / self.pre.low
|
||
else:
|
||
features['klc_low_ratio'] = 1
|
||
|
||
# 当前K线收盘价与前一根K线收盘价的相对位置
|
||
if self.pre.close != 0: # 避免除以零
|
||
features['klc_close_change_1'] = (self.close - self.pre.close) / self.pre.close
|
||
else:
|
||
features['klc_close_change_1'] = 0
|
||
|
||
# 如果有前两根K线
|
||
if self.pre.pre:
|
||
if self.pre.pre.close != 0: # 避免除以零
|
||
features['klc_close_change_2'] = (self.close - self.pre.pre.close) / self.pre.pre.close
|
||
else:
|
||
features['klc_close_change_2'] = 0
|
||
else:
|
||
features['klc_close_change_2'] = 0
|
||
else:
|
||
# 如果没有前K线,设置默认值
|
||
features['klc_high_ratio'] = 1
|
||
features['klc_low_ratio'] = 1
|
||
features['klc_close_change_1'] = 0
|
||
features['klc_close_change_2'] = 0
|
||
|
||
# 分型特征编码
|
||
# 这里直接使用现有的fx字段,不重复计算
|
||
|
||
# ===== 2.2 价格关系因子 =====
|
||
|
||
# 价格与均线的关系 (从KLU中获取)
|
||
klu_features = self.cal_klu_features()
|
||
|
||
# MA5与收盘价的关系
|
||
if 'klu_ma5' in klu_features and klu_features['klu_ma5'] != 0:
|
||
features['klc_close_to_ma5'] = (self.close - klu_features['klu_ma5']) / klu_features['klu_ma5']
|
||
else:
|
||
features['klc_close_to_ma5'] = 0
|
||
|
||
# MA10与收盘价的关系
|
||
if 'klu_ma10' in klu_features and klu_features['klu_ma10'] != 0:
|
||
features['klc_close_to_ma10'] = (self.close - klu_features['klu_ma10']) / klu_features['klu_ma10']
|
||
else:
|
||
features['klc_close_to_ma10'] = 0
|
||
|
||
# MA30与收盘价的关系
|
||
if 'klu_ma30' in klu_features and klu_features['klu_ma30'] != 0:
|
||
features['klc_close_to_ma30'] = (self.close - klu_features['klu_ma30']) / klu_features['klu_ma30']
|
||
else:
|
||
features['klc_close_to_ma30'] = 0
|
||
|
||
# 短期均线与长期均线的差异
|
||
if 'klu_ma5' in klu_features and 'klu_ma30' in klu_features and klu_features['klu_ma30'] != 0:
|
||
features['klc_ma_diff'] = (klu_features['klu_ma5'] - klu_features['klu_ma30']) / klu_features['klu_ma30']
|
||
else:
|
||
features['klc_ma_diff'] = 0
|
||
|
||
# 价格突破特征
|
||
# 检查当前K线是否突破前3根K线的最高/最低价
|
||
if self.pre:
|
||
max_high = self.pre.high
|
||
min_low = self.pre.low
|
||
|
||
temp = self.pre
|
||
count = 1
|
||
while temp.pre and count < 3:
|
||
temp = temp.pre
|
||
max_high = max(max_high, temp.high)
|
||
min_low = min(min_low, temp.low)
|
||
count += 1
|
||
|
||
features['klc_break_high'] = 1 if self.high > max_high else 0
|
||
features['klc_break_low'] = 1 if self.low < min_low else 0
|
||
else:
|
||
features['klc_break_high'] = 0
|
||
features['klc_break_low'] = 0
|
||
|
||
# ===== 2.3 技术指标因子 =====
|
||
|
||
# 获取技术指标
|
||
# RSI (从KLU中获取)
|
||
if 'klu_rsi' in klu_features:
|
||
features['klc_rsi'] = klu_features['klu_rsi']
|
||
else:
|
||
features['klc_rsi'] = 50 # 默认中性值
|
||
|
||
# MACD (从KLU中获取)
|
||
if 'klu_macd' in klu_features:
|
||
features['klc_macd'] = klu_features['klu_macd']
|
||
else:
|
||
features['klc_macd'] = 0
|
||
|
||
if 'klu_signal' in klu_features:
|
||
features['klc_macd_signal'] = klu_features['klu_signal']
|
||
else:
|
||
features['klc_macd_signal'] = 0
|
||
|
||
if 'klu_macdhist' in klu_features:
|
||
features['klc_macdhist'] = klu_features['klu_macdhist']
|
||
else:
|
||
features['klc_macdhist'] = 0
|
||
|
||
# 成交量变化
|
||
if self.pre:
|
||
vol_sum = 0
|
||
count = 0
|
||
temp = self.pre
|
||
|
||
# 计算前5根K线的平均成交量
|
||
while temp and count < 5:
|
||
vol_sum += temp.volume
|
||
count += 1
|
||
temp = temp.pre
|
||
|
||
avg_vol = vol_sum / count if count > 0 else self.volume
|
||
|
||
if avg_vol != 0: # 避免除以零
|
||
features['klc_vol_ratio'] = self.volume / avg_vol
|
||
else:
|
||
features['klc_vol_ratio'] = 1
|
||
else:
|
||
features['klc_vol_ratio'] = 1
|
||
|
||
# ===== 2.4 市场环境因子 =====
|
||
|
||
# 价格波动率 (前5根K线收盘价的标准差)
|
||
if self.pre:
|
||
close_vals = [self.close]
|
||
temp = self.pre
|
||
count = 0
|
||
|
||
while temp and count < 5:
|
||
close_vals.append(temp.close)
|
||
count += 1
|
||
temp = temp.pre
|
||
|
||
if len(close_vals) > 1:
|
||
import numpy as np
|
||
std_dev = np.std(close_vals)
|
||
avg_close = np.mean(close_vals)
|
||
|
||
if avg_close != 0: # 避免除以零
|
||
features['klc_volatility'] = std_dev / avg_close
|
||
else:
|
||
features['klc_volatility'] = 0
|
||
else:
|
||
features['klc_volatility'] = 0
|
||
else:
|
||
features['klc_volatility'] = 0
|
||
|
||
# 前5根K线的价格趋势 (简单线性回归斜率)
|
||
if self.pre:
|
||
price_vals = [self.close]
|
||
temp = self.pre
|
||
count = 0
|
||
|
||
while temp and count < 5:
|
||
price_vals.append(temp.close)
|
||
count += 1
|
||
temp = temp.pre
|
||
|
||
if len(price_vals) > 2:
|
||
import numpy as np
|
||
y = np.array(price_vals)
|
||
x = np.arange(len(y))
|
||
|
||
# 简单线性回归
|
||
slope = np.polyfit(x, y, 1)[0]
|
||
|
||
# 归一化斜率
|
||
if abs(np.mean(y)) > 0: # 避免除以零
|
||
features['klc_trend_slope'] = slope / abs(np.mean(y))
|
||
else:
|
||
features['klc_trend_slope'] = 0
|
||
else:
|
||
features['klc_trend_slope'] = 0
|
||
else:
|
||
features['klc_trend_slope'] = 0
|
||
|
||
# ===== 2.5 其他衍生因子 =====
|
||
|
||
# K线组合形态
|
||
# 十字星 (实体非常小)
|
||
body_pct = abs(self.close - self.open) / (self.high - self.low) if (self.high - self.low) > 0 else 0
|
||
features['klc_is_doji'] = 1 if body_pct < 0.1 else 0 # 实体小于10%算十字星
|
||
|
||
# 锤子线/上吊线 (下影线长,上影线短,实体小)
|
||
if high_low_range > 0:
|
||
lower_shadow_pct = (min_oc - self.low) / high_low_range
|
||
upper_shadow_pct = (self.high - max_oc) / high_low_range
|
||
features['klc_is_hammer'] = 1 if (lower_shadow_pct > 0.6 and upper_shadow_pct < 0.1) else 0
|
||
else:
|
||
features['klc_is_hammer'] = 0
|
||
|
||
# 吞没形态
|
||
if self.pre:
|
||
prev_body_size = abs(self.pre.close - self.pre.open)
|
||
curr_body_size = abs(self.close - self.open)
|
||
|
||
# 看涨吞没
|
||
if (self.pre.close < self.pre.open # 前一根是阴线
|
||
and self.close > self.open # 当前是阳线
|
||
and self.open <= self.pre.close # 当前开盘低于前收盘
|
||
and self.close >= self.pre.open # 当前收盘高于前开盘
|
||
and curr_body_size > prev_body_size): # 当前实体大于前实体
|
||
features['klc_is_bullish_engulfing'] = 1
|
||
else:
|
||
features['klc_is_bullish_engulfing'] = 0
|
||
|
||
# 看跌吞没
|
||
if (self.pre.close > self.pre.open # 前一根是阳线
|
||
and self.close < self.open # 当前是阴线
|
||
and self.open >= self.pre.close # 当前开盘高于前收盘
|
||
and self.close <= self.pre.open # 当前收盘低于前开盘
|
||
and curr_body_size > prev_body_size): # 当前实体大于前实体
|
||
features['klc_is_bearish_engulfing'] = 1
|
||
else:
|
||
features['klc_is_bearish_engulfing'] = 0
|
||
else:
|
||
features['klc_is_bullish_engulfing'] = 0
|
||
features['klc_is_bearish_engulfing'] = 0
|
||
|
||
# 包含关系
|
||
if self.pre:
|
||
# 向上包含
|
||
if (self.high >= self.pre.high and self.low >= self.pre.low):
|
||
features['klc_is_up_inclusive'] = 1
|
||
else:
|
||
features['klc_is_up_inclusive'] = 0
|
||
|
||
# 向下包含
|
||
if (self.high <= self.pre.high and self.low <= self.pre.low):
|
||
features['klc_is_down_inclusive'] = 1
|
||
else:
|
||
features['klc_is_down_inclusive'] = 0
|
||
|
||
# 完全包含
|
||
if (self.high >= self.pre.high and self.low <= self.pre.low):
|
||
features['klc_is_full_inclusive'] = 1
|
||
else:
|
||
features['klc_is_full_inclusive'] = 0
|
||
|
||
# 被完全包含
|
||
if (self.high <= self.pre.high and self.low >= self.pre.low):
|
||
features['klc_is_inner_inclusive'] = 1
|
||
else:
|
||
features['klc_is_inner_inclusive'] = 0
|
||
else:
|
||
features['klc_is_up_inclusive'] = 0
|
||
features['klc_is_down_inclusive'] = 0
|
||
features['klc_is_full_inclusive'] = 0
|
||
features['klc_is_inner_inclusive'] = 0
|
||
|
||
# 从KLU获取其他特征
|
||
#features.update(self.cal_klu_features())
|
||
|
||
# ===== 3.1 价格形态扩展因子 =====
|
||
|
||
# 区间突破强度
|
||
if self.pre and self.pre.pre:
|
||
prev_range = self.pre.high - self.pre.low
|
||
if prev_range > 0:
|
||
features['klc_breakout_strength'] = (self.close - self.pre.high) / prev_range if self.close > self.pre.high else (self.pre.low - self.close) / prev_range if self.close < self.pre.low else 0
|
||
else:
|
||
features['klc_breakout_strength'] = 0
|
||
else:
|
||
features['klc_breakout_strength'] = 0
|
||
|
||
# 价格动量
|
||
if self.pre:
|
||
features['klc_momentum_1'] = self.close - self.pre.close
|
||
if self.pre.pre:
|
||
features['klc_momentum_2'] = self.close - self.pre.pre.close
|
||
else:
|
||
features['klc_momentum_2'] = 0
|
||
else:
|
||
features['klc_momentum_1'] = 0
|
||
features['klc_momentum_2'] = 0
|
||
|
||
# 价格加速度
|
||
if self.pre and self.pre.pre:
|
||
prev_change = self.pre.close - self.pre.pre.close
|
||
curr_change = self.close - self.pre.close
|
||
features['klc_price_acceleration'] = curr_change - prev_change
|
||
else:
|
||
features['klc_price_acceleration'] = 0
|
||
|
||
# 相对位置
|
||
if self.high != self.low:
|
||
features['klc_relative_position'] = (self.close - self.low) / (self.high - self.low)
|
||
else:
|
||
features['klc_relative_position'] = 0.5
|
||
|
||
# 价格区间位置 (前N根K线)
|
||
prev_klcs = []
|
||
temp = self.pre
|
||
for _ in range(10): # 前10根K线
|
||
if temp:
|
||
prev_klcs.append(temp)
|
||
temp = temp.pre
|
||
else:
|
||
break
|
||
|
||
if prev_klcs:
|
||
max_high = max([klc.high for klc in prev_klcs]) if prev_klcs else self.high
|
||
min_low = min([klc.low for klc in prev_klcs]) if prev_klcs else self.low
|
||
price_range = max_high - min_low
|
||
|
||
if price_range > 0:
|
||
features['klc_range_position'] = (self.close - min_low) / price_range
|
||
else:
|
||
features['klc_range_position'] = 0.5
|
||
else:
|
||
features['klc_range_position'] = 0.5
|
||
|
||
# ===== 3.2 更多技术指标因子 =====
|
||
|
||
# MACD趋势
|
||
if self.pre and 'klc_macdhist' in features:
|
||
features['klc_macdhist_change'] = features['klc_macdhist'] - self.pre.macdhist
|
||
else:
|
||
features['klc_macdhist_change'] = 0
|
||
|
||
# RSI趋势
|
||
if self.pre and 'klc_rsi' in features:
|
||
features['klc_rsi_change'] = features['klc_rsi'] - self.pre.rsi
|
||
else:
|
||
features['klc_rsi_change'] = 0
|
||
|
||
# RSI超买超卖
|
||
if 'klc_rsi' in features:
|
||
features['klc_rsi_overbought'] = 1 if features['klc_rsi'] > 70 else 0
|
||
features['klc_rsi_oversold'] = 1 if features['klc_rsi'] < 30 else 0
|
||
else:
|
||
features['klc_rsi_overbought'] = 0
|
||
features['klc_rsi_oversold'] = 0
|
||
|
||
# 布林带位置 (如果可从KLU获取)
|
||
if 'klu_upper_band' in klu_features and 'klu_lower_band' in klu_features:
|
||
upper_band = klu_features['klu_upper_band']
|
||
lower_band = klu_features['klu_lower_band']
|
||
middle_band = klu_features['klu_middle_band'] if 'klu_middle_band' in klu_features else (upper_band + lower_band) / 2
|
||
|
||
band_width = upper_band - lower_band
|
||
|
||
if band_width > 0:
|
||
features['klc_bollinger_position'] = (self.close - lower_band) / band_width
|
||
else:
|
||
features['klc_bollinger_position'] = 0.5
|
||
|
||
features['klc_bollinger_width'] = band_width / middle_band if middle_band > 0 else 0
|
||
features['klc_upper_band_touch'] = 1 if self.high >= upper_band else 0
|
||
features['klc_lower_band_touch'] = 1 if self.low <= lower_band else 0
|
||
else:
|
||
features['klc_bollinger_position'] = 0.5
|
||
features['klc_bollinger_width'] = 0
|
||
features['klc_upper_band_touch'] = 0
|
||
features['klc_lower_band_touch'] = 0
|
||
|
||
# 量价关系
|
||
if self.pre:
|
||
price_change = self.close - self.pre.close
|
||
if price_change != 0:
|
||
features['klc_volume_price_ratio'] = self.volume / abs(price_change)
|
||
else:
|
||
features['klc_volume_price_ratio'] = 0
|
||
else:
|
||
features['klc_volume_price_ratio'] = 0
|
||
|
||
# ===== 3.3 波动性因子 =====
|
||
|
||
# 真实波动幅度 (True Range)
|
||
if self.pre:
|
||
tr1 = self.high - self.low
|
||
tr2 = abs(self.high - self.pre.close)
|
||
tr3 = abs(self.low - self.pre.close)
|
||
features['klc_true_range'] = max(tr1, tr2, tr3)
|
||
else:
|
||
features['klc_true_range'] = self.high - self.low
|
||
|
||
# 归一化真实波动幅度
|
||
if self.pre and self.pre.close > 0:
|
||
features['klc_normalized_tr'] = features['klc_true_range'] / self.pre.close
|
||
else:
|
||
features['klc_normalized_tr'] = 0
|
||
|
||
# 滑动窗口波动率
|
||
if prev_klcs:
|
||
tr_values = []
|
||
|
||
for i in range(len(prev_klcs)):
|
||
if i == 0:
|
||
tr = max(prev_klcs[i].high - prev_klcs[i].low,
|
||
abs(prev_klcs[i].high - self.close),
|
||
abs(prev_klcs[i].low - self.close))
|
||
else:
|
||
tr = max(prev_klcs[i].high - prev_klcs[i].low,
|
||
abs(prev_klcs[i].high - prev_klcs[i-1].close),
|
||
abs(prev_klcs[i].low - prev_klcs[i-1].close))
|
||
tr_values.append(tr)
|
||
|
||
if tr_values:
|
||
import numpy as np
|
||
# ATR (Average True Range)
|
||
features['klc_atr'] = np.mean(tr_values)
|
||
if self.close > 0:
|
||
features['klc_atr_percent'] = features['klc_atr'] / self.close
|
||
else:
|
||
features['klc_atr_percent'] = 0
|
||
|
||
# 高低点波动
|
||
if len(prev_klcs) >= 5:
|
||
highs = [klc.high for klc in prev_klcs[:5]]
|
||
lows = [klc.low for klc in prev_klcs[:5]]
|
||
|
||
max_high = max(highs)
|
||
min_low = min(lows)
|
||
|
||
features['klc_high_volatility'] = np.std(highs) / np.mean(highs) if np.mean(highs) > 0 else 0
|
||
features['klc_low_volatility'] = np.std(lows) / np.mean(lows) if np.mean(lows) > 0 else 0
|
||
features['klc_price_range'] = (max_high - min_low) / min_low if min_low > 0 else 0
|
||
else:
|
||
features['klc_high_volatility'] = 0
|
||
features['klc_low_volatility'] = 0
|
||
features['klc_price_range'] = 0
|
||
else:
|
||
features['klc_atr'] = 0
|
||
features['klc_atr_percent'] = 0
|
||
features['klc_high_volatility'] = 0
|
||
features['klc_low_volatility'] = 0
|
||
features['klc_price_range'] = 0
|
||
else:
|
||
features['klc_atr'] = 0
|
||
features['klc_atr_percent'] = 0
|
||
features['klc_high_volatility'] = 0
|
||
features['klc_low_volatility'] = 0
|
||
features['klc_price_range'] = 0
|
||
|
||
# ===== 3.4 趋势强度因子 =====
|
||
|
||
# 方向移动指标
|
||
if self.pre:
|
||
# 上升动量和下降动量
|
||
up_move = self.high - self.pre.high
|
||
down_move = self.pre.low - self.low
|
||
|
||
features['klc_plus_dm'] = up_move if up_move > down_move and up_move > 0 else 0
|
||
features['klc_minus_dm'] = down_move if down_move > up_move and down_move > 0 else 0
|
||
|
||
# 方向指数
|
||
if features['klc_atr'] > 0:
|
||
features['klc_plus_di'] = 100 * features['klc_plus_dm'] / features['klc_atr']
|
||
features['klc_minus_di'] = 100 * features['klc_minus_dm'] / features['klc_atr']
|
||
else:
|
||
features['klc_plus_di'] = 0
|
||
features['klc_minus_di'] = 0
|
||
|
||
# 方向指数差
|
||
features['klc_dx'] = 100 * abs(features['klc_plus_di'] - features['klc_minus_di']) / (features['klc_plus_di'] + features['klc_minus_di']) if (features['klc_plus_di'] + features['klc_minus_di']) > 0 else 0
|
||
else:
|
||
features['klc_plus_dm'] = 0
|
||
features['klc_minus_dm'] = 0
|
||
features['klc_plus_di'] = 0
|
||
features['klc_minus_di'] = 0
|
||
features['klc_dx'] = 0
|
||
|
||
# 价格趋势强度
|
||
if prev_klcs and len(prev_klcs) >= 5:
|
||
import numpy as np
|
||
prices = [self.close] + [klc.close for klc in prev_klcs[:5]]
|
||
x = np.arange(len(prices))
|
||
|
||
# 线性回归
|
||
slope, intercept = np.polyfit(x, prices, 1)
|
||
|
||
# 趋势线拟合度 (R^2)
|
||
y_pred = slope * x + intercept
|
||
ss_total = np.sum((prices - np.mean(prices)) ** 2)
|
||
ss_residual = np.sum((prices - y_pred) ** 2)
|
||
|
||
if ss_total > 0:
|
||
features['klc_trend_r2'] = 1 - (ss_residual / ss_total)
|
||
else:
|
||
features['klc_trend_r2'] = 0
|
||
|
||
# 趋势线斜率
|
||
features['klc_trend_slope_norm'] = slope / np.mean(prices) if np.mean(prices) > 0 else 0
|
||
|
||
# 价格与趋势线的距离
|
||
current_trend_value = slope * 0 + intercept # x=0 表示当前K线在预测线上的值
|
||
if current_trend_value > 0:
|
||
features['klc_trend_distance'] = (self.close - current_trend_value) / current_trend_value
|
||
else:
|
||
features['klc_trend_distance'] = 0
|
||
else:
|
||
features['klc_trend_r2'] = 0
|
||
features['klc_trend_slope_norm'] = 0
|
||
features['klc_trend_distance'] = 0
|
||
|
||
# ===== 3.5 支撑与阻力因子 =====
|
||
|
||
# 前N根K线的支撑和阻力
|
||
if prev_klcs and len(prev_klcs) >= 5:
|
||
highs = [klc.high for klc in prev_klcs[:5]]
|
||
lows = [klc.low for klc in prev_klcs[:5]]
|
||
|
||
# 简单支撑位 (前5根K线最低点)
|
||
support = min(lows)
|
||
# 简单阻力位 (前5根K线最高点)
|
||
resistance = max(highs)
|
||
|
||
# 与支撑阻力的距离
|
||
if support > 0:
|
||
features['klc_distance_to_support'] = (self.close - support) / support
|
||
else:
|
||
features['klc_distance_to_support'] = 0
|
||
|
||
if resistance > 0:
|
||
features['klc_distance_to_resistance'] = (resistance - self.close) / resistance
|
||
else:
|
||
features['klc_distance_to_resistance'] = 0
|
||
|
||
# 支撑阻力突破
|
||
features['klc_breaks_support'] = 1 if self.low < support else 0
|
||
features['klc_breaks_resistance'] = 1 if self.high > resistance else 0
|
||
|
||
# 支撑阻力区间位置
|
||
if resistance > support:
|
||
features['klc_sr_position'] = (self.close - support) / (resistance - support)
|
||
else:
|
||
features['klc_sr_position'] = 0.5
|
||
else:
|
||
features['klc_distance_to_support'] = 0
|
||
features['klc_distance_to_resistance'] = 0
|
||
features['klc_breaks_support'] = 0
|
||
features['klc_breaks_resistance'] = 0
|
||
features['klc_sr_position'] = 0.5
|
||
|
||
# ===== 3.6 量价关系扩展因子 =====
|
||
|
||
# 价格与成交量的相关性
|
||
if prev_klcs and len(prev_klcs) >= 5:
|
||
import numpy as np
|
||
prices = [self.close] + [klc.close for klc in prev_klcs[:5]]
|
||
volumes = [self.volume] + [klc.volume for klc in prev_klcs[:5]]
|
||
|
||
# 计算相关系数
|
||
if len(prices) > 1 and np.std(prices) > 0 and np.std(volumes) > 0:
|
||
price_mean = np.mean(prices)
|
||
volume_mean = np.mean(volumes)
|
||
|
||
numerator = np.sum((prices - price_mean) * (volumes - volume_mean))
|
||
denominator = np.sqrt(np.sum((prices - price_mean) ** 2) * np.sum((volumes - volume_mean) ** 2))
|
||
|
||
if denominator > 0:
|
||
features['klc_price_volume_corr'] = numerator / denominator
|
||
else:
|
||
features['klc_price_volume_corr'] = 0
|
||
else:
|
||
features['klc_price_volume_corr'] = 0
|
||
|
||
# 价格上涨时的平均成交量
|
||
up_prices = []
|
||
up_volumes = []
|
||
|
||
# 价格下跌时的平均成交量
|
||
down_prices = []
|
||
down_volumes = []
|
||
|
||
for i in range(len(prev_klcs)):
|
||
if i < len(prev_klcs) - 1:
|
||
if prev_klcs[i].close > prev_klcs[i+1].close:
|
||
up_prices.append(prev_klcs[i].close)
|
||
up_volumes.append(prev_klcs[i].volume)
|
||
else:
|
||
down_prices.append(prev_klcs[i].close)
|
||
down_volumes.append(prev_klcs[i].volume)
|
||
|
||
features['klc_up_volume_avg'] = np.mean(up_volumes) if up_volumes else 0
|
||
features['klc_down_volume_avg'] = np.mean(down_volumes) if down_volumes else 0
|
||
|
||
if features['klc_down_volume_avg'] > 0:
|
||
features['klc_volume_ratio_up_down'] = features['klc_up_volume_avg'] / features['klc_down_volume_avg']
|
||
else:
|
||
features['klc_volume_ratio_up_down'] = 1
|
||
else:
|
||
features['klc_price_volume_corr'] = 0
|
||
features['klc_up_volume_avg'] = 0
|
||
features['klc_down_volume_avg'] = 0
|
||
features['klc_volume_ratio_up_down'] = 1
|
||
|
||
# 成交量变化率
|
||
if self.pre:
|
||
if self.pre.volume > 0:
|
||
features['klc_volume_change'] = (self.volume - self.pre.volume) / self.pre.volume
|
||
else:
|
||
features['klc_volume_change'] = 0
|
||
else:
|
||
features['klc_volume_change'] = 0
|
||
|
||
# 量能扩散
|
||
if prev_klcs and len(prev_klcs) >= 5:
|
||
avg_volume = np.mean([klc.volume for klc in prev_klcs[:5]])
|
||
if avg_volume > 0:
|
||
features['klc_volume_expansion'] = self.volume / avg_volume
|
||
else:
|
||
features['klc_volume_expansion'] = 1
|
||
else:
|
||
features['klc_volume_expansion'] = 1
|
||
|
||
# ===== 3.7 K线时序模式因子 =====
|
||
|
||
# 连续上涨/下跌计数
|
||
up_count = 0
|
||
down_count = 0
|
||
|
||
if prev_klcs:
|
||
temp = self
|
||
last_close = temp.close
|
||
|
||
for klc in prev_klcs:
|
||
if klc.close < last_close:
|
||
up_count += 1
|
||
down_count = 0
|
||
elif klc.close > last_close:
|
||
down_count += 1
|
||
up_count = 0
|
||
last_close = klc.close
|
||
|
||
features['klc_consecutive_up'] = up_count
|
||
features['klc_consecutive_down'] = down_count
|
||
else:
|
||
features['klc_consecutive_up'] = 0
|
||
features['klc_consecutive_down'] = 0
|
||
|
||
# 跳空缺口
|
||
if self.pre:
|
||
features['klc_gap_up'] = self.low - self.pre.high if self.low > self.pre.high else 0
|
||
features['klc_gap_down'] = self.pre.low - self.high if self.high < self.pre.low else 0
|
||
|
||
# 归一化缺口大小
|
||
if self.pre.close > 0:
|
||
features['klc_gap_up_pct'] = features['klc_gap_up'] / self.pre.close
|
||
features['klc_gap_down_pct'] = features['klc_gap_down'] / self.pre.close
|
||
else:
|
||
features['klc_gap_up_pct'] = 0
|
||
features['klc_gap_down_pct'] = 0
|
||
else:
|
||
features['klc_gap_up'] = 0
|
||
features['klc_gap_down'] = 0
|
||
features['klc_gap_up_pct'] = 0
|
||
features['klc_gap_down_pct'] = 0
|
||
|
||
# 价格回撤
|
||
if prev_klcs:
|
||
max_price = self.close
|
||
min_price = self.close
|
||
|
||
for klc in prev_klcs[:5]:
|
||
max_price = max(max_price, klc.close)
|
||
min_price = min(min_price, klc.close)
|
||
|
||
if max_price > 0:
|
||
features['klc_drawdown'] = (max_price - self.close) / max_price
|
||
else:
|
||
features['klc_drawdown'] = 0
|
||
|
||
if min_price > 0:
|
||
features['klc_pullback'] = (self.close - min_price) / min_price
|
||
else:
|
||
features['klc_pullback'] = 0
|
||
else:
|
||
features['klc_drawdown'] = 0
|
||
features['klc_pullback'] = 0
|
||
|
||
# ===== 3.8 复杂形态识别因子 =====
|
||
|
||
# 双顶/双底形态
|
||
if self.pre and self.pre.pre and self.pre.pre.pre and self.pre.pre.pre.pre:
|
||
p5 = self.pre.pre.pre.pre
|
||
p4 = self.pre.pre.pre
|
||
p3 = self.pre.pre
|
||
p2 = self.pre
|
||
p1 = self
|
||
|
||
# 双顶检测 (M形)
|
||
double_top = (p5.high < p4.high and p4.high > p3.high and
|
||
p3.high < p2.high and p2.high > p1.high and
|
||
abs(p4.high - p2.high) / p4.high < 0.03) # 两个顶的高度接近
|
||
|
||
# 双底检测 (W形)
|
||
double_bottom = (p5.low > p4.low and p4.low < p3.low and
|
||
p3.low > p2.low and p2.low < p1.low and
|
||
abs(p4.low - p2.low) / p4.low < 0.03) # 两个底的低点接近
|
||
|
||
features['klc_double_top'] = 1 if double_top else 0
|
||
features['klc_double_bottom'] = 1 if double_bottom else 0
|
||
else:
|
||
features['klc_double_top'] = 0
|
||
features['klc_double_bottom'] = 0
|
||
|
||
# 头肩顶/底形态
|
||
if self.pre and self.pre.pre and self.pre.pre.pre and self.pre.pre.pre.pre and self.pre.pre.pre.pre.pre:
|
||
p7 = self.pre.pre.pre.pre.pre
|
||
p6 = self.pre.pre.pre.pre
|
||
p5 = self.pre.pre.pre
|
||
p4 = self.pre.pre
|
||
p3 = self.pre
|
||
p2 = self
|
||
|
||
# 头肩顶 (左肩-头-右肩)
|
||
head_shoulders_top = (p7.high < p6.high and p6.high > p5.high and
|
||
p5.high < p4.high and p4.high > p3.high and
|
||
p3.high < p2.high and
|
||
abs(p6.high - p2.high) / p6.high < 0.05 and # 左肩和右肩高度接近
|
||
p4.high > p6.high and p4.high > p2.high) # 头部高于肩部
|
||
|
||
# 头肩底 (左肩-头-右肩)
|
||
head_shoulders_bottom = (p7.low > p6.low and p6.low < p5.low and
|
||
p5.low > p4.low and p4.low < p3.low and
|
||
p3.low > p2.low and
|
||
abs(p6.low - p2.low) / p6.low < 0.05 and # 左肩和右肩低点接近
|
||
p4.low < p6.low and p4.low < p2.low) # 头部低于肩部
|
||
|
||
features['klc_head_shoulders_top'] = 1 if head_shoulders_top else 0
|
||
features['klc_head_shoulders_bottom'] = 1 if head_shoulders_bottom else 0
|
||
else:
|
||
features['klc_head_shoulders_top'] = 0
|
||
features['klc_head_shoulders_bottom'] = 0
|
||
|
||
# 旗形/三角形
|
||
if prev_klcs and len(prev_klcs) >= 5:
|
||
import numpy as np
|
||
|
||
highs = [self.high] + [klc.high for klc in prev_klcs[:5]]
|
||
lows = [self.low] + [klc.low for klc in prev_klcs[:5]]
|
||
|
||
# 计算高点趋势线斜率
|
||
x = np.arange(len(highs))
|
||
high_slope, _ = np.polyfit(x, highs, 1)
|
||
|
||
# 计算低点趋势线斜率
|
||
low_slope, _ = np.polyfit(x, lows, 1)
|
||
|
||
# 旗形: 高点和低点趋势线平行且方向相同
|
||
if abs(high_slope - low_slope) / (abs(high_slope) + 1e-10) < 0.2:
|
||
features['klc_flag_pattern'] = 1
|
||
else:
|
||
features['klc_flag_pattern'] = 0
|
||
|
||
# 上升三角形: 高点趋势线水平,低点趋势线向上
|
||
if abs(high_slope) < 0.01 and low_slope > 0.01:
|
||
features['klc_ascending_triangle'] = 1
|
||
else:
|
||
features['klc_ascending_triangle'] = 0
|
||
|
||
# 下降三角形: 高点趋势线向下,低点趋势线水平
|
||
if high_slope < -0.01 and abs(low_slope) < 0.01:
|
||
features['klc_descending_triangle'] = 1
|
||
else:
|
||
features['klc_descending_triangle'] = 0
|
||
|
||
# 对称三角形: 高点趋势线向下,低点趋势线向上
|
||
if high_slope < -0.01 and low_slope > 0.01:
|
||
features['klc_symmetric_triangle'] = 1
|
||
else:
|
||
features['klc_symmetric_triangle'] = 0
|
||
else:
|
||
features['klc_flag_pattern'] = 0
|
||
features['klc_ascending_triangle'] = 0
|
||
features['klc_descending_triangle'] = 0
|
||
features['klc_symmetric_triangle'] = 0
|
||
|
||
# ===== 3.9 微观结构因子 =====
|
||
|
||
# 价格动量加速度
|
||
if self.pre and self.pre.pre and self.pre.pre.pre:
|
||
mom1 = self.close - self.pre.close
|
||
mom2 = self.pre.close - self.pre.pre.close
|
||
mom3 = self.pre.pre.close - self.pre.pre.pre.close
|
||
|
||
# 一阶动量变化
|
||
features['klc_mom_change_1'] = mom1 - mom2
|
||
|
||
# 二阶动量变化
|
||
features['klc_mom_change_2'] = (mom1 - mom2) - (mom2 - mom3)
|
||
|
||
# 动量方向变化
|
||
features['klc_mom_direction_change'] = 1 if (mom1 > 0 and mom2 < 0) or (mom1 < 0 and mom2 > 0) else 0
|
||
else:
|
||
features['klc_mom_change_1'] = 0
|
||
features['klc_mom_change_2'] = 0
|
||
features['klc_mom_direction_change'] = 0
|
||
|
||
# 微观价格结构分析
|
||
if self.pre:
|
||
# K线重叠程度
|
||
overlap_range = min(self.high, self.pre.high) - max(self.low, self.pre.low)
|
||
total_range = max(self.high, self.pre.high) - min(self.low, self.pre.low)
|
||
|
||
if total_range > 0:
|
||
features['klc_overlap_ratio'] = max(0, overlap_range) / total_range
|
||
else:
|
||
features['klc_overlap_ratio'] = 0
|
||
|
||
# 收盘价在当前K线的相对位置
|
||
if self.high > self.low:
|
||
features['klc_close_position_inbar'] = (self.close - self.low) / (self.high - self.low)
|
||
else:
|
||
features['klc_close_position_inbar'] = 0.5
|
||
|
||
# 当前K线相对于前一根K线的位置
|
||
if self.pre.high > self.pre.low:
|
||
features['klc_rel_position_to_prev'] = (self.close - self.pre.low) / (self.pre.high - self.pre.low)
|
||
else:
|
||
features['klc_rel_position_to_prev'] = 0.5
|
||
else:
|
||
features['klc_overlap_ratio'] = 0
|
||
features['klc_close_position_inbar'] = 0.5
|
||
features['klc_rel_position_to_prev'] = 0.5
|
||
|
||
# 价格变化率序列
|
||
if prev_klcs and len(prev_klcs) >= 3:
|
||
ret1 = self.close / prev_klcs[0].close - 1 if prev_klcs[0].close > 0 else 0
|
||
ret2 = prev_klcs[0].close / prev_klcs[1].close - 1 if prev_klcs[1].close > 0 else 0
|
||
ret3 = prev_klcs[1].close / prev_klcs[2].close - 1 if prev_klcs[2].close > 0 else 0
|
||
|
||
features['klc_return_1'] = ret1
|
||
features['klc_return_2'] = ret2
|
||
features['klc_return_3'] = ret3
|
||
|
||
# 收益率加速度
|
||
features['klc_return_accel_1'] = ret1 - ret2
|
||
features['klc_return_accel_2'] = (ret1 - ret2) - (ret2 - ret3)
|
||
else:
|
||
features['klc_return_1'] = 0
|
||
features['klc_return_2'] = 0
|
||
features['klc_return_3'] = 0
|
||
features['klc_return_accel_1'] = 0
|
||
features['klc_return_accel_2'] = 0
|
||
|
||
# ===== 3.10 综合形态因子 =====
|
||
|
||
# 能量比率 (K线实体与影线比例)
|
||
body_size = abs(self.close - self.open)
|
||
if self.high > self.low:
|
||
upper_shadow = self.high - max(self.open, self.close)
|
||
lower_shadow = min(self.open, self.close) - self.low
|
||
|
||
features['klc_upper_shadow_ratio'] = upper_shadow / (self.high - self.low)
|
||
features['klc_lower_shadow_ratio'] = lower_shadow / (self.high - self.low)
|
||
features['klc_body_to_range_ratio'] = body_size / (self.high - self.low)
|
||
else:
|
||
features['klc_upper_shadow_ratio'] = 0
|
||
features['klc_lower_shadow_ratio'] = 0
|
||
features['klc_body_to_range_ratio'] = 1
|
||
|
||
# K线平衡点
|
||
features['klc_balance_point'] = (self.high + self.low + self.close) / 3
|
||
|
||
# 与平衡点的距离
|
||
if features['klc_balance_point'] > 0:
|
||
features['klc_distance_to_balance'] = (self.close - features['klc_balance_point']) / features['klc_balance_point']
|
||
else:
|
||
features['klc_distance_to_balance'] = 0
|
||
|
||
# 波动性和趋势组合因子
|
||
if 'klc_volatility' in features and 'klc_trend_slope_norm' in features:
|
||
features['klc_volatility_trend_ratio'] = features['klc_volatility'] / (abs(features['klc_trend_slope_norm']) + 1e-10)
|
||
else:
|
||
features['klc_volatility_trend_ratio'] = 0
|
||
|
||
# K线逆转形态
|
||
if self.pre:
|
||
# 看涨逆转 (前一根阴线,当前阳线,且当前收盘高于前一根中点)
|
||
bullish_reversal = (self.pre.close < self.pre.open and # 前一根阴线
|
||
self.close > self.open and # 当前阳线
|
||
self.close > (self.pre.high + self.pre.low) / 2) # 收盘价高于前一根中点
|
||
|
||
# 看跌逆转 (前一根阳线,当前阴线,且当前收盘低于前一根中点)
|
||
bearish_reversal = (self.pre.close > self.pre.open and # 前一根阳线
|
||
self.close < self.open and # 当前阴线
|
||
self.close < (self.pre.high + self.pre.low) / 2) # 收盘价低于前一根中点
|
||
|
||
features['klc_bullish_reversal'] = 1 if bullish_reversal else 0
|
||
features['klc_bearish_reversal'] = 1 if bearish_reversal else 0
|
||
else:
|
||
features['klc_bullish_reversal'] = 0
|
||
features['klc_bearish_reversal'] = 0
|
||
|
||
# 特殊K线形态
|
||
# 大阳线/大阴线
|
||
avg_body = 0
|
||
if prev_klcs and len(prev_klcs) >= 5:
|
||
bodies = [abs(klc.close - klc.open) for klc in prev_klcs[:5]]
|
||
avg_body = sum(bodies) / len(bodies) if bodies else 0
|
||
|
||
if avg_body > 0:
|
||
features['klc_large_candle'] = body_size / avg_body
|
||
else:
|
||
features['klc_large_candle'] = 1
|
||
|
||
# 长上影线/长下影线
|
||
if self.high > self.low:
|
||
upper_shadow_ratio = (self.high - max(self.open, self.close)) / (self.high - self.low)
|
||
lower_shadow_ratio = (min(self.open, self.close) - self.low) / (self.high - self.low)
|
||
|
||
features['klc_long_upper_shadow'] = 1 if upper_shadow_ratio > 0.6 else 0
|
||
features['klc_long_lower_shadow'] = 1 if lower_shadow_ratio > 0.6 else 0
|
||
else:
|
||
features['klc_long_upper_shadow'] = 0
|
||
features['klc_long_lower_shadow'] = 0
|
||
|
||
# 星线形态 (当前K线实体小,且与前一根K线有缺口)
|
||
if self.pre and (self.high - self.low) > 0:
|
||
small_body = body_size / (self.high - self.low) < 0.3
|
||
gap_with_prev = (min(self.open, self.close) > self.pre.close) if self.pre.close > self.pre.open else (max(self.open, self.close) < self.pre.close)
|
||
|
||
features['klc_star_pattern'] = 1 if small_body and gap_with_prev else 0
|
||
else:
|
||
features['klc_star_pattern'] = 0
|
||
|
||
# ===== 分型强度特征 =====
|
||
# 添加分型强度相关特征
|
||
features['klc_fx_strength'] = self.cal_fx_strength()
|
||
features['klc_fx_strength_level'] = self.get_fx_strength_level()
|
||
features['klc_is_strong_fx'] = 1 if self.is_strong_fx() else 0
|
||
|
||
# 分型强度分类特征
|
||
fx_strength = features['klc_fx_strength']
|
||
features['klc_fx_strength_extreme'] = 1 if fx_strength >= 80 else 0 # 极强分型
|
||
features['klc_fx_strength_strong'] = 1 if 60 <= fx_strength < 80 else 0 # 强分型
|
||
features['klc_fx_strength_medium'] = 1 if 40 <= fx_strength < 60 else 0 # 中等分型
|
||
features['klc_fx_strength_weak'] = 1 if 20 <= fx_strength < 40 else 0 # 弱分型
|
||
features['klc_fx_strength_very_weak'] = 1 if fx_strength < 20 else 0 # 极弱分型
|
||
|
||
return features
|
||
|
||
def cal_fx_strength(self):
|
||
"""
|
||
统一的分型强度计算函数 - 直接计算当前KLC的分型强度
|
||
包含技术指标确认
|
||
|
||
Returns:
|
||
int: 强度评分 15-80分
|
||
"""
|
||
return self.cal_fx()
|
||
# 如果不是分型,返回0
|
||
if self.fx == Chan_FX_TYPE.UNKNOWN or self.pre == None or self.next == None or self.next.end_klu == None:
|
||
return 0
|
||
|
||
# 如果没有前一个KLC,返回基础分
|
||
if not self.pre:
|
||
return 15
|
||
|
||
score = 15 # 基础分数,任何分型都有基础分
|
||
|
||
# === 1. 突出程度评分(0-25分) ===
|
||
if self.fx == Chan_FX_TYPE.TOP:
|
||
# 顶分型:当前高点与前一个高点的差异
|
||
if self.pre.high > 0:
|
||
prominence = abs(self.high - self.pre.high) / self.pre.high
|
||
else:
|
||
prominence = 0
|
||
else: # 底分型
|
||
# 底分型:当前低点与前一个低点的差异
|
||
if self.pre.low > 0:
|
||
prominence = abs(self.pre.low - self.low) / self.pre.low
|
||
else:
|
||
prominence = 0
|
||
|
||
# 突出程度评分 - 极度宽松
|
||
if prominence >= 0.03: # 3%以上突出
|
||
score += 25
|
||
elif prominence >= 0.02: # 2-3%突出
|
||
score += 20
|
||
elif prominence >= 0.015: # 1.5-2%突出
|
||
score += 15
|
||
elif prominence >= 0.01: # 1-1.5%突出
|
||
score += 10
|
||
elif prominence >= 0.005: # 0.5-1%突出
|
||
score += 6
|
||
elif prominence >= 0.002: # 0.2-0.5%突出
|
||
score += 3
|
||
else:
|
||
score += 1 # 任何突出度都给分
|
||
|
||
# === 2. K线形态评分(0-15分) ===
|
||
kline_range = self.high - self.low
|
||
if kline_range > 0:
|
||
if self.fx == Chan_FX_TYPE.TOP:
|
||
# 顶分型看上影线
|
||
upper_shadow = self.high - max(self.open, self.close)
|
||
shadow_ratio = upper_shadow / kline_range
|
||
else:
|
||
# 底分型看下影线
|
||
lower_shadow = min(self.open, self.close) - self.low
|
||
shadow_ratio = lower_shadow / kline_range
|
||
|
||
# 影线评分 - 极度宽松
|
||
if shadow_ratio >= 0.3: # 长影线
|
||
score += 15
|
||
elif shadow_ratio >= 0.2: # 明显影线
|
||
score += 12
|
||
elif shadow_ratio >= 0.1: # 一般影线
|
||
score += 8
|
||
elif shadow_ratio >= 0.05: # 短影线
|
||
score += 5
|
||
else:
|
||
score += 2 # 有一点影线就给分
|
||
else:
|
||
score += 2 # 十字星也给点分
|
||
|
||
# === 3. 成交量评分(0-10分) ===
|
||
avg_volume = self._calculate_average_volume(lookback=5)
|
||
if avg_volume > 0:
|
||
volume_ratio = self.volume / avg_volume
|
||
if volume_ratio >= 2.0: # 大量
|
||
score += 10
|
||
elif volume_ratio >= 1.5: # 明显放量
|
||
score += 8
|
||
elif volume_ratio >= 1.2: # 适度放量
|
||
score += 6
|
||
elif volume_ratio >= 1.0: # 正常量
|
||
score += 4
|
||
elif volume_ratio >= 0.8: # 略缩量
|
||
score += 2
|
||
else:
|
||
score += 1 # 大幅缩量也给1分
|
||
else:
|
||
score += 4 # 无法计算时给默认分
|
||
|
||
# === 4. 价格位置评分(0-10分) ===
|
||
if kline_range > 0:
|
||
close_position = (self.close - self.low) / kline_range
|
||
|
||
if self.fx == Chan_FX_TYPE.TOP:
|
||
# 顶分型:收盘价越低越好
|
||
if close_position <= 0.3: # 收盘在下部
|
||
score += 10
|
||
elif close_position <= 0.5: # 收盘在中下部
|
||
score += 7
|
||
elif close_position <= 0.7: # 收盘在中上部
|
||
score += 4
|
||
else:
|
||
score += 2 # 收盘位置偏高但还给分
|
||
else:
|
||
# 底分型:收盘价越高越好
|
||
if close_position >= 0.7: # 收盘在上部
|
||
score += 10
|
||
elif close_position >= 0.5: # 收盘在中上部
|
||
score += 7
|
||
elif close_position >= 0.3: # 收盘在中下部
|
||
score += 4
|
||
else:
|
||
score += 2 # 收盘位置偏低但还给分
|
||
else:
|
||
score += 5 # 无区间时给中等分
|
||
|
||
# === 5. 技术指标确认(0-20分) ===
|
||
tech_score = 0
|
||
|
||
# 5.1 RSI确认(0-4分)
|
||
if hasattr(self, 'rsi') and self.rsi is not None:
|
||
if self.fx == Chan_FX_TYPE.TOP:
|
||
# 顶分型:RSI超买确认
|
||
if self.rsi >= 80: # 严重超买
|
||
tech_score += 4
|
||
elif self.rsi >= 70: # 超买
|
||
tech_score += 3
|
||
elif self.rsi >= 60: # 偏高
|
||
tech_score += 2
|
||
elif self.rsi >= 50: # 中性偏高
|
||
tech_score += 1
|
||
else:
|
||
# 底分型:RSI超卖确认
|
||
if self.rsi <= 20: # 严重超卖
|
||
tech_score += 4
|
||
elif self.rsi <= 30: # 超卖
|
||
tech_score += 3
|
||
elif self.rsi <= 40: # 偏低
|
||
tech_score += 2
|
||
elif self.rsi <= 50: # 中性偏低
|
||
tech_score += 1
|
||
|
||
# 5.2 MACD确认(0-4分)
|
||
if hasattr(self, 'macdhist') and self.macdhist is not None:
|
||
if self.fx == Chan_FX_TYPE.TOP:
|
||
# 顶分型:MACD背离或转弱
|
||
if self.macdhist < 0: # MACD柱状图为负
|
||
tech_score += 2
|
||
# 检查是否从正转负
|
||
if self.pre and hasattr(self.pre, 'macdhist') and self.pre.macdhist is not None:
|
||
if self.pre.macdhist > 0: # 前一根为正
|
||
tech_score += 2 # 从正转负,额外加分
|
||
elif self.pre and hasattr(self.pre, 'macdhist') and self.pre.macdhist is not None:
|
||
# 检查MACD是否减弱
|
||
if self.macdhist < self.pre.macdhist:
|
||
tech_score += 1
|
||
else:
|
||
# 底分型:MACD转强
|
||
if self.macdhist > 0: # MACD柱状图为正
|
||
tech_score += 2
|
||
# 检查是否从负转正
|
||
if self.pre and hasattr(self.pre, 'macdhist') and self.pre.macdhist is not None:
|
||
if self.pre.macdhist < 0: # 前一根为负
|
||
tech_score += 2 # 从负转正,额外加分
|
||
elif self.pre and hasattr(self.pre, 'macdhist') and self.pre.macdhist is not None:
|
||
# 检查MACD是否增强
|
||
if self.macdhist > self.pre.macdhist:
|
||
tech_score += 1
|
||
|
||
# 5.3 布林带确认(0-4分)
|
||
bb_score = self._analyze_bollinger_for_fx()
|
||
tech_score += min(4, bb_score)
|
||
|
||
# 5.4 EMA趋势确认(0-4分)
|
||
ema_score = self._analyze_ema_for_fx()
|
||
tech_score += min(4, ema_score)
|
||
|
||
# 5.5 ATR波动率确认(0-4分)
|
||
atr_score = self._analyze_atr_for_fx()
|
||
tech_score += min(4, atr_score)
|
||
|
||
score += min(20, tech_score)
|
||
|
||
return min(80, max(15, score)) # 确保至少15分,最高80分
|
||
|
||
def _analyze_bollinger_for_fx(self):
|
||
"""
|
||
布林带分析 - 统一版本
|
||
"""
|
||
score = 0
|
||
|
||
# 计算简化的布林带(基于收盘价)
|
||
closes = [self.close]
|
||
temp = self.pre
|
||
|
||
for i in range(19): # 布林带通常使用20周期
|
||
if temp:
|
||
closes.append(temp.close)
|
||
temp = temp.pre
|
||
else:
|
||
break
|
||
|
||
if len(closes) >= 20:
|
||
import numpy as np
|
||
|
||
# 计算20周期移动平均线和标准差
|
||
ma20 = np.mean(closes[:20])
|
||
std = np.std(closes[:20])
|
||
|
||
# 布林带上轨和下轨
|
||
upper_band = ma20 + 2 * std
|
||
lower_band = ma20 - 2 * std
|
||
|
||
if self.fx == Chan_FX_TYPE.TOP:
|
||
# 顶分型:价格接近或突破上轨
|
||
if self.high >= upper_band: # 触及或突破上轨
|
||
score += 4
|
||
elif self.close > ma20:
|
||
# 计算价格在上半区的位置
|
||
if upper_band > ma20:
|
||
position = (self.close - ma20) / (upper_band - ma20)
|
||
if position > 0.8: # 接近上轨
|
||
score += 3
|
||
elif position > 0.6:
|
||
score += 2
|
||
elif position > 0.3:
|
||
score += 1
|
||
else:
|
||
# 底分型:价格接近或突破下轨
|
||
if self.low <= lower_band: # 触及或突破下轨
|
||
score += 4
|
||
elif self.close < ma20:
|
||
# 计算价格在下半区的位置
|
||
if ma20 > lower_band:
|
||
position = (ma20 - self.close) / (ma20 - lower_band)
|
||
if position > 0.8: # 接近下轨
|
||
score += 3
|
||
elif position > 0.6:
|
||
score += 2
|
||
elif position > 0.3:
|
||
score += 1
|
||
|
||
return score
|
||
|
||
def _analyze_ema_for_fx(self):
|
||
"""
|
||
EMA趋势分析
|
||
"""
|
||
score = 0
|
||
|
||
# 获取多周期收盘价用于EMA计算
|
||
closes = [self.close]
|
||
temp = self.pre
|
||
|
||
for i in range(29): # 获取30根K线用于EMA计算
|
||
if temp:
|
||
closes.append(temp.close)
|
||
temp = temp.pre
|
||
else:
|
||
break
|
||
|
||
if len(closes) >= 12: # 至少需要12根K线
|
||
import numpy as np
|
||
|
||
# 计算EMA12和EMA26
|
||
def calculate_ema(prices, period):
|
||
alpha = 2 / (period + 1)
|
||
ema = [prices[0]]
|
||
for price in prices[1:period]:
|
||
ema.append(alpha * price + (1 - alpha) * ema[-1])
|
||
return ema[-1] if len(ema) > 0 else prices[0]
|
||
|
||
if len(closes) >= 12:
|
||
ema12 = calculate_ema(closes[:12][::-1], 12) # 反转顺序,最新的在前
|
||
|
||
if len(closes) >= 26:
|
||
ema26 = calculate_ema(closes[:26][::-1], 26)
|
||
|
||
if self.fx == Chan_FX_TYPE.TOP:
|
||
# 顶分型:价格高于EMA,EMA向上但可能转向
|
||
if self.close > ema12 > ema26: # 多头排列
|
||
score += 2
|
||
elif self.close > ema12: # 价格在短期EMA上方
|
||
score += 1
|
||
|
||
# 检查EMA12是否开始转向
|
||
if self.pre and len(closes) >= 13:
|
||
prev_ema12 = calculate_ema(closes[1:13][::-1], 12)
|
||
if ema12 < prev_ema12: # EMA12开始下降
|
||
score += 2
|
||
else:
|
||
# 底分型:价格低于EMA,EMA向下但可能转向
|
||
if self.close < ema12 < ema26: # 空头排列
|
||
score += 2
|
||
elif self.close < ema12: # 价格在短期EMA下方
|
||
score += 1
|
||
|
||
# 检查EMA12是否开始转向
|
||
if self.pre and len(closes) >= 13:
|
||
prev_ema12 = calculate_ema(closes[1:13][::-1], 12)
|
||
if ema12 > prev_ema12: # EMA12开始上升
|
||
score += 2
|
||
|
||
return score
|
||
|
||
def _analyze_atr_for_fx(self):
|
||
"""
|
||
ATR波动率分析
|
||
"""
|
||
score = 0
|
||
|
||
# 计算ATR
|
||
recent_atr = self._calculate_recent_atr(lookback=14)
|
||
if recent_atr > 0:
|
||
# 当前K线的真实波动范围
|
||
current_tr = self.high - self.low
|
||
if self.pre:
|
||
current_tr = max(
|
||
self.high - self.low,
|
||
abs(self.high - self.pre.close),
|
||
abs(self.low - self.pre.close)
|
||
)
|
||
|
||
# ATR倍数
|
||
atr_ratio = current_tr / recent_atr
|
||
|
||
if self.fx == Chan_FX_TYPE.TOP:
|
||
# 顶分型:波动率放大确认反转
|
||
if atr_ratio >= 2.5: # 波动率大幅放大
|
||
score += 4
|
||
elif atr_ratio >= 2.0: # 波动率明显放大
|
||
score += 3
|
||
elif atr_ratio >= 1.5: # 波动率适度放大
|
||
score += 2
|
||
elif atr_ratio >= 1.2: # 波动率略微放大
|
||
score += 1
|
||
else:
|
||
# 底分型:波动率放大确认反转
|
||
if atr_ratio >= 2.5: # 波动率大幅放大
|
||
score += 4
|
||
elif atr_ratio >= 2.0: # 波动率明显放大
|
||
score += 3
|
||
elif atr_ratio >= 1.5: # 波动率适度放大
|
||
score += 2
|
||
elif atr_ratio >= 1.2: # 波动率略微放大
|
||
score += 1
|
||
|
||
# 额外检查:波动率从低到高的变化
|
||
if self.pre:
|
||
prev_tr = self.pre.high - self.pre.low
|
||
if self.pre.pre:
|
||
prev_tr = max(
|
||
self.pre.high - self.pre.low,
|
||
abs(self.pre.high - self.pre.pre.close),
|
||
abs(self.pre.low - self.pre.pre.close)
|
||
)
|
||
|
||
# 波动率加速放大
|
||
if current_tr > prev_tr * 1.5:
|
||
score += 1
|
||
|
||
return score
|
||
|
||
def get_fx_strength_level(self):
|
||
"""
|
||
获取分型强度等级
|
||
|
||
Returns:
|
||
str: 强度等级描述
|
||
"""
|
||
strength = self.cal_fx_strength()
|
||
|
||
# 调整后的强度等级阈值(匹配15-80分范围)
|
||
if strength >= 70:
|
||
return "极强分型" # 极强分型:70分以上
|
||
elif strength >= 60:
|
||
return "强分型" # 强分型:60-69分
|
||
elif strength >= 50:
|
||
return "中强分型" # 中强分型:50-59分
|
||
elif strength >= 40:
|
||
return "中等分型" # 中等分型:40-49分
|
||
elif strength >= 25:
|
||
return "弱分型" # 弱分型:25-39分
|
||
else:
|
||
return "极弱分型" # 极弱分型:25分以下
|
||
|
||
def is_strong_fx(self, threshold=55):
|
||
"""
|
||
判断是否为强分型
|
||
|
||
Args:
|
||
threshold: 强分型的阈值,调整为55分(适配80分制)
|
||
|
||
Returns:
|
||
bool: 是否为强分型
|
||
"""
|
||
return self.cal_fx_strength() >= threshold
|
||
|
||
def _default_top_strength_judgment(self, first_info, middle_info, last_info, first_kline, middle_kline, last_kline):
|
||
"""
|
||
顶分型默认强弱判断
|
||
当不满足特定强弱条件时的保底判断
|
||
"""
|
||
# 严格的强分型判断条件
|
||
strong_signals = 0
|
||
|
||
# 判断条件1:成交量显著放大(提高标准)
|
||
avg_volume = self._calculate_average_volume(lookback=5)
|
||
volume_significantly_amplified = middle_kline.volume > avg_volume * 2.0 if avg_volume > 0 else False
|
||
if volume_significantly_amplified:
|
||
strong_signals += 1
|
||
|
||
# 判断条件2:中间K线有长上影线(提高标准)
|
||
has_long_upper_shadow = middle_info['upper_shadow_ratio'] > 0.6 # 从0.3提高到0.6
|
||
if has_long_upper_shadow:
|
||
strong_signals += 1
|
||
|
||
# 判断条件3:后续K线收盘明显偏低(更严格)
|
||
middle_range = middle_kline.high - middle_kline.low
|
||
last_close_position = (last_kline.close - middle_kline.low) / middle_range if middle_range > 0 else 0.5
|
||
close_significantly_low = last_close_position < 0.3 # 从0.6提高到0.3
|
||
if close_significantly_low:
|
||
strong_signals += 1
|
||
|
||
# 判断条件4:最后一根K线是明显的阴线且跌幅较大
|
||
is_significant_bearish = (last_info['is_bearish'] and
|
||
last_info['body_size'] > last_info['total_range'] * 0.5)
|
||
if is_significant_bearish:
|
||
strong_signals += 1
|
||
|
||
# 判断条件5:跌破前一根K线重要价位
|
||
breaks_important_level = last_kline.low < first_kline.low
|
||
if breaks_important_level:
|
||
strong_signals += 1
|
||
|
||
# 需要至少4个强信号才判断为强分型,否则为弱分型
|
||
return 1 if strong_signals >= 4 else -1
|
||
|
||
def _default_bottom_strength_judgment(self, first_info, middle_info, last_info, first_kline, middle_kline, last_kline):
|
||
"""
|
||
底分型默认强弱判断
|
||
当不满足特定强弱条件时的保底判断
|
||
"""
|
||
# 严格的强分型判断条件
|
||
strong_signals = 0
|
||
|
||
# 判断条件1:成交量显著放大(提高标准)
|
||
avg_volume = self._calculate_average_volume(lookback=5)
|
||
volume_significantly_amplified = middle_kline.volume > avg_volume * 2.0 if avg_volume > 0 else False
|
||
if volume_significantly_amplified:
|
||
strong_signals += 1
|
||
|
||
# 判断条件2:中间K线有长下影线(提高标准)
|
||
has_long_lower_shadow = middle_info['lower_shadow_ratio'] > 0.6 # 从0.3提高到0.6
|
||
if has_long_lower_shadow:
|
||
strong_signals += 1
|
||
|
||
# 判断条件3:后续K线收盘明显偏高(更严格)
|
||
middle_range = middle_kline.high - middle_kline.low
|
||
last_close_position = (last_kline.close - middle_kline.low) / middle_range if middle_range > 0 else 0.5
|
||
close_significantly_high = last_close_position > 0.7 # 从0.4降低到0.7
|
||
if close_significantly_high:
|
||
strong_signals += 1
|
||
|
||
# 判断条件4:最后一根K线是明显的阳线且涨幅较大
|
||
is_significant_bullish = (last_info['is_bullish'] and
|
||
last_info['body_size'] > last_info['total_range'] * 0.5)
|
||
if is_significant_bullish:
|
||
strong_signals += 1
|
||
|
||
# 判断条件5:突破前一根K线重要价位
|
||
breaks_important_level = last_kline.high > first_kline.high
|
||
if breaks_important_level:
|
||
strong_signals += 1
|
||
|
||
# 需要至少4个强信号才判断为强分型,否则为弱分型
|
||
return 1 if strong_signals >= 4 else -1
|
||
|
||
def get_real_price_range(self):
|
||
"""
|
||
获取真实价格区间(合并前所有原始K线的最高最低点)
|
||
返回: (real_high, real_low)
|
||
"""
|
||
if not self.klus:
|
||
return self.high, self.low
|
||
|
||
real_high = max(klu.high for klu in self.klus)
|
||
real_low = min(klu.low for klu in self.klus)
|
||
|
||
return real_high, real_low
|
||
|
||
def get_real_range_size(self):
|
||
"""
|
||
获取真实价格区间大小
|
||
"""
|
||
real_high, real_low = self.get_real_price_range()
|
||
return real_high - real_low
|
||
|
||
def get_real_close_position(self):
|
||
"""
|
||
获取收盘价在真实价格区间中的位置
|
||
"""
|
||
real_high, real_low = self.get_real_price_range()
|
||
real_range = real_high - real_low
|
||
|
||
if real_range > 0:
|
||
return (self.close - real_low) / real_range
|
||
else:
|
||
return 0.5
|
||
|
||
def get_real_upper_shadow_ratio(self):
|
||
"""
|
||
获取上影线在真实区间中的比例
|
||
"""
|
||
real_high, real_low = self.get_real_price_range()
|
||
real_range = real_high - real_low
|
||
|
||
if real_range > 0:
|
||
upper_shadow = real_high - max(self.open, self.close)
|
||
return upper_shadow / real_range
|
||
else:
|
||
return 0
|
||
|
||
def get_real_lower_shadow_ratio(self):
|
||
"""
|
||
获取下影线在真实区间中的比例
|
||
"""
|
||
real_high, real_low = self.get_real_price_range()
|
||
real_range = real_high - real_low
|
||
|
||
if real_range > 0:
|
||
lower_shadow = min(self.open, self.close) - real_low
|
||
return lower_shadow / real_range
|
||
else:
|
||
return 0
|
||
|
||
def _analyze_macd_for_top_fx(self):
|
||
"""
|
||
MACD指标在顶分型中的综合分析
|
||
"""
|
||
score = 0
|
||
|
||
# 检查MACD背离
|
||
if self._check_macd_bearish_divergence():
|
||
score += 3
|
||
|
||
# 检查MACD柱状图趋势
|
||
if hasattr(self, 'macdhist') and self.macdhist is not None:
|
||
if self.macdhist < 0: # MACD柱状图为负
|
||
score += 1
|
||
|
||
# 检查MACD柱状图是否从正转负
|
||
if self.pre and hasattr(self.pre, 'macdhist') and self.pre.macdhist is not None:
|
||
if self.pre.macdhist > 0 and self.macdhist < 0:
|
||
score += 2
|
||
|
||
# 检查MACD线是否在零轴上方形成顶背离
|
||
klu_features = self.cal_klu_features()
|
||
if 'klu_macd' in klu_features and 'klu_signal' in klu_features:
|
||
macd_line = klu_features['klu_macd']
|
||
signal_line = klu_features['klu_signal']
|
||
|
||
# MACD线高于信号线但趋势减弱
|
||
if macd_line > signal_line and macd_line > 0:
|
||
score += 1
|
||
|
||
return score
|
||
|
||
def _analyze_macd_for_bottom_fx(self):
|
||
"""
|
||
MACD指标在底分型中的综合分析
|
||
"""
|
||
score = 0
|
||
|
||
# 检查MACD背离
|
||
if self._check_macd_bullish_divergence():
|
||
score += 3
|
||
|
||
# 检查MACD柱状图趋势
|
||
if hasattr(self, 'macdhist') and self.macdhist is not None:
|
||
if self.macdhist > 0: # MACD柱状图为正
|
||
score += 1
|
||
|
||
# 检查MACD柱状图是否从负转正
|
||
if self.pre and hasattr(self.pre, 'macdhist') and self.pre.macdhist is not None:
|
||
if self.pre.macdhist < 0 and self.macdhist > 0:
|
||
score += 2
|
||
|
||
# 检查MACD线是否在零轴下方形成底背离
|
||
klu_features = self.cal_klu_features()
|
||
if 'klu_macd' in klu_features and 'klu_signal' in klu_features:
|
||
macd_line = klu_features['klu_macd']
|
||
signal_line = klu_features['klu_signal']
|
||
|
||
# MACD线低于信号线但趋势增强
|
||
if macd_line < signal_line and macd_line < 0:
|
||
score += 1
|
||
|
||
return score
|
||
|
||
def _analyze_kdj_for_top_fx(self):
|
||
"""
|
||
KDJ指标在顶分型中的分析
|
||
"""
|
||
score = 0
|
||
|
||
# 模拟KDJ计算(基于真实价格区间)
|
||
real_high, real_low = self.get_real_price_range()
|
||
|
||
# 获取前面几根K线的最高最低价
|
||
temp = self.pre
|
||
highs = [real_high]
|
||
lows = [real_low]
|
||
closes = [self.close]
|
||
|
||
for i in range(8): # KDJ通常使用9周期
|
||
if temp:
|
||
temp_high, temp_low = temp.get_real_price_range()
|
||
highs.append(temp_high)
|
||
lows.append(temp_low)
|
||
closes.append(temp.close)
|
||
temp = temp.pre
|
||
else:
|
||
break
|
||
|
||
if len(highs) >= 9:
|
||
# 计算9周期的最高价和最低价
|
||
highest_high = max(highs[:9])
|
||
lowest_low = min(lows[:9])
|
||
|
||
# 计算RSV(未成熟随机值)
|
||
if highest_high > lowest_low:
|
||
rsv = (self.close - lowest_low) / (highest_high - lowest_low) * 100
|
||
|
||
# 简化的K值计算
|
||
k_value = rsv # 简化处理
|
||
|
||
# KDJ超买判断
|
||
if k_value > 80: # K值超买
|
||
score += 3
|
||
elif k_value > 70:
|
||
score += 2
|
||
|
||
# 检查KDJ死叉形态
|
||
if self.pre:
|
||
pre_highs = highs[1:10] if len(highs) > 9 else highs[1:]
|
||
pre_lows = lows[1:10] if len(lows) > 9 else lows[1:]
|
||
|
||
if pre_highs and pre_lows:
|
||
pre_highest = max(pre_highs)
|
||
pre_lowest = min(pre_lows)
|
||
|
||
if pre_highest > pre_lowest:
|
||
pre_rsv = (self.pre.close - pre_lowest) / (pre_highest - pre_lowest) * 100
|
||
pre_k_value = pre_rsv
|
||
|
||
# 检查K值是否从高位下降
|
||
if pre_k_value > k_value and pre_k_value > 70:
|
||
score += 2
|
||
|
||
return score
|
||
|
||
def _analyze_kdj_for_bottom_fx(self):
|
||
"""
|
||
KDJ指标在底分型中的分析
|
||
"""
|
||
score = 0
|
||
|
||
# 模拟KDJ计算(基于真实价格区间)
|
||
real_high, real_low = self.get_real_price_range()
|
||
|
||
# 获取前面几根K线的最高最低价
|
||
temp = self.pre
|
||
highs = [real_high]
|
||
lows = [real_low]
|
||
closes = [self.close]
|
||
|
||
for i in range(8): # KDJ通常使用9周期
|
||
if temp:
|
||
temp_high, temp_low = temp.get_real_price_range()
|
||
highs.append(temp_high)
|
||
lows.append(temp_low)
|
||
closes.append(temp.close)
|
||
temp = temp.pre
|
||
else:
|
||
break
|
||
|
||
if len(highs) >= 9:
|
||
# 计算9周期的最高价和最低价
|
||
highest_high = max(highs[:9])
|
||
lowest_low = min(lows[:9])
|
||
|
||
# 计算RSV(未成熟随机值)
|
||
if highest_high > lowest_low:
|
||
rsv = (self.close - lowest_low) / (highest_high - lowest_low) * 100
|
||
|
||
# 简化的K值计算
|
||
k_value = rsv # 简化处理
|
||
|
||
# KDJ超卖判断
|
||
if k_value < 20: # K值超卖
|
||
score += 3
|
||
elif k_value < 30:
|
||
score += 2
|
||
|
||
# 检查KDJ金叉形态
|
||
if self.pre:
|
||
pre_highs = highs[1:10] if len(highs) > 9 else highs[1:]
|
||
pre_lows = lows[1:10] if len(lows) > 9 else lows[1:]
|
||
|
||
if pre_highs and pre_lows:
|
||
pre_highest = max(pre_highs)
|
||
pre_lowest = min(pre_lows)
|
||
|
||
if pre_highest > pre_lowest:
|
||
pre_rsv = (self.pre.close - pre_lowest) / (pre_highest - pre_lowest) * 100
|
||
pre_k_value = pre_rsv
|
||
|
||
# 检查K值是否从低位上升
|
||
if k_value > pre_k_value and pre_k_value < 30:
|
||
score += 2
|
||
|
||
return score
|
||
|
||
def _calculate_recent_atr(self, lookback=14):
|
||
"""
|
||
计算最近的ATR(平均真实波动范围)
|
||
|
||
Args:
|
||
lookback: 回看周期,默认14
|
||
|
||
Returns:
|
||
float: ATR值
|
||
"""
|
||
if not self.pre:
|
||
return 0
|
||
|
||
true_ranges = []
|
||
temp = self
|
||
|
||
for i in range(lookback):
|
||
if temp and temp.pre:
|
||
# 计算真实波动范围(TR)
|
||
tr = max(
|
||
temp.high - temp.low, # 当前高低价差
|
||
abs(temp.high - temp.pre.close), # 当前高价与前收盘价差的绝对值
|
||
abs(temp.low - temp.pre.close) # 当前低价与前收盘价差的绝对值
|
||
)
|
||
true_ranges.append(tr)
|
||
temp = temp.pre
|
||
else:
|
||
break
|
||
|
||
if true_ranges:
|
||
return sum(true_ranges) / len(true_ranges)
|
||
else:
|
||
return 0
|
||
|
||
def _calculate_average_volume(self, lookback=5):
|
||
"""
|
||
计算平均成交量
|
||
"""
|
||
volumes = []
|
||
temp = self.pre # 从前一根K线开始计算
|
||
|
||
for i in range(lookback):
|
||
if temp:
|
||
volumes.append(temp.volume)
|
||
temp = temp.pre
|
||
else:
|
||
break
|
||
|
||
return sum(volumes) / len(volumes) if volumes else 0
|
||
|
||
def _check_macd_bearish_divergence(self):
|
||
"""
|
||
检查MACD看跌背离
|
||
"""
|
||
# 简化版本,可以根据实际MACD数据进行更复杂的背离分析
|
||
if hasattr(self, 'macdhist') and self.macdhist:
|
||
# 如果MACD柱状图在减弱,可能形成顶背离
|
||
if self.pre and hasattr(self.pre, 'macdhist') and self.pre.macdhist:
|
||
if self.macdhist < self.pre.macdhist and self.macdhist < 0:
|
||
return True
|
||
return False
|
||
|
||
def _check_macd_bullish_divergence(self):
|
||
"""
|
||
检查MACD看涨背离
|
||
"""
|
||
# 简化版本,可以根据实际MACD数据进行更复杂的背离分析
|
||
if hasattr(self, 'macdhist') and self.macdhist:
|
||
# 如果MACD柱状图在增强,可能形成底背离
|
||
if self.pre and hasattr(self.pre, 'macdhist') and self.pre.macdhist:
|
||
if self.macdhist > self.pre.macdhist and self.macdhist > 0:
|
||
return True
|
||
return False
|
||
|
||
def _near_resistance_level(self):
|
||
"""
|
||
检查是否接近阻力位(使用真实价格区间)
|
||
"""
|
||
# 简化版本:检查是否接近前面几根K线的最高点
|
||
if self.pre:
|
||
real_high, _ = self.get_real_price_range()
|
||
temp = self.pre
|
||
max_high = 0
|
||
for i in range(10): # 检查前10根K线
|
||
if temp:
|
||
temp_real_high, _ = temp.get_real_price_range()
|
||
max_high = max(max_high, temp_real_high)
|
||
temp = temp.pre
|
||
else:
|
||
break
|
||
|
||
if max_high > 0:
|
||
# 如果当前真实高点接近前期高点,可能是阻力位
|
||
distance_ratio = abs(real_high - max_high) / max_high
|
||
return distance_ratio < 0.02 # 2%以内算接近
|
||
|
||
return False
|
||
|
||
def _near_support_level(self):
|
||
"""
|
||
检查是否接近支撑位(使用真实价格区间)
|
||
"""
|
||
# 简化版本:检查是否接近前面几根K线的最低点
|
||
if self.pre:
|
||
_, real_low = self.get_real_price_range()
|
||
temp = self.pre
|
||
min_low = float('inf')
|
||
for i in range(10): # 检查前10根K线
|
||
if temp:
|
||
_, temp_real_low = temp.get_real_price_range()
|
||
min_low = min(min_low, temp_real_low)
|
||
temp = temp.pre
|
||
else:
|
||
break
|
||
|
||
if min_low != float('inf') and min_low > 0:
|
||
# 如果当前真实低点接近前期低点,可能是支撑位
|
||
distance_ratio = abs(real_low - min_low) / min_low
|
||
return distance_ratio < 0.02 # 2%以内算接近
|
||
|
||
return False
|
||
|
||
def cal_fx_strength_realtime(self):
|
||
"""
|
||
实时计算分型强度 - 严格版本(不使用缓存,强制重新计算)
|
||
基于最后两根K线评估分型强度,不使用未来数据
|
||
Returns:
|
||
int: 强度评分 0-70分
|
||
"""
|
||
if not self.pre or not self.pre.is_fx():
|
||
return 0
|
||
|
||
fx_type = self.pre.fx_type
|
||
|
||
# 强制重新计算,不使用任何缓存
|
||
if fx_type == Chan_FX_TYPE.TOP:
|
||
strength = self._calculate_top_fx_power_realtime_v2()
|
||
elif fx_type == Chan_FX_TYPE.BOTTOM:
|
||
strength = self._calculate_bottom_fx_power_realtime_v2()
|
||
else:
|
||
strength = 0
|
||
|
||
return strength
|
||
|
||
def _calculate_top_fx_power_realtime_v2(self):
|
||
"""
|
||
宽松版本的实时顶分型力度计算 - 确保合理分型有分数
|
||
"""
|
||
score = 10 # 提高基础分数,确认是分型就有基础分
|
||
|
||
# 获取前一个KLC的最高价用于比较
|
||
if not self.pre:
|
||
return score
|
||
|
||
# 1. 突出程度评分(0-25分)- 大幅放宽标准
|
||
current_high = self.high
|
||
prev_high = self.pre.high
|
||
|
||
# 计算突出程度 - 修正计算逻辑
|
||
if prev_high > 0:
|
||
high_prominence = abs(current_high - prev_high) / prev_high
|
||
else:
|
||
high_prominence = 0
|
||
|
||
# 极度放宽的评分标准
|
||
if high_prominence >= 0.05: # 5%以上突出 - 极强
|
||
score += 25
|
||
elif high_prominence >= 0.03: # 3-5%突出 - 很强
|
||
score += 20
|
||
elif high_prominence >= 0.02: # 2-3%突出 - 强
|
||
score += 15
|
||
elif high_prominence >= 0.015: # 1.5-2%突出 - 中等
|
||
score += 12
|
||
elif high_prominence >= 0.01: # 1-1.5%突出 - 较弱
|
||
score += 8
|
||
elif high_prominence >= 0.005: # 0.5-1%突出 - 弱
|
||
score += 5
|
||
elif high_prominence >= 0.002: # 0.2-0.5%突出 - 极弱
|
||
score += 2
|
||
else:
|
||
score += 1 # 有一定突出度就给点分
|
||
|
||
# 2. K线形态评分(0-20分)- 大幅放宽
|
||
kline_range = self.high - self.low
|
||
if kline_range > 0:
|
||
upper_shadow = self.high - max(self.open, self.close)
|
||
upper_shadow_ratio = upper_shadow / kline_range
|
||
|
||
if upper_shadow_ratio >= 0.4: # 长上影线
|
||
score += 20
|
||
elif upper_shadow_ratio >= 0.25: # 明显上影线
|
||
score += 15
|
||
elif upper_shadow_ratio >= 0.15: # 一般上影线
|
||
score += 10
|
||
elif upper_shadow_ratio >= 0.08: # 短上影线
|
||
score += 6
|
||
elif upper_shadow_ratio >= 0.03: # 很短上影线
|
||
score += 3
|
||
else:
|
||
score += 1 # 有一点上影线就给分
|
||
|
||
# 3. 成交量评分(0-15分)- 大幅放宽
|
||
avg_volume = self._calculate_average_volume(lookback=5)
|
||
if avg_volume > 0:
|
||
volume_ratio = self.volume / avg_volume
|
||
if volume_ratio >= 2.5: # 大量
|
||
score += 15
|
||
elif volume_ratio >= 1.8: # 明显放量
|
||
score += 12
|
||
elif volume_ratio >= 1.3: # 适度放量
|
||
score += 9
|
||
elif volume_ratio >= 1.1: # 轻微放量
|
||
score += 6
|
||
elif volume_ratio >= 0.8: # 正常量
|
||
score += 3
|
||
elif volume_ratio >= 0.5: # 缩量但可接受
|
||
score += 1
|
||
else:
|
||
score += 0 # 极度缩量
|
||
else:
|
||
score += 3 # 无法计算成交量时给默认分
|
||
|
||
# 4. 价格位置评分(0-10分)- 大幅放宽
|
||
if kline_range > 0:
|
||
close_position = (self.close - self.low) / kline_range
|
||
if close_position <= 0.2: # 收盘在下部
|
||
score += 10
|
||
elif close_position <= 0.4: # 收盘在中下部
|
||
score += 8
|
||
elif close_position <= 0.6: # 收盘在中部
|
||
score += 5
|
||
elif close_position <= 0.8: # 收盘在中上部
|
||
score += 3
|
||
else:
|
||
score += 1 # 收盘位置偏高但还有分
|
||
|
||
# === 去除大部分惩罚机制,只保留最基本的 ===
|
||
|
||
# 只有在完全没有突出度时才轻微降分
|
||
if high_prominence < 0.001: # 突出度低于0.1%
|
||
score = int(score * 0.8)
|
||
|
||
return min(60, max(10, score)) # 确保至少有10分,最高60分
|
||
|
||
def _calculate_bottom_fx_power_realtime_v2(self):
|
||
"""
|
||
宽松版本的实时底分型力度计算 - 确保合理分型有分数
|
||
"""
|
||
score = 10 # 提高基础分数,确认是分型就有基础分
|
||
|
||
# 获取前一个KLC的最低价用于比较
|
||
if not self.pre:
|
||
return score
|
||
|
||
# 1. 突出程度评分(0-25分)- 大幅放宽标准
|
||
current_low = self.low
|
||
prev_low = self.pre.low
|
||
|
||
# 计算突出程度 - 修正计算逻辑
|
||
if prev_low > 0:
|
||
low_prominence = abs(prev_low - current_low) / prev_low
|
||
else:
|
||
low_prominence = 0
|
||
|
||
# 极度放宽的评分标准
|
||
if low_prominence >= 0.05: # 5%以上突出 - 极强
|
||
score += 25
|
||
elif low_prominence >= 0.03: # 3-5%突出 - 很强
|
||
score += 20
|
||
elif low_prominence >= 0.02: # 2-3%突出 - 强
|
||
score += 15
|
||
elif low_prominence >= 0.015: # 1.5-2%突出 - 中等
|
||
score += 12
|
||
elif low_prominence >= 0.01: # 1-1.5%突出 - 较弱
|
||
score += 8
|
||
elif low_prominence >= 0.005: # 0.5-1%突出 - 弱
|
||
score += 5
|
||
elif low_prominence >= 0.002: # 0.2-0.5%突出 - 极弱
|
||
score += 2
|
||
else:
|
||
score += 1 # 有一定突出度就给点分
|
||
|
||
# 2. K线形态评分(0-20分)- 大幅放宽
|
||
kline_range = self.high - self.low
|
||
if kline_range > 0:
|
||
lower_shadow = min(self.open, self.close) - self.low
|
||
lower_shadow_ratio = lower_shadow / kline_range
|
||
|
||
if lower_shadow_ratio >= 0.4: # 长下影线
|
||
score += 20
|
||
elif lower_shadow_ratio >= 0.25: # 明显下影线
|
||
score += 15
|
||
elif lower_shadow_ratio >= 0.15: # 一般下影线
|
||
score += 10
|
||
elif lower_shadow_ratio >= 0.08: # 短下影线
|
||
score += 6
|
||
elif lower_shadow_ratio >= 0.03: # 很短下影线
|
||
score += 3
|
||
else:
|
||
score += 1 # 有一点下影线就给分
|
||
|
||
# 3. 成交量评分(0-15分)- 大幅放宽
|
||
avg_volume = self._calculate_average_volume(lookback=5)
|
||
if avg_volume > 0:
|
||
volume_ratio = self.volume / avg_volume
|
||
if volume_ratio >= 2.5: # 大量
|
||
score += 15
|
||
elif volume_ratio >= 1.8: # 明显放量
|
||
score += 12
|
||
elif volume_ratio >= 1.3: # 适度放量
|
||
score += 9
|
||
elif volume_ratio >= 1.1: # 轻微放量
|
||
score += 6
|
||
elif volume_ratio >= 0.8: # 正常量
|
||
score += 3
|
||
elif volume_ratio >= 0.5: # 缩量但可接受
|
||
score += 1
|
||
else:
|
||
score += 0 # 极度缩量
|
||
else:
|
||
score += 3 # 无法计算成交量时给默认分
|
||
|
||
# 4. 价格位置评分(0-10分)- 大幅放宽
|
||
if kline_range > 0:
|
||
close_position = (self.close - self.low) / kline_range
|
||
if close_position >= 0.8: # 收盘在上部
|
||
score += 10
|
||
elif close_position >= 0.6: # 收盘在中上部
|
||
score += 8
|
||
elif close_position >= 0.4: # 收盘在中部
|
||
score += 5
|
||
elif close_position >= 0.2: # 收盘在中下部
|
||
score += 3
|
||
else:
|
||
score += 1 # 收盘位置偏低但还有分
|
||
|
||
# === 去除大部分惩罚机制,只保留最基本的 ===
|
||
|
||
# 只有在完全没有突出度时才轻微降分
|
||
if low_prominence < 0.001: # 突出度低于0.1%
|
||
score = int(score * 0.8)
|
||
|
||
return min(60, max(10, score)) # 确保至少有10分,最高60分 |