删除根目录旧 Chan 模块、策略、配置、文档及 wyckoff 相关代码;更新缠论 pipeline 与笔中枢计算;补充 research 研究与 web 测试。 Co-authored-by: Cursor <cursoragent@cursor.com>
569 lines
21 KiB
Python
569 lines
21 KiB
Python
"""TF_DF builder mixin — 由 split_tfdf_builders 自动生成,逻辑与原 TF_DF 一致。"""
|
|
from __future__ import annotations
|
|
|
|
from datetime import timedelta
|
|
from decimal import Decimal
|
|
|
|
import numpy as np
|
|
import pandas as pd
|
|
import talib.abstract as ta
|
|
from pandas import DataFrame
|
|
from technical.util import resample_to_interval
|
|
|
|
from chanlun.core.ChanBI import ChanBI
|
|
from chanlun.core.ChanBIZS import ChanBIZS
|
|
from chanlun.core.ChanBSP import ChanBSP
|
|
from chanlun.core.ChanEnum import (
|
|
Chan_BI_DIR,
|
|
Chan_BSP_DIR,
|
|
Chan_BSP_TYPE,
|
|
Chan_FX_TYPE,
|
|
Chan_K_DIR,
|
|
Chan_KLC_FX,
|
|
Chan_KLC_STATE,
|
|
Chan_KLINE_DIR,
|
|
Chan_KLU_PATTERN,
|
|
Chan_PRICE_TREND,
|
|
Chan_SEG_DIR,
|
|
Chan_ZS_DIR,
|
|
)
|
|
from chanlun.core.ChanKLC import ChanKLC
|
|
from chanlun.core.ChanKLU import ChanKLU
|
|
from chanlun.core.ChanSBI import ChanSBI
|
|
from chanlun.core.ChanSEG import ChanSEG
|
|
from chanlun.core.ChanZS import ChanZS, ChanZS_Big
|
|
from chanlun.indicators.ChanMACD import ChanMACD
|
|
|
|
|
|
class KlineBuilderMixin:
|
|
def get_klu_state(self, dataframe):
|
|
klc_list = self.get_klc_list(self.get_klu_list(dataframe))
|
|
bi_list = self.cal_bi_list(klc_list)
|
|
klu_state_list = []
|
|
klc_index = 0
|
|
for index in range(0, len(dataframe)):
|
|
if klc_index == len(klc_list):
|
|
klc_index = len(klc_list) - 1
|
|
klc = klc_list[klc_index]
|
|
if klc.end_klu and klc.end_klu.idx == index:
|
|
if klc.klc_state == Chan_KLC_STATE.S10:
|
|
klu_state_list.append("10")
|
|
#print(klc.end_time, klc.klc_fx_type)
|
|
elif klc.klc_state == Chan_KLC_STATE.S_10:
|
|
klu_state_list.append("-10")
|
|
#print(klc.end_time, klc.klc_fx_type)
|
|
elif klc.klc_state == Chan_KLC_STATE.S11:
|
|
klu_state_list.append("11")
|
|
#print(klc.end_time, klc.klc_fx_type)
|
|
elif klc.klc_state == Chan_KLC_STATE.S_11:
|
|
klu_state_list.append("-11")
|
|
#print(klc.end_time, klc.klc_fx_type)
|
|
else:
|
|
klu_state_list.append("00")
|
|
klc_index += 1
|
|
else:
|
|
klu_state_list.append("00")
|
|
print(klu_state_list[:20])
|
|
return klu_state_list
|
|
|
|
|
|
def check_fx1(self, klc):
|
|
if klc.pre and klc.next:
|
|
if klc.high > klc.pre.high and klc.high > klc.next.high and klc.low > klc.pre.low and klc.low > klc.next.low:
|
|
if klc.pre.pre and klc.next.next:
|
|
if klc.high > klc.pre.pre.high and klc.high > klc.next.next.high:
|
|
#if (klc.close > klc.ema52 or klc.next.close > klc.next.ema52) and klc.macd > 0:
|
|
klc.set_fx(Chan_FX_TYPE.TOP)
|
|
#print(klc.start_time, klc.end_time,klc.next.start_time, klc.next.end_time, klc.macd, klc.state, klc.fx, "TOP")
|
|
return Chan_FX_TYPE.TOP
|
|
elif klc.low < klc.pre.low and klc.low < klc.next.low and klc.high < klc.pre.high and klc.high < klc.next.high:
|
|
#if (klc.close < klc.ema52 or klc.next.close < klc.next.ema52) and klc.macd < 0:
|
|
if klc.pre.pre and klc.next.next:
|
|
if klc.low < klc.pre.pre.low and klc.low < klc.next.next.low:
|
|
klc.set_fx(Chan_FX_TYPE.BOTTOM)
|
|
#print(klc.start_time, klc.end_time,klc.next.start_time, klc.next.end_time, klc.macd, klc.state, klc.fx, "BOTTOM")
|
|
return Chan_FX_TYPE.BOTTOM
|
|
return Chan_FX_TYPE.UNKNOWN
|
|
|
|
def check_fx(self, klc):
|
|
# 右K未完成(仍在包含合并)时不分型:否则确认笔会随 next 扩区间被 check_*_fx 收回
|
|
if klc.pre and klc.next and klc.next.end_klu is not None:
|
|
if klc.high > klc.pre.high and klc.high > klc.next.high and klc.low > klc.pre.low and klc.low > klc.next.low:
|
|
#if (klc.close > klc.ema52 or klc.next.close > klc.next.ema52) and klc.macd > 0:
|
|
klc.set_fx(Chan_FX_TYPE.TOP)
|
|
#print(klc.start_time, klc.end_time,klc.next.start_time, klc.next.end_time, klc.macd, klc.state, klc.fx, "TOP")
|
|
return Chan_FX_TYPE.TOP
|
|
elif klc.low < klc.pre.low and klc.low < klc.next.low and klc.high < klc.pre.high and klc.high < klc.next.high:
|
|
#if (klc.close < klc.ema52 or klc.next.close < klc.next.ema52) and klc.macd < 0:
|
|
klc.set_fx(Chan_FX_TYPE.BOTTOM)
|
|
#print(klc.start_time, klc.end_time,klc.next.start_time, klc.next.end_time, klc.macd, klc.state, klc.fx, "BOTTOM")
|
|
return Chan_FX_TYPE.BOTTOM
|
|
return Chan_FX_TYPE.UNKNOWN
|
|
|
|
def check_fx3(self, klc):
|
|
# 右K未完成(仍在包含合并)时不分型:否则确认笔会随 next 扩区间被 check_*_fx 收回
|
|
if klc.pre and klc.next and klc.next.end_klu is not None:
|
|
next_klu = klc.next.end_klu.next
|
|
if klc.high > klc.pre.high and klc.high > klc.next.high and klc.low > klc.pre.low and klc.low > klc.next.low and klc.high > next_klu.high:
|
|
#if (klc.close > klc.ema52 or klc.next.close > klc.next.ema52) and klc.macd > 0:
|
|
klc.set_fx(Chan_FX_TYPE.TOP)
|
|
#print(klc.start_time, klc.end_time,klc.next.start_time, klc.next.end_time, klc.macd, klc.state, klc.fx, "TOP")
|
|
return Chan_FX_TYPE.TOP
|
|
elif klc.low < klc.pre.low and klc.low < klc.next.low and klc.high < klc.pre.high and klc.high < klc.next.high and klc.low < next_klu.low:
|
|
#if (klc.close < klc.ema52 or klc.next.close < klc.next.ema52) and klc.macd < 0:
|
|
klc.set_fx(Chan_FX_TYPE.BOTTOM)
|
|
#print(klc.start_time, klc.end_time,klc.next.start_time, klc.next.end_time, klc.macd, klc.state, klc.fx, "BOTTOM")
|
|
return Chan_FX_TYPE.BOTTOM
|
|
return Chan_FX_TYPE.UNKNOWN
|
|
def check_fx2(self, klc):
|
|
if klc.pre and klc.next:
|
|
if klc.high > klc.pre.close and klc.close > klc.next.close and klc.close > klc.pre.close and klc.close > klc.next.close:
|
|
#if (klc.close > klc.ema52 or klc.next.close > klc.next.ema52) and klc.macd > 0:
|
|
klc.set_fx(Chan_FX_TYPE.TOP)
|
|
#print(klc.start_time, klc.end_time,klc.next.start_time, klc.next.end_time, klc.macd, klc.state, klc.fx, "TOP")
|
|
return Chan_FX_TYPE.TOP
|
|
elif klc.low < klc.pre.close and klc.close < klc.next.close and klc.close < klc.pre.close and klc.close < klc.next.close:
|
|
#if (klc.close < klc.ema52 or klc.next.close < klc.next.ema52) and klc.macd < 0:
|
|
klc.set_fx(Chan_FX_TYPE.BOTTOM)
|
|
#print(klc.start_time, klc.end_time,klc.next.start_time, klc.next.end_time, klc.macd, klc.state, klc.fx, "BOTTOM")
|
|
return Chan_FX_TYPE.BOTTOM
|
|
return Chan_FX_TYPE.UNKNOWN
|
|
|
|
def check_fx_pattern(self, klc):
|
|
klu_list = klc.pre.klu_list + klc.klu_list + klc.next.klu_list
|
|
|
|
self.cal_klu_pattern(klu_list)
|
|
p = ""
|
|
for klu in klu_list:
|
|
p += klu.to_string()
|
|
#print(p)
|
|
|
|
def cal_volume_ratio(self, dataframe, window=10):
|
|
df = dataframe.copy()
|
|
# 计算过去N根K线的平均成交量
|
|
df['avg_volume'] = df['volume'].rolling(window=window).mean()
|
|
# 计算量比
|
|
df['volume_ratio'] = df['volume'] / df['avg_volume']
|
|
# 填充缺失值(前N根K线)
|
|
df['volume_ratio'] = df['volume_ratio'].fillna(1.0)
|
|
return df['volume_ratio']
|
|
|
|
def cal_kl_data(self, dataframe:DataFrame):
|
|
fields = "time,open,high,low,close,volume"
|
|
klu_list = []
|
|
last_klu = None
|
|
for i in range(0, len(dataframe)):
|
|
item = dataframe.iloc[i]
|
|
date = item['date']
|
|
o = item['open']
|
|
h = item['high']
|
|
l = item['low']
|
|
c = item['close']
|
|
v = item['volume']
|
|
# time_obj = date.fromtimestamp(date)
|
|
# date = date + timedelta(hours=8)
|
|
time_str = date.strftime('%Y-%m-%d %H:%M:%S')
|
|
item_data = [
|
|
time_str,
|
|
o,
|
|
h,
|
|
l,
|
|
c,
|
|
v
|
|
]
|
|
# klu = KLU(self.create_item_dict(item_data, GetColumnNameFromFieldList(fields)))
|
|
klu = ChanKLU(time_str, o, h, l, c, v)
|
|
# print(klu.time, klu.open, klu.high, klu.low, klu.close, klu.volume)
|
|
klu.set_idx(i)
|
|
klu_list.append(klu)
|
|
if last_klu:
|
|
last_klu.set_next(klu)
|
|
klu.set_pre(last_klu)
|
|
last_klu = klu
|
|
if 'macd' in item:
|
|
klu.set_indicators(item)
|
|
return klu_list
|
|
|
|
def get_kl_data(self, dataframe:DataFrame):
|
|
return self.cal_kl_data(dataframe)
|
|
|
|
def _push_klu_into_klc_list(self, klc_list, klu, last_klu):
|
|
"""把一根 KLU 并入包含K线列表。与 get_klc_list 的几何规则相同。"""
|
|
if len(klc_list) > 0:
|
|
last_klc = klc_list[-1]
|
|
if klu.exception:
|
|
ddir = Chan_KLINE_DIR.DOWN
|
|
if last_klc.high < klu.high:
|
|
ddir = Chan_KLINE_DIR.UP
|
|
klc = ChanKLC(klu, index=len(klc_list), ddir=ddir)
|
|
klc.high = klu.close if klu.close > klu.open else klu.open
|
|
klc.low = klu.open if klu.close > klu.open else klu.close
|
|
klc_list.append(klc)
|
|
last_klc.set_next(klc)
|
|
klc.set_pre(last_klc)
|
|
last_klc.set_end_klu(last_klu)
|
|
klc.set_pre_fx()
|
|
else:
|
|
included = last_klc.check_klu_included(klu)
|
|
if not included:
|
|
ddir = Chan_KLINE_DIR.DOWN
|
|
if last_klc.high < klu.high:
|
|
ddir = Chan_KLINE_DIR.UP
|
|
klc = ChanKLC(klu, index=len(klc_list), ddir=ddir)
|
|
klc_list.append(klc)
|
|
last_klc.set_next(klc)
|
|
klc.set_pre(last_klc)
|
|
last_klc.set_end_klu(last_klu)
|
|
klc.set_pre_fx()
|
|
else:
|
|
last_klc.add_klu(klu)
|
|
else:
|
|
ddir = Chan_KLINE_DIR.UP
|
|
if klu.open > klu.close:
|
|
ddir = Chan_KLINE_DIR.DOWN
|
|
klc = ChanKLC(klu, 0, ddir)
|
|
klc_list.append(klc)
|
|
|
|
def get_klc_list(self, klu_list):
|
|
klc_list = []
|
|
last_klu = None
|
|
# ChanMACD.__init__ 已调用 cal_macd_state,切勿再调一次(会重复堆积 seg/unittf)
|
|
macd = ChanMACD(klu_list)
|
|
klu_list = macd.klu_list
|
|
self._last_chan_macd = macd
|
|
ema_up_list = []
|
|
ema_down_list = []
|
|
ema_up_count = 0
|
|
ema_down_count = 0
|
|
last_klu = None
|
|
for klu in klu_list:
|
|
ema = klu.ema52
|
|
last_ema = last_klu.ema52 if last_klu else 0
|
|
if klu.close >= ema:
|
|
ema_up_count += 1
|
|
elif klu.close < ema:
|
|
ema_down_count += 1
|
|
if last_klu and last_klu.close >= last_ema and klu.close < ema:
|
|
ema_up_list.append(ema_up_count)
|
|
#print(last_klu.time, ema_up_count, "UP END")
|
|
ema_up_count = 0
|
|
elif last_klu and last_klu.close < last_ema and klu.close >= ema:
|
|
ema_down_list.append(ema_down_count)
|
|
#print(last_klu.time, ema_down_count, "DOWN END")
|
|
ema_down_count = 0
|
|
self._push_klu_into_klc_list(klc_list, klu, last_klu)
|
|
last_klu = klu
|
|
klc_list = self.cal_trend(klc_list)
|
|
#print(ema52_up_list, ema52_down_list)
|
|
return klc_list
|
|
|
|
|
|
def get_klu_list(self, dataframe):
|
|
klu_list = self.get_kl_data(dataframe)
|
|
#klu_list = self.cal_klu_pattern(klu_list)
|
|
return klu_list
|
|
|
|
def cal_klu_pattern(self, klu_list):
|
|
"""
|
|
计算裸K的pattern - 识别反转形态
|
|
"""
|
|
if not klu_list or len(klu_list) < 3:
|
|
return klu_list
|
|
|
|
for i, klu in enumerate(klu_list):
|
|
# 单根K线反转模式识别
|
|
self._detect_single_reversal_pattern(klu)
|
|
|
|
# 双根K线形态识别
|
|
if i >= 1:
|
|
self._detect_double_pattern(klu_list[i-1], klu)
|
|
|
|
# 三根K线形态识别
|
|
if i >= 2:
|
|
self._detect_triple_pattern(klu_list[i-2], klu_list[i-1], klu)
|
|
|
|
#if klu.pattern != Chan_KLU_PATTERN.UNKNOWN:
|
|
#print(klu.time, klu.pattern, klu.lower_shadow_ratio, klu.upper_shadow_ratio, klu.body_ratio, klu.lower_shadow_ratio/klu.body_ratio, klu.upper_shadow_ratio/klu.body_ratio)
|
|
return klu_list
|
|
|
|
|
|
def _detect_single_reversal_pattern(self, klu):
|
|
"""检测单根K线反转模式"""
|
|
body = abs(klu.close - klu.open)
|
|
upper_shadow = klu.high - max(klu.close, klu.open)
|
|
lower_shadow = min(klu.close, klu.open) - klu.low
|
|
total_range = klu.high - klu.low
|
|
|
|
# 避免除零
|
|
if total_range == 0:
|
|
return
|
|
|
|
body_ratio = body / total_range
|
|
upper_ratio = upper_shadow / total_range
|
|
lower_ratio = lower_shadow / total_range
|
|
#print(klu.time, upper_ratio, lower_ratio, body_ratio, upper_ratio/body_ratio, lower_ratio/body_ratio)
|
|
# 避免body_ratio为0时的除零错误
|
|
if body_ratio == 0:
|
|
return
|
|
# 锤子线/上吊线 - 反转信号
|
|
if lower_ratio / body_ratio >= 2:
|
|
# 锤子线:底部反转,需要前面一段
|
|
if klu.close > klu.open and klu.pre:
|
|
klu.set_pattern(Chan_KLU_PATTERN.HAMMER) # 底部反转
|
|
# 上吊线:顶部反转,需要前一根是上涨趋势
|
|
elif klu.close < klu.open and klu.pre:
|
|
klu.set_pattern(Chan_KLU_PATTERN.HANGING_MAN) # 顶部反转
|
|
|
|
# 倒锤子线/射击之星 - 反转信号
|
|
elif upper_ratio / body_ratio >= 2:
|
|
# 倒锤子线:底部反转,需要前一根是下跌趋势
|
|
if klu.close > klu.open and klu.pre:
|
|
klu.set_pattern(Chan_KLU_PATTERN.INVERTED_HAMMER) # 底部反转
|
|
# 射击之星:顶部反转,需要前一根是上涨趋势
|
|
elif klu.close < klu.open and klu.pre:
|
|
klu.set_pattern(Chan_KLU_PATTERN.SHOOTING_STAR) # 顶部反转
|
|
|
|
# 十字星 - 反转信号
|
|
elif body_ratio <= 0.1:
|
|
if upper_ratio > 0.4 and lower_ratio > 0.4:
|
|
klu.set_pattern(Chan_KLU_PATTERN.LONG_LEGGED_DOJI) # 强烈反转信号
|
|
elif upper_ratio > 0.4 and lower_ratio <= 0.1:
|
|
# 墓碑十字星:顶部反转,需要前一根是上涨趋势
|
|
if klu.pre and klu.pre.close > klu.pre.open:
|
|
klu.set_pattern(Chan_KLU_PATTERN.GRAVESTONE_DOJI) # 顶部反转
|
|
elif lower_ratio > 0.4 and upper_ratio <= 0.1:
|
|
# 蜻蜓十字星:底部反转,需要前一根是下跌趋势
|
|
if klu.pre and klu.pre.close < klu.pre.open:
|
|
klu.set_pattern(Chan_KLU_PATTERN.DRAGONFLY_DOJI) # 底部反转
|
|
else:
|
|
klu.set_pattern(Chan_KLU_PATTERN.DOJI) # 一般反转信号
|
|
|
|
|
|
def _detect_double_pattern(self, prev_klu, curr_klu):
|
|
"""检测两根K线形成的形态
|
|
包括:吞没形态(看涨/看跌)、乌云盖顶、曙光初现
|
|
"""
|
|
# 如果前一根K线已经有形态,不再识别双K线形态
|
|
if prev_klu.pattern != Chan_KLU_PATTERN.UNKNOWN:
|
|
return
|
|
|
|
# 计算K线实体
|
|
prev_body = abs(prev_klu.close - prev_klu.open)
|
|
curr_body = abs(curr_klu.close - curr_klu.open)
|
|
|
|
# 判断K线颜色(阴阳)
|
|
prev_bullish = prev_klu.close > prev_klu.open
|
|
curr_bullish = curr_klu.close > curr_klu.open
|
|
|
|
# 检查是否存在长期趋势(至少需要5根K线的趋势)
|
|
def check_long_trend(klu, bullish_trend=True, min_bars=5):
|
|
"""检查是否存在长期趋势
|
|
bullish_trend=True: 检查上涨趋势
|
|
bullish_trend=False: 检查下跌趋势
|
|
min_bars: 最少需要多少根K线形成趋势
|
|
"""
|
|
if not klu or not klu.pre:
|
|
return False
|
|
return True
|
|
|
|
# 使用EMA指标判断长期趋势
|
|
if klu.ema52 > 0:
|
|
if bullish_trend and klu.close < klu.ema52:
|
|
return False
|
|
if not bullish_trend and klu.close > klu.ema52:
|
|
return False
|
|
|
|
# 检查连续的K线方向
|
|
count = 0
|
|
current = klu.pre
|
|
|
|
while current and count < min_bars:
|
|
if not current.pre:
|
|
break
|
|
|
|
if bullish_trend:
|
|
# 上涨趋势:当前收盘价高于前一根收盘价
|
|
if current.close <= current.pre.close:
|
|
break
|
|
else:
|
|
# 下跌趋势:当前收盘价低于前一根收盘价
|
|
if current.close >= current.pre.close:
|
|
break
|
|
|
|
count += 1
|
|
current = current.pre
|
|
|
|
return count >= min_bars
|
|
|
|
# 1. 看涨吞没形态:前阴后阳,后者完全吞没前者
|
|
# 要求前面有明显的下跌趋势
|
|
if not prev_bullish and curr_bullish and \
|
|
abs(curr_klu.open - prev_klu.close) < 10 and \
|
|
curr_klu.close > prev_klu.open and \
|
|
check_long_trend(prev_klu, bullish_trend=False, min_bars=5):
|
|
curr_klu.set_pattern(Chan_KLU_PATTERN.BULLISH_ENGULFING)
|
|
return
|
|
|
|
# 2. 看跌吞没形态:前阳后阴,后者完全吞没前者
|
|
# 要求前面有明显的上涨趋势
|
|
if prev_bullish and not curr_bullish and \
|
|
abs(curr_klu.open - prev_klu.close) < 10 and \
|
|
curr_klu.close < prev_klu.open and \
|
|
check_long_trend(prev_klu, bullish_trend=True, min_bars=5):
|
|
curr_klu.set_pattern(Chan_KLU_PATTERN.BEARISH_ENGULFING)
|
|
return
|
|
|
|
# 3. 乌云盖顶:前阳后阴,后者开盘价高于前者最高价,收盘价在前者实体中部以下
|
|
# 要求前面有明显的上涨趋势
|
|
if prev_bullish and not curr_bullish and \
|
|
curr_klu.open > prev_klu.high and \
|
|
curr_klu.close < (prev_klu.open + prev_klu.close) / 2 and \
|
|
curr_klu.close > prev_klu.open and \
|
|
check_long_trend(prev_klu, bullish_trend=True, min_bars=5):
|
|
curr_klu.set_pattern(Chan_KLU_PATTERN.DARK_CLOUD_COVER)
|
|
return
|
|
|
|
# 4. 曙光初现:前阴后阳,后者开盘价低于前者最低价,收盘价在前者实体中部以上
|
|
# 要求前面有明显的下跌趋势
|
|
if not prev_bullish and curr_bullish and \
|
|
curr_klu.open < prev_klu.low and \
|
|
curr_klu.close > (prev_klu.open + prev_klu.close) / 2 and \
|
|
curr_klu.close < prev_klu.open and \
|
|
check_long_trend(prev_klu, bullish_trend=False, min_bars=5):
|
|
curr_klu.set_pattern(Chan_KLU_PATTERN.PIERCING_LINE)
|
|
return
|
|
|
|
# 平顶和平底移至三根K线形态中判断
|
|
|
|
|
|
def _detect_triple_pattern(self, first_klu, second_klu, third_klu):
|
|
"""检测三根K线形成的形态
|
|
包括:早晨之星、黄昏之星、平顶、平底
|
|
"""
|
|
# 如果前两根K线已经有形态,不再识别三K线形态
|
|
if first_klu.pattern != Chan_KLU_PATTERN.UNKNOWN or \
|
|
second_klu.pattern != Chan_KLU_PATTERN.UNKNOWN:
|
|
return
|
|
|
|
# 判断K线颜色(阴阳)
|
|
first_bullish = first_klu.close > first_klu.open
|
|
second_bullish = second_klu.close > second_klu.open
|
|
third_bullish = third_klu.close > third_klu.open
|
|
|
|
# 计算实体大小
|
|
first_body = abs(first_klu.close - first_klu.open)
|
|
second_body = abs(second_klu.close - second_klu.open)
|
|
third_body = abs(third_klu.close - third_klu.open)
|
|
|
|
# 检查是否存在长期趋势(至少需要5根K线的趋势)
|
|
def check_long_trend(klu, bullish_trend=True, min_bars=5):
|
|
"""检查是否存在长期趋势
|
|
bullish_trend=True: 检查上涨趋势
|
|
bullish_trend=False: 检查下跌趋势
|
|
min_bars: 最少需要多少根K线形成趋势
|
|
"""
|
|
if not klu or not klu.pre:
|
|
return False
|
|
|
|
# 使用EMA指标判断长期趋势
|
|
if klu.ema52 > 0:
|
|
if bullish_trend and klu.close < klu.ema52:
|
|
return False
|
|
if not bullish_trend and klu.close > klu.ema52:
|
|
return False
|
|
|
|
# 检查连续的K线方向
|
|
count = 0
|
|
current = klu.pre
|
|
|
|
while current and count < min_bars:
|
|
if not current.pre:
|
|
break
|
|
|
|
if bullish_trend:
|
|
# 上涨趋势:当前收盘价高于前一根收盘价
|
|
if current.close <= current.pre.close:
|
|
break
|
|
else:
|
|
# 下跌趋势:当前收盘价低于前一根收盘价
|
|
if current.close >= current.pre.close:
|
|
break
|
|
|
|
count += 1
|
|
current = current.pre
|
|
|
|
return count >= min_bars
|
|
|
|
# 1. 早晨之星:第一根阴线,第二根十字星或小实体,第三根阳线
|
|
# 要求前面有明显的下跌趋势
|
|
if not first_bullish and third_bullish and \
|
|
second_body < first_body * 0.3 and \
|
|
third_body > first_body * 0.5 and \
|
|
max(second_klu.open, second_klu.close) < first_klu.close and \
|
|
min(second_klu.open, second_klu.close) < third_klu.open and \
|
|
third_klu.close > (first_klu.open + first_klu.close) / 2 and \
|
|
check_long_trend(first_klu, bullish_trend=False, min_bars=7):
|
|
third_klu.set_pattern(Chan_KLU_PATTERN.MORNING_STAR)
|
|
return
|
|
|
|
# 2. 黄昏之星:第一根阳线,第二根十字星或小实体,第三根阴线
|
|
# 要求前面有明显的上涨趋势
|
|
if first_bullish and not third_bullish and \
|
|
second_body < first_body * 0.3 and \
|
|
third_body > first_body * 0.5 and \
|
|
min(second_klu.open, second_klu.close) > first_klu.close and \
|
|
max(second_klu.open, second_klu.close) > third_klu.open and \
|
|
third_klu.close < (first_klu.open + first_klu.close) / 2 and \
|
|
check_long_trend(first_klu, bullish_trend=True, min_bars=7):
|
|
third_klu.set_pattern(Chan_KLU_PATTERN.EVENING_STAR)
|
|
return
|
|
|
|
# 3. 平顶:三根K线的最高点几乎相同(上升趋势中更有意义)
|
|
# 要求前面有明显的上涨趋势
|
|
if (abs(first_klu.high - second_klu.high) / first_klu.high < 0.0002 and
|
|
abs(second_klu.high - third_klu.high) / second_klu.high < 0.0002 and
|
|
check_long_trend(first_klu, bullish_trend=True, min_bars=7)):
|
|
# 额外确认:价格接近阻力位或关键技术指标
|
|
is_near_resistance = False
|
|
|
|
# 检查是否接近EMA52阻力位
|
|
if first_klu.ema52 > 0:
|
|
resistance_level = first_klu.ema52
|
|
if abs(first_klu.high - resistance_level) / resistance_level < 0.01:
|
|
is_near_resistance = True
|
|
|
|
# 检查是否有成交量确认(成交量减少表示上涨动能减弱)
|
|
volume_confirmation = False
|
|
if (first_klu.volume > 0 and second_klu.volume > 0 and third_klu.volume > 0 and
|
|
third_klu.volume < second_klu.volume and second_klu.volume < first_klu.volume):
|
|
volume_confirmation = True
|
|
|
|
if is_near_resistance or volume_confirmation:
|
|
third_klu.set_pattern(Chan_KLU_PATTERN.TWEEZER_TOP)
|
|
return
|
|
|
|
# 4. 平底:三根K线的最低点几乎相同(下降趋势中更有意义)
|
|
# 要求前面有明显的下跌趋势
|
|
if (abs(first_klu.low - second_klu.low) / first_klu.low < 0.0002 and
|
|
abs(second_klu.low - third_klu.low) / second_klu.low < 0.0002 and
|
|
check_long_trend(first_klu, bullish_trend=False, min_bars=7)):
|
|
# 额外确认:价格接近支撑位或关键技术指标
|
|
is_near_support = False
|
|
|
|
# 检查是否接近EMA52支撑位
|
|
if first_klu.ema52 > 0:
|
|
support_level = first_klu.ema52
|
|
if abs(first_klu.low - support_level) / support_level < 0.01:
|
|
is_near_support = True
|
|
|
|
# 检查是否有成交量确认(成交量减少表示下跌动能减弱)
|
|
volume_confirmation = False
|
|
if (first_klu.volume > 0 and second_klu.volume > 0 and third_klu.volume > 0 and
|
|
third_klu.volume < second_klu.volume and second_klu.volume < first_klu.volume):
|
|
volume_confirmation = True
|
|
|
|
if is_near_support or volume_confirmation:
|
|
third_klu.set_pattern(Chan_KLU_PATTERN.TWEEZER_BOTTOM)
|
|
return
|
|
|